1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-05 13:20:20 -05:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Taloth Saldono
779ab39f50 Fixed failing test 2019-01-12 13:30:08 +01:00
Taloth Saldono
00283e3d6e New: Limit indexer/download client backoff to 5 min during the first 15 min of application start.
closes #2366
2019-01-12 13:15:41 +01:00
Taloth Saldono
2b4429f8b7 Fixed: Erroneously matching Anime 10.5 special as 10.
fixes #2868
2019-01-12 13:14:47 +01:00
Taloth Saldono
2446c4185a Added 10-bit to parser cleanup.
fixes #2870
2019-01-12 13:14:47 +01:00
Taloth Saldono
04900e5f90 Tweaked reverse title detection to handle triple digit episode numbers.
fixes #2871
2019-01-12 13:14:47 +01:00
Taloth Saldono
ce59db528b Fixed: Mono bug causing memory leakage when http connections use gzip compression.
The bug is registered upstream, but this commit works around the problem by doing the gzip decompression separately from the http stack.

Ref #2296
2019-01-10 20:13:48 +01:00
Taloth Saldono
31b266659e Fixed bad test due to skyhook now doing it's own fuzzy search. 2018-12-29 13:05:03 +01:00
Taloth Saldono
e071b0c2e0 DataMapper LazyLoaded needlessly keeping the parent mapper alive. 2018-12-29 12:45:07 +01:00
Taloth Saldono
270f04d2d2 Fixed: Excessive memory usage due to sqlite cache configuration.
ref #2296
2018-12-29 12:43:35 +01:00
Mark McDowall
9af57c6786 New: Store last search time for EpisodeSearch
Closes #420
2018-12-06 20:59:09 -08:00
Mark McDowall
ff4a550cbb New: Include OriginalFilePath with Episode Files
Closes #2336
2018-12-06 20:59:09 -08:00
Kevin Richter
537e4d7c39 Fix Quality Detection with DDP5.1 2018-11-24 11:24:24 +01:00
Taloth Saldono
9f16d9b2fc Fixed: File names and release titles lacking a series title and starting with the Air date.
fixes #2825
2018-11-21 22:02:51 +01:00
Taloth Saldono
ae6d920e2a Updated error message if skyhook and other services respond with html content.
closes #2817
2018-11-14 21:48:56 +01:00
Mark McDowall
0d22f9ec29 Improve logging when rejecting release with unmonitored episodes 2018-11-11 21:11:14 -08:00
Mark McDowall
699076a405 New: Added warning for Download Station that 2FA is not supported
Closes #2451
2018-11-10 16:23:33 -08:00
Jeffrey Neer
df593f486f New: Added priority levels to Join Notifications 2018-11-10 14:51:14 -08:00
Mark McDowall
0d95873a05 New: Parsing french anime releases with single absolute episode number
Closes #2798
2018-11-03 18:42:06 -07:00
Mark McDowall
b20acc9063 Fixed: Sort The A-Team properly in series list 2018-11-03 11:58:00 -07:00
Mark McDowall
70d6d25178 New: Parse names with 1080i as 1080p if they are not RAW HD
Closes #2793
2018-11-03 11:52:02 -07:00
Mark McDowall
196d165432 New: Parse names with FHD as 1080p
Closes #2793
2018-11-03 11:45:34 -07:00
Mark McDowall
bb3ca998fc Restrict 4k parsing to avoid false positives 2018-11-03 11:30:41 -07:00
Mark McDowall
da73221cef Fixed: Handling of poorly formed items when parsing results from indexer 2018-10-24 20:43:52 -07:00
Mark McDowall
36f66eed21 New: Parse names with 4k as 2160p
Closes #2788
2018-10-24 20:13:57 -07:00
Mark McDowall
8e916d60f5 Fixed: Parsing of specials with only season and episode numbers in the file name 2018-10-24 18:32:22 -07:00
Mark McDowall
44048207f2 Remove file quality matches release import spec
New: Don't reject imports when quality doesn't match release quality
New: Reject grab when release was grabbed and imported already
Closes #2783
2018-10-22 20:37:32 -07:00
Mark McDowall
b73b99df8d Fixed: Don't clean Kodi library if Always Update is disabled and video is playing
Fixes #2773
2018-10-22 14:20:22 -07:00
Mark McDowall
ad69ecc5eb Fixed: Use season number from episode instead of parsed from release for custom scripts
Closes #2748
2018-10-07 19:03:32 -07:00
Mark McDowall
1304bc8fb9 Fixed: Exclude /snap/* locations from disk space
Closes #2743
2018-10-07 19:03:32 -07:00
Mark McDowall
a4f63e728c Fixed: Don't use media info for non-video files
Fixes #2745
2018-10-07 19:03:32 -07:00
Jeff Byrnes
307b3536b7 New: Compatibility with Hombrew-installed mono 2018-09-15 10:49:22 -07:00
68 changed files with 943 additions and 313 deletions

View File

@@ -9,7 +9,11 @@ APPNAME="Sonarr"
#set up environment
if [[ -x '/opt/local/bin/mono' ]]; then
# Macports and mono-supplied installer path
export PATH="/opt/local/bin:$PATH"
elif [[ -x '/usr/local/bin/mono' ]]; then
# Homebrew-supplied path to mono
export PATH="/usr/local/bin:$PATH"
fi
export DYLD_FALLBACK_LIBRARY_PATH="$DIR"

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Common;
@@ -91,9 +91,11 @@ namespace Marr.Data.Mapping
Type entType = ent.GetType();
if (_repos.Relationships.ContainsKey(entType))
{
var provider = _db.ProviderFactory;
var connectionString = _db.ConnectionString;
Func<IDataMapper> dbCreate = () =>
{
var db = new DataMapper(_db.ProviderFactory, _db.ConnectionString);
var db = new DataMapper(provider, connectionString);
db.SqlMode = SqlModes.Text;
return db;
};

View File

@@ -16,6 +16,7 @@ namespace NzbDrone.Api.EpisodeFiles
public string SceneName { get; set; }
public QualityModel Quality { get; set; }
public MediaInfoResource MediaInfo { get; set; }
public string OriginalFilePath { get; set; }
public bool QualityCutoffNotMet { get; set; }
}
@@ -38,8 +39,8 @@ namespace NzbDrone.Api.EpisodeFiles
DateAdded = model.DateAdded,
SceneName = model.SceneName,
Quality = model.Quality,
MediaInfo = model.MediaInfo.ToResource(model.SceneName)
//QualityCutoffNotMet
MediaInfo = model.MediaInfo.ToResource(model.SceneName),
OriginalFilePath = model.OriginalFilePath
};
}
@@ -61,6 +62,7 @@ namespace NzbDrone.Api.EpisodeFiles
Quality = model.Quality,
QualityCutoffNotMet = qualityUpgradableSpecification.CutoffNotMet(series.Profile.Value, model.Quality),
MediaInfo = model.MediaInfo.ToResource(model.SceneName),
OriginalFilePath = model.OriginalFilePath
};
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
@@ -30,6 +30,7 @@ namespace NzbDrone.Api.Episodes
public bool UnverifiedSceneNumbering { get; set; }
public string SeriesTitle { get; set; }
public SeriesResource Series { get; set; }
public DateTime? LastSearchTime { get; set; }
//Hiding this so people don't think its usable (only used to set the initial state)
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
@@ -65,6 +66,7 @@ namespace NzbDrone.Api.Episodes
UnverifiedSceneNumbering = model.UnverifiedSceneNumbering,
SeriesTitle = model.SeriesTitle,
//Series = model.Series.MapToResource(),
LastSearchTime = model.LastSearchTime
};
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using Nancy;
using Nancy.Bootstrapper;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.Extensions.Pipelines
@@ -15,9 +16,14 @@ namespace NzbDrone.Api.Extensions.Pipelines
public int Order => 0;
private readonly Action<Action<Stream>, Stream> _writeGZipStream;
public GzipCompressionPipeline(Logger logger)
{
_logger = logger;
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
_writeGZipStream = PlatformInfo.IsMono ? WriteGZipStreamMono : (Action<Action<Stream>, Stream>)WriteGZipStream;
}
public void Register(IPipelines pipelines)
@@ -43,14 +49,7 @@ namespace NzbDrone.Api.Extensions.Pipelines
var contents = response.Contents;
response.Headers["Content-Encoding"] = "gzip";
response.Contents = responseStream =>
{
using (var gzip = new GZipStream(responseStream, CompressionMode.Compress, true))
using (var buffered = new BufferedStream(gzip, 8192))
{
contents.Invoke(buffered);
}
};
response.Contents = responseStream => _writeGZipStream(contents, responseStream);
}
}
@@ -61,6 +60,25 @@ namespace NzbDrone.Api.Extensions.Pipelines
}
}
private static void WriteGZipStreamMono(Action<Stream> innerContent, Stream targetStream)
{
using (var membuffer = new MemoryStream())
{
WriteGZipStream(innerContent, membuffer);
membuffer.Position = 0;
membuffer.CopyTo(targetStream);
}
}
private static void WriteGZipStream(Action<Stream> innerContent, Stream targetStream)
{
using (var gzip = new GZipStream(targetStream, CompressionMode.Compress, true))
using (var buffered = new BufferedStream(gzip, 8192))
{
innerContent.Invoke(buffered);
}
}
private static bool ContentLengthIsTooSmall(Response response)
{
var contentLength = response.Headers.GetValueOrDefault("Content-Length");

View File

@@ -1,7 +1,10 @@
using System;
namespace NzbDrone.Common.EnvironmentInfo
{
public interface IRuntimeInfo
{
DateTime StartTime { get; }
bool IsUserInteractive { get; }
bool IsAdmin { get; }
bool IsWindowsService { get; }

View File

@@ -12,6 +12,7 @@ namespace NzbDrone.Common.EnvironmentInfo
public class RuntimeInfo : IRuntimeInfo
{
private readonly Logger _logger;
private readonly DateTime _startTime = DateTime.UtcNow;
public RuntimeInfo(IServiceProvider serviceProvider, Logger logger)
{
@@ -37,6 +38,14 @@ namespace NzbDrone.Common.EnvironmentInfo
IsProduction = InternalIsProduction();
}
public DateTime StartTime
{
get
{
return _startTime;
}
}
public static bool IsUserInteractive => Environment.UserInteractive;
bool IRuntimeInfo.IsUserInteractive => IsUserInteractive;

View File

@@ -191,6 +191,24 @@ namespace NzbDrone.Common.Extensions
return directories;
}
public static string GetAncestorPath(this string path, string ancestorName)
{
var parent = Path.GetDirectoryName(path);
while (parent != null)
{
var currentPath = parent;
parent = Path.GetDirectoryName(parent);
if (Path.GetFileName(currentPath) == ancestorName)
{
return currentPath;
}
}
return null;
}
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
{
return appFolderInfo.AppDataFolder;

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -25,11 +26,20 @@ namespace NzbDrone.Common.Http.Dispatchers
{
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
// Deflate is not a standard and could break depending on implementation.
// we should just stick with the more compatible Gzip
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
if (PlatformInfo.IsMono)
{
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
webRequest.AutomaticDecompression = DecompressionMethods.None;
webRequest.Headers.Add("Accept-Encoding", "gzip");
}
else
{
// Deflate is not a standard and could break depending on implementation.
// we should just stick with the more compatible Gzip
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
}
webRequest.Method = request.Method.ToString();
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
webRequest.KeepAlive = request.ConnectionKeepAlive;
@@ -107,6 +117,19 @@ namespace NzbDrone.Common.Http.Dispatchers
try
{
data = responseStream.ToBytes();
if (PlatformInfo.IsMono && httpWebResponse.ContentEncoding == "gzip")
{
using (var compressedStream = new MemoryStream(data))
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var decompressedStream = new MemoryStream())
{
gzip.CopyTo(decompressedStream);
data = decompressedStream.ToArray();
}
httpWebResponse.Headers.Remove("Content-Encoding");
}
}
catch (Exception ex)
{

View File

@@ -267,6 +267,7 @@ namespace NzbDrone.Common.Http
public HttpResponse<T> Get<T>(HttpRequest request) where T : new()
{
var response = Get(request);
CheckResponseContentType(response);
return new HttpResponse<T>(response);
}
@@ -285,7 +286,16 @@ namespace NzbDrone.Common.Http
public HttpResponse<T> Post<T>(HttpRequest request) where T : new()
{
var response = Post(request);
CheckResponseContentType(response);
return new HttpResponse<T>(response);
}
private void CheckResponseContentType(HttpResponse response)
{
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
{
throw new UnexpectedHtmlContentException(response);
}
}
}
}

View File

@@ -7,13 +7,19 @@ namespace NzbDrone.Common.Http
public HttpRequest Request { get; private set; }
public HttpResponse Response { get; private set; }
public HttpException(HttpRequest request, HttpResponse response)
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url))
public HttpException(HttpRequest request, HttpResponse response, string message)
: base(message)
{
Request = request;
Response = response;
}
public HttpException(HttpRequest request, HttpResponse response)
: this(request, response, string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url))
{
}
public HttpException(HttpResponse response)
: this(response.Request, response)
{
@@ -30,4 +36,4 @@ namespace NzbDrone.Common.Http
return base.ToString();
}
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace NzbDrone.Common.Http
{
public class UnexpectedHtmlContentException : HttpException
{
public UnexpectedHtmlContentException(HttpResponse response)
: base(response.Request, response, $"Site responded with browser content instead of api data. This disruption may be temporary, please try again later. [{response.Request.Url}]")
{
}
}
}

View File

@@ -177,6 +177,7 @@
<Compile Include="Http\HttpRequestBuilderFactory.cs" />
<Compile Include="Http\Proxy\ProxyType.cs" />
<Compile Include="Http\TlsFailureException.cs" />
<Compile Include="Http\UnexpectedHtmlContentException.cs" />
<Compile Include="Http\TooManyRequestsException.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Http\UserAgentBuilder.cs" />

View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class AlreadyImportedSpecificationFixture : CoreTest<AlreadyImportedSpecification>
{
private const int FIRST_EPISODE_ID = 1;
private const string TITLE = "Series.Title.S01E01.720p.HDTV.x264-Sonarr";
private Series _series;
private QualityModel _hdtv720p;
private QualityModel _hdtv1080p;
private RemoteEpisode _remoteEpisode;
private List<History.History> _history;
[SetUp]
public void Setup()
{
var singleEpisodeList = new List<Episode>
{
new Episode
{
Id = FIRST_EPISODE_ID,
SeasonNumber = 12,
EpisodeNumber = 3,
EpisodeFileId = 1
}
};
_series = Builder<Series>.CreateNew()
.Build();
_hdtv720p = new QualityModel(Quality.HDTV720p, new Revision(version: 1));
_hdtv1080p = new QualityModel(Quality.HDTV1080p, new Revision(version: 1));
_remoteEpisode = new RemoteEpisode
{
Series = _series,
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = _hdtv720p },
Episodes = singleEpisodeList,
Release = Builder<ReleaseInfo>.CreateNew()
.Build()
};
_history = new List<History.History>();
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(true);
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByEpisodeId(It.IsAny<int>()))
.Returns(_history);
}
private void GivenCdhDisabled()
{
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(false);
}
private void GivenHistoryItem(string downloadId, string sourceTitle, QualityModel quality, HistoryEventType eventType)
{
_history.Add(new History.History
{
DownloadId = downloadId,
SourceTitle = sourceTitle,
Quality = quality,
Date = DateTime.UtcNow,
EventType = eventType
});
}
[Test]
public void should_be_accepted_if_CDH_is_disabled()
{
GivenCdhDisabled();
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_episode_does_not_have_a_file()
{
_remoteEpisode.Episodes.First().EpisodeFileId = 0;
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_episode_does_not_have_grabbed_event()
{
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_episode_does_not_have_imported_event()
{
GivenHistoryItem(Guid.NewGuid().ToString().ToUpper(), TITLE, _hdtv720p, HistoryEventType.Grabbed);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_grabbed_and_imported_quality_is_the_same()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.DownloadFolderImported);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_rejected_if_grabbed_download_id_matches_release_torrent_hash()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _hdtv1080p, HistoryEventType.DownloadFolderImported);
_remoteEpisode.Release = Builder<TorrentInfo>.CreateNew()
.With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
.With(t => t.InfoHash = downloadId)
.Build();
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
[Test]
public void should_be_rejected_if_release_title_matches_grabbed_event_source_title()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _hdtv1080p, HistoryEventType.DownloadFolderImported);
_remoteEpisode.Release = Builder<TorrentInfo>.CreateNew()
.With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
.With(t => t.InfoHash = downloadId)
.Build();
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
@@ -16,7 +16,7 @@ using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
{
[TestFixture]
public class HistorySpecificationFixture : CoreTest<HistorySpecification>

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Download;
using NzbDrone.Core.Test.Framework;
@@ -16,6 +17,10 @@ namespace NzbDrone.Core.Test.Download
public void SetUp()
{
_epoch = DateTime.UtcNow;
Mocker.GetMock<IRuntimeInfo>()
.SetupGet(v => v.StartTime)
.Returns(_epoch - TimeSpan.FromHours(1));
}
private DownloadClientStatus WithStatus(DownloadClientStatus status)

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
@@ -16,6 +17,10 @@ namespace NzbDrone.Core.Test.IndexerTests
public void SetUp()
{
_epoch = DateTime.UtcNow;
Mocker.GetMock<IRuntimeInfo>()
.SetupGet(v => v.StartTime)
.Returns(_epoch - TimeSpan.FromHours(1));
}
private void WithStatus(IndexerStatus status)

View File

@@ -3,6 +3,7 @@ using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@@ -124,5 +125,28 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
}
[Test]
public void should_use_special_info_when_not_null()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("S00E01");
var specialEpisodeInfo = fileEpisodeInfo.JsonClone();
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
Path = @"C:\Test\TV\Series\Specials\S00E01.mkv".AsOsAgnostic(),
Series = _series
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.ParseSpecialEpisodeTitle(fileEpisodeInfo, It.IsAny<string>(), _series))
.Returns(specialEpisodeInfo);
Subject.Aggregate(localEpisode, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(specialEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
}
}
}

View File

@@ -87,7 +87,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
private void GivenAugmentationSuccess()
{
Mocker.GetMock<IAugmentingService>()
Mocker.GetMock<IAggregationService>()
.Setup(s => s.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()))
.Callback<LocalEpisode, bool>((localEpisode, otherFiles) =>
{
@@ -158,7 +158,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
{
GivenSpecifications(_pass1);
Mocker.GetMock<IAugmentingService>()
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()))
.Throws<TestException>();
@@ -173,7 +173,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IAugmentingService>()
Mocker.GetMock<IAggregationService>()
.Verify(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
ExceptionVerification.ExpectedErrors(3);
@@ -195,7 +195,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
var decisions = Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IAugmentingService>()
Mocker.GetMock<IAggregationService>()
.Verify(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
decisions.Should().HaveCount(3);
@@ -205,7 +205,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
[Test]
public void should_return_a_decision_when_exception_is_caught()
{
Mocker.GetMock<IAugmentingService>()
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<bool>()))
.Throws<TestException>();

View File

@@ -1,123 +0,0 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
{
[TestFixture]
public class GrabbedReleaseQualityFixture : CoreTest<GrabbedReleaseQualitySpecification>
{
private LocalEpisode _localEpisode;
private DownloadClientItem _downloadClientItem;
[SetUp]
public void Setup()
{
_localEpisode = Builder<LocalEpisode>.CreateNew()
.With(l => l.Quality = new QualityModel(Quality.Bluray720p))
.Build();
_downloadClientItem = Builder<DownloadClientItem>.CreateNew()
.Build();
}
private void GivenHistory(List<History.History> history)
{
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(history);
}
[Test]
public void should_be_accepted_when_downloadClientItem_is_null()
{
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_no_history_for_downloadId()
{
GivenHistory(new List<History.History>());
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_no_grabbed_history_for_downloadId()
{
var history = Builder<History.History>.CreateListOfSize(1)
.All()
.With(h => h.EventType = HistoryEventType.Unknown)
.BuildList();
GivenHistory(history);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_grabbed_history_is_for_a_season_pack()
{
var history = Builder<History.History>.CreateListOfSize(1)
.All()
.With(h => h.EventType = HistoryEventType.Grabbed)
.With(h => h.Quality = _localEpisode.Quality)
.With(h => h.SourceTitle = "Series.Title.S01.720p.HDTV.x264-RlsGroup")
.BuildList();
GivenHistory(history);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_grabbed_history_quality_is_unknown()
{
var history = Builder<History.History>.CreateListOfSize(1)
.All()
.With(h => h.EventType = HistoryEventType.Grabbed)
.With(h => h.Quality = new QualityModel(Quality.Unknown))
.BuildList();
GivenHistory(history);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_grabbed_history_quality_matches()
{
var history = Builder<History.History>.CreateListOfSize(1)
.All()
.With(h => h.EventType = HistoryEventType.Grabbed)
.With(h => h.Quality = _localEpisode.Quality)
.BuildList();
GivenHistory(history);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
}
[Test]
public void should_be_rejected_if_grabbed_history_quality_does_not_match()
{
var history = Builder<History.History>.CreateListOfSize(1)
.All()
.With(h => h.EventType = HistoryEventType.Grabbed)
.With(h => h.Quality = new QualityModel(Quality.HDTV720p))
.BuildList();
GivenHistory(history);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeFalse();
}
}
}

View File

@@ -5,6 +5,7 @@ using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles;
@@ -34,6 +35,8 @@ namespace NzbDrone.Core.Test.MediaFiles
_rejectedDecisions = new List<ImportDecision>();
_approvedDecisions = new List<ImportDecision>();
var outputPath = @"C:\Test\Unsorted\TV\30.Rock.S01E01".AsOsAgnostic();
var series = Builder<Series>.CreateNew()
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(s => s.Path = @"C:\Test\TV\30 Rock".AsOsAgnostic())
@@ -66,7 +69,14 @@ namespace NzbDrone.Core.Test.MediaFiles
.Setup(s => s.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), It.IsAny<LocalEpisode>(), It.IsAny<bool>()))
.Returns(new EpisodeFileMoveResult());
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
_downloadClientItem = Builder<DownloadClientItem>.CreateNew()
.With(d => d.OutputPath = new OsPath(outputPath))
.Build();
}
private void GivenNewDownload()
{
_approvedDecisions.ForEach(a => a.LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), Path.GetFileName(a.LocalEpisode.Path)));
}
[Test]
@@ -140,6 +150,7 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_use_nzb_title_as_scene_name()
{
GivenNewDownload();
_downloadClientItem.Title = "malcolm.in.the.middle.s02e05.dvdrip.xvid-ingot";
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
@@ -152,6 +163,7 @@ namespace NzbDrone.Core.Test.MediaFiles
[TestCase(".nzb")]
public void should_remove_extension_from_nzb_title_for_scene_name(string extension)
{
GivenNewDownload();
var title = "malcolm.in.the.middle.s02e05.dvdrip.xvid-ingot";
_downloadClientItem.Title = title + extension;
@@ -164,7 +176,8 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_not_use_nzb_title_as_scene_name_if_full_season()
{
_approvedDecisions.First().LocalEpisode.Path = "c:\\tv\\season1\\malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot.mkv".AsOsAgnostic();
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot.mkv");
_downloadClientItem.Title = "malcolm.in.the.middle.s02.dvdrip.xvid-ingot";
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
@@ -175,7 +188,8 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_use_file_name_as_scenename_only_if_it_looks_like_scenename()
{
_approvedDecisions.First().LocalEpisode.Path = "c:\\tv\\malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot.mkv".AsOsAgnostic();
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot.mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
@@ -185,7 +199,8 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_not_use_file_name_as_scenename_if_it_doesnt_looks_like_scenename()
{
_approvedDecisions.First().LocalEpisode.Path = "c:\\tv\\aaaaa.mkv".AsOsAgnostic();
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
@@ -223,7 +238,11 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_copy_when_cannot_move_files_downloads()
{
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", CanMoveFiles = false});
GivenNewDownload();
_downloadClientItem.Title = "30.Rock.S01E01";
_downloadClientItem.CanMoveFiles = false;
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IUpgradeMediaFiles>()
.Verify(v => v.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), _approvedDecisions.First().LocalEpisode, true), Times.Once());
@@ -232,10 +251,71 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_use_override_importmode()
{
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", CanMoveFiles = false }, ImportMode.Move);
GivenNewDownload();
_downloadClientItem.Title = "30.Rock.S01E01";
_downloadClientItem.CanMoveFiles = false;
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem, ImportMode.Move);
Mocker.GetMock<IUpgradeMediaFiles>()
.Verify(v => v.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), _approvedDecisions.First().LocalEpisode, false), Times.Once());
}
[Test]
public void should_use_file_name_only_for_download_client_item_without_a_job_folder()
{
var fileName = "Series.Title.S01E01.720p.HDTV.x264-Sonarr.mkv";
var path = Path.Combine(@"C:\Test\Unsorted\TV\".AsOsAgnostic(), fileName);
_downloadClientItem.OutputPath = new OsPath(path);
_approvedDecisions.First().LocalEpisode.Path = path;
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.OriginalFilePath == fileName)));
}
[Test]
public void should_use_folder_and_file_name_only_for_download_client_item_with_a_job_folder()
{
var name = "Series.Title.S01E01.720p.HDTV.x264-Sonarr";
var outputPath = Path.Combine(@"C:\Test\Unsorted\TV\".AsOsAgnostic(), name);
_downloadClientItem.OutputPath = new OsPath(outputPath);
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(outputPath, name + ".mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.OriginalFilePath == $"{name}\\{name}.mkv".AsOsAgnostic())));
}
[Test]
public void should_include_intermediate_folders_for_download_client_item_with_a_job_folder()
{
var name = "Series.Title.S01E01.720p.HDTV.x264-Sonarr";
var outputPath = Path.Combine(@"C:\Test\Unsorted\TV\".AsOsAgnostic(), name);
_downloadClientItem.OutputPath = new OsPath(outputPath);
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(outputPath, "subfolder", name + ".mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.OriginalFilePath == $"{name}\\subfolder\\{name}.mkv".AsOsAgnostic())));
}
[Test]
public void should_use_folder_info_release_title_to_find_relative_path()
{
var name = "Series.Title.S01E01.720p.HDTV.x264-Sonarr";
var outputPath = Path.Combine(@"C:\Test\Unsorted\TV\".AsOsAgnostic(), name);
var localEpisode = _approvedDecisions.First().LocalEpisode;
localEpisode.FolderEpisodeInfo = new ParsedEpisodeInfo { ReleaseTitle = name };
localEpisode.Path = Path.Combine(outputPath, "subfolder", name + ".mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, null);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.OriginalFilePath == $"{name}\\subfolder\\{name}.mkv".AsOsAgnostic())));
}
}
}

View File

@@ -44,12 +44,12 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
[TestCase("tvdbid: 0")]
[TestCase("tvdbid: -12")]
[TestCase("tvdbid:289578")]
[TestCase("adjalkwdjkalwdjklawjdlKAJD;EF")]
[TestCase("adjalkwdjkalwdjklawjdlKAJD")]
public void no_search_result(string term)
{
var result = Subject.SearchForNewSeries(term);
result.Should().BeEmpty();
ExceptionVerification.IgnoreWarns();
}
}

View File

@@ -166,7 +166,8 @@
<Compile Include="DecisionEngineTests\ProtocolSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\CutoffSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\DownloadDecisionMakerFixture.cs" />
<Compile Include="DecisionEngineTests\HistorySpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\AlreadyImportedSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\HistorySpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\LanguageSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\MonitoredEpisodeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\QueueSpecificationFixture.cs" />
@@ -316,7 +317,6 @@
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\SameFileSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\GrabbedReleaseQualityFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\MatchesFolderSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecificationFixture.cs" />

View File

@@ -91,6 +91,9 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Love Rerun 2018 06 720p x265 AOZ.mp4", "Love Rerun 2018", 6, 0, 0)]
[TestCase("Boku No Hero Academia S03 - EP14 VOSTFR [1080p] [HardSub] Yass'Kun", "Boku No Hero Academia S03", 14, 0, 0)]
[TestCase("Boku No Hero Academia S3 - 15 VOSTFR [720p]", "Boku No Hero Academia S3", 15, 0, 0)]
[TestCase("Tokyo Ghoul: RE S2 - Episode 4 VOSTFR (1080p)", "Tokyo Ghoul RE S2", 4, 0, 0)]
[TestCase("To Aru Majutsu no Index III - Episode 5 VOSTFR (1080p)", "To Aru Majutsu no Index III", 5, 0, 0)]
[TestCase("[Prout] Steins;Gate 0 - Episode 5 VOSTFR (BDRip 1920x1080 x264 FLAC)", "Steins;Gate 0", 5, 0, 0)]
//[TestCase("", "", 0, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
{
@@ -133,5 +136,29 @@ namespace NzbDrone.Core.Test.ParserTests
result.SeriesTitle.Should().Be(title);
result.FullSeason.Should().BeFalse();
}
[TestCase("[Vivid] Living Sky Saga S01 [Web][MKV][h264 10-bit][1080p][AAC 2.0]", "Living Sky Saga", 1)]
public void should_parse_anime_season_packs(string postTitle, string title, int seasonNumber)
{
var result = Parser.Parser.ParseTitle(postTitle);
result.Should().NotBeNull();
result.AbsoluteEpisodeNumbers.Should().BeEmpty();
result.SeriesTitle.Should().Be(title);
result.FullSeason.Should().BeTrue();
result.SeasonNumber.Should().Be(seasonNumber);
}
[TestCase("[HorribleSubs] Goblin Slayer - 10.5 [1080p].mkv", "Goblin Slayer", 10.5)]
public void should_handle_anime_recap_numbering(string postTitle, string title, double specialEpisodeNumber)
{
var result = Parser.Parser.ParseTitle(postTitle);
result.Should().NotBeNull();
result.SeriesTitle.Should().Be(title);
result.AbsoluteEpisodeNumbers.Should().BeEmpty();
result.SpecialAbsoluteEpisodeNumbers.Should().NotBeEmpty();
result.SpecialAbsoluteEpisodeNumbers.Should().BeEquivalentTo(new[] { (decimal)specialEpisodeNumber });
result.FullSeason.Should().BeFalse();
}
}
}

View File

@@ -28,6 +28,8 @@ namespace NzbDrone.Core.Test.ParserTests
//[TestCase("Corrie.07.01.15", "Corrie", 2015, 1, 7)]
[TestCase("The Nightly Show with Larry Wilmore 2015 02 09 WEBRIP s01e13", "The Nightly Show with Larry Wilmore", 2015, 2, 9)]
[TestCase("Jimmy_Fallon_2018_06_22_Seth_Meyers_720p_HEVC_x265-MeGusta", "Jimmy Fallon", 2018, 6, 22)]
[TestCase("20161024- Exotic Payback.21x41_720.mkv", "", 2016, 10, 24)]
[TestCase("2018-11-14.1080.all.mp4", "", 2018, 11, 14)]
//[TestCase("", "", 0, 0, 0)]
public void should_parse_daily_episode(string postTitle, string title, int year, int month, int day)
{

View File

@@ -87,6 +87,13 @@ namespace NzbDrone.Core.Test.ParserTests
"Fargo",
Quality.WEBDL1080p,
"RARBG"
},
new object[]
{
@"C:\Test\XxQVHK4GJMP3n2dLpmhW\XxQVHK4GJMP3n2dLpmhW\MKV\010E70S.yhcranA.fo.snoS.mkv".AsOsAgnostic(),
"Sons of Anarchy",
Quality.HDTV720p,
null
}
};

View File

@@ -118,6 +118,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Hells.Kitchen.US.S12E17.HR.WS.PDTV.X264-DIMENSION", false)]
[TestCase("Survivorman.The.Lost.Pilots.Summer.HR.WS.PDTV.x264-DHD", false)]
[TestCase("Victoria S01E07 - Motor zmen (CZ)[TvRip][HEVC][720p]", false)]
[TestCase("flashpoint.S05E06.720p.HDTV.x264-FHD", false)]
public void should_parse_hdtv720p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.HDTV720p, proper);
@@ -130,11 +131,27 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Dexter - S01E01 - Title [HDTV-1080p]", false)]
[TestCase("[HorribleSubs] Yowamushi Pedal - 32 [1080p]", false)]
[TestCase("Victoria S01E07 - Motor zmen (CZ)[TvRip][HEVC][1080p]", false)]
[TestCase("Sword Art Online Alicization 04 vostfr FHD", false)]
[TestCase("Goblin Slayer 04 vostfr FHD.mkv", false)]
[TestCase("[Onii-ChanSub] SSSS.Gridman - 02 vostfr (FHD 1080p 10bits).mkv", false)]
[TestCase("[Miaou] Akanesasu Shoujo 02 VOSTFR FHD 10 bits", false)]
[TestCase("[mhastream.com]_Episode_05_FHD.mp4", false)]
[TestCase("[Kousei]_One_Piece_ - _609_[FHD][648A87C7].mp4", false)]
[TestCase("Presunto culpable 1x02 Culpabilidad [HDTV 1080i AVC MP2 2.0 Sub][GrupoHDS]", false)]
[TestCase("Cuéntame cómo pasó - 19x15 [344] Cuarenta años de baile [HDTV 1080i AVC MP2 2.0 Sub][GrupoHDS]", false)]
public void should_parse_hdtv1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.HDTV1080p, proper);
}
[TestCase("My Title - S01E01 - EpTitle [HEVC 4k DTSHD-MA-6ch]", false)]
[TestCase("My Title - S01E01 - EpTitle [HEVC-4k DTSHD-MA-6ch]", false)]
[TestCase("My Title - S01E01 - EpTitle [4k HEVC DTSHD-MA-6ch]", false)]
public void should_parse_hdtv2160p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.HDTV2160p, proper);
}
[TestCase("Arrested.Development.S04E01.720p.WEBRip.AAC2.0.x264-NFRiP", false)]
[TestCase("Vanguard S01E04 Mexicos Death Train 720p WEB DL", false)]
[TestCase("Hawaii Five 0 S02E21 720p WEB DL DD5 1 H 264", false)]
@@ -178,6 +195,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Incorporated.S01E08.Das.geloeschte.Ich.German.DD51.Dubbed.DL.1080p.AmazonHD.x264-TVS", false)]
[TestCase("Death.Note.2017.German.DD51.DL.1080p.NetflixHD.x264-TVS", false)]
[TestCase("Played.S01E08.Pro.Gamer.1440p.BKPL.WEB-DL.H.264-LiGHT", false)]
[TestCase("Good.Luck.Charlie.S04E11.Teddy's.Choice.FHD.1080p.Web-DL", false)]
[TestCase("Outlander.S04E03.The.False.Bride.1080p.NF.WEB.DDP5.1.x264-NTb[rartv]", false)]
public void should_parse_webdl1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.WEBDL1080p, proper);
@@ -210,6 +229,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[Elysium]Lucky.Star.01(BD.720p.AAC.DA)[0BB96AD8].mkv", false)]
[TestCase("Battlestar.Galactica.S01E01.33.720p.HDDVD.x264-SiNNERS.mkv", false)]
[TestCase("The.Expanse.S01E07.RERIP.720p.BluRay.x264-DEMAND", true)]
[TestCase("Sans.Laisser.De.Traces.FRENCH.720p.BluRay.x264-FHD", false)]
public void should_parse_bluray720p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.Bluray720p, proper);
@@ -225,6 +245,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("WEEDS.S03E01-06.DUAL.1080p.Blu-ray.AC3.-HELLYWOOD.avi", false)]
[TestCase("[Coalgirls]_Durarara!!_01_(1920x1080_Blu-ray_FLAC)_[8370CB8F].mkv", false)]
[TestCase("Planet.Earth.S01E11.Ocean.Deep.1080p.HD-DVD.DD.VC1-TRB", false)]
[TestCase("Spirited Away(2001) Bluray FHD Hi10P.mkv", false)]
public void should_parse_bluray1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.Bluray1080p, proper);
@@ -232,6 +253,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("House.of.Cards.US.s05e13.4K.UHD.Bluray", false)]
[TestCase("House.of.Cards.US.s05e13.UHD.4K.Bluray", false)]
[TestCase("[DameDesuYo] Backlog Bundle - Part 1 (BD 4K 8bit FLAC)", false)]
public void should_parse_bluray2160p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.Bluray2160p, proper);

View File

@@ -1,9 +1,10 @@
using System;
using System;
using System.Linq;
using FluentAssertions;
using Moq;
using NLog;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.ThingiProvider;
@@ -25,8 +26,8 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
public class MockProviderStatusService : ProviderStatusServiceBase<IMockProvider, MockProviderStatus>
{
public MockProviderStatusService(IMockProviderStatusRepository providerStatusRepository, IEventAggregator eventAggregator, Logger logger)
: base(providerStatusRepository, eventAggregator, logger)
public MockProviderStatusService(IMockProviderStatusRepository providerStatusRepository, IEventAggregator eventAggregator, IRuntimeInfo runtimeInfo, Logger logger)
: base(providerStatusRepository, eventAggregator, runtimeInfo, logger)
{
}
@@ -40,9 +41,20 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
public void SetUp()
{
_epoch = DateTime.UtcNow;
Mocker.GetMock<IRuntimeInfo>()
.SetupGet(v => v.StartTime)
.Returns(_epoch - TimeSpan.FromHours(1));
}
private void WithStatus(MockProviderStatus status)
private void GivenRecentStartup()
{
Mocker.GetMock<IRuntimeInfo>()
.SetupGet(v => v.StartTime)
.Returns(_epoch - TimeSpan.FromMinutes(12));
}
private MockProviderStatus WithStatus(MockProviderStatus status)
{
Mocker.GetMock<IMockProviderStatusRepository>()
.Setup(v => v.FindByProviderId(1))
@@ -51,6 +63,8 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
Mocker.GetMock<IMockProviderStatusRepository>()
.Setup(v => v.All())
.Returns(new[] { status });
return status;
}
private void VerifyUpdate()
@@ -122,5 +136,32 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
status.DisabledTill.Should().HaveValue();
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(15), 500);
}
[Test]
public void should_not_escalate_further_till_after_5_minutes_since_startup()
{
GivenRecentStartup();
var origStatus = WithStatus(new MockProviderStatus
{
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
EscalationLevel = 3
});
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
Subject.RecordFailure(1);
var status = Subject.GetBlockedProviders().FirstOrDefault();
status.Should().NotBeNull();
origStatus.EscalationLevel.Should().Be(3);
status.DisabledTill.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
}
}
}

View File

@@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Tv;
@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Test.TvTests
[TestCase("A to Z", 281588, "a to z")]
[TestCase("A.D. The Bible Continues", 289260, "ad bible continues")]
[TestCase("A.P. Bio", 328534, "ap bio")]
[TestCase("The A-Team", 77904, "ateam")]
public void should_use_precomputed_title(string title, int tvdbId, string expected)
{
SeriesTitleNormalizer.Normalize(title, tvdbId).Should().Be(expected);

View File

@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Datastore
var connectionBuilder = new SQLiteConnectionStringBuilder();
connectionBuilder.DataSource = dbPath;
connectionBuilder.CacheSize = (int)-10.Megabytes();
connectionBuilder.CacheSize = (int)-10000;
connectionBuilder.DateTimeKind = DateTimeKind.Utc;
connectionBuilder.JournalMode = OsInfo.IsOsx ? SQLiteJournalModeEnum.Truncate : SQLiteJournalModeEnum.Wal;
connectionBuilder.Pooling = true;

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(129)]
public class add_relative_original_path_to_episode_file : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("EpisodeFiles").AddColumn("OriginalFilePath").AsString().Nullable();
}
}
}

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(130)]
public class episode_last_searched_time : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Episodes").AddColumn("LastSearchTime").AsDateTime().Nullable();
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class AlreadyImportedSpecification : IDecisionEngineSpecification
{
private readonly IHistoryService _historyService;
private readonly IConfigService _configService;
private readonly Logger _logger;
public AlreadyImportedSpecification(IHistoryService historyService,
IConfigService configService,
Logger logger)
{
_historyService = historyService;
_configService = configService;
_logger = logger;
}
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
if (!cdhEnabled)
{
_logger.Debug("Skipping already imported check because CDH is disabled");
return Decision.Accept();
}
_logger.Debug("Performing alerady imported check on report");
foreach (var episode in subject.Episodes)
{
if (!episode.HasFile)
{
_logger.Debug("Skipping already imported check for episode without file");
continue;
}
var historyForEpisode = _historyService.FindByEpisodeId(episode.Id);
var lastGrabbed = historyForEpisode.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
if (lastGrabbed == null)
{
continue;
}
var imported = historyForEpisode.FirstOrDefault(h =>
h.EventType == HistoryEventType.DownloadFolderImported &&
h.DownloadId == lastGrabbed.DownloadId);
if (imported == null)
{
continue;
}
// This is really only a guard against redownloading the same release over
// and over when the grabbed and imported qualities do not match, if they do
// match skip this check.
if (lastGrabbed.Quality.Equals(imported.Quality))
{
continue;
}
var release = subject.Release;
if (release.DownloadProtocol == DownloadProtocol.Torrent)
{
var torrentInfo = release as TorrentInfo;
if (torrentInfo != null && torrentInfo.InfoHash.ToUpper() == lastGrabbed.DownloadId)
{
_logger.Debug("Has same torrent hash as a grabbed and imported release");
return Decision.Reject("Has same torrent hash as a grabbed and imported release");
}
}
// Only based on title because a release with the same title on another indexer/released at
// a different time very likely has the exact same content and we don't need to also try it.
if (release.Title.Equals(lastGrabbed.SourceTitle, StringComparison.InvariantCultureIgnoreCase))
{
_logger.Debug("Has same release name as a grabbed and imported release");
return Decision.Reject("Has same release name as a grabbed and imported release");
}
}
return Decision.Accept();
}
}
}

View File

@@ -30,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
if (!subject.Series.Monitored)
{
_logger.Debug("{0} is present in the DB but not tracked. skipping.", subject.Series);
_logger.Debug("{0} is present in the DB but not tracked. Rejecting", subject.Series);
return Decision.Reject("Series is not monitored");
}
@@ -40,8 +40,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
return Decision.Accept();
}
_logger.Debug("Only {0}/{1} episodes are monitored. skipping.", monitoredCount, subject.Episodes.Count);
return Decision.Reject("Episode is not monitored");
if (subject.Episodes.Count == 1)
{
_logger.Debug("Episode is not monitored. Rejecting", monitoredCount, subject.Episodes.Count);
return Decision.Reject("Episode is not monitored");
}
if (monitoredCount == 0)
{
_logger.Debug("No episodes in the release are monitored. Rejecting", monitoredCount, subject.Episodes.Count);
}
else
{
_logger.Debug("Only {0}/{1} episodes in the release are monitored. Rejecting", monitoredCount, subject.Episodes.Count);
}
return Decision.Reject("One or more episodes is not monitored");
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -13,6 +13,7 @@ using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Download.Clients.DownloadStation
@@ -47,6 +48,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public override string Name => "Download Station";
public override ProviderMessage Message => new ProviderMessage("Sonarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning);
protected IEnumerable<DownloadStationTask> GetTasks()
{
return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.BT.ToString().ToLower());

View File

@@ -11,6 +11,7 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Download.Clients.DownloadStation
@@ -46,6 +47,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
public override string Name => "Download Station";
public override ProviderMessage Message => new ProviderMessage("Sonarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning);
protected IEnumerable<DownloadStationTask> GetTasks()
{
return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.NZB.ToString().ToLower());

View File

@@ -1,5 +1,6 @@
using System;
using System;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider.Status;
@@ -12,8 +13,8 @@ namespace NzbDrone.Core.Download
public class DownloadClientStatusService : ProviderStatusServiceBase<IDownloadClient, DownloadClientStatus>, IDownloadClientStatusService
{
public DownloadClientStatusService(IDownloadClientStatusRepository providerStatusRepository, IEventAggregator eventAggregator, Logger logger)
: base(providerStatusRepository, eventAggregator, logger)
public DownloadClientStatusService(IDownloadClientStatusRepository providerStatusRepository, IEventAggregator eventAggregator, IRuntimeInfo runtimeInfo, Logger logger)
: base(providerStatusRepository, eventAggregator, runtimeInfo, logger)
{
MinimumTimeSinceInitialFailure = TimeSpan.FromMinutes(5);
MaximumEscalationLevel = 5;

View File

@@ -16,18 +16,18 @@ namespace NzbDrone.Core.Extras.Metadata
public class ExistingMetadataImporter : ImportExistingExtraFilesBase<MetadataFile>
{
private readonly IExtraFileService<MetadataFile> _metadataFileService;
private readonly IAugmentingService _augmentingService;
private readonly IAggregationService _aggregationService;
private readonly Logger _logger;
private readonly List<IMetadata> _consumers;
public ExistingMetadataImporter(IExtraFileService<MetadataFile> metadataFileService,
IEnumerable<IMetadata> consumers,
IAugmentingService augmentingService,
IAggregationService aggregationService,
Logger logger)
: base(metadataFileService)
{
_metadataFileService = metadataFileService;
_augmentingService = augmentingService;
_aggregationService = aggregationService;
_logger = logger;
_consumers = consumers.ToList();
}
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Extras.Metadata
try
{
_augmentingService.Augment(localEpisode, false);
_aggregationService.Augment(localEpisode, false);
}
catch (AugmentingFailedException ex)
{

View File

@@ -15,16 +15,16 @@ namespace NzbDrone.Core.Extras.Others
public class ExistingOtherExtraImporter : ImportExistingExtraFilesBase<OtherExtraFile>
{
private readonly IExtraFileService<OtherExtraFile> _otherExtraFileService;
private readonly IAugmentingService _augmentingService;
private readonly IAggregationService _aggregationService;
private readonly Logger _logger;
public ExistingOtherExtraImporter(IExtraFileService<OtherExtraFile> otherExtraFileService,
IAugmentingService augmentingService,
IAggregationService aggregationService,
Logger logger)
: base(otherExtraFileService)
{
_otherExtraFileService = otherExtraFileService;
_augmentingService = augmentingService;
_aggregationService = aggregationService;
_logger = logger;
}
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Extras.Others
try
{
_augmentingService.Augment(localEpisode, false);
_aggregationService.Augment(localEpisode, false);
}
catch (AugmentingFailedException ex)
{

View File

@@ -14,16 +14,16 @@ namespace NzbDrone.Core.Extras.Subtitles
public class ExistingSubtitleImporter : ImportExistingExtraFilesBase<SubtitleFile>
{
private readonly IExtraFileService<SubtitleFile> _subtitleFileService;
private readonly IAugmentingService _augmentingService;
private readonly IAggregationService _aggregationService;
private readonly Logger _logger;
public ExistingSubtitleImporter(IExtraFileService<SubtitleFile> subtitleFileService,
IAugmentingService augmentingService,
IAggregationService aggregationService,
Logger logger)
: base (subtitleFileService)
{
_subtitleFileService = subtitleFileService;
_augmentingService = augmentingService;
_aggregationService = aggregationService;
_logger = logger;
}
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Extras.Subtitles
try
{
_augmentingService.Augment(localEpisode, false);
_aggregationService.Augment(localEpisode, false);
}
catch (AugmentingFailedException ex)
{

View File

@@ -13,6 +13,7 @@ namespace NzbDrone.Core.History
{
List<QualityModel> GetBestQualityInHistory(int episodeId);
History MostRecentForEpisode(int episodeId);
List<History> FindByEpisodeId(int episodeId);
History MostRecentForDownloadId(string downloadId);
List<History> FindByDownloadId(string downloadId);
List<History> FindDownloadHistory(int idSeriesId, QualityModel quality);
@@ -43,6 +44,13 @@ namespace NzbDrone.Core.History
.FirstOrDefault();
}
public List<History> FindByEpisodeId(int episodeId)
{
return Query.Where(h => h.EpisodeId == episodeId)
.OrderByDescending(h => h.Date)
.ToList();
}
public History MostRecentForDownloadId(string downloadId)
{
return Query.Where(h => h.DownloadId == downloadId)

View File

@@ -21,6 +21,7 @@ namespace NzbDrone.Core.History
QualityModel GetBestQualityInHistory(Profile profile, int episodeId);
PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
History MostRecentForEpisode(int episodeId);
List<History> FindByEpisodeId(int episodeId);
History MostRecentForDownloadId(string downloadId);
History Get(int historyId);
List<History> Find(string downloadId, HistoryEventType eventType);
@@ -55,6 +56,11 @@ namespace NzbDrone.Core.History
return _historyRepository.MostRecentForEpisode(episodeId);
}
public List<History> FindByEpisodeId(int episodeId)
{
return _historyRepository.FindByEpisodeId(episodeId);
}
public History MostRecentForDownloadId(string downloadId)
{
return _historyRepository.MostRecentForDownloadId(downloadId);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
@@ -314,6 +314,16 @@ namespace NzbDrone.Core.IndexerSearch
_logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
// Update the last search time for all episodes if at least 1 indexer was searched.
if (indexers.Any())
{
var lastSearchTime = DateTime.UtcNow;
_logger.Debug("Setting last search time to: {0}", lastSearchTime);
criteriaBase.Episodes.ForEach(e => e.LastSearchTime = lastSearchTime);
_episodeService.UpdateEpisodes(criteriaBase.Episodes);
}
return _makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList();
}
}

View File

@@ -1,4 +1,5 @@
using NLog;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider.Status;
@@ -14,8 +15,8 @@ namespace NzbDrone.Core.Indexers
public class IndexerStatusService : ProviderStatusServiceBase<IIndexer, IndexerStatus>, IIndexerStatusService
{
public IndexerStatusService(IIndexerStatusRepository providerStatusRepository, IEventAggregator eventAggregator, Logger logger)
: base(providerStatusRepository, eventAggregator, logger)
public IndexerStatusService(IIndexerStatusRepository providerStatusRepository, IEventAggregator eventAggregator, IRuntimeInfo runtimeInfo, Logger logger)
: base(providerStatusRepository, eventAggregator, runtimeInfo, logger)
{
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -253,12 +253,25 @@ namespace NzbDrone.Core.Indexers
protected virtual RssEnclosure[] GetEnclosures(XElement item)
{
var enclosures = item.Elements("enclosure")
.Select(v => new RssEnclosure
.Select(v =>
{
Url = v.Attribute("url").Value,
Type = v.Attribute("type").Value,
Length = (long)v.Attribute("length")
try
{
return new RssEnclosure
{
Url = v.Attribute("url").Value,
Type = v.Attribute("type").Value,
Length = (long)v.Attribute("length")
};
}
catch (Exception e)
{
_logger.Warn(e, "Failed to get enclosure for: {0}", item.Title());
}
return null;
})
.Where(v => v != null)
.ToArray();
return enclosures;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Marr.Data;
using NzbDrone.Core.Datastore;
@@ -17,6 +17,7 @@ namespace NzbDrone.Core.MediaFiles
public string Path { get; set; }
public long Size { get; set; }
public DateTime DateAdded { get; set; }
public string OriginalFilePath { get; set; }
public string SceneName { get; set; }
public string ReleaseGroup { get; set; }
public QualityModel Quality { get; set; }

View File

@@ -10,12 +10,12 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation
{
public interface IAugmentingService
public interface IAggregationService
{
LocalEpisode Augment(LocalEpisode localEpisode, bool otherFiles);
}
public class AugmentingService : IAugmentingService
public class AggregationService : IAggregationService
{
private readonly IEnumerable<IAggregateLocalEpisode> _augmenters;
private readonly IDiskProvider _diskProvider;
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation
private readonly IConfigService _configService;
private readonly Logger _logger;
public AugmentingService(IEnumerable<IAggregateLocalEpisode> augmenters,
public AggregationService(IEnumerable<IAggregateLocalEpisode> augmenters,
IDiskProvider diskProvider,
IVideoFileInfoReader videoFileInfoReader,
IConfigService configService,
@@ -38,11 +38,13 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation
public LocalEpisode Augment(LocalEpisode localEpisode, bool otherFiles)
{
var isMediaFile = MediaFileExtensions.Extensions.Contains(Path.GetExtension(localEpisode.Path));
if (localEpisode.DownloadClientEpisodeInfo == null &&
localEpisode.FolderEpisodeInfo == null &&
localEpisode.FileEpisodeInfo == null)
{
if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(localEpisode.Path)))
if (isMediaFile)
{
throw new AugmentingFailedException("Unable to parse episode info from path: {0}", localEpisode.Path);
}
@@ -50,7 +52,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation
localEpisode.Size = _diskProvider.GetFileSize(localEpisode.Path);
if (!localEpisode.ExistingFile || _configService.EnableMediaInfo)
if (isMediaFile && (!localEpisode.ExistingFile || _configService.EnableMediaInfo))
{
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(localEpisode.Path);
}

View File

@@ -49,7 +49,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators
var title = Path.GetFileNameWithoutExtension(localEpisode.Path);
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, title, localEpisode.Series);
return specialEpisodeInfo;
if (specialEpisodeInfo != null)
{
parsedEpisodeInfo = specialEpisodeInfo;
}
}
return parsedEpisodeInfo;

View File

@@ -102,6 +102,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
if (newDownload)
{
episodeFile.OriginalFilePath = GetOriginalFilePath(downloadClientItem, localEpisode);
episodeFile.SceneName = GetSceneName(downloadClientItem, localEpisode);
var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly);
@@ -148,6 +149,37 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return importResults;
}
private string GetOriginalFilePath(DownloadClientItem downloadClientItem, LocalEpisode localEpisode)
{
if (downloadClientItem != null)
{
return downloadClientItem.OutputPath.Directory.ToString().GetRelativePath(localEpisode.Path);
}
var path = localEpisode.Path;
var folderEpisodeInfo = localEpisode.FolderEpisodeInfo;
if (folderEpisodeInfo != null)
{
var folderPath = path.GetAncestorPath(folderEpisodeInfo.ReleaseTitle);
if (folderPath != null)
{
return folderPath.GetParentPath().GetRelativePath(path);
}
}
var parentPath = path.GetParentPath();
var grandparentPath = parentPath.GetParentPath();
if (grandparentPath != null)
{
return grandparentPath.GetRelativePath(path);
}
return Path.Combine(Path.GetFileName(parentPath), Path.GetFileName(path));
}
private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode)
{
if (downloadClientItem != null)

View File

@@ -23,21 +23,21 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
private readonly IEnumerable<IImportDecisionEngineSpecification> _specifications;
private readonly IMediaFileService _mediaFileService;
private readonly IAugmentingService _augmentingService;
private readonly IAggregationService _aggregationService;
private readonly IDiskProvider _diskProvider;
private readonly IDetectSample _detectSample;
private readonly Logger _logger;
public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification> specifications,
IMediaFileService mediaFileService,
IAugmentingService augmentingService,
IAggregationService aggregationService,
IDiskProvider diskProvider,
IDetectSample detectSample,
Logger logger)
{
_specifications = specifications;
_mediaFileService = mediaFileService;
_augmentingService = augmentingService;
_aggregationService = aggregationService;
_diskProvider = diskProvider;
_detectSample = detectSample;
_logger = logger;
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
try
{
_augmentingService.Augment(localEpisode, otherFiles);
_aggregationService.Augment(localEpisode, otherFiles);
if (localEpisode.Episodes.Empty())
{

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
private readonly IEpisodeService _episodeService;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
private readonly IAugmentingService _augmentingService;
private readonly IAggregationService _aggregationService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IEventAggregator _eventAggregator;
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
ISeriesService seriesService,
IEpisodeService episodeService,
IVideoFileInfoReader videoFileInfoReader,
IAugmentingService augmentingService,
IAggregationService aggregationService,
IImportApprovedEpisodes importApprovedEpisodes,
ITrackedDownloadService trackedDownloadService,
IDownloadedEpisodesImportService downloadedEpisodesImportService,
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
_seriesService = seriesService;
_episodeService = episodeService;
_videoFileInfoReader = videoFileInfoReader;
_augmentingService = augmentingService;
_aggregationService = aggregationService;
_importApprovedEpisodes = importApprovedEpisodes;
_trackedDownloadService = trackedDownloadService;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
@@ -281,7 +281,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
localEpisode.FolderEpisodeInfo = Parser.Parser.ParseTitle(file.FolderName);
}
localEpisode = _augmentingService.Augment(localEpisode, false);
localEpisode = _aggregationService.Augment(localEpisode, false);
// Apply the user-chosen values.
localEpisode.Series = series;

View File

@@ -1,60 +0,0 @@
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
public class GrabbedReleaseQualitySpecification : IImportDecisionEngineSpecification
{
private readonly IHistoryService _historyService;
private readonly Logger _logger;
public GrabbedReleaseQualitySpecification(IHistoryService historyService, Logger logger)
{
_historyService = historyService;
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (downloadClientItem == null)
{
_logger.Debug("No download client item provided, skipping.");
return Decision.Accept();
}
var grabbedHistory = _historyService.FindByDownloadId(downloadClientItem.DownloadId)
.Where(h => h.EventType == HistoryEventType.Grabbed)
.ToList();
if (grabbedHistory.Empty())
{
_logger.Debug("No grabbed history for this download client item");
return Decision.Accept();
}
var parsedReleaseName = Parser.Parser.ParseTitle(grabbedHistory.First().SourceTitle);
if (parsedReleaseName != null && parsedReleaseName.FullSeason)
{
_logger.Debug("File is part of a season pack, skipping.");
return Decision.Accept();
}
foreach (var item in grabbedHistory)
{
if (item.Quality.Quality != Quality.Unknown && item.Quality != localEpisode.Quality)
{
_logger.Debug("Quality for grabbed release ({0}) does not match the quality of the file ({1})", item.Quality, localEpisode.Quality);
return Decision.Reject("File quality does not match quality of the grabbed release");
}
}
return Decision.Accept();
}
}
}

View File

@@ -43,8 +43,9 @@ namespace NzbDrone.Core.Notifications.CustomScript
environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId ?? string.Empty);
environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString());
environmentVariables.Add("Sonarr_Release_EpisodeCount", remoteEpisode.Episodes.Count.ToString());
environmentVariables.Add("Sonarr_Release_SeasonNumber", remoteEpisode.ParsedEpisodeInfo.SeasonNumber.ToString());
environmentVariables.Add("Sonarr_Release_SeasonNumber", remoteEpisode.Episodes.First().SeasonNumber.ToString());
environmentVariables.Add("Sonarr_Release_EpisodeNumbers", string.Join(",", remoteEpisode.Episodes.Select(e => e.EpisodeNumber)));
environmentVariables.Add("Sonarr_Release_AbsoluteEpisodeNumbers", string.Join(",", remoteEpisode.Episodes.Select(e => e.AbsoluteEpisodeNumber)));
environmentVariables.Add("Sonarr_Release_EpisodeAirDates", string.Join(",", remoteEpisode.Episodes.Select(e => e.AirDate)));
environmentVariables.Add("Sonarr_Release_EpisodeAirDatesUtc", string.Join(",", remoteEpisode.Episodes.Select(e => e.AirDateUtc)));
environmentVariables.Add("Sonarr_Release_EpisodeTitles", string.Join("|", remoteEpisode.Episodes.Select(e => e.Title)));

View File

@@ -0,0 +1,11 @@
namespace NzbDrone.Core.Notifications.Join
{
public enum JoinPriority
{
Silent = -2,
Quiet = -1,
Normal = 0,
High = 1,
Emergency = 2
}
}

View File

@@ -94,6 +94,7 @@ namespace NzbDrone.Core.Notifications.Join
request.AddParameter("text", message);
request.AddParameter("icon", "https://cdn.rawgit.com/Sonarr/Sonarr/develop/Logo/256.png"); // Use the Sonarr logo.
request.AddParameter("smallicon", "https://cdn.rawgit.com/Sonarr/Sonarr/develop/Logo/96-Outline-White.png"); // 96x96px with outline at 88x88px on a transparent background.
request.AddParameter("priority", settings.Priority);
var response = client.ExecuteAndValidate(request);
var res = Json.Deserialize<JoinResponseModel>(response.Content);

View File

@@ -16,6 +16,12 @@ namespace NzbDrone.Core.Notifications.Join
public class JoinSettings : IProviderConfig
{
public JoinSettings()
{
Priority = (int)JoinPriority.Normal;
}
private static readonly JoinSettingsValidator Validator = new JoinSettingsValidator();
[FieldDefinition(0, Label = "API Key", HelpText = "The API Key from your Join account settings (click Join API button).", HelpLink = "https://joinjoaomgcd.appspot.com/")]
@@ -27,6 +33,9 @@ namespace NzbDrone.Core.Notifications.Join
[FieldDefinition(2, Label = "Device Names", HelpText = "Comma separated list of full or partial device names you'd like to send notifications to. If unset, all devices will receive notifications.", HelpLink = "https://joaoapps.com/join/api/")]
public string DeviceNames { get; set; }
[FieldDefinition(3, Label = "Notification Priority", Type = FieldType.Select, SelectOptions = typeof(JoinPriority))]
public int Priority { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -53,6 +53,18 @@ namespace NzbDrone.Core.Notifications.Xbmc
public void Clean(XbmcSettings settings)
{
if (!settings.AlwaysUpdate)
{
_logger.Debug("Determining if there are any active players on XBMC host: {0}", settings.Address);
var activePlayers = GetActivePlayers(settings);
if (activePlayers.Any(a => a.Type.Equals("video")))
{
_logger.Debug("Video is currently playing, skipping library cleaning");
return;
}
}
const string cleanVideoLibrary = "CleanLibrary(video)";
var command = BuildExecBuiltInCommand(cleanVideoLibrary);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
if (!settings.AlwaysUpdate)
{
_logger.Debug("Determining if there are any active players on XBMC host: {0}", settings.Address);
var activePlayers = _proxy.GetActivePlayers(settings);
var activePlayers = GetActivePlayers(settings);
if (activePlayers.Any(a => a.Type.Equals("video")))
{
@@ -47,6 +47,18 @@ namespace NzbDrone.Core.Notifications.Xbmc
public void Clean(XbmcSettings settings)
{
if (!settings.AlwaysUpdate)
{
_logger.Debug("Determining if there are any active players on XBMC host: {0}", settings.Address);
var activePlayers = GetActivePlayers(settings);
if (activePlayers.Any(a => a.Type.Equals("video")))
{
_logger.Debug("Video is currently playing, skipping library cleaning");
return;
}
}
_proxy.CleanLibrary(settings);
}

View File

@@ -143,6 +143,9 @@
<Compile Include="Configuration\IConfigService.cs" />
<Compile Include="Configuration\InvalidConfigFileException.cs" />
<Compile Include="Configuration\ResetApiKeyCommand.cs" />
<Compile Include="Datastore\Migration\130_episode_last_searched_time.cs" />
<Compile Include="Datastore\Migration\129_add_relative_original_path_to_episode_file.cs" />
<Compile Include="DecisionEngine\Specifications\AlreadyImportedSpecification.cs" />
<Compile Include="Indexers\SeedConfigProvider.cs" />
<Compile Include="DataAugmentation\DailySeries\DailySeries.cs" />
<Compile Include="DataAugmentation\DailySeries\DailySeriesDataProxy.cs" />
@@ -817,7 +820,6 @@
<Compile Include="MediaFiles\EpisodeImport\Specifications\EpisodeTitleSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\SameFileSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\GrabbedReleaseQualitySpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\MatchesFolderSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecification.cs" />
@@ -911,6 +913,7 @@
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
<Compile Include="Notifications\Join\JoinAuthException.cs" />
<Compile Include="Notifications\Join\JoinInvalidDeviceException.cs" />
<Compile Include="Notifications\Join\JoinPriority.cs" />
<Compile Include="Notifications\Join\JoinResponseModel.cs" />
<Compile Include="Notifications\Join\Join.cs" />
<Compile Include="Notifications\Join\JoinException.cs" />

View File

@@ -13,6 +13,7 @@ namespace NzbDrone.Core.Parser.Model
public int SeasonNumber { get; set; }
public int[] EpisodeNumbers { get; set; }
public int[] AbsoluteEpisodeNumbers { get; set; }
public decimal[] SpecialAbsoluteEpisodeNumbers { get; set; }
public string AirDate { get; set; }
public Language Language { get; set; }
public bool FullSeason { get; set; }
@@ -27,6 +28,7 @@ namespace NzbDrone.Core.Parser.Model
{
EpisodeNumbers = new int[0];
AbsoluteEpisodeNumbers = new int[0];
SpecialAbsoluteEpisodeNumbers = new decimal[0];
}
public bool IsDaily

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
@@ -22,6 +23,10 @@ namespace NzbDrone.Core.Parser
// new Regex(@"^(?:(?<absoluteepisode>\d{2,3})(?:_|-|\s|\.)+)+(?<title>.+?)(?:\W|_)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)",
// RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Daily episodes without title (2018-10-12, 20181012) (Strict pattern to avoid false matches)
new Regex(@"^(?<airyear>19[6-9]\d|20\d\d)(?<sep>[-]?)(?<airmonth>0\d|1[0-2])\k<sep>(?<airday>[0-2]\d|3[01])(?!\d)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Multi-Part episodes without a title (S01E05.S01E06)
new Regex(@"^(?:\W*S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,3}(?!\d+)))+){2,}",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -35,15 +40,15 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title Episode Absolute Episode Number ([SubGroup] Series Title Episode 01)
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title Absolute Episode Number + Season+Episode
new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.)?)(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>\d{2,3}))+(?:_|-|\s|\.)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+).*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.)",
new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.)?)(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>\d{2,3}(\.\d{1,2})?))+(?:_|-|\s|\.)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+).*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title Season+Episode + Absolute Episode Number
new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.)?)(?<title>.+?)(?:[-_\W](?<![()\[!]))+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)(?:(?:_|-|\s|\.)+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.)?)(?<title>.+?)(?:[-_\W](?<![()\[!]))+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)(?:(?:_|-|\s|\.)+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title Season+Episode
@@ -51,15 +56,15 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title with trailing number Absolute Episode Number
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?\d+?)[-_. ]+(?:[-_. ]?(?<absoluteepisode>\d{3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?\d+?)[-_. ]+(?:[-_. ]?(?<absoluteepisode>\d{3}(\.\d{1,2})?(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title - Absolute Episode Number
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:[. ]-[. ](?<absoluteepisode>\d{2,3}(?!\d+|[-])))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:[. ]-[. ](?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+|[-])))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - [SubGroup] Title Absolute Episode Number
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+\(?(?:[-_. ]?#?(?<absoluteepisode>\d{2,3}(?!\d+)))+\)?(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+\(?(?:[-_. ]?#?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+)))+\)?(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Multi-episode Repeated (S01E05 - S01E06, 1x05 - 1x06, etc)
@@ -71,7 +76,7 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - Title Season EpisodeNumber + Absolute Episode Number [SubGroup]
new Regex(@"^(?<title>.+?)(?:[-_\W](?<![()\[!]))+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]|\W[ex]){1,2}(?<episode>(?<!\d+)\d{2}(?!\d+)))).+?(?:[-_. ]?(?<absoluteepisode>(?<!\d+)\d{3}(?!\d+)))+.+?\[(?<subgroup>.+?)\](?:$|\.mkv)",
new Regex(@"^(?<title>.+?)(?:[-_\W](?<![()\[!]))+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]|\W[ex]){1,2}(?<episode>(?<!\d+)\d{2}(?!\d+)))).+?(?:[-_. ]?(?<absoluteepisode>(?<!\d+)\d{3}(\.\d{1,2})?(?!\d+)))+.+?\[(?<subgroup>.+?)\](?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Multi-Episode with a title (S01E05E06, S01E05-06, S01E05 E06, etc) and trailing info in slashes
@@ -79,11 +84,11 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - Title Absolute Episode Number [SubGroup]
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{3}(?!\d+)))+(?:.+?)\[(?<subgroup>.+?)\].*?(?<hash>\[\w{8}\])?(?:$|\.)",
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{3}(\.\d{1,2})?(?!\d+)))+(?:.+?)\[(?<subgroup>.+?)\].*?(?<hash>\[\w{8}\])?(?:$|\.)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - Title Absolute Episode Number [Hash]
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?[-_. ]+.*?(?<hash>\[\w{8}\])(?:$|\.)",
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?[-_. ]+.*?(?<hash>\[\w{8}\])(?:$|\.)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with airdate AND season/episode number, capture season/epsiode only
@@ -167,7 +172,11 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Anime - Title with season number - Absolute Episode Number (Title S01 - EP14)
new Regex(@"^(?<title>.+?S\d{1,2})[-_. ]{3,}(?:EP)?(?<absoluteepisode>\d{2,3}(?!\d+|[-]))",
new Regex(@"^(?<title>.+?S\d{1,2})[-_. ]{3,}(?:EP)?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+|[-]))",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Anime - French titles with single episode numbers, with or without leading sub group ([RlsGroup] Title - Episode 1)
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)[-_. ]+?(?:Episode[-_. ]+?)(?<absoluteepisode>\d{1}(\.\d{1,2})?(?!\d+))",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Season only releases
@@ -222,19 +231,19 @@ namespace NzbDrone.Core.Parser
// TODO: THIS ONE
//Anime - Title Absolute Episode Number (e66)
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?:e|ep)(?<absoluteepisode>\d{2,3}))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?:e|ep)(?<absoluteepisode>\d{2,3}(\.\d{1,2})?))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - Title Episode Absolute Episode Number (Series Title Episode 01)
new Regex(@"^(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
new Regex(@"^(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - Title Absolute Episode Number
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - Title {Absolute Episode Number}
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Extant, terrible multi-episode naming (extant.10708.hdtv-lol.mp4)
@@ -278,11 +287,11 @@ namespace NzbDrone.Core.Parser
new Regex(@"^b00bs$", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// 170424_26 - Started appearing August 2018
new Regex(@"^\d{6}_\d{2}$"),
new Regex(@"^\d{6}_\d{2}$"),
};
//Regex to detect whether the title was reversed.
private static readonly Regex ReversedTitleRegex = new Regex(@"[-._ ](p027|p0801|\d{2}E\d{2}S)[-._ ]", RegexOptions.Compiled);
private static readonly Regex ReversedTitleRegex = new Regex(@"[-._ ](p027|p0801|\d{2,3}E\d{2}S)[-._ ]", RegexOptions.Compiled);
private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?<!^)(a(?!$)|an|the|and|or|of)(?:\b|_))|\W|_",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
@@ -290,7 +299,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SimpleTitleRegex = new Regex(@"(?:(480|720|1080|2160)[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|3840x2160|4096x2160|(8|10)b(it)?)\s*?",
private static readonly Regex SimpleTitleRegex = new Regex(@"(?:(480|720|1080|2160)[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|3840x2160|4096x2160|(8|10)b(it)?|10-bit)\s*?",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*|^www\.[a-z]+\.(?:com|net)[ -]*",
@@ -645,21 +654,32 @@ namespace NzbDrone.Core.Parser
if (absoluteEpisodeCaptures.Any())
{
var first = Convert.ToInt32(absoluteEpisodeCaptures.First().Value);
var last = Convert.ToInt32(absoluteEpisodeCaptures.Last().Value);
var first = Convert.ToDecimal(absoluteEpisodeCaptures.First().Value, CultureInfo.InvariantCulture);
var last = Convert.ToDecimal(absoluteEpisodeCaptures.Last().Value, CultureInfo.InvariantCulture);
if (first > last)
{
return null;
}
var count = last - first + 1;
result.AbsoluteEpisodeNumbers = Enumerable.Range(first, count).ToArray();
if (matchGroup.Groups["special"].Success)
if ((first % 1) != 0 || (last % 1) != 0)
{
if (absoluteEpisodeCaptures.Count != 1)
return null; // Multiple matches not allowed for specials
result.SpecialAbsoluteEpisodeNumbers = new decimal[] { first };
result.Special = true;
}
else
{
var count = last - first + 1;
result.AbsoluteEpisodeNumbers = Enumerable.Range((int)first, (int)count).ToArray();
if (matchGroup.Groups["special"].Success)
{
result.Special = true;
}
}
}
if (!episodeCaptures.Any() && !absoluteEpisodeCaptures.Any())

View File

@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex SourceRegex = new Regex(@"\b(?:
(?<bluray>BluRay|Blu-Ray|HD-?DVD|BD)|
(?<webdl>WEB[-_. ]DL|WEBDL|WebRip|AmazonHD|iTunesHD|NetflixU?HD|WebHD|[. ]WEB[. ](?:[xh]26[45]|DD5[. ]1)|\d+0p[. ]WEB[. ]|WEB-DLMux)|
(?<webdl>WEB[-_. ]DL|WEBDL|WebRip|AmazonHD|iTunesHD|NetflixU?HD|WebHD|[. ]WEB[. ](?:[xh]26[45]|DDP?5[. ]1)|\d+0p[. ]WEB[. ]|WEB-DLMux)|
(?<hdtv>HDTV)|
(?<bdrip>BDRip)|
(?<brrip>BRRip)|
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex RealRegex = new Regex(@"\b(?<real>REAL)\b",
RegexOptions.Compiled);
private static readonly Regex ResolutionRegex = new Regex(@"\b(?:(?<R480p>480p|640x480|848x480)|(?<R576p>576p)|(?<R720p>720p|1280x720)|(?<R1080p>1080p|1920x1080|1440p)|(?<R2160p>2160p|4k[-_. ]UHD|UHD[-_. ]4k))\b",
private static readonly Regex ResolutionRegex = new Regex(@"\b(?:(?<R480p>480p|640x480|848x480)|(?<R576p>576p)|(?<R720p>720p|1280x720)|(?<R1080p>1080p|1920x1080|1440p|FHD|1080i)|(?<R2160p>2160p|4k[-_. ](?:UHD|HEVC|BD)|(?:UHD|HEVC|BD)[-_. ]4k))\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex CodecRegex = new Regex(@"\b(?:(?<x264>x264)|(?<h264>h264)|(?<xvidhd>XvidHD)|(?<xvid>Xvid)|(?<divx>divx))\b",

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider.Events;
@@ -24,15 +25,18 @@ namespace NzbDrone.Core.ThingiProvider.Status
protected readonly IProviderStatusRepository<TModel> _providerStatusRepository;
protected readonly IEventAggregator _eventAggregator;
protected readonly IRuntimeInfo _runtimeInfo;
protected readonly Logger _logger;
protected int MaximumEscalationLevel { get; set; } = EscalationBackOff.Periods.Length - 1;
protected TimeSpan MinimumTimeSinceInitialFailure { get; set; } = TimeSpan.Zero;
protected TimeSpan MinimumTimeSinceStartup { get; set; } = TimeSpan.FromMinutes(15);
public ProviderStatusServiceBase(IProviderStatusRepository<TModel> providerStatusRepository, IEventAggregator eventAggregator, Logger logger)
public ProviderStatusServiceBase(IProviderStatusRepository<TModel> providerStatusRepository, IEventAggregator eventAggregator, IRuntimeInfo runtimeInfo, Logger logger)
{
_providerStatusRepository = providerStatusRepository;
_eventAggregator = eventAggregator;
_runtimeInfo = runtimeInfo;
_logger = logger;
}
@@ -89,9 +93,10 @@ namespace NzbDrone.Core.ThingiProvider.Status
escalate = false;
}
var inStartupGracePeriod = (_runtimeInfo.StartTime + MinimumTimeSinceStartup) > now;
var inGracePeriod = (status.InitialFailure.Value + MinimumTimeSinceInitialFailure) > now;
if (escalate && !inGracePeriod)
if (escalate && !inGracePeriod && !inStartupGracePeriod)
{
status.EscalationLevel = Math.Min(MaximumEscalationLevel, status.EscalationLevel + 1);
}
@@ -109,6 +114,15 @@ namespace NzbDrone.Core.ThingiProvider.Status
status.DisabledTill = now + CalculateBackOffPeriod(status);
}
if (inStartupGracePeriod && minimumBackOff == TimeSpan.Zero && status.DisabledTill.HasValue)
{
var maximumDisabledTill = now + TimeSpan.FromSeconds(EscalationBackOff.Periods[1]);
if (maximumDisabledTill < status.DisabledTill)
{
status.DisabledTill = maximumDisabledTill;
}
}
_providerStatusRepository.Upsert(status);
_eventAggregator.PublishEvent(new ProviderStatusChangedEvent<TProvider>(providerId, status));

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Marr.Data;
using NzbDrone.Common.Extensions;
@@ -32,6 +32,7 @@ namespace NzbDrone.Core.Tv
public bool UnverifiedSceneNumbering { get; set; }
public Ratings Ratings { get; set; }
public List<MediaCover.MediaCover> Images { get; set; }
public DateTime? LastSearchTime { get; set; }
public string SeriesTitle { get; private set; }
@@ -46,4 +47,4 @@ namespace NzbDrone.Core.Tv
return string.Format("[{0}]{1}", Id, Title.NullSafe());
}
}
}
}

View File

@@ -1,14 +1,15 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Tv
{
public static class SeriesTitleNormalizer
{
private readonly static Dictionary<int, string> PreComputedTitles = new Dictionary<int, string>
private static readonly Dictionary<int, string> PreComputedTitles = new Dictionary<int, string>
{
{ 281588, "a to z" },
{ 289260, "ad bible continues"},
{ 328534, "ap bio"}
{ 328534, "ap bio"},
{ 77904, "ateam" }
};
public static string Normalize(string title, int tvdbId)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -268,8 +268,6 @@ namespace NzbDrone.Mono.Disk
}
return g.gr_gid;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -117,6 +117,12 @@ namespace NzbDrone.Mono.Disk
return null;
}
if (mount.StartsWith("/snap/"))
{
// Mount point for snap packages
return null;
}
var driveType = FindDriveType.Find(type);
if (name.StartsWith("/dev/") || GetFileSystems().GetValueOrDefault(type, false))

View File

@@ -30,9 +30,13 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_WHILE_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_FIXED_STMT/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_USINGS_STMT/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">ON_SINGLE_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AllowAlias/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/CanUseGlobalAlias/@EntryValue">False</s:Boolean>
@@ -71,7 +75,12 @@
<s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=File_003A_003AC_003A_005CDropbox_005CGit_005CNzbDrone_005CNzbDrone_002Esln_002EDotSettings/@KeyIndexDefined">True</s:Boolean>
<s:Double x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=File_003A_003AC_003A_005CDropbox_005CGit_005CNzbDrone_005CNzbDrone_002Esln_002EDotSettings/RelativePriority/@EntryValue">2</s:Double>
<s:Boolean x:Key="/Default/Environment/MemoryUsageIndicator/IsVisible/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TextControl/HighlightCurrentLine/@EntryValue">True</s:Boolean>