mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-05 13:20:20 -05:00
Compare commits
56 Commits
v2.0.0.432
...
v2.0.0.438
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b093be3f4e | ||
|
|
3c8b263694 | ||
|
|
43c5d03f9a | ||
|
|
9fbe06ad68 | ||
|
|
db899a9bb8 | ||
|
|
d3890bd712 | ||
|
|
1a61796092 | ||
|
|
1251e294cd | ||
|
|
0411b82e65 | ||
|
|
9519f3137c | ||
|
|
f8d97cac7d | ||
|
|
f2ecbe776b | ||
|
|
1ac442d0e6 | ||
|
|
5f2aeb0cea | ||
|
|
2ece05cd1e | ||
|
|
25a3f83ebc | ||
|
|
cdce65a922 | ||
|
|
8f73a51522 | ||
|
|
bc438a6a63 | ||
|
|
4167ffe11a | ||
|
|
6fb1aa85d0 | ||
|
|
eb8ef6c337 | ||
|
|
c076f1ddb1 | ||
|
|
eeff79b288 | ||
|
|
697a62da0a | ||
|
|
f1a289cc74 | ||
|
|
c39a26d9e0 | ||
|
|
f2ccf94835 | ||
|
|
19d625c6c5 | ||
|
|
cd3b6000a0 | ||
|
|
ff33f15bac | ||
|
|
50a0e9514e | ||
|
|
7ef1ca8a00 | ||
|
|
e0d1e08f94 | ||
|
|
9fae76015a | ||
|
|
17bf438cad | ||
|
|
c0b0567c23 | ||
|
|
edc1e0b8d1 | ||
|
|
cd79b42f5f | ||
|
|
dc82e66dde | ||
|
|
b034d0c1b3 | ||
|
|
36a3e86882 | ||
|
|
e76fb8c90b | ||
|
|
e6288148ad | ||
|
|
bf8d68a873 | ||
|
|
080e2e9eff | ||
|
|
e3310e590c | ||
|
|
a0b4d3a38d | ||
|
|
a72b856fb8 | ||
|
|
522ef9d8d5 | ||
|
|
a486bff40b | ||
|
|
0de1f3f17a | ||
|
|
755fdce227 | ||
|
|
cd8659e684 | ||
|
|
a621f0d49b | ||
|
|
2e96c4e798 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -41,6 +41,9 @@ src/**/[Oo]bj/
|
||||
_ReSharper*
|
||||
_dotCover*
|
||||
|
||||
# DevExpress CodeRush
|
||||
src/.cr/
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
6
.idea/encodings.xml
generated
Normal file
6
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/jsLibraryMappings.xml
generated
1
.idea/jsLibraryMappings.xml
generated
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="file://$PROJECT_DIR$" libraries="{Sonarr node_modules}" />
|
||||
<includedPredefinedLibrary name="ECMAScript 6" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -8,8 +8,8 @@ Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
## Development ##
|
||||
|
||||
### Tools required ###
|
||||
- Visual Studio 2013
|
||||
- HTML/Javascript editor of choice (Sublime Text/Webstorm/etc)
|
||||
- Visual Studio 2015
|
||||
- HTML/Javascript editor of choice (Sublime Text/Webstorm/Atom/etc)
|
||||
- npm (node package manager)
|
||||
- git
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@
|
||||
<xs:enumeration value="seedtype" /> <!-- TBD, which criteria must be met. was going for 'ratio,seedtime,both' but afaik it's always 'either' -->
|
||||
<xs:enumeration value="minimumratio" />
|
||||
<xs:enumeration value="minimumseedtime" />
|
||||
<xs:enumeration value="downloadvolumefactor" /> <!-- factor for the download volume, in most cases it should be set to 1, if a torrent is set to freeleech set it to 0, if only 50% is counted set it to 0.5 -->
|
||||
<xs:enumeration value="uploadvolumefactor" /> <!-- factor for the upload volume, in most cases it should be set to 1, if a torrent is set to neutral leech (upload is not counted) set it to 0, if it's set to double upload set it to 2 -->
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:element name="attr">
|
||||
|
||||
@@ -198,7 +198,8 @@ namespace Marr.Data.Mapping
|
||||
{
|
||||
return AutoMapPropertiesWhere(m =>
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
!DataHelper.IsSimpleType((m as PropertyInfo).PropertyType));
|
||||
!DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) &&
|
||||
!MapRepository.Instance.TypeConverters.ContainsKey((m as PropertyInfo).PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Api.Authentication
|
||||
expiry = DateTime.UtcNow.AddDays(7);
|
||||
}
|
||||
|
||||
return this.LoginAndRedirect(user.Identifier, expiry);
|
||||
return this.LoginAndRedirect(user.Identifier, expiry, _configFileProvider.UrlBase + "/");
|
||||
}
|
||||
|
||||
private Response Logout()
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace NzbDrone.Api.Authentication
|
||||
if (context.Request.IsApiRequest())
|
||||
{
|
||||
if ((context.Response.StatusCode == HttpStatusCode.SeeOther &&
|
||||
context.Response.Headers["Location"].StartsWith("/login", StringComparison.InvariantCultureIgnoreCase)) ||
|
||||
context.Response.Headers["Location"].StartsWith($"{_configFileProvider.UrlBase}/login", StringComparison.InvariantCultureIgnoreCase)) ||
|
||||
context.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
context.Response = new { Error = "Unauthorized" }.AsResponse(HttpStatusCode.Unauthorized);
|
||||
|
||||
@@ -5,17 +5,21 @@ using System.Linq;
|
||||
using DDay.iCal;
|
||||
using NzbDrone.Core.Tv;
|
||||
using Nancy.Responses;
|
||||
using NzbDrone.Core.Tags;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Api.Calendar
|
||||
{
|
||||
public class CalendarFeedModule : NzbDroneFeedModule
|
||||
{
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly ITagService _tagService;
|
||||
|
||||
public CalendarFeedModule(IEpisodeService episodeService)
|
||||
public CalendarFeedModule(IEpisodeService episodeService, ITagService tagService)
|
||||
: base("calendar")
|
||||
{
|
||||
_episodeService = episodeService;
|
||||
_tagService = tagService;
|
||||
|
||||
Get["/NzbDrone.ics"] = options => GetCalendarFeed();
|
||||
}
|
||||
@@ -23,10 +27,12 @@ namespace NzbDrone.Api.Calendar
|
||||
private Response GetCalendarFeed()
|
||||
{
|
||||
var pastDays = 7;
|
||||
var futureDays = 28;
|
||||
var futureDays = 28;
|
||||
var start = DateTime.Today.AddDays(-pastDays);
|
||||
var end = DateTime.Today.AddDays(futureDays);
|
||||
var unmonitored = false;
|
||||
var premiersOnly = false;
|
||||
var tags = new List<int>();
|
||||
|
||||
// TODO: Remove start/end parameters in v3, they don't work well for iCal
|
||||
var queryStart = Request.Query.Start;
|
||||
@@ -34,6 +40,8 @@ namespace NzbDrone.Api.Calendar
|
||||
var queryPastDays = Request.Query.PastDays;
|
||||
var queryFutureDays = Request.Query.FutureDays;
|
||||
var queryUnmonitored = Request.Query.Unmonitored;
|
||||
var queryPremiersOnly = Request.Query.PremiersOnly;
|
||||
var queryTags = Request.Query.Tags;
|
||||
|
||||
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
|
||||
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
|
||||
@@ -55,11 +63,32 @@ namespace NzbDrone.Api.Calendar
|
||||
unmonitored = bool.Parse(queryUnmonitored.Value);
|
||||
}
|
||||
|
||||
if (queryPremiersOnly.HasValue)
|
||||
{
|
||||
premiersOnly = bool.Parse(queryPremiersOnly.Value);
|
||||
}
|
||||
|
||||
if (queryTags.HasValue)
|
||||
{
|
||||
var tagInput = (string)queryTags.Value.ToString();
|
||||
tags.AddRange(tagInput.Split(',').Select(_tagService.GetTag).Select(t => t.Id));
|
||||
}
|
||||
|
||||
var episodes = _episodeService.EpisodesBetweenDates(start, end, unmonitored);
|
||||
var icalCalendar = new iCalendar();
|
||||
|
||||
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
|
||||
{
|
||||
if (premiersOnly && (episode.SeasonNumber == 0 || episode.EpisodeNumber != 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tags.Any() && tags.None(episode.Series.Tags.Contains))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var occurrence = icalCalendar.Create<Event>();
|
||||
occurrence.UID = "NzbDrone_episode_" + episode.Id.ToString();
|
||||
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
@@ -21,6 +20,7 @@ namespace NzbDrone.Api.Config
|
||||
|
||||
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
||||
public bool CopyUsingHardlinks { get; set; }
|
||||
public string ExtraFileExtensions { get; set; }
|
||||
public bool EnableMediaInfo { get; set; }
|
||||
}
|
||||
|
||||
@@ -44,7 +44,8 @@ namespace NzbDrone.Api.Config
|
||||
|
||||
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
|
||||
CopyUsingHardlinks = model.CopyUsingHardlinks,
|
||||
EnableMediaInfo = model.EnableMediaInfo,
|
||||
ExtraFileExtensions = model.ExtraFileExtensions,
|
||||
EnableMediaInfo = model.EnableMediaInfo
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using NzbDrone.Core.Metadata;
|
||||
using NzbDrone.Core.Extras.Metadata;
|
||||
|
||||
namespace NzbDrone.Api.Metadata
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace NzbDrone.Api.REST
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetType().Name.ToLower().Replace("resource", "");
|
||||
return GetType().Name.ToLowerInvariant().Replace("resource", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests.IEnumerableExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ExceptByFixture
|
||||
{
|
||||
public class Object1
|
||||
{
|
||||
public string Prop1 { get; set; }
|
||||
}
|
||||
|
||||
public class Object2
|
||||
{
|
||||
public string Prop1 { get; set; }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_empty_when_object_with_property_exists_in_both_lists()
|
||||
{
|
||||
var first = new List<Object1>
|
||||
{
|
||||
new Object1 { Prop1 = "one" },
|
||||
new Object1 { Prop1 = "two" }
|
||||
};
|
||||
|
||||
var second = new List<Object1>
|
||||
{
|
||||
new Object1 { Prop1 = "two" },
|
||||
new Object1 { Prop1 = "one" }
|
||||
};
|
||||
|
||||
first.ExceptBy(o => o.Prop1, second, o => o.Prop1, StringComparer.InvariantCultureIgnoreCase).Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_objects_that_do_not_have_a_match_in_the_second_list()
|
||||
{
|
||||
var first = new List<Object1>
|
||||
{
|
||||
new Object1 { Prop1 = "one" },
|
||||
new Object1 { Prop1 = "two" }
|
||||
};
|
||||
|
||||
var second = new List<Object1>
|
||||
{
|
||||
new Object1 { Prop1 = "one" },
|
||||
new Object1 { Prop1 = "four" }
|
||||
};
|
||||
|
||||
var result = first.ExceptBy(o => o.Prop1, second, o => o.Prop1, StringComparer.InvariantCultureIgnoreCase).ToList();
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().GetType().Should().Be(typeof (Object1));
|
||||
result.First().Prop1.Should().Be("two");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests.IEnumerableExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IntersectByFixture
|
||||
{
|
||||
public class Object1
|
||||
{
|
||||
public string Prop1 { get; set; }
|
||||
}
|
||||
|
||||
public class Object2
|
||||
{
|
||||
public string Prop1 { get; set; }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_empty_when_no_intersections()
|
||||
{
|
||||
var first = new List<Object1>
|
||||
{
|
||||
new Object1 { Prop1 = "one" },
|
||||
new Object1 { Prop1 = "two" }
|
||||
};
|
||||
|
||||
var second = new List<Object1>
|
||||
{
|
||||
new Object1 { Prop1 = "three" },
|
||||
new Object1 { Prop1 = "four" }
|
||||
};
|
||||
|
||||
first.IntersectBy(o => o.Prop1, second, o => o.Prop1, StringComparer.InvariantCultureIgnoreCase).Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_objects_with_intersecting_values()
|
||||
{
|
||||
var first = new List<Object1>
|
||||
{
|
||||
new Object1 { Prop1 = "one" },
|
||||
new Object1 { Prop1 = "two" }
|
||||
};
|
||||
|
||||
var second = new List<Object1>
|
||||
{
|
||||
new Object1 { Prop1 = "one" },
|
||||
new Object1 { Prop1 = "four" }
|
||||
};
|
||||
|
||||
var result = first.IntersectBy(o => o.Prop1, second, o => o.Prop1, StringComparer.InvariantCultureIgnoreCase).ToList();
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().Prop1.Should().Be("one");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,8 @@
|
||||
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
|
||||
<Compile Include="EnvironmentTests\StartupArgumentsFixture.cs" />
|
||||
<Compile Include="ExtensionTests\FromOctalStringFixture.cs" />
|
||||
<Compile Include="ExtensionTests\IEnumerableExtensionTests\ExceptByFixture.cs" />
|
||||
<Compile Include="ExtensionTests\IEnumerableExtensionTests\IntersectByFixture.cs" />
|
||||
<Compile Include="ExtensionTests\Int64ExtensionFixture.cs" />
|
||||
<Compile Include="Http\HttpClientFixture.cs" />
|
||||
<Compile Include="Http\HttpRequestBuilderFixture.cs" />
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace NzbDrone.Common.Test
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Sanbox()
|
||||
public void Sandbox()
|
||||
{
|
||||
GetIAppDirectoryInfo().GetUpdateSandboxFolder().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\".AsOsAgnostic());
|
||||
}
|
||||
@@ -271,5 +271,33 @@ namespace NzbDrone.Common.Test
|
||||
{
|
||||
GetIAppDirectoryInfo().GetUpdateLogFolder().Should().BeEquivalentTo(@"C:\NzbDrone\UpdateLogs\".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAncestorFolders_should_return_all_ancestors_in_path_Windows()
|
||||
{
|
||||
WindowsOnly();
|
||||
var path = @"C:\Test\TV\Series Title";
|
||||
var result = path.GetAncestorFolders();
|
||||
|
||||
result.Count.Should().Be(4);
|
||||
result[0].Should().Be(@"C:\");
|
||||
result[1].Should().Be(@"Test");
|
||||
result[2].Should().Be(@"TV");
|
||||
result[3].Should().Be(@"Series Title");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAncestorFolders_should_return_all_ancestors_in_path_Linux()
|
||||
{
|
||||
MonoOnly();
|
||||
var path = @"/Test/TV/Series Title";
|
||||
var result = path.GetAncestorFolders();
|
||||
|
||||
result.Count.Should().Be(4);
|
||||
result[0].Should().Be(@"/");
|
||||
result[1].Should().Be(@"Test");
|
||||
result[2].Should().Be(@"TV");
|
||||
result[3].Should().Be(@"Series Title");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace NzbDrone.Common.Disk
|
||||
.Select(d => new FileSystemModel
|
||||
{
|
||||
Type = FileSystemEntityType.Drive,
|
||||
Name = d.VolumeLabel,
|
||||
Name = d.VolumeName,
|
||||
Path = d.RootDirectory,
|
||||
LastModified = null
|
||||
})
|
||||
@@ -163,7 +163,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
private string GetParent(string path)
|
||||
{
|
||||
var di = new DirectoryInfo(path);
|
||||
|
||||
@@ -13,5 +13,6 @@ namespace NzbDrone.Common.Disk
|
||||
long TotalFreeSpace { get; }
|
||||
long TotalSize { get; }
|
||||
string VolumeLabel { get; }
|
||||
string VolumeName { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) &&
|
||||
serviceProvider.GetStatus(ServiceProvider.NZBDRONE_SERVICE_NAME) == ServiceControllerStatus.StartPending;
|
||||
|
||||
//Guarded to avoid issues when running in a non-managed process
|
||||
//Guarded to avoid issues when running in a non-managed process
|
||||
var entry = Assembly.GetEntryAssembly();
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
ExecutingApplication = entry.Location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RuntimeInfoBase()
|
||||
@@ -69,7 +69,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
public bool IsWindowsService { get; private set; }
|
||||
|
||||
public bool IsConsole
|
||||
{
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
@@ -78,7 +78,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRunning { get; set; }
|
||||
@@ -105,7 +105,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
@@ -115,15 +115,14 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
string lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
|
||||
if (lowerCurrentDir.Contains("teamcity")) return false;
|
||||
if (lowerCurrentDir.Contains("_output")) return false;
|
||||
if (lowerCurrentDir.StartsWith("/run/")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,44 @@ namespace NzbDrone.Common.Extensions
|
||||
return source.Where(element => knownKeys.Add(keySelector(element)));
|
||||
}
|
||||
|
||||
public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first, Func<TFirst, TKey> firstKeySelector,
|
||||
IEnumerable<TSecond> second, Func<TSecond, TKey> secondKeySelector,
|
||||
IEqualityComparer<TKey> keyComparer)
|
||||
{
|
||||
var keys = new HashSet<TKey>(second.Select(secondKeySelector), keyComparer);
|
||||
|
||||
foreach (var element in first)
|
||||
{
|
||||
var key = firstKeySelector(element);
|
||||
|
||||
// Remove the key so we only yield once
|
||||
if (keys.Remove(key))
|
||||
{
|
||||
yield return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TFirst> ExceptBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first, Func<TFirst, TKey> firstKeySelector,
|
||||
IEnumerable<TSecond> second, Func<TSecond, TKey> secondKeySelector,
|
||||
IEqualityComparer<TKey> keyComparer)
|
||||
{
|
||||
var keys = new HashSet<TKey>(second.Select(secondKeySelector), keyComparer);
|
||||
var matchedKeys = new HashSet<TKey>();
|
||||
|
||||
foreach (var element in first)
|
||||
{
|
||||
var key = firstKeySelector(element);
|
||||
|
||||
if (!keys.Contains(key) && !matchedKeys.Contains(key))
|
||||
{
|
||||
// Store the key so we only yield once
|
||||
matchedKeys.Add(key);
|
||||
yield return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddIfNotNull<TSource>(this List<TSource> source, TSource item)
|
||||
{
|
||||
if (item == null)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
@@ -37,6 +38,11 @@ namespace NzbDrone.Common.Extensions
|
||||
return info.FullName.TrimEnd('/').Trim('\\', ' ');
|
||||
}
|
||||
|
||||
public static bool PathNotEquals(this string firstPath, string secondPath, StringComparison? comparison = null)
|
||||
{
|
||||
return !PathEquals(firstPath, secondPath, comparison);
|
||||
}
|
||||
|
||||
public static bool PathEquals(this string firstPath, string secondPath, StringComparison? comparison = null)
|
||||
{
|
||||
if (!comparison.HasValue)
|
||||
@@ -169,6 +175,21 @@ namespace NzbDrone.Common.Extensions
|
||||
return Path.Combine(GetProperCapitalization(dirInfo), fileName);
|
||||
}
|
||||
|
||||
public static List<string> GetAncestorFolders(this string path)
|
||||
{
|
||||
var directory = new DirectoryInfo(path);
|
||||
var directories = new List<string>();
|
||||
|
||||
while (directory != null)
|
||||
{
|
||||
directories.Insert(0, directory.Name);
|
||||
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
return directories;
|
||||
}
|
||||
|
||||
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return appFolderInfo.AppDataFolder;
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace NzbDrone.Common.Http
|
||||
public bool UseSimplifiedUserAgent { get; set; }
|
||||
public bool AllowAutoRedirect { get; set; }
|
||||
public bool ConnectionKeepAlive { get; set; }
|
||||
public TimeSpan RateLimit { get; set; }
|
||||
public bool LogResponseContent { get; set; }
|
||||
public NetworkCredential NetworkCredential { get; set; }
|
||||
public Dictionary<string, string> Cookies { get; private set; }
|
||||
@@ -75,7 +76,7 @@ namespace NzbDrone.Common.Http
|
||||
protected virtual HttpUri CreateUri()
|
||||
{
|
||||
var url = BaseUrl.CombinePath(ResourceUrl).AddQueryParams(QueryParams.Concat(SuffixQueryParams));
|
||||
|
||||
|
||||
if (Segments.Any())
|
||||
{
|
||||
var fullUri = url.FullUri;
|
||||
@@ -103,6 +104,7 @@ namespace NzbDrone.Common.Http
|
||||
request.UseSimplifiedUserAgent = UseSimplifiedUserAgent;
|
||||
request.AllowAutoRedirect = AllowAutoRedirect;
|
||||
request.ConnectionKeepAlive = ConnectionKeepAlive;
|
||||
request.RateLimit = RateLimit;
|
||||
request.LogResponseContent = LogResponseContent;
|
||||
|
||||
if (NetworkCredential != null)
|
||||
@@ -245,6 +247,13 @@ namespace NzbDrone.Common.Http
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual HttpRequestBuilder WithRateLimit(double seconds)
|
||||
{
|
||||
RateLimit = TimeSpan.FromSeconds(seconds);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual HttpRequestBuilder Post()
|
||||
{
|
||||
Method = HttpMethod.POST;
|
||||
@@ -371,4 +380,4 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,5 +108,19 @@ namespace NzbDrone.Core.Test.Configuration
|
||||
|
||||
keys.Should().OnlyHaveUniqueItems();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_ignore_null_properties()
|
||||
{
|
||||
Mocker.GetMock<IConfigRepository>()
|
||||
.Setup(v => v.Get("downloadedepisodesfolder"))
|
||||
.Returns(new Config { Id = 1, Key = "DownloadedEpisodesFolder", Value = @"C:\test".AsOsAgnostic() });
|
||||
|
||||
var dict = new Dictionary<string, object>();
|
||||
dict.Add("DownloadedEpisodesFolder", null);
|
||||
Subject.SaveConfigDictionary(dict);
|
||||
|
||||
Mocker.GetMock<IConfigRepository>().Verify(c => c.Upsert("downloadedepisodesfolder", It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Datastore.Migration;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||
{
|
||||
[TestFixture]
|
||||
public class metadata_files_extensionFixture : MigrationTest<extra_and_subtitle_files>
|
||||
{
|
||||
[Test]
|
||||
public void should_set_extension_using_relative_path()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("MetadataFiles").Row(new
|
||||
{
|
||||
SeriesId = 1,
|
||||
RelativePath = "banner.jpg",
|
||||
LastUpdated = "2016-05-30 20:23:02.3725923",
|
||||
Type = 3,
|
||||
Consumer = "XbmcMetadata"
|
||||
});
|
||||
|
||||
c.Insert.IntoTable("MetadataFiles").Row(new
|
||||
{
|
||||
SeriesId = 1,
|
||||
SeasonNumber = 1,
|
||||
EpisodeFileId = 1,
|
||||
RelativePath = "Series.Title.S01E01.jpg",
|
||||
LastUpdated = "2016-05-30 20:23:02.3725923",
|
||||
Type = 5,
|
||||
Consumer = "XbmcMetadata"
|
||||
});
|
||||
|
||||
c.Insert.IntoTable("MetadataFiles").Row(new
|
||||
{
|
||||
SeriesId = 1,
|
||||
RelativePath = "Series Title",
|
||||
LastUpdated = "2016-05-30 20:23:02.3725923",
|
||||
Type = 3,
|
||||
Consumer = "RoksboxMetadata"
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<MetadataFile99>("SELECT * FROM MetadataFiles");
|
||||
|
||||
items.Should().HaveCount(2);
|
||||
items.First().Extension.Should().Be(".jpg");
|
||||
items.Last().Extension.Should().Be(".jpg");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Datastore.Migration;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||
{
|
||||
[TestFixture]
|
||||
public class fix_metadata_file_extensionsFixture : MigrationTest<fix_metadata_file_extensions>
|
||||
{
|
||||
[Test]
|
||||
public void should_fix_extension_when_relative_path_contained_multiple_periods()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("MetadataFiles").Row(new
|
||||
{
|
||||
SeriesId = 1,
|
||||
SeasonNumber = 1,
|
||||
EpisodeFileId = 1,
|
||||
RelativePath = "Series.Title.S01E01.jpg",
|
||||
LastUpdated = "2016-05-30 20:23:02.3725923",
|
||||
Type = 5,
|
||||
Consumer = "XbmcMetadata",
|
||||
Extension = ".S01E01.jpg"
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<MetadataFile99>("SELECT * FROM MetadataFiles");
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().Extension.Should().Be(".jpg");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
Series = new Series(),
|
||||
Episodes = new List<Episode> { new Episode { Id = 1 } }
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -77,11 +77,11 @@ namespace NzbDrone.Core.Test.Download
|
||||
.Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
|
||||
.Returns((History.History)null);
|
||||
}
|
||||
|
||||
|
||||
private void GivenSuccessfulImport()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }))
|
||||
@@ -145,7 +145,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
GivenNoGrabbedHistory();
|
||||
GivenSeriesMatch();
|
||||
GivenSuccessfulImport();
|
||||
|
||||
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
AssertCompletedDownload();
|
||||
@@ -179,13 +179,13 @@ namespace NzbDrone.Core.Test.Download
|
||||
public void should_mark_as_imported_if_all_episodes_were_imported()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})),
|
||||
|
||||
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"}))
|
||||
@@ -200,13 +200,13 @@ namespace NzbDrone.Core.Test.Download
|
||||
public void should_not_mark_as_imported_if_all_files_were_rejected()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"),
|
||||
|
||||
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure")
|
||||
@@ -224,13 +224,13 @@ namespace NzbDrone.Core.Test.Download
|
||||
public void should_not_mark_as_imported_if_no_episodes_were_parsed()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"),
|
||||
|
||||
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure")
|
||||
@@ -247,7 +247,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
public void should_not_mark_as_imported_if_all_files_were_skipped()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"),
|
||||
@@ -271,7 +271,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
};
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})),
|
||||
@@ -294,7 +294,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
};
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})),
|
||||
@@ -314,7 +314,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
GivenABadlyNamedDownload();
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}))
|
||||
@@ -323,7 +323,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
.Setup(v => v.GetSeries(It.IsAny<int>()))
|
||||
.Returns(BuildRemoteEpisode().Series);
|
||||
|
||||
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
AssertCompletedDownload();
|
||||
@@ -335,7 +335,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
GivenABadlyNamedDownload();
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}))
|
||||
@@ -370,7 +370,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
};
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}))
|
||||
@@ -408,7 +408,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
private void AssertNoAttemptedImport()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Verify(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
||||
.Verify(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
||||
|
||||
AssertNoCompletedDownload();
|
||||
}
|
||||
@@ -424,7 +424,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
private void AssertCompletedDownload()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once());
|
||||
.Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, ImportMode.Auto, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once());
|
||||
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadCompletedEvent>()), Times.Once());
|
||||
|
||||
@@ -35,6 +35,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new Byte[0]));
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(new QBittorrentPreferences());
|
||||
}
|
||||
|
||||
protected void GivenRedirectToMagnet()
|
||||
@@ -85,6 +89,17 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
});
|
||||
}
|
||||
|
||||
protected void GivenMaxRatio(float maxRatio, bool removeOnMaxRatio = true)
|
||||
{
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(new QBittorrentPreferences
|
||||
{
|
||||
RemoveOnMaxRatio = removeOnMaxRatio,
|
||||
MaxRatio = maxRatio
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void GivenTorrents(List<QBittorrentTorrent> torrents)
|
||||
{
|
||||
if (torrents == null)
|
||||
@@ -253,13 +268,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
[Test]
|
||||
public void should_return_status_with_outputdirs()
|
||||
{
|
||||
var configItems = new Dictionary<string, Object>();
|
||||
|
||||
configItems.Add("save_path", @"C:\Downloads\Finished\QBittorrent".AsOsAgnostic());
|
||||
var config = new QBittorrentPreferences
|
||||
{
|
||||
SavePath = @"C:\Downloads\Finished\QBittorrent".AsOsAgnostic()
|
||||
};
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(v => v.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(configItems);
|
||||
.Returns(config);
|
||||
|
||||
var result = Subject.GetStatus();
|
||||
|
||||
@@ -293,5 +309,97 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_read_only_if_max_ratio_not_reached()
|
||||
{
|
||||
GivenMaxRatio(1.0f);
|
||||
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = "uploading",
|
||||
Label = "",
|
||||
SavePath = "",
|
||||
Ratio = 0.5f
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.IsReadOnly.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_read_only_if_max_ratio_reached_and_not_paused()
|
||||
{
|
||||
GivenMaxRatio(1.0f);
|
||||
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = "uploading",
|
||||
Label = "",
|
||||
SavePath = "",
|
||||
Ratio = 1.0f
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.IsReadOnly.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_read_only_if_max_ratio_is_not_set()
|
||||
{
|
||||
GivenMaxRatio(1.0f, false);
|
||||
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = "uploading",
|
||||
Label = "",
|
||||
SavePath = "",
|
||||
Ratio = 1.0f
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.IsReadOnly.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_be_read_only_if_max_ratio_reached_and_paused()
|
||||
{
|
||||
GivenMaxRatio(1.0f);
|
||||
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
Hash = "HASH",
|
||||
Name = _title,
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = "pausedUP",
|
||||
Label = "",
|
||||
SavePath = "",
|
||||
Ratio = 1.0f
|
||||
};
|
||||
GivenTorrents(new List<QBittorrentTorrent> { torrent });
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.IsReadOnly.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
{
|
||||
@@ -436,5 +437,18 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
|
||||
error.IsValid.Should().Be(expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_test_develop_version_successfully()
|
||||
{
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Setup(v => v.GetVersion(It.IsAny<SabnzbdSettings>()))
|
||||
.Returns("develop");
|
||||
|
||||
var result = new NzbDroneValidationResult(Subject.Test());
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
result.HasWarnings.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
DownloadUrl = _downloadUrl,
|
||||
RootDownloadPath = "somepath"
|
||||
};
|
||||
|
||||
|
||||
Mocker.GetMock<ITorrentFileInfoReader>()
|
||||
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>()))
|
||||
.Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951");
|
||||
@@ -130,8 +130,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
PrepareClientToReturnQueuedItem();
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void GivenTorrents(List<UTorrentTorrent> torrents)
|
||||
|
||||
protected virtual void GivenTorrents(List<UTorrentTorrent> torrents, string cacheNumber = null)
|
||||
{
|
||||
if (torrents == null)
|
||||
{
|
||||
@@ -139,13 +139,30 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
}
|
||||
|
||||
Mocker.GetMock<IUTorrentProxy>()
|
||||
.Setup(s => s.GetTorrents(It.IsAny<UTorrentSettings>()))
|
||||
.Returns(torrents);
|
||||
.Setup(s => s.GetTorrents(It.IsAny<string>(), It.IsAny<UTorrentSettings>()))
|
||||
.Returns(new UTorrentResponse { Torrents = torrents, CacheNumber = cacheNumber });
|
||||
}
|
||||
|
||||
protected virtual void GivenDifferentialTorrents(string oldCacheNumber, List<UTorrentTorrent> changed, List<string> removed, string cacheNumber)
|
||||
{
|
||||
if (changed == null)
|
||||
{
|
||||
changed = new List<UTorrentTorrent>();
|
||||
}
|
||||
|
||||
if (removed == null)
|
||||
{
|
||||
removed = new List<string>();
|
||||
}
|
||||
|
||||
Mocker.GetMock<IUTorrentProxy>()
|
||||
.Setup(s => s.GetTorrents(oldCacheNumber, It.IsAny<UTorrentSettings>()))
|
||||
.Returns(new UTorrentResponse { TorrentsChanged = changed, TorrentsRemoved = removed, CacheNumber = cacheNumber });
|
||||
}
|
||||
|
||||
protected void PrepareClientToReturnQueuedItem()
|
||||
{
|
||||
GivenTorrents(new List<UTorrentTorrent>
|
||||
GivenTorrents(new List<UTorrentTorrent>
|
||||
{
|
||||
_queued
|
||||
});
|
||||
@@ -153,7 +170,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
|
||||
protected void PrepareClientToReturnDownloadingItem()
|
||||
{
|
||||
GivenTorrents(new List<UTorrentTorrent>
|
||||
GivenTorrents(new List<UTorrentTorrent>
|
||||
{
|
||||
_downloading
|
||||
});
|
||||
@@ -161,7 +178,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
|
||||
protected void PrepareClientToReturnFailedItem()
|
||||
{
|
||||
GivenTorrents(new List<UTorrentTorrent>
|
||||
GivenTorrents(new List<UTorrentTorrent>
|
||||
{
|
||||
_failed
|
||||
});
|
||||
@@ -296,13 +313,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
public void should_return_status_with_outputdirs()
|
||||
{
|
||||
var configItems = new Dictionary<string, string>();
|
||||
|
||||
|
||||
configItems.Add("dir_active_download_flag", "true");
|
||||
configItems.Add("dir_active_download", @"C:\Downloads\Downloading\utorrent".AsOsAgnostic());
|
||||
configItems.Add("dir_completed_download", @"C:\Downloads\Finished\utorrent".AsOsAgnostic());
|
||||
configItems.Add("dir_completed_download_flag", "true");
|
||||
configItems.Add("dir_add_label", "true");
|
||||
|
||||
|
||||
Mocker.GetMock<IUTorrentProxy>()
|
||||
.Setup(v => v.GetConfig(It.IsAny<UTorrentSettings>()))
|
||||
.Returns(configItems);
|
||||
@@ -353,5 +370,29 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_query_with_cache_id_if_available()
|
||||
{
|
||||
_downloading.Status = UTorrentTorrentStatus.Started;
|
||||
|
||||
GivenTorrents(new List<UTorrentTorrent> { _downloading }, "abc");
|
||||
|
||||
var item1 = Subject.GetItems().Single();
|
||||
|
||||
Mocker.GetMock<IUTorrentProxy>().Verify(v => v.GetTorrents(null, It.IsAny<UTorrentSettings>()), Times.Once());
|
||||
|
||||
GivenTorrents(new List<UTorrentTorrent> { _downloading, _queued }, "abc2");
|
||||
GivenDifferentialTorrents("abc", new List<UTorrentTorrent> { _queued }, new List<string>(), "abc2");
|
||||
GivenDifferentialTorrents("abc2", new List<UTorrentTorrent>(), new List<string>(), "abc2");
|
||||
|
||||
var item2 = Subject.GetItems().Single();
|
||||
|
||||
Mocker.GetMock<IUTorrentProxy>().Verify(v => v.GetTorrents("abc", It.IsAny<UTorrentSettings>()), Times.Once());
|
||||
|
||||
var item3 = Subject.GetItems().Single();
|
||||
|
||||
Mocker.GetMock<IUTorrentProxy>().Verify(v => v.GetTorrents("abc2", It.IsAny<UTorrentSettings>()), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Extras.Metadata;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.Metadata;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
@@ -19,7 +21,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
[TestFixture]
|
||||
public class DeleteBadMediaCoversFixture : CoreTest<DeleteBadMediaCovers>
|
||||
{
|
||||
private List<MetadataFile> _metaData;
|
||||
private List<MetadataFile> _metadata;
|
||||
private List<Series> _series;
|
||||
|
||||
[SetUp]
|
||||
@@ -31,7 +33,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Build().ToList();
|
||||
|
||||
|
||||
_metaData = Builder<MetadataFile>.CreateListOfSize(1)
|
||||
_metadata = Builder<MetadataFile>.CreateListOfSize(1)
|
||||
.Build().ToList();
|
||||
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
@@ -41,7 +43,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
|
||||
Mocker.GetMock<IMetadataFileService>()
|
||||
.Setup(c => c.GetFilesBySeries(_series.First().Id))
|
||||
.Returns(_metaData);
|
||||
.Returns(_metadata);
|
||||
|
||||
|
||||
Mocker.GetMock<IConfigService>().SetupGet(c => c.CleanupMetadataImages).Returns(true);
|
||||
@@ -51,8 +53,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
[Test]
|
||||
public void should_not_process_non_image_files()
|
||||
{
|
||||
_metaData.First().RelativePath = "season\\file.xml".AsOsAgnostic();
|
||||
_metaData.First().Type = MetadataType.EpisodeMetadata;
|
||||
_metadata.First().RelativePath = "season\\file.xml".AsOsAgnostic();
|
||||
_metadata.First().Type = MetadataType.EpisodeMetadata;
|
||||
|
||||
Subject.Clean();
|
||||
|
||||
@@ -63,7 +65,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
[Test]
|
||||
public void should_not_process_images_before_tvdb_switch()
|
||||
{
|
||||
_metaData.First().LastUpdated = new DateTime(2014, 12, 25);
|
||||
_metadata.First().LastUpdated = new DateTime(2014, 12, 25);
|
||||
|
||||
Subject.Clean();
|
||||
|
||||
@@ -89,7 +91,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
[Test]
|
||||
public void should_set_clean_flag_to_false()
|
||||
{
|
||||
_metaData.First().LastUpdated = new DateTime(2014, 12, 25);
|
||||
_metadata.First().LastUpdated = new DateTime(2014, 12, 25);
|
||||
|
||||
Subject.Clean();
|
||||
|
||||
@@ -102,9 +104,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
|
||||
var imagePath = "C:\\TV\\Season\\image.jpg".AsOsAgnostic();
|
||||
_metaData.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||
_metaData.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||
_metaData.First().Type = MetadataType.SeriesImage;
|
||||
_metadata.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||
_metadata.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||
_metadata.First().Type = MetadataType.SeriesImage;
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.OpenReadStream(imagePath))
|
||||
@@ -115,7 +117,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.DeleteFile(imagePath), Times.Once());
|
||||
Mocker.GetMock<IMetadataFileService>().Verify(c => c.Delete(_metaData.First().Id), Times.Once());
|
||||
Mocker.GetMock<IMetadataFileService>().Verify(c => c.Delete(_metadata.First().Id), Times.Once());
|
||||
}
|
||||
|
||||
|
||||
@@ -124,9 +126,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
|
||||
var imagePath = "C:\\TV\\Season\\image.jpg".AsOsAgnostic();
|
||||
_metaData.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||
_metaData.First().Type = MetadataType.SeasonImage;
|
||||
_metaData.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||
_metadata.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||
_metadata.First().Type = MetadataType.SeasonImage;
|
||||
_metadata.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.OpenReadStream(imagePath))
|
||||
@@ -136,7 +138,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
Subject.Clean();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.DeleteFile(imagePath), Times.Once());
|
||||
Mocker.GetMock<IMetadataFileService>().Verify(c => c.Delete(_metaData.First().Id), Times.Once());
|
||||
Mocker.GetMock<IMetadataFileService>().Verify(c => c.Delete(_metadata.First().Id), Times.Once());
|
||||
}
|
||||
|
||||
|
||||
@@ -145,8 +147,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
|
||||
var imagePath = "C:\\TV\\Season\\image.jpg".AsOsAgnostic();
|
||||
_metaData.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||
_metaData.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||
_metadata.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||
_metadata.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.OpenReadStream(imagePath))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -22,76 +21,41 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(string.Format("{0} (tarball Wed Sep 25 16:35:44 CDT 2013)", version));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_when_mono_3_0()
|
||||
[TestCase("3.10")]
|
||||
[TestCase("4.0.0.0")]
|
||||
[TestCase("4.2")]
|
||||
[TestCase("4.6")]
|
||||
[TestCase("4.4.2")]
|
||||
public void should_return_ok(string version)
|
||||
{
|
||||
GivenOutput("3.0.0.1");
|
||||
GivenOutput(version);
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[TestCase("2.10.2")]
|
||||
[TestCase("2.10.8.1")]
|
||||
[TestCase("3.0.0.1")]
|
||||
[TestCase("3.2.0.1")]
|
||||
[TestCase("3.2.1")]
|
||||
[TestCase("3.2.7")]
|
||||
[TestCase("3.6.1")]
|
||||
[TestCase("3.8")]
|
||||
public void should_return_warning(string version)
|
||||
{
|
||||
GivenOutput(version);
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_when_mono_2_10_8()
|
||||
|
||||
[TestCase("4.4.0")]
|
||||
[TestCase("4.4.1")]
|
||||
public void should_return_error(string version)
|
||||
{
|
||||
GivenOutput("2.10.8.1");
|
||||
GivenOutput(version);
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_when_mono_2_10_2()
|
||||
{
|
||||
GivenOutput("2.10.2");
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_when_mono_3_2()
|
||||
{
|
||||
GivenOutput("3.2.0.1");
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_when_mono_4_0()
|
||||
{
|
||||
GivenOutput("4.0.0.0");
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_when_mono_3_2_7()
|
||||
{
|
||||
GivenOutput("3.2.7");
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_when_mono_3_2_1()
|
||||
{
|
||||
GivenOutput("3.2.1");
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_when_mono_3_6_1()
|
||||
{
|
||||
GivenOutput("3.6.1");
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_when_mono_3_10()
|
||||
{
|
||||
GivenOutput("3.10");
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
Subject.Check().ShouldBeError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace NzbDrone.Core.Test.HistoryTests
|
||||
Path = @"C:\Test\Unsorted\Series.s01e01.mkv"
|
||||
};
|
||||
|
||||
Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, true, "sab","abcd"));
|
||||
Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, true, "sab", "abcd", true));
|
||||
|
||||
Mocker.GetMock<IHistoryRepository>()
|
||||
.Verify(v => v.Insert(It.Is<History.History>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Extras.Metadata;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.Metadata;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
public void should_not_delete_metadata_files_when_there_is_only_one_for_that_series_and_consumer()
|
||||
{
|
||||
var file = Builder<MetadataFile>.CreateNew()
|
||||
.BuildNew();
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(file);
|
||||
Subject.Clean();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Extras.Metadata;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
@@ -94,10 +94,10 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
Db.Insert(series);
|
||||
|
||||
var metadataFile = Builder<MetadataFile>.CreateNew()
|
||||
.With(m => m.SeriesId = series.Id)
|
||||
.With(m => m.Type = MetadataType.EpisodeMetadata)
|
||||
.With(m => m.EpisodeFileId = 0)
|
||||
.BuildNew();
|
||||
.With(m => m.SeriesId = series.Id)
|
||||
.With(m => m.Type = MetadataType.EpisodeMetadata)
|
||||
.With(m => m.EpisodeFileId = 0)
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(metadataFile);
|
||||
Subject.Clean();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Tags;
|
||||
using NzbDrone.Core.Restrictions;
|
||||
|
||||
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
{
|
||||
[TestFixture]
|
||||
public class CleanupUnusedTagsFixture : DbTest<CleanupUnusedTags, Tag>
|
||||
{
|
||||
[Test]
|
||||
public void should_delete_unused_tags()
|
||||
{
|
||||
var tags = Builder<Tag>.CreateListOfSize(2).BuildList();
|
||||
|
||||
Db.InsertMany(tags);
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_delete_used_tags()
|
||||
{
|
||||
var tags = Builder<Tag>.CreateListOfSize(2).BuildList();
|
||||
Db.InsertMany(tags);
|
||||
|
||||
var restrictions = Builder<Restriction>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(v => v.Tags.Add(tags[0].Id))
|
||||
.BuildList();
|
||||
Db.InsertMany(restrictions);
|
||||
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().HaveCount(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Returns(new List<ImportResult>());
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>());
|
||||
|
||||
var downloadItem = Builder<DownloadClientItem>.CreateNew()
|
||||
@@ -113,7 +113,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder });
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), null, null), Times.Once());
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), ImportMode.Auto, null, null), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -123,7 +123,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFile });
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), null, null), Times.Once());
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), ImportMode.Auto, null, null), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" });
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(_downloadFolder, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once());
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(_downloadFolder, ImportMode.Auto, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -144,7 +144,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" });
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(_downloadFolder, null, null), Times.Once());
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(_downloadFolder, ImportMode.Auto, null, null), Times.Once());
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
@@ -154,9 +154,19 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
{
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder });
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), null, null), Times.Never());
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), ImportMode.Auto, null, null), Times.Never());
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_override_import_mode()
|
||||
{
|
||||
GivenExistingFile(_downloadFile);
|
||||
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFile, ImportMode = ImportMode.Copy });
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), ImportMode.Copy, null, null), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Returns(new List<ImportResult>());
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Returns(true);
|
||||
|
||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
||||
|
||||
|
||||
VerifyNoImport();
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
public void should_not_delete_folder_if_no_files_were_imported()
|
||||
{
|
||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), false, null))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), false, null, ImportMode.Auto))
|
||||
.Returns(new List<ImportResult>());
|
||||
|
||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
||||
@@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
||||
|
||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
||||
@@ -159,7 +159,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
||||
|
||||
Mocker.GetMock<IDetectSample>()
|
||||
@@ -231,7 +231,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
||||
|
||||
Mocker.GetMock<IDetectSample>()
|
||||
@@ -342,7 +342,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Returns(new List<ImportResult>());
|
||||
|
||||
Mocker.GetMock<IDetectSample>()
|
||||
@@ -365,14 +365,14 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
private void VerifyNoImport()
|
||||
{
|
||||
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null),
|
||||
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
private void VerifyImport()
|
||||
{
|
||||
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null),
|
||||
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),
|
||||
Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
}
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.Setup(s => s.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), It.IsAny<LocalEpisode>(), false))
|
||||
.Setup(s => s.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), It.IsAny<LocalEpisode>(), It.IsAny<bool>()))
|
||||
.Returns(new EpisodeFileMoveResult());
|
||||
|
||||
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||
@@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
all.AddRange(_approvedDecisions);
|
||||
|
||||
var result = Subject.Import(all, false);
|
||||
|
||||
|
||||
result.Should().HaveCount(all.Count);
|
||||
result.Where(i => i.Result == ImportResultType.Imported).Should().HaveCount(_approvedDecisions.Count);
|
||||
}
|
||||
@@ -223,5 +223,23 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
results.Should().ContainSingle(d => d.Result == ImportResultType.Imported);
|
||||
results.Should().ContainSingle(d => d.Result == ImportResultType.Imported && d.ImportDecision.LocalEpisode.Size == fileDecision.LocalEpisode.Size);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_copy_readonly_downloads()
|
||||
{
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", IsReadOnly = true });
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.Verify(v => v.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), _approvedDecisions.First().LocalEpisode, true), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_override_importmode()
|
||||
{
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", IsReadOnly = true }, ImportMode.Move);
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.Verify(v => v.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), _approvedDecisions.First().LocalEpisode, false), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Metadata;
|
||||
using NzbDrone.Core.Metadata.Consumers.Roksbox;
|
||||
using NzbDrone.Core.Extras.Metadata;
|
||||
using NzbDrone.Core.Extras.Metadata.Consumers.Roksbox;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Metadata;
|
||||
using NzbDrone.Core.Metadata.Consumers.Wdtv;
|
||||
using NzbDrone.Core.Extras.Metadata;
|
||||
using NzbDrone.Core.Extras.Metadata.Consumers.Wdtv;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
@@ -119,6 +119,8 @@
|
||||
<Compile Include="Datastore\DatabaseRelationshipFixture.cs" />
|
||||
<Compile Include="Datastore\MappingExtentionFixture.cs" />
|
||||
<Compile Include="Datastore\MarrDataLazyLoadingFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\103_fix_metadata_file_extensionsFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\099_extra_and_subtitle_filesFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\101_add_ultrahd_quality_in_profilesFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\071_unknown_quality_in_profileFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\072_history_downloadIdFixture.cs" />
|
||||
@@ -222,6 +224,7 @@
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedIndexerStatusFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupUnusedTagsFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleasesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasksFixture.cs" />
|
||||
<Compile Include="Http\HttpProxySettingsProviderFixture.cs" />
|
||||
|
||||
@@ -119,6 +119,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("[RlsGrp] Series Title (2010) - S01E01-02-03 - 001-002-003 - Episode Title HDTV-720p v2", "Series Title (2010)", new[] { 1, 2, 3 })]
|
||||
[TestCase("[RlsGrp] Series Title (2010) - S01E01-02 - 001-002 - Episode Title HDTV-720p v2", "Series Title (2010)", new[] { 1, 2 })]
|
||||
[TestCase("Series Title (2010) - S01E01-02 (001-002) - Episode Title (1) HDTV-720p v2 [RlsGrp]", "Series Title (2010)", new[] { 1, 2 })]
|
||||
[TestCase("[HorribleSubs] Haikyuu!! (01-25) [1080p] (Batch)", "Haikyuu!!", new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 })]
|
||||
public void should_parse_multi_episode_absolute_numbers(string postTitle, string title, int[] absoluteEpisodeNumbers)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
|
||||
@@ -48,8 +48,17 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Castle.2009.S01E14.HDTV.XviD.HUN-LOL", Language.Hungarian)]
|
||||
public void should_parse_language(string postTitle, Language language)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
result.Language.Should().Be(language);
|
||||
var result = LanguageParser.ParseLanguage(postTitle);
|
||||
result.Should().Be(language);
|
||||
}
|
||||
|
||||
[TestCase("2 Broke Girls - S01E01 - Pilot.en.sub", Language.English)]
|
||||
[TestCase("2 Broke Girls - S01E01 - Pilot.eng.sub", Language.English)]
|
||||
[TestCase("2 Broke Girls - S01E01 - Pilot.sub", Language.Unknown)]
|
||||
public void should_parse_subtitle_language(string fileName, Language language)
|
||||
{
|
||||
var result = LanguageParser.ParseSubtitleLanguage(fileName);
|
||||
result.Should().Be(language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Series Title S06E08 1080p WEB h264-EXCLUSIVE", false)]
|
||||
[TestCase("Series Title S06E08 No One PROPER 1080p WEB DD5 1 H 264-EXCLUSIVE", true)]
|
||||
[TestCase("Series Title S06E08 No One PROPER 1080p WEB H 264-EXCLUSIVE", true)]
|
||||
[TestCase("The.Simpsons.S25E21.Pay.Pal.1080p.WEB-DL.DD5.1.H.264-NTb", false)]
|
||||
public void should_parse_webdl1080p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Quality.WEBDL1080p, proper);
|
||||
|
||||
@@ -123,6 +123,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Red Dwarf - S02 - E06 - Parallel Universe", "Red Dwarf", 2, 6)]
|
||||
[TestCase("O.J.Simpson.Made.in.America.Part.Two.720p.HDTV.x264-2HD", "O J Simpson Made in America", 1, 2)]
|
||||
[TestCase("The.100000.Dollar.Pyramid.2016.S01E05.720p.HDTV.x264-W4F", "The 100000 Dollar Pyramid 2016", 1, 5)]
|
||||
[TestCase("Class S01E02 (22 October 2016) HDTV 720p [Webrip]", "Class", 1, 2)]
|
||||
//[TestCase("", "", 0, 0)]
|
||||
public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
object currentValue;
|
||||
allWithDefaults.TryGetValue(configValue.Key, out currentValue);
|
||||
if (currentValue == null) continue;
|
||||
if (currentValue == null || configValue.Value == null) continue;
|
||||
|
||||
var equal = configValue.Value.ToString().Equals(currentValue.ToString());
|
||||
|
||||
@@ -202,6 +202,13 @@ namespace NzbDrone.Core.Configuration
|
||||
set { SetValue("EnableMediaInfo", value); }
|
||||
}
|
||||
|
||||
public string ExtraFileExtensions
|
||||
{
|
||||
get { return GetValue("ExtraFileExtensions", ""); }
|
||||
|
||||
set { SetValue("ExtraFileExtensions", value); }
|
||||
}
|
||||
|
||||
public bool SetPermissionsLinux
|
||||
{
|
||||
get { return GetValueBoolean("SetPermissionsLinux", false); }
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace NzbDrone.Core.Configuration
|
||||
bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
||||
bool CopyUsingHardlinks { get; set; }
|
||||
bool EnableMediaInfo { get; set; }
|
||||
string ExtraFileExtensions { get; set; }
|
||||
|
||||
//Permissions (Media Management)
|
||||
bool SetPermissionsLinux { get; set; }
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(99)]
|
||||
public class extra_and_subtitle_files : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Create.TableForModel("ExtraFiles")
|
||||
.WithColumn("SeriesId").AsInt32().NotNullable()
|
||||
.WithColumn("SeasonNumber").AsInt32().NotNullable()
|
||||
.WithColumn("EpisodeFileId").AsInt32().NotNullable()
|
||||
.WithColumn("RelativePath").AsString().NotNullable()
|
||||
.WithColumn("Extension").AsString().NotNullable()
|
||||
.WithColumn("Added").AsDateTime().NotNullable()
|
||||
.WithColumn("LastUpdated").AsDateTime().NotNullable();
|
||||
|
||||
Create.TableForModel("SubtitleFiles")
|
||||
.WithColumn("SeriesId").AsInt32().NotNullable()
|
||||
.WithColumn("SeasonNumber").AsInt32().NotNullable()
|
||||
.WithColumn("EpisodeFileId").AsInt32().NotNullable()
|
||||
.WithColumn("RelativePath").AsString().NotNullable()
|
||||
.WithColumn("Extension").AsString().NotNullable()
|
||||
.WithColumn("Added").AsDateTime().NotNullable()
|
||||
.WithColumn("LastUpdated").AsDateTime().NotNullable()
|
||||
.WithColumn("Language").AsInt32().NotNullable();
|
||||
|
||||
Alter.Table("MetadataFiles")
|
||||
.AddColumn("Added").AsDateTime().Nullable()
|
||||
.AddColumn("Extension").AsString().Nullable();
|
||||
|
||||
// Remove Metadata files that don't have an extension
|
||||
Execute.Sql("DELETE FROM MetadataFiles WHERE RelativePath NOT LIKE '%.%'");
|
||||
|
||||
// Set Extension using the extension from RelativePath
|
||||
Execute.WithConnection(SetMetadataFileExtension);
|
||||
|
||||
Alter.Table("MetadataFiles").AlterColumn("Extension").AsString().NotNullable();
|
||||
}
|
||||
|
||||
private void SetMetadataFileExtension(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT Id, RelativePath FROM MetadataFiles";
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var relativePath = reader.GetString(1);
|
||||
var extension = relativePath.Substring(relativePath.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
using (var updateCmd = conn.CreateCommand())
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
updateCmd.CommandText = "UPDATE MetadataFiles SET Extension = ? WHERE Id = ?";
|
||||
updateCmd.AddParameter(extension);
|
||||
updateCmd.AddParameter(id);
|
||||
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MetadataFile99
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
public int? EpisodeFileId { get; set; }
|
||||
public int? SeasonNumber { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
public DateTime LastUpdated { get; set; }
|
||||
public string Extension { get; set; }
|
||||
public string Hash { get; set; }
|
||||
public string Consumer { get; set; }
|
||||
public int Type { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(103)]
|
||||
public class fix_metadata_file_extensions : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(SetMetadataFileExtension);
|
||||
}
|
||||
|
||||
private void SetMetadataFileExtension(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT Id, Extension FROM MetadataFiles";
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var extension = reader.GetString(1);
|
||||
extension = extension.Substring(extension.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
using (var updateCmd = conn.CreateCommand())
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
updateCmd.CommandText = "UPDATE MetadataFiles SET Extension = ? WHERE Id = ?";
|
||||
updateCmd.AddParameter(extension);
|
||||
updateCmd.AddParameter(id);
|
||||
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,6 @@ using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Instrumentation;
|
||||
using NzbDrone.Core.Jobs;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Profiles.Delay;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.Notifications;
|
||||
@@ -31,6 +29,10 @@ using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Extras.Metadata;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.Extras.Others;
|
||||
using NzbDrone.Core.Extras.Subtitles;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
@@ -92,13 +94,14 @@ namespace NzbDrone.Core.Datastore
|
||||
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions")
|
||||
.Ignore(d => d.Weight);
|
||||
|
||||
|
||||
Mapper.Entity<Profile>().RegisterModel("Profiles");
|
||||
Mapper.Entity<Log>().RegisterModel("Logs");
|
||||
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
||||
Mapper.Entity<SeasonStatistics>().MapResultSet();
|
||||
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
||||
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
|
||||
Mapper.Entity<SubtitleFile>().RegisterModel("SubtitleFiles");
|
||||
Mapper.Entity<OtherExtraFile>().RegisterModel("ExtraFiles");
|
||||
|
||||
Mapper.Entity<PendingRelease>().RegisterModel("PendingReleases")
|
||||
.Ignore(e => e.RemoteEpisode);
|
||||
|
||||
@@ -53,8 +53,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
//If the parsed size is smaller than minSize we don't want it
|
||||
if (subject.Release.Size < minSize)
|
||||
{
|
||||
_logger.Debug("Item: {0}, Size: {1:0n} is smaller than minimum allowed size ({2:0}), rejecting.", subject, subject.Release.Size, minSize);
|
||||
return Decision.Reject("{0} is smaller than minimum allowed: {1}", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix());
|
||||
var runtimeMessage = subject.Episodes.Count == 1 ? $"{subject.Series.Runtime}min" : $"{subject.Episodes.Count}x {subject.Series.Runtime}min";
|
||||
|
||||
_logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2} bytes for {3}), rejecting.", subject, subject.Release.Size, minSize, runtimeMessage);
|
||||
return Decision.Reject("{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage);
|
||||
}
|
||||
}
|
||||
if (!qualityDefinition.MaxSize.HasValue || qualityDefinition.MaxSize.Value == 0)
|
||||
@@ -95,8 +97,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
//If the parsed size is greater than maxSize we don't want it
|
||||
if (subject.Release.Size > maxSize)
|
||||
{
|
||||
_logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2}), rejecting.", subject, subject.Release.Size, maxSize);
|
||||
return Decision.Reject("{0} is larger than maximum allowed: {1}", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix());
|
||||
var runtimeMessage = subject.Episodes.Count == 1 ? $"{subject.Series.Runtime}min" : $"{subject.Episodes.Count}x {subject.Series.Runtime}min";
|
||||
|
||||
_logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2} for {3}), rejecting.", subject, subject.Release.Size, maxSize, runtimeMessage);
|
||||
return Decision.Reject("{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), runtimeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
{
|
||||
failures.AddIfNotNull(TestConnection());
|
||||
failures.AddIfNotNull(TestCategory());
|
||||
failures.AddIfNotNull(TestSettings());
|
||||
}
|
||||
|
||||
private ValidationFailure TestConnection()
|
||||
@@ -315,7 +316,24 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
return null;
|
||||
}
|
||||
|
||||
// Javascript doesn't support 64 bit integers natively so json officially doesn't either.
|
||||
private ValidationFailure TestSettings()
|
||||
{
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
|
||||
var keepHistory = config.GetValueOrDefault("KeepHistory");
|
||||
if (keepHistory == "0")
|
||||
{
|
||||
return new NzbDroneValidationFailure(string.Empty, "NzbGet setting KeepHistory should be greater than 0")
|
||||
{
|
||||
InfoLink = string.Format("http://{0}:{1}/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "NzbGet setting KeepHistory is set to 0. Which prevents Sonarr from seeing completed downloads."
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Javascript doesn't support 64 bit integers natively so json officially doesn't either.
|
||||
// NzbGet api thus sends it in two 32 bit chunks. Here we join the two chunks back together.
|
||||
// Simplified decimal example: "42" splits into "4" and "2". To join them I shift (<<) the "4" 1 digit to the left = "40". combine it with "2". which becomes "42" again.
|
||||
private long MakeInt64(uint high, uint low)
|
||||
@@ -327,4 +345,4 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
queueItem.RemainingTime = null;
|
||||
}
|
||||
else if (sabQueueItem.Status == SabnzbdDownloadStatus.Queued || sabQueueItem.Status == SabnzbdDownloadStatus.Grabbing)
|
||||
else if (sabQueueItem.Status == SabnzbdDownloadStatus.Queued ||
|
||||
sabQueueItem.Status == SabnzbdDownloadStatus.Grabbing ||
|
||||
sabQueueItem.Status == SabnzbdDownloadStatus.Propagating)
|
||||
{
|
||||
queueItem.Status = DownloadItemStatus.Queued;
|
||||
}
|
||||
@@ -280,15 +282,31 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
var version = _proxy.GetVersion(Settings);
|
||||
var parsed = VersionRegex.Match(version);
|
||||
|
||||
int actualMajor;
|
||||
int actualMinor;
|
||||
int actualPatch;
|
||||
string actualCandidate;
|
||||
|
||||
if (!parsed.Success)
|
||||
{
|
||||
return false;
|
||||
if (!version.Equals("develop", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
actualMajor = 1;
|
||||
actualMinor = 1;
|
||||
actualPatch = 0;
|
||||
actualCandidate = null;
|
||||
}
|
||||
|
||||
var actualMajor = Convert.ToInt32(parsed.Groups["major"].Value);
|
||||
var actualMinor = Convert.ToInt32(parsed.Groups["minor"].Value);
|
||||
var actualPatch = Convert.ToInt32(parsed.Groups["patch"].Value.Replace("x", ""));
|
||||
var actualCandidate = parsed.Groups["candidate"].Value.ToUpper();
|
||||
else
|
||||
{
|
||||
actualMajor = Convert.ToInt32(parsed.Groups["major"].Value);
|
||||
actualMinor = Convert.ToInt32(parsed.Groups["minor"].Value);
|
||||
actualPatch = Convert.ToInt32(parsed.Groups["patch"].Value.Replace("x", ""));
|
||||
actualCandidate = parsed.Groups["candidate"].Value.ToUpper();
|
||||
}
|
||||
|
||||
if (actualMajor > major)
|
||||
{
|
||||
@@ -340,6 +358,15 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
if (!parsed.Success)
|
||||
{
|
||||
if (version.Equals("develop", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return new NzbDroneValidationFailure("Version", "Sabnzbd develop version, assuming version 1.1.0 or higher.")
|
||||
{
|
||||
IsWarning = true,
|
||||
DetailedDescription = "Sonarr may not be able to support new features added to SABnzbd when running develop versions."
|
||||
};
|
||||
}
|
||||
|
||||
return new ValidationFailure("Version", "Unknown Version: " + version);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
Paused,
|
||||
Checking,
|
||||
Downloading,
|
||||
ToPP,
|
||||
ToPP, // TODO: Remove in v3
|
||||
QuickCheck,
|
||||
Verifying,
|
||||
Repairing,
|
||||
@@ -17,6 +17,7 @@
|
||||
Running, // Running PP Script
|
||||
Completed,
|
||||
Failed,
|
||||
Deleted
|
||||
Deleted,
|
||||
Propagating
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using FluentValidation.Results;
|
||||
using System.Net;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
@@ -80,20 +79,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
}
|
||||
|
||||
public override ProviderMessage Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return new ProviderMessage("Sonarr is unable to remove torrents that have finished seeding when using qBittorrent", ProviderMessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
QBittorrentPreferences config;
|
||||
List<QBittorrentTorrent> torrents;
|
||||
|
||||
try
|
||||
{
|
||||
config = _proxy.GetConfig(Settings);
|
||||
torrents = _proxy.GetTorrents(Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
@@ -116,11 +109,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
item.RemainingTime = TimeSpan.FromSeconds(torrent.Eta);
|
||||
|
||||
item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.SavePath));
|
||||
|
||||
// At the moment there isn't an easy way to detect if the torrent has
|
||||
// reached the seeding limit, We would need to check the preferences
|
||||
// and still not be completely sure if that torrent has a limit set for it
|
||||
item.IsReadOnly = true;
|
||||
|
||||
// Avoid removing torrents that haven't reached the global max ratio.
|
||||
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
|
||||
item.IsReadOnly = (config.MaxRatioEnabled && config.MaxRatio > torrent.Ratio) || torrent.State != "pausedUP";
|
||||
|
||||
if (!item.OutputPath.IsEmpty && item.OutputPath.FileName != torrent.Name)
|
||||
{
|
||||
@@ -178,7 +170,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
|
||||
var destDir = new OsPath((string)config.GetValueOrDefault("save_path"));
|
||||
var destDir = new OsPath(config.SavePath);
|
||||
|
||||
return new DownloadClientStatus
|
||||
{
|
||||
@@ -227,6 +219,16 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
DetailedDescription = "Sonarr will not attempt to import completed downloads without a category."
|
||||
};
|
||||
}
|
||||
|
||||
// Complain if qBittorrent is configured to remove torrents on max ratio
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
if (config.MaxRatioEnabled && config.RemoveOnMaxRatio)
|
||||
{
|
||||
return new NzbDroneValidationFailure(String.Empty, "QBittorrent is configured to remove torrents when they reach their Share Ratio Limit")
|
||||
{
|
||||
DetailedDescription = "Sonarr will be unable to perform Completed Download Handling as configured. You can fix this in qBittorrent ('Tools -> Options...' in the menu) by changing 'Options -> BitTorrent -> Share Ratio Limiting' from 'Remove them' to 'Pause them'."
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
// qbittorrent settings from the list returned by /query/preferences
|
||||
public class QBittorrentPreferences
|
||||
{
|
||||
[JsonProperty(PropertyName = "save_path")]
|
||||
public string SavePath { get; set; } // Default save path for torrents, separated by slashes
|
||||
|
||||
[JsonProperty(PropertyName = "max_ratio_enabled")]
|
||||
public bool MaxRatioEnabled { get; set; } // True if share ratio limit is enabled
|
||||
|
||||
[JsonProperty(PropertyName = "max_ratio")]
|
||||
public float MaxRatio { get; set; } // Get the global share ratio limit
|
||||
|
||||
[JsonProperty(PropertyName = "max_ratio_act")]
|
||||
public bool RemoveOnMaxRatio { get; set; } // Action performed when a torrent reaches the maximum share ratio. [false = pause, true = remove]
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
public interface IQBittorrentProxy
|
||||
{
|
||||
int GetVersion(QBittorrentSettings settings);
|
||||
Dictionary<string, Object> GetConfig(QBittorrentSettings settings);
|
||||
QBittorrentPreferences GetConfig(QBittorrentSettings settings);
|
||||
List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings);
|
||||
|
||||
void AddTorrentFromUrl(string torrentUrl, QBittorrentSettings settings);
|
||||
@@ -47,10 +47,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return response;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetConfig(QBittorrentSettings settings)
|
||||
public QBittorrentPreferences GetConfig(QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/query/preferences");
|
||||
var response = ProcessRequest<Dictionary<string, object>>(request, settings);
|
||||
var response = ProcessRequest<QBittorrentPreferences>(request, settings);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -58,7 +58,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
public List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/query/torrents")
|
||||
.AddQueryParam("label", settings.TvCategory);
|
||||
.AddQueryParam("label", settings.TvCategory)
|
||||
.AddQueryParam("category", settings.TvCategory);
|
||||
|
||||
var response = ProcessRequest<List<QBittorrentTorrent>>(request, settings);
|
||||
|
||||
@@ -99,7 +100,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
.AddFormParameter("hashes", hash)
|
||||
.AddFormParameter("category", label);
|
||||
try
|
||||
{
|
||||
{
|
||||
ProcessRequest<object>(setCategoryRequest, settings);
|
||||
}
|
||||
catch(DownloadClientException ex)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
public double Progress { get; set; } // Torrent progress (%/100)
|
||||
|
||||
public int Eta { get; set; } // Torrent ETA (seconds)
|
||||
public ulong Eta { get; set; } // Torrent ETA (seconds)
|
||||
|
||||
public string State { get; set; } // Torrent state. See possible values here below
|
||||
|
||||
@@ -21,5 +21,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
[JsonProperty(PropertyName = "save_path")]
|
||||
public string SavePath { get; set; } // Torrent save path
|
||||
|
||||
public float Ratio { get; set; } // Torrent share ratio
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,17 @@ using FluentValidation.Results;
|
||||
using System.Net;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Common.Cache;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
{
|
||||
public class UTorrent : TorrentClientBase<UTorrentSettings>
|
||||
{
|
||||
private readonly IUTorrentProxy _proxy;
|
||||
private readonly ICached<UTorrentTorrentCache> _torrentCache;
|
||||
|
||||
public UTorrent(IUTorrentProxy proxy,
|
||||
ICacheManager cacheManager,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
@@ -29,6 +32,8 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
|
||||
_torrentCache = cacheManager.GetCache<UTorrentTorrentCache>(GetType(), "differentialTorrents");
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||
@@ -72,12 +77,37 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
{
|
||||
List<UTorrentTorrent> torrents;
|
||||
|
||||
try
|
||||
{
|
||||
torrents = _proxy.GetTorrents(Settings);
|
||||
var cacheKey = string.Format("{0}:{1}:{2}", Settings.Host, Settings.Port, Settings.TvCategory);
|
||||
var cache = _torrentCache.Find(cacheKey);
|
||||
|
||||
var response = _proxy.GetTorrents(cache == null ? null : cache.CacheID, Settings);
|
||||
|
||||
if (cache != null && response.Torrents == null)
|
||||
{
|
||||
var removedAndUpdated = new HashSet<string>(response.TorrentsChanged.Select(v => v.Hash).Concat(response.TorrentsRemoved));
|
||||
|
||||
torrents = cache.Torrents
|
||||
.Where(v => !removedAndUpdated.Contains(v.Hash))
|
||||
.Concat(response.TorrentsChanged)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
torrents = response.Torrents;
|
||||
}
|
||||
|
||||
cache = new UTorrentTorrentCache
|
||||
{
|
||||
CacheID = response.CacheNumber,
|
||||
Torrents = torrents
|
||||
};
|
||||
|
||||
_torrentCache.Set(cacheKey, cache, TimeSpan.FromMinutes(15));
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
@@ -122,7 +152,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
item.Status = DownloadItemStatus.Warning;
|
||||
item.Message = "uTorrent is reporting an error";
|
||||
}
|
||||
else if (torrent.Status.HasFlag(UTorrentTorrentStatus.Loaded) &&
|
||||
else if (torrent.Status.HasFlag(UTorrentTorrentStatus.Loaded) &&
|
||||
torrent.Status.HasFlag(UTorrentTorrentStatus.Checked) && torrent.Remaining == 0 && torrent.Progress == 1.0)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Completed;
|
||||
@@ -239,7 +269,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.GetTorrents(Settings);
|
||||
_proxy.GetTorrents(null, Settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
{
|
||||
int GetVersion(UTorrentSettings settings);
|
||||
Dictionary<string, string> GetConfig(UTorrentSettings settings);
|
||||
List<UTorrentTorrent> GetTorrents(UTorrentSettings settings);
|
||||
UTorrentResponse GetTorrents(string cacheID, UTorrentSettings settings);
|
||||
|
||||
void AddTorrentFromUrl(string torrentUrl, UTorrentSettings settings);
|
||||
void AddTorrentFromFile(string fileName, byte[] fileContent, UTorrentSettings settings);
|
||||
@@ -70,14 +70,19 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public List<UTorrentTorrent> GetTorrents(UTorrentSettings settings)
|
||||
public UTorrentResponse GetTorrents(string cacheID, UTorrentSettings settings)
|
||||
{
|
||||
var requestBuilder = BuildRequest(settings)
|
||||
.AddQueryParam("list", 1);
|
||||
|
||||
if (cacheID.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.AddQueryParam("cid", cacheID);
|
||||
}
|
||||
|
||||
var result = ProcessRequest(requestBuilder, settings);
|
||||
|
||||
return result.Torrents;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void AddTorrentFromUrl(string torrentUrl, UTorrentSettings settings)
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
public List<object> RssFeeds { get; set; }
|
||||
public List<object> RssFilters { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "torrentp")]
|
||||
public List<UTorrentTorrent> TorrentsChanged { get; set; }
|
||||
[JsonProperty(PropertyName = "torrentm")]
|
||||
public List<string> TorrentsRemoved { get; set; }
|
||||
[JsonProperty(PropertyName = "torrentc")]
|
||||
public string CacheNumber { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
{
|
||||
public class UTorrentTorrentCache
|
||||
{
|
||||
public string CacheID { get; set; }
|
||||
|
||||
public List<UTorrentTorrent> Torrents { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Download
|
||||
private void Import(TrackedDownload trackedDownload)
|
||||
{
|
||||
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
|
||||
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
|
||||
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
|
||||
|
||||
if (importResults.Empty())
|
||||
{
|
||||
|
||||
60
src/NzbDrone.Core/Extras/ExistingExtraFileService.cs
Normal file
60
src/NzbDrone.Core/Extras/ExistingExtraFileService.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras
|
||||
{
|
||||
public class ExistingExtraFileService : IHandle<SeriesScannedEvent>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly List<IImportExistingExtraFiles> _existingExtraFileImporters;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ExistingExtraFileService(IDiskProvider diskProvider,
|
||||
IDiskScanService diskScanService,
|
||||
List<IImportExistingExtraFiles> existingExtraFileImporters,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_diskScanService = diskScanService;
|
||||
_existingExtraFileImporters = existingExtraFileImporters.OrderBy(e => e.Order).ToList();
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Handle(SeriesScannedEvent message)
|
||||
{
|
||||
var series = message.Series;
|
||||
var extraFiles = new List<ExtraFile>();
|
||||
|
||||
if (!_diskProvider.FolderExists(series.Path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug("Looking for existing extra files in {0}", series.Path);
|
||||
|
||||
var filesOnDisk = _diskScanService.GetNonVideoFiles(series.Path);
|
||||
var possibleExtraFiles = _diskScanService.FilterFiles(series, filesOnDisk);
|
||||
|
||||
var filteredFiles = possibleExtraFiles;
|
||||
var importedFiles = new List<string>();
|
||||
|
||||
foreach (var existingExtraFileImporter in _existingExtraFileImporters)
|
||||
{
|
||||
var imported = existingExtraFileImporter.ProcessFiles(series, filteredFiles, importedFiles);
|
||||
|
||||
importedFiles.AddRange(imported.Select(f => Path.Combine(series.Path, f.RelativePath)));
|
||||
}
|
||||
|
||||
_logger.Info("Found {0} extra files", extraFiles.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
150
src/NzbDrone.Core/Extras/ExtraService.cs
Normal file
150
src/NzbDrone.Core/Extras/ExtraService.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras
|
||||
{
|
||||
public interface IExtraService
|
||||
{
|
||||
void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly);
|
||||
}
|
||||
|
||||
public class ExtraService : IExtraService,
|
||||
IHandle<MediaCoversUpdatedEvent>,
|
||||
IHandle<EpisodeFolderCreatedEvent>,
|
||||
IHandle<SeriesRenamedEvent>
|
||||
{
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly List<IManageExtraFiles> _extraFileManagers;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ExtraService(IMediaFileService mediaFileService,
|
||||
IEpisodeService episodeService,
|
||||
IDiskProvider diskProvider,
|
||||
IConfigService configService,
|
||||
List<IManageExtraFiles> extraFileManagers,
|
||||
Logger logger)
|
||||
{
|
||||
_mediaFileService = mediaFileService;
|
||||
_episodeService = episodeService;
|
||||
_diskProvider = diskProvider;
|
||||
_configService = configService;
|
||||
_extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList();
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly)
|
||||
{
|
||||
var series = localEpisode.Series;
|
||||
|
||||
foreach (var extraFileManager in _extraFileManagers)
|
||||
{
|
||||
extraFileManager.CreateAfterEpisodeImport(series, episodeFile);
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
// Not importing files yet, testing that parsing is working properly first
|
||||
return;
|
||||
|
||||
var sourcePath = localEpisode.Path;
|
||||
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
|
||||
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
|
||||
var files = _diskProvider.GetFiles(sourceFolder, SearchOption.TopDirectoryOnly);
|
||||
|
||||
var wantedExtensions = _configService.ExtraFileExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(e => e.Trim(' ', '.'))
|
||||
.ToList();
|
||||
|
||||
var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName));
|
||||
|
||||
foreach (var matchingFilename in matchingFilenames)
|
||||
{
|
||||
var matchingExtension = wantedExtensions.FirstOrDefault(e => matchingFilename.EndsWith(e));
|
||||
|
||||
if (matchingExtension == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var extraFileManager in _extraFileManagers)
|
||||
{
|
||||
var extraFile = extraFileManager.Import(series, episodeFile, matchingFilename, matchingExtension, isReadOnly);
|
||||
|
||||
if (extraFile != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Failed to import extra file: {0}", matchingFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(MediaCoversUpdatedEvent message)
|
||||
{
|
||||
var series = message.Series;
|
||||
var episodeFiles = GetEpisodeFiles(series.Id);
|
||||
|
||||
foreach (var extraFileManager in _extraFileManagers)
|
||||
{
|
||||
extraFileManager.CreateAfterSeriesScan(series, episodeFiles);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(EpisodeFolderCreatedEvent message)
|
||||
{
|
||||
var series = message.Series;
|
||||
|
||||
foreach (var extraFileManager in _extraFileManagers)
|
||||
{
|
||||
extraFileManager.CreateAfterEpisodeImport(series, message.SeriesFolder, message.SeasonFolder);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(SeriesRenamedEvent message)
|
||||
{
|
||||
var series = message.Series;
|
||||
var episodeFiles = GetEpisodeFiles(series.Id);
|
||||
|
||||
foreach (var extraFileManager in _extraFileManagers)
|
||||
{
|
||||
extraFileManager.MoveFilesAfterRename(series, episodeFiles);
|
||||
}
|
||||
}
|
||||
|
||||
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
|
||||
{
|
||||
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
|
||||
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
var localEpisodeFile = episodeFile;
|
||||
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
|
||||
}
|
||||
|
||||
return episodeFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
using System;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Files
|
||||
namespace NzbDrone.Core.Extras.Files
|
||||
{
|
||||
public class MetadataFile : ModelBase
|
||||
public abstract class ExtraFile : ModelBase
|
||||
{
|
||||
public int SeriesId { get; set; }
|
||||
public string Consumer { get; set; }
|
||||
public MetadataType Type { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public DateTime LastUpdated { get; set; }
|
||||
public int? EpisodeFileId { get; set; }
|
||||
public int? SeasonNumber { get; set; }
|
||||
public string Hash { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
public DateTime LastUpdated { get; set; }
|
||||
public string Extension { get; set; }
|
||||
}
|
||||
}
|
||||
70
src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs
Normal file
70
src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Files
|
||||
{
|
||||
public interface IManageExtraFiles
|
||||
{
|
||||
int Order { get; }
|
||||
IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles);
|
||||
IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
|
||||
IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder);
|
||||
IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles);
|
||||
ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly);
|
||||
}
|
||||
|
||||
public abstract class ExtraFileManager<TExtraFile> : IManageExtraFiles
|
||||
where TExtraFile : ExtraFile, new()
|
||||
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IExtraFileService<TExtraFile> _extraFileService;
|
||||
|
||||
public ExtraFileManager(IConfigService configService,
|
||||
IDiskTransferService diskTransferService,
|
||||
IExtraFileService<TExtraFile> extraFileService)
|
||||
{
|
||||
_configService = configService;
|
||||
_diskTransferService = diskTransferService;
|
||||
_extraFileService = extraFileService;
|
||||
}
|
||||
|
||||
public abstract int Order { get; }
|
||||
public abstract IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles);
|
||||
public abstract IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
|
||||
public abstract IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder);
|
||||
public abstract IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles);
|
||||
public abstract ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly);
|
||||
|
||||
protected TExtraFile ImportFile(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
|
||||
{
|
||||
var newFileName = Path.Combine(series.Path, Path.ChangeExtension(episodeFile.RelativePath, extension));
|
||||
|
||||
var transferMode = TransferMode.Move;
|
||||
|
||||
if (readOnly)
|
||||
{
|
||||
transferMode = _configService.CopyUsingHardlinks ? TransferMode.HardLinkOrCopy : TransferMode.Copy;
|
||||
}
|
||||
|
||||
_diskTransferService.TransferFile(path, newFileName, transferMode, true, false);
|
||||
|
||||
return new TExtraFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = episodeFile.SeasonNumber,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
RelativePath = series.Path.GetRelativePath(newFileName),
|
||||
Extension = Path.GetExtension(path)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,23 @@ using System.Linq;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Files
|
||||
namespace NzbDrone.Core.Extras.Files
|
||||
{
|
||||
public interface IMetadataFileRepository : IBasicRepository<MetadataFile>
|
||||
public interface IExtraFileRepository<TExtraFile> : IBasicRepository<TExtraFile> where TExtraFile : ExtraFile, new()
|
||||
{
|
||||
void DeleteForSeries(int seriesId);
|
||||
void DeleteForSeason(int seriesId, int seasonNumber);
|
||||
void DeleteForEpisodeFile(int episodeFileId);
|
||||
List<MetadataFile> GetFilesBySeries(int seriesId);
|
||||
List<MetadataFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||
List<MetadataFile> GetFilesByEpisodeFile(int episodeFileId);
|
||||
MetadataFile FindByPath(string path);
|
||||
List<TExtraFile> GetFilesBySeries(int seriesId);
|
||||
List<TExtraFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||
List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId);
|
||||
TExtraFile FindByPath(string path);
|
||||
}
|
||||
|
||||
public class MetadataFileRepository : BasicRepository<MetadataFile>, IMetadataFileRepository
|
||||
public class ExtraFileRepository<TExtraFile> : BasicRepository<TExtraFile>, IExtraFileRepository<TExtraFile>
|
||||
where TExtraFile : ExtraFile, new()
|
||||
{
|
||||
public MetadataFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
public ExtraFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
@@ -38,22 +39,22 @@ namespace NzbDrone.Core.Metadata.Files
|
||||
Delete(c => c.EpisodeFileId == episodeFileId);
|
||||
}
|
||||
|
||||
public List<MetadataFile> GetFilesBySeries(int seriesId)
|
||||
public List<TExtraFile> GetFilesBySeries(int seriesId)
|
||||
{
|
||||
return Query.Where(c => c.SeriesId == seriesId);
|
||||
}
|
||||
|
||||
public List<MetadataFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||
public List<TExtraFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||
{
|
||||
return Query.Where(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber);
|
||||
}
|
||||
|
||||
public List<MetadataFile> GetFilesByEpisodeFile(int episodeFileId)
|
||||
public List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId)
|
||||
{
|
||||
return Query.Where(c => c.EpisodeFileId == episodeFileId);
|
||||
}
|
||||
|
||||
public MetadataFile FindByPath(string path)
|
||||
public TExtraFile FindByPath(string path)
|
||||
{
|
||||
return Query.Where(c => c.RelativePath == path).SingleOrDefault();
|
||||
}
|
||||
139
src/NzbDrone.Core/Extras/Files/ExtraFileService.cs
Normal file
139
src/NzbDrone.Core/Extras/Files/ExtraFileService.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Files
|
||||
{
|
||||
public interface IExtraFileService<TExtraFile>
|
||||
where TExtraFile : ExtraFile, new()
|
||||
{
|
||||
List<TExtraFile> GetFilesBySeries(int seriesId);
|
||||
List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId);
|
||||
TExtraFile FindByPath(string path);
|
||||
void Upsert(TExtraFile extraFile);
|
||||
void Upsert(List<TExtraFile> extraFiles);
|
||||
void Delete(int id);
|
||||
void DeleteMany(IEnumerable<int> ids);
|
||||
}
|
||||
|
||||
public abstract class ExtraFileService<TExtraFile> : IExtraFileService<TExtraFile>,
|
||||
IHandleAsync<SeriesDeletedEvent>,
|
||||
IHandleAsync<EpisodeFileDeletedEvent>
|
||||
where TExtraFile : ExtraFile, new()
|
||||
{
|
||||
private readonly IExtraFileRepository<TExtraFile> _repository;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IRecycleBinProvider _recycleBinProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ExtraFileService(IExtraFileRepository<TExtraFile> repository,
|
||||
ISeriesService seriesService,
|
||||
IDiskProvider diskProvider,
|
||||
IRecycleBinProvider recycleBinProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_repository = repository;
|
||||
_seriesService = seriesService;
|
||||
_diskProvider = diskProvider;
|
||||
_recycleBinProvider = recycleBinProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public virtual bool PermanentlyDelete
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<TExtraFile> GetFilesBySeries(int seriesId)
|
||||
{
|
||||
return _repository.GetFilesBySeries(seriesId);
|
||||
}
|
||||
|
||||
public List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId)
|
||||
{
|
||||
return _repository.GetFilesByEpisodeFile(episodeFileId);
|
||||
}
|
||||
|
||||
public TExtraFile FindByPath(string path)
|
||||
{
|
||||
return _repository.FindByPath(path);
|
||||
}
|
||||
|
||||
public void Upsert(TExtraFile extraFile)
|
||||
{
|
||||
Upsert(new List<TExtraFile> { extraFile });
|
||||
}
|
||||
|
||||
public void Upsert(List<TExtraFile> extraFiles)
|
||||
{
|
||||
extraFiles.ForEach(m =>
|
||||
{
|
||||
m.LastUpdated = DateTime.UtcNow;
|
||||
|
||||
if (m.Id == 0)
|
||||
{
|
||||
m.Added = m.LastUpdated;
|
||||
}
|
||||
});
|
||||
|
||||
_repository.InsertMany(extraFiles.Where(m => m.Id == 0).ToList());
|
||||
_repository.UpdateMany(extraFiles.Where(m => m.Id > 0).ToList());
|
||||
}
|
||||
|
||||
public void Delete(int id)
|
||||
{
|
||||
_repository.Delete(id);
|
||||
}
|
||||
|
||||
public void DeleteMany(IEnumerable<int> ids)
|
||||
{
|
||||
_repository.DeleteMany(ids);
|
||||
}
|
||||
|
||||
public void HandleAsync(SeriesDeletedEvent message)
|
||||
{
|
||||
_logger.Debug("Deleting Extra from database for series: {0}", message.Series);
|
||||
_repository.DeleteForSeries(message.Series.Id);
|
||||
}
|
||||
|
||||
public void HandleAsync(EpisodeFileDeletedEvent message)
|
||||
{
|
||||
var episodeFile = message.EpisodeFile;
|
||||
var series = _seriesService.GetSeries(message.EpisodeFile.SeriesId);
|
||||
|
||||
foreach (var extra in _repository.GetFilesByEpisodeFile(episodeFile.Id))
|
||||
{
|
||||
var path = Path.Combine(series.Path, extra.RelativePath);
|
||||
|
||||
if (_diskProvider.FileExists(path))
|
||||
{
|
||||
if (PermanentlyDelete)
|
||||
{
|
||||
_diskProvider.DeleteFile(path);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Send extra files to the recycling bin so they can be recovered if necessary
|
||||
_recycleBinProvider.DeleteFile(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Deleting Extra from database for episode file: {0}", episodeFile);
|
||||
_repository.DeleteForEpisodeFile(episodeFile.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/NzbDrone.Core/Extras/IImportExistingExtraFiles.cs
Normal file
12
src/NzbDrone.Core/Extras/IImportExistingExtraFiles.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras
|
||||
{
|
||||
public interface IImportExistingExtraFiles
|
||||
{
|
||||
int Order { get; }
|
||||
IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
|
||||
namespace NzbDrone.Core.Extras
|
||||
{
|
||||
public class ImportExistingExtraFileFilterResult<TExtraFile>
|
||||
where TExtraFile : ExtraFile, new()
|
||||
{
|
||||
public ImportExistingExtraFileFilterResult(List<TExtraFile> previouslyImported, List<string> filesOnDisk)
|
||||
{
|
||||
PreviouslyImported = previouslyImported;
|
||||
FilesOnDisk = filesOnDisk;
|
||||
}
|
||||
|
||||
public List<TExtraFile> PreviouslyImported { get; set; }
|
||||
public List<string> FilesOnDisk { get; set; }
|
||||
}
|
||||
}
|
||||
57
src/NzbDrone.Core/Extras/ImportExistingExtraFilesBase.cs
Normal file
57
src/NzbDrone.Core/Extras/ImportExistingExtraFilesBase.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras
|
||||
{
|
||||
public abstract class ImportExistingExtraFilesBase<TExtraFile> : IImportExistingExtraFiles
|
||||
where TExtraFile : ExtraFile, new()
|
||||
{
|
||||
private readonly IExtraFileService<TExtraFile> _extraFileService;
|
||||
|
||||
public ImportExistingExtraFilesBase(IExtraFileService<TExtraFile> extraFileService)
|
||||
{
|
||||
_extraFileService = extraFileService;
|
||||
}
|
||||
|
||||
public abstract int Order { get; }
|
||||
public abstract IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles);
|
||||
|
||||
public virtual ImportExistingExtraFileFilterResult<TExtraFile> FilterAndClean(Series series, List<string> filesOnDisk, List<string> importedFiles)
|
||||
{
|
||||
var seriesFiles = _extraFileService.GetFilesBySeries(series.Id);
|
||||
|
||||
Clean(series, filesOnDisk, importedFiles, seriesFiles);
|
||||
|
||||
return Filter(series, filesOnDisk, importedFiles, seriesFiles);
|
||||
}
|
||||
|
||||
private ImportExistingExtraFileFilterResult<TExtraFile> Filter(Series series, List<string> filesOnDisk, List<string> importedFiles, List<TExtraFile> seriesFiles)
|
||||
{
|
||||
var previouslyImported = seriesFiles.IntersectBy(s => Path.Combine(series.Path, s.RelativePath), filesOnDisk, f => f, PathEqualityComparer.Instance).ToList();
|
||||
var filteredFiles = filesOnDisk.Except(previouslyImported.Select(f => Path.Combine(series.Path, f.RelativePath)).ToList(), PathEqualityComparer.Instance)
|
||||
.Except(importedFiles, PathEqualityComparer.Instance)
|
||||
.ToList();
|
||||
|
||||
// Return files that are already imported so they aren't imported again by other importers.
|
||||
// Filter out files that were previously imported and as well as ones imported by other importers.
|
||||
return new ImportExistingExtraFileFilterResult<TExtraFile>(previouslyImported, filteredFiles);
|
||||
}
|
||||
|
||||
private void Clean(Series series, List<string> filesOnDisk, List<string> importedFiles, List<TExtraFile> seriesFiles)
|
||||
{
|
||||
var alreadyImportedFileIds = seriesFiles.IntersectBy(f => Path.Combine(series.Path, f.RelativePath), importedFiles, i => i, PathEqualityComparer.Instance)
|
||||
.Select(f => f.Id);
|
||||
|
||||
var deletedFiles = seriesFiles.ExceptBy(f => Path.Combine(series.Path, f.RelativePath), filesOnDisk, i => i, PathEqualityComparer.Instance)
|
||||
.Select(f => f.Id);
|
||||
|
||||
_extraFileService.DeleteMany(alreadyImportedFileIds);
|
||||
_extraFileService.DeleteMany(deletedFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,27 +6,20 @@ using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.MediaBrowser
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.MediaBrowser
|
||||
{
|
||||
public class MediaBrowserMetadata : MetadataBase<MediaBrowserMetadataSettings>
|
||||
{
|
||||
private readonly IMapCoversToLocal _mediaCoverService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MediaBrowserMetadata(IMapCoversToLocal mediaCoverService,
|
||||
IDiskProvider diskProvider,
|
||||
public MediaBrowserMetadata(
|
||||
Logger logger)
|
||||
{
|
||||
_mediaCoverService = mediaCoverService;
|
||||
_diskProvider = diskProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -38,13 +31,6 @@ namespace NzbDrone.Core.Metadata.Consumers.MediaBrowser
|
||||
}
|
||||
}
|
||||
|
||||
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var updatedMetadataFiles = new List<MetadataFile>();
|
||||
|
||||
return updatedMetadataFiles;
|
||||
}
|
||||
|
||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
{
|
||||
var filename = Path.GetFileName(path);
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.MediaBrowser
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.MediaBrowser
|
||||
{
|
||||
public class MediaBrowserSettingsValidator : AbstractValidator<MediaBrowserMetadataSettings>
|
||||
{
|
||||
@@ -9,12 +9,13 @@ using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.Roksbox
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
|
||||
{
|
||||
public class RoksboxMetadata : MetadataBase<RoksboxMetadataSettings>
|
||||
{
|
||||
@@ -42,49 +43,22 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
|
||||
}
|
||||
}
|
||||
|
||||
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
|
||||
{
|
||||
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
|
||||
var updatedMetadataFiles = new List<MetadataFile>();
|
||||
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||
{
|
||||
var metadataFiles = episodeFilesMetadata.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
||||
|
||||
foreach (var metadataFile in metadataFiles)
|
||||
{
|
||||
string newFilename;
|
||||
|
||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||
{
|
||||
newFilename = GetEpisodeImageFilename(episodeFile.RelativePath);
|
||||
}
|
||||
|
||||
else if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||
{
|
||||
newFilename = GetEpisodeMetadataFilename(episodeFile.RelativePath);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Trace("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingFilename = Path.Combine(series.Path, metadataFile.RelativePath);
|
||||
newFilename = Path.Combine(series.Path, newFilename);
|
||||
|
||||
if (!newFilename.PathEquals(existingFilename))
|
||||
{
|
||||
_diskProvider.MoveFile(existingFilename, newFilename);
|
||||
metadataFile.RelativePath = series.Path.GetRelativePath(newFilename);
|
||||
|
||||
updatedMetadataFiles.Add(metadataFile);
|
||||
}
|
||||
}
|
||||
return GetEpisodeImageFilename(episodeFilePath);
|
||||
}
|
||||
|
||||
return updatedMetadataFiles;
|
||||
if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||
{
|
||||
return GetEpisodeMetadataFilename(episodeFilePath);
|
||||
}
|
||||
|
||||
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||
return Path.Combine(series.Path, metadataFile.RelativePath);
|
||||
}
|
||||
|
||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
@@ -221,7 +195,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
|
||||
}
|
||||
|
||||
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
|
||||
var destination = Path.Combine(series.Path, Path.GetFileName(series.Path) + Path.GetExtension(source));
|
||||
var destination = Path.GetFileName(series.Path) + Path.GetExtension(source);
|
||||
|
||||
return new List<ImageFileResult>{ new ImageFileResult(destination, source) };
|
||||
}
|
||||
@@ -246,7 +220,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
|
||||
}
|
||||
|
||||
var filename = Path.GetFileName(seasonFolder) + ".jpg";
|
||||
var path = Path.Combine(series.Path, seasonFolder, filename);
|
||||
var path = series.Path.GetRelativePath(Path.Combine(series.Path, seasonFolder, filename));
|
||||
|
||||
return new List<ImageFileResult> { new ImageFileResult(path, image.Url) };
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.Roksbox
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
|
||||
{
|
||||
public class RoksboxSettingsValidator : AbstractValidator<RoksboxMetadataSettings>
|
||||
{
|
||||
@@ -9,12 +9,13 @@ using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.Wdtv
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
|
||||
{
|
||||
public class WdtvMetadata : MetadataBase<WdtvMetadataSettings>
|
||||
{
|
||||
@@ -41,49 +42,23 @@ namespace NzbDrone.Core.Metadata.Consumers.Wdtv
|
||||
}
|
||||
}
|
||||
|
||||
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
|
||||
{
|
||||
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
|
||||
var updatedMetadataFiles = new List<MetadataFile>();
|
||||
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||
{
|
||||
var metadataFiles = episodeFilesMetadata.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
||||
|
||||
foreach (var metadataFile in metadataFiles)
|
||||
{
|
||||
string newFilename;
|
||||
|
||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||
{
|
||||
newFilename = GetEpisodeImageFilename(episodeFile.RelativePath);
|
||||
}
|
||||
|
||||
else if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||
{
|
||||
newFilename = GetEpisodeMetadataFilename(episodeFile.RelativePath);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Trace("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingPath = Path.Combine(series.Path, metadataFile.RelativePath);
|
||||
var newPath = Path.Combine(series.Path, newFilename);
|
||||
|
||||
if (!newPath.PathEquals(existingPath))
|
||||
{
|
||||
_diskProvider.MoveFile(existingPath, newPath);
|
||||
metadataFile.RelativePath = newFilename;
|
||||
|
||||
updatedMetadataFiles.Add(metadataFile);
|
||||
}
|
||||
}
|
||||
return GetEpisodeImageFilename(episodeFilePath);
|
||||
}
|
||||
|
||||
return updatedMetadataFiles;
|
||||
if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||
{
|
||||
return GetEpisodeMetadataFilename(episodeFilePath);
|
||||
}
|
||||
|
||||
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||
return Path.Combine(series.Path, metadataFile.RelativePath);
|
||||
|
||||
}
|
||||
|
||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.Wdtv
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
|
||||
{
|
||||
public class WdtvSettingsValidator : AbstractValidator<WdtvMetadataSettings>
|
||||
{
|
||||
@@ -9,25 +9,23 @@ using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
{
|
||||
public class XbmcMetadata : MetadataBase<XbmcMetadataSettings>
|
||||
{
|
||||
private readonly IMapCoversToLocal _mediaCoverService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public XbmcMetadata(IMapCoversToLocal mediaCoverService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_mediaCoverService = mediaCoverService;
|
||||
_diskProvider = diskProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -43,49 +41,22 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||
}
|
||||
}
|
||||
|
||||
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
|
||||
{
|
||||
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
|
||||
var updatedMetadataFiles = new List<MetadataFile>();
|
||||
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||
{
|
||||
var metadataFiles = episodeFilesMetadata.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
||||
|
||||
foreach (var metadataFile in metadataFiles)
|
||||
{
|
||||
string newFilename;
|
||||
|
||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||
{
|
||||
newFilename = GetEpisodeImageFilename(episodeFile.RelativePath);
|
||||
}
|
||||
|
||||
else if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||
{
|
||||
newFilename = GetEpisodeNfoFilename(episodeFile.RelativePath);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingFilename = Path.Combine(series.Path, metadataFile.RelativePath);
|
||||
newFilename = Path.Combine(series.Path, newFilename);
|
||||
|
||||
if (!newFilename.PathEquals(existingFilename))
|
||||
{
|
||||
_diskProvider.MoveFile(existingFilename, newFilename);
|
||||
metadataFile.RelativePath = series.Path.GetRelativePath(newFilename);
|
||||
|
||||
updatedMetadataFiles.Add(metadataFile);
|
||||
}
|
||||
}
|
||||
return GetEpisodeImageFilename(episodeFilePath);
|
||||
}
|
||||
|
||||
return updatedMetadataFiles;
|
||||
if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||
{
|
||||
return GetEpisodeMetadataFilename(episodeFilePath);
|
||||
}
|
||||
|
||||
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||
return Path.Combine(series.Path, metadataFile.RelativePath);
|
||||
}
|
||||
|
||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
@@ -328,7 +299,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||
}
|
||||
}
|
||||
|
||||
return new MetadataFileResult(GetEpisodeNfoFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||
return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||
}
|
||||
|
||||
public override List<ImageFileResult> SeriesImages(Series series)
|
||||
@@ -407,7 +378,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEpisodeNfoFilename(string episodeFilePath)
|
||||
private string GetEpisodeMetadataFilename(string episodeFilePath)
|
||||
{
|
||||
return Path.ChangeExtension(episodeFilePath, "nfo");
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
{
|
||||
public class XbmcSettingsValidator : AbstractValidator<XbmcMetadataSettings>
|
||||
{
|
||||
108
src/NzbDrone.Core/Extras/MetaData/ExistingMetadataImporter.cs
Normal file
108
src/NzbDrone.Core/Extras/MetaData/ExistingMetadataImporter.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.Extras.Subtitles;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
public class ExistingMetadataImporter : ImportExistingExtraFilesBase<MetadataFile>
|
||||
{
|
||||
private readonly IExtraFileService<MetadataFile> _metadataFileService;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly Logger _logger;
|
||||
private readonly List<IMetadata> _consumers;
|
||||
|
||||
public ExistingMetadataImporter(IExtraFileService<MetadataFile> metadataFileService,
|
||||
IEnumerable<IMetadata> consumers,
|
||||
IParsingService parsingService,
|
||||
Logger logger)
|
||||
: base(metadataFileService)
|
||||
{
|
||||
_metadataFileService = metadataFileService;
|
||||
_parsingService = parsingService;
|
||||
_logger = logger;
|
||||
_consumers = consumers.ToList();
|
||||
}
|
||||
|
||||
public override int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles)
|
||||
{
|
||||
_logger.Debug("Looking for existing metadata in {0}", series.Path);
|
||||
|
||||
var metadataFiles = new List<MetadataFile>();
|
||||
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles);
|
||||
|
||||
foreach (var possibleMetadataFile in filterResult.FilesOnDisk)
|
||||
{
|
||||
// Don't process files that have known Subtitle file extensions (saves a bit of unecessary processing)
|
||||
|
||||
if (SubtitleFileExtensions.Extensions.Contains(Path.GetExtension(possibleMetadataFile)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var consumer in _consumers)
|
||||
{
|
||||
var metadata = consumer.FindMetadataFile(series, possibleMetadataFile);
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (metadata.Type == MetadataType.EpisodeImage ||
|
||||
metadata.Type == MetadataType.EpisodeMetadata)
|
||||
{
|
||||
var localEpisode = _parsingService.GetLocalEpisode(possibleMetadataFile, series);
|
||||
|
||||
if (localEpisode == null)
|
||||
{
|
||||
_logger.Debug("Unable to parse extra file: {0}", possibleMetadataFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localEpisode.Episodes.Empty())
|
||||
{
|
||||
_logger.Debug("Cannot find related episodes for: {0}", possibleMetadataFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1)
|
||||
{
|
||||
_logger.Debug("Extra file: {0} does not match existing files.", possibleMetadataFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
metadata.SeasonNumber = localEpisode.SeasonNumber;
|
||||
metadata.EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId;
|
||||
}
|
||||
|
||||
metadata.Extension = Path.GetExtension(possibleMetadataFile);
|
||||
|
||||
metadataFiles.Add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info("Found {0} existing metadata files", metadataFiles.Count);
|
||||
_metadataFileService.Upsert(metadataFiles);
|
||||
|
||||
// Return files that were just imported along with files that were
|
||||
// previously imported so previously imported files aren't imported twice
|
||||
|
||||
return metadataFiles.Concat(filterResult.PreviouslyImported);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,22 @@ using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Files
|
||||
namespace NzbDrone.Core.Extras.Metadata.Files
|
||||
{
|
||||
public interface ICleanMetadataService
|
||||
{
|
||||
void Clean(Series series);
|
||||
}
|
||||
|
||||
public class CleanMetadataService : ICleanMetadataService
|
||||
public class CleanExtraFileService : ICleanMetadataService
|
||||
{
|
||||
private readonly IMetadataFileService _metadataFileService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public CleanMetadataService(IMetadataFileService metadataFileService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
public CleanExtraFileService(IMetadataFileService metadataFileService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_metadataFileService = metadataFileService;
|
||||
_diskProvider = diskProvider;
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Files
|
||||
namespace NzbDrone.Core.Extras.Metadata.Files
|
||||
{
|
||||
public class ImageFileResult
|
||||
{
|
||||
11
src/NzbDrone.Core/Extras/MetaData/Files/MetadataFile.cs
Normal file
11
src/NzbDrone.Core/Extras/MetaData/Files/MetadataFile.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Metadata.Files
|
||||
{
|
||||
public class MetadataFile : ExtraFile
|
||||
{
|
||||
public string Hash { get; set; }
|
||||
public string Consumer { get; set; }
|
||||
public MetadataType Type { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Metadata.Files
|
||||
{
|
||||
public interface IMetadataFileRepository : IExtraFileRepository<MetadataFile>
|
||||
{
|
||||
}
|
||||
|
||||
public class MetadataFileRepository : ExtraFileRepository<MetadataFile>, IMetadataFileRepository
|
||||
{
|
||||
public MetadataFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Files
|
||||
namespace NzbDrone.Core.Extras.Metadata.Files
|
||||
{
|
||||
public class MetadataFileResult
|
||||
{
|
||||
@@ -0,0 +1,28 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Metadata.Files
|
||||
{
|
||||
public interface IMetadataFileService : IExtraFileService<MetadataFile>
|
||||
{
|
||||
}
|
||||
|
||||
public class MetadataFileService : ExtraFileService<MetadataFile>, IMetadataFileService
|
||||
{
|
||||
public MetadataFileService(IExtraFileRepository<MetadataFile> repository, ISeriesService seriesService, IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, Logger logger)
|
||||
: base(repository, seriesService, diskProvider, recycleBinProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool PermanentlyDelete
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
public interface IMetadata : IProvider
|
||||
{
|
||||
List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
|
||||
string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile);
|
||||
MetadataFile FindMetadataFile(Series series, string path);
|
||||
|
||||
MetadataFileResult SeriesMetadata(Series series);
|
||||
MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
|
||||
List<ImageFileResult> SeriesImages(Series series);
|
||||
List<ImageFileResult> SeasonImages(Series series, Season season);
|
||||
List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FluentValidation.Results;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
public abstract class MetadataBase<TSettings> : IMetadata where TSettings : IProviderConfig, new()
|
||||
{
|
||||
@@ -43,7 +45,15 @@ namespace NzbDrone.Core.Metadata
|
||||
return new ValidationResult();
|
||||
}
|
||||
|
||||
public abstract List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
|
||||
public virtual string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
|
||||
{
|
||||
var existingFilename = Path.Combine(series.Path, metadataFile.RelativePath);
|
||||
var extension = Path.GetExtension(existingFilename).TrimStart('.');
|
||||
var newFileName = Path.ChangeExtension(Path.Combine(series.Path, episodeFile.RelativePath), extension);
|
||||
|
||||
return newFileName;
|
||||
}
|
||||
|
||||
public abstract MetadataFile FindMetadataFile(Series series, string path);
|
||||
|
||||
public abstract MetadataFileResult SeriesMetadata(Series series);
|
||||
@@ -1,6 +1,6 @@
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
public class MetadataDefinition : ProviderDefinition
|
||||
{
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
public interface IMetadataFactory : IProviderFactory<IMetadata, MetadataDefinition>
|
||||
{
|
||||
@@ -2,12 +2,10 @@
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
public interface IMetadataRepository : IProviderRepository<MetadataDefinition>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class MetadataRepository : ProviderRepository<MetadataDefinition>, IMetadataRepository
|
||||
@@ -7,159 +7,179 @@ using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
public class MetadataService : IHandle<MediaCoversUpdatedEvent>,
|
||||
IHandle<EpisodeImportedEvent>,
|
||||
IHandle<EpisodeFolderCreatedEvent>,
|
||||
IHandle<SeriesRenamedEvent>
|
||||
public class MetadataService : ExtraFileManager<MetadataFile>
|
||||
{
|
||||
private readonly IMetadataFactory _metadataFactory;
|
||||
private readonly IMetadataFileService _metadataFileService;
|
||||
private readonly ICleanMetadataService _cleanMetadataService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IDiskTransferService _diskTransferService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMetadataFileService _metadataFileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MetadataService(IMetadataFactory metadataFactory,
|
||||
IMetadataFileService metadataFileService,
|
||||
ICleanMetadataService cleanMetadataService,
|
||||
IMediaFileService mediaFileService,
|
||||
IEpisodeService episodeService,
|
||||
public MetadataService(IConfigService configService,
|
||||
IDiskTransferService diskTransferService,
|
||||
IMetadataFactory metadataFactory,
|
||||
ICleanMetadataService cleanMetadataService,
|
||||
IDiskProvider diskProvider,
|
||||
IHttpClient httpClient,
|
||||
IMediaFileAttributeService mediaFileAttributeService,
|
||||
IEventAggregator eventAggregator,
|
||||
IMetadataFileService metadataFileService,
|
||||
Logger logger)
|
||||
: base(configService, diskTransferService, metadataFileService)
|
||||
{
|
||||
_metadataFactory = metadataFactory;
|
||||
_metadataFileService = metadataFileService;
|
||||
_cleanMetadataService = cleanMetadataService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_episodeService = episodeService;
|
||||
_diskTransferService = diskTransferService;
|
||||
_diskProvider = diskProvider;
|
||||
_httpClient = httpClient;
|
||||
_mediaFileAttributeService = mediaFileAttributeService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_metadataFileService = metadataFileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Handle(MediaCoversUpdatedEvent message)
|
||||
public override int Order
|
||||
{
|
||||
_cleanMetadataService.Clean(message.Series);
|
||||
get
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderExists(message.Series.Path))
|
||||
public override IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
|
||||
_cleanMetadataService.Clean(series);
|
||||
|
||||
if (!_diskProvider.FolderExists(series.Path))
|
||||
{
|
||||
_logger.Info("Series folder does not exist, skipping metadata creation");
|
||||
return;
|
||||
return Enumerable.Empty<MetadataFile>();
|
||||
}
|
||||
|
||||
var seriesMetadataFiles = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
||||
var episodeFiles = GetEpisodeFiles(message.Series.Id);
|
||||
var files = new List<MetadataFile>();
|
||||
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
{
|
||||
var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
|
||||
var files = new List<MetadataFile>();
|
||||
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
|
||||
|
||||
files.AddIfNotNull(ProcessSeriesMetadata(consumer, message.Series, consumerFiles));
|
||||
files.AddRange(ProcessSeriesImages(consumer, message.Series, consumerFiles));
|
||||
files.AddRange(ProcessSeasonImages(consumer, message.Series, consumerFiles));
|
||||
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
|
||||
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
|
||||
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.Series, episodeFile, consumerFiles));
|
||||
files.AddRange(ProcessEpisodeImages(consumer, message.Series, episodeFile, consumerFiles));
|
||||
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, consumerFiles));
|
||||
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, consumerFiles));
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
||||
}
|
||||
|
||||
_metadataFileService.Upsert(files);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public void Handle(EpisodeImportedEvent message)
|
||||
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
|
||||
{
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
{
|
||||
var files = new List<MetadataFile>();
|
||||
|
||||
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List<MetadataFile>()));
|
||||
files.AddRange(ProcessEpisodeImages(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List<MetadataFile>()));
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(EpisodeFolderCreatedEvent message)
|
||||
{
|
||||
if (message.SeriesFolder.IsNullOrWhiteSpace() && message.SeasonFolder.IsNullOrWhiteSpace())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var seriesMetadataFiles = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
||||
var files = new List<MetadataFile>();
|
||||
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
{
|
||||
var files = new List<MetadataFile>();
|
||||
var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
|
||||
|
||||
if (message.SeriesFolder.IsNotNullOrWhiteSpace())
|
||||
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, new List<MetadataFile>()));
|
||||
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, new List<MetadataFile>()));
|
||||
}
|
||||
|
||||
_metadataFileService.Upsert(files);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder)
|
||||
{
|
||||
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
|
||||
|
||||
if (seriesFolder.IsNullOrWhiteSpace() && seasonFolder.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new List<MetadataFile>();
|
||||
}
|
||||
|
||||
var files = new List<MetadataFile>();
|
||||
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
{
|
||||
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
|
||||
|
||||
if (seriesFolder.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
files.AddIfNotNull(ProcessSeriesMetadata(consumer, message.Series, consumerFiles));
|
||||
files.AddRange(ProcessSeriesImages(consumer, message.Series, consumerFiles));
|
||||
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
|
||||
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
|
||||
}
|
||||
|
||||
if (message.SeasonFolder.IsNotNullOrWhiteSpace())
|
||||
if (seasonFolder.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
files.AddRange(ProcessSeasonImages(consumer, message.Series, consumerFiles));
|
||||
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
||||
}
|
||||
|
||||
_metadataFileService.Upsert(files);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public void Handle(SeriesRenamedEvent message)
|
||||
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
||||
var episodeFiles = GetEpisodeFiles(message.Series.Id);
|
||||
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
|
||||
var movedFiles = new List<MetadataFile>();
|
||||
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
// TODO: Move EpisodeImage and EpisodeMetadata metadata files, instead of relying on consumers to do it
|
||||
// (Xbmc's EpisodeImage is more than just the extension)
|
||||
|
||||
foreach (var consumer in _metadataFactory.GetAvailableProviders())
|
||||
{
|
||||
var updatedMetadataFiles = consumer.AfterRename(message.Series,
|
||||
GetMetadataFilesForConsumer(consumer, seriesMetadata),
|
||||
episodeFiles);
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
var metadataFilesForConsumer = GetMetadataFilesForConsumer(consumer, metadataFiles).Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
|
||||
foreach (var metadataFile in metadataFilesForConsumer)
|
||||
{
|
||||
var newFileName = consumer.GetFilenameAfterMove(series, episodeFile, metadataFile);
|
||||
var existingFileName = Path.Combine(series.Path, metadataFile.RelativePath);
|
||||
|
||||
if (newFileName.PathNotEquals(existingFileName))
|
||||
{
|
||||
try
|
||||
{
|
||||
_diskProvider.MoveFile(existingFileName, newFileName);
|
||||
metadataFile.RelativePath = series.Path.GetRelativePath(newFileName);
|
||||
movedFiles.Add(metadataFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Unable to move metadata file: {0}", existingFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_metadataFileService.Upsert(movedFiles);
|
||||
|
||||
return movedFiles;
|
||||
}
|
||||
|
||||
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
|
||||
public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
|
||||
{
|
||||
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
|
||||
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
var localEpisodeFile = episodeFile;
|
||||
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
|
||||
}
|
||||
|
||||
return episodeFiles;
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> seriesMetadata)
|
||||
@@ -205,6 +225,7 @@ namespace NzbDrone.Core.Metadata
|
||||
|
||||
metadata.Hash = hash;
|
||||
metadata.RelativePath = seriesMetadata.RelativePath;
|
||||
metadata.Extension = Path.GetExtension(fullPath);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
@@ -226,7 +247,7 @@ namespace NzbDrone.Core.Metadata
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!fullPath.PathEquals(existingFullPath))
|
||||
if (fullPath.PathNotEquals(existingFullPath))
|
||||
{
|
||||
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
||||
existingMetadata.RelativePath = episodeMetadata.RelativePath;
|
||||
@@ -239,10 +260,12 @@ namespace NzbDrone.Core.Metadata
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = episodeFile.SeasonNumber,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.EpisodeMetadata,
|
||||
RelativePath = episodeMetadata.RelativePath
|
||||
RelativePath = episodeMetadata.RelativePath,
|
||||
Extension = Path.GetExtension(fullPath)
|
||||
};
|
||||
|
||||
if (hash == metadata.Hash)
|
||||
@@ -279,7 +302,8 @@ namespace NzbDrone.Core.Metadata
|
||||
SeriesId = series.Id,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.SeriesImage,
|
||||
RelativePath = image.RelativePath
|
||||
RelativePath = image.RelativePath,
|
||||
Extension = Path.GetExtension(fullPath)
|
||||
};
|
||||
|
||||
DownloadImage(series, image);
|
||||
@@ -315,7 +339,8 @@ namespace NzbDrone.Core.Metadata
|
||||
SeasonNumber = season.SeasonNumber,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.SeasonImage,
|
||||
RelativePath = image.RelativePath
|
||||
RelativePath = image.RelativePath,
|
||||
Extension = Path.GetExtension(fullPath)
|
||||
};
|
||||
|
||||
DownloadImage(series, image);
|
||||
@@ -347,7 +372,7 @@ namespace NzbDrone.Core.Metadata
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!fullPath.PathEquals(existingFullPath))
|
||||
if (fullPath.PathNotEquals(existingFullPath))
|
||||
{
|
||||
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
||||
existingMetadata.RelativePath = image.RelativePath;
|
||||
@@ -360,10 +385,12 @@ namespace NzbDrone.Core.Metadata
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = episodeFile.SeasonNumber,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.EpisodeImage,
|
||||
RelativePath = image.RelativePath
|
||||
RelativePath = image.RelativePath,
|
||||
Extension = Path.GetExtension(fullPath)
|
||||
};
|
||||
|
||||
DownloadImage(series, image);
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Core.Metadata
|
||||
namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
public enum MetadataType
|
||||
{
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Others
|
||||
{
|
||||
public class ExistingOtherExtraImporter : ImportExistingExtraFilesBase<OtherExtraFile>
|
||||
{
|
||||
private readonly IExtraFileService<OtherExtraFile> _otherExtraFileService;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ExistingOtherExtraImporter(IExtraFileService<OtherExtraFile> otherExtraFileService,
|
||||
IParsingService parsingService,
|
||||
Logger logger)
|
||||
: base(otherExtraFileService)
|
||||
{
|
||||
_otherExtraFileService = otherExtraFileService;
|
||||
_parsingService = parsingService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles)
|
||||
{
|
||||
_logger.Debug("Looking for existing extra files in {0}", series.Path);
|
||||
|
||||
var extraFiles = new List<OtherExtraFile>();
|
||||
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles);
|
||||
|
||||
foreach (var possibleExtraFile in filterResult.FilesOnDisk)
|
||||
{
|
||||
var localEpisode = _parsingService.GetLocalEpisode(possibleExtraFile, series);
|
||||
|
||||
if (localEpisode == null)
|
||||
{
|
||||
_logger.Debug("Unable to parse extra file: {0}", possibleExtraFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localEpisode.Episodes.Empty())
|
||||
{
|
||||
_logger.Debug("Cannot find related episodes for: {0}", possibleExtraFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1)
|
||||
{
|
||||
_logger.Debug("Extra file: {0} does not match existing files.", possibleExtraFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
var extraFile = new OtherExtraFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = localEpisode.SeasonNumber,
|
||||
EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId,
|
||||
RelativePath = series.Path.GetRelativePath(possibleExtraFile),
|
||||
Extension = Path.GetExtension(possibleExtraFile)
|
||||
};
|
||||
|
||||
extraFiles.Add(extraFile);
|
||||
}
|
||||
|
||||
_logger.Info("Found {0} existing other extra files", extraFiles.Count);
|
||||
_otherExtraFileService.Upsert(extraFiles);
|
||||
|
||||
// Return files that were just imported along with files that were
|
||||
// previously imported so previously imported files aren't imported twice
|
||||
|
||||
return extraFiles.Concat(filterResult.PreviouslyImported);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/NzbDrone.Core/Extras/Others/OtherExtraFile.cs
Normal file
8
src/NzbDrone.Core/Extras/Others/OtherExtraFile.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Others
|
||||
{
|
||||
public class OtherExtraFile : ExtraFile
|
||||
{
|
||||
}
|
||||
}
|
||||
18
src/NzbDrone.Core/Extras/Others/OtherExtraFileRepository.cs
Normal file
18
src/NzbDrone.Core/Extras/Others/OtherExtraFileRepository.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Others
|
||||
{
|
||||
public interface IOtherExtraFileRepository : IExtraFileRepository<OtherExtraFile>
|
||||
{
|
||||
}
|
||||
|
||||
public class OtherExtraFileRepository : ExtraFileRepository<OtherExtraFile>, IOtherExtraFileRepository
|
||||
{
|
||||
public OtherExtraFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/NzbDrone.Core/Extras/Others/OtherExtraFileService.cs
Normal file
20
src/NzbDrone.Core/Extras/Others/OtherExtraFileService.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Extras.Files;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Others
|
||||
{
|
||||
public interface IOtherExtraFileService : IExtraFileService<OtherExtraFile>
|
||||
{
|
||||
}
|
||||
|
||||
public class OtherExtraFileService : ExtraFileService<OtherExtraFile>, IOtherExtraFileService
|
||||
{
|
||||
public OtherExtraFileService(IExtraFileRepository<OtherExtraFile> repository, ISeriesService seriesService, IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, Logger logger)
|
||||
: base(repository, seriesService, diskProvider, recycleBinProvider, logger)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user