Merge branch 'develop'

This commit is contained in:
Mark McDowall 2014-07-02 07:08:40 -07:00
commit bbfd09ca84
41 changed files with 408 additions and 208 deletions

View File

@ -3,19 +3,12 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions; 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.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Calendar namespace NzbDrone.Api.Calendar
{ {
public class CalendarModule : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>, public class CalendarModule : EpisodeModuleWithSignalR
IHandle<EpisodeGrabbedEvent>,
IHandle<EpisodeDownloadedEvent>
{ {
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly SeriesRepository _seriesRepository; private readonly SeriesRepository _seriesRepository;
@ -23,18 +16,12 @@ namespace NzbDrone.Api.Calendar
public CalendarModule(ICommandExecutor commandExecutor, public CalendarModule(ICommandExecutor commandExecutor,
IEpisodeService episodeService, IEpisodeService episodeService,
SeriesRepository seriesRepository) SeriesRepository seriesRepository)
: base(commandExecutor, "calendar") : base(episodeService, commandExecutor, "calendar")
{ {
_episodeService = episodeService; _episodeService = episodeService;
_seriesRepository = seriesRepository; _seriesRepository = seriesRepository;
GetResourceAll = GetCalendar; GetResourceAll = GetCalendar;
GetResourceById = GetEpisode;
}
private EpisodeResource GetEpisode(int id)
{
return _episodeService.GetEpisode(id).InjectTo<EpisodeResource>();
} }
private List<EpisodeResource> GetCalendar() private List<EpisodeResource> GetCalendar()
@ -53,24 +40,5 @@ namespace NzbDrone.Api.Calendar
return resources.OrderBy(e => e.AirDateUtc).ToList(); 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);
}
}
} }
} }

View File

@ -1,24 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.REST; 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.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Episodes namespace NzbDrone.Api.Episodes
{ {
public class EpisodeModule : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>, public class EpisodeModule : EpisodeModuleWithSignalR
IHandle<EpisodeGrabbedEvent>,
IHandle<EpisodeDownloadedEvent>
{ {
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
public EpisodeModule(ICommandExecutor commandExecutor, IEpisodeService episodeService) public EpisodeModule(ICommandExecutor commandExecutor, IEpisodeService episodeService)
: base(commandExecutor) : base(episodeService, commandExecutor)
{ {
_episodeService = episodeService; _episodeService = episodeService;
@ -43,29 +35,5 @@ namespace NzbDrone.Api.Episodes
{ {
_episodeService.SetEpisodeMonitored(episodeResource.Id, episodeResource.Monitored); _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);
}
}
} }
} }

View File

@ -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);
}
}
}
}

View File

@ -13,7 +13,10 @@ namespace NzbDrone.Api.Extensions.Pipelines
private void Handle(NancyContext context) 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());
}
} }
} }
} }

View File

@ -6,11 +6,11 @@ using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Api.Frontend.Mappers namespace NzbDrone.Api.Frontend.Mappers
{ {
public class LogFileMapper : StaticResourceMapperBase public class UpdateLogFileMapper : StaticResourceMapperBase
{ {
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
public LogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger) public UpdateLogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
: base(diskProvider, logger) : base(diskProvider, logger)
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
@ -21,12 +21,12 @@ namespace NzbDrone.Api.Frontend.Mappers
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = Path.GetFileName(path); path = Path.GetFileName(path);
return Path.Combine(_appFolderInfo.GetLogFolder(), path); return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), path);
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)
{ {
return resourceUrl.StartsWith("/logfile/") && resourceUrl.EndsWith(".txt"); return resourceUrl.StartsWith("/updatelogfile/") && resourceUrl.EndsWith(".txt");
} }
} }
} }

View File

@ -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");
}
}
}

View File

@ -1,63 +1,43 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using Nancy; using NzbDrone.Core.Configuration;
using Nancy.Responses;
namespace NzbDrone.Api.Logs 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 IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
public LogFileModule(IAppFolderInfo appFolderInfo, public LogFileModule(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider) IDiskProvider diskProvider,
: base("log/file") IConfigFileProvider configFileProvider)
: base(diskProvider, configFileProvider, "")
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_diskProvider = diskProvider; _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>(); return _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), SearchOption.TopDirectoryOnly);
}
var files = _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]; return "logfile";
result.Add(new LogFileResource
{
Id = i + 1,
Filename = Path.GetFileName(file),
LastWriteTime = _diskProvider.FileGetLastWriteUtc(file)
});
} }
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);
}
} }
} }

View File

@ -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; }
}
}

View File

@ -7,5 +7,7 @@ namespace NzbDrone.Api.Logs
{ {
public String Filename { get; set; } public String Filename { get; set; }
public DateTime LastWriteTime { get; set; } public DateTime LastWriteTime { get; set; }
public String ContentsUrl { get; set; }
public String DownloadUrl { get; set; }
} }
} }

View File

@ -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";
}
}
}
}

View File

@ -117,6 +117,7 @@
<Compile Include="EpisodeFiles\EpisodeFileResource.cs" /> <Compile Include="EpisodeFiles\EpisodeFileResource.cs" />
<Compile Include="Directories\DirectoryLookupService.cs" /> <Compile Include="Directories\DirectoryLookupService.cs" />
<Compile Include="Directories\DirectoryModule.cs" /> <Compile Include="Directories\DirectoryModule.cs" />
<Compile Include="Episodes\EpisodeModuleWithSignalR.cs" />
<Compile Include="Episodes\EpisodeModule.cs" /> <Compile Include="Episodes\EpisodeModule.cs" />
<Compile Include="Episodes\EpisodeResource.cs" /> <Compile Include="Episodes\EpisodeResource.cs" />
<Compile Include="Episodes\RenameEpisodeModule.cs" /> <Compile Include="Episodes\RenameEpisodeModule.cs" />
@ -129,6 +130,7 @@
<Compile Include="Extensions\NancyJsonSerializer.cs" /> <Compile Include="Extensions\NancyJsonSerializer.cs" />
<Compile Include="Extensions\RequestExtensions.cs" /> <Compile Include="Extensions\RequestExtensions.cs" />
<Compile Include="Frontend\IsCacheableSpecification.cs" /> <Compile Include="Frontend\IsCacheableSpecification.cs" />
<Compile Include="Frontend\Mappers\UpdateLogFileMapper.cs" />
<Compile Include="Frontend\Mappers\FaviconMapper.cs" /> <Compile Include="Frontend\Mappers\FaviconMapper.cs" />
<Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" /> <Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" />
<Compile Include="Frontend\Mappers\LogFileMapper.cs" /> <Compile Include="Frontend\Mappers\LogFileMapper.cs" />
@ -141,6 +143,8 @@
<Compile Include="Health\HealthModule.cs" /> <Compile Include="Health\HealthModule.cs" />
<Compile Include="History\HistoryResource.cs" /> <Compile Include="History\HistoryResource.cs" />
<Compile Include="History\HistoryModule.cs" /> <Compile Include="History\HistoryModule.cs" />
<Compile Include="Logs\LogFileModuleBase.cs" />
<Compile Include="Logs\UpdateLogFileModule.cs" />
<Compile Include="MediaCovers\MediaCoverModule.cs" /> <Compile Include="MediaCovers\MediaCoverModule.cs" />
<Compile Include="Metadata\MetadataResource.cs" /> <Compile Include="Metadata\MetadataResource.cs" />
<Compile Include="Metadata\MetadataModule.cs" /> <Compile Include="Metadata\MetadataModule.cs" />

View File

@ -2,17 +2,18 @@
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Wanted namespace NzbDrone.Api.Wanted
{ {
public class CutoffModule : NzbDroneRestModule<EpisodeResource> public class CutoffModule : EpisodeModuleWithSignalR
{ {
private readonly IEpisodeCutoffService _episodeCutoffService; private readonly IEpisodeCutoffService _episodeCutoffService;
private readonly SeriesRepository _seriesRepository; private readonly ISeriesRepository _seriesRepository;
public CutoffModule(IEpisodeCutoffService episodeCutoffService, SeriesRepository seriesRepository) public CutoffModule(IEpisodeService episodeService, IEpisodeCutoffService episodeCutoffService, ISeriesRepository seriesRepository, ICommandExecutor commandExecutor)
:base("wanted/cutoff") :base(episodeService, commandExecutor, "wanted/cutoff")
{ {
_episodeCutoffService = episodeCutoffService; _episodeCutoffService = episodeCutoffService;
_seriesRepository = seriesRepository; _seriesRepository = seriesRepository;

View File

@ -2,17 +2,18 @@
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Wanted namespace NzbDrone.Api.Wanted
{ {
public class MissingModule : NzbDroneRestModule<EpisodeResource> public class MissingModule : EpisodeModuleWithSignalR
{ {
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly SeriesRepository _seriesRepository; private readonly ISeriesRepository _seriesRepository;
public MissingModule(IEpisodeService episodeService, SeriesRepository seriesRepository) public MissingModule(IEpisodeService episodeService, ISeriesRepository seriesRepository, ICommandExecutor commandExecutor)
:base("wanted/missing") :base(episodeService, commandExecutor, "wanted/missing")
{ {
_episodeService = episodeService; _episodeService = episodeService;
_seriesRepository = seriesRepository; _seriesRepository = seriesRepository;

View File

@ -42,9 +42,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{ {
var url = remoteEpisode.Release.DownloadUrl; var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title + ".nzb"; var title = remoteEpisode.Release.Title + ".nzb";
var category = Settings.TvCategory;
string category = Settings.TvCategory; var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
int priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
_logger.Info("Adding report [{0}] to the queue.", title); _logger.Info("Adding report [{0}] to the queue.", title);
@ -271,10 +270,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
_proxy.GetVersion(settings); _proxy.GetVersion(settings);
var config = _proxy.GetConfig(settings); var config = _proxy.GetConfig(settings);
var categories = GetCategories(config); 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"); throw new ApplicationException("Category does not exist");
} }

View File

@ -222,7 +222,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{ {
var categories = _proxy.GetCategories(settings); 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"); throw new ApplicationException("Category does not exist");
} }

View File

@ -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;
}
}
}
}

View File

@ -1,4 +1,5 @@
using System.IO; using System;
using System.IO;
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Disk; 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 IDiskProvider _diskProvider;
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
@ -27,12 +28,14 @@ namespace NzbDrone.Core.Instrumentation
public void Execute(DeleteLogFilesCommand message) 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) public void Execute(DeleteUpdateLogFilesCommand message)
{ {
_diskProvider.DeleteFile(logFile); _logger.Debug("Deleting all files in: {0}", _appFolderInfo.GetUpdateLogFolder());
} _diskProvider.EmptyFolder(_appFolderInfo.GetUpdateLogFolder());
} }
} }
} }

View File

@ -73,8 +73,9 @@ namespace NzbDrone.Core.MediaFiles
private IEnumerable<RenameEpisodeFilePreview> GetPreviews(Series series, List<Episode> episodes, List<EpisodeFile> files) 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(); var episodesInFile = episodes.Where(e => e.EpisodeFileId == file.Id).ToList();
if (!episodesInFile.Any()) if (!episodesInFile.Any())
@ -95,18 +96,13 @@ namespace NzbDrone.Core.MediaFiles
SeasonNumber = seasonNumber, SeasonNumber = seasonNumber,
EpisodeNumbers = episodesInFile.Select(e => e.EpisodeNumber).ToList(), EpisodeNumbers = episodesInFile.Select(e => e.EpisodeNumber).ToList(),
EpisodeFileId = file.Id, EpisodeFileId = file.Id,
ExistingPath = GetRelativePath(series.Path, file.Path), ExistingPath = series.Path.GetRelativePath(file.Path),
NewPath = GetRelativePath(series.Path, newPath) 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) private void RenameFiles(List<EpisodeFile> episodeFiles, Series series)
{ {
var renamed = new List<EpisodeFile>(); var renamed = new List<EpisodeFile>();

View File

@ -334,6 +334,7 @@
<Compile Include="Indexers\RssSyncCommand.cs" /> <Compile Include="Indexers\RssSyncCommand.cs" />
<Compile Include="Indexers\XElementExtensions.cs" /> <Compile Include="Indexers\XElementExtensions.cs" />
<Compile Include="Instrumentation\Commands\ClearLogCommand.cs" /> <Compile Include="Instrumentation\Commands\ClearLogCommand.cs" />
<Compile Include="Instrumentation\Commands\DeleteUpdateLogFilesCommand.cs" />
<Compile Include="Instrumentation\Commands\DeleteLogFilesCommand.cs" /> <Compile Include="Instrumentation\Commands\DeleteLogFilesCommand.cs" />
<Compile Include="Instrumentation\Commands\TrimLogCommand.cs" /> <Compile Include="Instrumentation\Commands\TrimLogCommand.cs" />
<Compile Include="Instrumentation\DeleteLogFilesService.cs" /> <Compile Include="Instrumentation\DeleteLogFilesService.cs" />

View File

@ -55,7 +55,7 @@ define(
_importSeries: function () { _importSeries: function () {
this.rootFolderLayout = new RootFolderLayout(); this.rootFolderLayout = new RootFolderLayout();
this.rootFolderLayout.on('folderSelected', this._folderSelected, this); this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
AppLayout.modalRegion.show(this.rootFolderLayout); AppLayout.modalRegion.show(this.rootFolderLayout);
}, },

View File

@ -10,9 +10,14 @@
<div class="row"> <div class="row">
<h2 class="series-title"> <h2 class="series-title">
{{titleWithYear}} {{titleWithYear}}
{{#unless_eq status compare="continuing"}}
<span class="label label-important">Ended</span> <span class="labels">
{{/unless_eq}} <span class="label label-primary">{{network}}</span>
{{#unless_eq status compare="continuing"}}
<span class="label label-danger">Ended</span>
{{/unless_eq}}
</span>
</h2> </h2>
</div> </div>
<div class="row new-series-overview x-overview"> <div class="row new-series-overview x-overview">

View File

@ -40,9 +40,13 @@
padding-bottom : 20px; padding-bottom : 20px;
.series-title { .series-title {
.label { .labels {
margin-left: 15px; margin-left : 10px;
vertical-align: middle;
.label {
font-size : 12px;
vertical-align : middle;
}
} }
.year { .year {

View File

@ -14,7 +14,7 @@ define(
}, },
templateHelpers: { 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' icalWebCalUrl : 'webcal://' + window.location.host + StatusModel.get('urlBase') + '/feed/calendar/NzbDrone.ics'
}, },

View File

@ -6,21 +6,26 @@
</div> </div>
<div class="modal-body edit-series-modal"> <div class="modal-body edit-series-modal">
<div class="row"> <div class="row">
<div> <div class="col-md-12">
<div class="form-horizontal"> <div class="form-horizontal">
<div class="form-group">
<label class="control-label">iCal feed</label>
<div class="controls ical-url"> <div class="form-group">
<div class="input-group"> <label class="col-sm-3 control-label">iCal feed</label>
<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> <div class="col-sm-1 col-sm-push-8 help-inline">
<a class="btn btn-icon-only no-router" title="Subscribe" href="{{icalWebCalUrl}}" target="_blank"><i class="icon-calendar-empty"></i></a> <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>
<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> </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> </div>
</div> </div>

View File

@ -178,7 +178,6 @@
.ical-url { .ical-url {
input { input {
width : 440px; cursor : text !important;
cursor : text;
} }
} }

View File

@ -67,8 +67,9 @@ define(
initialize: function () { initialize: function () {
this.collection = new BlacklistCollection({ tableName: 'blacklist' }); this.collection = new BlacklistCollection({ tableName: 'blacklist' });
this.listenTo(this.collection, 'sync', this._showTable); 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 () { onShow: function () {

View File

@ -59,8 +59,7 @@ define(
initialize: function () { initialize: function () {
this.listenTo(this.model, 'change:monitored', this._setMonitoredState); this.listenTo(this.model, 'change:monitored', this._setMonitoredState);
this.listenTo(vent, vent.Events.SeriesDeleted, this._onSeriesDeleted); this.listenTo(vent, vent.Events.SeriesDeleted, this._onSeriesDeleted);
this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete);
vent.on(vent.Events.CommandComplete, this._commandComplete, this);
}, },
onShow: function () { onShow: function () {

View File

@ -31,7 +31,7 @@ define(
}, },
initialize: function () { initialize: function () {
vent.on(vent.Events.CommandComplete, this._commandComplete, this); this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete);
}, },
onRender: function(){ onRender: function(){

View File

@ -1,12 +1,11 @@
'use strict'; 'use strict';
define( define(
[ [
'backbone', 'backbone'
'System/StatusModel' ], function (Backbone) {
], function (Backbone, StatusModel) {
return Backbone.Model.extend({ return Backbone.Model.extend({
url: function () { url: function () {
return StatusModel.get('urlBase') + '/api/log/file/' + this.get('filename'); return this.get('contentsUrl');
}, },
parse: function (contents) { parse: function (contents) {

View File

@ -1,16 +1,15 @@
'use strict'; 'use strict';
define( define(
[ [
'Cells/NzbDroneCell', '../../../Cells/NzbDroneCell'
'System/StatusModel' ], function (NzbDroneCell) {
], function (NzbDroneCell, StatusModel) {
return NzbDroneCell.extend({ return NzbDroneCell.extend({
className: 'download-log-cell', className: 'download-log-cell',
render: function () { render: function () {
this.$el.empty(); 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; return this;
} }

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
define( define(
[ [
'Cells/NzbDroneCell' '../../../Cells/NzbDroneCell'
], function (NzbDroneCell) { ], function (NzbDroneCell) {
return NzbDroneCell.extend({ return NzbDroneCell.extend({

View File

@ -7,7 +7,6 @@ define(
'System/Logs/Files/FilenameCell', 'System/Logs/Files/FilenameCell',
'Cells/RelativeDateCell', 'Cells/RelativeDateCell',
'System/Logs/Files/DownloadLogCell', 'System/Logs/Files/DownloadLogCell',
'System/Logs/Files/LogFileCollection',
'System/Logs/Files/Row', 'System/Logs/Files/Row',
'System/Logs/Files/ContentsView', 'System/Logs/Files/ContentsView',
'System/Logs/Files/ContentsModel', 'System/Logs/Files/ContentsModel',
@ -20,7 +19,6 @@ define(
FilenameCell, FilenameCell,
RelativeDateCell, RelativeDateCell,
DownloadLogCell, DownloadLogCell,
LogFileCollection,
LogFileRow, LogFileRow,
ContentsView, ContentsView,
ContentsModel, ContentsModel,
@ -48,18 +46,19 @@ define(
cell : RelativeDateCell cell : RelativeDateCell
}, },
{ {
name : 'filename', name : 'downloadUrl',
label : '', label : '',
cell : DownloadLogCell, cell : DownloadLogCell,
sortable: false sortable: false
} }
], ],
initialize: function () { initialize: function (options) {
this.collection = new LogFileCollection(); this.collection = options.collection;
this.deleteFilesCommand = options.deleteFilesCommand;
vent.on(vent.Commands.ShowLogFile, this._fetchLogFileContents, this); this.listenTo(vent, vent.Commands.ShowLogFile, this._fetchLogFileContents);
vent.on(vent.Events.CommandComplete, this._commandComplete, this); this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete);
this.listenTo(this.collection, 'sync', this._collectionSynced); this.listenTo(this.collection, 'sync', this._collectionSynced);
this.collection.fetch(); this.collection.fetch();
@ -83,11 +82,10 @@ define(
ownerContext : this, ownerContext : this,
callback : this._refreshTable callback : this._refreshTable
}, },
{ {
title : 'Delete Log Files', title : 'Delete Log Files',
icon : 'icon-trash', icon : 'icon-trash',
command : 'deleteLogFiles', command : this.deleteFilesCommand,
successMessage : 'Log files have been deleted', successMessage : 'Log files have been deleted',
errorMessage : 'Failed to delete log files' errorMessage : 'Failed to delete log files'
} }
@ -125,11 +123,7 @@ define(
this.contents.show(new LoadingView()); this.contents.show(new LoadingView());
var model = options.model; var model = options.model;
var filename = model.get('filename'); var contentsModel = new ContentsModel(model.toJSON());
var contentsModel = new ContentsModel({
filename: filename
});
this.listenToOnce(contentsModel, 'sync', this._showDetails); this.listenToOnce(contentsModel, 'sync', this._showDetails);
@ -151,7 +145,7 @@ define(
}, },
_commandComplete: function (options) { _commandComplete: function (options) {
if (options.command.get('name') === 'deletelogfiles') { if (options.command.get('name') === this.deleteFilesCommand.toLowerCase()) {
this._refreshTable(); this._refreshTable();
} }
} }

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
define( define(
[ [
'vent', '../../../vent',
'backgrid' 'backgrid'
], function (vent, Backgrid) { ], function (vent, Backgrid) {

View File

@ -3,24 +3,29 @@ define(
[ [
'marionette', 'marionette',
'System/Logs/Table/LogsTableLayout', 'System/Logs/Table/LogsTableLayout',
'System/Logs/Files/LogFileLayout' 'System/Logs/Files/LogFileLayout',
], function (Marionette, LogsTableLayout, LogsFileLayout) { 'System/Logs/Files/LogFileCollection',
'System/Logs/Updates/LogFileCollection'
], function (Marionette, LogsTableLayout, LogsFileLayout, LogFileCollection, UpdateLogFileCollection) {
return Marionette.Layout.extend({ return Marionette.Layout.extend({
template: 'System/Logs/LogsLayoutTemplate', template: 'System/Logs/LogsLayoutTemplate',
ui: { ui: {
tableTab: '.x-table-tab', tableTab : '.x-table-tab',
filesTab: '.x-files-tab' filesTab : '.x-files-tab',
updateFilesTab : '.x-update-files-tab'
}, },
regions: { regions: {
table: '#table', table : '#table',
files: '#files' files : '#files',
updateFiles : '#update-files'
}, },
events: { events: {
'click .x-table-tab': '_showTable', 'click .x-table-tab' : '_showTable',
'click .x-files-tab': '_showFiles' 'click .x-files-tab' : '_showFiles',
'click .x-update-files-tab' : '_showUpdateFiles'
}, },
onShow: function () { onShow: function () {
@ -42,7 +47,22 @@ define(
} }
this.ui.filesTab.tab('show'); 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'
}));
} }
}); });
}); });

View File

@ -1,15 +1,17 @@
<div class="row"> <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"> <ul class="nav nav-pills nav-stacked">
<li><a href="#table" class="x-table-tab no-router">Table</a></li> <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="#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> </ul>
</div> </div>
<div class="col-md-11 col-sm-10"> <div class="col-md-10 col-sm-10">
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane" id="table"></div> <div class="tab-pane" id="table"></div>
<div class="tab-pane" id="files"></div> <div class="tab-pane" id="files"></div>
<div class="tab-pane" id="update-files"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -61,7 +61,7 @@ define(
this.collection = new LogCollection(); this.collection = new LogCollection();
this.listenTo(this.collection, 'sync', this._showTable); 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 () { onRender: function () {

View File

@ -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
}
});
});

View File

@ -0,0 +1,8 @@
'use strict';
define(
[
'backbone'
], function (Backbone) {
return Backbone.Model.extend({
});
});

View File

@ -4,10 +4,10 @@
<li><a href="#updates" class="x-updates-tab no-router">Updates</a></li> <li><a href="#updates" class="x-updates-tab no-router">Updates</a></li>
<li class="lifecycle-controls pull-right"> <li class="lifecycle-controls pull-right">
<div class="btn-group"> <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> <i class="icon-nd-shutdown"></i>
</button> </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> <i class="icon-nd-restart"></i>
</button> </button>
</div> </div>

View File

@ -14,7 +14,8 @@ define([
'Shared/LoadingView', 'Shared/LoadingView',
'Shared/Messenger', 'Shared/Messenger',
'Commands/CommandController', 'Commands/CommandController',
'backgrid.selectall' 'backgrid.selectall',
'Mixins/backbone.signalr.mixin'
], function (_, ], function (_,
Marionette, Marionette,
Backgrid, Backgrid,
@ -81,7 +82,7 @@ define([
], ],
initialize : function () { initialize : function () {
this.collection = new CutoffUnmetCollection(); this.collection = new CutoffUnmetCollection().bindSignalR({ updateOnly: true });
this.listenTo(this.collection, 'sync', this._showTable); this.listenTo(this.collection, 'sync', this._showTable);
}, },

View File

@ -14,7 +14,8 @@ define([
'Shared/LoadingView', 'Shared/LoadingView',
'Shared/Messenger', 'Shared/Messenger',
'Commands/CommandController', 'Commands/CommandController',
'backgrid.selectall' 'backgrid.selectall',
'Mixins/backbone.signalr.mixin'
], function (_, ], function (_,
Marionette, Marionette,
Backgrid, Backgrid,
@ -81,7 +82,7 @@ define([
], ],
initialize : function () { initialize : function () {
this.collection = new MissingCollection(); this.collection = new MissingCollection().bindSignalR({ updateOnly: true });
this.listenTo(this.collection, 'sync', this._showTable); this.listenTo(this.collection, 'sync', this._showTable);
}, },