1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-28 18:05:41 -04:00

Compare commits

...

30 Commits

Author SHA1 Message Date
Mark McDowall
24c6d3f4b3 Don't read response stream if it equals Stream.Null 2018-09-14 17:50:13 -07:00
Mark McDowall
4a052708c8 New: Updated pushover app clone URL 2018-09-04 00:19:09 -07:00
Mark McDowall
39a8d4f0d8 Fixed: Parsing of new hashed release filenames (######_##.ext) 2018-09-03 11:24:48 -07:00
Mark McDowall
ca22a25842 New: Add stopped option for rTorrent 2018-08-28 18:14:55 -07:00
Mark McDowall
ff9a9a5e4d More restrictions when using download client title or folder name for parsing
Fixes #2663
2018-08-27 21:35:03 -07:00
Mark McDowall
3d7c59bc3b New: Add unique IDs to Kodi metadata
Closes #2711
2018-08-27 20:42:32 -07:00
Mark McDowall
63ea1f1afd Fixed: Skip sample check when rescanning series folder 2018-08-19 09:23:34 -07:00
Mark McDowall
baf8f6cca6 Fixed: Parsing multi-episode in square bracket
Fixes #2669
2018-08-18 11:22:51 -07:00
Mark McDowall
c67c7e1b5a More flexible matching some anime releases 2018-08-18 10:56:18 -07:00
Mark McDowall
46d8e5830a Fixed: Concurrent manual imports silently failing 2018-08-18 10:56:18 -07:00
Taloth Saldono
37054673b7 Fixed: Too big eta in qbit api still occurring on official builds. 2018-08-07 19:08:38 +02:00
Mark McDowall
86bc5c5547 Fixed: Parsing of some anime releases with season number in title
Fixes #2684
2018-07-30 19:20:40 -07:00
Taloth Saldono
fc44607c73 Added missing UrlBase validation for SabnzbdSettings. 2018-07-18 07:39:58 +02:00
Taloth Saldono
2a1421f488 Fixed: Skip torrents in Deluge api that don't have hashes.
closes #2566
closes #2567
closes #2664
2018-07-16 19:16:26 +02:00
Nicholas Landriault
d7a054f637 Deluge torrents that don't have a hash are skipped
In some cases torrents in Deluge may not have a hash (ex: https://torguard.net/checkmytorrentipaddress.php). This causes Sonarr to fail when loading the torrents from Deluge with error message: 'Unable to communicate with deluge. Object reference not set to an instance of an object'. This commit simply causes Sonarr to skip over the torrent with the missing hash and continue loading torrents that do have hashes.
2018-07-16 19:11:10 +02:00
Taloth Saldono
9c9ad9aec3 New: Added optional UrlBase to Nzbget and Sabnzbd settings.
ref #1651
2018-07-15 12:24:27 +02:00
Mark McDowall
1467c52e03 Fixed: Multi-file torrents in Vuze with different folder and file names
Fixes #2571
2018-07-11 19:01:41 -07:00
Mark McDowall
e407145d10 Fixed: .vtt files treated as subtitles 2018-07-08 19:22:42 -07:00
Taloth Saldono
476110b1de Fixed: Store BitRate_Nominal (VBR) mediainfo in database instead of only BitRate.
ref Radarr/Radarr#2860
2018-07-08 18:36:36 +02:00
Taloth Saldono
45f9f45f50 Fixed: Quality parser for the rare HD-DVD. 2018-07-07 20:04:45 +02:00
Taloth Saldono
d581d997c2 Fixed: Ignore /etc in System disk overview. 2018-07-07 11:09:04 +02:00
Taloth Saldono
633344e5bb Disabled httpbin.org tests for now due to the site being flaky. 2018-07-06 21:53:03 +02:00
Taloth Saldono
0cce6b74f9 Added logging of json snippets on json deserialization errors. 2018-07-06 21:44:15 +02:00
Taloth Saldono
8b8bfb9bf0 Added third httpbin site. 2018-07-06 19:50:29 +02:00
Taloth Saldono
7241ca4ae9 Run http tests more gracefully. 2018-07-06 19:09:59 +02:00
Taloth Saldono
e9b11e55e9 Fixed: Regression with importing nested obfuscated directories.
Closes #2640
2018-07-04 21:55:08 +02:00
Mark McDowall
48126f55ed Fixed: Parsing titles with question marks
Fixes #2637
2018-06-27 19:54:25 -07:00
Mark McDowall
cb549507ee Fixed: Parsing dates using underscores for separation 2018-06-27 18:32:28 -07:00
Mark McDowall
a0b6cdb08e Fixed: Forced seeding in QBittorrent status treated as complete 2018-06-27 18:31:44 -07:00
Taloth Saldono
9b9597093c Fixed: Regression causing Manual Import to ignore user provided information. 2018-06-21 07:23:20 +02:00
49 changed files with 576 additions and 148 deletions

View File

@@ -52,15 +52,6 @@ CleanFolder()
find $path -depth -empty -type d -exec rm -r "{}" \;
}
AddJsonNet()
{
rm $outputFolder/Newtonsoft.Json.*
cp $sourceFolder/packages/Newtonsoft.Json.*/lib/net35/*.dll $outputFolder
cp $sourceFolder/packages/Newtonsoft.Json.*/lib/net35/*.dll $outputFolder/NzbDrone.Update
}
BuildWithMSBuild()
{
export PATH=$msBuild:$PATH
@@ -91,8 +82,6 @@ Build()
CleanFolder $outputFolder false
AddJsonNet
echo "Removing Mono.Posix.dll"
rm $outputFolder/Mono.Posix.dll

View File

@@ -13,6 +13,7 @@ namespace NzbDrone.Api.ManualImport
{
public string Path { get; set; }
public string RelativePath { get; set; }
public string FolderName { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public SeriesResource Series { get; set; }
@@ -36,6 +37,7 @@ namespace NzbDrone.Api.ManualImport
Path = model.Path,
RelativePath = model.RelativePath,
FolderName = model.FolderName,
Name = model.Name,
Size = model.Size,
Series = model.Series.ToResource(),

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using FluentAssertions;
@@ -24,13 +25,60 @@ namespace NzbDrone.Common.Test.Http
[TestFixture(typeof(CurlHttpDispatcher))]
public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispatcher : IHttpDispatcher
{
private static string[] _httpBinHosts = new[] { "eu.httpbin.org", "httpbin.org" };
private static int _httpBinRandom;
private string[] _httpBinHosts;
private int _httpBinSleep;
private int _httpBinRandom;
private string _httpBinHost;
private string _httpBinHost2;
[OneTimeSetUp]
public void FixtureSetUp()
{
var candidates = new[] { "eu.httpbin.org", /*"httpbin.org",*/ "www.httpbin.org" };
// httpbin.org is broken right now, occassionally redirecting to https if it's unavailable.
_httpBinHosts = candidates.Where(IsTestSiteAvailable).ToArray();
TestLogger.Info($"{candidates.Length} TestSites available.");
_httpBinSleep = _httpBinHosts.Count() < 2 ? 100 : 10;
}
private bool IsTestSiteAvailable(string site)
{
try
{
var req = WebRequest.Create($"http://{site}/get") as HttpWebRequest;
var res = req.GetResponse() as HttpWebResponse;
if (res.StatusCode != HttpStatusCode.OK) return false;
try
{
req = WebRequest.Create($"http://{site}/status/429") as HttpWebRequest;
res = req.GetResponse() as HttpWebResponse;
}
catch (WebException ex)
{
res = ex.Response as HttpWebResponse;
}
if (res == null || res.StatusCode != (HttpStatusCode)429) return false;
return true;
}
catch
{
return false;
}
}
[SetUp]
public void SetUp()
{
if (!_httpBinHosts.Any())
{
Assert.Inconclusive("No TestSites available");
}
Mocker.GetMock<IPlatformInfo>().Setup(c => c.Version).Returns(new Version("1.0.0"));
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
@@ -50,6 +98,13 @@ namespace NzbDrone.Common.Test.Http
// Roundrobin over the two servers, to reduce the chance of hitting the ratelimiter.
_httpBinHost = _httpBinHosts[_httpBinRandom++ % _httpBinHosts.Length];
_httpBinHost2 = _httpBinHosts[_httpBinRandom % _httpBinHosts.Length];
}
[TearDown]
public void TearDown()
{
Thread.Sleep(_httpBinSleep);
}
[Test]
@@ -245,7 +300,12 @@ namespace NzbDrone.Common.Test.Http
public void GivenOldCookie()
{
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
if (_httpBinHost == _httpBinHost2)
{
Assert.Inconclusive("Need both httpbin.org and eu.httpbin.org to run this test.");
}
var oldRequest = new HttpRequest($"http://{_httpBinHost2}/get");
oldRequest.Cookies["my"] = "cookie";
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.GetMock<IUserAgentBuilder>().Object, Mocker.Resolve<Logger>());
@@ -262,7 +322,7 @@ namespace NzbDrone.Common.Test.Http
{
GivenOldCookie();
var request = new HttpRequest("http://eu.httpbin.org/get");
var request = new HttpRequest($"http://{_httpBinHost2}/get");
var response = Subject.Get<HttpBinResource>(request);
@@ -278,7 +338,7 @@ namespace NzbDrone.Common.Test.Http
{
GivenOldCookie();
var request = new HttpRequest("http://httpbin.org/get");
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request);

View File

@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Net;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -101,7 +102,7 @@ namespace NzbDrone.Common.Http.Dispatchers
using (var responseStream = httpWebResponse.GetResponseStream())
{
if (responseStream != null)
if (responseStream != null && responseStream != Stream.Null)
{
try
{

View File

@@ -135,7 +135,7 @@ namespace NzbDrone.Common.Http
return new HttpUri(Scheme, Host, Port, CombinePath(Path, path), Query, Fragment);
}
private static string CombinePath(string basePath, string relativePath)
public static string CombinePath(string basePath, string relativePath)
{
if (relativePath.IsNullOrWhiteSpace())
{

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
@@ -14,13 +15,13 @@ namespace NzbDrone.Common.Serializer
static Json()
{
SerializerSetting = new JsonSerializerSettings
{
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented,
DefaultValueHandling = DefaultValueHandling.Include,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
{
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented,
DefaultValueHandling = DefaultValueHandling.Include,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
SerializerSetting.Converters.Add(new StringEnumConverter { CamelCaseText = true });
@@ -34,12 +35,61 @@ namespace NzbDrone.Common.Serializer
public static T Deserialize<T>(string json) where T : new()
{
return JsonConvert.DeserializeObject<T>(json, SerializerSetting);
try
{
return JsonConvert.DeserializeObject<T>(json, SerializerSetting);
}
catch (JsonReaderException ex)
{
throw DetailedJsonReaderException(ex, json);
}
}
public static object Deserialize(string json, Type type)
{
return JsonConvert.DeserializeObject(json, type, SerializerSetting);
try
{
return JsonConvert.DeserializeObject(json, type, SerializerSetting);
}
catch (JsonReaderException ex)
{
throw DetailedJsonReaderException(ex, json);
}
}
private static JsonReaderException DetailedJsonReaderException(JsonReaderException ex, string json)
{
var lineNumber = ex.LineNumber == 0 ? 0 : (ex.LineNumber - 1);
var linePosition = ex.LinePosition;
var lines = json.Split('\n');
if (lineNumber >= 0 && lineNumber < lines.Length &&
linePosition >= 0 && linePosition < lines[lineNumber].Length)
{
var line = lines[lineNumber];
var start = Math.Max(0, linePosition - 20);
var end = Math.Min(line.Length, linePosition + 20);
var snippetBefore = line.Substring(start, linePosition - start);
var snippetAfter = line.Substring(linePosition, end - linePosition);
var message = ex.Message + " (Json snippet '" + snippetBefore + "<--error-->" + snippetAfter + "')";
// Not risking updating JSON.net from 9.x to 10.x just to get this as public ctor.
var ctor = typeof(JsonReaderException).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(Exception), typeof(string), typeof(int), typeof(int) }, null);
if (ctor != null)
{
return (JsonReaderException)ctor.Invoke(new object[] { message, ex, ex.Path, ex.LineNumber, linePosition });
}
// JSON.net 10.x ctor in case we update later.
ctor = typeof(JsonReaderException).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(string), typeof(int), typeof(int), typeof(Exception) }, null);
if (ctor != null)
{
return (JsonReaderException)ctor.Invoke(new object[] { message, ex.Path, ex.LineNumber, linePosition, ex });
}
}
return ex;
}
public static bool TryDeserialize<T>(string json, out T result) where T : new()
@@ -78,4 +128,4 @@ namespace NzbDrone.Common.Serializer
Serialize(model, new StreamWriter(outputStream));
}
}
}
}

View File

@@ -137,6 +137,7 @@ namespace NzbDrone.Core.Test.DiskSpace
[TestCase("/var/lib/kubelet")]
[TestCase("/var/lib/docker")]
[TestCase("/some/place/docker/aufs")]
[TestCase("/etc/network")]
public void should_not_check_diskspace_for_irrelevant_mounts(string path)
{
var mount = new Mock<IMount>();

View File

@@ -290,6 +290,24 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
item.CanBeRemoved.Should().Be(canBeRemoved);
}
[Test]
public void GetItems_should_ignore_items_without_hash()
{
_downloading.Hash = null;
GivenTorrents(new List<DelugeTorrent>
{
_downloading,
_queued
});
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().Status.Should().Be(DownloadItemStatus.Queued);
}
[Test]
public void should_return_status_with_outputdirs()
{

View File

@@ -162,6 +162,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
[TestCase("uploading")]
[TestCase("stalledUP")]
[TestCase("checkingUP")]
[TestCase("forcedUP")]
public void completed_item_should_have_required_properties(string state)
{
var torrent = new QBittorrentTorrent
@@ -494,5 +495,18 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
var item = Subject.GetItems().Single();
item.Category.Should().Be(category);
}
[Test]
public void should_handle_eta_biginteger()
{
// Let this stand as a lesson to never write temporary unit tests on your dev machine and claim it works.
// Commit the tests and let it run with the official build on the official build agents.
// (Also don't replace library versions in your build script)
var json = "{ \"eta\": 18446744073709335000 }";
var torrent = Newtonsoft.Json.JsonConvert.DeserializeObject<QBittorrentTorrent>(json);
torrent.Eta.ToString().Should().Be("18446744073709335000");
}
}
}

View File

@@ -105,5 +105,24 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(folderEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
}
[Test]
public void should_use_file_when_folder_is_absolute_and_file_is_not()
{
var fileEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
var folderEpisodeInfo = Parser.Parser.ParseTitle("Series.Title.01");
var localEpisode = new LocalEpisode
{
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.101\Series.Title.S01E01.mkv".AsOsAgnostic(),
Series = _series
};
Subject.Aggregate(localEpisode, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
}
}
}

View File

@@ -29,6 +29,13 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
};
}
[Test]
public void should_return_true_if_no_fileinfo_available()
{
_localEpisode.FileEpisodeInfo = null;
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_when_file_contains_the_full_season()
{

View File

@@ -50,6 +50,15 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_file_name_is_not_parseable()
{
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01\AFDAFD.mkv".AsOsAgnostic();
_localEpisode.FileEpisodeInfo = null;
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_should_be_accepted_for_full_season()
{

View File

@@ -1,17 +1,29 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Update.Commands;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Messaging.Commands
{
[TestFixture]
public class CommandEqualityComparerFixture
{
private string GivenRandomPath()
{
return Path.Combine(@"C:\Tesst\", Guid.NewGuid().ToString()).AsOsAgnostic();
}
[Test]
public void should_return_true_when_there_are_no_properties()
{
@@ -107,5 +119,43 @@ namespace NzbDrone.Core.Test.Messaging.Commands
CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse();
}
[Test]
public void should_return_true_when_commands_list_for_non_primitive_type_match()
{
var files1 = Builder<ManualImportFile>.CreateListOfSize(2)
.All()
.With(m => m.Path = GivenRandomPath())
.Build()
.ToList();
var files2 = files1.JsonClone();
var command1 = new ManualImportCommand { Files = files1 };
var command2 = new ManualImportCommand { Files = files2 };
CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeTrue();
}
[Test]
public void should_return_false_when_commands_list_for_non_primitive_type_dont_match()
{
var files1 = Builder<ManualImportFile>.CreateListOfSize(2)
.All()
.With(m => m.Path = GivenRandomPath())
.Build()
.ToList();
var files2 = Builder<ManualImportFile>.CreateListOfSize(2)
.All()
.With(m => m.Path = GivenRandomPath())
.Build()
.ToList();
var command1 = new ManualImportCommand { Files = files1 };
var command2 = new ManualImportCommand { Files = files2 };
CommandEqualityComparer.Instance.Equals(command1, command2).Should().BeFalse();
}
}
}

View File

@@ -89,6 +89,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Love Rerun EP06 720p x265 AOZ.mp4", "Love Rerun", 6, 0, 0)]
[TestCase("Love Rerun 2018 EP06 720p x265 AOZ.mp4", "Love Rerun 2018", 6, 0, 0)]
[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("", "", 0, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
{

View File

@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("At_Midnight_140722_720p_HDTV_x264-YesTV", "At Midnight", 2014, 07, 22)]
//[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("", "", 0, 0, 0)]
public void should_parse_daily_episode(string postTitle, string title, int year, int month, int day)
{

View File

@@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
@@ -80,6 +80,13 @@ namespace NzbDrone.Core.Test.ParserTests
"The Good Wife",
Quality.HDTV720p,
"NZBgeek"
},
new object[]
{
@"C:\Test\Fargo.S03E04.1080p.WEB-DL.DD5.1.H264-RARBG\170424_26.mkv".AsOsAgnostic(),
"Fargo",
Quality.WEBDL1080p,
"RARBG"
}
};

View File

@@ -60,6 +60,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("S01E01-E03 - Episode Title.HDTV-720p", "", 1, new [] { 1, 2, 3 })]
[TestCase("1x01-x03 - Episode Title.HDTV-720p", "", 1, new [] { 1, 2, 3 })]
[TestCase("Are.You.Human.Too.E07-E08.180612.1080p-NEXT", "Are You Human Too", 1, new[] { 7, 8 })]
[TestCase("Are You Human Too? E11-E12 1080p HDTV AAC H.264-NEXT", "Are You Human Too", 1, new[] { 11, 12 })]
[TestCase("The Series Title (2010) - [S01E01-02-03] - Episode Title", "The Series Title (2010)", 1, new [] { 1, 2, 3 })]
//[TestCase("", "", , new [] { })]
public void should_parse_multiple_episodes(string postTitle, string title, int season, int[] episodes)
{

View File

@@ -224,6 +224,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[Zurako] Log Horizon - 01 - The Apocalypse (BD 1080p AAC) [7AE12174].mkv", false)]
[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)]
public void should_parse_bluray1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.Bluray1080p, proper);

View File

@@ -23,7 +23,7 @@ namespace NzbDrone.Core.DiskSpace
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/boot(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled);
private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled);
public DiskSpaceService(ISeriesService seriesService, IConfigService configService, IDiskProvider diskProvider, Logger logger)
{

View File

@@ -104,6 +104,8 @@ namespace NzbDrone.Core.Download.Clients.Deluge
foreach (var torrent in torrents)
{
if (torrent.Hash == null) continue;
var item = new DownloadClientItem();
item.DownloadId = torrent.Hash.ToUpper();
item.Title = torrent.Name;

View File

@@ -286,7 +286,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
return new NzbDroneValidationFailure("TvCategory", "Category does not exist")
{
InfoLink = string.Format("http://{0}:{1}/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings),
DetailedDescription = "The Category your entered doesn't exist in NzbGet. Go to NzbGet to create it."
};
}
@@ -304,7 +304,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
return new NzbDroneValidationFailure(string.Empty, "NzbGet setting KeepHistory should be greater than 0")
{
InfoLink = string.Format("http://{0}:{1}/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings),
DetailedDescription = "NzbGet setting KeepHistory is set to 0. Which prevents Sonarr from seeing completed downloads."
};
}
@@ -312,7 +312,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
return new NzbDroneValidationFailure(string.Empty, "NzbGet setting KeepHistory should be less than 25000")
{
InfoLink = string.Format("http://{0}:{1}/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings),
DetailedDescription = "NzbGet setting KeepHistory is set too high."
};
}

View File

@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
public interface INzbgetProxy
{
string GetBaseUrl(NzbgetSettings settings, string relativePath = null);
string DownloadNzb(byte[] nzbData, string title, string category, int priority, bool addpaused, NzbgetSettings settings);
NzbgetGlobalStatus GetGlobalStatus(NzbgetSettings settings);
List<NzbgetQueueItem> GetQueue(NzbgetSettings settings);
@@ -36,9 +37,17 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
_versionCache = cacheManager.GetCache<string>(GetType(), "versions");
}
public string GetBaseUrl(NzbgetSettings settings, string relativePath = null)
{
var baseUrl = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase);
baseUrl = HttpUri.CombinePath(baseUrl, relativePath);
return baseUrl;
}
private bool HasVersion(int minimumVersion, NzbgetSettings settings)
{
var versionString = _versionCache.Find(settings.Host + ":" + settings.Port) ?? GetVersion(settings);
var versionString = _versionCache.Find(GetBaseUrl(settings)) ?? GetVersion(settings);
var version = int.Parse(versionString.Split(new[] { '.', '-' })[0]);
@@ -139,7 +148,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
var response = ProcessRequest<string>(settings, "version");
_versionCache.Set(settings.Host + ":" + settings.Port, response, TimeSpan.FromDays(1));
_versionCache.Set(GetBaseUrl(settings), response, TimeSpan.FromDays(1));
return response;
}
@@ -170,7 +179,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
queueItem = queue.SingleOrDefault(h => h.Parameters.Any(p => p.Name == "drone" && id == (p.Value as string)));
historyItem = history.SingleOrDefault(h => h.Parameters.Any(p => p.Name == "drone" && id == (p.Value as string)));
}
if (queueItem != null)
{
if (!EditQueue("GroupFinalDelete", 0, "", queueItem.NzbId, settings))
@@ -218,7 +227,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
private T ProcessRequest<T>(NzbgetSettings settings, string method, params object[] parameters)
{
var baseUrl = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, "jsonrpc");
var baseUrl = GetBaseUrl(settings, "jsonrpc");
var requestBuilder = new JsonRpcRequestBuilder(baseUrl, method, parameters);
requestBuilder.LogResponseContent = true;

View File

@@ -1,4 +1,5 @@
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -11,6 +12,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.UrlBase).ValidUrlBase().When(c => c.UrlBase.IsNotNullOrWhiteSpace());
RuleFor(c => c.Username).NotEmpty().When(c => !string.IsNullOrWhiteSpace(c.Password));
RuleFor(c => c.Password).NotEmpty().When(c => !string.IsNullOrWhiteSpace(c.Username));
@@ -37,25 +40,28 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the nzbget url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)]
public string Username { get; set; }
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
public string TvCategory { get; set; }
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
public int RecentTvPriority { get; set; }
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(7, Label = "Add Paused", Type = FieldType.Checkbox, HelpText = "This option requires at least NzbGet version 16.0")]
[FieldDefinition(8, Label = "Add Paused", Type = FieldType.Checkbox, HelpText = "This option requires at least NzbGet version 16.0")]
public bool AddPaused { get; set; }
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
[FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]
public bool UseSsl { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -142,6 +142,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
case "stalledUP": // torrent is being seeded, but no connection were made
case "queuedUP": // queuing is enabled and torrent is queued for upload
case "checkingUP": // torrent has finished downloading and is being checked
case "forcedUP": // torrent has finished downloading and is being forcibly seeded
item.Status = DownloadItemStatus.Completed;
item.RemainingTime = TimeSpan.Zero; // qBittorrent sends eta=8640000 for completed torrents
break;

View File

@@ -419,7 +419,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
return new NzbDroneValidationFailure("", "Disable 'Check before download' option in Sabnbzd")
{
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/switches/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings, "config/switches/"),
DetailedDescription = "Using Check before download affects Sonarr ability to track new downloads. Also Sabnzbd recommends 'Abort jobs that cannot be completed' instead since it's more effective."
};
}
@@ -438,7 +438,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
return new NzbDroneValidationFailure("TvCategory", "Enable Job folders")
{
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/categories/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings, "config/categories/"),
DetailedDescription = "Sonarr prefers each download to have a separate folder. With * appended to the Folder/Path Sabnzbd will not create these job folders. Go to Sabnzbd to fix it."
};
}
@@ -449,7 +449,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
return new NzbDroneValidationFailure("TvCategory", "Category does not exist")
{
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/categories/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings, "config/categories/"),
DetailedDescription = "The Category your entered doesn't exist in Sabnzbd. Go to Sabnzbd to create it."
};
}
@@ -458,7 +458,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting")
{
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"),
DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it."
};
}
@@ -466,7 +466,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting")
{
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"),
DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it."
};
}
@@ -474,7 +474,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting")
{
InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port),
InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"),
DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Sonarr uses to prevent import issues. Go to Sabnzbd to fix it."
};
}

View File

@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public interface ISabnzbdProxy
{
string GetBaseUrl(SabnzbdSettings settings, string relativePath = null);
SabnzbdAddResponse DownloadNzb(byte[] nzbData, string filename, string category, int priority, SabnzbdSettings settings);
void RemoveFrom(string source, string id,bool deleteData, SabnzbdSettings settings);
string GetVersion(SabnzbdSettings settings);
@@ -32,6 +33,14 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
_logger = logger;
}
public string GetBaseUrl(SabnzbdSettings settings, string relativePath = null)
{
var baseUrl = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase);
baseUrl = HttpUri.CombinePath(baseUrl, relativePath);
return baseUrl;
}
public SabnzbdAddResponse DownloadNzb(byte[] nzbData, string filename, string category, int priority, SabnzbdSettings settings)
{
var request = BuildRequest("addfile", settings).Post();
@@ -140,10 +149,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
private HttpRequestBuilder BuildRequest(string mode, SabnzbdSettings settings)
{
var baseUrl = string.Format(@"{0}://{1}:{2}/api",
settings.UseSsl ? "https" : "http",
settings.Host,
settings.Port);
var baseUrl = GetBaseUrl(settings, "api");
var requestBuilder = new HttpRequestBuilder(baseUrl)
.Accept(HttpAccept.Json)

View File

@@ -1,4 +1,5 @@
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -11,6 +12,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.UrlBase).ValidUrlBase().When(c => c.UrlBase.IsNotNullOrWhiteSpace());
RuleFor(c => c.ApiKey).NotEmpty()
.WithMessage("API Key is required when username/password are not configured")
@@ -49,25 +51,28 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "API Key", Type = FieldType.Textbox)]
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Sabnzbd url, e.g. http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox)]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)]
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox)]
public string Username { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
[FieldDefinition(5, Label = "Password", Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
public string TvCategory { get; set; }
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
public int RecentTvPriority { get; set; }
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
[FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]
public bool UseSsl { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -172,7 +172,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
"errorString",
"uploadedEver",
"downloadedEver",
"seedRatioLimit"
"seedRatioLimit",
"fileCount"
};
var arguments = new Dictionary<string, object>();

View File

@@ -3,31 +3,19 @@
public class TransmissionTorrent
{
public int Id { get; set; }
public string HashString { get; set; }
public string Name { get; set; }
public string DownloadDir { get; set; }
public long TotalSize { get; set; }
public long LeftUntilDone { get; set; }
public bool IsFinished { get; set; }
public int Eta { get; set; }
public TransmissionTorrentStatus Status { get; set; }
public int SecondsDownloading { get; set; }
public string ErrorString { get; set; }
public long DownloadedEver { get; set; }
public long UploadedEver { get; set; }
public long SeedRatioLimit { get; set; }
public int FileCount { get; set; }
}
}

View File

@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Download.Clients.Vuze
// - A multi-file torrent is downloaded in a job folder and 'outputPath' points to that directory directly.
// - A single-file torrent is downloaded in the root folder and 'outputPath' poinst to that root folder.
// We have to make sure the return value points to the job folder OR file.
if (outputPath == null || outputPath.FileName == torrent.Name)
if (outputPath == null || outputPath.FileName == torrent.Name || torrent.FileCount > 1)
{
_logger.Trace("Vuze output directory: {0}", outputPath);
}

View File

@@ -2,7 +2,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
{
public enum RTorrentPriority
{
DoNotDownload = 0,
VeryLow = 0,
Low = 1,
Normal = 2,
High = 3

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@@ -26,9 +26,15 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[XmlRpcMethod("d.multicall2")]
object[] TorrentMulticall(params string[] parameters);
[XmlRpcMethod("load.normal")]
int LoadNormal(string target, string data, params string[] commands);
[XmlRpcMethod("load.start")]
int LoadStart(string target, string data, params string[] commands);
[XmlRpcMethod("load.raw")]
int LoadRaw(string target, byte[] data, params string[] commands);
[XmlRpcMethod("load.raw_start")]
int LoadRawStart(string target, byte[] data, params string[] commands);
@@ -107,10 +113,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
public void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings)
{
_logger.Debug("Executing remote method: load.normal");
var client = BuildClient(settings);
var response = ExecuteRequest(() => client.LoadStart("", torrentUrl, GetCommands(label, priority, directory)));
var response = ExecuteRequest(() =>
{
if (settings.AddStopped)
{
_logger.Debug("Executing remote method: load.normal");
return client.LoadNormal("", torrentUrl, GetCommands(label, priority, directory));
}
else
{
_logger.Debug("Executing remote method: load.start");
return client.LoadStart("", torrentUrl, GetCommands(label, priority, directory));
}
});
if (response != 0)
{
@@ -120,10 +136,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
public void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings)
{
_logger.Debug("Executing remote method: load.raw");
var client = BuildClient(settings);
var response = ExecuteRequest(() => client.LoadRawStart("", fileContent, GetCommands(label, priority, directory)));
var response = ExecuteRequest(() =>
{
if (settings.AddStopped)
{
_logger.Debug("Executing remote method: load.raw");
return client.LoadRaw("", fileContent, GetCommands(label, priority, directory));
}
else
{
_logger.Debug("Executing remote method: load.raw_start");
return client.LoadRawStart("", fileContent, GetCommands(label, priority, directory));
}
});
if (response != 0)
{

View File

@@ -1,4 +1,4 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -61,6 +61,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(10, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will prevent magnets from downloading before downloading")]
public bool AddStopped { 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;
@@ -163,6 +163,18 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
tvShow.Add(new XElement("mpaa", series.Certification));
tvShow.Add(new XElement("id", series.TvdbId));
var uniqueId = new XElement("uniqueid", series.TvdbId);
uniqueId.SetAttributeValue("type", "tvdb");
uniqueId.SetAttributeValue("default", true);
tvShow.Add(uniqueId);
if (series.ImdbId.IsNotNullOrWhiteSpace())
{
var imdbId = new XElement("uniqueid", series.ImdbId);
imdbId.SetAttributeValue("type", "imdb");
tvShow.Add(imdbId);
}
foreach (var genre in series.Genres)
{
tvShow.Add(new XElement("genre", genre));

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Extras.Subtitles
@@ -24,7 +24,8 @@ namespace NzbDrone.Core.Extras.Subtitles
".txt",
".utf",
".utf8",
".utf-8"
".utf-8",
".vtt"
};
}

View File

@@ -30,11 +30,15 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators
if (!otherFiles && !SceneChecker.IsSceneTitle(Path.GetFileNameWithoutExtension(localEpisode.Path)))
{
if (downloadClientEpisodeInfo != null && !downloadClientEpisodeInfo.FullSeason)
if (downloadClientEpisodeInfo != null &&
!downloadClientEpisodeInfo.FullSeason &&
PreferOtherEpisodeInfo(parsedEpisodeInfo, downloadClientEpisodeInfo))
{
parsedEpisodeInfo = localEpisode.DownloadClientEpisodeInfo;
}
else if (folderEpisodeInfo != null && !folderEpisodeInfo.FullSeason)
else if (folderEpisodeInfo != null &&
!folderEpisodeInfo.FullSeason &&
PreferOtherEpisodeInfo(parsedEpisodeInfo, folderEpisodeInfo))
{
parsedEpisodeInfo = localEpisode.FolderEpisodeInfo;
}
@@ -68,5 +72,21 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators
return new List<Episode>();
}
private bool PreferOtherEpisodeInfo(ParsedEpisodeInfo fileEpisodeInfo, ParsedEpisodeInfo otherEpisodeInfo)
{
if (fileEpisodeInfo == null)
{
return true;
}
// When the files episode info is not absolute prefer it over a parsed episode info that is absolute
if (!fileEpisodeInfo.IsAbsoluteNumbering && otherEpisodeInfo.IsAbsoluteNumbering)
{
return false;
}
return true;
}
}
}

View File

@@ -61,7 +61,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
downloadClientItemInfo = Parser.Parser.ParseTitle(downloadClientItem.Title);
}
var nonSampleVideoFileCount = GetNonSampleVideoFileCount(newFiles, series, downloadClientItemInfo, folderInfo);
// If not importing from a scene source (series folder for example), then assume all files are not samples
// to avoid using media info on every file needlessly (especially if Analyse Media Files is disabled).
var nonSampleVideoFileCount = sceneSource ? GetNonSampleVideoFileCount(newFiles, series, downloadClientItemInfo, folderInfo) : videoFiles.Count;
var decisions = new List<ImportDecision>();

View File

@@ -1,14 +1,47 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{
public class ManualImportFile
public class ManualImportFile : IEquatable<ManualImportFile>
{
public string Path { get; set; }
public string FolderName { get; set; }
public int SeriesId { get; set; }
public List<int> EpisodeIds { get; set; }
public QualityModel Quality { get; set; }
public string DownloadId { get; set; }
public bool Equals(ManualImportFile other)
{
if (other == null)
{
return false;
}
return Path.PathEquals(other.Path);
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (obj.GetType() != GetType())
{
return false;
}
return Path.PathEquals(((ManualImportFile)obj).Path);
}
public override int GetHashCode()
{
return Path != null ? Path.GetHashCode() : 0;
}
}
}

View File

@@ -9,6 +9,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{
public string Path { get; set; }
public string RelativePath { get; set; }
public string FolderName { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public Series Series { get; set; }

View File

@@ -90,16 +90,17 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
return new List<ManualImportItem>();
}
return new List<ManualImportItem> { ProcessFile(path, downloadId) };
var rootFolder = Path.GetDirectoryName(path);
return new List<ManualImportItem> { ProcessFile(rootFolder, rootFolder, path, downloadId) };
}
return ProcessFolder(path, downloadId);
return ProcessFolder(path, path, downloadId);
}
private List<ManualImportItem> ProcessFolder(string folder, string downloadId)
private List<ManualImportItem> ProcessFolder(string rootFolder, string baseFolder, string downloadId)
{
DownloadClientItem downloadClientItem = null;
var directoryInfo = new DirectoryInfo(folder);
var directoryInfo = new DirectoryInfo(baseFolder);
var series = _parsingService.GetSeries(directoryInfo.Name);
if (downloadId.IsNotNullOrWhiteSpace())
@@ -115,27 +116,26 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
if (series == null)
{
var files = _diskScanService.FilterFiles(folder, _diskScanService.GetVideoFiles(folder));
var files = _diskScanService.FilterFiles(baseFolder, _diskScanService.GetVideoFiles(baseFolder, false));
var subfolders = _diskScanService.FilterFiles(baseFolder, _diskProvider.GetDirectories(baseFolder));
return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
var processedFiles = files.Select(file => ProcessFile(rootFolder, baseFolder, file, downloadId));
var processedFolders = subfolders.SelectMany(subfolder => ProcessFolder(rootFolder, subfolder, downloadId));
return processedFiles.Concat(processedFolders).Where(i => i != null).ToList();
}
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, downloadClientItem, folderInfo, SceneSource(series, folder));
var seriesFiles = _diskScanService.GetVideoFiles(baseFolder).ToList();
var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, downloadClientItem, folderInfo, SceneSource(series, baseFolder));
return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList();
return decisions.Select(decision => MapItem(decision, rootFolder, downloadId, directoryInfo.Name)).ToList();
}
private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
private ManualImportItem ProcessFile(string rootFolder, string baseFolder, string file, string downloadId)
{
if (folder.IsNullOrWhiteSpace())
{
folder = new FileInfo(file).Directory.FullName;
}
DownloadClientItem downloadClientItem = null;
var relativeFile = folder.GetRelativePath(file);
var relativeFile = baseFolder.GetRelativePath(file);
var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
if (series == null)
@@ -171,23 +171,25 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
localEpisode.Quality = QualityParser.ParseQuality(file);
localEpisode.Size = _diskProvider.GetFileSize(file);
return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), rootFolder, downloadId, null);
}
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
series, downloadClientItem, null, SceneSource(series, folder));
series, downloadClientItem, null, SceneSource(series, baseFolder));
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : new ManualImportItem
{
DownloadId = downloadId,
Path = file,
RelativePath = folder.GetRelativePath(file),
Name = Path.GetFileNameWithoutExtension(file),
Rejections = new List<Rejection>
{
new Rejection("Unable to process file")
}
};
if (importDecisions.Any())
{
return MapItem(importDecisions.First(), rootFolder, downloadId, null);
}
return new ManualImportItem
{
DownloadId = downloadId,
Path = file,
RelativePath = rootFolder.GetRelativePath(file),
Name = Path.GetFileNameWithoutExtension(file),
Rejections = new List<Rejection>()
};
}
private bool SceneSource(Series series, string folder)
@@ -195,12 +197,13 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder));
}
private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
private ManualImportItem MapItem(ImportDecision decision, string rootFolder, string downloadId, string folderName)
{
var item = new ManualImportItem();
item.Path = decision.LocalEpisode.Path;
item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
item.FolderName = folderName;
item.RelativePath = rootFolder.GetRelativePath(decision.LocalEpisode.Path);
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
item.DownloadId = downloadId;
@@ -250,14 +253,13 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
var series = _seriesService.GetSeries(file.SeriesId);
var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
var fileEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
var existingFile = series.Path.IsParentPath(file.Path);
TrackedDownload trackedDownload = null;
var localEpisode = new LocalEpisode
{
ExistingFile = false,
Episodes = episodes,
MediaInfo = mediaInfo,
FileEpisodeInfo = fileEpisodeInfo,
Path = file.Path,
Quality = file.Quality,
@@ -265,20 +267,37 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
Size = 0
};
//TODO: Cleanup non-tracked downloads
if (file.DownloadId.IsNotNullOrWhiteSpace())
{
trackedDownload = _trackedDownloadService.Find(file.DownloadId);
if (trackedDownload != null)
{
localEpisode.DownloadClientEpisodeInfo = trackedDownload.RemoteEpisode.ParsedEpisodeInfo;
}
}
if (file.FolderName.IsNotNullOrWhiteSpace())
{
localEpisode.FolderEpisodeInfo = Parser.Parser.ParseTitle(file.FolderName);
}
localEpisode = _augmentingService.Augment(localEpisode, false);
// Apply the user-chosen values.
localEpisode.Series = series;
localEpisode.Episodes = episodes;
localEpisode.Quality = file.Quality;
//TODO: Cleanup non-tracked downloads
var importDecision = new ImportDecision(localEpisode);
if (file.DownloadId.IsNullOrWhiteSpace())
if (trackedDownload == null)
{
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
}
else
{
var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
imported.Add(importResult);

View File

@@ -16,6 +16,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.FileEpisodeInfo == null)
{
return Decision.Accept();
}
if (localEpisode.FileEpisodeInfo.FullSeason)
{
_logger.Debug("Single episode file detected as containing all episodes in the season");

View File

@@ -34,11 +34,19 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
if (folderInfo == null)
{
_logger.Debug("No folder ParsedEpisodeInfo, skipping check");
return Decision.Accept();
}
if (fileInfo == null)
{
_logger.Debug("No file ParsedEpisodeInfo, skipping check");
return Decision.Accept();
}
if (!folderInfo.EpisodeNumbers.Any())
{
_logger.Debug("No episode numbers in folder ParsedEpisodeInfo, skipping check");
return Decision.Accept();
}

View File

@@ -99,7 +99,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
string scanType = mediaInfo.Get(StreamKind.Video, 0, "ScanType");
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Width"), out width);
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Height"), out height);
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate);
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate_Nominal"), out videoBitRate);
if (videoBitRate <= 0)
{
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate);
}
decimal.TryParse(mediaInfo.Get(StreamKind.Video, 0, "FrameRate"), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out videoFrameRate);
int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitDepth"), out videoBitDepth);

View File

@@ -49,10 +49,13 @@ namespace NzbDrone.Core.Messaging.Commands
if (typeof(IEnumerable).IsAssignableFrom(xProperty.PropertyType))
{
var xValueCollection = ((IEnumerable)xValue).Cast<object>().OrderBy(t => t);
var yValueCollection = ((IEnumerable)yValue).Cast<object>().OrderBy(t => t);
var xValueCollection = ((IEnumerable)xValue).Cast<object>();
var yValueCollection = ((IEnumerable)yValue).Cast<object>();
if (!xValueCollection.SequenceEqual(yValueCollection))
var xNotY = xValueCollection.Except(yValueCollection);
var yNotX = yValueCollection.Except(xValueCollection);
if (xNotY.Any() || yNotX.Any())
{
return false;
}

View File

@@ -1,4 +1,4 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Notifications.Pushover
}
//TODO: Get Pushover to change our app name (or create a new app) when we have a new logo
[FieldDefinition(0, Label = "API Key", HelpLink = "https://pushover.net/apps/clone/nzbdrone")]
[FieldDefinition(0, Label = "API Key", HelpLink = "https://pushover.net/apps/clone/sonarr")]
public string ApiKey { get; set; }
[FieldDefinition(1, Label = "User Key", HelpLink = "https://pushover.net/")]

View File

@@ -95,7 +95,7 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+S?(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))(?:[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+S?(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))(?:[ex]|\W[ex]){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with a title, 4 digit season number, Single episodes (S2016E05, etc) & Multi-episode (S2016E05E06, S2016E05-06, S2016E05 E06, etc)
@@ -103,7 +103,7 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with a title, 4 digit season number, Single episodes (2016x05, etc) & Multi-episode (2016x05x06, 2016x05-06, 2016x05 x06, etc)
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<season>(?<!\d+)(?:\d{4})(?!\d+))(?:x|\Wx|_){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|x|\Wx|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<season>(?<!\d+)(?:\d{4})(?!\d+))(?:x|\Wx){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|x|\Wx|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Partial season pack
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Mini-Series, treated as season 1, multi episodes are labelled as E1-E2
new Regex(@"^(?<title>.+?)(?:\W[e])(?<episode>\d{2,3}(?!\d+))(?:(?:\-?[e])(?<episode>\d{2,3}(?!\d+)))+",
new Regex(@"^(?<title>.+?)(?:[-._ ][e])(?<episode>\d{2,3}(?!\d+))(?:(?:\-?[e])(?<episode>\d{2,3}(?!\d+)))+",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Mini-Series, treated as season 1, episodes are labelled as Part01, Part 01, Part.1
@@ -134,8 +134,12 @@ namespace NzbDrone.Core.Parser
new Regex(@"(?:.*(?:\""|^))(?<title>.*?)(?:[-_\W](?<![()\[]))+(?:\W?Season\W?)(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)+(?:Episode\W)(?:[-_. ]?(?<episode>(?<!\d+)\d{1,2}(?!\d+)))+",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Multi-episode with episodes in square brackets (Series Title [S01E11E12] or Series Title [S01E11-12])
new Regex(@"(?:.*(?:^))(?<title>.*?)[-._ ]+\[S(?<season>(?<!\d+)\d{2}(?!\d+))(?:[E-]{1,2}(?<episode>(?<!\d+)\d{2}(?!\d+)))+\]",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Multi-episode release with no space between series title and season (S01E11E12)
new Regex(@"(?:.*(?:^))(?<title>.*?)(?:\W?|_)S(?<season>(?<!\d+)\d{2}(?!\d+))(?:E(?<episode>(?<!\d+)\d{2}(?!\d+)))+",
new Regex(@"(?:.*(?:^))(?<title>.*?)S(?<season>(?<!\d+)\d{2}(?!\d+))(?:E(?<episode>(?<!\d+)\d{2}(?!\d+)))+",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Multi-episode with single episode numbers (S6.E1-E2, S6.E1E2, S6E1E2, etc)
@@ -162,6 +166,10 @@ namespace NzbDrone.Core.Parser
new Regex(@"^(?<title>.+?)(?:_|-|\s|\.)+S(?<season>\d{2}(?!\d+))(\W-\W)E(?<episode>(?<!\d+)\d{2}(?!\d+))(?!\\)",
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+|[-]))",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Season only releases
new Regex(@"^(?<title>.+?)\W(?:S|Season)\W?(?<season>\d{1,2}(?!\d+))(\W+|_|$)(?<extras>EXTRAS|SUBPACK)?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -189,11 +197,11 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with airdate (2018.04.28)
new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])(?!\W+[0-3][0-9])",
new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})[-_. ]+(?<airmonth>[0-1][0-9])[-_. ]+(?<airday>[0-3][0-9])(?![-_. ]+[0-3][0-9])",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with airdate (04.28.2018)
new Regex(@"^(?<title>.+?)?\W*(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])\W+(?<airyear>\d{4})(?!\d+)",
new Regex(@"^(?<title>.+?)?\W*(?<airmonth>[0-1][0-9])[-_. ]+(?<airday>[0-3][0-9])[-_. ]+(?<airyear>\d{4})(?!\d+)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Supports 1103/1113 naming
@@ -212,6 +220,7 @@ namespace NzbDrone.Core.Parser
new Regex(@"^(?:(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))(?:-(?<episode>\d{2,3}(?!\d+))))",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// TODO: THIS ONE
//Anime - Title Absolute Episode Number (e66)
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?:e|ep)(?<absoluteepisode>\d{2,3}))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -243,7 +252,7 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled)
};
private static readonly Regex[] RejectHashedReleasesRegex = new Regex[]
private static readonly Regex[] RejectHashedReleasesRegexes = new Regex[]
{
// Generic match for md5 and mixed-case hashes.
new Regex(@"^[0-9a-zA-Z]{32}", RegexOptions.Compiled),
@@ -266,7 +275,10 @@ namespace NzbDrone.Core.Parser
new Regex(@"^abc$", RegexOptions.Compiled | RegexOptions.IgnoreCase),
//b00bs - Started appearing January 2015
new Regex(@"^b00bs$", RegexOptions.Compiled | RegexOptions.IgnoreCase)
new Regex(@"^b00bs$", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// 170424_26 - Started appearing August 2018
new Regex(@"^\d{6}_\d{2}$"),
};
//Regex to detect whether the title was reversed.
@@ -278,7 +290,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)?)\s*?",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*|^www\.[a-z]+\.(?:com|net)[ -]*",
@@ -742,7 +754,7 @@ namespace NzbDrone.Core.Parser
var titleWithoutExtension = RemoveFileExtension(title);
if (RejectHashedReleasesRegex.Any(v => v.IsMatch(titleWithoutExtension)))
if (RejectHashedReleasesRegexes.Any(v => v.IsMatch(titleWithoutExtension)))
{
Logger.Debug("Rejected Hashed Release Title: " + title);
return false;

View File

@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Parser
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(QualityParser));
private static readonly Regex SourceRegex = new Regex(@"\b(?:
(?<bluray>BluRay|Blu-Ray|HDDVD|BD)|
(?<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)|
(?<hdtv>HDTV)|
(?<bdrip>BDRip)|

View File

@@ -1,3 +1,4 @@
using System;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
@@ -24,5 +25,31 @@ namespace NzbDrone.Libraries.Test.JsonTests
result.ShouldBeEquivalentTo(quality, o => o.IncludingAllRuntimeProperties());
}
[Test]
public void should_log_start_snippet_on_failure()
{
try
{
Json.Deserialize<object>("asdfl kasjd fsdfs derers");
}
catch (Exception ex)
{
ex.Message.Should().Contain("snippet '<--error-->asdfl kasjd fsdfs de'");
}
}
[Test]
public void should_log_line_snippet_on_failure()
{
try
{
Json.Deserialize<object>("{ \"a\": \r\n\"b\",\r\n \"b\": \"c\", asdfl kasjd fsdfs derers vsdfsdf");
}
catch (Exception ex)
{
ex.Message.Should().Contain("snippet ' \"b\": \"c\", asdfl <--error-->kasjd fsdfs derers v'");
}
}
}
}

View File

@@ -211,6 +211,7 @@ module.exports = Marionette.Layout.extend({
files : _.map(selected, function (file) {
return {
path : file.get('path'),
folderName : file.get('folderName'),
seriesId : file.get('series').id,
episodeIds : _.map(file.get('episodes'), 'id'),
quality : file.get('quality'),