parent
816cf608fc
commit
2e96c4e798
|
@ -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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="JavaScriptLibraryMappings">
|
<component name="JavaScriptLibraryMappings">
|
||||||
<file url="file://$PROJECT_DIR$" libraries="{Sonarr node_modules}" />
|
|
||||||
<includedPredefinedLibrary name="ECMAScript 6" />
|
<includedPredefinedLibrary name="ECMAScript 6" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using FluentValidation;
|
||||||
using FluentValidation;
|
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Validation.Paths;
|
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.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
|
||||||
|
@ -21,6 +20,7 @@ namespace NzbDrone.Api.Config
|
||||||
|
|
||||||
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
||||||
public bool CopyUsingHardlinks { get; set; }
|
public bool CopyUsingHardlinks { get; set; }
|
||||||
|
public string ExtraFileExtensions { get; set; }
|
||||||
public bool EnableMediaInfo { get; set; }
|
public bool EnableMediaInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using NzbDrone.Core.Metadata;
|
using NzbDrone.Core.Extras.Metadata;
|
||||||
|
|
||||||
namespace NzbDrone.Api.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\EnvironmentProviderTest.cs" />
|
||||||
<Compile Include="EnvironmentTests\StartupArgumentsFixture.cs" />
|
<Compile Include="EnvironmentTests\StartupArgumentsFixture.cs" />
|
||||||
<Compile Include="ExtensionTests\FromOctalStringFixture.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="ExtensionTests\Int64ExtensionFixture.cs" />
|
||||||
<Compile Include="Http\HttpClientFixture.cs" />
|
<Compile Include="Http\HttpClientFixture.cs" />
|
||||||
<Compile Include="Http\HttpRequestBuilderFixture.cs" />
|
<Compile Include="Http\HttpRequestBuilderFixture.cs" />
|
||||||
|
|
|
@ -13,6 +13,44 @@ namespace NzbDrone.Common.Extensions
|
||||||
return source.Where(element => knownKeys.Add(keySelector(element)));
|
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)
|
public static void AddIfNotNull<TSource>(this List<TSource> source, TSource item)
|
||||||
{
|
{
|
||||||
if (item == null)
|
if (item == null)
|
||||||
|
|
|
@ -37,6 +37,11 @@ namespace NzbDrone.Common.Extensions
|
||||||
return info.FullName.TrimEnd('/').Trim('\\', ' ');
|
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)
|
public static bool PathEquals(this string firstPath, string secondPath, StringComparison? comparison = null)
|
||||||
{
|
{
|
||||||
if (!comparison.HasValue)
|
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 NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Configuration;
|
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.Housekeeping.Housekeepers;
|
||||||
using NzbDrone.Core.Metadata;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
@ -19,7 +21,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class DeleteBadMediaCoversFixture : CoreTest<DeleteBadMediaCovers>
|
public class DeleteBadMediaCoversFixture : CoreTest<DeleteBadMediaCovers>
|
||||||
{
|
{
|
||||||
private List<MetadataFile> _metaData;
|
private List<MetadataFile> _metadata;
|
||||||
private List<Series> _series;
|
private List<Series> _series;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
|
@ -31,7 +33,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
.Build().ToList();
|
.Build().ToList();
|
||||||
|
|
||||||
|
|
||||||
_metaData = Builder<MetadataFile>.CreateListOfSize(1)
|
_metadata = Builder<MetadataFile>.CreateListOfSize(1)
|
||||||
.Build().ToList();
|
.Build().ToList();
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
Mocker.GetMock<ISeriesService>()
|
||||||
|
@ -41,7 +43,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
|
|
||||||
Mocker.GetMock<IMetadataFileService>()
|
Mocker.GetMock<IMetadataFileService>()
|
||||||
.Setup(c => c.GetFilesBySeries(_series.First().Id))
|
.Setup(c => c.GetFilesBySeries(_series.First().Id))
|
||||||
.Returns(_metaData);
|
.Returns(_metadata);
|
||||||
|
|
||||||
|
|
||||||
Mocker.GetMock<IConfigService>().SetupGet(c => c.CleanupMetadataImages).Returns(true);
|
Mocker.GetMock<IConfigService>().SetupGet(c => c.CleanupMetadataImages).Returns(true);
|
||||||
|
@ -51,8 +53,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_process_non_image_files()
|
public void should_not_process_non_image_files()
|
||||||
{
|
{
|
||||||
_metaData.First().RelativePath = "season\\file.xml".AsOsAgnostic();
|
_metadata.First().RelativePath = "season\\file.xml".AsOsAgnostic();
|
||||||
_metaData.First().Type = MetadataType.EpisodeMetadata;
|
_metadata.First().Type = MetadataType.EpisodeMetadata;
|
||||||
|
|
||||||
Subject.Clean();
|
Subject.Clean();
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_process_images_before_tvdb_switch()
|
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();
|
Subject.Clean();
|
||||||
|
|
||||||
|
@ -89,7 +91,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
[Test]
|
[Test]
|
||||||
public void should_set_clean_flag_to_false()
|
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();
|
Subject.Clean();
|
||||||
|
|
||||||
|
@ -102,9 +104,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
|
|
||||||
var imagePath = "C:\\TV\\Season\\image.jpg".AsOsAgnostic();
|
var imagePath = "C:\\TV\\Season\\image.jpg".AsOsAgnostic();
|
||||||
_metaData.First().LastUpdated = new DateTime(2014, 12, 29);
|
_metadata.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||||
_metaData.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
_metadata.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||||
_metaData.First().Type = MetadataType.SeriesImage;
|
_metadata.First().Type = MetadataType.SeriesImage;
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.OpenReadStream(imagePath))
|
.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<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();
|
var imagePath = "C:\\TV\\Season\\image.jpg".AsOsAgnostic();
|
||||||
_metaData.First().LastUpdated = new DateTime(2014, 12, 29);
|
_metadata.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||||
_metaData.First().Type = MetadataType.SeasonImage;
|
_metadata.First().Type = MetadataType.SeasonImage;
|
||||||
_metaData.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
_metadata.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.OpenReadStream(imagePath))
|
.Setup(c => c.OpenReadStream(imagePath))
|
||||||
|
@ -136,7 +138,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
Subject.Clean();
|
Subject.Clean();
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.DeleteFile(imagePath), Times.Once());
|
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();
|
var imagePath = "C:\\TV\\Season\\image.jpg".AsOsAgnostic();
|
||||||
_metaData.First().LastUpdated = new DateTime(2014, 12, 29);
|
_metadata.First().LastUpdated = new DateTime(2014, 12, 29);
|
||||||
_metaData.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
_metadata.First().RelativePath = "Season\\image.jpg".AsOsAgnostic();
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.OpenReadStream(imagePath))
|
.Setup(c => c.OpenReadStream(imagePath))
|
||||||
|
|
|
@ -81,7 +81,7 @@ namespace NzbDrone.Core.Test.HistoryTests
|
||||||
Path = @"C:\Test\Unsorted\Series.s01e01.mkv"
|
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>()
|
Mocker.GetMock<IHistoryRepository>()
|
||||||
.Verify(v => v.Insert(It.Is<History.History>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
|
.Verify(v => v.Insert(It.Is<History.History>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Extras.Metadata;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
using NzbDrone.Core.Metadata;
|
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Extras.Metadata;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata;
|
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Metadata;
|
using NzbDrone.Core.Extras.Metadata;
|
||||||
using NzbDrone.Core.Metadata.Consumers.Roksbox;
|
using NzbDrone.Core.Extras.Metadata.Consumers.Roksbox;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Metadata;
|
using NzbDrone.Core.Extras.Metadata;
|
||||||
using NzbDrone.Core.Metadata.Consumers.Wdtv;
|
using NzbDrone.Core.Extras.Metadata.Consumers.Wdtv;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
|
|
@ -119,6 +119,7 @@
|
||||||
<Compile Include="Datastore\DatabaseRelationshipFixture.cs" />
|
<Compile Include="Datastore\DatabaseRelationshipFixture.cs" />
|
||||||
<Compile Include="Datastore\MappingExtentionFixture.cs" />
|
<Compile Include="Datastore\MappingExtentionFixture.cs" />
|
||||||
<Compile Include="Datastore\MarrDataLazyLoadingFixture.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\101_add_ultrahd_quality_in_profilesFixture.cs" />
|
||||||
<Compile Include="Datastore\Migration\071_unknown_quality_in_profileFixture.cs" />
|
<Compile Include="Datastore\Migration\071_unknown_quality_in_profileFixture.cs" />
|
||||||
<Compile Include="Datastore\Migration\072_history_downloadIdFixture.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)]
|
[TestCase("Castle.2009.S01E14.HDTV.XviD.HUN-LOL", Language.Hungarian)]
|
||||||
public void should_parse_language(string postTitle, Language language)
|
public void should_parse_language(string postTitle, Language language)
|
||||||
{
|
{
|
||||||
var result = Parser.Parser.ParseTitle(postTitle);
|
var result = LanguageParser.ParseLanguage(postTitle);
|
||||||
result.Language.Should().Be(language);
|
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); }
|
set { SetValue("EnableMediaInfo", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ExtraFileExtensions
|
||||||
|
{
|
||||||
|
get { return GetValue("ExtraFileExtensions", ""); }
|
||||||
|
|
||||||
|
set { SetValue("ExtraFileExtensions", value); }
|
||||||
|
}
|
||||||
|
|
||||||
public bool SetPermissionsLinux
|
public bool SetPermissionsLinux
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("SetPermissionsLinux", false); }
|
get { return GetValueBoolean("SetPermissionsLinux", false); }
|
||||||
|
|
|
@ -35,6 +35,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
||||||
bool CopyUsingHardlinks { get; set; }
|
bool CopyUsingHardlinks { get; set; }
|
||||||
bool EnableMediaInfo { get; set; }
|
bool EnableMediaInfo { get; set; }
|
||||||
|
string ExtraFileExtensions { get; set; }
|
||||||
|
|
||||||
//Permissions (Media Management)
|
//Permissions (Media Management)
|
||||||
bool SetPermissionsLinux { get; set; }
|
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.Instrumentation;
|
||||||
using NzbDrone.Core.Jobs;
|
using NzbDrone.Core.Jobs;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata;
|
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Profiles.Delay;
|
using NzbDrone.Core.Profiles.Delay;
|
||||||
using NzbDrone.Core.RemotePathMappings;
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
using NzbDrone.Core.Notifications;
|
using NzbDrone.Core.Notifications;
|
||||||
|
@ -31,6 +29,10 @@ using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Authentication;
|
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;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
|
@ -92,13 +94,14 @@ namespace NzbDrone.Core.Datastore
|
||||||
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions")
|
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions")
|
||||||
.Ignore(d => d.Weight);
|
.Ignore(d => d.Weight);
|
||||||
|
|
||||||
|
|
||||||
Mapper.Entity<Profile>().RegisterModel("Profiles");
|
Mapper.Entity<Profile>().RegisterModel("Profiles");
|
||||||
Mapper.Entity<Log>().RegisterModel("Logs");
|
Mapper.Entity<Log>().RegisterModel("Logs");
|
||||||
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
||||||
Mapper.Entity<SeasonStatistics>().MapResultSet();
|
Mapper.Entity<SeasonStatistics>().MapResultSet();
|
||||||
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
||||||
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
|
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
|
||||||
|
Mapper.Entity<SubtitleFile>().RegisterModel("SubtitleFiles");
|
||||||
|
Mapper.Entity<OtherExtraFile>().RegisterModel("ExtraFiles");
|
||||||
|
|
||||||
Mapper.Entity<PendingRelease>().RegisterModel("PendingReleases")
|
Mapper.Entity<PendingRelease>().RegisterModel("PendingReleases")
|
||||||
.Ignore(e => e.RemoteEpisode);
|
.Ignore(e => e.RemoteEpisode);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 System;
|
||||||
using NzbDrone.Core.Datastore;
|
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 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? EpisodeFileId { get; set; }
|
||||||
public int? SeasonNumber { 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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.Datastore;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
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 DeleteForSeries(int seriesId);
|
||||||
void DeleteForSeason(int seriesId, int seasonNumber);
|
void DeleteForSeason(int seriesId, int seasonNumber);
|
||||||
void DeleteForEpisodeFile(int episodeFileId);
|
void DeleteForEpisodeFile(int episodeFileId);
|
||||||
List<MetadataFile> GetFilesBySeries(int seriesId);
|
List<TExtraFile> GetFilesBySeries(int seriesId);
|
||||||
List<MetadataFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
List<TExtraFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||||
List<MetadataFile> GetFilesByEpisodeFile(int episodeFileId);
|
List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId);
|
||||||
MetadataFile FindByPath(string path);
|
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)
|
: base(database, eventAggregator)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -38,22 +39,22 @@ namespace NzbDrone.Core.Metadata.Files
|
||||||
Delete(c => c.EpisodeFileId == episodeFileId);
|
Delete(c => c.EpisodeFileId == episodeFileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MetadataFile> GetFilesBySeries(int seriesId)
|
public List<TExtraFile> GetFilesBySeries(int seriesId)
|
||||||
{
|
{
|
||||||
return Query.Where(c => c.SeriesId == 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);
|
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);
|
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();
|
return Query.Where(c => c.RelativePath == path).SingleOrDefault();
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Extras
|
||||||
|
{
|
||||||
|
public interface IImportExistingExtraFiles
|
||||||
|
{
|
||||||
|
int Order { get; }
|
||||||
|
IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Consumers.MediaBrowser
|
namespace NzbDrone.Core.Extras.Metadata.Consumers.MediaBrowser
|
||||||
{
|
{
|
||||||
public class MediaBrowserMetadata : MetadataBase<MediaBrowserMetadataSettings>
|
public class MediaBrowserMetadata : MetadataBase<MediaBrowserMetadataSettings>
|
||||||
{
|
{
|
||||||
private readonly IMapCoversToLocal _mediaCoverService;
|
|
||||||
private readonly IDiskProvider _diskProvider;
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public MediaBrowserMetadata(IMapCoversToLocal mediaCoverService,
|
public MediaBrowserMetadata(
|
||||||
IDiskProvider diskProvider,
|
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_mediaCoverService = mediaCoverService;
|
|
||||||
_diskProvider = diskProvider;
|
|
||||||
_logger = logger;
|
_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)
|
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path);
|
|
@ -1,10 +1,9 @@
|
||||||
using System;
|
using FluentValidation;
|
||||||
using FluentValidation;
|
|
||||||
using NzbDrone.Core.Annotations;
|
using NzbDrone.Core.Annotations;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Consumers.MediaBrowser
|
namespace NzbDrone.Core.Extras.Metadata.Consumers.MediaBrowser
|
||||||
{
|
{
|
||||||
public class MediaBrowserSettingsValidator : AbstractValidator<MediaBrowserMetadataSettings>
|
public class MediaBrowserSettingsValidator : AbstractValidator<MediaBrowserMetadataSettings>
|
||||||
{
|
{
|
|
@ -9,12 +9,13 @@ using System.Xml.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Consumers.Roksbox
|
namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
|
||||||
{
|
{
|
||||||
public class RoksboxMetadata : MetadataBase<RoksboxMetadataSettings>
|
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 episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||||
var updatedMetadataFiles = new List<MetadataFile>();
|
|
||||||
|
|
||||||
foreach (var episodeFile in episodeFiles)
|
|
||||||
{
|
|
||||||
var metadataFiles = episodeFilesMetadata.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
|
||||||
|
|
||||||
foreach (var metadataFile in metadataFiles)
|
|
||||||
{
|
|
||||||
string newFilename;
|
|
||||||
|
|
||||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||||
{
|
{
|
||||||
newFilename = GetEpisodeImageFilename(episodeFile.RelativePath);
|
return GetEpisodeImageFilename(episodeFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||||
{
|
{
|
||||||
newFilename = GetEpisodeMetadataFilename(episodeFile.RelativePath);
|
return GetEpisodeMetadataFilename(episodeFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||||
{
|
return Path.Combine(series.Path, metadataFile.RelativePath);
|
||||||
_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 updatedMetadataFiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
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.Annotations;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Consumers.Roksbox
|
namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
|
||||||
{
|
{
|
||||||
public class RoksboxSettingsValidator : AbstractValidator<RoksboxMetadataSettings>
|
public class RoksboxSettingsValidator : AbstractValidator<RoksboxMetadataSettings>
|
||||||
{
|
{
|
|
@ -9,12 +9,13 @@ using System.Xml.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Consumers.Wdtv
|
namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
|
||||||
{
|
{
|
||||||
public class WdtvMetadata : MetadataBase<WdtvMetadataSettings>
|
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 episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||||
var updatedMetadataFiles = new List<MetadataFile>();
|
|
||||||
|
|
||||||
foreach (var episodeFile in episodeFiles)
|
|
||||||
{
|
|
||||||
var metadataFiles = episodeFilesMetadata.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
|
||||||
|
|
||||||
foreach (var metadataFile in metadataFiles)
|
|
||||||
{
|
|
||||||
string newFilename;
|
|
||||||
|
|
||||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||||
{
|
{
|
||||||
newFilename = GetEpisodeImageFilename(episodeFile.RelativePath);
|
return GetEpisodeImageFilename(episodeFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||||
{
|
{
|
||||||
newFilename = GetEpisodeMetadataFilename(episodeFile.RelativePath);
|
return GetEpisodeMetadataFilename(episodeFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||||
{
|
return Path.Combine(series.Path, metadataFile.RelativePath);
|
||||||
_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 updatedMetadataFiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
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.Annotations;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Consumers.Wdtv
|
namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
|
||||||
{
|
{
|
||||||
public class WdtvSettingsValidator : AbstractValidator<WdtvMetadataSettings>
|
public class WdtvSettingsValidator : AbstractValidator<WdtvMetadataSettings>
|
||||||
{
|
{
|
|
@ -9,12 +9,13 @@ using System.Xml.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||||
{
|
{
|
||||||
public class XbmcMetadata : MetadataBase<XbmcMetadataSettings>
|
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 episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||||
var updatedMetadataFiles = new List<MetadataFile>();
|
|
||||||
|
|
||||||
foreach (var episodeFile in episodeFiles)
|
|
||||||
{
|
|
||||||
var metadataFiles = episodeFilesMetadata.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
|
||||||
|
|
||||||
foreach (var metadataFile in metadataFiles)
|
|
||||||
{
|
|
||||||
string newFilename;
|
|
||||||
|
|
||||||
if (metadataFile.Type == MetadataType.EpisodeImage)
|
if (metadataFile.Type == MetadataType.EpisodeImage)
|
||||||
{
|
{
|
||||||
newFilename = GetEpisodeImageFilename(episodeFile.RelativePath);
|
return GetEpisodeImageFilename(episodeFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
if (metadataFile.Type == MetadataType.EpisodeMetadata)
|
||||||
{
|
{
|
||||||
newFilename = GetEpisodeNfoFilename(episodeFile.RelativePath);
|
return GetEpisodeMetadataFilename(episodeFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
|
||||||
continue;
|
return Path.Combine(series.Path, metadataFile.RelativePath);
|
||||||
}
|
|
||||||
|
|
||||||
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 updatedMetadataFiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
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)
|
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");
|
return Path.ChangeExtension(episodeFilePath, "nfo");
|
||||||
}
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
using System;
|
using FluentValidation;
|
||||||
using FluentValidation;
|
|
||||||
using NzbDrone.Core.Annotations;
|
using NzbDrone.Core.Annotations;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||||
{
|
{
|
||||||
public class XbmcSettingsValidator : AbstractValidator<XbmcMetadataSettings>
|
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,20 +3,20 @@ using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Files
|
namespace NzbDrone.Core.Extras.Metadata.Files
|
||||||
{
|
{
|
||||||
public interface ICleanMetadataService
|
public interface ICleanMetadataService
|
||||||
{
|
{
|
||||||
void Clean(Series series);
|
void Clean(Series series);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CleanMetadataService : ICleanMetadataService
|
public class CleanExtraFileService : ICleanMetadataService
|
||||||
{
|
{
|
||||||
private readonly IMetadataFileService _metadataFileService;
|
private readonly IMetadataFileService _metadataFileService;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public CleanMetadataService(IMetadataFileService metadataFileService,
|
public CleanExtraFileService(IMetadataFileService metadataFileService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace NzbDrone.Core.Extras.Metadata.Files
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Files
|
|
||||||
{
|
{
|
||||||
public class ImageFileResult
|
public class ImageFileResult
|
||||||
{
|
{
|
|
@ -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.Extras.Metadata.Files
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata.Files
|
|
||||||
{
|
{
|
||||||
public class MetadataFileResult
|
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 System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata
|
namespace NzbDrone.Core.Extras.Metadata
|
||||||
{
|
{
|
||||||
public interface IMetadata : IProvider
|
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);
|
MetadataFile FindMetadataFile(Series series, string path);
|
||||||
|
|
||||||
MetadataFileResult SeriesMetadata(Series series);
|
MetadataFileResult SeriesMetadata(Series series);
|
||||||
MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
|
MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
|
||||||
List<ImageFileResult> SeriesImages(Series series);
|
List<ImageFileResult> SeriesImages(Series series);
|
||||||
List<ImageFileResult> SeasonImages(Series series, Season season);
|
List<ImageFileResult> SeasonImages(Series series, Season season);
|
||||||
List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
|
List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata
|
namespace NzbDrone.Core.Extras.Metadata
|
||||||
{
|
{
|
||||||
public abstract class MetadataBase<TSettings> : IMetadata where TSettings : IProviderConfig, new()
|
public abstract class MetadataBase<TSettings> : IMetadata where TSettings : IProviderConfig, new()
|
||||||
{
|
{
|
||||||
|
@ -43,7 +45,15 @@ namespace NzbDrone.Core.Metadata
|
||||||
return new ValidationResult();
|
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 MetadataFile FindMetadataFile(Series series, string path);
|
||||||
|
|
||||||
public abstract MetadataFileResult SeriesMetadata(Series series);
|
public abstract MetadataFileResult SeriesMetadata(Series series);
|
|
@ -1,6 +1,6 @@
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata
|
namespace NzbDrone.Core.Extras.Metadata
|
||||||
{
|
{
|
||||||
public class MetadataDefinition : ProviderDefinition
|
public class MetadataDefinition : ProviderDefinition
|
||||||
{
|
{
|
|
@ -6,7 +6,7 @@ using NzbDrone.Common.Composition;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata
|
namespace NzbDrone.Core.Extras.Metadata
|
||||||
{
|
{
|
||||||
public interface IMetadataFactory : IProviderFactory<IMetadata, MetadataDefinition>
|
public interface IMetadataFactory : IProviderFactory<IMetadata, MetadataDefinition>
|
||||||
{
|
{
|
|
@ -2,12 +2,10 @@
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Extras.Metadata
|
||||||
namespace NzbDrone.Core.Metadata
|
|
||||||
{
|
{
|
||||||
public interface IMetadataRepository : IProviderRepository<MetadataDefinition>
|
public interface IMetadataRepository : IProviderRepository<MetadataDefinition>
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MetadataRepository : ProviderRepository<MetadataDefinition>, IMetadataRepository
|
public class MetadataRepository : ProviderRepository<MetadataDefinition>, IMetadataRepository
|
|
@ -7,159 +7,179 @@ using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
using NzbDrone.Core.Metadata.Files;
|
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata
|
namespace NzbDrone.Core.Extras.Metadata
|
||||||
{
|
{
|
||||||
public class MetadataService : IHandle<MediaCoversUpdatedEvent>,
|
public class MetadataService : ExtraFileManager<MetadataFile>
|
||||||
IHandle<EpisodeImportedEvent>,
|
|
||||||
IHandle<EpisodeFolderCreatedEvent>,
|
|
||||||
IHandle<SeriesRenamedEvent>
|
|
||||||
{
|
{
|
||||||
private readonly IMetadataFactory _metadataFactory;
|
private readonly IMetadataFactory _metadataFactory;
|
||||||
private readonly IMetadataFileService _metadataFileService;
|
|
||||||
private readonly ICleanMetadataService _cleanMetadataService;
|
private readonly ICleanMetadataService _cleanMetadataService;
|
||||||
private readonly IMediaFileService _mediaFileService;
|
|
||||||
private readonly IEpisodeService _episodeService;
|
|
||||||
private readonly IDiskTransferService _diskTransferService;
|
private readonly IDiskTransferService _diskTransferService;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IMetadataFileService _metadataFileService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public MetadataService(IMetadataFactory metadataFactory,
|
public MetadataService(IConfigService configService,
|
||||||
IMetadataFileService metadataFileService,
|
|
||||||
ICleanMetadataService cleanMetadataService,
|
|
||||||
IMediaFileService mediaFileService,
|
|
||||||
IEpisodeService episodeService,
|
|
||||||
IDiskTransferService diskTransferService,
|
IDiskTransferService diskTransferService,
|
||||||
|
IMetadataFactory metadataFactory,
|
||||||
|
ICleanMetadataService cleanMetadataService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
IMediaFileAttributeService mediaFileAttributeService,
|
IMediaFileAttributeService mediaFileAttributeService,
|
||||||
IEventAggregator eventAggregator,
|
IMetadataFileService metadataFileService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
|
: base(configService, diskTransferService, metadataFileService)
|
||||||
{
|
{
|
||||||
_metadataFactory = metadataFactory;
|
_metadataFactory = metadataFactory;
|
||||||
_metadataFileService = metadataFileService;
|
|
||||||
_cleanMetadataService = cleanMetadataService;
|
_cleanMetadataService = cleanMetadataService;
|
||||||
_mediaFileService = mediaFileService;
|
|
||||||
_episodeService = episodeService;
|
|
||||||
_diskTransferService = diskTransferService;
|
_diskTransferService = diskTransferService;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_mediaFileAttributeService = mediaFileAttributeService;
|
_mediaFileAttributeService = mediaFileAttributeService;
|
||||||
_eventAggregator = eventAggregator;
|
_metadataFileService = metadataFileService;
|
||||||
_logger = logger;
|
_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");
|
_logger.Info("Series folder does not exist, skipping metadata creation");
|
||||||
return;
|
return Enumerable.Empty<MetadataFile>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var seriesMetadataFiles = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
var files = new List<MetadataFile>();
|
||||||
var episodeFiles = GetEpisodeFiles(message.Series.Id);
|
|
||||||
|
|
||||||
foreach (var consumer in _metadataFactory.Enabled())
|
foreach (var consumer in _metadataFactory.Enabled())
|
||||||
{
|
{
|
||||||
var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
|
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
|
||||||
var files = new List<MetadataFile>();
|
|
||||||
|
|
||||||
files.AddIfNotNull(ProcessSeriesMetadata(consumer, message.Series, consumerFiles));
|
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
|
||||||
files.AddRange(ProcessSeriesImages(consumer, message.Series, consumerFiles));
|
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
|
||||||
files.AddRange(ProcessSeasonImages(consumer, message.Series, consumerFiles));
|
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
|
||||||
|
|
||||||
foreach (var episodeFile in episodeFiles)
|
foreach (var episodeFile in episodeFiles)
|
||||||
{
|
{
|
||||||
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.Series, episodeFile, consumerFiles));
|
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, consumerFiles));
|
||||||
files.AddRange(ProcessEpisodeImages(consumer, message.Series, episodeFile, consumerFiles));
|
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, consumerFiles));
|
||||||
}
|
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(EpisodeImportedEvent message)
|
_metadataFileService.Upsert(files);
|
||||||
{
|
|
||||||
foreach (var consumer in _metadataFactory.Enabled())
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
var files = new List<MetadataFile>();
|
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);
|
|
||||||
|
|
||||||
foreach (var consumer in _metadataFactory.Enabled())
|
foreach (var consumer in _metadataFactory.Enabled())
|
||||||
{
|
{
|
||||||
|
|
||||||
|
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>();
|
var files = new List<MetadataFile>();
|
||||||
var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
|
|
||||||
|
|
||||||
if (message.SeriesFolder.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
files.AddIfNotNull(ProcessSeriesMetadata(consumer, message.Series, consumerFiles));
|
|
||||||
files.AddRange(ProcessSeriesImages(consumer, message.Series, consumerFiles));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.SeasonFolder.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
files.AddRange(ProcessSeasonImages(consumer, message.Series, consumerFiles));
|
|
||||||
}
|
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Handle(SeriesRenamedEvent message)
|
|
||||||
{
|
|
||||||
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
|
||||||
var episodeFiles = GetEpisodeFiles(message.Series.Id);
|
|
||||||
|
|
||||||
foreach (var consumer in _metadataFactory.Enabled())
|
foreach (var consumer in _metadataFactory.Enabled())
|
||||||
{
|
{
|
||||||
var updatedMetadataFiles = consumer.AfterRename(message.Series,
|
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
|
||||||
GetMetadataFilesForConsumer(consumer, seriesMetadata),
|
|
||||||
episodeFiles);
|
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
|
if (seriesFolder.IsNotNullOrWhiteSpace())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
|
|
||||||
{
|
{
|
||||||
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
|
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
|
||||||
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seasonFolder.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_metadataFileService.Upsert(files);
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles)
|
||||||
|
{
|
||||||
|
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
|
||||||
|
var movedFiles = new List<MetadataFile>();
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
{
|
||||||
foreach (var episodeFile in episodeFiles)
|
foreach (var episodeFile in episodeFiles)
|
||||||
{
|
{
|
||||||
var localEpisodeFile = episodeFile;
|
var metadataFilesForConsumer = GetMetadataFilesForConsumer(consumer, metadataFiles).Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
||||||
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return episodeFiles;
|
_metadataFileService.Upsert(movedFiles);
|
||||||
|
|
||||||
|
return movedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> seriesMetadata)
|
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> seriesMetadata)
|
||||||
|
@ -226,7 +246,7 @@ namespace NzbDrone.Core.Metadata
|
||||||
if (existingMetadata != null)
|
if (existingMetadata != null)
|
||||||
{
|
{
|
||||||
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||||
if (!fullPath.PathEquals(existingFullPath))
|
if (fullPath.PathNotEquals(existingFullPath))
|
||||||
{
|
{
|
||||||
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
||||||
existingMetadata.RelativePath = episodeMetadata.RelativePath;
|
existingMetadata.RelativePath = episodeMetadata.RelativePath;
|
||||||
|
@ -239,6 +259,7 @@ namespace NzbDrone.Core.Metadata
|
||||||
new MetadataFile
|
new MetadataFile
|
||||||
{
|
{
|
||||||
SeriesId = series.Id,
|
SeriesId = series.Id,
|
||||||
|
SeasonNumber = episodeFile.SeasonNumber,
|
||||||
EpisodeFileId = episodeFile.Id,
|
EpisodeFileId = episodeFile.Id,
|
||||||
Consumer = consumer.GetType().Name,
|
Consumer = consumer.GetType().Name,
|
||||||
Type = MetadataType.EpisodeMetadata,
|
Type = MetadataType.EpisodeMetadata,
|
||||||
|
@ -347,7 +368,7 @@ namespace NzbDrone.Core.Metadata
|
||||||
if (existingMetadata != null)
|
if (existingMetadata != null)
|
||||||
{
|
{
|
||||||
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||||
if (!fullPath.PathEquals(existingFullPath))
|
if (fullPath.PathNotEquals(existingFullPath))
|
||||||
{
|
{
|
||||||
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
||||||
existingMetadata.RelativePath = image.RelativePath;
|
existingMetadata.RelativePath = image.RelativePath;
|
||||||
|
@ -360,6 +381,7 @@ namespace NzbDrone.Core.Metadata
|
||||||
new MetadataFile
|
new MetadataFile
|
||||||
{
|
{
|
||||||
SeriesId = series.Id,
|
SeriesId = series.Id,
|
||||||
|
SeasonNumber = episodeFile.SeasonNumber,
|
||||||
EpisodeFileId = episodeFile.Id,
|
EpisodeFileId = episodeFile.Id,
|
||||||
Consumer = consumer.GetType().Name,
|
Consumer = consumer.GetType().Name,
|
||||||
Type = MetadataType.EpisodeImage,
|
Type = MetadataType.EpisodeImage,
|
|
@ -1,4 +1,4 @@
|
||||||
namespace NzbDrone.Core.Metadata
|
namespace NzbDrone.Core.Extras.Metadata
|
||||||
{
|
{
|
||||||
public enum MetadataType
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Extras.Others
|
||||||
|
{
|
||||||
|
public class OtherExtraFile : ExtraFile
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Metadata.Files;
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
public class DeleteBadMediaCovers : IHousekeepingTask
|
public class DeleteBadMediaCovers : IHousekeepingTask
|
||||||
{
|
{
|
||||||
|
private readonly IMetadataFileService _metaFileService;
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
private readonly IMetadataFileService _metadataFileService;
|
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly Logger _logger;
|
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;
|
_seriesService = seriesService;
|
||||||
_metadataFileService = metadataFileService;
|
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -34,7 +38,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
|
||||||
foreach (var show in series)
|
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));
|
.Where(c => c.LastUpdated > new DateTime(2014, 12, 27) && c.RelativePath.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
foreach (var image in images)
|
foreach (var image in images)
|
||||||
|
@ -61,7 +65,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
|
||||||
private void DeleteMetadata(int id, string path)
|
private void DeleteMetadata(int id, string path)
|
||||||
{
|
{
|
||||||
_metadataFileService.Delete(id);
|
_metaFileService.Delete(id);
|
||||||
_diskProvider.DeleteFile(path);
|
_diskProvider.DeleteFile(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Extras;
|
||||||
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
@ -24,18 +25,21 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
{
|
{
|
||||||
private readonly IUpgradeMediaFiles _episodeFileUpgrader;
|
private readonly IUpgradeMediaFiles _episodeFileUpgrader;
|
||||||
private readonly IMediaFileService _mediaFileService;
|
private readonly IMediaFileService _mediaFileService;
|
||||||
|
private readonly IExtraService _extraService;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public ImportApprovedEpisodes(IUpgradeMediaFiles episodeFileUpgrader,
|
public ImportApprovedEpisodes(IUpgradeMediaFiles episodeFileUpgrader,
|
||||||
IMediaFileService mediaFileService,
|
IMediaFileService mediaFileService,
|
||||||
|
IExtraService extraService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_episodeFileUpgrader = episodeFileUpgrader;
|
_episodeFileUpgrader = episodeFileUpgrader;
|
||||||
_mediaFileService = mediaFileService;
|
_mediaFileService = mediaFileService;
|
||||||
|
_extraService = extraService;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -98,9 +102,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
_mediaFileService.Add(episodeFile);
|
_mediaFileService.Add(episodeFile);
|
||||||
importResults.Add(new ImportResult(importDecision));
|
importResults.Add(new ImportResult(importDecision));
|
||||||
|
|
||||||
|
if (newDownload)
|
||||||
|
{
|
||||||
|
_extraService.ImportExtraFiles(localEpisode, episodeFile, downloadClientItem != null && downloadClientItem.IsReadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
if (downloadClientItem != null)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using NzbDrone.Common.Messaging;
|
||||||
using NzbDrone.Common.Messaging;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.Events
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
@ -11,6 +10,7 @@ namespace NzbDrone.Core.MediaFiles.Events
|
||||||
public bool NewDownload { get; private set; }
|
public bool NewDownload { get; private set; }
|
||||||
public string DownloadClient { get; private set; }
|
public string DownloadClient { get; private set; }
|
||||||
public string DownloadId { get; private set; }
|
public string DownloadId { get; private set; }
|
||||||
|
public bool IsReadOnly { get; set; }
|
||||||
|
|
||||||
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload)
|
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload)
|
||||||
{
|
{
|
||||||
|
@ -19,13 +19,14 @@ namespace NzbDrone.Core.MediaFiles.Events
|
||||||
NewDownload = newDownload;
|
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;
|
EpisodeInfo = episodeInfo;
|
||||||
ImportedEpisode = importedEpisode;
|
ImportedEpisode = importedEpisode;
|
||||||
NewDownload = newDownload;
|
NewDownload = newDownload;
|
||||||
DownloadClient = downloadClient;
|
DownloadClient = downloadClient;
|
||||||
DownloadId = downloadId;
|
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\093_naming_config_replace_characters.cs" />
|
||||||
<Compile Include="Datastore\Migration\092_add_unverifiedscenenumbering.cs" />
|
<Compile Include="Datastore\Migration\092_add_unverifiedscenenumbering.cs" />
|
||||||
<Compile Include="Datastore\Migration\100_add_scene_season_number.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\094_add_tvmazeid.cs" />
|
||||||
<Compile Include="Datastore\Migration\098_remove_titans_of_tv.cs">
|
<Compile Include="Datastore\Migration\098_remove_titans_of_tv.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
|
@ -487,6 +488,29 @@
|
||||||
<Compile Include="Exceptions\SeriesNotFoundException.cs" />
|
<Compile Include="Exceptions\SeriesNotFoundException.cs" />
|
||||||
<Compile Include="Exceptions\ReleaseDownloadException.cs" />
|
<Compile Include="Exceptions\ReleaseDownloadException.cs" />
|
||||||
<Compile Include="Exceptions\StatusCodeToExceptions.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="Fluent.cs" />
|
||||||
<Compile Include="HealthCheck\CheckHealthCommand.cs" />
|
<Compile Include="HealthCheck\CheckHealthCommand.cs" />
|
||||||
<Compile Include="HealthCheck\Checks\AppDataLocationCheck.cs" />
|
<Compile Include="HealthCheck\Checks\AppDataLocationCheck.cs" />
|
||||||
|
@ -757,29 +781,25 @@
|
||||||
<Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" />
|
<Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" />
|
||||||
<Compile Include="MetadataSource\SearchSeriesComparer.cs" />
|
<Compile Include="MetadataSource\SearchSeriesComparer.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\SkyHookException.cs" />
|
<Compile Include="MetadataSource\SkyHook\SkyHookException.cs" />
|
||||||
<Compile Include="Metadata\Consumers\MediaBrowser\MediaBrowserMetadata.cs" />
|
<Compile Include="Extras\Metadata\Consumers\MediaBrowser\MediaBrowserMetadata.cs" />
|
||||||
<Compile Include="Metadata\Consumers\MediaBrowser\MediaBrowserMetadataSettings.cs" />
|
<Compile Include="Extras\Metadata\Consumers\MediaBrowser\MediaBrowserMetadataSettings.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Roksbox\RoksboxMetadata.cs" />
|
<Compile Include="Extras\Metadata\Consumers\Roksbox\RoksboxMetadata.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Roksbox\RoksboxMetadataSettings.cs" />
|
<Compile Include="Extras\Metadata\Consumers\Roksbox\RoksboxMetadataSettings.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Wdtv\WdtvMetadata.cs" />
|
<Compile Include="Extras\Metadata\Consumers\Wdtv\WdtvMetadata.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Wdtv\WdtvMetadataSettings.cs" />
|
<Compile Include="Extras\Metadata\Consumers\Wdtv\WdtvMetadataSettings.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadata.cs" />
|
<Compile Include="Extras\Metadata\Consumers\Xbmc\XbmcMetadata.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadataSettings.cs" />
|
<Compile Include="Extras\Metadata\Consumers\Xbmc\XbmcMetadataSettings.cs" />
|
||||||
<Compile Include="Metadata\ExistingMetadataService.cs" />
|
<Compile Include="Extras\Metadata\ExistingMetadataImporter.cs" />
|
||||||
<Compile Include="Metadata\Files\CleanMetadataService.cs" />
|
<Compile Include="Extras\Metadata\Files\CleanMetadataFileService.cs" />
|
||||||
<Compile Include="Metadata\Files\ImageFileResult.cs" />
|
<Compile Include="Extras\Metadata\Files\ImageFileResult.cs" />
|
||||||
<Compile Include="Metadata\Files\MetadataFile.cs" />
|
<Compile Include="Extras\Metadata\Files\MetadataFileResult.cs" />
|
||||||
<Compile Include="Metadata\Files\MetadataFileRepository.cs" />
|
<Compile Include="Extras\Metadata\IMetadata.cs" />
|
||||||
<Compile Include="Metadata\Files\MetadataFileResult.cs" />
|
<Compile Include="Extras\Metadata\MetadataBase.cs" />
|
||||||
<Compile Include="Metadata\Files\MetadataFileService.cs" />
|
<Compile Include="Extras\Metadata\MetadataDefinition.cs" />
|
||||||
<Compile Include="Metadata\Files\MetadataFilesUpdated.cs" />
|
<Compile Include="Extras\Metadata\MetadataFactory.cs" />
|
||||||
<Compile Include="Metadata\IMetadata.cs" />
|
<Compile Include="Extras\Metadata\MetadataRepository.cs" />
|
||||||
<Compile Include="Metadata\MetadataBase.cs" />
|
<Compile Include="Extras\Metadata\MetadataService.cs" />
|
||||||
<Compile Include="Metadata\MetadataDefinition.cs" />
|
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
||||||
<Compile Include="Metadata\MetadataFactory.cs" />
|
|
||||||
<Compile Include="Metadata\MetadataRepository.cs" />
|
|
||||||
<Compile Include="Metadata\MetadataService.cs" />
|
|
||||||
<Compile Include="Metadata\MetadataType.cs" />
|
|
||||||
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
||||||
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
|
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
|
||||||
<Compile Include="Notifications\Join\JoinAuthException.cs" />
|
<Compile Include="Notifications\Join\JoinAuthException.cs" />
|
||||||
|
@ -827,6 +847,9 @@
|
||||||
<Compile Include="Notifications\Twitter\Twitter.cs" />
|
<Compile Include="Notifications\Twitter\Twitter.cs" />
|
||||||
<Compile Include="Notifications\Twitter\TwitterService.cs" />
|
<Compile Include="Notifications\Twitter\TwitterService.cs" />
|
||||||
<Compile Include="Notifications\Twitter\TwitterSettings.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\DelayProfile.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
|
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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|\.)?)",
|
private static readonly Regex AnimeReleaseGroupRegex = new Regex(@"^(?:\[(?<subgroup>(?!\s).+?(?<!\s))\](?:_|-|\s|\.)?)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
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})",
|
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_)?(?<year>\d{4})",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
@ -358,7 +355,7 @@ namespace NzbDrone.Core.Parser
|
||||||
result.Special = true;
|
result.Special = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Language = ParseLanguage(title);
|
result.Language = LanguageParser.ParseLanguage(title);
|
||||||
Logger.Debug("Language parsed: {0}", result.Language);
|
Logger.Debug("Language parsed: {0}", result.Language);
|
||||||
|
|
||||||
result.Quality = QualityParser.ParseQuality(title);
|
result.Quality = QualityParser.ParseQuality(title);
|
||||||
|
@ -494,96 +491,6 @@ namespace NzbDrone.Core.Parser
|
||||||
return title;
|
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)
|
private static SeriesTitleInfo GetSeriesTitleInfo(string title)
|
||||||
{
|
{
|
||||||
var seriesTitleInfo = new SeriesTitleInfo();
|
var seriesTitleInfo = new SeriesTitleInfo();
|
||||||
|
|
|
@ -233,7 +233,7 @@ namespace NzbDrone.Core.Parser
|
||||||
info.FullSeason = false;
|
info.FullSeason = false;
|
||||||
info.Quality = QualityParser.ParseQuality(title);
|
info.Quality = QualityParser.ParseQuality(title);
|
||||||
info.ReleaseGroup = Parser.ParseReleaseGroup(title);
|
info.ReleaseGroup = Parser.ParseReleaseGroup(title);
|
||||||
info.Language = Parser.ParseLanguage(title);
|
info.Language = LanguageParser.ParseLanguage(title);
|
||||||
info.Special = true;
|
info.Special = true;
|
||||||
|
|
||||||
_logger.Debug("Found special episode {0} for title '{1}'", info, title);
|
_logger.Debug("Found special episode {0} for title '{1}'", info, title);
|
||||||
|
|
Loading…
Reference in New Issue