diff --git a/src/NzbDrone.Api/Calendar/CalendarModule.cs b/src/NzbDrone.Api/Calendar/CalendarModule.cs index 484d3a52b..79fabf7ed 100644 --- a/src/NzbDrone.Api/Calendar/CalendarModule.cs +++ b/src/NzbDrone.Api/Calendar/CalendarModule.cs @@ -3,19 +3,12 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Api.Episodes; using NzbDrone.Api.Extensions; -using NzbDrone.Api.Mapping; -using NzbDrone.Core.Datastore.Events; -using NzbDrone.Core.Download; -using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Commands; -using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; namespace NzbDrone.Api.Calendar { - public class CalendarModule : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>, - IHandle<EpisodeGrabbedEvent>, - IHandle<EpisodeDownloadedEvent> + public class CalendarModule : EpisodeModuleWithSignalR { private readonly IEpisodeService _episodeService; private readonly SeriesRepository _seriesRepository; @@ -23,18 +16,12 @@ namespace NzbDrone.Api.Calendar public CalendarModule(ICommandExecutor commandExecutor, IEpisodeService episodeService, SeriesRepository seriesRepository) - : base(commandExecutor, "calendar") + : base(episodeService, commandExecutor, "calendar") { _episodeService = episodeService; _seriesRepository = seriesRepository; GetResourceAll = GetCalendar; - GetResourceById = GetEpisode; - } - - private EpisodeResource GetEpisode(int id) - { - return _episodeService.GetEpisode(id).InjectTo<EpisodeResource>(); } private List<EpisodeResource> GetCalendar() @@ -53,24 +40,5 @@ namespace NzbDrone.Api.Calendar return resources.OrderBy(e => e.AirDateUtc).ToList(); } - - public void Handle(EpisodeGrabbedEvent message) - { - foreach (var episode in message.Episode.Episodes) - { - var resource = episode.InjectTo<EpisodeResource>(); - resource.Grabbed = true; - - BroadcastResourceChange(ModelAction.Updated, resource); - } - } - - public void Handle(EpisodeDownloadedEvent message) - { - foreach (var episode in message.Episode.Episodes) - { - BroadcastResourceChange(ModelAction.Updated, episode.Id); - } - } } } diff --git a/src/NzbDrone.Api/Episodes/EpisodeModule.cs b/src/NzbDrone.Api/Episodes/EpisodeModule.cs index 4e24cb78f..e698ab242 100644 --- a/src/NzbDrone.Api/Episodes/EpisodeModule.cs +++ b/src/NzbDrone.Api/Episodes/EpisodeModule.cs @@ -1,24 +1,16 @@ using System.Collections.Generic; -using NzbDrone.Api.Mapping; using NzbDrone.Api.REST; -using NzbDrone.Core.Datastore.Events; -using NzbDrone.Core.Download; -using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Commands; -using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; namespace NzbDrone.Api.Episodes { - public class EpisodeModule : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>, - IHandle<EpisodeGrabbedEvent>, - IHandle<EpisodeDownloadedEvent> - + public class EpisodeModule : EpisodeModuleWithSignalR { private readonly IEpisodeService _episodeService; public EpisodeModule(ICommandExecutor commandExecutor, IEpisodeService episodeService) - : base(commandExecutor) + : base(episodeService, commandExecutor) { _episodeService = episodeService; @@ -43,29 +35,5 @@ namespace NzbDrone.Api.Episodes { _episodeService.SetEpisodeMonitored(episodeResource.Id, episodeResource.Monitored); } - - private EpisodeResource GetEpisode(int id) - { - return _episodeService.GetEpisode(id).InjectTo<EpisodeResource>(); - } - - public void Handle(EpisodeGrabbedEvent message) - { - foreach (var episode in message.Episode.Episodes) - { - var resource = episode.InjectTo<EpisodeResource>(); - resource.Grabbed = true; - - BroadcastResourceChange(ModelAction.Updated, resource); - } - } - - public void Handle(EpisodeDownloadedEvent message) - { - foreach (var episode in message.Episode.Episodes) - { - BroadcastResourceChange(ModelAction.Updated, episode.Id); - } - } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Episodes/EpisodeModuleWithSignalR.cs b/src/NzbDrone.Api/Episodes/EpisodeModuleWithSignalR.cs new file mode 100644 index 000000000..7612a9601 --- /dev/null +++ b/src/NzbDrone.Api/Episodes/EpisodeModuleWithSignalR.cs @@ -0,0 +1,55 @@ +using NzbDrone.Api.Mapping; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Download; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Api.Episodes +{ + public abstract class EpisodeModuleWithSignalR : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>, + IHandle<EpisodeGrabbedEvent>, + IHandle<EpisodeDownloadedEvent> + { + private readonly IEpisodeService _episodeService; + + protected EpisodeModuleWithSignalR(IEpisodeService episodeService, ICommandExecutor commandExecutor) + : base(commandExecutor) + { + _episodeService = episodeService; + + GetResourceById = GetEpisode; + } + + protected EpisodeModuleWithSignalR(IEpisodeService episodeService, ICommandExecutor commandExecutor, string resource) + : base(commandExecutor, resource) + { + _episodeService = episodeService; + } + + protected EpisodeResource GetEpisode(int id) + { + return _episodeService.GetEpisode(id).InjectTo<EpisodeResource>(); + } + + public void Handle(EpisodeGrabbedEvent message) + { + foreach (var episode in message.Episode.Episodes) + { + var resource = episode.InjectTo<EpisodeResource>(); + resource.Grabbed = true; + + BroadcastResourceChange(ModelAction.Updated, resource); + } + } + + public void Handle(EpisodeDownloadedEvent message) + { + foreach (var episode in message.Episode.Episodes) + { + BroadcastResourceChange(ModelAction.Updated, episode.Id); + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs b/src/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs index 1ac4ae86c..28cecd695 100644 --- a/src/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs +++ b/src/NzbDrone.Api/Extensions/Pipelines/CacheHeaderPipeline.cs @@ -13,7 +13,10 @@ namespace NzbDrone.Api.Extensions.Pipelines private void Handle(NancyContext context) { - context.Response.Headers.Add("X-ApplicationVersion", BuildInfo.Version.ToString()); + if (!context.Response.Headers.ContainsKey("X-ApplicationVersion")) + { + context.Response.Headers.Add("X-ApplicationVersion", BuildInfo.Version.ToString()); + } } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Frontend/Mappers/LogFileMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/LogFileMapper.cs index bc7124a97..588775839 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/LogFileMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/LogFileMapper.cs @@ -6,11 +6,11 @@ using NzbDrone.Common.EnvironmentInfo; namespace NzbDrone.Api.Frontend.Mappers { - public class LogFileMapper : StaticResourceMapperBase + public class UpdateLogFileMapper : StaticResourceMapperBase { private readonly IAppFolderInfo _appFolderInfo; - public LogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger) + public UpdateLogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger) : base(diskProvider, logger) { _appFolderInfo = appFolderInfo; @@ -21,12 +21,12 @@ namespace NzbDrone.Api.Frontend.Mappers var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = Path.GetFileName(path); - return Path.Combine(_appFolderInfo.GetLogFolder(), path); + return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), path); } public override bool CanHandle(string resourceUrl) { - return resourceUrl.StartsWith("/logfile/") && resourceUrl.EndsWith(".txt"); + return resourceUrl.StartsWith("/updatelogfile/") && resourceUrl.EndsWith(".txt"); } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Frontend/Mappers/UpdateLogFileMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/UpdateLogFileMapper.cs new file mode 100644 index 000000000..bc7124a97 --- /dev/null +++ b/src/NzbDrone.Api/Frontend/Mappers/UpdateLogFileMapper.cs @@ -0,0 +1,32 @@ +using System.IO; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.Disk; +using NzbDrone.Common.EnvironmentInfo; + +namespace NzbDrone.Api.Frontend.Mappers +{ + public class LogFileMapper : StaticResourceMapperBase + { + private readonly IAppFolderInfo _appFolderInfo; + + public LogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger) + : base(diskProvider, logger) + { + _appFolderInfo = appFolderInfo; + } + + protected override string Map(string resourceUrl) + { + var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); + path = Path.GetFileName(path); + + return Path.Combine(_appFolderInfo.GetLogFolder(), path); + } + + public override bool CanHandle(string resourceUrl) + { + return resourceUrl.StartsWith("/logfile/") && resourceUrl.EndsWith(".txt"); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Logs/LogFileModule.cs b/src/NzbDrone.Api/Logs/LogFileModule.cs index b44e77b6d..aaa8797d3 100644 --- a/src/NzbDrone.Api/Logs/LogFileModule.cs +++ b/src/NzbDrone.Api/Logs/LogFileModule.cs @@ -1,63 +1,43 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using NzbDrone.Common; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; -using Nancy; -using Nancy.Responses; +using NzbDrone.Core.Configuration; namespace NzbDrone.Api.Logs { - public class LogFileModule : NzbDroneRestModule<LogFileResource> + public class LogFileModule : LogFileModuleBase { - private const string LOGFILE_ROUTE = @"/(?<filename>nzbdrone(?:\.\d+)?\.txt)"; - private readonly IAppFolderInfo _appFolderInfo; private readonly IDiskProvider _diskProvider; public LogFileModule(IAppFolderInfo appFolderInfo, - IDiskProvider diskProvider) - : base("log/file") + IDiskProvider diskProvider, + IConfigFileProvider configFileProvider) + : base(diskProvider, configFileProvider, "") { _appFolderInfo = appFolderInfo; _diskProvider = diskProvider; - GetResourceAll = GetLogFiles; - - Get[LOGFILE_ROUTE] = options => GetLogFile(options.filename); } - private List<LogFileResource> GetLogFiles() + protected override IEnumerable<string> GetLogFiles() { - var result = new List<LogFileResource>(); - - var files = _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), SearchOption.TopDirectoryOnly); + return _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), SearchOption.TopDirectoryOnly); + } - for (int i = 0; i < files.Length; i++) + protected override string GetLogFilePath(string filename) + { + return Path.Combine(_appFolderInfo.GetLogFolder(), filename); + } + + protected override string DownloadUrlRoot + { + get { - var file = files[i]; - - result.Add(new LogFileResource - { - Id = i + 1, - Filename = Path.GetFileName(file), - LastWriteTime = _diskProvider.FileGetLastWriteUtc(file) - }); + return "logfile"; } - - return result.OrderByDescending(l => l.LastWriteTime).ToList(); } - private Response GetLogFile(string filename) - { - var filePath = Path.Combine(_appFolderInfo.GetLogFolder(), filename); - - if (!_diskProvider.FileExists(filePath)) - return new NotFoundResponse(); - - var data = _diskProvider.ReadAllText(filePath); - - return new TextResponse(data); - } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Logs/LogFileModuleBase.cs b/src/NzbDrone.Api/Logs/LogFileModuleBase.cs new file mode 100644 index 000000000..3f1a531d6 --- /dev/null +++ b/src/NzbDrone.Api/Logs/LogFileModuleBase.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NzbDrone.Common.Disk; +using Nancy; +using Nancy.Responses; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Api.Logs +{ + public abstract class LogFileModuleBase : NzbDroneRestModule<LogFileResource> + { + protected const string LOGFILE_ROUTE = @"/(?<filename>[-.a-zA-Z0-9]+?\.txt)"; + + private readonly IDiskProvider _diskProvider; + private readonly IConfigFileProvider _configFileProvider; + + public LogFileModuleBase(IDiskProvider diskProvider, + IConfigFileProvider configFileProvider, + String route) + : base("log/file" + route) + { + _diskProvider = diskProvider; + _configFileProvider = configFileProvider; + GetResourceAll = GetLogFilesResponse; + + Get[LOGFILE_ROUTE] = options => GetLogFileResponse(options.filename); + } + + private List<LogFileResource> GetLogFilesResponse() + { + var result = new List<LogFileResource>(); + + var files = GetLogFiles().ToList(); + + for (int i = 0; i < files.Count; i++) + { + var file = files[i]; + var filename = Path.GetFileName(file); + + result.Add(new LogFileResource + { + Id = i + 1, + Filename = filename, + LastWriteTime = _diskProvider.FileGetLastWriteUtc(file), + ContentsUrl = String.Format("{0}/api/{1}/{2}", _configFileProvider.UrlBase, Resource, filename), + DownloadUrl = String.Format("{0}/{1}/{2}", _configFileProvider.UrlBase, DownloadUrlRoot, filename) + }); + } + + return result.OrderByDescending(l => l.LastWriteTime).ToList(); + } + + private Response GetLogFileResponse(String filename) + { + var filePath = GetLogFilePath(filename); + + if (!_diskProvider.FileExists(filePath)) + return new NotFoundResponse(); + + var data = _diskProvider.ReadAllText(filePath); + + return new TextResponse(data); + } + + protected abstract IEnumerable<String> GetLogFiles(); + protected abstract String GetLogFilePath(String filename); + + protected abstract String DownloadUrlRoot { get; } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Logs/LogFileResource.cs b/src/NzbDrone.Api/Logs/LogFileResource.cs index 9d2847e89..8109eeb07 100644 --- a/src/NzbDrone.Api/Logs/LogFileResource.cs +++ b/src/NzbDrone.Api/Logs/LogFileResource.cs @@ -7,5 +7,7 @@ namespace NzbDrone.Api.Logs { public String Filename { get; set; } public DateTime LastWriteTime { get; set; } + public String ContentsUrl { get; set; } + public String DownloadUrl { get; set; } } } diff --git a/src/NzbDrone.Api/Logs/UpdateLogFileModule.cs b/src/NzbDrone.Api/Logs/UpdateLogFileModule.cs new file mode 100644 index 000000000..3c91173ce --- /dev/null +++ b/src/NzbDrone.Api/Logs/UpdateLogFileModule.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using NzbDrone.Common; +using NzbDrone.Common.Disk; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Api.Logs +{ + public class UpdateLogFileModule : LogFileModuleBase + { + private readonly IAppFolderInfo _appFolderInfo; + private readonly IDiskProvider _diskProvider; + + public UpdateLogFileModule(IAppFolderInfo appFolderInfo, + IDiskProvider diskProvider, + IConfigFileProvider configFileProvider) + : base(diskProvider, configFileProvider, "/update") + { + _appFolderInfo = appFolderInfo; + _diskProvider = diskProvider; + } + + protected override IEnumerable<String> GetLogFiles() + { + return _diskProvider.GetFiles(_appFolderInfo.GetUpdateLogFolder(), SearchOption.TopDirectoryOnly) + .Where(f => Regex.IsMatch(Path.GetFileName(f), LOGFILE_ROUTE.TrimStart('/'), RegexOptions.IgnoreCase)) + .ToList(); + } + + protected override String GetLogFilePath(String filename) + { + return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), filename); + } + + protected override String DownloadUrlRoot + { + get + { + return "updatelogfile"; + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 0c23ee731..21a84886e 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -117,6 +117,7 @@ <Compile Include="EpisodeFiles\EpisodeFileResource.cs" /> <Compile Include="Directories\DirectoryLookupService.cs" /> <Compile Include="Directories\DirectoryModule.cs" /> + <Compile Include="Episodes\EpisodeModuleWithSignalR.cs" /> <Compile Include="Episodes\EpisodeModule.cs" /> <Compile Include="Episodes\EpisodeResource.cs" /> <Compile Include="Episodes\RenameEpisodeModule.cs" /> @@ -129,6 +130,7 @@ <Compile Include="Extensions\NancyJsonSerializer.cs" /> <Compile Include="Extensions\RequestExtensions.cs" /> <Compile Include="Frontend\IsCacheableSpecification.cs" /> + <Compile Include="Frontend\Mappers\UpdateLogFileMapper.cs" /> <Compile Include="Frontend\Mappers\FaviconMapper.cs" /> <Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" /> <Compile Include="Frontend\Mappers\LogFileMapper.cs" /> @@ -141,6 +143,8 @@ <Compile Include="Health\HealthModule.cs" /> <Compile Include="History\HistoryResource.cs" /> <Compile Include="History\HistoryModule.cs" /> + <Compile Include="Logs\LogFileModuleBase.cs" /> + <Compile Include="Logs\UpdateLogFileModule.cs" /> <Compile Include="MediaCovers\MediaCoverModule.cs" /> <Compile Include="Metadata\MetadataResource.cs" /> <Compile Include="Metadata\MetadataModule.cs" /> diff --git a/src/NzbDrone.Api/Wanted/CutoffModule.cs b/src/NzbDrone.Api/Wanted/CutoffModule.cs index ee0b9f219..da2e44ef7 100644 --- a/src/NzbDrone.Api/Wanted/CutoffModule.cs +++ b/src/NzbDrone.Api/Wanted/CutoffModule.cs @@ -2,17 +2,18 @@ using NzbDrone.Api.Episodes; using NzbDrone.Api.Extensions; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Tv; namespace NzbDrone.Api.Wanted { - public class CutoffModule : NzbDroneRestModule<EpisodeResource> + public class CutoffModule : EpisodeModuleWithSignalR { private readonly IEpisodeCutoffService _episodeCutoffService; - private readonly SeriesRepository _seriesRepository; + private readonly ISeriesRepository _seriesRepository; - public CutoffModule(IEpisodeCutoffService episodeCutoffService, SeriesRepository seriesRepository) - :base("wanted/cutoff") + public CutoffModule(IEpisodeService episodeService, IEpisodeCutoffService episodeCutoffService, ISeriesRepository seriesRepository, ICommandExecutor commandExecutor) + :base(episodeService, commandExecutor, "wanted/cutoff") { _episodeCutoffService = episodeCutoffService; _seriesRepository = seriesRepository; diff --git a/src/NzbDrone.Api/Wanted/MissingModule.cs b/src/NzbDrone.Api/Wanted/MissingModule.cs index dd4d97f69..14fef9104 100644 --- a/src/NzbDrone.Api/Wanted/MissingModule.cs +++ b/src/NzbDrone.Api/Wanted/MissingModule.cs @@ -2,17 +2,18 @@ using NzbDrone.Api.Episodes; using NzbDrone.Api.Extensions; using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Tv; namespace NzbDrone.Api.Wanted { - public class MissingModule : NzbDroneRestModule<EpisodeResource> + public class MissingModule : EpisodeModuleWithSignalR { private readonly IEpisodeService _episodeService; - private readonly SeriesRepository _seriesRepository; + private readonly ISeriesRepository _seriesRepository; - public MissingModule(IEpisodeService episodeService, SeriesRepository seriesRepository) - :base("wanted/missing") + public MissingModule(IEpisodeService episodeService, ISeriesRepository seriesRepository, ICommandExecutor commandExecutor) + :base(episodeService, commandExecutor, "wanted/missing") { _episodeService = episodeService; _seriesRepository = seriesRepository; diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index e99382894..6dd047f4c 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -42,9 +42,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget { var url = remoteEpisode.Release.DownloadUrl; var title = remoteEpisode.Release.Title + ".nzb"; - - string category = Settings.TvCategory; - int priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority; + var category = Settings.TvCategory; + var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority; _logger.Info("Adding report [{0}] to the queue.", title); @@ -271,10 +270,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget _proxy.GetVersion(settings); var config = _proxy.GetConfig(settings); - var categories = GetCategories(config); - if (!categories.Any(v => v.Name == settings.TvCategory)) + if (!settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == settings.TvCategory)) { throw new ApplicationException("Category does not exist"); } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index e14c15336..5c5a05e31 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -222,7 +222,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { var categories = _proxy.GetCategories(settings); - if (!categories.Any(v => v == settings.TvCategory)) + if (!settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v == settings.TvCategory)) { throw new ApplicationException("Category does not exist"); } diff --git a/src/NzbDrone.Core/Instrumentation/Commands/DeleteUpdateLogFilesCommand.cs b/src/NzbDrone.Core/Instrumentation/Commands/DeleteUpdateLogFilesCommand.cs new file mode 100644 index 000000000..a55e91502 --- /dev/null +++ b/src/NzbDrone.Core/Instrumentation/Commands/DeleteUpdateLogFilesCommand.cs @@ -0,0 +1,15 @@ +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.Instrumentation.Commands +{ + public class DeleteUpdateLogFilesCommand : Command + { + public override bool SendUpdatesToClient + { + get + { + return true; + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs b/src/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs index 25ae79cad..e2e31ca7b 100644 --- a/src/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs +++ b/src/NzbDrone.Core/Instrumentation/DeleteLogFilesService.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using NLog; using NzbDrone.Common; using NzbDrone.Common.Disk; @@ -12,7 +13,7 @@ namespace NzbDrone.Core.Instrumentation { } - public class DeleteLogFilesService : IDeleteLogFilesService, IExecute<DeleteLogFilesCommand> + public class DeleteLogFilesService : IDeleteLogFilesService, IExecute<DeleteLogFilesCommand>, IExecute<DeleteUpdateLogFilesCommand> { private readonly IDiskProvider _diskProvider; private readonly IAppFolderInfo _appFolderInfo; @@ -27,12 +28,14 @@ namespace NzbDrone.Core.Instrumentation public void Execute(DeleteLogFilesCommand message) { - var logFiles = _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), SearchOption.TopDirectoryOnly); + _logger.Debug("Deleting all files in: {0}", _appFolderInfo.GetLogFolder()); + _diskProvider.EmptyFolder(_appFolderInfo.GetLogFolder()); + } - foreach (var logFile in logFiles) - { - _diskProvider.DeleteFile(logFile); - } + public void Execute(DeleteUpdateLogFilesCommand message) + { + _logger.Debug("Deleting all files in: {0}", _appFolderInfo.GetUpdateLogFolder()); + _diskProvider.EmptyFolder(_appFolderInfo.GetUpdateLogFolder()); } } } diff --git a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs index 060f3c3ba..6a81ee4c8 100644 --- a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs @@ -73,8 +73,9 @@ namespace NzbDrone.Core.MediaFiles private IEnumerable<RenameEpisodeFilePreview> GetPreviews(Series series, List<Episode> episodes, List<EpisodeFile> files) { - foreach (var file in files) + foreach (var f in files) { + var file = f; var episodesInFile = episodes.Where(e => e.EpisodeFileId == file.Id).ToList(); if (!episodesInFile.Any()) @@ -95,18 +96,13 @@ namespace NzbDrone.Core.MediaFiles SeasonNumber = seasonNumber, EpisodeNumbers = episodesInFile.Select(e => e.EpisodeNumber).ToList(), EpisodeFileId = file.Id, - ExistingPath = GetRelativePath(series.Path, file.Path), - NewPath = GetRelativePath(series.Path, newPath) + ExistingPath = series.Path.GetRelativePath(file.Path), + NewPath = series.Path.GetRelativePath(newPath) }; } } } - private string GetRelativePath(string seriesPath, string path) - { - return path.Substring(seriesPath.Length + 1); - } - private void RenameFiles(List<EpisodeFile> episodeFiles, Series series) { var renamed = new List<EpisodeFile>(); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 8b70b8ca6..97f8093a6 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -334,6 +334,7 @@ <Compile Include="Indexers\RssSyncCommand.cs" /> <Compile Include="Indexers\XElementExtensions.cs" /> <Compile Include="Instrumentation\Commands\ClearLogCommand.cs" /> + <Compile Include="Instrumentation\Commands\DeleteUpdateLogFilesCommand.cs" /> <Compile Include="Instrumentation\Commands\DeleteLogFilesCommand.cs" /> <Compile Include="Instrumentation\Commands\TrimLogCommand.cs" /> <Compile Include="Instrumentation\DeleteLogFilesService.cs" /> diff --git a/src/UI/AddSeries/AddSeriesLayout.js b/src/UI/AddSeries/AddSeriesLayout.js index e6e265abe..b51245d3a 100644 --- a/src/UI/AddSeries/AddSeriesLayout.js +++ b/src/UI/AddSeries/AddSeriesLayout.js @@ -55,7 +55,7 @@ define( _importSeries: function () { this.rootFolderLayout = new RootFolderLayout(); - this.rootFolderLayout.on('folderSelected', this._folderSelected, this); + this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected); AppLayout.modalRegion.show(this.rootFolderLayout); }, diff --git a/src/UI/AddSeries/SearchResultViewTemplate.html b/src/UI/AddSeries/SearchResultViewTemplate.html index d87a7e126..9fa74a539 100644 --- a/src/UI/AddSeries/SearchResultViewTemplate.html +++ b/src/UI/AddSeries/SearchResultViewTemplate.html @@ -10,9 +10,14 @@ <div class="row"> <h2 class="series-title"> {{titleWithYear}} - {{#unless_eq status compare="continuing"}} - <span class="label label-important">Ended</span> - {{/unless_eq}} + + <span class="labels"> + <span class="label label-primary">{{network}}</span> + {{#unless_eq status compare="continuing"}} + <span class="label label-danger">Ended</span> + {{/unless_eq}} + </span> + </h2> </div> <div class="row new-series-overview x-overview"> diff --git a/src/UI/AddSeries/addSeries.less b/src/UI/AddSeries/addSeries.less index 01a3b19fd..d0fdf3579 100644 --- a/src/UI/AddSeries/addSeries.less +++ b/src/UI/AddSeries/addSeries.less @@ -40,9 +40,13 @@ padding-bottom : 20px; .series-title { - .label { - margin-left: 15px; - vertical-align: middle; + .labels { + margin-left : 10px; + + .label { + font-size : 12px; + vertical-align : middle; + } } .year { diff --git a/src/UI/Calendar/CalendarFeedView.js b/src/UI/Calendar/CalendarFeedView.js index e69895af9..cf18cb711 100644 --- a/src/UI/Calendar/CalendarFeedView.js +++ b/src/UI/Calendar/CalendarFeedView.js @@ -14,7 +14,7 @@ define( }, templateHelpers: { - icalHttpUrl : window.location.protocol + '//' + window.location.host + StatusModel.get('urlBase') + '/feed/calendar/NzbDrone.ics', + icalHttpUrl : window.location.protocol + '//' + window.location.host + StatusModel.get('urlBase') + '/feed/calendar/NzbDrone.ics?apikey=' + window.NzbDrone.ApiKey, icalWebCalUrl : 'webcal://' + window.location.host + StatusModel.get('urlBase') + '/feed/calendar/NzbDrone.ics' }, diff --git a/src/UI/Calendar/CalendarFeedViewTemplate.html b/src/UI/Calendar/CalendarFeedViewTemplate.html index d920089b3..56ba9eb61 100644 --- a/src/UI/Calendar/CalendarFeedViewTemplate.html +++ b/src/UI/Calendar/CalendarFeedViewTemplate.html @@ -6,21 +6,26 @@ </div> <div class="modal-body edit-series-modal"> <div class="row"> - <div> + <div class="col-md-12"> <div class="form-horizontal"> - <div class="form-group"> - <label class="control-label">iCal feed</label> - <div class="controls ical-url"> - <div class="input-group"> - <input type="text" class="x-ical-url" value="{{icalHttpUrl}}" readonly="readonly" /> - <button class="btn btn-icon-only x-ical-copy" title="Copy to clipboard"><i class="icon-copy"></i></button> - <a class="btn btn-icon-only no-router" title="Subscribe" href="{{icalWebCalUrl}}" target="_blank"><i class="icon-calendar-empty"></i></a> - </div> - <span class="help-inline"> - <i class="icon-nd-form-info" title="Copy this url into your clients subscription form or use the subscribe button if your browser support webcal"/> - </span> + <div class="form-group"> + <label class="col-sm-3 control-label">iCal feed</label> + + <div class="col-sm-1 col-sm-push-8 help-inline"> + <i class="icon-nd-form-info" title="Copy this url into your clients subscription form or use the subscribe button if your browser support webcal"/> </div> + + <div class="col-sm-8 col-sm-pull-1"> + <div class="input-group ical-url"> + <input type="text" class="form-control x-ical-url" value="{{icalHttpUrl}}" readonly="readonly" /> + <div class="input-group-btn"> + <button class="btn btn-icon-only x-ical-copy" title="Copy to clipboard"><i class="icon-copy"></i></button> + <button class="btn btn-icon-only no-router"><a title="Subscribe" href="{{icalWebCalUrl}}" target="_blank"><i class="icon-calendar-empty"></i></a></button> + </div> + </div> + </div> + </div> </div> </div> diff --git a/src/UI/Calendar/calendar.less b/src/UI/Calendar/calendar.less index 3aedf3eef..5b6b1faf7 100644 --- a/src/UI/Calendar/calendar.less +++ b/src/UI/Calendar/calendar.less @@ -178,7 +178,6 @@ .ical-url { input { - width : 440px; - cursor : text; + cursor : text !important; } } diff --git a/src/UI/History/Blacklist/BlacklistLayout.js b/src/UI/History/Blacklist/BlacklistLayout.js index 8a6a74487..d8b214ca2 100644 --- a/src/UI/History/Blacklist/BlacklistLayout.js +++ b/src/UI/History/Blacklist/BlacklistLayout.js @@ -67,8 +67,9 @@ define( initialize: function () { this.collection = new BlacklistCollection({ tableName: 'blacklist' }); + this.listenTo(this.collection, 'sync', this._showTable); - vent.on(vent.Events.CommandComplete, this._commandComplete, this); + this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); }, onShow: function () { diff --git a/src/UI/Series/Details/SeriesDetailsLayout.js b/src/UI/Series/Details/SeriesDetailsLayout.js index 95d20c627..cceef6655 100644 --- a/src/UI/Series/Details/SeriesDetailsLayout.js +++ b/src/UI/Series/Details/SeriesDetailsLayout.js @@ -59,8 +59,7 @@ define( initialize: function () { this.listenTo(this.model, 'change:monitored', this._setMonitoredState); this.listenTo(vent, vent.Events.SeriesDeleted, this._onSeriesDeleted); - - vent.on(vent.Events.CommandComplete, this._commandComplete, this); + this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); }, onShow: function () { diff --git a/src/UI/Settings/General/GeneralView.js b/src/UI/Settings/General/GeneralView.js index b28ed659c..1b5aaaed7 100644 --- a/src/UI/Settings/General/GeneralView.js +++ b/src/UI/Settings/General/GeneralView.js @@ -31,7 +31,7 @@ define( }, initialize: function () { - vent.on(vent.Events.CommandComplete, this._commandComplete, this); + this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); }, onRender: function(){ diff --git a/src/UI/System/Logs/Files/ContentsModel.js b/src/UI/System/Logs/Files/ContentsModel.js index d109013dc..baf529561 100644 --- a/src/UI/System/Logs/Files/ContentsModel.js +++ b/src/UI/System/Logs/Files/ContentsModel.js @@ -1,12 +1,11 @@ 'use strict'; define( [ - 'backbone', - 'System/StatusModel' - ], function (Backbone, StatusModel) { + 'backbone' + ], function (Backbone) { return Backbone.Model.extend({ url: function () { - return StatusModel.get('urlBase') + '/api/log/file/' + this.get('filename'); + return this.get('contentsUrl'); }, parse: function (contents) { diff --git a/src/UI/System/Logs/Files/DownloadLogCell.js b/src/UI/System/Logs/Files/DownloadLogCell.js index 6ce544374..63dfb66f2 100644 --- a/src/UI/System/Logs/Files/DownloadLogCell.js +++ b/src/UI/System/Logs/Files/DownloadLogCell.js @@ -1,16 +1,15 @@ 'use strict'; define( [ - 'Cells/NzbDroneCell', - 'System/StatusModel' - ], function (NzbDroneCell, StatusModel) { + '../../../Cells/NzbDroneCell' + ], function (NzbDroneCell) { return NzbDroneCell.extend({ className: 'download-log-cell', render: function () { this.$el.empty(); - this.$el.html('<a href="{0}/logfile/{1}" class="no-router" target="_blank">Download</a>'.format(StatusModel.get('urlBase'), this.cellValue)); + this.$el.html('<a href="{0}" class="no-router" target="_blank">Download</a>'.format(this.cellValue)); return this; } diff --git a/src/UI/System/Logs/Files/FilenameCell.js b/src/UI/System/Logs/Files/FilenameCell.js index 3fefb67a5..feec6ed8b 100644 --- a/src/UI/System/Logs/Files/FilenameCell.js +++ b/src/UI/System/Logs/Files/FilenameCell.js @@ -1,7 +1,7 @@ 'use strict'; define( [ - 'Cells/NzbDroneCell' + '../../../Cells/NzbDroneCell' ], function (NzbDroneCell) { return NzbDroneCell.extend({ diff --git a/src/UI/System/Logs/Files/LogFileLayout.js b/src/UI/System/Logs/Files/LogFileLayout.js index 8cffc029e..75280515a 100644 --- a/src/UI/System/Logs/Files/LogFileLayout.js +++ b/src/UI/System/Logs/Files/LogFileLayout.js @@ -7,7 +7,6 @@ define( 'System/Logs/Files/FilenameCell', 'Cells/RelativeDateCell', 'System/Logs/Files/DownloadLogCell', - 'System/Logs/Files/LogFileCollection', 'System/Logs/Files/Row', 'System/Logs/Files/ContentsView', 'System/Logs/Files/ContentsModel', @@ -20,7 +19,6 @@ define( FilenameCell, RelativeDateCell, DownloadLogCell, - LogFileCollection, LogFileRow, ContentsView, ContentsModel, @@ -48,18 +46,19 @@ define( cell : RelativeDateCell }, { - name : 'filename', + name : 'downloadUrl', label : '', cell : DownloadLogCell, sortable: false } ], - initialize: function () { - this.collection = new LogFileCollection(); + initialize: function (options) { + this.collection = options.collection; + this.deleteFilesCommand = options.deleteFilesCommand; - vent.on(vent.Commands.ShowLogFile, this._fetchLogFileContents, this); - vent.on(vent.Events.CommandComplete, this._commandComplete, this); + this.listenTo(vent, vent.Commands.ShowLogFile, this._fetchLogFileContents); + this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); this.listenTo(this.collection, 'sync', this._collectionSynced); this.collection.fetch(); @@ -83,11 +82,10 @@ define( ownerContext : this, callback : this._refreshTable }, - { title : 'Delete Log Files', icon : 'icon-trash', - command : 'deleteLogFiles', + command : this.deleteFilesCommand, successMessage : 'Log files have been deleted', errorMessage : 'Failed to delete log files' } @@ -125,11 +123,7 @@ define( this.contents.show(new LoadingView()); var model = options.model; - var filename = model.get('filename'); - - var contentsModel = new ContentsModel({ - filename: filename - }); + var contentsModel = new ContentsModel(model.toJSON()); this.listenToOnce(contentsModel, 'sync', this._showDetails); @@ -151,7 +145,7 @@ define( }, _commandComplete: function (options) { - if (options.command.get('name') === 'deletelogfiles') { + if (options.command.get('name') === this.deleteFilesCommand.toLowerCase()) { this._refreshTable(); } } diff --git a/src/UI/System/Logs/Files/Row.js b/src/UI/System/Logs/Files/Row.js index 926869008..b506e214d 100644 --- a/src/UI/System/Logs/Files/Row.js +++ b/src/UI/System/Logs/Files/Row.js @@ -1,7 +1,7 @@ 'use strict'; define( [ - 'vent', + '../../../vent', 'backgrid' ], function (vent, Backgrid) { diff --git a/src/UI/System/Logs/LogsLayout.js b/src/UI/System/Logs/LogsLayout.js index 15511b431..a8c1cf0ac 100644 --- a/src/UI/System/Logs/LogsLayout.js +++ b/src/UI/System/Logs/LogsLayout.js @@ -3,24 +3,29 @@ define( [ 'marionette', 'System/Logs/Table/LogsTableLayout', - 'System/Logs/Files/LogFileLayout' - ], function (Marionette, LogsTableLayout, LogsFileLayout) { + 'System/Logs/Files/LogFileLayout', + 'System/Logs/Files/LogFileCollection', + 'System/Logs/Updates/LogFileCollection' + ], function (Marionette, LogsTableLayout, LogsFileLayout, LogFileCollection, UpdateLogFileCollection) { return Marionette.Layout.extend({ template: 'System/Logs/LogsLayoutTemplate', ui: { - tableTab: '.x-table-tab', - filesTab: '.x-files-tab' + tableTab : '.x-table-tab', + filesTab : '.x-files-tab', + updateFilesTab : '.x-update-files-tab' }, regions: { - table: '#table', - files: '#files' + table : '#table', + files : '#files', + updateFiles : '#update-files' }, events: { - 'click .x-table-tab': '_showTable', - 'click .x-files-tab': '_showFiles' + 'click .x-table-tab' : '_showTable', + 'click .x-files-tab' : '_showFiles', + 'click .x-update-files-tab' : '_showUpdateFiles' }, onShow: function () { @@ -42,7 +47,22 @@ define( } this.ui.filesTab.tab('show'); - this.files.show(new LogsFileLayout()); + this.files.show(new LogsFileLayout({ + collection: new LogFileCollection(), + deleteFilesCommand: 'deleteLogFiles' + })); + }, + + _showUpdateFiles: function (e) { + if (e) { + e.preventDefault(); + } + + this.ui.updateFilesTab.tab('show'); + this.updateFiles.show(new LogsFileLayout({ + collection: new UpdateLogFileCollection(), + deleteFilesCommand: 'deleteUpdateLogFiles' + })); } }); }); diff --git a/src/UI/System/Logs/LogsLayoutTemplate.html b/src/UI/System/Logs/LogsLayoutTemplate.html index aa250b933..a9832519a 100644 --- a/src/UI/System/Logs/LogsLayoutTemplate.html +++ b/src/UI/System/Logs/LogsLayoutTemplate.html @@ -1,15 +1,17 @@ <div class="row"> - <div class="col-md-1 col-sm-2"> + <div class="col-md-2 col-sm-2"> <ul class="nav nav-pills nav-stacked"> <li><a href="#table" class="x-table-tab no-router">Table</a></li> <li><a href="#files" class="x-files-tab no-router">Files</a></li> + <li><a href="#update-files" class="x-update-files-tab no-router">Updates</a></li> </ul> </div> - <div class="col-md-11 col-sm-10"> + <div class="col-md-10 col-sm-10"> <div class="tab-content"> <div class="tab-pane" id="table"></div> <div class="tab-pane" id="files"></div> + <div class="tab-pane" id="update-files"></div> </div> </div> </div> \ No newline at end of file diff --git a/src/UI/System/Logs/Table/LogsTableLayout.js b/src/UI/System/Logs/Table/LogsTableLayout.js index 841a5a92b..2e06e1b7d 100644 --- a/src/UI/System/Logs/Table/LogsTableLayout.js +++ b/src/UI/System/Logs/Table/LogsTableLayout.js @@ -61,7 +61,7 @@ define( this.collection = new LogCollection(); this.listenTo(this.collection, 'sync', this._showTable); - vent.on(vent.Events.CommandComplete, this._commandComplete, this); + this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); }, onRender: function () { diff --git a/src/UI/System/Logs/Updates/LogFileCollection.js b/src/UI/System/Logs/Updates/LogFileCollection.js new file mode 100644 index 000000000..9a6d928f9 --- /dev/null +++ b/src/UI/System/Logs/Updates/LogFileCollection.js @@ -0,0 +1,17 @@ +'use strict'; + +define( + [ + 'backbone', + 'System/Logs/Updates/LogFileModel' + ], function (Backbone, LogFileModel) { + return Backbone.Collection.extend({ + url : window.NzbDrone.ApiRoot + '/log/file/update', + model: LogFileModel, + + state: { + sortKey: 'lastWriteTime', + order : 1 + } + }); + }); diff --git a/src/UI/System/Logs/Updates/LogFileModel.js b/src/UI/System/Logs/Updates/LogFileModel.js new file mode 100644 index 000000000..17c0fd6da --- /dev/null +++ b/src/UI/System/Logs/Updates/LogFileModel.js @@ -0,0 +1,8 @@ +'use strict'; +define( + [ + 'backbone' + ], function (Backbone) { + return Backbone.Model.extend({ + }); + }); diff --git a/src/UI/System/SystemLayoutTemplate.html b/src/UI/System/SystemLayoutTemplate.html index c84df1ca7..e3b245715 100644 --- a/src/UI/System/SystemLayoutTemplate.html +++ b/src/UI/System/SystemLayoutTemplate.html @@ -4,10 +4,10 @@ <li><a href="#updates" class="x-updates-tab no-router">Updates</a></li> <li class="lifecycle-controls pull-right"> <div class="btn-group"> - <button class="btn btn-default btn-icon-only x-shutdown" title="Shutdown"> + <button class="btn btn-default btn-icon-only x-shutdown" title="Shutdown" data-container="body"> <i class="icon-nd-shutdown"></i> </button> - <button class="btn btn-default btn-icon-only x-restart" title="Restart"> + <button class="btn btn-default btn-icon-only x-restart" title="Restart" data-container="body"> <i class="icon-nd-restart"></i> </button> </div> diff --git a/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js b/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js index d01397bd4..df889f4f5 100644 --- a/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js +++ b/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js @@ -14,7 +14,8 @@ define([ 'Shared/LoadingView', 'Shared/Messenger', 'Commands/CommandController', - 'backgrid.selectall' + 'backgrid.selectall', + 'Mixins/backbone.signalr.mixin' ], function (_, Marionette, Backgrid, @@ -81,7 +82,7 @@ define([ ], initialize : function () { - this.collection = new CutoffUnmetCollection(); + this.collection = new CutoffUnmetCollection().bindSignalR({ updateOnly: true }); this.listenTo(this.collection, 'sync', this._showTable); }, diff --git a/src/UI/Wanted/Missing/MissingLayout.js b/src/UI/Wanted/Missing/MissingLayout.js index 45bfb9d4e..7e1cf6ae2 100644 --- a/src/UI/Wanted/Missing/MissingLayout.js +++ b/src/UI/Wanted/Missing/MissingLayout.js @@ -14,7 +14,8 @@ define([ 'Shared/LoadingView', 'Shared/Messenger', 'Commands/CommandController', - 'backgrid.selectall' + 'backgrid.selectall', + 'Mixins/backbone.signalr.mixin' ], function (_, Marionette, Backgrid, @@ -81,7 +82,7 @@ define([ ], initialize : function () { - this.collection = new MissingCollection(); + this.collection = new MissingCollection().bindSignalR({ updateOnly: true }); this.listenTo(this.collection, 'sync', this._showTable); },