diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 000000000..97626ba45
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
index f5ca71492..b8387eb1b 100644
--- a/.idea/jsLibraryMappings.xml
+++ b/.idea/jsLibraryMappings.xml
@@ -1,7 +1,6 @@
-
\ No newline at end of file
diff --git a/src/NzbDrone.Api/Config/MediaManagementConfigModule.cs b/src/NzbDrone.Api/Config/MediaManagementConfigModule.cs
index e8d3b9eb4..8b35e53ed 100644
--- a/src/NzbDrone.Api/Config/MediaManagementConfigModule.cs
+++ b/src/NzbDrone.Api/Config/MediaManagementConfigModule.cs
@@ -1,5 +1,4 @@
-using System;
-using FluentValidation;
+using FluentValidation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Validation.Paths;
diff --git a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs
index f18c1f731..a51e8b4d3 100644
--- a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs
+++ b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs
@@ -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; }
}
diff --git a/src/NzbDrone.Api/Metadata/MetadataModule.cs b/src/NzbDrone.Api/Metadata/MetadataModule.cs
index 77c828093..7b4607abe 100644
--- a/src/NzbDrone.Api/Metadata/MetadataModule.cs
+++ b/src/NzbDrone.Api/Metadata/MetadataModule.cs
@@ -1,5 +1,5 @@
using System;
-using NzbDrone.Core.Metadata;
+using NzbDrone.Core.Extras.Metadata;
namespace NzbDrone.Api.Metadata
{
diff --git a/src/NzbDrone.Common.Test/ExtensionTests/IEnumerableExtensionTests/ExceptByFixture.cs b/src/NzbDrone.Common.Test/ExtensionTests/IEnumerableExtensionTests/ExceptByFixture.cs
new file mode 100644
index 000000000..1688fd8c4
--- /dev/null
+++ b/src/NzbDrone.Common.Test/ExtensionTests/IEnumerableExtensionTests/ExceptByFixture.cs
@@ -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
+ {
+ new Object1 { Prop1 = "one" },
+ new Object1 { Prop1 = "two" }
+ };
+
+ var second = new List
+ {
+ 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
+ {
+ new Object1 { Prop1 = "one" },
+ new Object1 { Prop1 = "two" }
+ };
+
+ var second = new List
+ {
+ 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");
+ }
+ }
+}
diff --git a/src/NzbDrone.Common.Test/ExtensionTests/IEnumerableExtensionTests/IntersectByFixture.cs b/src/NzbDrone.Common.Test/ExtensionTests/IEnumerableExtensionTests/IntersectByFixture.cs
new file mode 100644
index 000000000..f88b9ed40
--- /dev/null
+++ b/src/NzbDrone.Common.Test/ExtensionTests/IEnumerableExtensionTests/IntersectByFixture.cs
@@ -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
+ {
+ new Object1 { Prop1 = "one" },
+ new Object1 { Prop1 = "two" }
+ };
+
+ var second = new List
+ {
+ 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
+ {
+ new Object1 { Prop1 = "one" },
+ new Object1 { Prop1 = "two" }
+ };
+
+ var second = new List
+ {
+ 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");
+ }
+ }
+}
diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
index 867f6adaf..ec929d47b 100644
--- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
+++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj
@@ -80,6 +80,8 @@
+
+
diff --git a/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs b/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs
index 1593c69b9..a1beecaa9 100644
--- a/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs
+++ b/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs
@@ -13,6 +13,44 @@ namespace NzbDrone.Common.Extensions
return source.Where(element => knownKeys.Add(keySelector(element)));
}
+ public static IEnumerable IntersectBy(this IEnumerable first, Func firstKeySelector,
+ IEnumerable second, Func secondKeySelector,
+ IEqualityComparer keyComparer)
+ {
+ var keys = new HashSet(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 ExceptBy(this IEnumerable first, Func firstKeySelector,
+ IEnumerable second, Func secondKeySelector,
+ IEqualityComparer keyComparer)
+ {
+ var keys = new HashSet(second.Select(secondKeySelector), keyComparer);
+ var matchedKeys = new HashSet();
+
+ 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(this List source, TSource item)
{
if (item == null)
diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs
index 5d7ff3e19..c37243439 100644
--- a/src/NzbDrone.Common/Extensions/PathExtensions.cs
+++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs
@@ -37,6 +37,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)
diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/099_extra_and_subtitle_filesFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/099_extra_and_subtitle_filesFixture.cs
new file mode 100644
index 000000000..43396618d
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Datastore/Migration/099_extra_and_subtitle_filesFixture.cs
@@ -0,0 +1,33 @@
+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
+ {
+ [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"
+ });
+ });
+
+ var items = db.Query("SELECT * FROM MetadataFiles");
+
+ items.Should().HaveCount(1);
+ items.First().Extension.Should().Be(".jpg");
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/DeleteBadMediaCovers.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/DeleteBadMediaCovers.cs
index 8dfd295cf..b9d69fc61 100644
--- a/src/NzbDrone.Core.Test/HealthCheck/Checks/DeleteBadMediaCovers.cs
+++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/DeleteBadMediaCovers.cs
@@ -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
{
- private List _metaData;
+ private List _metadata;
private List _series;
[SetUp]
@@ -31,7 +33,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Build().ToList();
- _metaData = Builder.CreateListOfSize(1)
+ _metadata = Builder.CreateListOfSize(1)
.Build().ToList();
Mocker.GetMock()
@@ -41,7 +43,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock()
.Setup(c => c.GetFilesBySeries(_series.First().Id))
- .Returns(_metaData);
+ .Returns(_metadata);
Mocker.GetMock().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()
.Setup(c => c.OpenReadStream(imagePath))
@@ -115,7 +117,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock().Verify(c => c.DeleteFile(imagePath), Times.Once());
- Mocker.GetMock().Verify(c => c.Delete(_metaData.First().Id), Times.Once());
+ Mocker.GetMock().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()
.Setup(c => c.OpenReadStream(imagePath))
@@ -136,7 +138,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Subject.Clean();
Mocker.GetMock().Verify(c => c.DeleteFile(imagePath), Times.Once());
- Mocker.GetMock().Verify(c => c.Delete(_metaData.First().Id), Times.Once());
+ Mocker.GetMock().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()
.Setup(c => c.OpenReadStream(imagePath))
diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs
index 004863cdf..c2d436ec8 100644
--- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs
@@ -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()
.Verify(v => v.Insert(It.Is(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFilesFixture.cs
index a17e16e1d..65b05f32d 100644
--- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFilesFixture.cs
+++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFilesFixture.cs
@@ -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;
diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupDuplicateMetadataFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupDuplicateMetadataFilesFixture.cs
index 99bcb8fd6..5bfeaefc0 100644
--- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupDuplicateMetadataFilesFixture.cs
+++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupDuplicateMetadataFilesFixture.cs
@@ -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.CreateNew()
- .BuildNew();
+ .BuildNew();
Db.Insert(file);
Subject.Clean();
diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs
index 7e4dc060f..27679d8d3 100644
--- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs
+++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs
@@ -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.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();
diff --git a/src/NzbDrone.Core.Test/Metadata/Consumers/Roksbox/FindMetadataFileFixture.cs b/src/NzbDrone.Core.Test/Metadata/Consumers/Roksbox/FindMetadataFileFixture.cs
index 06cf6fb51..6d4328b32 100644
--- a/src/NzbDrone.Core.Test/Metadata/Consumers/Roksbox/FindMetadataFileFixture.cs
+++ b/src/NzbDrone.Core.Test/Metadata/Consumers/Roksbox/FindMetadataFileFixture.cs
@@ -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;
diff --git a/src/NzbDrone.Core.Test/Metadata/Consumers/Wdtv/FindMetadataFileFixture.cs b/src/NzbDrone.Core.Test/Metadata/Consumers/Wdtv/FindMetadataFileFixture.cs
index 648f9641d..078744ec8 100644
--- a/src/NzbDrone.Core.Test/Metadata/Consumers/Wdtv/FindMetadataFileFixture.cs
+++ b/src/NzbDrone.Core.Test/Metadata/Consumers/Wdtv/FindMetadataFileFixture.cs
@@ -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;
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 8f4e61c4c..4c8e12548 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -119,6 +119,7 @@
+
diff --git a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs
index 5e6b37902..4b430e171 100644
--- a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs
+++ b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs
@@ -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);
}
}
}
diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs
index e8068584a..a04dba0e5 100644
--- a/src/NzbDrone.Core/Configuration/ConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigService.cs
@@ -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); }
diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs
index 891b0c494..02d6756be 100644
--- a/src/NzbDrone.Core/Configuration/IConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/IConfigService.cs
@@ -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; }
diff --git a/src/NzbDrone.Core/Datastore/Migration/099_extra_and_subtitle_files.cs b/src/NzbDrone.Core/Datastore/Migration/099_extra_and_subtitle_files.cs
new file mode 100644
index 000000000..2d7f54143
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/099_extra_and_subtitle_files.cs
@@ -0,0 +1,56 @@
+using System;
+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();
+
+ // Set Extension using the extension from RelativePath
+ Execute.Sql("UPDATE MetadataFiles SET Extension = substr(RelativePath, instr(RelativePath, '.'));");
+
+ Alter.Table("MetadataFiles").AlterColumn("Extension").AsString().NotNullable();
+ }
+ }
+
+ 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; }
+ }
+}
diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs
index 2f2401fb8..62f6aeb8b 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -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().RegisterModel("QualityDefinitions")
.Ignore(d => d.Weight);
-
Mapper.Entity().RegisterModel("Profiles");
Mapper.Entity().RegisterModel("Logs");
Mapper.Entity().RegisterModel("NamingConfig");
Mapper.Entity().MapResultSet();
Mapper.Entity().RegisterModel("Blacklist");
Mapper.Entity().RegisterModel("MetadataFiles");
+ Mapper.Entity().RegisterModel("SubtitleFiles");
+ Mapper.Entity().RegisterModel("ExtraFiles");
Mapper.Entity().RegisterModel("PendingReleases")
.Ignore(e => e.RemoteEpisode);
diff --git a/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs b/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs
new file mode 100644
index 000000000..c4621d1a3
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs
@@ -0,0 +1,61 @@
+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
+ {
+ private readonly IDiskProvider _diskProvider;
+ private readonly List _existingExtraFileImporters;
+ private readonly List _extraFileManagers;
+ private readonly Logger _logger;
+
+ public ExistingExtraFileService(IDiskProvider diskProvider,
+ List existingExtraFileImporters,
+ List extraFileManagers,
+ Logger logger)
+ {
+ _diskProvider = diskProvider;
+ _existingExtraFileImporters = existingExtraFileImporters.OrderBy(e => e.Order).ToList();
+ _extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList();
+ _logger = logger;
+ }
+
+ public void Handle(SeriesScannedEvent message)
+ {
+ var series = message.Series;
+ var extraFiles = new List();
+
+ if (!_diskProvider.FolderExists(series.Path))
+ {
+ return;
+ }
+
+ _logger.Debug("Looking for existing extra files in {0}", series.Path);
+
+ var filesOnDisk = _diskProvider.GetFiles(series.Path, SearchOption.AllDirectories);
+ var possibleExtraFiles = filesOnDisk.Where(c => !MediaFileExtensions.Extensions.Contains(Path.GetExtension(c).ToLower()) &&
+ !c.StartsWith(Path.Combine(series.Path, "EXTRAS"))).ToList();
+
+ var filteredFiles = possibleExtraFiles;
+ var importedFiles = new List();
+
+ 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);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/ExtraService.cs b/src/NzbDrone.Core/Extras/ExtraService.cs
new file mode 100644
index 000000000..6881ebee6
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/ExtraService.cs
@@ -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,
+ IHandle,
+ IHandle
+ {
+ private readonly IMediaFileService _mediaFileService;
+ private readonly IEpisodeService _episodeService;
+ private readonly IDiskProvider _diskProvider;
+ private readonly IConfigService _configService;
+ private readonly List _extraFileManagers;
+ private readonly Logger _logger;
+
+ public ExtraService(IMediaFileService mediaFileService,
+ IEpisodeService episodeService,
+ IDiskProvider diskProvider,
+ IConfigService configService,
+ List 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)
+ {
+ // TODO: Remove
+ // Not importing files yet, testing that parsing is working properly first
+ return;
+
+ var series = localEpisode.Series;
+
+ foreach (var extraFileManager in _extraFileManagers)
+ {
+ extraFileManager.CreateAfterEpisodeImport(series, episodeFile);
+ }
+
+ 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 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(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
+ }
+
+ return episodeFiles;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Metadata/Files/MetadataFile.cs b/src/NzbDrone.Core/Extras/Files/ExtraFile.cs
similarity index 58%
rename from src/NzbDrone.Core/Metadata/Files/MetadataFile.cs
rename to src/NzbDrone.Core/Extras/Files/ExtraFile.cs
index 96cdd1fe0..036eaec33 100644
--- a/src/NzbDrone.Core/Metadata/Files/MetadataFile.cs
+++ b/src/NzbDrone.Core/Extras/Files/ExtraFile.cs
@@ -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; }
}
}
diff --git a/src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs b/src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs
new file mode 100644
index 000000000..e2a6f31d2
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs
@@ -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 CreateAfterSeriesScan(Series series, List episodeFiles);
+ IEnumerable CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
+ IEnumerable CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder);
+ IEnumerable MoveFilesAfterRename(Series series, List episodeFiles);
+ ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly);
+ }
+
+ public abstract class ExtraFileManager : IManageExtraFiles
+ where TExtraFile : ExtraFile, new()
+
+ {
+ private readonly IConfigService _configService;
+ private readonly IDiskTransferService _diskTransferService;
+ private readonly IExtraFileService _extraFileService;
+
+ public ExtraFileManager(IConfigService configService,
+ IDiskTransferService diskTransferService,
+ IExtraFileService extraFileService)
+ {
+ _configService = configService;
+ _diskTransferService = diskTransferService;
+ _extraFileService = extraFileService;
+ }
+
+ public abstract int Order { get; }
+ public abstract IEnumerable CreateAfterSeriesScan(Series series, List episodeFiles);
+ public abstract IEnumerable CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
+ public abstract IEnumerable CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder);
+ public abstract IEnumerable MoveFilesAfterRename(Series series, List 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)
+ };
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Metadata/Files/MetadataFileRepository.cs b/src/NzbDrone.Core/Extras/Files/ExtraFileRepository.cs
similarity index 56%
rename from src/NzbDrone.Core/Metadata/Files/MetadataFileRepository.cs
rename to src/NzbDrone.Core/Extras/Files/ExtraFileRepository.cs
index e1116d05a..7cb4644c3 100644
--- a/src/NzbDrone.Core/Metadata/Files/MetadataFileRepository.cs
+++ b/src/NzbDrone.Core/Extras/Files/ExtraFileRepository.cs
@@ -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
+ public interface IExtraFileRepository : IBasicRepository where TExtraFile : ExtraFile, new()
{
void DeleteForSeries(int seriesId);
void DeleteForSeason(int seriesId, int seasonNumber);
void DeleteForEpisodeFile(int episodeFileId);
- List GetFilesBySeries(int seriesId);
- List GetFilesBySeason(int seriesId, int seasonNumber);
- List GetFilesByEpisodeFile(int episodeFileId);
- MetadataFile FindByPath(string path);
+ List GetFilesBySeries(int seriesId);
+ List GetFilesBySeason(int seriesId, int seasonNumber);
+ List GetFilesByEpisodeFile(int episodeFileId);
+ TExtraFile FindByPath(string path);
}
- public class MetadataFileRepository : BasicRepository, IMetadataFileRepository
+ public class ExtraFileRepository : BasicRepository, IExtraFileRepository
+ 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 GetFilesBySeries(int seriesId)
+ public List GetFilesBySeries(int seriesId)
{
return Query.Where(c => c.SeriesId == seriesId);
}
- public List GetFilesBySeason(int seriesId, int seasonNumber)
+ public List GetFilesBySeason(int seriesId, int seasonNumber)
{
return Query.Where(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber);
}
- public List GetFilesByEpisodeFile(int episodeFileId)
+ public List 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();
}
diff --git a/src/NzbDrone.Core/Extras/Files/ExtraFileService.cs b/src/NzbDrone.Core/Extras/Files/ExtraFileService.cs
new file mode 100644
index 000000000..d1fafd0d1
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/Files/ExtraFileService.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using NLog;
+using NzbDrone.Common.Disk;
+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
+ where TExtraFile : ExtraFile, new()
+ {
+ List GetFilesBySeries(int seriesId);
+ List GetFilesByEpisodeFile(int episodeFileId);
+ TExtraFile FindByPath(string path);
+ void Upsert(TExtraFile extraFile);
+ void Upsert(List extraFiles);
+ void Delete(int id);
+ void DeleteMany(IEnumerable ids);
+ }
+
+ public abstract class ExtraFileService : IExtraFileService,
+ IHandleAsync,
+ IHandleAsync
+ where TExtraFile : ExtraFile, new()
+ {
+ private readonly IExtraFileRepository _repository;
+ private readonly ISeriesService _seriesService;
+ private readonly IDiskProvider _diskProvider;
+ private readonly Logger _logger;
+
+ public ExtraFileService(IExtraFileRepository repository,
+ ISeriesService seriesService,
+ IDiskProvider diskProvider,
+ Logger logger)
+ {
+ _repository = repository;
+ _seriesService = seriesService;
+ _diskProvider = diskProvider;
+ _logger = logger;
+ }
+
+ public List GetFilesBySeries(int seriesId)
+ {
+ return _repository.GetFilesBySeries(seriesId);
+ }
+
+ public List GetFilesByEpisodeFile(int episodeFileId)
+ {
+ return _repository.GetFilesByEpisodeFile(episodeFileId);
+ }
+
+ public TExtraFile FindByPath(string path)
+ {
+ return _repository.FindByPath(path);
+ }
+
+ public void Upsert(TExtraFile extraFile)
+ {
+ Upsert(new List { extraFile });
+ }
+
+ public void Upsert(List 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 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))
+ {
+ _diskProvider.DeleteFile(path);
+ }
+ }
+
+ _logger.Debug("Deleting Extra from database for episode file: {0}", episodeFile);
+ _repository.DeleteForEpisodeFile(episodeFile.Id);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/IImportExistingExtraFiles.cs b/src/NzbDrone.Core/Extras/IImportExistingExtraFiles.cs
new file mode 100644
index 000000000..ad14b60a5
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/IImportExistingExtraFiles.cs
@@ -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 ProcessFiles(Series series, List filesOnDisk, List importedFiles);
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/ImportExistingExtraFilesBase.cs b/src/NzbDrone.Core/Extras/ImportExistingExtraFilesBase.cs
new file mode 100644
index 000000000..2c94038c6
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/ImportExistingExtraFilesBase.cs
@@ -0,0 +1,53 @@
+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 : IImportExistingExtraFiles
+ where TExtraFile : ExtraFile, new()
+ {
+ private readonly IExtraFileService _extraFileService;
+
+ public ImportExistingExtraFilesBase(IExtraFileService extraFileService)
+ {
+ _extraFileService = extraFileService;
+ }
+
+ public abstract int Order { get; }
+ public abstract IEnumerable ProcessFiles(Series series, List filesOnDisk, List importedFiles);
+
+ public virtual List FilterAndClean(Series series, List filesOnDisk, List importedFiles)
+ {
+ var seriesFiles = _extraFileService.GetFilesBySeries(series.Id);
+
+ Clean(series, filesOnDisk, importedFiles, seriesFiles);
+
+ return Filter(series, filesOnDisk, importedFiles, seriesFiles);
+ }
+
+ private List Filter(Series series, List filesOnDisk, List importedFiles, List seriesFiles)
+ {
+ var filteredFiles = filesOnDisk;
+
+ filteredFiles = filteredFiles.Except(seriesFiles.Select(f => Path.Combine(series.Path, f.RelativePath)).ToList(), PathEqualityComparer.Instance).ToList();
+ return filteredFiles.Except(importedFiles, PathEqualityComparer.Instance).ToList();
+ }
+
+ private void Clean(Series series, List filesOnDisk, List importedFiles, List 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);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Metadata/Consumers/MediaBrowser/MediaBrowserMetadata.cs b/src/NzbDrone.Core/Extras/MetaData/Consumers/MediaBrowser/MediaBrowserMetadata.cs
similarity index 87%
rename from src/NzbDrone.Core/Metadata/Consumers/MediaBrowser/MediaBrowserMetadata.cs
rename to src/NzbDrone.Core/Extras/MetaData/Consumers/MediaBrowser/MediaBrowserMetadata.cs
index 9d17e5504..38d999886 100644
--- a/src/NzbDrone.Core/Metadata/Consumers/MediaBrowser/MediaBrowserMetadata.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Consumers/MediaBrowser/MediaBrowserMetadata.cs
@@ -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
{
- 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 AfterRename(Series series, List existingMetadataFiles, List episodeFiles)
- {
- var updatedMetadataFiles = new List();
-
- return updatedMetadataFiles;
- }
-
public override MetadataFile FindMetadataFile(Series series, string path)
{
var filename = Path.GetFileName(path);
diff --git a/src/NzbDrone.Core/Metadata/Consumers/MediaBrowser/MediaBrowserMetadataSettings.cs b/src/NzbDrone.Core/Extras/MetaData/Consumers/MediaBrowser/MediaBrowserMetadataSettings.cs
similarity index 90%
rename from src/NzbDrone.Core/Metadata/Consumers/MediaBrowser/MediaBrowserMetadataSettings.cs
rename to src/NzbDrone.Core/Extras/MetaData/Consumers/MediaBrowser/MediaBrowserMetadataSettings.cs
index e4201e8ba..c0e5d75bc 100644
--- a/src/NzbDrone.Core/Metadata/Consumers/MediaBrowser/MediaBrowserMetadataSettings.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Consumers/MediaBrowser/MediaBrowserMetadataSettings.cs
@@ -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
{
diff --git a/src/NzbDrone.Core/Metadata/Consumers/Roksbox/RoksboxMetadata.cs b/src/NzbDrone.Core/Extras/MetaData/Consumers/Roksbox/RoksboxMetadata.cs
similarity index 84%
rename from src/NzbDrone.Core/Metadata/Consumers/Roksbox/RoksboxMetadata.cs
rename to src/NzbDrone.Core/Extras/MetaData/Consumers/Roksbox/RoksboxMetadata.cs
index 1859b7efa..072e3428d 100644
--- a/src/NzbDrone.Core/Metadata/Consumers/Roksbox/RoksboxMetadata.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Consumers/Roksbox/RoksboxMetadata.cs
@@ -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
{
@@ -42,49 +43,22 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
}
}
- public override List AfterRename(Series series, List existingMetadataFiles, List episodeFiles)
+ public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
{
- var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
- var updatedMetadataFiles = new List();
+ 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)
diff --git a/src/NzbDrone.Core/Metadata/Consumers/Roksbox/RoksboxMetadataSettings.cs b/src/NzbDrone.Core/Extras/MetaData/Consumers/Roksbox/RoksboxMetadataSettings.cs
similarity index 93%
rename from src/NzbDrone.Core/Metadata/Consumers/Roksbox/RoksboxMetadataSettings.cs
rename to src/NzbDrone.Core/Extras/MetaData/Consumers/Roksbox/RoksboxMetadataSettings.cs
index 08858653a..5ac09ef78 100644
--- a/src/NzbDrone.Core/Metadata/Consumers/Roksbox/RoksboxMetadataSettings.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Consumers/Roksbox/RoksboxMetadataSettings.cs
@@ -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
{
diff --git a/src/NzbDrone.Core/Metadata/Consumers/Wdtv/WdtvMetadata.cs b/src/NzbDrone.Core/Extras/MetaData/Consumers/Wdtv/WdtvMetadata.cs
similarity index 85%
rename from src/NzbDrone.Core/Metadata/Consumers/Wdtv/WdtvMetadata.cs
rename to src/NzbDrone.Core/Extras/MetaData/Consumers/Wdtv/WdtvMetadata.cs
index 260d21455..4964caeb4 100644
--- a/src/NzbDrone.Core/Metadata/Consumers/Wdtv/WdtvMetadata.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Consumers/Wdtv/WdtvMetadata.cs
@@ -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
{
@@ -41,49 +42,23 @@ namespace NzbDrone.Core.Metadata.Consumers.Wdtv
}
}
- public override List AfterRename(Series series, List existingMetadataFiles, List episodeFiles)
+ public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
{
- var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
- var updatedMetadataFiles = new List();
+ 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)
diff --git a/src/NzbDrone.Core/Metadata/Consumers/Wdtv/WdtvMetadataSettings.cs b/src/NzbDrone.Core/Extras/MetaData/Consumers/Wdtv/WdtvMetadataSettings.cs
similarity index 94%
rename from src/NzbDrone.Core/Metadata/Consumers/Wdtv/WdtvMetadataSettings.cs
rename to src/NzbDrone.Core/Extras/MetaData/Consumers/Wdtv/WdtvMetadataSettings.cs
index b8fcfe599..052c6deae 100644
--- a/src/NzbDrone.Core/Metadata/Consumers/Wdtv/WdtvMetadataSettings.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Consumers/Wdtv/WdtvMetadataSettings.cs
@@ -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
{
diff --git a/src/NzbDrone.Core/Metadata/Consumers/Xbmc/XbmcMetadata.cs b/src/NzbDrone.Core/Extras/MetaData/Consumers/Xbmc/XbmcMetadata.cs
similarity index 87%
rename from src/NzbDrone.Core/Metadata/Consumers/Xbmc/XbmcMetadata.cs
rename to src/NzbDrone.Core/Extras/MetaData/Consumers/Xbmc/XbmcMetadata.cs
index 376c11985..5a1a71fd0 100644
--- a/src/NzbDrone.Core/Metadata/Consumers/Xbmc/XbmcMetadata.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Consumers/Xbmc/XbmcMetadata.cs
@@ -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.Xbmc
+namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
{
public class XbmcMetadata : MetadataBase
{
@@ -43,49 +44,22 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
}
}
- public override List AfterRename(Series series, List existingMetadataFiles, List episodeFiles)
+ public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
{
- var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
- var updatedMetadataFiles = new List();
+ 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 +302,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 SeriesImages(Series series)
@@ -407,7 +381,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
}
}
- private string GetEpisodeNfoFilename(string episodeFilePath)
+ private string GetEpisodeMetadataFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "nfo");
}
diff --git a/src/NzbDrone.Core/Metadata/Consumers/Xbmc/XbmcMetadataSettings.cs b/src/NzbDrone.Core/Extras/MetaData/Consumers/Xbmc/XbmcMetadataSettings.cs
similarity index 94%
rename from src/NzbDrone.Core/Metadata/Consumers/Xbmc/XbmcMetadataSettings.cs
rename to src/NzbDrone.Core/Extras/MetaData/Consumers/Xbmc/XbmcMetadataSettings.cs
index 0908ff69f..35ec5ee32 100644
--- a/src/NzbDrone.Core/Metadata/Consumers/Xbmc/XbmcMetadataSettings.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Consumers/Xbmc/XbmcMetadataSettings.cs
@@ -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
{
diff --git a/src/NzbDrone.Core/Extras/MetaData/ExistingMetadataImporter.cs b/src/NzbDrone.Core/Extras/MetaData/ExistingMetadataImporter.cs
new file mode 100644
index 000000000..eb4f8ee31
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/MetaData/ExistingMetadataImporter.cs
@@ -0,0 +1,96 @@
+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.Parser;
+using NzbDrone.Core.Tv;
+
+namespace NzbDrone.Core.Extras.Metadata
+{
+ public class ExistingMetadataImporter : ImportExistingExtraFilesBase
+ {
+ private readonly IExtraFileService _metadataFileService;
+ private readonly IParsingService _parsingService;
+ private readonly Logger _logger;
+ private readonly List _consumers;
+
+ public ExistingMetadataImporter(IExtraFileService metadataFileService,
+ IEnumerable 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 ProcessFiles(Series series, List filesOnDisk, List importedFiles)
+ {
+ _logger.Debug("Looking for existing metadata in {0}", series.Path);
+
+ var metadataFiles = new List();
+ var filteredFiles = FilterAndClean(series, filesOnDisk, importedFiles);
+
+ foreach (var possibleMetadataFile in filteredFiles)
+ {
+ 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 metadataFiles;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Metadata/Files/CleanMetadataService.cs b/src/NzbDrone.Core/Extras/MetaData/Files/CleanMetadataFileService.cs
similarity index 78%
rename from src/NzbDrone.Core/Metadata/Files/CleanMetadataService.cs
rename to src/NzbDrone.Core/Extras/MetaData/Files/CleanMetadataFileService.cs
index 566d5a293..6166ae20b 100644
--- a/src/NzbDrone.Core/Metadata/Files/CleanMetadataService.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Files/CleanMetadataFileService.cs
@@ -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;
diff --git a/src/NzbDrone.Core/Metadata/Files/ImageFileResult.cs b/src/NzbDrone.Core/Extras/MetaData/Files/ImageFileResult.cs
similarity index 83%
rename from src/NzbDrone.Core/Metadata/Files/ImageFileResult.cs
rename to src/NzbDrone.Core/Extras/MetaData/Files/ImageFileResult.cs
index 26a2e41df..be810acaa 100644
--- a/src/NzbDrone.Core/Metadata/Files/ImageFileResult.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Files/ImageFileResult.cs
@@ -1,6 +1,4 @@
-using System;
-
-namespace NzbDrone.Core.Metadata.Files
+namespace NzbDrone.Core.Extras.Metadata.Files
{
public class ImageFileResult
{
diff --git a/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFile.cs b/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFile.cs
new file mode 100644
index 000000000..efc6e6fc4
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFile.cs
@@ -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; }
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileRepository.cs b/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileRepository.cs
new file mode 100644
index 000000000..d1f29ea75
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileRepository.cs
@@ -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
+ {
+ }
+
+ public class MetadataFileRepository : ExtraFileRepository, IMetadataFileRepository
+ {
+ public MetadataFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
+ : base(database, eventAggregator)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Metadata/Files/MetadataFileResult.cs b/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileResult.cs
similarity index 84%
rename from src/NzbDrone.Core/Metadata/Files/MetadataFileResult.cs
rename to src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileResult.cs
index fd0b69316..f76c11d33 100644
--- a/src/NzbDrone.Core/Metadata/Files/MetadataFileResult.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileResult.cs
@@ -1,6 +1,4 @@
-using System;
-
-namespace NzbDrone.Core.Metadata.Files
+namespace NzbDrone.Core.Extras.Metadata.Files
{
public class MetadataFileResult
{
diff --git a/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileService.cs b/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileService.cs
new file mode 100644
index 000000000..2bb3d603d
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/MetaData/Files/MetadataFileService.cs
@@ -0,0 +1,19 @@
+using NLog;
+using NzbDrone.Common.Disk;
+using NzbDrone.Core.Extras.Files;
+using NzbDrone.Core.Tv;
+
+namespace NzbDrone.Core.Extras.Metadata.Files
+{
+ public interface IMetadataFileService : IExtraFileService
+ {
+ }
+
+ public class MetadataFileService : ExtraFileService, IMetadataFileService
+ {
+ public MetadataFileService(IExtraFileRepository repository, ISeriesService seriesService, IDiskProvider diskProvider, Logger logger)
+ : base(repository, seriesService, diskProvider, logger)
+ {
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Metadata/IMetadata.cs b/src/NzbDrone.Core/Extras/MetaData/IMetadata.cs
similarity index 75%
rename from src/NzbDrone.Core/Metadata/IMetadata.cs
rename to src/NzbDrone.Core/Extras/MetaData/IMetadata.cs
index f9c1feae3..b631425e6 100644
--- a/src/NzbDrone.Core/Metadata/IMetadata.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/IMetadata.cs
@@ -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 AfterRename(Series series, List existingMetadataFiles, List 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 SeriesImages(Series series);
List SeasonImages(Series series, Season season);
List EpisodeImages(Series series, EpisodeFile episodeFile);
-
}
}
diff --git a/src/NzbDrone.Core/Metadata/MetadataBase.cs b/src/NzbDrone.Core/Extras/MetaData/MetadataBase.cs
similarity index 75%
rename from src/NzbDrone.Core/Metadata/MetadataBase.cs
rename to src/NzbDrone.Core/Extras/MetaData/MetadataBase.cs
index ce6b7ad87..39a4162ce 100644
--- a/src/NzbDrone.Core/Metadata/MetadataBase.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/MetadataBase.cs
@@ -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 : IMetadata where TSettings : IProviderConfig, new()
{
@@ -43,7 +45,15 @@ namespace NzbDrone.Core.Metadata
return new ValidationResult();
}
- public abstract List AfterRename(Series series, List existingMetadataFiles, List 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);
diff --git a/src/NzbDrone.Core/Metadata/MetadataDefinition.cs b/src/NzbDrone.Core/Extras/MetaData/MetadataDefinition.cs
similarity index 73%
rename from src/NzbDrone.Core/Metadata/MetadataDefinition.cs
rename to src/NzbDrone.Core/Extras/MetaData/MetadataDefinition.cs
index 0784037dc..4ee8a2d4b 100644
--- a/src/NzbDrone.Core/Metadata/MetadataDefinition.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/MetadataDefinition.cs
@@ -1,6 +1,6 @@
using NzbDrone.Core.ThingiProvider;
-namespace NzbDrone.Core.Metadata
+namespace NzbDrone.Core.Extras.Metadata
{
public class MetadataDefinition : ProviderDefinition
{
diff --git a/src/NzbDrone.Core/Metadata/MetadataFactory.cs b/src/NzbDrone.Core/Extras/MetaData/MetadataFactory.cs
similarity index 97%
rename from src/NzbDrone.Core/Metadata/MetadataFactory.cs
rename to src/NzbDrone.Core/Extras/MetaData/MetadataFactory.cs
index ae7056779..5fe8db3f5 100644
--- a/src/NzbDrone.Core/Metadata/MetadataFactory.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/MetadataFactory.cs
@@ -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
{
diff --git a/src/NzbDrone.Core/Metadata/MetadataRepository.cs b/src/NzbDrone.Core/Extras/MetaData/MetadataRepository.cs
similarity index 92%
rename from src/NzbDrone.Core/Metadata/MetadataRepository.cs
rename to src/NzbDrone.Core/Extras/MetaData/MetadataRepository.cs
index e6749799e..349da708e 100644
--- a/src/NzbDrone.Core/Metadata/MetadataRepository.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/MetadataRepository.cs
@@ -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
{
-
}
public class MetadataRepository : ProviderRepository, IMetadataRepository
diff --git a/src/NzbDrone.Core/Metadata/MetadataService.cs b/src/NzbDrone.Core/Extras/MetaData/MetadataService.cs
similarity index 74%
rename from src/NzbDrone.Core/Metadata/MetadataService.cs
rename to src/NzbDrone.Core/Extras/MetaData/MetadataService.cs
index 281392d68..10c04d719 100644
--- a/src/NzbDrone.Core/Metadata/MetadataService.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/MetadataService.cs
@@ -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,
- IHandle,
- IHandle,
- IHandle
+ public class MetadataService : ExtraFileManager
{
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 CreateAfterSeriesScan(Series series, List 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();
}
- var seriesMetadataFiles = _metadataFileService.GetFilesBySeries(message.Series.Id);
- var episodeFiles = GetEpisodeFiles(message.Series.Id);
+ var files = new List();
foreach (var consumer in _metadataFactory.Enabled())
{
- var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
- var files = new List();
+ 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 CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{
- foreach (var consumer in _metadataFactory.Enabled())
- {
- var files = new List();
-
- files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List()));
- files.AddRange(ProcessEpisodeImages(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List()));
-
- _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();
foreach (var consumer in _metadataFactory.Enabled())
{
- var files = new List();
- var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
- if (message.SeriesFolder.IsNotNullOrWhiteSpace())
+ files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, new List()));
+ files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, new List()));
+ }
+
+ _metadataFileService.Upsert(files);
+
+ return files;
+ }
+
+ public override IEnumerable CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder)
+ {
+ var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
+
+ if (seriesFolder.IsNullOrWhiteSpace() && seasonFolder.IsNullOrWhiteSpace())
+ {
+ return new List();
+ }
+
+ var files = new List();
+
+ 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 MoveFilesAfterRename(Series series, List episodeFiles)
{
- var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
- var episodeFiles = GetEpisodeFiles(message.Series.Id);
+ var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
+ var movedFiles = new List();
- 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 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(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
- }
-
- return episodeFiles;
+ return null;
}
private List GetMetadataFilesForConsumer(IMetadata consumer, List seriesMetadata)
@@ -226,7 +246,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,6 +259,7 @@ namespace NzbDrone.Core.Metadata
new MetadataFile
{
SeriesId = series.Id,
+ SeasonNumber = episodeFile.SeasonNumber,
EpisodeFileId = episodeFile.Id,
Consumer = consumer.GetType().Name,
Type = MetadataType.EpisodeMetadata,
@@ -347,7 +368,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,6 +381,7 @@ namespace NzbDrone.Core.Metadata
new MetadataFile
{
SeriesId = series.Id,
+ SeasonNumber = episodeFile.SeasonNumber,
EpisodeFileId = episodeFile.Id,
Consumer = consumer.GetType().Name,
Type = MetadataType.EpisodeImage,
diff --git a/src/NzbDrone.Core/Metadata/MetadataType.cs b/src/NzbDrone.Core/Extras/MetaData/MetadataType.cs
similarity index 82%
rename from src/NzbDrone.Core/Metadata/MetadataType.cs
rename to src/NzbDrone.Core/Extras/MetaData/MetadataType.cs
index 07a054c39..849bc31dd 100644
--- a/src/NzbDrone.Core/Metadata/MetadataType.cs
+++ b/src/NzbDrone.Core/Extras/MetaData/MetadataType.cs
@@ -1,4 +1,4 @@
-namespace NzbDrone.Core.Metadata
+namespace NzbDrone.Core.Extras.Metadata
{
public enum MetadataType
{
diff --git a/src/NzbDrone.Core/Extras/Others/ExistingOtherExtraImporter.cs b/src/NzbDrone.Core/Extras/Others/ExistingOtherExtraImporter.cs
new file mode 100644
index 000000000..d744c2259
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/Others/ExistingOtherExtraImporter.cs
@@ -0,0 +1,83 @@
+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
+ {
+ private readonly IExtraFileService _otherExtraFileService;
+ private readonly IParsingService _parsingService;
+ private readonly Logger _logger;
+
+ public ExistingOtherExtraImporter(IExtraFileService otherExtraFileService,
+ IParsingService parsingService,
+ Logger logger)
+ : base(otherExtraFileService)
+ {
+ _otherExtraFileService = otherExtraFileService;
+ _parsingService = parsingService;
+ _logger = logger;
+ }
+
+ public override int Order
+ {
+ get
+ {
+ return 2;
+ }
+ }
+
+ public override IEnumerable ProcessFiles(Series series, List filesOnDisk, List importedFiles)
+ {
+ _logger.Debug("Looking for existing extra files in {0}", series.Path);
+
+ var extraFiles = new List();
+ var filteredFiles = FilterAndClean(series, filesOnDisk, importedFiles);
+
+ foreach (var possibleExtraFile in filteredFiles)
+ {
+ 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 extraFiles;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/Others/OtherExtraFile.cs b/src/NzbDrone.Core/Extras/Others/OtherExtraFile.cs
new file mode 100644
index 000000000..12187cfc1
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/Others/OtherExtraFile.cs
@@ -0,0 +1,8 @@
+using NzbDrone.Core.Extras.Files;
+
+namespace NzbDrone.Core.Extras.Others
+{
+ public class OtherExtraFile : ExtraFile
+ {
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/Others/OtherExtraFileRepository.cs b/src/NzbDrone.Core/Extras/Others/OtherExtraFileRepository.cs
new file mode 100644
index 000000000..3f33a3eb8
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/Others/OtherExtraFileRepository.cs
@@ -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
+ {
+ }
+
+ public class OtherExtraFileRepository : ExtraFileRepository, IOtherExtraFileRepository
+ {
+ public OtherExtraFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
+ : base(database, eventAggregator)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Extras/Others/OtherExtraFileService.cs b/src/NzbDrone.Core/Extras/Others/OtherExtraFileService.cs
new file mode 100644
index 000000000..3d4f65c61
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/Others/OtherExtraFileService.cs
@@ -0,0 +1,19 @@
+using NLog;
+using NzbDrone.Common.Disk;
+using NzbDrone.Core.Extras.Files;
+using NzbDrone.Core.Tv;
+
+namespace NzbDrone.Core.Extras.Others
+{
+ public interface IOtherExtraFileService : IExtraFileService
+ {
+ }
+
+ public class OtherExtraFileService : ExtraFileService, IOtherExtraFileService
+ {
+ public OtherExtraFileService(IExtraFileRepository repository, ISeriesService seriesService, IDiskProvider diskProvider, Logger logger)
+ : base(repository, seriesService, diskProvider, logger)
+ {
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/Others/OtherExtraService.cs b/src/NzbDrone.Core/Extras/Others/OtherExtraService.cs
new file mode 100644
index 000000000..d1eb1f0cc
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/Others/OtherExtraService.cs
@@ -0,0 +1,112 @@
+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.Extras.Files;
+using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.Tv;
+
+namespace NzbDrone.Core.Extras.Others
+{
+ public class OtherExtraService : ExtraFileManager
+ {
+ private readonly IOtherExtraFileService _otherExtraFileService;
+ private readonly IDiskProvider _diskProvider;
+ private readonly Logger _logger;
+
+ public OtherExtraService(IConfigService configService,
+ IDiskTransferService diskTransferService,
+ IOtherExtraFileService otherExtraFileService,
+ IDiskProvider diskProvider,
+ Logger logger)
+ : base(configService, diskTransferService, otherExtraFileService)
+ {
+ _otherExtraFileService = otherExtraFileService;
+ _diskProvider = diskProvider;
+ _logger = logger;
+ }
+
+ public override int Order
+ {
+ get
+ {
+ return 2;
+ }
+ }
+
+ public override IEnumerable CreateAfterSeriesScan(Series series, List episodeFiles)
+ {
+ return Enumerable.Empty();
+ }
+
+ public override IEnumerable CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
+ {
+ return Enumerable.Empty();
+ }
+
+ public override IEnumerable CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder)
+ {
+ return Enumerable.Empty();
+ }
+
+ public override IEnumerable MoveFilesAfterRename(Series series, List episodeFiles)
+ {
+ // TODO: Remove
+ // We don't want to move files after rename yet.
+
+ return Enumerable.Empty();
+
+ var extraFiles = _otherExtraFileService.GetFilesBySeries(series.Id);
+ var movedFiles = new List();
+
+ foreach (var episodeFile in episodeFiles)
+ {
+ var extraFilesForEpisodeFile = extraFiles.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
+
+ foreach (var extraFile in extraFilesForEpisodeFile)
+ {
+ var existingFileName = Path.Combine(series.Path, extraFile.RelativePath);
+ var extension = Path.GetExtension(existingFileName).TrimStart('.');
+ var newFileName = Path.ChangeExtension(Path.Combine(series.Path, episodeFile.RelativePath), extension);
+
+ if (newFileName.PathNotEquals(existingFileName))
+ {
+ try
+ {
+ _diskProvider.MoveFile(existingFileName, newFileName);
+ extraFile.RelativePath = series.Path.GetRelativePath(newFileName);
+ movedFiles.Add(extraFile);
+ }
+ catch (Exception ex)
+ {
+ _logger.Warn(ex, "Unable to move extra file: {0}", existingFileName);
+ }
+ }
+ }
+ }
+
+ _otherExtraFileService.Upsert(movedFiles);
+
+ return movedFiles;
+ }
+
+ public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
+ {
+ // If the extension is .nfo we need to change it to .nfo-orig
+ if (Path.GetExtension(path).Equals(".nfo"))
+ {
+ extension += "-orig";
+ }
+
+ var extraFile = ImportFile(series, episodeFile, path, extension, readOnly);
+
+ _otherExtraFileService.Upsert(extraFile);
+
+ return extraFile;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs b/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs
new file mode 100644
index 000000000..11e1b7742
--- /dev/null
+++ b/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs
@@ -0,0 +1,89 @@
+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.Subtitles
+{
+ public class ExistingSubtitleImporter : ImportExistingExtraFilesBase
+ {
+ private readonly IExtraFileService _subtitleFileService;
+ private readonly IParsingService _parsingService;
+ private readonly Logger _logger;
+
+ public ExistingSubtitleImporter(IExtraFileService subtitleFileService,
+ IParsingService parsingService,
+ Logger logger)
+ : base (subtitleFileService)
+ {
+ _subtitleFileService = subtitleFileService;
+ _parsingService = parsingService;
+ _logger = logger;
+ }
+
+ public override int Order
+ {
+ get
+ {
+ return 1;
+ }
+ }
+
+ public override IEnumerable ProcessFiles(Series series, List filesOnDisk, List importedFiles)
+ {
+ _logger.Debug("Looking for existing subtitle files in {0}", series.Path);
+
+ var subtitleFiles = new List