mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-10-29 23:12:39 +01:00
parent
816cf608fc
commit
2e96c4e798
6
.idea/encodings.xml
Normal file
6
.idea/encodings.xml
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,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);
|
||||
|
Loading…
Reference in New Issue
Block a user