Merge branch 'develop'
This commit is contained in:
commit
878a89b4e2
|
@ -127,6 +127,9 @@ Function PackageOsx()
|
|||
Write-Host "Adding sqlite dylibs"
|
||||
Copy-Item "$sourceFolder\Libraries\sqlite\*.dylib" "$outputFolderOsx"
|
||||
|
||||
Write-Host "Adding MediaInfo dylib"
|
||||
Copy-Item "$sourceFolder\Libraries\MediaInfo\*.dylib" "$outputFolderOsx"
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Creating OS X Package']"
|
||||
}
|
||||
|
||||
|
|
|
@ -184,6 +184,9 @@ namespace Exceptron.Client
|
|||
{
|
||||
report.cul = Thread.CurrentThread.CurrentCulture.Name;
|
||||
|
||||
if (string.IsNullOrEmpty(report.cul))
|
||||
report.cul = "en";
|
||||
|
||||
try
|
||||
{
|
||||
report.os = Environment.OSVersion.VersionString;
|
||||
|
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<dllmap os="osx" dll="MediaInfo.dll" target="libmediainfo.dylib"/>
|
||||
<dllmap os="osx" dll="MediaInfo.dll" target="libmediainfo.0.dylib"/>
|
||||
<dllmap os="linux" dll="MediaInfo.dll" target="libmediainfo.so.0" />
|
||||
<dllmap os="freebsd" dll="MediaInfo.dll" target="libmediainfo.so.0" />
|
||||
<dllmap os="solaris" dll="MediaInfo.dll" target="libmediainfo.so.0.0.0" />
|
||||
|
|
|
@ -12,12 +12,10 @@ namespace NzbDrone.Api.Authentication
|
|||
{
|
||||
public class EnableStatelessAuthInNancy : IRegisterNancyPipeline
|
||||
{
|
||||
private readonly IAuthenticationService _authenticationService;
|
||||
private static String API_KEY;
|
||||
|
||||
public EnableStatelessAuthInNancy(IAuthenticationService authenticationService, IConfigFileProvider configFileProvider)
|
||||
public EnableStatelessAuthInNancy(IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_authenticationService = authenticationService;
|
||||
API_KEY = configFileProvider.ApiKey;
|
||||
}
|
||||
|
||||
|
@ -30,16 +28,11 @@ namespace NzbDrone.Api.Authentication
|
|||
{
|
||||
Response response = null;
|
||||
|
||||
if (!RuntimeInfo.IsProduction && context.Request.IsLocalRequest())
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
var authorizationHeader = context.Request.Headers.Authorization;
|
||||
var apiKeyHeader = context.Request.Headers["X-Api-Key"].FirstOrDefault();
|
||||
var apiKey = apiKeyHeader.IsNullOrWhiteSpace() ? authorizationHeader : apiKeyHeader;
|
||||
|
||||
if (context.Request.IsApiRequest() && !ValidApiKey(apiKey) && !IsAuthenticated(context))
|
||||
if (context.Request.IsApiRequest() && !ValidApiKey(apiKey))
|
||||
{
|
||||
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
||||
}
|
||||
|
@ -49,15 +42,9 @@ namespace NzbDrone.Api.Authentication
|
|||
|
||||
private bool ValidApiKey(string apiKey)
|
||||
{
|
||||
if (apiKey.IsNullOrWhiteSpace()) return false;
|
||||
if (!apiKey.Equals(API_KEY)) return false;
|
||||
if (!API_KEY.Equals(apiKey)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAuthenticated(NancyContext context)
|
||||
{
|
||||
return _authenticationService.Enabled && _authenticationService.IsAuthenticated(context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,8 +40,8 @@ namespace NzbDrone.Api.Calendar
|
|||
var occurrence = icalCalendar.Create<Event>();
|
||||
occurrence.UID = "NzbDrone_episode_" + episode.Id.ToString();
|
||||
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
|
||||
occurrence.Start = new iCalDateTime(episode.AirDateUtc.Value);
|
||||
occurrence.End = new iCalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime));
|
||||
occurrence.Start = new iCalDateTime(episode.AirDateUtc.Value) { HasTime = true };
|
||||
occurrence.End = new iCalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime)) { HasTime = true };
|
||||
occurrence.Description = episode.Overview;
|
||||
occurrence.Categories = new List<string>() { episode.Series.Network };
|
||||
|
||||
|
|
|
@ -17,5 +17,7 @@ namespace NzbDrone.Api.Config
|
|||
public String FolderChmod { get; set; }
|
||||
public String ChownUser { get; set; }
|
||||
public String ChownGroup { get; set; }
|
||||
|
||||
public Boolean SkipFreeSpaceCheckWhenImporting { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace NzbDrone.Api.Extensions
|
|||
{
|
||||
public static bool IsApiRequest(this Request request)
|
||||
{
|
||||
return request.Path.StartsWith("/api/", StringComparison.InvariantCultureIgnoreCase) || request.IsLogFileRequest();
|
||||
return request.Path.StartsWith("/api/", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public static bool IsSignalRRequest(this Request request)
|
||||
|
@ -21,11 +21,5 @@ namespace NzbDrone.Api.Extensions
|
|||
request.UserHostAddress.Equals("127.0.0.1") ||
|
||||
request.UserHostAddress.Equals("::1"));
|
||||
}
|
||||
|
||||
private static bool IsLogFileRequest(this Request request)
|
||||
{
|
||||
return request.Path.StartsWith("/log/", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
request.Path.EndsWith(".txt", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,15 @@ using System.Linq;
|
|||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using Nancy;
|
||||
using Nancy.Responses;
|
||||
|
||||
namespace NzbDrone.Api.Logs
|
||||
{
|
||||
public class LogFileModule : NzbDroneRestModule<LogFileResource>
|
||||
{
|
||||
private const string LOGFILE_ROUTE = @"/(?<filename>nzbdrone(?:\.\d+)?\.txt)";
|
||||
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
|
||||
|
@ -19,6 +23,8 @@ namespace NzbDrone.Api.Logs
|
|||
_appFolderInfo = appFolderInfo;
|
||||
_diskProvider = diskProvider;
|
||||
GetResourceAll = GetLogFiles;
|
||||
|
||||
Get[LOGFILE_ROUTE] = options => GetLogFile(options.filename);
|
||||
}
|
||||
|
||||
private List<LogFileResource> GetLogFiles()
|
||||
|
@ -41,5 +47,17 @@ namespace NzbDrone.Api.Logs
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,7 +32,13 @@ namespace NzbDrone.Api.Series
|
|||
public SeriesModule(ICommandExecutor commandExecutor,
|
||||
ISeriesService seriesService,
|
||||
ISeriesStatisticsService seriesStatisticsService,
|
||||
IMapCoversToLocal coverMapper)
|
||||
IMapCoversToLocal coverMapper,
|
||||
RootFolderValidator rootFolderValidator,
|
||||
PathExistsValidator pathExistsValidator,
|
||||
SeriesPathValidator seriesPathValidator,
|
||||
SeriesExistsValidator seriesExistsValidator,
|
||||
DroneFactoryValidator droneFactoryValidator
|
||||
)
|
||||
: base(commandExecutor)
|
||||
{
|
||||
_commandExecutor = commandExecutor;
|
||||
|
@ -48,11 +54,18 @@ namespace NzbDrone.Api.Series
|
|||
|
||||
SharedValidator.RuleFor(s => s.QualityProfileId).ValidId();
|
||||
|
||||
PutValidator.RuleFor(s => s.Path).IsValidPath();
|
||||
PutValidator.RuleFor(s => s.Path)
|
||||
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||
.IsValidPath()
|
||||
.SetValidator(rootFolderValidator)
|
||||
.SetValidator(pathExistsValidator)
|
||||
.SetValidator(seriesPathValidator)
|
||||
.SetValidator(droneFactoryValidator);
|
||||
|
||||
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => String.IsNullOrEmpty(s.RootFolderPath));
|
||||
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => String.IsNullOrEmpty(s.Path));
|
||||
PostValidator.RuleFor(s => s.Title).NotEmpty();
|
||||
PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator);
|
||||
}
|
||||
|
||||
private SeriesResource GetSeries(int id)
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace NzbDrone.Common
|
|||
{
|
||||
string DownloadString(string url);
|
||||
string DownloadString(string url, string username, string password);
|
||||
string DownloadString(string url, ICredentials credentials);
|
||||
Dictionary<string, string> GetHeader(string url);
|
||||
|
||||
Stream DownloadStream(string url, NetworkCredential credential = null);
|
||||
|
@ -44,7 +45,7 @@ namespace NzbDrone.Common
|
|||
return DownloadString(url, new NetworkCredential(username, password));
|
||||
}
|
||||
|
||||
private string DownloadString(string url, ICredentials identity)
|
||||
public string DownloadString(string url, ICredentials identity)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -116,7 +116,7 @@ namespace NzbDrone.Common.Processes
|
|||
};
|
||||
|
||||
|
||||
logger.Info("Starting {0} {1}", path, args);
|
||||
logger.Debug("Starting {0} {1}", path, args);
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
|
@ -163,7 +163,7 @@ namespace NzbDrone.Common.Processes
|
|||
path = "mono";
|
||||
}
|
||||
|
||||
Logger.Info("Starting {0} {1}", path, args);
|
||||
Logger.Debug("Starting {0} {1}", path, args);
|
||||
|
||||
var startInfo = new ProcessStartInfo(path, args);
|
||||
var process = new Process
|
||||
|
|
|
@ -10,6 +10,7 @@ using NzbDrone.Core.Parser.Model;
|
|||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
using FizzWare.NBuilder;
|
||||
|
||||
namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
{
|
||||
|
@ -141,7 +142,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
|||
results.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test] public void should_not_attempt_to_map_episode_series_title_is_blank()
|
||||
[Test]
|
||||
public void should_not_attempt_to_map_episode_series_title_is_blank()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
_reports[0].Title = "1937 - Snow White and the Seven Dwarves";
|
||||
|
@ -204,5 +206,49 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
|||
|
||||
result.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_only_include_reports_for_requested_episodes()
|
||||
{
|
||||
var series = Builder<Series>.CreateNew().Build();
|
||||
|
||||
var episodes = Builder<Episode>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(v => v.SeriesId, series.Id)
|
||||
.With(v => v.Series, series)
|
||||
.With(v => v.SeasonNumber, 1)
|
||||
.With(v => v.SceneSeasonNumber, 2)
|
||||
.BuildList();
|
||||
|
||||
var criteria = new SeasonSearchCriteria { Episodes = episodes.Take(1).ToList(), SeasonNumber = 1 };
|
||||
|
||||
var reports = episodes.Select(v =>
|
||||
new ReleaseInfo()
|
||||
{
|
||||
Title = string.Format("{0}.S{1:00}E{2:00}.720p.WEB-DL-DRONE", series.Title, v.SceneSeasonNumber, v.SceneEpisodeNumber)
|
||||
}).ToList();
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(v => v.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()))
|
||||
.Returns<ParsedEpisodeInfo, int, SearchCriteriaBase>((p,id,c) =>
|
||||
new RemoteEpisode
|
||||
{
|
||||
DownloadAllowed = true,
|
||||
ParsedEpisodeInfo = p,
|
||||
Series = series,
|
||||
Episodes = episodes.Where(v => v.SceneEpisodeNumber == p.EpisodeNumbers.First()).ToList()
|
||||
});
|
||||
|
||||
Mocker.SetConstant<IEnumerable<IDecisionEngineSpecification>>(new List<IDecisionEngineSpecification>
|
||||
{
|
||||
Mocker.Resolve<NzbDrone.Core.DecisionEngine.Specifications.Search.EpisodeRequestedSpecification>()
|
||||
});
|
||||
|
||||
var decisions = Subject.GetSearchDecision(reports, criteria);
|
||||
|
||||
var approvedDecisions = decisions.Where(v => v.Approved).ToList();
|
||||
|
||||
approvedDecisions.Count.Should().Be(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
public class NzbSearchServiceFixture : CoreTest<NzbSearchService>
|
||||
{
|
||||
private Series _xemSeries;
|
||||
private List<Episode> _xemEpisodes;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
var indexer = Mocker.GetMock<IIndexer>();
|
||||
indexer.SetupGet(s => s.SupportsSearching).Returns(true);
|
||||
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(s => s.GetAvailableProviders())
|
||||
.Returns(new List<IIndexer> { indexer.Object });
|
||||
|
||||
Mocker.GetMock<NzbDrone.Core.DecisionEngine.IMakeDownloadDecision>()
|
||||
.Setup(s => s.GetSearchDecision(It.IsAny<List<Parser.Model.ReleaseInfo>>(), It.IsAny<SearchCriteriaBase>()))
|
||||
.Returns(new List<NzbDrone.Core.DecisionEngine.Specifications.DownloadDecision>());
|
||||
|
||||
_xemSeries = Builder<Series>.CreateNew()
|
||||
.With(v => v.UseSceneNumbering = true)
|
||||
.Build();
|
||||
|
||||
_xemEpisodes = new List<Episode>();
|
||||
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
.Setup(v => v.GetSeries(_xemSeries.Id))
|
||||
.Returns(_xemSeries);
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Setup(v => v.GetEpisodesBySeason(_xemSeries.Id, It.IsAny<int>()))
|
||||
.Returns<int, int>((i, j) => _xemEpisodes.Where(d => d.SeasonNumber == j).ToList());
|
||||
}
|
||||
|
||||
private void WithEpisode(int seasonNumber, int episodeNumber, int sceneSeasonNumber, int sceneEpisodeNumber)
|
||||
{
|
||||
var episode = Builder<Episode>.CreateNew()
|
||||
.With(v => v.SeriesId == _xemSeries.Id)
|
||||
.With(v => v.Series == _xemSeries)
|
||||
.With(v => v.SeasonNumber, seasonNumber)
|
||||
.With(v => v.EpisodeNumber, episodeNumber)
|
||||
.With(v => v.SceneSeasonNumber, sceneSeasonNumber)
|
||||
.With(v => v.SceneEpisodeNumber, sceneEpisodeNumber)
|
||||
.Build();
|
||||
|
||||
_xemEpisodes.Add(episode);
|
||||
}
|
||||
|
||||
private void WithEpisodes()
|
||||
{
|
||||
// Season 1 maps to Scene Season 2 (one-to-one)
|
||||
WithEpisode(1, 12, 2, 3);
|
||||
WithEpisode(1, 13, 2, 4);
|
||||
|
||||
// Season 2 maps to Scene Season 3 & 4 (one-to-one)
|
||||
WithEpisode(2, 1, 3, 11);
|
||||
WithEpisode(2, 2, 3, 12);
|
||||
WithEpisode(2, 3, 4, 11);
|
||||
WithEpisode(2, 4, 4, 12);
|
||||
|
||||
// Season 3 maps to Scene Season 5 (partial)
|
||||
// Season 4 maps to Scene Season 5 & 6 (partial)
|
||||
WithEpisode(3, 1, 5, 11);
|
||||
WithEpisode(3, 2, 5, 12);
|
||||
WithEpisode(4, 1, 5, 13);
|
||||
WithEpisode(4, 2, 5, 14);
|
||||
WithEpisode(4, 3, 6, 11);
|
||||
WithEpisode(5, 1, 6, 12);
|
||||
|
||||
// Season 7+ maps normally, so no mapping specified.
|
||||
WithEpisode(7, 1, 0, 0);
|
||||
WithEpisode(7, 2, 0, 0);
|
||||
}
|
||||
|
||||
private List<SearchCriteriaBase> WatchForSearchCriteria()
|
||||
{
|
||||
List<SearchCriteriaBase> result = new List<SearchCriteriaBase>();
|
||||
|
||||
Mocker.GetMock<IFetchFeedFromIndexers>()
|
||||
.Setup(v => v.Fetch(It.IsAny<IIndexer>(), It.IsAny<SingleEpisodeSearchCriteria>()))
|
||||
.Callback<IIndexer, SingleEpisodeSearchCriteria>((i, s) => result.Add(s))
|
||||
.Returns(new List<Parser.Model.ReleaseInfo>());
|
||||
|
||||
Mocker.GetMock<IFetchFeedFromIndexers>()
|
||||
.Setup(v => v.Fetch(It.IsAny<IIndexer>(), It.IsAny<SeasonSearchCriteria>()))
|
||||
.Callback<IIndexer, SeasonSearchCriteria>((i, s) => result.Add(s))
|
||||
.Returns(new List<Parser.Model.ReleaseInfo>());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_episodesearch()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.EpisodeSearch(_xemEpisodes.First());
|
||||
|
||||
var criteria = allCriteria.OfType<SingleEpisodeSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
criteria[0].SeasonNumber.Should().Be(2);
|
||||
criteria[0].EpisodeNumber.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_seasonsearch()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.SeasonSearch(_xemSeries.Id, 1);
|
||||
|
||||
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
criteria[0].SeasonNumber.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_seasonsearch_should_search_multiple_seasons()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.SeasonSearch(_xemSeries.Id, 2);
|
||||
|
||||
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(2);
|
||||
criteria[0].SeasonNumber.Should().Be(3);
|
||||
criteria[1].SeasonNumber.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_seasonsearch_should_search_single_episode_if_possible()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.SeasonSearch(_xemSeries.Id, 4);
|
||||
|
||||
var criteria1 = allCriteria.OfType<SeasonSearchCriteria>().ToList();
|
||||
var criteria2 = allCriteria.OfType<SingleEpisodeSearchCriteria>().ToList();
|
||||
|
||||
criteria1.Count.Should().Be(1);
|
||||
criteria1[0].SeasonNumber.Should().Be(5);
|
||||
|
||||
criteria2.Count.Should().Be(1);
|
||||
criteria2[0].SeasonNumber.Should().Be(6);
|
||||
criteria2[0].EpisodeNumber.Should().Be(11);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_seasonsearch_should_use_seasonnumber_if_no_scene_number_is_available()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.SeasonSearch(_xemSeries.Id, 7);
|
||||
|
||||
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
criteria[0].SeasonNumber.Should().Be(7);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ using NzbDrone.Core.MediaFiles.Commands;
|
|||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
@ -22,7 +23,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
public class DownloadedEpisodesImportServiceFixture : CoreTest<DownloadedEpisodesImportService>
|
||||
{
|
||||
private string[] _subFolders = new[] { "c:\\root\\foldername".AsOsAgnostic() };
|
||||
private string[] _videoFiles = new[] { "c:\\root\\foldername\\video.ext".AsOsAgnostic() };
|
||||
private string[] _videoFiles = new[] { "c:\\root\\foldername\\30.rock.s01e01.ext".AsOsAgnostic() };
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
|
@ -113,6 +114,8 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(v => v.GetSeries(It.IsAny<String>()), Times.Never());
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -129,7 +132,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_folder_if_files_were_imported()
|
||||
public void should_delete_folder_if_files_were_imported_and_video_files_remain()
|
||||
{
|
||||
GivenValidSeries();
|
||||
|
||||
|
@ -148,6 +151,40 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFolder(It.IsAny<String>(), true), Times.Never());
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_folder_if_files_were_imported_and_only_sample_files_remain()
|
||||
{
|
||||
GivenValidSeries();
|
||||
|
||||
var localEpisode = new LocalEpisode();
|
||||
|
||||
var imported = new List<ImportDecision>();
|
||||
imported.Add(new ImportDecision(localEpisode));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), true, null))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<ISampleService>()
|
||||
.Setup(s => s.IsSample(It.IsAny<Series>(),
|
||||
It.IsAny<QualityModel>(),
|
||||
It.IsAny<String>(),
|
||||
It.IsAny<Int64>(),
|
||||
It.IsAny<Int32>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFolder(It.IsAny<String>(), true), Times.Once());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
{
|
||||
[TestFixture]
|
||||
public class SampleServiceFixture : CoreTest<SampleService>
|
||||
{
|
||||
private Series _series;
|
||||
private LocalEpisode _localEpisode;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>.CreateNew()
|
||||
.With(s => s.SeriesType = SeriesTypes.Standard)
|
||||
.Build();
|
||||
|
||||
var episodes = Builder<Episode>.CreateListOfSize(1)
|
||||
.All()
|
||||
.With(e => e.SeasonNumber = 1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
_localEpisode = new LocalEpisode
|
||||
{
|
||||
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
|
||||
Episodes = episodes,
|
||||
Series = _series,
|
||||
Quality = new QualityModel(Quality.HDTV720p)
|
||||
};
|
||||
}
|
||||
|
||||
private void GivenFileSize(long size)
|
||||
{
|
||||
_localEpisode.Size = size;
|
||||
}
|
||||
|
||||
private void GivenRuntime(int seconds)
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
||||
.Returns(new TimeSpan(0, 0, seconds));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_if_season_zero()
|
||||
{
|
||||
_localEpisode.Episodes[0].SeasonNumber = 0;
|
||||
ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_for_flv()
|
||||
{
|
||||
_localEpisode.Path = @"C:\Test\some.show.s01e01.flv";
|
||||
|
||||
ShouldBeFalse();
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_runtime()
|
||||
{
|
||||
GivenRuntime(120);
|
||||
GivenFileSize(1000.Megabytes());
|
||||
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
_localEpisode.Quality,
|
||||
_localEpisode.Path,
|
||||
_localEpisode.Size,
|
||||
_localEpisode.SeasonNumber);
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(v => v.GetRunTime(It.IsAny<String>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_runtime_is_less_than_minimum()
|
||||
{
|
||||
GivenRuntime(60);
|
||||
|
||||
ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_if_runtime_greater_than_than_minimum()
|
||||
{
|
||||
GivenRuntime(120);
|
||||
|
||||
ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_acceptable_size()
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
||||
.Throws<DllNotFoundException>();
|
||||
|
||||
GivenFileSize(1000.Megabytes());
|
||||
ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_undersize()
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
||||
.Throws<DllNotFoundException>();
|
||||
|
||||
GivenFileSize(1.Megabytes());
|
||||
ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void ShouldBeTrue()
|
||||
{
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
_localEpisode.Quality,
|
||||
_localEpisode.Path,
|
||||
_localEpisode.Size,
|
||||
_localEpisode.SeasonNumber).Should().BeTrue();
|
||||
}
|
||||
|
||||
private void ShouldBeFalse()
|
||||
{
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
_localEpisode.Quality,
|
||||
_localEpisode.Path,
|
||||
_localEpisode.Size,
|
||||
_localEpisode.SeasonNumber).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ using Moq;
|
|||
using NUnit.Framework;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
@ -143,5 +144,15 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
|||
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_when_skip_check_is_enabled()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.SkipFreeSpaceCheckWhenImporting)
|
||||
.Returns(true);
|
||||
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,96 +41,11 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
|||
};
|
||||
}
|
||||
|
||||
private void GivenFileSize(long size)
|
||||
{
|
||||
_localEpisode.Size = size;
|
||||
}
|
||||
|
||||
private void GivenRuntime(int seconds)
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
||||
.Returns(new TimeSpan(0, 0, seconds));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_series_is_daily()
|
||||
{
|
||||
_series.SeriesType = SeriesTypes.Daily;
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_season_zero()
|
||||
{
|
||||
_localEpisode.Episodes[0].SeasonNumber = 0;
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_existing_file()
|
||||
{
|
||||
_localEpisode.ExistingFile = true;
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_flv()
|
||||
{
|
||||
_localEpisode.Path = @"C:\Test\some.show.s01e01.flv";
|
||||
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_runtime()
|
||||
{
|
||||
GivenRuntime(120);
|
||||
GivenFileSize(1000.Megabytes());
|
||||
|
||||
Subject.IsSatisfiedBy(_localEpisode);
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(v => v.GetRunTime(It.IsAny<String>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_if_runtime_is_less_than_minimum()
|
||||
{
|
||||
GivenRuntime(60);
|
||||
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_runtime_greater_than_than_minimum()
|
||||
{
|
||||
GivenRuntime(120);
|
||||
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_acceptable_size()
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
||||
.Throws<DllNotFoundException>();
|
||||
|
||||
GivenFileSize(1000.Megabytes());
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_undersize()
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
||||
.Throws<DllNotFoundException>();
|
||||
|
||||
GivenFileSize(1.Megabytes());
|
||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,9 @@ namespace NzbDrone.Core.Test.MetadataSourceTests
|
|||
private void ValidateEpisode(Episode episode)
|
||||
{
|
||||
episode.Should().NotBeNull();
|
||||
episode.EpisodeNumber.Should().NotBe(0);
|
||||
|
||||
//TODO: Is there a better way to validate that episode number or season number is greater than zero?
|
||||
(episode.EpisodeNumber + episode.SeasonNumber).Should().NotBe(0);
|
||||
|
||||
episode.Should().NotBeNull();
|
||||
|
||||
|
|
|
@ -138,6 +138,7 @@
|
|||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupDuplicateMetadataFilesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasksFixture.cs" />
|
||||
<Compile Include="IndexerSearchTests\NzbSearchServiceFixture.cs" />
|
||||
<Compile Include="IndexerSearchTests\SearchDefinitionFixture.cs" />
|
||||
<Compile Include="IndexerTests\BasicRssParserFixture.cs" />
|
||||
<Compile Include="IndexerTests\IndexerServiceFixture.cs" />
|
||||
|
@ -155,6 +156,7 @@
|
|||
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMakerFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotInUseSpecificationFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\SampleServiceFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecificationFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecificationFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" />
|
||||
|
@ -212,6 +214,7 @@
|
|||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodesFixture.cs" />
|
||||
<Compile Include="ThingiProviderTests\NullConfigFixture.cs" />
|
||||
<Compile Include="ThingiProvider\ProviderBaseFixture.cs" />
|
||||
<Compile Include="TvTests\EpisodeRepositoryTests\ByAirDateFixture.cs" />
|
||||
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWithFilesFixture.cs" />
|
||||
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWhereCutoffUnmetFixture.cs" />
|
||||
<Compile Include="TvTests\RefreshEpisodeServiceFixture.cs" />
|
||||
|
|
|
@ -39,6 +39,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||
[TestCase("Ray Donovan - S01E01.720p.HDtv.x264-Evolve (NLsub)", Language.Dutch)]
|
||||
[TestCase("Shield,.The.1x13.Tueurs.De.Flics.FR.DVDRip.XviD", Language.French)]
|
||||
[TestCase("True.Detective.S01E01.1080p.WEB-DL.Rus.Eng.TVKlondike", Language.Russian)]
|
||||
[TestCase("The.Trip.To.Italy.S02E01.720p.HDTV.x264-TLA", Language.English)]
|
||||
public void should_parse_language(string postTitle, Language language)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ByAirDateFixture : DbTest<EpisodeRepository, Episode>
|
||||
{
|
||||
private const int SERIES_ID = 1;
|
||||
private const string AIR_DATE = "2014-04-02";
|
||||
|
||||
private void GivenEpisode(int seasonNumber)
|
||||
{
|
||||
var episode = Builder<Episode>.CreateNew()
|
||||
.With(e => e.SeriesId = 1)
|
||||
.With(e => e.SeasonNumber = seasonNumber)
|
||||
.With(e => e.AirDate = AIR_DATE)
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(episode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_when_multiple_regular_episodes_are_found()
|
||||
{
|
||||
GivenEpisode(1);
|
||||
GivenEpisode(2);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => Subject.Get(SERIES_ID, AIR_DATE));
|
||||
Assert.Throws<InvalidOperationException>(() => Subject.Find(SERIES_ID, AIR_DATE));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_when_get_finds_no_episode()
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(() => Subject.Get(SERIES_ID, AIR_DATE));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_episode_when_single_episode_exists_for_air_date()
|
||||
{
|
||||
GivenEpisode(1);
|
||||
|
||||
Subject.Get(SERIES_ID, AIR_DATE).Should().NotBeNull();
|
||||
Subject.Find(SERIES_ID, AIR_DATE).Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_episode_when_regular_episode_and_special_share_the_same_air_date()
|
||||
{
|
||||
GivenEpisode(1);
|
||||
GivenEpisode(0);
|
||||
|
||||
Subject.Get(SERIES_ID, AIR_DATE).Should().NotBeNull();
|
||||
Subject.Find(SERIES_ID, AIR_DATE).Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_special_when_its_the_only_episode_for_the_date_provided()
|
||||
{
|
||||
GivenEpisode(0);
|
||||
|
||||
Subject.Get(SERIES_ID, AIR_DATE).Should().NotBeNull();
|
||||
Subject.Find(SERIES_ID, AIR_DATE).Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -164,6 +164,13 @@ namespace NzbDrone.Core.Configuration
|
|||
set { SetValue("DownloadedEpisodesScanInterval", value); }
|
||||
}
|
||||
|
||||
public Boolean SkipFreeSpaceCheckWhenImporting
|
||||
{
|
||||
get { return GetValueBoolean("SkipFreeSpaceCheckWhenImporting", false); }
|
||||
|
||||
set { SetValue("SkipFreeSpaceCheckWhenImporting", value); }
|
||||
}
|
||||
|
||||
public Boolean SetPermissionsLinux
|
||||
{
|
||||
get { return GetValueBoolean("SetPermissionsLinux", false); }
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace NzbDrone.Core.Configuration
|
|||
Boolean AutoDownloadPropers { get; set; }
|
||||
Boolean CreateEmptySeriesFolders { get; set; }
|
||||
FileDateType FileDate { get; set; }
|
||||
Boolean SkipFreeSpaceCheckWhenImporting { get; set; }
|
||||
|
||||
//Permissions (Media Management)
|
||||
Boolean SetPermissionsLinux { get; set; }
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class EpisodeRequestedSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public EpisodeRequestedSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Episode wasn't requested";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var criteriaEpisodes = searchCriteria.Episodes.Select(v => v.Id).ToList();
|
||||
var remoteEpisodes = remoteEpisode.Episodes.Select(v => v.Id).ToList();
|
||||
if (!criteriaEpisodes.Intersect(remoteEpisodes).Any())
|
||||
{
|
||||
_logger.Debug("Release rejected since the episode wasn't requested: {0}", remoteEpisode.ParsedEpisodeInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
|||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@ using NzbDrone.Core.Messaging.Commands;
|
|||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class MissingEpisodeSearchCommand : Command
|
||||
public class EpisodeSearchCommand : Command
|
||||
{
|
||||
public List<int> EpisodeIds { get; set; }
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
PageSize = 100000,
|
||||
SortDirection = SortDirection.Ascending,
|
||||
SortKey = "Id",
|
||||
FilterExpression = v => v.Monitored && v.Series.Monitored
|
||||
FilterExpression = v => v.Monitored == true && v.Series.Monitored == true
|
||||
}).Records.ToList();
|
||||
|
||||
_logger.ProgressInfo("Performing missing search for {0} episodes", episodes.Count);
|
||||
|
|
|
@ -3,7 +3,7 @@ using NzbDrone.Core.Messaging.Commands;
|
|||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class EpisodeSearchCommand : Command
|
||||
public class MissingEpisodeSearchCommand : Command
|
||||
{
|
||||
public List<int> EpisodeIds { get; set; }
|
||||
|
||||
|
|
|
@ -138,10 +138,53 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
return SearchSpecial(series, episodes);
|
||||
}
|
||||
|
||||
var searchSpec = Get<SeasonSearchCriteria>(series, episodes);
|
||||
searchSpec.SeasonNumber = seasonNumber;
|
||||
List<DownloadDecision> downloadDecisions = new List<DownloadDecision>();
|
||||
|
||||
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
if (series.UseSceneNumbering)
|
||||
{
|
||||
var sceneSeasonGroups = episodes.GroupBy(v =>
|
||||
{
|
||||
if (v.SceneSeasonNumber == 0 && v.SceneEpisodeNumber == 0)
|
||||
return v.SeasonNumber;
|
||||
else
|
||||
return v.SceneSeasonNumber;
|
||||
}).Distinct();
|
||||
|
||||
foreach (var sceneSeasonEpisodes in sceneSeasonGroups)
|
||||
{
|
||||
if (sceneSeasonEpisodes.Count() == 1)
|
||||
{
|
||||
var episode = sceneSeasonEpisodes.First();
|
||||
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, sceneSeasonEpisodes.ToList());
|
||||
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
|
||||
if (episode.SceneSeasonNumber == 0 && episode.SceneEpisodeNumber == 0)
|
||||
searchSpec.EpisodeNumber = episode.EpisodeNumber;
|
||||
else
|
||||
searchSpec.EpisodeNumber = episode.SceneEpisodeNumber;
|
||||
|
||||
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
}
|
||||
else
|
||||
{
|
||||
var searchSpec = Get<SeasonSearchCriteria>(series, sceneSeasonEpisodes.ToList());
|
||||
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
|
||||
|
||||
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var searchSpec = Get<SeasonSearchCriteria>(series, episodes);
|
||||
searchSpec.SeasonNumber = seasonNumber;
|
||||
|
||||
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
}
|
||||
|
||||
return downloadDecisions;
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(Series series, List<Episode> episodes) where TSpec : SearchCriteriaBase, new()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Commands
|
||||
|
@ -12,6 +13,7 @@ namespace NzbDrone.Core.MediaFiles.Commands
|
|||
}
|
||||
}
|
||||
|
||||
public bool SendUpdates { get; set; }
|
||||
public Boolean SendUpdates { get; set; }
|
||||
public String Path { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
|
@ -24,6 +26,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
private readonly IConfigService _configService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||
private readonly ISampleService _sampleService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadedEpisodesImportService(IDiskProvider diskProvider,
|
||||
|
@ -33,6 +36,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
IConfigService configService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
IImportApprovedEpisodes importApprovedEpisodes,
|
||||
ISampleService sampleService,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
|
@ -42,6 +46,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
_configService = configService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_importApprovedEpisodes = importApprovedEpisodes;
|
||||
_sampleService = sampleService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -64,24 +69,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
foreach (var subFolder in _diskProvider.GetDirectories(downloadedEpisodesFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_seriesService.SeriesPathExists(subFolder))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var importedFiles = ProcessSubFolder(new DirectoryInfo(subFolder));
|
||||
|
||||
if (importedFiles.Any())
|
||||
{
|
||||
_diskProvider.DeleteFolder(subFolder, true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException("An error has occurred while importing folder: " + subFolder, e);
|
||||
}
|
||||
ProcessFolder(subFolder);
|
||||
}
|
||||
|
||||
foreach (var videoFile in _diskScanService.GetVideoFiles(downloadedEpisodesFolder, false))
|
||||
|
@ -97,9 +85,9 @@ namespace NzbDrone.Core.MediaFiles
|
|||
}
|
||||
}
|
||||
|
||||
private List<ImportDecision> ProcessSubFolder(DirectoryInfo subfolderInfo)
|
||||
private List<ImportDecision> ProcessFolder(DirectoryInfo directoryInfo)
|
||||
{
|
||||
var cleanedUpName = GetCleanedUpFolderName(subfolderInfo.Name);
|
||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
var series = _parsingService.GetSeries(cleanedUpName);
|
||||
var quality = QualityParser.ParseQuality(cleanedUpName);
|
||||
_logger.Debug("{0} folder quality: {1}", cleanedUpName, quality);
|
||||
|
@ -110,7 +98,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
return new List<ImportDecision>();
|
||||
}
|
||||
|
||||
var videoFiles = _diskScanService.GetVideoFiles(subfolderInfo.FullName);
|
||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
|
||||
return ProcessFiles(series, quality, videoFiles);
|
||||
}
|
||||
|
@ -140,6 +128,33 @@ namespace NzbDrone.Core.MediaFiles
|
|||
return _importApprovedEpisodes.Import(decisions, true);
|
||||
}
|
||||
|
||||
private void ProcessFolder(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
|
||||
try
|
||||
{
|
||||
if (_seriesService.SeriesPathExists(path))
|
||||
{
|
||||
_logger.Warn("Unable to process folder that contains sorted TV Shows");
|
||||
return;
|
||||
}
|
||||
|
||||
var directoryFolderInfo = new DirectoryInfo(path);
|
||||
var importedFiles = ProcessFolder(directoryFolderInfo);
|
||||
|
||||
if (importedFiles.Any() && ShouldDeleteFolder(directoryFolderInfo))
|
||||
{
|
||||
_logger.Debug("Deleting folder after importing valid files");
|
||||
_diskProvider.DeleteFolder(path, true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException("An error has occurred while importing folder: " + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCleanedUpFolderName(string folder)
|
||||
{
|
||||
folder = folder.Replace("_UNPACK_", "")
|
||||
|
@ -148,9 +163,47 @@ namespace NzbDrone.Core.MediaFiles
|
|||
return folder;
|
||||
}
|
||||
|
||||
private bool ShouldDeleteFolder(DirectoryInfo directoryInfo)
|
||||
{
|
||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
var series = _parsingService.GetSeries(cleanedUpName);
|
||||
|
||||
foreach (var videoFile in videoFiles)
|
||||
{
|
||||
var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile));
|
||||
|
||||
if (episodeParseResult == null)
|
||||
{
|
||||
_logger.Warn("Unable to parse file on import: [{0}]", videoFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
var size = _diskProvider.GetFileSize(videoFile);
|
||||
var quality = QualityParser.ParseQuality(videoFile);
|
||||
|
||||
if (!_sampleService.IsSample(series, quality, videoFile, size,
|
||||
episodeParseResult.SeasonNumber))
|
||||
{
|
||||
_logger.Warn("Non-sample file detected: [{0}]", videoFile);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Execute(DownloadedEpisodesScanCommand message)
|
||||
{
|
||||
ProcessDownloadedEpisodesFolder();
|
||||
if (message.Path.IsNullOrWhiteSpace())
|
||||
{
|
||||
ProcessDownloadedEpisodesFolder();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
ProcessFolder(message.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
{
|
||||
public interface ISampleService
|
||||
{
|
||||
bool IsSample(Series series, QualityModel quality, string path, long size, int seasonNumber);
|
||||
}
|
||||
|
||||
public class SampleService : ISampleService
|
||||
{
|
||||
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private static List<Quality> _largeSampleSizeQualities = new List<Quality> { Quality.HDTV1080p, Quality.WEBDL1080p, Quality.Bluray1080p };
|
||||
|
||||
public SampleService(IVideoFileInfoReader videoFileInfoReader, Logger logger)
|
||||
{
|
||||
_videoFileInfoReader = videoFileInfoReader;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public static long SampleSizeLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
return 70.Megabytes();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSample(Series series, QualityModel quality, string path, long size, int seasonNumber)
|
||||
{
|
||||
if (seasonNumber == 0)
|
||||
{
|
||||
_logger.Debug("Special, skipping sample check");
|
||||
return false;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
if (extension != null && extension.Equals(".flv", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_logger.Debug("Skipping sample check for .flv file");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var runTime = _videoFileInfoReader.GetRunTime(path);
|
||||
|
||||
if (runTime.TotalMinutes.Equals(0))
|
||||
{
|
||||
_logger.Error("[{0}] has a runtime of 0, is it a valid video file?", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (runTime.TotalSeconds < 90)
|
||||
{
|
||||
_logger.Debug("[{0}] appears to be a sample. Size: {1} Runtime: {2}", path, size, runTime);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
_logger.Debug("Falling back to file size detection");
|
||||
|
||||
return CheckSize(size, quality);
|
||||
}
|
||||
|
||||
_logger.Debug("Runtime is over 90 seconds");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckSize(long size, QualityModel quality)
|
||||
{
|
||||
if (_largeSampleSizeQualities.Contains(quality.Quality))
|
||||
{
|
||||
if (size < SampleSizeLimit * 2)
|
||||
{
|
||||
_logger.Debug("1080p file is less than sample limit");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (size < SampleSizeLimit)
|
||||
{
|
||||
_logger.Debug("File is less than sample limit");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||
|
@ -10,11 +10,13 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
public class FreeSpaceSpecification : IImportDecisionEngineSpecification
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public FreeSpaceSpecification(IDiskProvider diskProvider, Logger logger)
|
||||
public FreeSpaceSpecification(IDiskProvider diskProvider, IConfigService configService, Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -22,6 +24,12 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
|
||||
public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
||||
{
|
||||
if (_configService.SkipFreeSpaceCheckWhenImporting)
|
||||
{
|
||||
_logger.Debug("Skipping free space check when importing");
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (localEpisode.ExistingFile)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
@ -12,25 +11,16 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
{
|
||||
public class NotSampleSpecification : IImportDecisionEngineSpecification
|
||||
{
|
||||
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
||||
private readonly ISampleService _sampleService;
|
||||
private readonly Logger _logger;
|
||||
private static List<Quality> _largeSampleSizeQualities = new List<Quality> { Quality.HDTV1080p, Quality.WEBDL1080p, Quality.Bluray1080p };
|
||||
|
||||
public NotSampleSpecification(IVideoFileInfoReader videoFileInfoReader,
|
||||
public NotSampleSpecification(ISampleService sampleService,
|
||||
Logger logger)
|
||||
{
|
||||
_videoFileInfoReader = videoFileInfoReader;
|
||||
_sampleService = sampleService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public static long SampleSizeLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
return 70.Megabytes();
|
||||
}
|
||||
}
|
||||
|
||||
public string RejectionReason { get { return "Sample"; } }
|
||||
|
||||
public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
||||
|
@ -41,72 +31,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
return true;
|
||||
}
|
||||
|
||||
if (localEpisode.Series.SeriesType == SeriesTypes.Daily)
|
||||
{
|
||||
_logger.Debug("Daily Series, skipping sample check");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (localEpisode.SeasonNumber == 0)
|
||||
{
|
||||
_logger.Debug("Special, skipping sample check");
|
||||
return true;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(localEpisode.Path);
|
||||
|
||||
if (extension != null && extension.Equals(".flv", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_logger.Debug("Skipping sample check for .flv file");
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var runTime = _videoFileInfoReader.GetRunTime(localEpisode.Path);
|
||||
|
||||
if (runTime.TotalMinutes.Equals(0))
|
||||
{
|
||||
_logger.Error("[{0}] has a runtime of 0, is it a valid video file?", localEpisode);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (runTime.TotalSeconds < 90)
|
||||
{
|
||||
_logger.Debug("[{0}] appears to be a sample. Size: {1} Runtime: {2}", localEpisode.Path, localEpisode.Size, runTime);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
_logger.Debug("Falling back to file size detection");
|
||||
|
||||
return CheckSize(localEpisode);
|
||||
}
|
||||
|
||||
_logger.Debug("Runtime is over 90 seconds");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CheckSize(LocalEpisode localEpisode)
|
||||
{
|
||||
if (_largeSampleSizeQualities.Contains(localEpisode.Quality.Quality))
|
||||
{
|
||||
if (localEpisode.Size < SampleSizeLimit * 2)
|
||||
{
|
||||
_logger.Debug("1080p file is less than sample limit");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (localEpisode.Size < SampleSizeLimit)
|
||||
{
|
||||
_logger.Debug("File is less than sample limit");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !_sampleService.IsSample(localEpisode.Series,
|
||||
localEpisode.Quality,
|
||||
localEpisode.Path,
|
||||
localEpisode.Size,
|
||||
localEpisode.SeasonNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,16 +193,22 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
|||
{
|
||||
metadata.Type = MetadataType.SeasonImage;
|
||||
|
||||
var seasonNumber = seasonMatch.Groups["season"].Value;
|
||||
var seasonNumberMatch = seasonMatch.Groups["season"].Value;
|
||||
int seasonNumber;
|
||||
|
||||
if (seasonNumber.Contains("specials"))
|
||||
if (seasonNumberMatch.Contains("specials"))
|
||||
{
|
||||
metadata.SeasonNumber = 0;
|
||||
}
|
||||
|
||||
else if (Int32.TryParse(seasonNumberMatch, out seasonNumber))
|
||||
{
|
||||
metadata.SeasonNumber = seasonNumber;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
metadata.SeasonNumber = Convert.ToInt32(seasonNumber);
|
||||
return null;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
|
@ -313,14 +319,16 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
|||
}
|
||||
|
||||
_diskProvider.CopyFile(source, destination, false);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, destination);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage) ??
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage &&
|
||||
c.RelativePath == relativePath) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeriesImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, destination)
|
||||
RelativePath = relativePath
|
||||
};
|
||||
|
||||
yield return metadata;
|
||||
|
@ -341,18 +349,20 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
|||
}
|
||||
|
||||
var path = Path.Combine(series.Path, filename);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, path);
|
||||
|
||||
DownloadImage(series, image.Url, path);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
|
||||
c.SeasonNumber == season.SeasonNumber) ??
|
||||
c.SeasonNumber == season.SeasonNumber &&
|
||||
c.RelativePath == relativePath) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = season.SeasonNumber,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeasonImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
|
||||
RelativePath = relativePath
|
||||
};
|
||||
|
||||
yield return metadata;
|
||||
|
@ -458,7 +468,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
|||
var filename = GetEpisodeImageFilename(episodeFile.Path);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
|
||||
|
||||
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage &&
|
||||
var existingMetadata = existingMetadataFiles.FirstOrDefault(c => c.Type == MetadataType.EpisodeImage &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Growl.Connector;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
@ -63,6 +64,8 @@ namespace NzbDrone.Core.Notifications.Growl
|
|||
const string title = "Test Notification";
|
||||
const string body = "This is a test message from NzbDrone";
|
||||
|
||||
Thread.Sleep(5000);
|
||||
|
||||
SendNotification(title, body, "TEST", message.Host, message.Port, message.Password);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,13 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
[FieldDefinition(1, Label = "Port")]
|
||||
public Int32 Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Update Library", Type = FieldType.Checkbox)]
|
||||
[FieldDefinition(2, Label = "Username")]
|
||||
public String Username { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Password")]
|
||||
public String Password { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Update Library", Type = FieldType.Checkbox)]
|
||||
public Boolean UpdateLibrary { get; set; }
|
||||
|
||||
public bool IsValid
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
|
@ -58,7 +59,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
{
|
||||
_logger.Debug("Getting sections from Plex host: {0}", settings.Host);
|
||||
var url = String.Format("http://{0}:{1}/library/sections", settings.Host, settings.Port);
|
||||
var xmlStream = _httpProvider.DownloadStream(url, null);
|
||||
var xmlStream = _httpProvider.DownloadStream(url, GetCredentials(settings));
|
||||
var xDoc = XDocument.Load(xmlStream);
|
||||
var mediaContainer = xDoc.Descendants("MediaContainer").FirstOrDefault();
|
||||
var directories = mediaContainer.Descendants("Directory").Where(x => x.Attribute("type").Value == "show");
|
||||
|
@ -70,7 +71,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
{
|
||||
_logger.Debug("Updating Plex host: {0}, Section: {1}", settings.Host, key);
|
||||
var url = String.Format("http://{0}:{1}/library/sections/{2}/refresh", settings.Host, settings.Port, key);
|
||||
_httpProvider.DownloadString(url);
|
||||
_httpProvider.DownloadString(url, GetCredentials(settings));
|
||||
}
|
||||
|
||||
public string SendCommand(string host, int port, string command, string username, string password)
|
||||
|
@ -85,6 +86,13 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
return _httpProvider.DownloadString(url);
|
||||
}
|
||||
|
||||
private NetworkCredential GetCredentials(PlexServerSettings settings)
|
||||
{
|
||||
if (settings.Username.IsNullOrWhiteSpace()) return null;
|
||||
|
||||
return new NetworkCredential(settings.Username, settings.Password);
|
||||
}
|
||||
|
||||
public void Execute(TestPlexClientCommand message)
|
||||
{
|
||||
_logger.Debug("Sending Test Notifcation to Plex Client: {0}", message.Host);
|
||||
|
@ -100,7 +108,13 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
|
||||
public void Execute(TestPlexServerCommand message)
|
||||
{
|
||||
if (!GetSectionKeys(new PlexServerSettings {Host = message.Host, Port = message.Port}).Any())
|
||||
if (!GetSectionKeys(new PlexServerSettings
|
||||
{
|
||||
Host = message.Host,
|
||||
Port = message.Port,
|
||||
Username = message.Username,
|
||||
Password = message.Password
|
||||
}).Any())
|
||||
{
|
||||
throw new Exception("Unable to connect to Plex Server");
|
||||
}
|
||||
|
|
|
@ -14,5 +14,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
|||
public PushBulletSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.ApiKey).NotEmpty();
|
||||
RuleFor(c => c.DeviceId).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +21,7 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
|||
[FieldDefinition(0, Label = "API Key", HelpLink = "https://www.pushbullet.com/")]
|
||||
public String ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Device ID", HelpText = "device_iden in the device's URL on pubshbullet.com")]
|
||||
[FieldDefinition(1, Label = "Device ID", HelpText = "device_iden in the device's URL on pubshbullet.com (leave blank to send to all devices)")]
|
||||
public String DeviceId { get; set; }
|
||||
|
||||
public bool IsValid
|
||||
|
|
|
@ -20,14 +20,14 @@ namespace NzbDrone.Core.Notifications.Pushover
|
|||
{
|
||||
const string title = "Episode Grabbed";
|
||||
|
||||
_pushoverProxy.SendNotification(title, message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority);
|
||||
_pushoverProxy.SendNotification(title, message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority, Settings.Sound);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
const string title = "Episode Downloaded";
|
||||
|
||||
_pushoverProxy.SendNotification(title, message.Message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority);
|
||||
_pushoverProxy.SendNotification(title, message.Message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority, Settings.Sound);
|
||||
}
|
||||
|
||||
public override void AfterRename(Series series)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using RestSharp;
|
||||
using NzbDrone.Core.Rest;
|
||||
|
||||
|
@ -6,14 +7,14 @@ namespace NzbDrone.Core.Notifications.Pushover
|
|||
{
|
||||
public interface IPushoverProxy
|
||||
{
|
||||
void SendNotification(string title, string message, string apiKey, string userKey, PushoverPriority priority);
|
||||
void SendNotification(string title, string message, string apiKey, string userKey, PushoverPriority priority, string sound);
|
||||
}
|
||||
|
||||
public class PushoverProxy : IPushoverProxy, IExecute<TestPushoverCommand>
|
||||
{
|
||||
private const string URL = "https://api.pushover.net/1/messages.json";
|
||||
|
||||
public void SendNotification(string title, string message, string apiKey, string userKey, PushoverPriority priority)
|
||||
public void SendNotification(string title, string message, string apiKey, string userKey, PushoverPriority priority, string sound)
|
||||
{
|
||||
var client = new RestClient(URL);
|
||||
var request = new RestRequest(Method.POST);
|
||||
|
@ -23,6 +24,9 @@ namespace NzbDrone.Core.Notifications.Pushover
|
|||
request.AddParameter("message", message);
|
||||
request.AddParameter("priority", (int)priority);
|
||||
|
||||
if (!sound.IsNullOrWhiteSpace()) request.AddParameter("sound", sound);
|
||||
|
||||
|
||||
client.ExecuteAndValidate(request);
|
||||
}
|
||||
|
||||
|
@ -31,7 +35,7 @@ namespace NzbDrone.Core.Notifications.Pushover
|
|||
const string title = "Test Notification";
|
||||
const string body = "This is a test message from NzbDrone";
|
||||
|
||||
SendNotification(title, body, message.ApiKey, message.UserKey, (PushoverPriority)message.Priority);
|
||||
SendNotification(title, body, message.ApiKey, message.UserKey, (PushoverPriority)message.Priority, message.Sound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ namespace NzbDrone.Core.Notifications.Pushover
|
|||
[FieldDefinition(2, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(PushoverPriority) )]
|
||||
public Int32 Priority { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Sound", Type = FieldType.Textbox, HelpText = "Notification sound, leave blank to use the default", HelpLink = "https://pushover.net/api#sounds")]
|
||||
public String Sound { get; set; }
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
|
|
|
@ -16,5 +16,6 @@ namespace NzbDrone.Core.Notifications.Pushover
|
|||
public string ApiKey { get; set; }
|
||||
public string UserKey { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Sound { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -217,6 +217,7 @@
|
|||
<Compile Include="DecisionEngine\Specifications\NotRestrictedReleaseSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\NotSampleSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RssSync\ProperSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\Search\EpisodeRequestedSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\Search\SeriesSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\Search\SeasonMatchSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\Search\DailyEpisodeMatchSpecification.cs" />
|
||||
|
@ -326,6 +327,7 @@
|
|||
<Compile Include="MediaCover\MediaCoversUpdatedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeFileMoveResult.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\SampleService.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
|
||||
<Compile Include="MediaFiles\FileDateType.cs" />
|
||||
|
@ -680,9 +682,11 @@
|
|||
<Compile Include="Update\UpdatePackageProvider.cs" />
|
||||
<Compile Include="Update\UpdatePackage.cs" />
|
||||
<Compile Include="Update\UpdateCheckService.cs" />
|
||||
<Compile Include="Validation\Paths\SeriesExistsValidator.cs" />
|
||||
<Compile Include="Validation\Paths\RootFolderValidator.cs" />
|
||||
<Compile Include="Validation\Paths\DroneFactoryValidator.cs" />
|
||||
<Compile Include="Validation\Paths\PathValidator.cs" />
|
||||
<Compile Include="Validation\Paths\RootFolderValidator.cs" />
|
||||
<Compile Include="Validation\Paths\SeriesPathValidator.cs" />
|
||||
<Compile Include="Validation\Paths\PathExistsValidator.cs" />
|
||||
<Compile Include="Validation\FolderValidator.cs" />
|
||||
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
||||
|
|
|
@ -107,7 +107,7 @@ namespace NzbDrone.Core.Parser
|
|||
|
||||
private static readonly Regex MultiPartCleanupRegex = new Regex(@"\(\d+\)$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>ita|italian)|(?<german>german\b)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)FR)(?:\W|_)|(?<russian>\brus\b)",
|
||||
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>\bita\b|italian)|(?<german>german\b)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)FR)(?:\W|_)|(?<russian>\brus\b)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_)?(?<year>\d{4})",
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Extentions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
@ -14,8 +16,8 @@ namespace NzbDrone.Core.Tv
|
|||
{
|
||||
Episode Find(int seriesId, int season, int episodeNumber);
|
||||
Episode Find(int seriesId, int absoluteEpisodeNumber);
|
||||
Episode Get(int seriesId, String date);
|
||||
Episode Find(int seriesId, String date);
|
||||
Episode Get(int seriesId, string date);
|
||||
Episode Find(int seriesId, string date);
|
||||
List<Episode> GetEpisodes(int seriesId);
|
||||
List<Episode> GetEpisodes(int seriesId, int seasonNumber);
|
||||
List<Episode> GetEpisodeByFileId(int fileId);
|
||||
|
@ -32,11 +34,13 @@ namespace NzbDrone.Core.Tv
|
|||
public class EpisodeRepository : BasicRepository<Episode>, IEpisodeRepository
|
||||
{
|
||||
private readonly IDatabase _database;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public EpisodeRepository(IDatabase database, IEventAggregator eventAggregator)
|
||||
public EpisodeRepository(IDatabase database, IEventAggregator eventAggregator, Logger logger)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
_database = database;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Episode Find(int seriesId, int season, int episodeNumber)
|
||||
|
@ -54,18 +58,21 @@ namespace NzbDrone.Core.Tv
|
|||
.SingleOrDefault();
|
||||
}
|
||||
|
||||
public Episode Get(int seriesId, String date)
|
||||
public Episode Get(int seriesId, string date)
|
||||
{
|
||||
return Query.Where(s => s.SeriesId == seriesId)
|
||||
.AndWhere(s => s.AirDate == date)
|
||||
.Single();
|
||||
var episode = FindOneByAirDate(seriesId, date);
|
||||
|
||||
if (episode == null)
|
||||
{
|
||||
throw new InvalidOperationException("Expected at one episode");
|
||||
}
|
||||
|
||||
return episode;
|
||||
}
|
||||
|
||||
public Episode Find(int seriesId, String date)
|
||||
public Episode Find(int seriesId, string date)
|
||||
{
|
||||
return Query.Where(s => s.SeriesId == seriesId)
|
||||
.AndWhere(s => s.AirDate == date)
|
||||
.SingleOrDefault();
|
||||
return FindOneByAirDate(seriesId, date);
|
||||
}
|
||||
|
||||
public List<Episode> GetEpisodes(int seriesId)
|
||||
|
@ -207,5 +214,28 @@ namespace NzbDrone.Core.Tv
|
|||
|
||||
return String.Format("({0})", String.Join(" OR ", clauses));
|
||||
}
|
||||
|
||||
private Episode FindOneByAirDate(int seriesId, string date)
|
||||
{
|
||||
var episodes = Query.Where(s => s.SeriesId == seriesId)
|
||||
.AndWhere(s => s.AirDate == date)
|
||||
.ToList();
|
||||
|
||||
if (!episodes.Any()) return null;
|
||||
|
||||
if (episodes.Count == 1) return episodes.First();
|
||||
|
||||
_logger.Debug("Multiple episodes with the same air date were found, will exclude specials");
|
||||
|
||||
var regularEpisodes = episodes.Where(e => e.SeasonNumber > 0).ToList();
|
||||
|
||||
if (regularEpisodes.Count == 1)
|
||||
{
|
||||
_logger.Debug("Left with one episode after excluding specials");
|
||||
return regularEpisodes.First();
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Multiple episodes with the same air date found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
@ -197,14 +198,22 @@ namespace NzbDrone.Core.Tv
|
|||
{
|
||||
foreach (var s in series)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(s.RootFolderPath))
|
||||
if (!s.RootFolderPath.IsNullOrWhiteSpace())
|
||||
{
|
||||
var folderName = new DirectoryInfo(s.Path).Name;
|
||||
s.Path = Path.Combine(s.RootFolderPath, folderName);
|
||||
_logger.Trace("Changing path for {0} to {1}", s.Title, s.Path);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Trace("Not changing path for: {0}", s.Title);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Updating {0} series", series.Count);
|
||||
_seriesRepository.UpdateMany(series);
|
||||
_logger.Debug("{0} series updated", series.Count);
|
||||
|
||||
return series;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using FluentValidation.Validators;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Validation.Paths
|
||||
{
|
||||
public class SeriesExistsValidator : PropertyValidator
|
||||
{
|
||||
private readonly ISeriesService _seriesService;
|
||||
|
||||
public SeriesExistsValidator(ISeriesService seriesService)
|
||||
: base("This series has already been added")
|
||||
{
|
||||
_seriesService = seriesService;
|
||||
}
|
||||
|
||||
protected override bool IsValid(PropertyValidatorContext context)
|
||||
{
|
||||
if (context.PropertyValue == null) return true;
|
||||
|
||||
var tvdbId = Convert.ToInt32(context.PropertyValue.ToString());
|
||||
|
||||
return (!_seriesService.GetAllSeries().Exists(s => s.TvdbId == tvdbId));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using FluentValidation.Validators;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Tv;
|
||||
using Omu.ValueInjecter;
|
||||
|
||||
namespace NzbDrone.Core.Validation.Paths
|
||||
{
|
||||
public class SeriesPathValidator : PropertyValidator
|
||||
{
|
||||
private readonly ISeriesService _seriesService;
|
||||
|
||||
public SeriesPathValidator(ISeriesService seriesService)
|
||||
: base("Path is already configured for another series")
|
||||
{
|
||||
_seriesService = seriesService;
|
||||
}
|
||||
|
||||
protected override bool IsValid(PropertyValidatorContext context)
|
||||
{
|
||||
if (context.PropertyValue == null) return true;
|
||||
|
||||
var series = new Series();
|
||||
series.InjectFrom(context.ParentContext.InstanceToValidate);
|
||||
|
||||
if (series.Id == 0) return true;
|
||||
|
||||
return (!_seriesService.GetAllSeries().Exists(s => s.Path.PathEquals(context.PropertyValue.ToString()) && s.Id != series.Id));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'underscore',
|
||||
'vent',
|
||||
'AppLayout',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'marionette',
|
||||
'Quality/QualityProfileCollection',
|
||||
'AddSeries/RootFolders/RootFolderCollection',
|
||||
|
@ -13,7 +14,18 @@ define(
|
|||
'Shared/Messenger',
|
||||
'Mixins/AsValidatedView',
|
||||
'jquery.dotdotdot'
|
||||
], function (vent, AppLayout, _, Marionette, QualityProfiles, RootFolders, RootFolderLayout, SeriesCollection, Config, Messenger, AsValidatedView) {
|
||||
], function (_,
|
||||
vent,
|
||||
AppLayout,
|
||||
Backbone,
|
||||
Marionette,
|
||||
QualityProfiles,
|
||||
RootFolders,
|
||||
RootFolderLayout,
|
||||
SeriesCollection,
|
||||
Config,
|
||||
Messenger,
|
||||
AsValidatedView) {
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
|
||||
|
@ -156,7 +168,17 @@ define(
|
|||
icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search');
|
||||
|
||||
Messenger.show({
|
||||
message: 'Added: ' + self.model.get('title')
|
||||
message: 'Added: ' + self.model.get('title'),
|
||||
actions : {
|
||||
goToSeries: {
|
||||
label: 'Go to Series Page',
|
||||
action: function() {
|
||||
Backbone.history.navigate('/series/' + self.model.get('titleSlug'), { trigger: true });
|
||||
}
|
||||
}
|
||||
},
|
||||
hideAfter: 8,
|
||||
hideOnNavigate: true
|
||||
});
|
||||
|
||||
vent.trigger(vent.Events.SeriesAdded, { series: self.model });
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
{{overview}}
|
||||
</div>
|
||||
</div>
|
||||
{{#unless existing}}
|
||||
<div class="row labels">
|
||||
{{#unless path}}
|
||||
<div class="span4">Path</div>
|
||||
|
@ -29,6 +30,7 @@
|
|||
<div class="span1 starting-season starting-season-label">Starting Season</div>
|
||||
<div class="span2">Quality Profile</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
<div class="row">
|
||||
<form class="form-inline">
|
||||
{{#if existing}}
|
||||
|
|
|
@ -76,6 +76,10 @@ define(
|
|||
size : 14,
|
||||
animate : false
|
||||
});
|
||||
|
||||
this.$(element).find('.chart').tooltip({
|
||||
title: 'Episode is downloading - {0}% {1}'.format(event.progress.toFixed(1), event.releaseTitle)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -109,6 +113,7 @@ define(
|
|||
allDay : false,
|
||||
statusLevel : self._getStatusLevel(model, end),
|
||||
progress : self._getDownloadProgress(model),
|
||||
releaseTitle: self._getReleaseTitle(model),
|
||||
model : model
|
||||
};
|
||||
|
||||
|
@ -163,6 +168,16 @@ define(
|
|||
}
|
||||
|
||||
return 100 - (downloading.get('sizeleft') / downloading.get('size') * 100);
|
||||
},
|
||||
|
||||
_getReleaseTitle: function (element) {
|
||||
var downloading = QueueCollection.findEpisode(element.get('id'));
|
||||
|
||||
if (!downloading) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return downloading.get('title');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ define(
|
|||
_onClick: function () {
|
||||
var self = this;
|
||||
|
||||
if (window.confirm('Are you sure you want to delete \'{0}\' form disk?'.format(this.model.get('path')))) {
|
||||
if (window.confirm('Are you sure you want to delete \'{0}\' from disk?'.format(this.model.get('path')))) {
|
||||
this.model.destroy()
|
||||
.done(function () {
|
||||
vent.trigger(vent.Events.EpisodeFileDeleted, { episodeFile: self.model });
|
||||
|
|
|
@ -69,7 +69,7 @@ define(
|
|||
if (downloading) {
|
||||
var progress = 100 - (downloading.get('sizeleft') / downloading.get('size') * 100);
|
||||
|
||||
this.$el.html('<div class="progress progress-purple" title="Episode is downloading - {0}%" data-container="body">'.format(progress.toFixed(1)) +
|
||||
this.$el.html('<div class="progress progress-purple" title="Episode is downloading - {0}% {1}" data-container="body">'.format(progress.toFixed(1), downloading.get('title')) +
|
||||
'<div class="bar" style="width: {0}%;"></div></div>'.format(progress));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -41,3 +41,7 @@
|
|||
height : 1em;
|
||||
line-height : 1em;
|
||||
}
|
||||
|
||||
.tooltip-inner {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
'use strict';
|
||||
|
||||
define(
|
||||
[
|
||||
'jquery',
|
||||
'vent',
|
||||
'marionette',
|
||||
'Cells/NzbDroneCell'
|
||||
], function ($, vent, Marionette, NzbDroneCell) {
|
||||
return NzbDroneCell.extend({
|
||||
|
||||
className: 'episode-actions-cell',
|
||||
|
||||
events: {
|
||||
'click .x-failed' : '_markAsFailed'
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.empty();
|
||||
|
||||
if (this.model.get('eventType') === 'grabbed') {
|
||||
this.$el.html('<i class="icon-nd-delete x-failed" title="Mark download as failed"></i>');
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_markAsFailed: function () {
|
||||
var url = window.NzbDrone.ApiRoot + '/history/failed';
|
||||
var data = {
|
||||
id: this.model.get('id')
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -7,9 +7,18 @@ define(
|
|||
'Cells/EventTypeCell',
|
||||
'Cells/QualityCell',
|
||||
'Cells/RelativeDateCell',
|
||||
'Episode/Activity/EpisodeActivityActionsCell',
|
||||
'Episode/Activity/NoActivityView',
|
||||
'Shared/LoadingView'
|
||||
], function (Marionette, Backgrid, HistoryCollection, EventTypeCell, QualityCell, RelativeDateCell, NoActivityView, LoadingView) {
|
||||
], function (Marionette,
|
||||
Backgrid,
|
||||
HistoryCollection,
|
||||
EventTypeCell,
|
||||
QualityCell,
|
||||
RelativeDateCell,
|
||||
EpisodeActivityActionsCell,
|
||||
NoActivityView,
|
||||
LoadingView) {
|
||||
|
||||
return Marionette.Layout.extend({
|
||||
template: 'Episode/Activity/EpisodeActivityLayoutTemplate',
|
||||
|
@ -40,6 +49,12 @@ define(
|
|||
name : 'date',
|
||||
label: 'Date',
|
||||
cell : RelativeDateCell
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : '',
|
||||
cell : EpisodeActivityActionsCell,
|
||||
sortable: false
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ define(
|
|||
type: 'POST',
|
||||
data: data
|
||||
});
|
||||
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -13,42 +13,58 @@
|
|||
{{#if_eq eventType compare="grabbed"}}
|
||||
<dl class="dl-horizontal">
|
||||
|
||||
<dt>Name</dt>
|
||||
<dt>Name:</dt>
|
||||
<dd>{{sourceTitle}}</dd>
|
||||
|
||||
{{#with data}}
|
||||
{{#if indexer}}
|
||||
<dt>Indexer</dt>
|
||||
<dt>Indexer:</dt>
|
||||
<dd>{{indexer}}</dd>
|
||||
{{/if}}
|
||||
|
||||
{{#if releaseGroup}}
|
||||
<dt>Release Group</dt>
|
||||
<dt>Release Group:</dt>
|
||||
<dd>{{releaseGroup}}</dd>
|
||||
{{/if}}
|
||||
|
||||
{{#if nzbInfoUrl}}
|
||||
<dt>Info</dt>
|
||||
<dt>Info:</dt>
|
||||
<dd><a href="{{nzbInfoUrl}}">{{nzbInfoUrl}}</a></dd>
|
||||
{{/if}}
|
||||
|
||||
{{#if downloadClient}}
|
||||
<dt>Download Client:</dt>
|
||||
<dd>{{downloadClient}}</dd>
|
||||
{{/if}}
|
||||
|
||||
{{#if downloadClientId}}
|
||||
<dt>Download Client ID:</dt>
|
||||
<dd>{{downloadClientId}}</dd>
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
</dl>
|
||||
{{/if_eq}}
|
||||
{{#if_eq eventType compare="downloadFailed"}}
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Source Title</dt>
|
||||
|
||||
<dt>Name:</dt>
|
||||
<dd>{{sourceTitle}}</dd>
|
||||
|
||||
{{#with data}}
|
||||
<dt>Message</dt>
|
||||
<dt>Message:</dt>
|
||||
<dd>{{message}}</dd>
|
||||
{{/with}}
|
||||
</dl>
|
||||
{{/if_eq}}
|
||||
{{#if_eq eventType compare="downloadFolderImported"}}
|
||||
{{#if data}}
|
||||
<dl class="dl-horizontal">
|
||||
|
||||
{{#if sourceTitle}}
|
||||
<dt>Name:</dt>
|
||||
<dd>{{sourceTitle}}</dd>
|
||||
{{/if}}
|
||||
|
||||
{{#with data}}
|
||||
<dl class="dl-horizontal">
|
||||
{{#if droppedPath}}
|
||||
<dt>Source:</dt>
|
||||
<dd>{{droppedPath}}</dd>
|
||||
|
@ -58,11 +74,8 @@
|
|||
<dt>Imported To:</dt>
|
||||
<dd>{{importedPath}}</dd>
|
||||
{{/if}}
|
||||
</dl>
|
||||
{{/with}}
|
||||
{{else}}
|
||||
No details available
|
||||
{{/if}}
|
||||
</dl>
|
||||
{{/if_eq}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
|
@ -22,3 +22,30 @@
|
|||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{{#if_mono}}
|
||||
<fieldset class="advanced-setting">
|
||||
<legend>Importing</legend>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">Skip Free Space Check</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="skipFreeSpaceCheckWhenImporting"/>
|
||||
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Use when drone is unable to detect free space from your series root folder"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{{/if_mono}}
|
||||
|
|
|
@ -23,13 +23,16 @@ define(function () {
|
|||
}
|
||||
}
|
||||
|
||||
options.hideOnNavigate = options.hideOnNavigate || false;
|
||||
|
||||
return window.Messenger().post({
|
||||
message : options.message,
|
||||
type : options.type,
|
||||
showCloseButton: true,
|
||||
hideAfter : options.hideAfter,
|
||||
id : options.id,
|
||||
actions : options.actions
|
||||
actions : options.actions,
|
||||
hideOnNavigate : options.hideOnNavigate
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ define(
|
|||
], function (Backbone, StatusModel) {
|
||||
return Backbone.Model.extend({
|
||||
url: function () {
|
||||
return StatusModel.get('urlBase') + '/logfile/' + this.get('filename');
|
||||
return StatusModel.get('urlBase') + '/api/log/file/' + this.get('filename');
|
||||
},
|
||||
|
||||
parse: function (contents) {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
define(
|
||||
[
|
||||
'marionette'
|
||||
], function (Marionette) {
|
||||
return Marionette.ItemView.extend({
|
||||
template: 'System/Update/EmptyViewTemplate'
|
||||
});
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
<div>No updates are available</div>
|
|
@ -2,9 +2,11 @@
|
|||
define(
|
||||
[
|
||||
'marionette',
|
||||
'System/Update/UpdateItemView'
|
||||
], function (Marionette, UpdateItemView) {
|
||||
'System/Update/UpdateItemView',
|
||||
'System/Update/EmptyView'
|
||||
], function (Marionette, UpdateItemView, EmptyView) {
|
||||
return Marionette.CollectionView.extend({
|
||||
itemView: UpdateItemView
|
||||
itemView : UpdateItemView,
|
||||
emptyView: EmptyView
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue