You've already forked Sonarr
							
							
				mirror of
				https://github.com/Sonarr/Sonarr.git
				synced 2025-10-31 00:07:55 +02:00 
			
		
		
		
	
							
								
								
									
										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> | ||||
| @@ -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; } | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using System; | ||||
| using NzbDrone.Core.Metadata; | ||||
| using NzbDrone.Core.Extras.Metadata; | ||||
| 
 | ||||
| namespace NzbDrone.Api.Metadata | ||||
| { | ||||
|   | ||||
| @@ -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" /> | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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<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" | ||||
|                 }); | ||||
|             }); | ||||
| 
 | ||||
|             var items = db.Query<MetadataFile99>("SELECT * FROM MetadataFiles"); | ||||
| 
 | ||||
|             items.Should().HaveCount(1); | ||||
|             items.First().Extension.Should().Be(".jpg"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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)) | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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,7 @@ | ||||
|     <Compile Include="Datastore\DatabaseRelationshipFixture.cs" /> | ||||
|     <Compile Include="Datastore\MappingExtentionFixture.cs" /> | ||||
|     <Compile Include="Datastore\MarrDataLazyLoadingFixture.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" /> | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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,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; } | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|   | ||||
							
								
								
									
										61
									
								
								src/NzbDrone.Core/Extras/ExistingExtraFileService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/NzbDrone.Core/Extras/ExistingExtraFileService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<SeriesScannedEvent> | ||||
|     { | ||||
|         private readonly IDiskProvider _diskProvider; | ||||
|         private readonly List<IImportExistingExtraFiles> _existingExtraFileImporters; | ||||
|         private readonly List<IManageExtraFiles> _extraFileManagers; | ||||
|         private readonly Logger _logger; | ||||
| 
 | ||||
|         public ExistingExtraFileService(IDiskProvider diskProvider, | ||||
|                                         List<IImportExistingExtraFiles> existingExtraFileImporters, | ||||
|                                         List<IManageExtraFiles> 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<ExtraFile>(); | ||||
| 
 | ||||
|             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<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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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) | ||||
|         { | ||||
|             // 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<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(); | ||||
|         } | ||||
							
								
								
									
										118
									
								
								src/NzbDrone.Core/Extras/Files/ExtraFileService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/NzbDrone.Core/Extras/Files/ExtraFileService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<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 Logger _logger; | ||||
| 
 | ||||
|         public ExtraFileService(IExtraFileRepository<TExtraFile> repository, | ||||
|                                 ISeriesService seriesService, | ||||
|                                 IDiskProvider diskProvider, | ||||
|                                 Logger logger) | ||||
|         { | ||||
|             _repository = repository; | ||||
|             _seriesService = seriesService; | ||||
|             _diskProvider = diskProvider; | ||||
|             _logger = logger; | ||||
|         } | ||||
| 
 | ||||
|         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)) | ||||
|                 { | ||||
|                     _diskProvider.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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										53
									
								
								src/NzbDrone.Core/Extras/ImportExistingExtraFilesBase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/NzbDrone.Core/Extras/ImportExistingExtraFilesBase.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<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 List<string> 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 List<string> Filter(Series series, List<string> filesOnDisk, List<string> importedFiles, List<TExtraFile> 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<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) | ||||
| @@ -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,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<XbmcMetadataSettings> | ||||
|     { | ||||
| @@ -43,49 +44,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 +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<ImageFileResult> 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"); | ||||
|         } | ||||
| @@ -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> | ||||
|     { | ||||
| @@ -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<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 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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,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<MetadataFile> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public class MetadataFileService : ExtraFileService<MetadataFile>, IMetadataFileService | ||||
|     { | ||||
|         public MetadataFileService(IExtraFileRepository<MetadataFile> repository, ISeriesService seriesService, IDiskProvider diskProvider, Logger logger) | ||||
|             : base(repository, seriesService, diskProvider, logger) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
| @@ -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, | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace NzbDrone.Core.Metadata | ||||
| namespace NzbDrone.Core.Extras.Metadata | ||||
| { | ||||
|     public enum MetadataType | ||||
|     { | ||||
| @@ -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<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 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/NzbDrone.Core/Extras/Others/OtherExtraFileService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/NzbDrone.Core/Extras/Others/OtherExtraFileService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<OtherExtraFile> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public class OtherExtraFileService : ExtraFileService<OtherExtraFile>, IOtherExtraFileService | ||||
|     { | ||||
|         public OtherExtraFileService(IExtraFileRepository<OtherExtraFile> repository, ISeriesService seriesService, IDiskProvider diskProvider, Logger logger) | ||||
|             : base(repository, seriesService, diskProvider, logger) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										112
									
								
								src/NzbDrone.Core/Extras/Others/OtherExtraService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/NzbDrone.Core/Extras/Others/OtherExtraService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<OtherExtraFile> | ||||
|     { | ||||
|         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<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles) | ||||
|         { | ||||
|             return Enumerable.Empty<ExtraFile>(); | ||||
|         } | ||||
| 
 | ||||
|         public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile) | ||||
|         { | ||||
|             return Enumerable.Empty<ExtraFile>(); | ||||
|         } | ||||
| 
 | ||||
|         public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder) | ||||
|         { | ||||
|             return Enumerable.Empty<ExtraFile>(); | ||||
|         } | ||||
| 
 | ||||
|         public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles) | ||||
|         { | ||||
|             // TODO: Remove | ||||
|             // We don't want to move files after rename yet. | ||||
| 
 | ||||
|             return Enumerable.Empty<ExtraFile>(); | ||||
| 
 | ||||
|             var extraFiles = _otherExtraFileService.GetFilesBySeries(series.Id); | ||||
|             var movedFiles = new List<OtherExtraFile>(); | ||||
| 
 | ||||
|             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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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<SubtitleFile> | ||||
|     { | ||||
|         private readonly IExtraFileService<SubtitleFile> _subtitleFileService; | ||||
|         private readonly IParsingService _parsingService; | ||||
|         private readonly Logger _logger; | ||||
| 
 | ||||
|         public ExistingSubtitleImporter(IExtraFileService<SubtitleFile> subtitleFileService, | ||||
|                                         IParsingService parsingService, | ||||
|                                         Logger logger) | ||||
|             : base (subtitleFileService) | ||||
|         { | ||||
|             _subtitleFileService = subtitleFileService; | ||||
|             _parsingService = parsingService; | ||||
|             _logger = logger; | ||||
|         } | ||||
| 
 | ||||
|         public override int Order | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 return 1; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles) | ||||
|         { | ||||
|             _logger.Debug("Looking for existing subtitle files in {0}", series.Path); | ||||
| 
 | ||||
|             var subtitleFiles = new List<SubtitleFile>(); | ||||
|             var filteredFiles = FilterAndClean(series, filesOnDisk, importedFiles); | ||||
| 
 | ||||
|             foreach (var possibleSubtitleFile in filteredFiles) | ||||
|             { | ||||
|                 var extension = Path.GetExtension(possibleSubtitleFile); | ||||
| 
 | ||||
|                 if (SubtitleFileExtensions.Extensions.Contains(extension)) | ||||
|                 { | ||||
|                     var localEpisode = _parsingService.GetLocalEpisode(possibleSubtitleFile, series); | ||||
| 
 | ||||
|                     if (localEpisode == null) | ||||
|                     { | ||||
|                         _logger.Debug("Unable to parse subtitle file: {0}", possibleSubtitleFile); | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     if (localEpisode.Episodes.Empty()) | ||||
|                     { | ||||
|                         _logger.Debug("Cannot find related episodes for: {0}", possibleSubtitleFile); | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1) | ||||
|                     { | ||||
|                         _logger.Debug("Subtitle file: {0} does not match existing files.", possibleSubtitleFile); | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     var subtitleFile = new SubtitleFile | ||||
|                                        { | ||||
|                                            SeriesId = series.Id, | ||||
|                                            SeasonNumber = localEpisode.SeasonNumber, | ||||
|                                            EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId, | ||||
|                                            RelativePath = series.Path.GetRelativePath(possibleSubtitleFile), | ||||
|                                            Language = LanguageParser.ParseSubtitleLanguage(possibleSubtitleFile), | ||||
|                                            Extension = extension | ||||
|                                        }; | ||||
| 
 | ||||
|                     subtitleFiles.Add(subtitleFile); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             _logger.Info("Found {0} existing subtitle files", subtitleFiles.Count); | ||||
|             _subtitleFileService.Upsert(subtitleFiles); | ||||
| 
 | ||||
|             return subtitleFiles; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								src/NzbDrone.Core/Extras/Subtitles/ImportedSubtitleFiles.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/NzbDrone.Core/Extras/Subtitles/ImportedSubtitleFiles.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| using System.Collections.Generic; | ||||
| using NzbDrone.Core.Extras.Files; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Extras.Subtitles | ||||
| { | ||||
|     public class ImportedSubtitleFiles | ||||
|     { | ||||
|         public List<string> SourceFiles { get; set; } | ||||
|         public List<ExtraFile> SubtitleFiles { get; set; } | ||||
| 
 | ||||
|         public ImportedSubtitleFiles() | ||||
|         { | ||||
|             SourceFiles = new List<string>(); | ||||
|             SubtitleFiles = new List<ExtraFile>(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| using NzbDrone.Core.Extras.Files; | ||||
| using NzbDrone.Core.Parser; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Extras.Subtitles | ||||
| { | ||||
|     public class SubtitleFile : ExtraFile | ||||
|     { | ||||
|         public Language Language { get; set; } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										35
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleFileExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleFileExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Extras.Subtitles | ||||
| { | ||||
|     public static class SubtitleFileExtensions | ||||
|     { | ||||
|         private static HashSet<string> _fileExtensions; | ||||
| 
 | ||||
|         static SubtitleFileExtensions() | ||||
|         { | ||||
|             _fileExtensions = new HashSet<string> | ||||
|                               { | ||||
|                                   ".aqt", | ||||
|                                   ".ass", | ||||
|                                   ".idx", | ||||
|                                   ".jss", | ||||
|                                   ".psb", | ||||
|                                   ".rt", | ||||
|                                   ".smi", | ||||
|                                   ".srt", | ||||
|                                   ".ssa", | ||||
|                                   ".sub", | ||||
|                                   ".txt", | ||||
|                                   ".utf", | ||||
|                                   ".utf8", | ||||
|                                   ".utf-8" | ||||
|                               }; | ||||
|         } | ||||
| 
 | ||||
|         public static HashSet<string> Extensions | ||||
|         { | ||||
|             get { return _fileExtensions; } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleFileRepository.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleFileRepository.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.Subtitles | ||||
| { | ||||
|     public interface ISubtitleFileRepository : IExtraFileRepository<SubtitleFile> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public class SubtitleFileRepository : ExtraFileRepository<SubtitleFile>, ISubtitleFileRepository | ||||
|     { | ||||
|         public SubtitleFileRepository(IMainDatabase database, IEventAggregator eventAggregator) | ||||
|             : base(database, eventAggregator) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleFileService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleFileService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| using NLog; | ||||
| using NzbDrone.Common.Disk; | ||||
| using NzbDrone.Core.Extras.Files; | ||||
| using NzbDrone.Core.Tv; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Extras.Subtitles | ||||
| { | ||||
|     public interface ISubtitleFileService : IExtraFileService<SubtitleFile> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public class SubtitleFileService : ExtraFileService<SubtitleFile>, ISubtitleFileService | ||||
|     { | ||||
|         public SubtitleFileService(IExtraFileRepository<SubtitleFile> repository, ISeriesService seriesService, IDiskProvider diskProvider, Logger logger) | ||||
|             : base(repository, seriesService, diskProvider, logger) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										152
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| using System; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| 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.Parser; | ||||
| using NzbDrone.Core.Tv; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Extras.Subtitles | ||||
| { | ||||
|     public class SubtitleService : ExtraFileManager<SubtitleFile> | ||||
|     { | ||||
|         private readonly ISubtitleFileService _subtitleFileService; | ||||
|         private readonly IDiskProvider _diskProvider; | ||||
|         private readonly Logger _logger; | ||||
| 
 | ||||
|         public SubtitleService(IConfigService configService, | ||||
|                                IDiskTransferService diskTransferService, | ||||
|                                ISubtitleFileService subtitleFileService, | ||||
|                                IDiskProvider diskProvider, | ||||
|                                Logger logger) | ||||
|             : base(configService, diskTransferService, subtitleFileService) | ||||
|         { | ||||
|             _subtitleFileService = subtitleFileService; | ||||
|             _diskProvider = diskProvider; | ||||
|             _logger = logger; | ||||
|         } | ||||
| 
 | ||||
|         public override int Order | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 return 1; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public override IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles) | ||||
|         { | ||||
|             return Enumerable.Empty<SubtitleFile>(); | ||||
|         } | ||||
| 
 | ||||
|         public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile) | ||||
|         { | ||||
|             return Enumerable.Empty<SubtitleFile>(); | ||||
|         } | ||||
| 
 | ||||
|         public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder) | ||||
|         { | ||||
|             return Enumerable.Empty<SubtitleFile>(); | ||||
|         } | ||||
| 
 | ||||
|         public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles) | ||||
|         { | ||||
|             // TODO: Remove | ||||
|             // We don't want to move files after rename yet. | ||||
| 
 | ||||
|             return Enumerable.Empty<ExtraFile>(); | ||||
| 
 | ||||
|             var subtitleFiles = _subtitleFileService.GetFilesBySeries(series.Id); | ||||
| 
 | ||||
|             var movedFiles = new List<SubtitleFile>(); | ||||
| 
 | ||||
|             foreach (var episodeFile in episodeFiles) | ||||
|             { | ||||
|                 var groupedExtraFilesForEpisodeFile = subtitleFiles.Where(m => m.EpisodeFileId == episodeFile.Id) | ||||
|                                                             .GroupBy(s => s.Language + s.Extension).ToList(); | ||||
| 
 | ||||
|                 foreach (var group in groupedExtraFilesForEpisodeFile) | ||||
|                 { | ||||
|                     var groupCount = group.Count(); | ||||
|                     var copy = 1; | ||||
| 
 | ||||
|                     if (groupCount > 1) | ||||
|                     { | ||||
|                         _logger.Warn("Multiple subtitle files found with the same language and extension for {0}", Path.Combine(series.Path, episodeFile.RelativePath)); | ||||
|                     } | ||||
| 
 | ||||
|                     foreach (var extraFile in group) | ||||
|                     { | ||||
|                         var existingFileName = Path.Combine(series.Path, extraFile.RelativePath); | ||||
|                         var extension = GetExtension(extraFile, existingFileName, copy, groupCount > 1); | ||||
|                         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 subtitle file: {0}", existingFileName); | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                         copy++; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             _subtitleFileService.Upsert(movedFiles); | ||||
| 
 | ||||
|             return movedFiles; | ||||
|         } | ||||
| 
 | ||||
|         public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly) | ||||
|         { | ||||
|             if (SubtitleFileExtensions.Extensions.Contains(Path.GetExtension(path))) | ||||
|             { | ||||
|                 var subtitleFile = ImportFile(series, episodeFile, path, extension, readOnly); | ||||
|                 subtitleFile.Language = LanguageParser.ParseSubtitleLanguage(path); | ||||
| 
 | ||||
|                 _subtitleFileService.Upsert(subtitleFile); | ||||
| 
 | ||||
|                 return subtitleFile; | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         private string GetExtension(SubtitleFile extraFile, string existingFileName, int copy, bool multipleCopies = false) | ||||
|         { | ||||
|             var fileExtension = Path.GetExtension(existingFileName); | ||||
|             var extensionBuilder = new StringBuilder(); | ||||
| 
 | ||||
|             if (multipleCopies) | ||||
|             { | ||||
|                 extensionBuilder.Append(copy); | ||||
|                 extensionBuilder.Append("."); | ||||
|             } | ||||
| 
 | ||||
|             if (extraFile.Language != Language.Unknown) | ||||
|             { | ||||
|                 extensionBuilder.Append(IsoLanguages.Get(extraFile.Language).TwoLetterCode); | ||||
|                 extensionBuilder.Append("."); | ||||
|             } | ||||
| 
 | ||||
|             extensionBuilder.Append(fileExtension.TrimStart('.')); | ||||
| 
 | ||||
|             return extensionBuilder.ToString(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -4,23 +4,27 @@ using System.Linq; | ||||
| using NLog; | ||||
| using NzbDrone.Common.Disk; | ||||
| using NzbDrone.Core.Configuration; | ||||
| using NzbDrone.Core.Metadata.Files; | ||||
| using NzbDrone.Core.Extras.Metadata.Files; | ||||
| using NzbDrone.Core.Tv; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Housekeeping.Housekeepers | ||||
| { | ||||
|     public class DeleteBadMediaCovers : IHousekeepingTask | ||||
|     { | ||||
|         private readonly IMetadataFileService _metaFileService; | ||||
|         private readonly ISeriesService _seriesService; | ||||
|         private readonly IMetadataFileService _metadataFileService; | ||||
|         private readonly IDiskProvider _diskProvider; | ||||
|         private readonly IConfigService _configService; | ||||
|         private readonly Logger _logger; | ||||
| 
 | ||||
|         public DeleteBadMediaCovers(ISeriesService seriesService, IMetadataFileService metadataFileService, IDiskProvider diskProvider, IConfigService configService, Logger logger) | ||||
|         public DeleteBadMediaCovers(IMetadataFileService metaFileService, | ||||
|                                     ISeriesService seriesService, | ||||
|                                     IDiskProvider diskProvider, | ||||
|                                     IConfigService configService, | ||||
|                                     Logger logger) | ||||
|         { | ||||
|             _metaFileService = metaFileService; | ||||
|             _seriesService = seriesService; | ||||
|             _metadataFileService = metadataFileService; | ||||
|             _diskProvider = diskProvider; | ||||
|             _configService = configService; | ||||
|             _logger = logger; | ||||
| @@ -34,7 +38,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers | ||||
| 
 | ||||
|             foreach (var show in series) | ||||
|             { | ||||
|                 var images = _metadataFileService.GetFilesBySeries(show.Id) | ||||
|                 var images = _metaFileService.GetFilesBySeries(show.Id) | ||||
|                     .Where(c => c.LastUpdated > new DateTime(2014, 12, 27) && c.RelativePath.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase)); | ||||
| 
 | ||||
|                 foreach (var image in images) | ||||
| @@ -61,7 +65,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers | ||||
| 
 | ||||
|         private void DeleteMetadata(int id, string path) | ||||
|         { | ||||
|             _metadataFileService.Delete(id); | ||||
|             _metaFileService.Delete(id); | ||||
|             _diskProvider.DeleteFile(path); | ||||
|         } | ||||
| 
 | ||||
|   | ||||
| @@ -11,6 +11,7 @@ using NzbDrone.Core.Parser; | ||||
| using NzbDrone.Core.Parser.Model; | ||||
| using NzbDrone.Core.Qualities; | ||||
| using NzbDrone.Core.Download; | ||||
| using NzbDrone.Core.Extras; | ||||
| 
 | ||||
| 
 | ||||
| namespace NzbDrone.Core.MediaFiles.EpisodeImport | ||||
| @@ -24,18 +25,21 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport | ||||
|     { | ||||
|         private readonly IUpgradeMediaFiles _episodeFileUpgrader; | ||||
|         private readonly IMediaFileService _mediaFileService; | ||||
|         private readonly IExtraService _extraService; | ||||
|         private readonly IDiskProvider _diskProvider; | ||||
|         private readonly IEventAggregator _eventAggregator; | ||||
|         private readonly Logger _logger; | ||||
| 
 | ||||
|         public ImportApprovedEpisodes(IUpgradeMediaFiles episodeFileUpgrader, | ||||
|                                       IMediaFileService mediaFileService, | ||||
|                                       IExtraService extraService, | ||||
|                                       IDiskProvider diskProvider, | ||||
|                                       IEventAggregator eventAggregator, | ||||
|                                       Logger logger) | ||||
|         { | ||||
|             _episodeFileUpgrader = episodeFileUpgrader; | ||||
|             _mediaFileService = mediaFileService; | ||||
|             _extraService = extraService; | ||||
|             _diskProvider = diskProvider; | ||||
|             _eventAggregator = eventAggregator; | ||||
|             _logger = logger; | ||||
| @@ -98,9 +102,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport | ||||
|                     _mediaFileService.Add(episodeFile); | ||||
|                     importResults.Add(new ImportResult(importDecision)); | ||||
| 
 | ||||
|                     if (newDownload) | ||||
|                     { | ||||
|                         _extraService.ImportExtraFiles(localEpisode, episodeFile, downloadClientItem != null && downloadClientItem.IsReadOnly); | ||||
|                     } | ||||
| 
 | ||||
|                     if (downloadClientItem != null) | ||||
|                     { | ||||
|                         _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId)); | ||||
|                         _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly)); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| using System; | ||||
| using NzbDrone.Common.Messaging; | ||||
| using NzbDrone.Common.Messaging; | ||||
| using NzbDrone.Core.Parser.Model; | ||||
| 
 | ||||
| namespace NzbDrone.Core.MediaFiles.Events | ||||
| @@ -11,6 +10,7 @@ namespace NzbDrone.Core.MediaFiles.Events | ||||
|         public bool NewDownload { get; private set; } | ||||
|         public string DownloadClient { get; private set; } | ||||
|         public string DownloadId { get; private set; } | ||||
|         public bool IsReadOnly { get; set; } | ||||
| 
 | ||||
|         public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload) | ||||
|         { | ||||
| @@ -19,13 +19,14 @@ namespace NzbDrone.Core.MediaFiles.Events | ||||
|             NewDownload = newDownload; | ||||
|         } | ||||
| 
 | ||||
|         public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadId) | ||||
|         public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadId, bool isReadOnly) | ||||
|         { | ||||
|             EpisodeInfo = episodeInfo; | ||||
|             ImportedEpisode = importedEpisode; | ||||
|             NewDownload = newDownload; | ||||
|             DownloadClient = downloadClient; | ||||
|             DownloadId = downloadId; | ||||
|             IsReadOnly = isReadOnly; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,92 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using NLog; | ||||
| using NzbDrone.Common.Disk; | ||||
| using NzbDrone.Common.Extensions; | ||||
| using NzbDrone.Core.MediaFiles; | ||||
| using NzbDrone.Core.MediaFiles.Events; | ||||
| using NzbDrone.Core.Messaging.Events; | ||||
| using NzbDrone.Core.Metadata.Files; | ||||
| using NzbDrone.Core.Parser; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Metadata | ||||
| { | ||||
|     public class ExistingMetadataService : IHandle<SeriesScannedEvent> | ||||
|     { | ||||
|         private readonly IDiskProvider _diskProvider; | ||||
|         private readonly IMetadataFileService _metadataFileService; | ||||
|         private readonly IParsingService _parsingService; | ||||
|         private readonly Logger _logger; | ||||
|         private readonly List<IMetadata> _consumers; | ||||
| 
 | ||||
|         public ExistingMetadataService(IDiskProvider diskProvider, | ||||
|                                        IEnumerable<IMetadata> consumers, | ||||
|                                        IMetadataFileService metadataFileService, | ||||
|                                        IParsingService parsingService, | ||||
|                                        Logger logger) | ||||
|         { | ||||
|             _diskProvider = diskProvider; | ||||
|             _metadataFileService = metadataFileService; | ||||
|             _parsingService = parsingService; | ||||
|             _logger = logger; | ||||
|             _consumers = consumers.ToList(); | ||||
|         } | ||||
| 
 | ||||
|         public void Handle(SeriesScannedEvent message) | ||||
|         { | ||||
|             if (!_diskProvider.FolderExists(message.Series.Path)) return; | ||||
| 
 | ||||
|             _logger.Debug("Looking for existing metadata in {0}", message.Series.Path); | ||||
| 
 | ||||
|             var filesOnDisk = _diskProvider.GetFiles(message.Series.Path, SearchOption.AllDirectories); | ||||
| 
 | ||||
|             var possibleMetadataFiles = filesOnDisk.Where(c => !MediaFileExtensions.Extensions.Contains(Path.GetExtension(c).ToLower()) && | ||||
|                                                          !c.StartsWith(Path.Combine(message.Series.Path, "EXTRAS"))).ToList(); | ||||
| 
 | ||||
|             var filteredFiles = _metadataFileService.FilterExistingFiles(possibleMetadataFiles, message.Series); | ||||
| 
 | ||||
|             var metadataFiles = new List<MetadataFile>(); | ||||
| 
 | ||||
|             foreach (var possibleMetadataFile in filteredFiles) | ||||
|             { | ||||
|                 foreach (var consumer in _consumers) | ||||
|                 { | ||||
|                     var metadata = consumer.FindMetadataFile(message.Series, possibleMetadataFile); | ||||
| 
 | ||||
|                     if (metadata == null) continue; | ||||
| 
 | ||||
|                     if (metadata.Type == MetadataType.EpisodeImage || | ||||
|                         metadata.Type == MetadataType.EpisodeMetadata) | ||||
|                     { | ||||
|                         var localEpisode = _parsingService.GetLocalEpisode(possibleMetadataFile, message.Series); | ||||
| 
 | ||||
|                         if (localEpisode == null) | ||||
|                         { | ||||
|                             _logger.Debug("Unable to parse meta data file: {0}", possibleMetadataFile); | ||||
|                             break; | ||||
|                         } | ||||
| 
 | ||||
|                         if (localEpisode.Episodes.Empty()) | ||||
|                         { | ||||
|                             _logger.Debug("Cannot find related episodes for: {0}", possibleMetadataFile); | ||||
|                             break; | ||||
|                         } | ||||
| 
 | ||||
|                         if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1) | ||||
|                         { | ||||
|                             _logger.Debug("Metadata file: {0} does not match existing files.", possibleMetadataFile); | ||||
|                             break; | ||||
|                         } | ||||
| 
 | ||||
|                         metadata.EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId; | ||||
|                     } | ||||
| 
 | ||||
|                     metadataFiles.Add(metadata); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             _metadataFileService.Upsert(metadataFiles); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,113 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using NLog; | ||||
| using NzbDrone.Common; | ||||
| 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.Metadata.Files | ||||
| { | ||||
|     public interface IMetadataFileService | ||||
|     { | ||||
|         List<MetadataFile> GetFilesBySeries(int seriesId); | ||||
|         List<MetadataFile> GetFilesByEpisodeFile(int episodeFileId); | ||||
|         MetadataFile FindByPath(string path); | ||||
|         List<string> FilterExistingFiles(List<string> files, Series series); | ||||
|         void Upsert(List<MetadataFile> metadataFiles); | ||||
|         void Delete(int id); | ||||
|     } | ||||
| 
 | ||||
|     public class MetadataFileService : IMetadataFileService, | ||||
|                                               IHandleAsync<SeriesDeletedEvent>, | ||||
|                                               IHandleAsync<EpisodeFileDeletedEvent>, | ||||
|                                               IHandle<MetadataFilesUpdated> | ||||
|     { | ||||
|         private readonly IMetadataFileRepository _repository; | ||||
|         private readonly ISeriesService _seriesService; | ||||
|         private readonly IDiskProvider _diskProvider; | ||||
|         private readonly Logger _logger; | ||||
| 
 | ||||
|         public MetadataFileService(IMetadataFileRepository repository, | ||||
|                                           ISeriesService seriesService, | ||||
|                                           IDiskProvider diskProvider, | ||||
|                                           Logger logger) | ||||
|         { | ||||
|             _repository = repository; | ||||
|             _seriesService = seriesService; | ||||
|             _diskProvider = diskProvider; | ||||
|             _logger = logger; | ||||
|         } | ||||
| 
 | ||||
|         public List<MetadataFile> GetFilesBySeries(int seriesId) | ||||
|         { | ||||
|             return _repository.GetFilesBySeries(seriesId); | ||||
|         } | ||||
| 
 | ||||
|         public List<MetadataFile> GetFilesByEpisodeFile(int episodeFileId) | ||||
|         { | ||||
|             return _repository.GetFilesByEpisodeFile(episodeFileId); | ||||
|         } | ||||
| 
 | ||||
|         public MetadataFile FindByPath(string path) | ||||
|         { | ||||
|             return _repository.FindByPath(path); | ||||
|         } | ||||
| 
 | ||||
|         public List<string> FilterExistingFiles(List<string> files, Series series) | ||||
|         { | ||||
|             var seriesFiles = GetFilesBySeries(series.Id).Select(f => Path.Combine(series.Path, f.RelativePath)).ToList(); | ||||
| 
 | ||||
|             if (!seriesFiles.Any()) return files; | ||||
| 
 | ||||
|             return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList(); | ||||
|         } | ||||
| 
 | ||||
|         public void Upsert(List<MetadataFile> metadataFiles) | ||||
|         { | ||||
|             metadataFiles.ForEach(m => m.LastUpdated = DateTime.UtcNow); | ||||
| 
 | ||||
|             _repository.InsertMany(metadataFiles.Where(m => m.Id == 0).ToList()); | ||||
|             _repository.UpdateMany(metadataFiles.Where(m => m.Id > 0).ToList()); | ||||
|         } | ||||
| 
 | ||||
|         public void Delete(int id) | ||||
|         { | ||||
|             _repository.Delete(id); | ||||
|         } | ||||
| 
 | ||||
|         public void HandleAsync(SeriesDeletedEvent message) | ||||
|         { | ||||
|             _logger.Debug("Deleting Metadata 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 metadata in _repository.GetFilesByEpisodeFile(episodeFile.Id)) | ||||
|             { | ||||
|                 var path = Path.Combine(series.Path, metadata.RelativePath); | ||||
| 
 | ||||
|                 if (_diskProvider.FileExists(path)) | ||||
|                 { | ||||
|                     _diskProvider.DeleteFile(path); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             _logger.Debug("Deleting Metadata from database for episode file: {0}", episodeFile); | ||||
|             _repository.DeleteForEpisodeFile(episodeFile.Id); | ||||
|         } | ||||
| 
 | ||||
|         public void Handle(MetadataFilesUpdated message) | ||||
|         { | ||||
|             Upsert(message.MetadataFiles); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using NzbDrone.Common.Messaging; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Metadata.Files | ||||
| { | ||||
|     public class MetadataFilesUpdated : IEvent | ||||
|     { | ||||
|         public List<MetadataFile> MetadataFiles { get; set; } | ||||
| 
 | ||||
|         public MetadataFilesUpdated(List<MetadataFile> metadataFiles) | ||||
|         { | ||||
|             MetadataFiles = metadataFiles; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -275,6 +275,7 @@ | ||||
|     <Compile Include="Datastore\Migration\093_naming_config_replace_characters.cs" /> | ||||
|     <Compile Include="Datastore\Migration\092_add_unverifiedscenenumbering.cs" /> | ||||
|     <Compile Include="Datastore\Migration\100_add_scene_season_number.cs" /> | ||||
|     <Compile Include="Datastore\Migration\099_extra_and_subtitle_files.cs" /> | ||||
|     <Compile Include="Datastore\Migration\094_add_tvmazeid.cs" /> | ||||
|     <Compile Include="Datastore\Migration\098_remove_titans_of_tv.cs"> | ||||
|       <SubType>Code</SubType> | ||||
| @@ -487,6 +488,29 @@ | ||||
|     <Compile Include="Exceptions\SeriesNotFoundException.cs" /> | ||||
|     <Compile Include="Exceptions\ReleaseDownloadException.cs" /> | ||||
|     <Compile Include="Exceptions\StatusCodeToExceptions.cs" /> | ||||
|     <Compile Include="Extras\ExistingExtraFileService.cs" /> | ||||
|     <Compile Include="Extras\Files\ExtraFile.cs" /> | ||||
|     <Compile Include="Extras\Files\ExtraFileManager.cs" /> | ||||
|     <Compile Include="Extras\Files\ExtraFileService.cs" /> | ||||
|     <Compile Include="Extras\Files\ExtraFileRepository.cs" /> | ||||
|     <Compile Include="Extras\ExtraService.cs" /> | ||||
|     <Compile Include="Extras\IImportExistingExtraFiles.cs" /> | ||||
|     <Compile Include="Extras\ImportExistingExtraFilesBase.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Files\MetadataFile.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Files\MetadataFileRepository.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Files\MetadataFileService.cs" /> | ||||
|     <Compile Include="Extras\Others\ExistingOtherExtraImporter.cs" /> | ||||
|     <Compile Include="Extras\Others\OtherExtraFileRepository.cs" /> | ||||
|     <Compile Include="Extras\Others\OtherExtraFileService.cs" /> | ||||
|     <Compile Include="Extras\Others\OtherExtraFile.cs" /> | ||||
|     <Compile Include="Extras\Others\OtherExtraService.cs" /> | ||||
|     <Compile Include="Extras\Subtitles\ExistingSubtitleImporter.cs" /> | ||||
|     <Compile Include="Extras\Subtitles\SubtitleFileRepository.cs" /> | ||||
|     <Compile Include="Extras\Subtitles\SubtitleFileService.cs" /> | ||||
|     <Compile Include="Extras\Subtitles\SubtitleFile.cs" /> | ||||
|     <Compile Include="Extras\Subtitles\SubtitleFileExtensions.cs" /> | ||||
|     <Compile Include="Extras\Subtitles\ImportedSubtitleFiles.cs" /> | ||||
|     <Compile Include="Extras\Subtitles\SubtitleService.cs" /> | ||||
|     <Compile Include="Fluent.cs" /> | ||||
|     <Compile Include="HealthCheck\CheckHealthCommand.cs" /> | ||||
|     <Compile Include="HealthCheck\Checks\AppDataLocationCheck.cs" /> | ||||
| @@ -757,29 +781,25 @@ | ||||
|     <Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" /> | ||||
|     <Compile Include="MetadataSource\SearchSeriesComparer.cs" /> | ||||
|     <Compile Include="MetadataSource\SkyHook\SkyHookException.cs" /> | ||||
|     <Compile Include="Metadata\Consumers\MediaBrowser\MediaBrowserMetadata.cs" /> | ||||
|     <Compile Include="Metadata\Consumers\MediaBrowser\MediaBrowserMetadataSettings.cs" /> | ||||
|     <Compile Include="Metadata\Consumers\Roksbox\RoksboxMetadata.cs" /> | ||||
|     <Compile Include="Metadata\Consumers\Roksbox\RoksboxMetadataSettings.cs" /> | ||||
|     <Compile Include="Metadata\Consumers\Wdtv\WdtvMetadata.cs" /> | ||||
|     <Compile Include="Metadata\Consumers\Wdtv\WdtvMetadataSettings.cs" /> | ||||
|     <Compile Include="Metadata\Consumers\Xbmc\XbmcMetadata.cs" /> | ||||
|     <Compile Include="Metadata\Consumers\Xbmc\XbmcMetadataSettings.cs" /> | ||||
|     <Compile Include="Metadata\ExistingMetadataService.cs" /> | ||||
|     <Compile Include="Metadata\Files\CleanMetadataService.cs" /> | ||||
|     <Compile Include="Metadata\Files\ImageFileResult.cs" /> | ||||
|     <Compile Include="Metadata\Files\MetadataFile.cs" /> | ||||
|     <Compile Include="Metadata\Files\MetadataFileRepository.cs" /> | ||||
|     <Compile Include="Metadata\Files\MetadataFileResult.cs" /> | ||||
|     <Compile Include="Metadata\Files\MetadataFileService.cs" /> | ||||
|     <Compile Include="Metadata\Files\MetadataFilesUpdated.cs" /> | ||||
|     <Compile Include="Metadata\IMetadata.cs" /> | ||||
|     <Compile Include="Metadata\MetadataBase.cs" /> | ||||
|     <Compile Include="Metadata\MetadataDefinition.cs" /> | ||||
|     <Compile Include="Metadata\MetadataFactory.cs" /> | ||||
|     <Compile Include="Metadata\MetadataRepository.cs" /> | ||||
|     <Compile Include="Metadata\MetadataService.cs" /> | ||||
|     <Compile Include="Metadata\MetadataType.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Consumers\MediaBrowser\MediaBrowserMetadata.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Consumers\MediaBrowser\MediaBrowserMetadataSettings.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Consumers\Roksbox\RoksboxMetadata.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Consumers\Roksbox\RoksboxMetadataSettings.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Consumers\Wdtv\WdtvMetadata.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Consumers\Wdtv\WdtvMetadataSettings.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Consumers\Xbmc\XbmcMetadata.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Consumers\Xbmc\XbmcMetadataSettings.cs" /> | ||||
|     <Compile Include="Extras\Metadata\ExistingMetadataImporter.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Files\CleanMetadataFileService.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Files\ImageFileResult.cs" /> | ||||
|     <Compile Include="Extras\Metadata\Files\MetadataFileResult.cs" /> | ||||
|     <Compile Include="Extras\Metadata\IMetadata.cs" /> | ||||
|     <Compile Include="Extras\Metadata\MetadataBase.cs" /> | ||||
|     <Compile Include="Extras\Metadata\MetadataDefinition.cs" /> | ||||
|     <Compile Include="Extras\Metadata\MetadataFactory.cs" /> | ||||
|     <Compile Include="Extras\Metadata\MetadataRepository.cs" /> | ||||
|     <Compile Include="Extras\Metadata\MetadataService.cs" /> | ||||
|     <Compile Include="Extras\Metadata\MetadataType.cs" /> | ||||
|     <Compile Include="MetadataSource\IProvideSeriesInfo.cs" /> | ||||
|     <Compile Include="MetadataSource\ISearchForNewSeries.cs" /> | ||||
|     <Compile Include="Notifications\Join\JoinAuthException.cs" /> | ||||
| @@ -827,6 +847,9 @@ | ||||
|     <Compile Include="Notifications\Twitter\Twitter.cs" /> | ||||
|     <Compile Include="Notifications\Twitter\TwitterService.cs" /> | ||||
|     <Compile Include="Notifications\Twitter\TwitterSettings.cs" /> | ||||
|     <Compile Include="Parser\IsoLanguage.cs" /> | ||||
|     <Compile Include="Parser\IsoLanguages.cs" /> | ||||
|     <Compile Include="Parser\LanguageParser.cs" /> | ||||
|     <Compile Include="Profiles\Delay\DelayProfile.cs" /> | ||||
|     <Compile Include="Profiles\Delay\DelayProfileService.cs" /> | ||||
|     <Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" /> | ||||
|   | ||||
							
								
								
									
										16
									
								
								src/NzbDrone.Core/Parser/IsoLanguage.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/NzbDrone.Core/Parser/IsoLanguage.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| namespace NzbDrone.Core.Parser | ||||
| { | ||||
|     public class IsoLanguage | ||||
|     { | ||||
|         public string TwoLetterCode { get; set; } | ||||
|         public string ThreeLetterCode { get; set; } | ||||
|         public Language Language { get; set; } | ||||
| 
 | ||||
|         public IsoLanguage(string twoLetterCode, string threeLetterCode, Language language) | ||||
|         { | ||||
|             TwoLetterCode = twoLetterCode; | ||||
|             ThreeLetterCode = threeLetterCode; | ||||
|             Language = language; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/NzbDrone.Core/Parser/IsoLanguages.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/NzbDrone.Core/Parser/IsoLanguages.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Parser | ||||
| { | ||||
|     public static class IsoLanguages | ||||
|     { | ||||
|         private static readonly HashSet<IsoLanguage> All = new HashSet<IsoLanguage> | ||||
|                                                            { | ||||
|                                                                new IsoLanguage("en", "eng", Language.English), | ||||
|                                                                new IsoLanguage("fr", "fra", Language.French), | ||||
|                                                                new IsoLanguage("es", "spa", Language.Spanish), | ||||
|                                                                new IsoLanguage("de", "deu", Language.German), | ||||
|                                                                new IsoLanguage("it", "ita", Language.Italian), | ||||
|                                                                new IsoLanguage("da", "dan", Language.Danish), | ||||
|                                                                new IsoLanguage("nl", "nld", Language.Dutch), | ||||
|                                                                new IsoLanguage("ja", "jpn", Language.Japanese), | ||||
| //                                                             new IsoLanguage("", "", Language.Cantonese), | ||||
| //                                                             new IsoLanguage("", "", Language.Mandarin), | ||||
|                                                                new IsoLanguage("ru", "rus", Language.Russian), | ||||
|                                                                new IsoLanguage("pl", "pol", Language.Polish), | ||||
|                                                                new IsoLanguage("vi", "vie", Language.Vietnamese), | ||||
|                                                                new IsoLanguage("sv", "swe", Language.Swedish), | ||||
|                                                                new IsoLanguage("no", "nor", Language.Norwegian), | ||||
|                                                                new IsoLanguage("fi", "fin", Language.Finnish), | ||||
|                                                                new IsoLanguage("tr", "tur", Language.Turkish), | ||||
|                                                                new IsoLanguage("pt", "por", Language.Portuguese), | ||||
| //                                                             new IsoLanguage("nl", "nld", Language.Flemish), | ||||
|                                                                new IsoLanguage("el", "ell", Language.Greek), | ||||
|                                                                new IsoLanguage("ko", "kor", Language.Korean), | ||||
|                                                                new IsoLanguage("hu", "hun", Language.Hungarian) | ||||
|                                                            }; | ||||
| 
 | ||||
|         public static IsoLanguage Find(string isoCode) | ||||
|         { | ||||
|             if (isoCode.Length == 2) | ||||
|             { | ||||
|                 //Lookup ISO639-1 code | ||||
|                 return All.SingleOrDefault(l => l.TwoLetterCode == isoCode); | ||||
|             } | ||||
|             else if (isoCode.Length == 3) | ||||
|             { | ||||
|                 //Lookup ISO639-2T code | ||||
|                 return All.SingleOrDefault(l => l.ThreeLetterCode == isoCode); | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         public static IsoLanguage Get(Language language) | ||||
|         { | ||||
|             return All.SingleOrDefault(l => l.Language == language); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										137
									
								
								src/NzbDrone.Core/Parser/LanguageParser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/NzbDrone.Core/Parser/LanguageParser.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text.RegularExpressions; | ||||
| using NLog; | ||||
| using NzbDrone.Common.Instrumentation; | ||||
| 
 | ||||
| namespace NzbDrone.Core.Parser | ||||
| { | ||||
|     public static class LanguageParser | ||||
|     { | ||||
|         private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(LanguageParser)); | ||||
| 
 | ||||
|         private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>\b(?:ita|italian)\b)|(?<german>german\b|videomann)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)(?:FR|VOSTFR)(?:\W|_))|(?<russian>\brus\b)|(?<dutch>nl\W?subs?)|(?<hungarian>\b(?:HUNDUB|HUN)\b)", | ||||
|                                                                 RegexOptions.IgnoreCase | RegexOptions.Compiled); | ||||
| 
 | ||||
|         private static readonly Regex SubtitleLanguageRegex = new Regex(".+?[-_. ](?<iso_code>[a-z]{2,3})$", RegexOptions.Compiled | RegexOptions.IgnoreCase); | ||||
| 
 | ||||
|         public static Language ParseLanguage(string title) | ||||
|         { | ||||
|             var lowerTitle = title.ToLower(); | ||||
| 
 | ||||
|             if (lowerTitle.Contains("english")) | ||||
|                 return Language.English; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("french")) | ||||
|                 return Language.French; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("spanish")) | ||||
|                 return Language.Spanish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("danish")) | ||||
|                 return Language.Danish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("dutch")) | ||||
|                 return Language.Dutch; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("japanese")) | ||||
|                 return Language.Japanese; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("cantonese")) | ||||
|                 return Language.Cantonese; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("mandarin")) | ||||
|                 return Language.Mandarin; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("korean")) | ||||
|                 return Language.Korean; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("russian")) | ||||
|                 return Language.Russian; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("polish")) | ||||
|                 return Language.Polish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("vietnamese")) | ||||
|                 return Language.Vietnamese; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("swedish")) | ||||
|                 return Language.Swedish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("norwegian")) | ||||
|                 return Language.Norwegian; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("nordic")) | ||||
|                 return Language.Norwegian; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("finnish")) | ||||
|                 return Language.Finnish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("turkish")) | ||||
|                 return Language.Turkish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("portuguese")) | ||||
|                 return Language.Portuguese; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("hungarian")) | ||||
|                 return Language.Hungarian; | ||||
| 
 | ||||
|             var match = LanguageRegex.Match(title); | ||||
| 
 | ||||
|             if (match.Groups["italian"].Captures.Cast<Capture>().Any()) | ||||
|                 return Language.Italian; | ||||
| 
 | ||||
|             if (match.Groups["german"].Captures.Cast<Capture>().Any()) | ||||
|                 return Language.German; | ||||
| 
 | ||||
|             if (match.Groups["flemish"].Captures.Cast<Capture>().Any()) | ||||
|                 return Language.Flemish; | ||||
| 
 | ||||
|             if (match.Groups["greek"].Captures.Cast<Capture>().Any()) | ||||
|                 return Language.Greek; | ||||
| 
 | ||||
|             if (match.Groups["french"].Success) | ||||
|                 return Language.French; | ||||
| 
 | ||||
|             if (match.Groups["russian"].Success) | ||||
|                 return Language.Russian; | ||||
| 
 | ||||
|             if (match.Groups["dutch"].Success) | ||||
|                 return Language.Dutch; | ||||
| 
 | ||||
|             if (match.Groups["hungarian"].Success) | ||||
|                 return Language.Hungarian; | ||||
| 
 | ||||
|             return Language.English; | ||||
|         } | ||||
| 
 | ||||
|         public static Language ParseSubtitleLanguage(string fileName) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 Logger.Debug("Parsing language from subtitlte file: {0}", fileName); | ||||
| 
 | ||||
|                 var simpleFilename = Path.GetFileNameWithoutExtension(fileName); | ||||
|                 var languageMatch = SubtitleLanguageRegex.Match(simpleFilename); | ||||
| 
 | ||||
|                 if (languageMatch.Success) | ||||
|                 { | ||||
|                     var isoCode = languageMatch.Groups["iso_code"].Value; | ||||
|                     var isoLanguage = IsoLanguages.Find(isoCode); | ||||
| 
 | ||||
|                     return isoLanguage?.Language ?? Language.Unknown; | ||||
|                 } | ||||
| 
 | ||||
|                 Logger.Debug("Unable to parse langauge from subtitle file: {0}", fileName); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 Logger.Debug("Failed parsing langauge from subtitle file: {0}", fileName); | ||||
|             } | ||||
|              | ||||
|             return Language.Unknown; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -248,9 +248,6 @@ namespace NzbDrone.Core.Parser | ||||
|         private static readonly Regex AnimeReleaseGroupRegex = new Regex(@"^(?:\[(?<subgroup>(?!\s).+?(?<!\s))\](?:_|-|\s|\.)?)", | ||||
|                                                                 RegexOptions.IgnoreCase | RegexOptions.Compiled); | ||||
| 
 | ||||
|         private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>\b(?:ita|italian)\b)|(?<german>german\b|videomann)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)(?:FR|VOSTFR)(?:\W|_))|(?<russian>\brus\b)|(?<dutch>nl\W?subs?)|(?<hungarian>\b(?:HUNDUB|HUN)\b)", | ||||
|                                                                 RegexOptions.IgnoreCase | RegexOptions.Compiled); | ||||
| 
 | ||||
|         private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_)?(?<year>\d{4})", | ||||
|                                                                 RegexOptions.IgnoreCase | RegexOptions.Compiled); | ||||
| 
 | ||||
| @@ -358,7 +355,7 @@ namespace NzbDrone.Core.Parser | ||||
|                                     result.Special = true; | ||||
|                                 } | ||||
| 
 | ||||
|                                 result.Language = ParseLanguage(title); | ||||
|                                 result.Language = LanguageParser.ParseLanguage(title); | ||||
|                                 Logger.Debug("Language parsed: {0}", result.Language); | ||||
| 
 | ||||
|                                 result.Quality = QualityParser.ParseQuality(title); | ||||
| @@ -493,97 +490,7 @@ namespace NzbDrone.Core.Parser | ||||
| 
 | ||||
|             return title; | ||||
|         } | ||||
| 
 | ||||
|         public static Language ParseLanguage(string title) | ||||
|         { | ||||
|             var lowerTitle = title.ToLower(); | ||||
| 
 | ||||
|             if (lowerTitle.Contains("english")) | ||||
|                 return Language.English; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("french")) | ||||
|                 return Language.French; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("spanish")) | ||||
|                 return Language.Spanish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("danish")) | ||||
|                 return Language.Danish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("dutch")) | ||||
|                 return Language.Dutch; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("japanese")) | ||||
|                 return Language.Japanese; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("cantonese")) | ||||
|                 return Language.Cantonese; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("mandarin")) | ||||
|                 return Language.Mandarin; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("korean")) | ||||
|                 return Language.Korean; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("russian")) | ||||
|                 return Language.Russian; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("polish")) | ||||
|                 return Language.Polish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("vietnamese")) | ||||
|                 return Language.Vietnamese; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("swedish")) | ||||
|                 return Language.Swedish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("norwegian")) | ||||
|                 return Language.Norwegian; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("nordic")) | ||||
|                 return Language.Norwegian; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("finnish")) | ||||
|                 return Language.Finnish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("turkish")) | ||||
|                 return Language.Turkish; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("portuguese")) | ||||
|                 return Language.Portuguese; | ||||
| 
 | ||||
|             if (lowerTitle.Contains("hungarian")) | ||||
|                 return Language.Hungarian; | ||||
|                  | ||||
|             var match = LanguageRegex.Match(title); | ||||
| 
 | ||||
|             if (match.Groups["italian"].Captures.Cast<Capture>().Any()) | ||||
|                 return Language.Italian; | ||||
| 
 | ||||
|             if (match.Groups["german"].Captures.Cast<Capture>().Any()) | ||||
|                 return Language.German; | ||||
| 
 | ||||
|             if (match.Groups["flemish"].Captures.Cast<Capture>().Any()) | ||||
|                 return Language.Flemish; | ||||
| 
 | ||||
|             if (match.Groups["greek"].Captures.Cast<Capture>().Any()) | ||||
|                 return Language.Greek; | ||||
| 
 | ||||
|             if (match.Groups["french"].Success) | ||||
|                 return Language.French; | ||||
| 
 | ||||
|             if (match.Groups["russian"].Success) | ||||
|                 return Language.Russian; | ||||
| 
 | ||||
|             if (match.Groups["dutch"].Success) | ||||
|                 return Language.Dutch; | ||||
| 
 | ||||
|             if (match.Groups["hungarian"].Success) | ||||
|                 return Language.Hungarian; | ||||
| 
 | ||||
|             return Language.English; | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|         private static SeriesTitleInfo GetSeriesTitleInfo(string title) | ||||
|         { | ||||
|             var seriesTitleInfo = new SeriesTitleInfo(); | ||||
|   | ||||
| @@ -233,7 +233,7 @@ namespace NzbDrone.Core.Parser | ||||
|                 info.FullSeason = false; | ||||
|                 info.Quality = QualityParser.ParseQuality(title); | ||||
|                 info.ReleaseGroup = Parser.ParseReleaseGroup(title); | ||||
|                 info.Language = Parser.ParseLanguage(title); | ||||
|                 info.Language = LanguageParser.ParseLanguage(title); | ||||
|                 info.Special = true; | ||||
| 
 | ||||
|                 _logger.Debug("Found special episode {0} for title '{1}'", info, title); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user