Merge branch 'develop'
|
@ -50,7 +50,6 @@ namespace NzbDrone.Api.Test.MappingTests
|
||||||
MappingValidation.ValidateMapping(modelType, resourceType);
|
MappingValidation.ValidateMapping(modelType, resourceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_map_lay_loaded_values_should_not_be_inject_if_not_loaded()
|
public void should_map_lay_loaded_values_should_not_be_inject_if_not_loaded()
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
using NzbDrone.Core.Metadata;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Metadata
|
||||||
|
{
|
||||||
|
public class MetadataModule : ProviderModuleBase<MetadataResource, IMetadata, MetadataDefinition>
|
||||||
|
{
|
||||||
|
public MetadataModule(IMetadataFactory metadataFactory)
|
||||||
|
: base(metadataFactory, "metadata")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Validate(MetadataDefinition definition)
|
||||||
|
{
|
||||||
|
if (!definition.Enable) return;
|
||||||
|
base.Validate(definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Metadata
|
||||||
|
{
|
||||||
|
public class MetadataResource : ProviderResource
|
||||||
|
{
|
||||||
|
public Boolean Enable { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace NzbDrone.Api.Notifications
|
namespace NzbDrone.Api.Notifications
|
||||||
{
|
{
|
||||||
public class IndexerModule : ProviderModuleBase<NotificationResource, INotification, NotificationDefinition>
|
public class NotificationModule : ProviderModuleBase<NotificationResource, INotification, NotificationDefinition>
|
||||||
{
|
{
|
||||||
public IndexerModule(NotificationFactory notificationFactory)
|
public NotificationModule(NotificationFactory notificationFactory)
|
||||||
: base(notificationFactory, "notification")
|
: base(notificationFactory, "notification")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,8 @@
|
||||||
<Compile Include="Frontend\StaticResourceModule.cs" />
|
<Compile Include="Frontend\StaticResourceModule.cs" />
|
||||||
<Compile Include="History\HistoryResource.cs" />
|
<Compile Include="History\HistoryResource.cs" />
|
||||||
<Compile Include="History\HistoryModule.cs" />
|
<Compile Include="History\HistoryModule.cs" />
|
||||||
|
<Compile Include="Metadata\MetadataResource.cs" />
|
||||||
|
<Compile Include="Metadata\MetadataModule.cs" />
|
||||||
<Compile Include="ProviderResource.cs" />
|
<Compile Include="ProviderResource.cs" />
|
||||||
<Compile Include="ProviderModuleBase.cs" />
|
<Compile Include="ProviderModuleBase.cs" />
|
||||||
<Compile Include="Indexers\IndexerSchemaModule.cs" />
|
<Compile Include="Indexers\IndexerSchemaModule.cs" />
|
||||||
|
|
|
@ -10,7 +10,6 @@ namespace NzbDrone.Api.Qualities
|
||||||
private readonly IQualityProfileService _qualityProfileService;
|
private readonly IQualityProfileService _qualityProfileService;
|
||||||
|
|
||||||
public QualityProfileModule(IQualityProfileService qualityProfileService)
|
public QualityProfileModule(IQualityProfileService qualityProfileService)
|
||||||
: base("/qualityprofiles")
|
|
||||||
{
|
{
|
||||||
_qualityProfileService = qualityProfileService;
|
_qualityProfileService = qualityProfileService;
|
||||||
SharedValidator.RuleFor(c => c.Name).NotEmpty();
|
SharedValidator.RuleFor(c => c.Name).NotEmpty();
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace NzbDrone.Api.Qualities
|
||||||
private readonly IQualityDefinitionService _qualityDefinitionService;
|
private readonly IQualityDefinitionService _qualityDefinitionService;
|
||||||
|
|
||||||
public QualityProfileSchemaModule(IQualityDefinitionService qualityDefinitionService)
|
public QualityProfileSchemaModule(IQualityDefinitionService qualityDefinitionService)
|
||||||
: base("/qualityprofiles/schema")
|
: base("/qualityprofile/schema")
|
||||||
{
|
{
|
||||||
_qualityDefinitionService = qualityDefinitionService;
|
_qualityDefinitionService = qualityDefinitionService;
|
||||||
|
|
||||||
|
|
|
@ -59,5 +59,7 @@ namespace NzbDrone.Api.Series
|
||||||
public String ImdbId { get; set; }
|
public String ImdbId { get; set; }
|
||||||
public String TitleSlug { get; set; }
|
public String TitleSlug { get; set; }
|
||||||
public String RootFolderPath { get; set; }
|
public String RootFolderPath { get; set; }
|
||||||
|
public String Certification { get; set; }
|
||||||
|
public List<String> Genres { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Test.DiskProviderTests
|
namespace NzbDrone.Common.Test.DiskProviderTests
|
||||||
{
|
{
|
||||||
public class IsParentFixtureBase<TSubject> : TestBase<TSubject> where TSubject : class, IDiskProvider
|
public class IsParentFixture : TestBase
|
||||||
{
|
{
|
||||||
private string _parent = @"C:\Test".AsOsAgnostic();
|
private string _parent = @"C:\Test".AsOsAgnostic();
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
|
||||||
{
|
{
|
||||||
var path = @"C:\Another Folder".AsOsAgnostic();
|
var path = @"C:\Another Folder".AsOsAgnostic();
|
||||||
|
|
||||||
Subject.IsParent(_parent, path).Should().BeFalse();
|
DiskProviderBase.IsParent(_parent, path).Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -22,7 +22,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
|
||||||
{
|
{
|
||||||
var path = @"C:\Test\TV".AsOsAgnostic();
|
var path = @"C:\Test\TV".AsOsAgnostic();
|
||||||
|
|
||||||
Subject.IsParent(_parent, path).Should().BeTrue();
|
DiskProviderBase.IsParent(_parent, path).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -30,7 +30,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
|
||||||
{
|
{
|
||||||
var path = @"C:\Test\30.Rock.S01E01.Pilot.avi".AsOsAgnostic();
|
var path = @"C:\Test\30.Rock.S01E01.Pilot.avi".AsOsAgnostic();
|
||||||
|
|
||||||
Subject.IsParent(_parent, path).Should().BeTrue();
|
DiskProviderBase.IsParent(_parent, path).Should().BeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace NzbDrone.Common.Composition
|
||||||
Container = new Container(new TinyIoCContainer(), _loadedTypes);
|
Container = new Container(new TinyIoCContainer(), _loadedTypes);
|
||||||
AutoRegisterInterfaces();
|
AutoRegisterInterfaces();
|
||||||
Container.Register(args);
|
Container.Register(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AutoRegisterInterfaces()
|
private void AutoRegisterInterfaces()
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Security.AccessControl;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Disk
|
namespace NzbDrone.Common.Disk
|
||||||
|
@ -24,6 +25,37 @@ namespace NzbDrone.Common.Disk
|
||||||
public abstract void SetPermissions(string path, string mask, string user, string group);
|
public abstract void SetPermissions(string path, string mask, string user, string group);
|
||||||
public abstract long? GetTotalSize(string path);
|
public abstract long? GetTotalSize(string path);
|
||||||
|
|
||||||
|
public static string GetRelativePath(string parentPath, string childPath)
|
||||||
|
{
|
||||||
|
if (!IsParent(parentPath, childPath))
|
||||||
|
{
|
||||||
|
throw new NotParentException("{0} is not a child of {1}", childPath, parentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsParent(string parentPath, string childPath)
|
||||||
|
{
|
||||||
|
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
var parent = new DirectoryInfo(parentPath);
|
||||||
|
var child = new DirectoryInfo(childPath);
|
||||||
|
|
||||||
|
while (child.Parent != null)
|
||||||
|
{
|
||||||
|
if (child.Parent.FullName == parent.FullName)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
child = child.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public DateTime GetLastFolderWrite(string path)
|
public DateTime GetLastFolderWrite(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath();
|
||||||
|
@ -333,27 +365,6 @@ namespace NzbDrone.Common.Disk
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsParent(string parentPath, string childPath)
|
|
||||||
{
|
|
||||||
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
|
|
||||||
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar);
|
|
||||||
|
|
||||||
var parent = new DirectoryInfo(parentPath);
|
|
||||||
var child = new DirectoryInfo(childPath);
|
|
||||||
|
|
||||||
while (child.Parent != null)
|
|
||||||
{
|
|
||||||
if (child.Parent.FullName == parent.FullName)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
child = child.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetFolderWriteTime(string path, DateTime time)
|
public void SetFolderWriteTime(string path, DateTime time)
|
||||||
{
|
{
|
||||||
Directory.SetLastWriteTimeUtc(path, time);
|
Directory.SetLastWriteTimeUtc(path, time);
|
||||||
|
|
|
@ -37,7 +37,6 @@ namespace NzbDrone.Common.Disk
|
||||||
string GetPathRoot(string path);
|
string GetPathRoot(string path);
|
||||||
string GetParentFolder(string path);
|
string GetParentFolder(string path);
|
||||||
void SetPermissions(string filename, WellKnownSidType accountSid, FileSystemRights rights, AccessControlType controlType);
|
void SetPermissions(string filename, WellKnownSidType accountSid, FileSystemRights rights, AccessControlType controlType);
|
||||||
bool IsParent(string parentPath, string childPath);
|
|
||||||
void SetFolderWriteTime(string path, DateTime time);
|
void SetFolderWriteTime(string path, DateTime time);
|
||||||
FileAttributes GetFileAttributes(string path);
|
FileAttributes GetFileAttributes(string path);
|
||||||
void EmptyFolder(string path);
|
void EmptyFolder(string path);
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Exceptions
|
||||||
|
{
|
||||||
|
public class NotParentException : NzbDroneException
|
||||||
|
{
|
||||||
|
public NotParentException(string message, params object[] args) : base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotParentException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,6 +91,7 @@
|
||||||
<Compile Include="EnvironmentInfo\StartupContext.cs" />
|
<Compile Include="EnvironmentInfo\StartupContext.cs" />
|
||||||
<Compile Include="EnvironmentInfo\RuntimeInfo.cs" />
|
<Compile Include="EnvironmentInfo\RuntimeInfo.cs" />
|
||||||
<Compile Include="EnvironmentInfo\OsInfo.cs" />
|
<Compile Include="EnvironmentInfo\OsInfo.cs" />
|
||||||
|
<Compile Include="Exceptions\NotParentException.cs" />
|
||||||
<Compile Include="Exceptions\NzbDroneException.cs" />
|
<Compile Include="Exceptions\NzbDroneException.cs" />
|
||||||
<Compile Include="IEnumerableExtensions.cs" />
|
<Compile Include="IEnumerableExtensions.cs" />
|
||||||
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
|
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
|
||||||
|
|
|
@ -189,7 +189,11 @@ namespace NzbDrone.Common.Processes
|
||||||
public void WaitForExit(Process process)
|
public void WaitForExit(Process process)
|
||||||
{
|
{
|
||||||
Logger.Trace("Waiting for process {0} to exit.", process.ProcessName);
|
Logger.Trace("Waiting for process {0} to exit.", process.ProcessName);
|
||||||
process.WaitForExit();
|
|
||||||
|
if (!process.HasExited)
|
||||||
|
{
|
||||||
|
process.WaitForExit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPriority(int processId, ProcessPriorityClass priority)
|
public void SetPriority(int processId, ProcessPriorityClass priority)
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Metadata.Files;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CleanupOrphanedMetadataFilesFixture : DbTest<CleanupOrphanedMetadataFiles, MetadataFile>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_delete_metadata_files_that_dont_have_a_coresponding_series()
|
||||||
|
{
|
||||||
|
var metadataFile = Builder<MetadataFile>.CreateNew()
|
||||||
|
.With(m => m.EpisodeFileId = null)
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(metadataFile);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_metadata_files_that_have_a_coresponding_series()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(series);
|
||||||
|
|
||||||
|
var metadataFile = Builder<MetadataFile>.CreateNew()
|
||||||
|
.With(m => m.SeriesId = series.Id)
|
||||||
|
.With(m => m.EpisodeFileId = null)
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(metadataFile);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_delete_metadata_files_that_dont_have_a_coresponding_episode_file()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(series);
|
||||||
|
|
||||||
|
var metadataFile = Builder<MetadataFile>.CreateNew()
|
||||||
|
.With(m => m.SeriesId = series.Id)
|
||||||
|
.With(m => m.EpisodeFileId = 10)
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(metadataFile);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_metadata_files_that_have_a_coresponding_episode_file()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
var episodeFile = Builder<EpisodeFile>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(series);
|
||||||
|
Db.Insert(episodeFile);
|
||||||
|
|
||||||
|
var metadataFile = Builder<MetadataFile>.CreateNew()
|
||||||
|
.With(m => m.SeriesId = series.Id)
|
||||||
|
.With(m => m.EpisodeFileId = episodeFile.Id)
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(metadataFile);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,13 +33,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
||||||
_localEpisode.ExistingFile = true;
|
_localEpisode.ExistingFile = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenNewFile()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(s => s.IsParent(_localEpisode.Series.Path, _localEpisode.Path))
|
|
||||||
.Returns(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_true_if_file_is_under_series_folder()
|
public void should_return_true_if_file_is_under_series_folder()
|
||||||
{
|
{
|
||||||
|
@ -62,8 +55,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_false_if_file_is_in_use()
|
public void should_return_false_if_file_is_in_use()
|
||||||
{
|
{
|
||||||
GivenNewFile();
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(s => s.IsFileLocked(It.IsAny<string>()))
|
.Setup(s => s.IsFileLocked(It.IsAny<string>()))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
@ -74,8 +65,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_true_if_file_is_not_in_use()
|
public void should_return_true_if_file_is_not_in_use()
|
||||||
{
|
{
|
||||||
GivenNewFile();
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(s => s.IsFileLocked(It.IsAny<string>()))
|
.Setup(s => s.IsFileLocked(It.IsAny<string>()))
|
||||||
.Returns(false);
|
.Returns(false);
|
||||||
|
|
|
@ -122,15 +122,6 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
Times.Never());
|
Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_trigger_EpisodeImportedEvent_for_existing_files()
|
|
||||||
{
|
|
||||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() });
|
|
||||||
|
|
||||||
Mocker.GetMock<IEventAggregator>()
|
|
||||||
.Verify(v => v.PublishEvent(It.IsAny<EpisodeImportedEvent>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_import_larger_files_first()
|
public void should_import_larger_files_first()
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,10 +36,6 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
Mocker.GetMock<IEpisodeService>()
|
Mocker.GetMock<IEpisodeService>()
|
||||||
.Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
|
.Setup(c => c.GetEpisodeBySeries(It.IsAny<int>()))
|
||||||
.Returns(_episodes);
|
.Returns(_episodes);
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(s => s.IsParent(It.IsAny<String>(), It.IsAny<String>()))
|
|
||||||
.Returns(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenEpisodeFiles(IEnumerable<EpisodeFile> episodeFiles)
|
private void GivenEpisodeFiles(IEnumerable<EpisodeFile> episodeFiles)
|
||||||
|
@ -58,13 +54,6 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
.Returns(_episodes);
|
.Returns(_episodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenFileIsNotInSeriesFolder()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(s => s.IsParent(It.IsAny<String>(), It.IsAny<String>()))
|
|
||||||
.Returns(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_skip_files_that_exist_in_disk()
|
public void should_skip_files_that_exist_in_disk()
|
||||||
{
|
{
|
||||||
|
@ -118,7 +107,6 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
GivenEpisodeFiles(episodeFiles);
|
GivenEpisodeFiles(episodeFiles);
|
||||||
GivenFileIsNotInSeriesFolder();
|
|
||||||
|
|
||||||
Subject.Execute(new CleanMediaFileDb(0));
|
Subject.Execute(new CleanMediaFileDb(0));
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,7 @@
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFilesFixture.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFilesFixture.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupAdditionalNamingSpecsFixture.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupAdditionalNamingSpecsFixture.cs" />
|
||||||
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasksFixture.cs" />
|
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasksFixture.cs" />
|
||||||
<Compile Include="IndexerSearchTests\SearchDefinitionFixture.cs" />
|
<Compile Include="IndexerSearchTests\SearchDefinitionFixture.cs" />
|
||||||
<Compile Include="IndexerTests\BasicRssParserFixture.cs" />
|
<Compile Include="IndexerTests\BasicRssParserFixture.cs" />
|
||||||
|
|
|
@ -371,5 +371,39 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
||||||
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
|
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||||
.Should().Be("30 Rock - 30.Rock.S01E01.xvid-LOL");
|
.Should().Be("30 Rock - 30.Rock.S01E01.xvid-LOL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_trim_periods_from_end_of_episode_title()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
|
||||||
|
_namingConfig.MultiEpisodeStyle = 3;
|
||||||
|
|
||||||
|
var episode = Builder<Episode>.CreateNew()
|
||||||
|
.With(e => e.Title = "Part 1.")
|
||||||
|
.With(e => e.SeasonNumber = 6)
|
||||||
|
.With(e => e.EpisodeNumber = 6)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
|
||||||
|
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
|
||||||
|
.Should().Be("30 Rock - S06E06 - Part 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_trim_question_marks_from_end_of_episode_title()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
|
||||||
|
_namingConfig.MultiEpisodeStyle = 3;
|
||||||
|
|
||||||
|
var episode = Builder<Episode>.CreateNew()
|
||||||
|
.With(e => e.Title = "Part 1?")
|
||||||
|
.With(e => e.SeasonNumber = 6)
|
||||||
|
.With(e => e.EpisodeNumber = 6)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
|
||||||
|
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
|
||||||
|
.Should().Be("30 Rock - S06E06 - Part 1");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -409,6 +409,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
[TestCase("Burn.Notice.S04E15.Brotherly.Love.GERMAN.DUBBED.WS.WEBRiP.XviD.REPACK-TVP", Language.German)]
|
[TestCase("Burn.Notice.S04E15.Brotherly.Love.GERMAN.DUBBED.WS.WEBRiP.XviD.REPACK-TVP", Language.German)]
|
||||||
[TestCase("Ray Donovan - S01E01.720p.HDtv.x264-Evolve (NLsub)", Language.Norwegian)]
|
[TestCase("Ray Donovan - S01E01.720p.HDtv.x264-Evolve (NLsub)", Language.Norwegian)]
|
||||||
[TestCase("Shield,.The.1x13.Tueurs.De.Flics.FR.DVDRip.XviD", Language.French)]
|
[TestCase("Shield,.The.1x13.Tueurs.De.Flics.FR.DVDRip.XviD", Language.French)]
|
||||||
|
[TestCase("True.Detective.S01E01.1080p.WEB-DL.Rus.Eng.TVKlondike", Language.Russian)]
|
||||||
public void parse_language(string postTitle, Language language)
|
public void parse_language(string postTitle, Language language)
|
||||||
{
|
{
|
||||||
var result = Parser.Parser.ParseTitle(postTitle);
|
var result = Parser.Parser.ParseTitle(postTitle);
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(39)]
|
||||||
|
public class add_metadata_tables : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Create.TableForModel("Metadata")
|
||||||
|
.WithColumn("Enable").AsBoolean().NotNullable()
|
||||||
|
.WithColumn("Name").AsString().NotNullable()
|
||||||
|
.WithColumn("Implementation").AsString().NotNullable()
|
||||||
|
.WithColumn("Settings").AsString().NotNullable()
|
||||||
|
.WithColumn("ConfigContract").AsString().NotNullable();
|
||||||
|
|
||||||
|
Create.TableForModel("MetadataFiles")
|
||||||
|
.WithColumn("SeriesId").AsInt32().NotNullable()
|
||||||
|
.WithColumn("Consumer").AsString().NotNullable()
|
||||||
|
.WithColumn("Type").AsInt32().NotNullable()
|
||||||
|
.WithColumn("RelativePath").AsString().NotNullable()
|
||||||
|
.WithColumn("LastUpdated").AsDateTime().NotNullable()
|
||||||
|
.WithColumn("SeasonNumber").AsInt32().Nullable()
|
||||||
|
.WithColumn("EpisodeFileId").AsInt32().Nullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(40)]
|
||||||
|
public class add_metadata_to_episodes_and_series : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("Series")
|
||||||
|
.AddColumn("Actors").AsString().Nullable()
|
||||||
|
.AddColumn("Ratings").AsString().Nullable()
|
||||||
|
.AddColumn("Genres").AsString().Nullable()
|
||||||
|
.AddColumn("Certification").AsString().Nullable();
|
||||||
|
|
||||||
|
Alter.Table("Episodes")
|
||||||
|
.AddColumn("Ratings").AsString().Nullable()
|
||||||
|
.AddColumn("Images").AsString().Nullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ 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.Notifications;
|
using NzbDrone.Core.Notifications;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
|
@ -35,8 +37,8 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
Mapper.Entity<IndexerDefinition>().RegisterModel("Indexers");
|
Mapper.Entity<IndexerDefinition>().RegisterModel("Indexers");
|
||||||
Mapper.Entity<ScheduledTask>().RegisterModel("ScheduledTasks");
|
Mapper.Entity<ScheduledTask>().RegisterModel("ScheduledTasks");
|
||||||
Mapper.Entity<NotificationDefinition>()
|
Mapper.Entity<NotificationDefinition>().RegisterModel("Notifications");
|
||||||
.RegisterModel("Notifications");
|
Mapper.Entity<MetadataDefinition>().RegisterModel("Metadata");
|
||||||
|
|
||||||
Mapper.Entity<SceneMapping>().RegisterModel("SceneMappings");
|
Mapper.Entity<SceneMapping>().RegisterModel("SceneMappings");
|
||||||
|
|
||||||
|
@ -59,16 +61,13 @@ namespace NzbDrone.Core.Datastore
|
||||||
.Relationships.AutoMapICollectionOrComplexProperties();
|
.Relationships.AutoMapICollectionOrComplexProperties();
|
||||||
|
|
||||||
Mapper.Entity<QualityProfile>().RegisterModel("QualityProfiles");
|
Mapper.Entity<QualityProfile>().RegisterModel("QualityProfiles");
|
||||||
|
|
||||||
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions");
|
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions");
|
||||||
|
|
||||||
Mapper.Entity<Log>().RegisterModel("Logs");
|
Mapper.Entity<Log>().RegisterModel("Logs");
|
||||||
|
|
||||||
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
||||||
|
|
||||||
Mapper.Entity<SeriesStatistics>().MapResultSet();
|
Mapper.Entity<SeriesStatistics>().MapResultSet();
|
||||||
|
|
||||||
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
||||||
|
|
||||||
|
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterMappers()
|
private static void RegisterMappers()
|
||||||
|
@ -85,6 +84,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
|
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
|
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
|
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
|
||||||
|
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterProviderSettingConverter()
|
private static void RegisterProviderSettingConverter()
|
||||||
|
|
|
@ -120,6 +120,11 @@ namespace NzbDrone.Core.History
|
||||||
|
|
||||||
public void Handle(EpisodeImportedEvent message)
|
public void Handle(EpisodeImportedEvent message)
|
||||||
{
|
{
|
||||||
|
if (!message.NewDownload)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var episode in message.EpisodeInfo.Episodes)
|
foreach (var episode in message.EpisodeInfo.Episodes)
|
||||||
{
|
{
|
||||||
var history = new History
|
var history = new History
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
public class CleanupOrphanedMetadataFiles : IHousekeepingTask
|
||||||
|
{
|
||||||
|
private readonly IDatabase _database;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public CleanupOrphanedMetadataFiles(IDatabase database, Logger logger)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clean()
|
||||||
|
{
|
||||||
|
_logger.Trace("Running orphaned episode files cleanup");
|
||||||
|
|
||||||
|
DeleteOrphanedBySeries();
|
||||||
|
DeleteOrphanedByEpisodeFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteOrphanedBySeries()
|
||||||
|
{
|
||||||
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
|
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||||
|
WHERE Id IN (
|
||||||
|
SELECT MetadataFiles.Id FROM MetadataFiles
|
||||||
|
LEFT OUTER JOIN Series
|
||||||
|
ON MetadataFiles.SeriesId = Series.Id
|
||||||
|
WHERE Series.Id IS NULL)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteOrphanedByEpisodeFile()
|
||||||
|
{
|
||||||
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
|
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||||
|
WHERE Id IN (
|
||||||
|
SELECT MetadataFiles.Id FROM MetadataFiles
|
||||||
|
LEFT OUTER JOIN EpisodeFiles
|
||||||
|
ON MetadataFiles.EpisodeFileId = EpisodeFiles.Id
|
||||||
|
WHERE MetadataFiles.EpisodeFileId > 0
|
||||||
|
AND EpisodeFiles.Id IS NULL)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,14 +14,12 @@ namespace NzbDrone.Core.Indexers
|
||||||
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory
|
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory
|
||||||
{
|
{
|
||||||
private readonly IIndexerRepository _providerRepository;
|
private readonly IIndexerRepository _providerRepository;
|
||||||
private readonly IEnumerable<IIndexer> _providers;
|
|
||||||
private readonly INewznabTestService _newznabTestService;
|
private readonly INewznabTestService _newznabTestService;
|
||||||
|
|
||||||
public IndexerFactory(IIndexerRepository providerRepository, IEnumerable<IIndexer> providers, IContainer container, INewznabTestService newznabTestService, Logger logger)
|
public IndexerFactory(IIndexerRepository providerRepository, IEnumerable<IIndexer> providers, IContainer container, INewznabTestService newznabTestService, Logger logger)
|
||||||
: base(providerRepository, providers, container, logger)
|
: base(providerRepository, providers, container, logger)
|
||||||
{
|
{
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_providers = providers;
|
|
||||||
_newznabTestService = newznabTestService;
|
_newznabTestService = newznabTestService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,24 @@ namespace NzbDrone.Core.MediaCover
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
Poster = 1,
|
Poster = 1,
|
||||||
Banner = 2,
|
Banner = 2,
|
||||||
Fanart = 3
|
Fanart = 3,
|
||||||
|
Screenshot = 4,
|
||||||
|
Headshot = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MediaCover : IEmbeddedDocument
|
public class MediaCover : IEmbeddedDocument
|
||||||
{
|
{
|
||||||
public MediaCoverTypes CoverType { get; set; }
|
public MediaCoverTypes CoverType { get; set; }
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
public MediaCover()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaCover(MediaCoverTypes coverType, string url)
|
||||||
|
{
|
||||||
|
CoverType = coverType;
|
||||||
|
Url = url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,13 @@ using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaCover
|
namespace NzbDrone.Core.MediaCover
|
||||||
{
|
{
|
||||||
|
public interface IMapCoversToLocal
|
||||||
|
{
|
||||||
|
void ConvertToLocalUrls(int seriesId, IEnumerable<MediaCover> covers);
|
||||||
|
string GetCoverPath(int seriesId, MediaCoverTypes mediaCoverTypes);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public class MediaCoverService :
|
public class MediaCoverService :
|
||||||
IHandleAsync<SeriesUpdatedEvent>,
|
IHandleAsync<SeriesUpdatedEvent>,
|
||||||
IHandleAsync<SeriesDeletedEvent>,
|
IHandleAsync<SeriesDeletedEvent>,
|
||||||
|
@ -22,25 +29,53 @@ namespace NzbDrone.Core.MediaCover
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly ICoverExistsSpecification _coverExistsSpecification;
|
private readonly ICoverExistsSpecification _coverExistsSpecification;
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private readonly string _coverRootFolder;
|
private readonly string _coverRootFolder;
|
||||||
|
|
||||||
public MediaCoverService(IHttpProvider httpProvider, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo,
|
public MediaCoverService(IHttpProvider httpProvider,
|
||||||
ICoverExistsSpecification coverExistsSpecification, IConfigFileProvider configFileProvider, Logger logger)
|
IDiskProvider diskProvider,
|
||||||
|
IAppFolderInfo appFolderInfo,
|
||||||
|
ICoverExistsSpecification coverExistsSpecification,
|
||||||
|
IConfigFileProvider configFileProvider,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
Logger logger)
|
||||||
{
|
{
|
||||||
_httpProvider = httpProvider;
|
_httpProvider = httpProvider;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_coverExistsSpecification = coverExistsSpecification;
|
_coverExistsSpecification = coverExistsSpecification;
|
||||||
_configFileProvider = configFileProvider;
|
_configFileProvider = configFileProvider;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_coverRootFolder = appFolderInfo.GetMediaCoverPath();
|
_coverRootFolder = appFolderInfo.GetMediaCoverPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleAsync(SeriesUpdatedEvent message)
|
public string GetCoverPath(int seriesId, MediaCoverTypes coverTypes)
|
||||||
{
|
{
|
||||||
EnsureCovers(message.Series);
|
return Path.Combine(GetSeriesCoverPath(seriesId), coverTypes.ToString().ToLower() + ".jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConvertToLocalUrls(int seriesId, IEnumerable<MediaCover> covers)
|
||||||
|
{
|
||||||
|
foreach (var mediaCover in covers)
|
||||||
|
{
|
||||||
|
var filePath = GetCoverPath(seriesId, mediaCover.CoverType);
|
||||||
|
|
||||||
|
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
|
||||||
|
|
||||||
|
if (_diskProvider.FileExists(filePath))
|
||||||
|
{
|
||||||
|
var lastWrite = _diskProvider.GetLastFileWrite(filePath);
|
||||||
|
mediaCover.Url += "?lastWrite=" + lastWrite.Ticks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSeriesCoverPath(int seriesId)
|
||||||
|
{
|
||||||
|
return Path.Combine(_coverRootFolder, seriesId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureCovers(Series series)
|
private void EnsureCovers(Series series)
|
||||||
|
@ -72,7 +107,12 @@ namespace NzbDrone.Core.MediaCover
|
||||||
|
|
||||||
_logger.Info("Downloading {0} for {1} {2}", cover.CoverType, series, cover.Url);
|
_logger.Info("Downloading {0} for {1} {2}", cover.CoverType, series, cover.Url);
|
||||||
_httpProvider.DownloadFile(cover.Url, fileName);
|
_httpProvider.DownloadFile(cover.Url, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleAsync(SeriesUpdatedEvent message)
|
||||||
|
{
|
||||||
|
EnsureCovers(message.Series);
|
||||||
|
_eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Series));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleAsync(SeriesDeletedEvent message)
|
public void HandleAsync(SeriesDeletedEvent message)
|
||||||
|
@ -83,36 +123,5 @@ namespace NzbDrone.Core.MediaCover
|
||||||
_diskProvider.DeleteFolder(path, true);
|
_diskProvider.DeleteFolder(path, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCoverPath(int seriesId, MediaCoverTypes coverTypes)
|
|
||||||
{
|
|
||||||
return Path.Combine(GetSeriesCoverPath(seriesId), coverTypes.ToString().ToLower() + ".jpg");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetSeriesCoverPath(int seriesId)
|
|
||||||
{
|
|
||||||
return Path.Combine(_coverRootFolder, seriesId.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConvertToLocalUrls(int seriesId, IEnumerable<MediaCover> covers)
|
|
||||||
{
|
|
||||||
foreach (var mediaCover in covers)
|
|
||||||
{
|
|
||||||
var filePath = GetCoverPath(seriesId, mediaCover.CoverType);
|
|
||||||
|
|
||||||
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
|
|
||||||
|
|
||||||
if (_diskProvider.FileExists(filePath))
|
|
||||||
{
|
|
||||||
var lastWrite = _diskProvider.GetLastFileWrite(filePath);
|
|
||||||
mediaCover.Url += "?lastWrite=" + lastWrite.Ticks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IMapCoversToLocal
|
|
||||||
{
|
|
||||||
void ConvertToLocalUrls(int seriesId, IEnumerable<MediaCover> covers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaCover
|
||||||
|
{
|
||||||
|
public class MediaCoversUpdatedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Series Series { get; set; }
|
||||||
|
|
||||||
|
public MediaCoversUpdatedEvent(Series series)
|
||||||
|
{
|
||||||
|
Series = series;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -87,9 +87,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
_mediaFileService.Add(episodeFile);
|
_mediaFileService.Add(episodeFile);
|
||||||
imported.Add(importDecision);
|
imported.Add(importDecision);
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload));
|
||||||
|
|
||||||
if (newDownload)
|
if (newDownload)
|
||||||
{
|
{
|
||||||
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile));
|
|
||||||
_eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode, episodeFile, oldFiles));
|
_eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode, episodeFile, oldFiles));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using NzbDrone.Common.Messaging;
|
using System;
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.Events
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
@ -7,11 +8,13 @@ namespace NzbDrone.Core.MediaFiles.Events
|
||||||
{
|
{
|
||||||
public LocalEpisode EpisodeInfo { get; private set; }
|
public LocalEpisode EpisodeInfo { get; private set; }
|
||||||
public EpisodeFile ImportedEpisode { get; private set; }
|
public EpisodeFile ImportedEpisode { get; private set; }
|
||||||
|
public Boolean NewDownload { get; set; }
|
||||||
|
|
||||||
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode)
|
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload)
|
||||||
{
|
{
|
||||||
EpisodeInfo = episodeInfo;
|
EpisodeInfo = episodeInfo;
|
||||||
ImportedEpisode = importedEpisode;
|
ImportedEpisode = importedEpisode;
|
||||||
|
NewDownload = newDownload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -30,6 +30,5 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
.AndWhere(c => c.SeasonNumber == seasonNumber)
|
.AndWhere(c => c.SeasonNumber == seasonNumber)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_diskProvider.IsParent(series.Path, episodeFile.Path))
|
if (!DiskProviderBase.IsParent(series.Path, episodeFile.Path))
|
||||||
{
|
{
|
||||||
_logger.Trace("File [{0}] does not belong to this series, removing from db", episodeFile.Path);
|
_logger.Trace("File [{0}] does not belong to this series, removing from db", episodeFile.Path);
|
||||||
_mediaFileService.Delete(episodeFile);
|
_mediaFileService.Delete(episodeFile);
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Metadata.Files;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata.Consumers.Fake
|
||||||
|
{
|
||||||
|
public class FakeMetadata : MetadataBase<FakeMetadataSettings>
|
||||||
|
{
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IHttpProvider _httpProvider;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public FakeMetadata(IDiskProvider diskProvider, IHttpProvider httpProvider, Logger logger)
|
||||||
|
: base(diskProvider, httpProvider, logger)
|
||||||
|
{
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_httpProvider = httpProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AfterRename(Series series)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
using FluentValidation;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata.Consumers.Fake
|
||||||
|
{
|
||||||
|
public class FakeMetadataSettingsValidator : AbstractValidator<FakeMetadataSettings>
|
||||||
|
{
|
||||||
|
public FakeMetadataSettingsValidator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FakeMetadataSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly FakeMetadataSettingsValidator Validator = new FakeMetadataSettingsValidator();
|
||||||
|
|
||||||
|
public FakeMetadataSettings()
|
||||||
|
{
|
||||||
|
FakeSetting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Fake Setting", Type = FieldType.Checkbox)]
|
||||||
|
public Boolean FakeSetting { get; set; }
|
||||||
|
|
||||||
|
public bool IsValid
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationResult Validate()
|
||||||
|
{
|
||||||
|
return Validator.Validate(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,385 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.MediaCover;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Metadata.Files;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
|
{
|
||||||
|
public class XbmcMetadata : MetadataBase<XbmcMetadataSettings>
|
||||||
|
{
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly IMapCoversToLocal _mediaCoverService;
|
||||||
|
private readonly IMediaFileService _mediaFileService;
|
||||||
|
private readonly IMetadataFileService _metadataFileService;
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IHttpProvider _httpProvider;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public XbmcMetadata(IEventAggregator eventAggregator,
|
||||||
|
IMapCoversToLocal mediaCoverService,
|
||||||
|
IMediaFileService mediaFileService,
|
||||||
|
IMetadataFileService metadataFileService,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
IHttpProvider httpProvider,
|
||||||
|
Logger logger)
|
||||||
|
: base(diskProvider, httpProvider, logger)
|
||||||
|
{
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_mediaCoverService = mediaCoverService;
|
||||||
|
_mediaFileService = mediaFileService;
|
||||||
|
_metadataFileService = metadataFileService;
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_httpProvider = httpProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Regex SeriesImagesRegex = new Regex(@"^(?<type>poster|banner|fanart)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
private static readonly Regex SeasonImagesRegex = new Regex(@"^season(?<season>\d{2,}|-all|-specials)-(?<type>poster|banner|fanart)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
private static readonly Regex EpisodeImageRegex = new Regex(@"-thumb\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles)
|
||||||
|
{
|
||||||
|
if (Settings.SeriesMetadata)
|
||||||
|
{
|
||||||
|
EnsureFolder(series.Path);
|
||||||
|
WriteTvShowNfo(series, existingMetadataFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.SeriesImages)
|
||||||
|
{
|
||||||
|
EnsureFolder(series.Path);
|
||||||
|
WriteSeriesImages(series, existingMetadataFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.SeasonImages)
|
||||||
|
{
|
||||||
|
EnsureFolder(series.Path);
|
||||||
|
WriteSeasonImages(series, existingMetadataFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
|
||||||
|
{
|
||||||
|
if (Settings.EpisodeMetadata)
|
||||||
|
{
|
||||||
|
WriteEpisodeNfo(series, episodeFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.EpisodeImages)
|
||||||
|
{
|
||||||
|
WriteEpisodeImages(series, episodeFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AfterRename(Series series)
|
||||||
|
{
|
||||||
|
var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
|
||||||
|
var episodeFilesMetadata = _metadataFileService.GetFilesBySeries(series.Id).Where(c => c.EpisodeFileId > 0).ToList();
|
||||||
|
|
||||||
|
foreach (var episodeFile in episodeFiles)
|
||||||
|
{
|
||||||
|
var metadataFiles = episodeFilesMetadata.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
||||||
|
var episodeFilenameWithoutExtension =
|
||||||
|
Path.GetFileNameWithoutExtension(DiskProviderBase.GetRelativePath(series.Path, episodeFile.Path));
|
||||||
|
|
||||||
|
foreach (var metadataFile in metadataFiles)
|
||||||
|
{
|
||||||
|
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(metadataFile.RelativePath);
|
||||||
|
var extension = Path.GetExtension(metadataFile.RelativePath);
|
||||||
|
|
||||||
|
if (!fileNameWithoutExtension.Equals(episodeFilenameWithoutExtension))
|
||||||
|
{
|
||||||
|
var source = Path.Combine(series.Path, metadataFile.RelativePath);
|
||||||
|
var destination = Path.Combine(series.Path, fileNameWithoutExtension + extension);
|
||||||
|
|
||||||
|
_diskProvider.MoveFile(source, destination);
|
||||||
|
metadataFile.RelativePath = fileNameWithoutExtension + extension;
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadataFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||||
|
{
|
||||||
|
var filename = Path.GetFileName(path);
|
||||||
|
|
||||||
|
if (filename == null) return null;
|
||||||
|
|
||||||
|
var metadata = new MetadataFile
|
||||||
|
{
|
||||||
|
SeriesId = series.Id,
|
||||||
|
Consumer = GetType().Name,
|
||||||
|
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (SeriesImagesRegex.IsMatch(filename))
|
||||||
|
{
|
||||||
|
metadata.Type = MetadataType.SeriesImage;
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
var seasonMatch = SeasonImagesRegex.Match(filename);
|
||||||
|
|
||||||
|
if (seasonMatch.Success)
|
||||||
|
{
|
||||||
|
metadata.Type = MetadataType.SeasonImage;
|
||||||
|
|
||||||
|
var seasonNumber = seasonMatch.Groups["season"].Value;
|
||||||
|
|
||||||
|
if (seasonNumber.Contains("specials"))
|
||||||
|
{
|
||||||
|
metadata.SeasonNumber = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
metadata.SeasonNumber = Convert.ToInt32(seasonNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EpisodeImageRegex.IsMatch(filename))
|
||||||
|
{
|
||||||
|
metadata.Type = MetadataType.EpisodeImage;
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename.Equals("tvshow.nfo", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
metadata.Type = MetadataType.SeriesMetadata;
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseResult = Parser.Parser.ParseTitle(filename);
|
||||||
|
|
||||||
|
if (parseResult != null &&
|
||||||
|
!parseResult.FullSeason &&
|
||||||
|
Path.GetExtension(filename) == ".nfo")
|
||||||
|
{
|
||||||
|
metadata.Type = MetadataType.EpisodeMetadata;
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteTvShowNfo(Series series, List<MetadataFile> existingMetadataFiles)
|
||||||
|
{
|
||||||
|
_logger.Trace("Generating tvshow.nfo for: {0}", series.Title);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var xws = new XmlWriterSettings();
|
||||||
|
xws.OmitXmlDeclaration = true;
|
||||||
|
xws.Indent = false;
|
||||||
|
|
||||||
|
using (var xw = XmlWriter.Create(sb, xws))
|
||||||
|
{
|
||||||
|
var tvShow = new XElement("tvshow");
|
||||||
|
|
||||||
|
tvShow.Add(new XElement("title", series.Title));
|
||||||
|
tvShow.Add(new XElement("rating", series.Ratings.Percentage));
|
||||||
|
tvShow.Add(new XElement("plot", series.Overview));
|
||||||
|
|
||||||
|
//Todo: probably will need to use TVDB to use this feature...
|
||||||
|
// tvShow.Add(new XElement("episodeguide", new XElement("url", episodeGuideUrl)));
|
||||||
|
// tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl));
|
||||||
|
tvShow.Add(new XElement("mpaa", series.Certification));
|
||||||
|
tvShow.Add(new XElement("id", series.TvdbId));
|
||||||
|
|
||||||
|
foreach (var genre in series.Genres)
|
||||||
|
{
|
||||||
|
tvShow.Add(new XElement("genre", genre));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series.FirstAired.HasValue)
|
||||||
|
{
|
||||||
|
tvShow.Add(new XElement("premiered", series.FirstAired.Value.ToString("yyyy-MM-dd")));
|
||||||
|
}
|
||||||
|
|
||||||
|
tvShow.Add(new XElement("studio", series.Network));
|
||||||
|
|
||||||
|
foreach (var actor in series.Actors)
|
||||||
|
{
|
||||||
|
tvShow.Add(new XElement("actor",
|
||||||
|
new XElement("name", actor.Name),
|
||||||
|
new XElement("role", actor.Character),
|
||||||
|
new XElement("thumb", actor.Images.First())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
var doc = new XDocument(tvShow);
|
||||||
|
doc.Save(xw);
|
||||||
|
|
||||||
|
_logger.Debug("Saving tvshow.nfo for {0}", series.Title);
|
||||||
|
|
||||||
|
var path = Path.Combine(series.Path, "tvshow.nfo");
|
||||||
|
|
||||||
|
_diskProvider.WriteAllText(path, doc.ToString());
|
||||||
|
|
||||||
|
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesMetadata) ??
|
||||||
|
new MetadataFile
|
||||||
|
{
|
||||||
|
SeriesId = series.Id,
|
||||||
|
Consumer = GetType().Name,
|
||||||
|
Type = MetadataType.SeriesMetadata,
|
||||||
|
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
|
||||||
|
};
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
|
||||||
|
{
|
||||||
|
foreach (var image in series.Images)
|
||||||
|
{
|
||||||
|
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
|
||||||
|
var destination = Path.Combine(series.Path, image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(source));
|
||||||
|
|
||||||
|
//TODO: Do we want to overwrite the file if it exists?
|
||||||
|
if (_diskProvider.FileExists(destination))
|
||||||
|
{
|
||||||
|
_logger.Trace("Series image: {0} already exists.", image.CoverType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_diskProvider.CopyFile(source, destination, false);
|
||||||
|
|
||||||
|
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage) ??
|
||||||
|
new MetadataFile
|
||||||
|
{
|
||||||
|
SeriesId = series.Id,
|
||||||
|
Consumer = GetType().Name,
|
||||||
|
Type = MetadataType.SeriesImage,
|
||||||
|
RelativePath = DiskProviderBase.GetRelativePath(series.Path, destination)
|
||||||
|
};
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
|
||||||
|
{
|
||||||
|
foreach (var season in series.Seasons)
|
||||||
|
{
|
||||||
|
foreach (var image in season.Images)
|
||||||
|
{
|
||||||
|
var filename = String.Format("season{0:00}-{1}.jpg", season.SeasonNumber, image.CoverType.ToString().ToLower());
|
||||||
|
|
||||||
|
if (season.SeasonNumber == 0)
|
||||||
|
{
|
||||||
|
filename = String.Format("season-specials-{0}.jpg", image.CoverType.ToString().ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = Path.Combine(series.Path, filename);
|
||||||
|
|
||||||
|
DownloadImage(series, image.Url, path);
|
||||||
|
|
||||||
|
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
|
||||||
|
c.SeasonNumber == season.SeasonNumber) ??
|
||||||
|
new MetadataFile
|
||||||
|
{
|
||||||
|
SeriesId = series.Id,
|
||||||
|
SeasonNumber = season.SeasonNumber,
|
||||||
|
Consumer = GetType().Name,
|
||||||
|
Type = MetadataType.SeriesMetadata,
|
||||||
|
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
|
||||||
|
};
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEpisodeNfo(Series series, EpisodeFile episodeFile)
|
||||||
|
{
|
||||||
|
var filename = episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".nfo");
|
||||||
|
|
||||||
|
_logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path);
|
||||||
|
|
||||||
|
var xmlResult = String.Empty;
|
||||||
|
foreach (var episode in episodeFile.Episodes.Value)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var xws = new XmlWriterSettings();
|
||||||
|
xws.OmitXmlDeclaration = true;
|
||||||
|
xws.Indent = false;
|
||||||
|
|
||||||
|
using (var xw = XmlWriter.Create(sb, xws))
|
||||||
|
{
|
||||||
|
var doc = new XDocument();
|
||||||
|
|
||||||
|
var details = new XElement("episodedetails");
|
||||||
|
details.Add(new XElement("title", episode.Title));
|
||||||
|
details.Add(new XElement("season", episode.SeasonNumber));
|
||||||
|
details.Add(new XElement("episode", episode.EpisodeNumber));
|
||||||
|
details.Add(new XElement("aired", episode.AirDate));
|
||||||
|
details.Add(new XElement("plot", episode.Overview));
|
||||||
|
details.Add(new XElement("displayseason", episode.SeasonNumber));
|
||||||
|
details.Add(new XElement("displayepisode", episode.EpisodeNumber));
|
||||||
|
details.Add(new XElement("thumb", episode.Images.Single(i => i.CoverType == MediaCoverTypes.Screenshot).Url));
|
||||||
|
details.Add(new XElement("watched", "false"));
|
||||||
|
details.Add(new XElement("rating", episode.Ratings.Percentage));
|
||||||
|
|
||||||
|
//Todo: get guest stars, writer and director
|
||||||
|
//details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));
|
||||||
|
//details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault()));
|
||||||
|
|
||||||
|
doc.Add(details);
|
||||||
|
doc.Save(xw);
|
||||||
|
|
||||||
|
xmlResult += doc.ToString();
|
||||||
|
xmlResult += Environment.NewLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("Saving episodedetails to: {0}", filename);
|
||||||
|
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||||
|
|
||||||
|
var metadata = new MetadataFile
|
||||||
|
{
|
||||||
|
SeriesId = series.Id,
|
||||||
|
EpisodeFileId = episodeFile.Id,
|
||||||
|
Consumer = GetType().Name,
|
||||||
|
Type = MetadataType.SeasonImage,
|
||||||
|
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||||
|
};
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEpisodeImages(Series series, EpisodeFile episodeFile)
|
||||||
|
{
|
||||||
|
var screenshot = episodeFile.Episodes.Value.First().Images.Single(i => i.CoverType == MediaCoverTypes.Screenshot);
|
||||||
|
|
||||||
|
var filename = Path.ChangeExtension(episodeFile.Path, "").Trim('.') + "-thumb.jpg";
|
||||||
|
|
||||||
|
DownloadImage(series, screenshot.Url, filename);
|
||||||
|
|
||||||
|
var metadata = new MetadataFile
|
||||||
|
{
|
||||||
|
SeriesId = series.Id,
|
||||||
|
EpisodeFileId = episodeFile.Id,
|
||||||
|
Consumer = GetType().Name,
|
||||||
|
Type = MetadataType.SeasonImage,
|
||||||
|
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||||
|
};
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System;
|
||||||
|
using FluentValidation;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
|
{
|
||||||
|
public class XbmcSettingsValidator : AbstractValidator<XbmcMetadataSettings>
|
||||||
|
{
|
||||||
|
public XbmcSettingsValidator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class XbmcMetadataSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly XbmcSettingsValidator Validator = new XbmcSettingsValidator();
|
||||||
|
|
||||||
|
public XbmcMetadataSettings()
|
||||||
|
{
|
||||||
|
SeriesMetadata = true;
|
||||||
|
EpisodeMetadata = true;
|
||||||
|
SeriesImages = true;
|
||||||
|
SeasonImages = true;
|
||||||
|
EpisodeImages = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Series Metadata", Type = FieldType.Checkbox)]
|
||||||
|
public Boolean SeriesMetadata { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "Episode Metadata", Type = FieldType.Checkbox)]
|
||||||
|
public Boolean EpisodeMetadata { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "Series Images", Type = FieldType.Checkbox)]
|
||||||
|
public Boolean SeriesImages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Label = "Season Images", Type = FieldType.Checkbox)]
|
||||||
|
public Boolean SeasonImages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(4, Label = "Episode Images", Type = FieldType.Checkbox)]
|
||||||
|
public Boolean EpisodeImages { get; set; }
|
||||||
|
|
||||||
|
public bool IsValid
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationResult Validate()
|
||||||
|
{
|
||||||
|
return Validator.Validate(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Metadata.Files;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata
|
||||||
|
{
|
||||||
|
public interface IMetadata : IProvider
|
||||||
|
{
|
||||||
|
void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles);
|
||||||
|
void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
|
||||||
|
void AfterRename(Series series);
|
||||||
|
MetadataFile FindMetadataFile(Series series, string path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata
|
||||||
|
{
|
||||||
|
public class MetadataDefinition : ProviderDefinition
|
||||||
|
{
|
||||||
|
public Boolean Enable { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Composition;
|
||||||
|
using NzbDrone.Core.Metadata.Consumers.Fake;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata
|
||||||
|
{
|
||||||
|
public interface IMetadataFactory : IProviderFactory<IMetadata, MetadataDefinition>
|
||||||
|
{
|
||||||
|
List<IMetadata> Enabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MetadataFactory : ProviderFactory<IMetadata, MetadataDefinition>, IMetadataFactory
|
||||||
|
{
|
||||||
|
private readonly IMetadataRepository _providerRepository;
|
||||||
|
|
||||||
|
public MetadataFactory(IMetadataRepository providerRepository, IEnumerable<IMetadata> providers, IContainer container, Logger logger)
|
||||||
|
: base(providerRepository, providers, container, logger)
|
||||||
|
{
|
||||||
|
_providerRepository = providerRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeProviders()
|
||||||
|
{
|
||||||
|
var definitions = new List<MetadataDefinition>();
|
||||||
|
|
||||||
|
foreach (var provider in _providers)
|
||||||
|
{
|
||||||
|
if (provider.GetType() == typeof(FakeMetadata)) continue;;
|
||||||
|
|
||||||
|
definitions.Add(new MetadataDefinition
|
||||||
|
{
|
||||||
|
Enable = false,
|
||||||
|
Name = provider.GetType().Name.Replace("Metadata", ""),
|
||||||
|
Implementation = provider.GetType().Name,
|
||||||
|
Settings = (IProviderConfig)Activator.CreateInstance(provider.ConfigContract)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentProviders = All();
|
||||||
|
|
||||||
|
var newProviders = definitions.Where(def => currentProviders.All(c => c.Implementation != def.Implementation)).ToList();
|
||||||
|
|
||||||
|
if (newProviders.Any())
|
||||||
|
{
|
||||||
|
_providerRepository.InsertMany(newProviders.Cast<MetadataDefinition>().ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IMetadata> Enabled()
|
||||||
|
{
|
||||||
|
return GetAvailableProviders().Where(n => ((MetadataDefinition)n.Definition).Enable).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata
|
||||||
|
{
|
||||||
|
public interface IMetadataRepository : IProviderRepository<MetadataDefinition>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MetadataRepository : ProviderRepository<MetadataDefinition>, IMetadataRepository
|
||||||
|
{
|
||||||
|
public MetadataRepository(IDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Core.MediaCover;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Metadata.Files;
|
||||||
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata
|
||||||
|
{
|
||||||
|
public class NotificationService
|
||||||
|
: IHandle<MediaCoversUpdatedEvent>,
|
||||||
|
IHandle<EpisodeImportedEvent>,
|
||||||
|
IHandle<SeriesRenamedEvent>
|
||||||
|
{
|
||||||
|
private readonly IMetadataFactory _metadataFactory;
|
||||||
|
private readonly MetadataFileService _metadataFileService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public NotificationService(IMetadataFactory metadataFactory, MetadataFileService metadataFileService, Logger logger)
|
||||||
|
{
|
||||||
|
_metadataFactory = metadataFactory;
|
||||||
|
_metadataFileService = metadataFileService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MediaCoversUpdatedEvent message)
|
||||||
|
{
|
||||||
|
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
||||||
|
|
||||||
|
foreach (var consumer in _metadataFactory.Enabled())
|
||||||
|
{
|
||||||
|
consumer.OnSeriesUpdated(message.Series, seriesMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(EpisodeImportedEvent message)
|
||||||
|
{
|
||||||
|
foreach (var consumer in _metadataFactory.Enabled())
|
||||||
|
{
|
||||||
|
consumer.OnEpisodeImport(message.EpisodeInfo.Series, message.ImportedEpisode, message.NewDownload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(SeriesRenamedEvent message)
|
||||||
|
{
|
||||||
|
foreach (var consumer in _metadataFactory.Enabled())
|
||||||
|
{
|
||||||
|
consumer.AfterRename(message.Series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata
|
||||||
|
{
|
||||||
|
public enum MetadataType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
SeriesMetadata = 1,
|
||||||
|
EpisodeMetadata = 2,
|
||||||
|
SeriesImage = 3,
|
||||||
|
SeasonImage = 4,
|
||||||
|
EpisodeImage = 5
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Metadata.Files;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata
|
||||||
|
{
|
||||||
|
public class ExistingMetadataService : IHandleAsync<SeriesUpdatedEvent>
|
||||||
|
{
|
||||||
|
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 HandleAsync(SeriesUpdatedEvent message)
|
||||||
|
{
|
||||||
|
if (!_diskProvider.FolderExists(message.Series.Path)) return;
|
||||||
|
|
||||||
|
_logger.Trace("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())).ToList();
|
||||||
|
var filteredFiles = _metadataFileService.FilterExistingFiles(possibleMetadataFiles, message.Series);
|
||||||
|
|
||||||
|
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.GetEpisodes(possibleMetadataFile, message.Series, false);
|
||||||
|
|
||||||
|
if (localEpisode == null)
|
||||||
|
{
|
||||||
|
_logger.Trace("Cannot find related episodes for: {0}", possibleMetadataFile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1)
|
||||||
|
{
|
||||||
|
_logger.Trace("Metadata file: {0} does not match existing files.", possibleMetadataFile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
_metadataFileService.Upsert(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata.Files
|
||||||
|
{
|
||||||
|
public class MetadataFile : ModelBase
|
||||||
|
{
|
||||||
|
public Int32 SeriesId { get; set; }
|
||||||
|
public String Consumer { get; set; }
|
||||||
|
public MetadataType Type { get; set; }
|
||||||
|
public String RelativePath { get; set; }
|
||||||
|
public DateTime LastUpdated { get; set; }
|
||||||
|
public Int32? EpisodeFileId { get; set; }
|
||||||
|
public Int32? SeasonNumber { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata.Files
|
||||||
|
{
|
||||||
|
public interface IMetadataFileRepository : IBasicRepository<MetadataFile>
|
||||||
|
{
|
||||||
|
void DeleteForSeries(int seriesId);
|
||||||
|
void DeleteForSeason(int seriesId, int seasonNumber);
|
||||||
|
void DeleteForEpisodeFile(int episodeFileId);
|
||||||
|
List<MetadataFile> GetFilesBySeries(int seriesId);
|
||||||
|
List<MetadataFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||||
|
List<MetadataFile> GetFilesByEpisodeFile(int episodeFileId);
|
||||||
|
MetadataFile FindByPath(string path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MetadataFileRepository : BasicRepository<MetadataFile>, IMetadataFileRepository
|
||||||
|
{
|
||||||
|
public MetadataFileRepository(IDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteForSeries(int seriesId)
|
||||||
|
{
|
||||||
|
Delete(c => c.SeriesId == seriesId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteForSeason(int seriesId, int seasonNumber)
|
||||||
|
{
|
||||||
|
Delete(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteForEpisodeFile(int episodeFileId)
|
||||||
|
{
|
||||||
|
Delete(c => c.EpisodeFileId == episodeFileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MetadataFile> GetFilesBySeries(int seriesId)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.SeriesId == seriesId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MetadataFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MetadataFile> GetFilesByEpisodeFile(int episodeFileId)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.EpisodeFileId == episodeFileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MetadataFile FindByPath(string path)
|
||||||
|
{
|
||||||
|
return Query.SingleOrDefault(c => c.RelativePath == path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
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);
|
||||||
|
MetadataFile Upsert(MetadataFile metadataFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MetadataFileService : IMetadataFileService,
|
||||||
|
IHandleAsync<SeriesDeletedEvent>,
|
||||||
|
IHandleAsync<EpisodeFileDeletedEvent>,
|
||||||
|
IHandle<MetadataFileUpdated>
|
||||||
|
{
|
||||||
|
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 MetadataFile Upsert(MetadataFile metadataFile)
|
||||||
|
{
|
||||||
|
metadataFile.LastUpdated = DateTime.UtcNow;
|
||||||
|
return _repository.Upsert(metadataFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleAsync(SeriesDeletedEvent message)
|
||||||
|
{
|
||||||
|
_logger.Trace("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.Trace("Deleting Metadata from database for episode file: {0}", episodeFile);
|
||||||
|
_repository.DeleteForEpisodeFile(episodeFile.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(MetadataFileUpdated message)
|
||||||
|
{
|
||||||
|
Upsert(message.Metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata.Files
|
||||||
|
{
|
||||||
|
public class MetadataFileUpdated : IEvent
|
||||||
|
{
|
||||||
|
public MetadataFile Metadata { get; set; }
|
||||||
|
|
||||||
|
public MetadataFileUpdated(MetadataFile metadata)
|
||||||
|
{
|
||||||
|
Metadata = metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Metadata.Files;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata
|
||||||
|
{
|
||||||
|
public abstract class MetadataBase<TSettings> : IMetadata where TSettings : IProviderConfig, new()
|
||||||
|
{
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IHttpProvider _httpProvider;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
protected MetadataBase(IDiskProvider diskProvider, IHttpProvider httpProvider, Logger logger)
|
||||||
|
{
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_httpProvider = httpProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type ConfigContract
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return typeof(TSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ProviderDefinition> DefaultDefinitions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new List<ProviderDefinition>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderDefinition Definition { get; set; }
|
||||||
|
|
||||||
|
public abstract void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles);
|
||||||
|
public abstract void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
|
||||||
|
public abstract void AfterRename(Series series);
|
||||||
|
public abstract MetadataFile FindMetadataFile(Series series, string path);
|
||||||
|
|
||||||
|
protected TSettings Settings
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (TSettings)Definition.Settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void EnsureFolder(string path)
|
||||||
|
{
|
||||||
|
_diskProvider.CreateFolder(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DownloadImage(Series series, string url, string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_diskProvider.FileExists(path))
|
||||||
|
{
|
||||||
|
_logger.Trace("Image already exists: {0}, will not download again.", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpProvider.DownloadFile(url, path);
|
||||||
|
}
|
||||||
|
catch (WebException e)
|
||||||
|
{
|
||||||
|
_logger.Warn(string.Format("Couldn't download image {0} for {1}. {2}", url, series, e.Message));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Couldn't download image " + url + " for " + series, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return GetType().Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MetadataSource.Trakt
|
||||||
|
{
|
||||||
|
public class Actor
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public string character { get; set; }
|
||||||
|
public Images images { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,5 +13,7 @@
|
||||||
public int first_aired_utc { get; set; }
|
public int first_aired_utc { get; set; }
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public string screen { get; set; }
|
public string screen { get; set; }
|
||||||
|
public Ratings ratings { get; set; }
|
||||||
|
public Images images { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -29,6 +29,8 @@ namespace NzbDrone.Core.MetadataSource.Trakt
|
||||||
public Images images { get; set; }
|
public Images images { get; set; }
|
||||||
public List<string> genres { get; set; }
|
public List<string> genres { get; set; }
|
||||||
public List<Season> seasons { get; set; }
|
public List<Season> seasons { get; set; }
|
||||||
|
public Ratings ratings { get; set; }
|
||||||
|
public People people { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SearchShow
|
public class SearchShow
|
||||||
|
|
|
@ -5,5 +5,7 @@
|
||||||
public string poster { get; set; }
|
public string poster { get; set; }
|
||||||
public string fanart { get; set; }
|
public string fanart { get; set; }
|
||||||
public string banner { get; set; }
|
public string banner { get; set; }
|
||||||
|
public string screen { get; set; }
|
||||||
|
public string headshot { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MetadataSource.Trakt
|
||||||
|
{
|
||||||
|
public class People
|
||||||
|
{
|
||||||
|
public List<Actor> actors { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MetadataSource.Trakt
|
||||||
|
{
|
||||||
|
public class Ratings
|
||||||
|
{
|
||||||
|
public Int32 percentage { get; set; }
|
||||||
|
public Int32 votes { get; set; }
|
||||||
|
public Int32 loved { get; set; }
|
||||||
|
public Int32 hated { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,5 +8,6 @@ namespace NzbDrone.Core.MetadataSource.Trakt
|
||||||
public List<Episode> episodes { get; set; }
|
public List<Episode> episodes { get; set; }
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public string poster { get; set; }
|
public string poster { get; set; }
|
||||||
|
public Images images { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,7 +8,9 @@ using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.MetadataSource.Trakt;
|
using NzbDrone.Core.MetadataSource.Trakt;
|
||||||
|
using NzbDrone.Core.Notifications.Xbmc.Model;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
using Omu.ValueInjecter;
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
using Episode = NzbDrone.Core.Tv.Episode;
|
using Episode = NzbDrone.Core.Tv.Episode;
|
||||||
using NzbDrone.Core.Rest;
|
using NzbDrone.Core.Rest;
|
||||||
|
@ -79,15 +81,16 @@ namespace NzbDrone.Core.MetadataSource
|
||||||
series.AirTime = show.air_time_utc;
|
series.AirTime = show.air_time_utc;
|
||||||
series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", "");
|
series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", "");
|
||||||
series.Status = GetSeriesStatus(show.status, show.ended);
|
series.Status = GetSeriesStatus(show.status, show.ended);
|
||||||
|
series.Ratings = GetRatings(show.ratings);
|
||||||
series.Seasons = show.seasons.Select(s => new Tv.Season
|
series.Genres = show.genres;
|
||||||
{
|
series.Certification = show.certification;
|
||||||
SeasonNumber = s.season
|
series.Actors = GetActors(show.people);
|
||||||
}).OrderByDescending(s => s.SeasonNumber).ToList();
|
series.Seasons = GetSeasons(show);
|
||||||
|
|
||||||
series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.images.banner });
|
series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.images.banner });
|
||||||
series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Poster, Url = GetPosterThumbnailUrl(show.images.poster) });
|
series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Poster, Url = GetPosterThumbnailUrl(show.images.poster) });
|
||||||
series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Fanart, Url = show.images.fanart });
|
series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Fanart, Url = show.images.fanart });
|
||||||
|
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +104,9 @@ namespace NzbDrone.Core.MetadataSource
|
||||||
episode.Title = traktEpisode.title;
|
episode.Title = traktEpisode.title;
|
||||||
episode.AirDate = FromIsoToString(traktEpisode.first_aired_iso);
|
episode.AirDate = FromIsoToString(traktEpisode.first_aired_iso);
|
||||||
episode.AirDateUtc = FromIso(traktEpisode.first_aired_iso);
|
episode.AirDateUtc = FromIso(traktEpisode.first_aired_iso);
|
||||||
|
episode.Ratings = GetRatings(traktEpisode.ratings);
|
||||||
|
|
||||||
|
episode.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Screenshot, traktEpisode.images.screen));
|
||||||
|
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
@ -175,5 +181,64 @@ namespace NzbDrone.Core.MetadataSource
|
||||||
|
|
||||||
return year;
|
return year;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Tv.Ratings GetRatings(Trakt.Ratings ratings)
|
||||||
|
{
|
||||||
|
return new Tv.Ratings
|
||||||
|
{
|
||||||
|
Percentage = ratings.percentage,
|
||||||
|
Votes = ratings.votes,
|
||||||
|
Loved = ratings.loved,
|
||||||
|
Hated = ratings.hated
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Tv.Actor> GetActors(People people)
|
||||||
|
{
|
||||||
|
if (people == null)
|
||||||
|
{
|
||||||
|
return new List<Tv.Actor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetActors(people.actors).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Tv.Actor> GetActors(IEnumerable<Trakt.Actor> trakcActors)
|
||||||
|
{
|
||||||
|
foreach (var traktActor in trakcActors)
|
||||||
|
{
|
||||||
|
var actor = new Tv.Actor
|
||||||
|
{
|
||||||
|
Name = traktActor.name,
|
||||||
|
Character = traktActor.character,
|
||||||
|
};
|
||||||
|
|
||||||
|
actor.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Headshot, traktActor.images.headshot));
|
||||||
|
|
||||||
|
yield return actor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Tv.Season> GetSeasons(Show show)
|
||||||
|
{
|
||||||
|
var seasons = new List<Tv.Season>();
|
||||||
|
|
||||||
|
foreach (var traktSeason in show.seasons.OrderByDescending(s => s.season))
|
||||||
|
{
|
||||||
|
var season = new Tv.Season
|
||||||
|
{
|
||||||
|
SeasonNumber = traktSeason.season
|
||||||
|
};
|
||||||
|
|
||||||
|
if (traktSeason.images != null)
|
||||||
|
{
|
||||||
|
season.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Poster, traktSeason.images.poster));
|
||||||
|
}
|
||||||
|
|
||||||
|
seasons.Add(season);
|
||||||
|
}
|
||||||
|
|
||||||
|
return seasons;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -195,6 +195,8 @@
|
||||||
<Compile Include="Datastore\Migration\036_update_with_quality_converters.cs" />
|
<Compile Include="Datastore\Migration\036_update_with_quality_converters.cs" />
|
||||||
<Compile Include="Datastore\Migration\037_add_configurable_qualities.cs" />
|
<Compile Include="Datastore\Migration\037_add_configurable_qualities.cs" />
|
||||||
<Compile Include="Datastore\Migration\038_add_on_upgrade_to_notifications.cs" />
|
<Compile Include="Datastore\Migration\038_add_on_upgrade_to_notifications.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\040_add_metadata_to_episodes_and_series.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\039_add_metadata_tables.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
||||||
|
@ -259,9 +261,10 @@
|
||||||
<Compile Include="Exceptions\StatusCodeToExceptions.cs" />
|
<Compile Include="Exceptions\StatusCodeToExceptions.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodes.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodes.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItems.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItems.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFiles.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFiles.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupAdditionalNamingSpecs.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupAdditionalNamingSpecs.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\UpdateCleanTitleForSeries.cs" />
|
<Compile Include="Housekeeping\Housekeepers\UpdateCleanTitleForSeries.cs" />
|
||||||
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFiles.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasks.cs" />
|
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasks.cs" />
|
||||||
<Compile Include="Housekeeping\HousekeepingCommand.cs" />
|
<Compile Include="Housekeeping\HousekeepingCommand.cs" />
|
||||||
<Compile Include="Housekeeping\HousekeepingService.cs" />
|
<Compile Include="Housekeeping\HousekeepingService.cs" />
|
||||||
|
@ -294,6 +297,7 @@
|
||||||
<Compile Include="Lifecycle\Commands\ShutdownCommand.cs" />
|
<Compile Include="Lifecycle\Commands\ShutdownCommand.cs" />
|
||||||
<Compile Include="Lifecycle\Commands\RestartCommand.cs" />
|
<Compile Include="Lifecycle\Commands\RestartCommand.cs" />
|
||||||
<Compile Include="Lifecycle\LifecycleService.cs" />
|
<Compile Include="Lifecycle\LifecycleService.cs" />
|
||||||
|
<Compile Include="MediaCover\MediaCoversUpdatedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeFileMoveResult.cs" />
|
<Compile Include="MediaFiles\EpisodeFileMoveResult.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" />
|
||||||
|
@ -310,7 +314,26 @@
|
||||||
<Compile Include="Messaging\Events\EventAggregator.cs" />
|
<Compile Include="Messaging\Events\EventAggregator.cs" />
|
||||||
<Compile Include="Messaging\Events\IEventAggregator.cs" />
|
<Compile Include="Messaging\Events\IEventAggregator.cs" />
|
||||||
<Compile Include="Messaging\Events\IHandle.cs" />
|
<Compile Include="Messaging\Events\IHandle.cs" />
|
||||||
|
<Compile Include="MetadataSource\Trakt\Actor.cs" />
|
||||||
|
<Compile Include="MetadataSource\Trakt\People.cs" />
|
||||||
|
<Compile Include="MetadataSource\Trakt\Ratings.cs" />
|
||||||
|
<Compile Include="Metadata\Consumers\Fake\Fake.cs" />
|
||||||
|
<Compile Include="Metadata\Consumers\Fake\FakeSettings.cs" />
|
||||||
|
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadata.cs" />
|
||||||
|
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadataSettings.cs" />
|
||||||
|
<Compile Include="Metadata\ExistingMetadataService.cs" />
|
||||||
|
<Compile Include="Metadata\Files\MetadataFile.cs" />
|
||||||
|
<Compile Include="Metadata\Files\MetadataFileRepository.cs" />
|
||||||
|
<Compile Include="Metadata\Files\MetadataFileService.cs" />
|
||||||
|
<Compile Include="Metadata\Files\MetadataFileUpdated.cs" />
|
||||||
|
<Compile Include="Metadata\IMetadata.cs" />
|
||||||
|
<Compile Include="Metadata\MetadataBase.cs" />
|
||||||
<Compile Include="MetadataSource\Trakt\TraktException.cs" />
|
<Compile Include="MetadataSource\Trakt\TraktException.cs" />
|
||||||
|
<Compile Include="Metadata\MetadataDefinition.cs" />
|
||||||
|
<Compile Include="Metadata\MetadataFactory.cs" />
|
||||||
|
<Compile Include="Metadata\MetadataRepository.cs" />
|
||||||
|
<Compile Include="Metadata\MetadataService.cs" />
|
||||||
|
<Compile Include="Metadata\MetadataType.cs" />
|
||||||
<Compile Include="Notifications\NotificationFactory.cs" />
|
<Compile Include="Notifications\NotificationFactory.cs" />
|
||||||
<Compile Include="Notifications\NotificationService.cs" />
|
<Compile Include="Notifications\NotificationService.cs" />
|
||||||
<Compile Include="Notifications\DownloadMessage.cs" />
|
<Compile Include="Notifications\DownloadMessage.cs" />
|
||||||
|
@ -489,6 +512,7 @@
|
||||||
<Compile Include="ThingiProvider\ProviderDefinition.cs" />
|
<Compile Include="ThingiProvider\ProviderDefinition.cs" />
|
||||||
<Compile Include="ThingiProvider\ProviderRepository.cs" />
|
<Compile Include="ThingiProvider\ProviderRepository.cs" />
|
||||||
<Compile Include="ThingiProvider\ProviderFactory.cs" />
|
<Compile Include="ThingiProvider\ProviderFactory.cs" />
|
||||||
|
<Compile Include="Tv\Actor.cs" />
|
||||||
<Compile Include="Tv\EpisodeService.cs" />
|
<Compile Include="Tv\EpisodeService.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesEditedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesEditedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
||||||
|
@ -500,6 +524,7 @@
|
||||||
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
||||||
<Compile Include="Datastore\ResultSet.cs" />
|
<Compile Include="Datastore\ResultSet.cs" />
|
||||||
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
|
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
|
||||||
|
<Compile Include="Tv\Ratings.cs" />
|
||||||
<Compile Include="Tv\RefreshEpisodeService.cs" />
|
<Compile Include="Tv\RefreshEpisodeService.cs" />
|
||||||
<Compile Include="Tv\SeriesRepository.cs" />
|
<Compile Include="Tv\SeriesRepository.cs" />
|
||||||
<Compile Include="Qualities\QualityModel.cs" />
|
<Compile Include="Qualities\QualityModel.cs" />
|
||||||
|
|
|
@ -44,6 +44,8 @@ namespace NzbDrone.Core.Organizer
|
||||||
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>\s|\.|-|_)Title\})",
|
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>\s|\.|-|_)Title\})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
private static readonly char[] EpisodeTitleTrimCharaters = new[] { ' ', '.', '?' };
|
||||||
|
|
||||||
public FileNameBuilder(INamingConfigService namingConfigService,
|
public FileNameBuilder(INamingConfigService namingConfigService,
|
||||||
IQualityDefinitionService qualityDefinitionService,
|
IQualityDefinitionService qualityDefinitionService,
|
||||||
ICacheManger cacheManger,
|
ICacheManger cacheManger,
|
||||||
|
@ -88,7 +90,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
var pattern = namingConfig.StandardEpisodeFormat;
|
var pattern = namingConfig.StandardEpisodeFormat;
|
||||||
var episodeTitles = new List<string>
|
var episodeTitles = new List<string>
|
||||||
{
|
{
|
||||||
sortedEpisodes.First().Title
|
sortedEpisodes.First().Title.TrimEnd(EpisodeTitleTrimCharaters)
|
||||||
};
|
};
|
||||||
|
|
||||||
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
|
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
|
||||||
|
@ -140,7 +142,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeTitles.Add(episode.Title);
|
episodeTitles.Add(episode.Title.TrimEnd(EpisodeTitleTrimCharaters));
|
||||||
}
|
}
|
||||||
|
|
||||||
seasonEpisodePattern = ReplaceNumberTokens(seasonEpisodePattern, sortedEpisodes);
|
seasonEpisodePattern = ReplaceNumberTokens(seasonEpisodePattern, sortedEpisodes);
|
||||||
|
|
|
@ -108,7 +108,7 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
private static readonly Regex MultiPartCleanupRegex = new Regex(@"\(\d+\)$", RegexOptions.Compiled);
|
private static readonly Regex MultiPartCleanupRegex = new Regex(@"\(\d+\)$", RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>ita|italian)|(?<german>german\b)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)FR)(?:\W|_)",
|
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>ita|italian)|(?<german>german\b)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)FR)(?:\W|_)|(?<russian>\brus\b)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
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})",
|
||||||
|
@ -148,7 +148,6 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
foreach (var regex in ReportTitleRegex)
|
foreach (var regex in ReportTitleRegex)
|
||||||
{
|
{
|
||||||
var regexString = regex.ToString();
|
|
||||||
var match = regex.Matches(simpleTitle);
|
var match = regex.Matches(simpleTitle);
|
||||||
|
|
||||||
if (match.Count != 0)
|
if (match.Count != 0)
|
||||||
|
@ -467,6 +466,9 @@ namespace NzbDrone.Core.Parser
|
||||||
if (match.Groups["french"].Success)
|
if (match.Groups["french"].Success)
|
||||||
return Language.French;
|
return Language.French;
|
||||||
|
|
||||||
|
if (match.Groups["russian"].Success)
|
||||||
|
return Language.Russian;
|
||||||
|
|
||||||
return Language.English;
|
return Language.English;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ namespace NzbDrone.Core.Parser
|
||||||
Episodes = episodes,
|
Episodes = episodes,
|
||||||
Path = filename,
|
Path = filename,
|
||||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||||
ExistingFile = _diskProvider.IsParent(series.Path, filename)
|
ExistingFile = DiskProviderBase.IsParent(series.Path, filename)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace NzbDrone.Core.ThingiProvider
|
||||||
private readonly IContainer _container;
|
private readonly IContainer _container;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private readonly List<TProvider> _providers;
|
protected readonly List<TProvider> _providers;
|
||||||
|
|
||||||
protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository,
|
protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository,
|
||||||
IEnumerable<TProvider> providers,
|
IEnumerable<TProvider> providers,
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv
|
||||||
|
{
|
||||||
|
public class Actor : IEmbeddedDocument
|
||||||
|
{
|
||||||
|
public Actor()
|
||||||
|
{
|
||||||
|
Images = new List<MediaCover.MediaCover>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String Name { get; set; }
|
||||||
|
public String Character { get; set; }
|
||||||
|
public List<MediaCover.MediaCover> Images { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Marr.Data;
|
using Marr.Data;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Tv
|
namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
public class Episode : ModelBase
|
public class Episode : ModelBase
|
||||||
{
|
{
|
||||||
|
public Episode()
|
||||||
|
{
|
||||||
|
Images = new List<MediaCover.MediaCover>();
|
||||||
|
}
|
||||||
|
|
||||||
public const string AIR_DATE_FORMAT = "yyyy-MM-dd";
|
public const string AIR_DATE_FORMAT = "yyyy-MM-dd";
|
||||||
|
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
|
@ -18,12 +23,13 @@ namespace NzbDrone.Core.Tv
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string AirDate { get; set; }
|
public string AirDate { get; set; }
|
||||||
public DateTime? AirDateUtc { get; set; }
|
public DateTime? AirDateUtc { get; set; }
|
||||||
|
|
||||||
public string Overview { get; set; }
|
public string Overview { get; set; }
|
||||||
public Boolean Monitored { get; set; }
|
public Boolean Monitored { get; set; }
|
||||||
public Nullable<Int32> AbsoluteEpisodeNumber { get; set; }
|
public Nullable<Int32> AbsoluteEpisodeNumber { get; set; }
|
||||||
public int SceneSeasonNumber { get; set; }
|
public int SceneSeasonNumber { get; set; }
|
||||||
public int SceneEpisodeNumber { get; set; }
|
public int SceneEpisodeNumber { get; set; }
|
||||||
|
public Ratings Ratings { get; set; }
|
||||||
|
public List<MediaCover.MediaCover> Images { get; set; }
|
||||||
|
|
||||||
public String SeriesTitle { get; private set; }
|
public String SeriesTitle { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv
|
||||||
|
{
|
||||||
|
public class Ratings : IEmbeddedDocument
|
||||||
|
{
|
||||||
|
public Int32 Percentage { get; set; }
|
||||||
|
public Int32 Votes { get; set; }
|
||||||
|
public Int32 Loved { get; set; }
|
||||||
|
public Int32 Hated { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,6 +65,8 @@ namespace NzbDrone.Core.Tv
|
||||||
episodeToUpdate.Overview = episode.Overview;
|
episodeToUpdate.Overview = episode.Overview;
|
||||||
episodeToUpdate.AirDate = episode.AirDate;
|
episodeToUpdate.AirDate = episode.AirDate;
|
||||||
episodeToUpdate.AirDateUtc = episode.AirDateUtc;
|
episodeToUpdate.AirDateUtc = episode.AirDateUtc;
|
||||||
|
episodeToUpdate.Ratings = episode.Ratings;
|
||||||
|
episodeToUpdate.Images = episode.Images;
|
||||||
|
|
||||||
successCount++;
|
successCount++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,10 @@ namespace NzbDrone.Core.Tv
|
||||||
series.Images = seriesInfo.Images;
|
series.Images = seriesInfo.Images;
|
||||||
series.Network = seriesInfo.Network;
|
series.Network = seriesInfo.Network;
|
||||||
series.FirstAired = seriesInfo.FirstAired;
|
series.FirstAired = seriesInfo.FirstAired;
|
||||||
|
series.Ratings = seriesInfo.Ratings;
|
||||||
|
series.Actors = seriesInfo.Actors;
|
||||||
|
series.Genres = seriesInfo.Genres;
|
||||||
|
series.Certification = seriesInfo.Certification;
|
||||||
|
|
||||||
if (_dailySeriesService.IsDailySeries(series.TvdbId))
|
if (_dailySeriesService.IsDailySeries(series.TvdbId))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Tv
|
namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
public class Season : IEmbeddedDocument
|
public class Season : IEmbeddedDocument
|
||||||
{
|
{
|
||||||
|
public Season()
|
||||||
|
{
|
||||||
|
Images = new List<MediaCover.MediaCover>();
|
||||||
|
}
|
||||||
|
|
||||||
public int SeasonNumber { get; set; }
|
public int SeasonNumber { get; set; }
|
||||||
public Boolean Monitored { get; set; }
|
public Boolean Monitored { get; set; }
|
||||||
|
public List<MediaCover.MediaCover> Images { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,8 @@ namespace NzbDrone.Core.Tv
|
||||||
public Series()
|
public Series()
|
||||||
{
|
{
|
||||||
Images = new List<MediaCover.MediaCover>();
|
Images = new List<MediaCover.MediaCover>();
|
||||||
|
Genres = new List<String>();
|
||||||
|
Actors = new List<Actor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int TvdbId { get; set; }
|
public int TvdbId { get; set; }
|
||||||
|
@ -35,6 +37,10 @@ namespace NzbDrone.Core.Tv
|
||||||
public string TitleSlug { get; set; }
|
public string TitleSlug { get; set; }
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public int Year { get; set; }
|
public int Year { get; set; }
|
||||||
|
public Ratings Ratings { get; set; }
|
||||||
|
public List<String> Genres { get; set; }
|
||||||
|
public List<Actor> Actors { get; set; }
|
||||||
|
public String Certification { get; set; }
|
||||||
|
|
||||||
public string RootFolderPath { get; set; }
|
public string RootFolderPath { get; set; }
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -1,14 +0,0 @@
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Common.Test.DiskProviderTests;
|
|
||||||
|
|
||||||
namespace NzbDrone.Mono.Test.DiskProviderTests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class IsParentFixtureFixture : IsParentFixtureBase<DiskProvider>
|
|
||||||
{
|
|
||||||
public IsParentFixtureFixture()
|
|
||||||
{
|
|
||||||
LinuxOnly();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -68,7 +68,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
|
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
|
||||||
<Compile Include="DiskProviderTests\FreeSpaceFixture.cs" />
|
<Compile Include="DiskProviderTests\FreeSpaceFixture.cs" />
|
||||||
<Compile Include="DiskProviderTests\IsParentFixture.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ServiceFactoryFixture.cs" />
|
<Compile Include="ServiceFactoryFixture.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace NzbDrone.Mono
|
||||||
|
|
||||||
return monoProcesses.Where(c =>
|
return monoProcesses.Where(c =>
|
||||||
{
|
{
|
||||||
var processArgs = _processProvider.StartAndCapture("ps", String.Format("--pid {0} -o args=", c.Id));
|
var processArgs = _processProvider.StartAndCapture("ps", String.Format("-p {0} -o args=", c.Id));
|
||||||
|
|
||||||
return processArgs.Standard.Any(p => p.Contains(ProcessProvider.NZB_DRONE_PROCESS_NAME + ".exe") ||
|
return processArgs.Standard.Any(p => p.Contains(ProcessProvider.NZB_DRONE_PROCESS_NAME + ".exe") ||
|
||||||
p.Contains(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME + ".exe"));
|
p.Contains(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME + ".exe"));
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Common.Test.DiskProviderTests;
|
|
||||||
|
|
||||||
namespace NzbDrone.Windows.Test.DiskProviderTests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class IsParentFixtureFixture : IsParentFixtureBase<DiskProvider>
|
|
||||||
{
|
|
||||||
public IsParentFixtureFixture()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -67,7 +67,6 @@
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="DiskProviderTests\IsParentFixture.cs" />
|
|
||||||
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
|
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
|
||||||
<Compile Include="DiskProviderTests\FreeSpaceFixture.cs" />
|
<Compile Include="DiskProviderTests\FreeSpaceFixture.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
|
|
@ -28,10 +28,12 @@ define(
|
||||||
var templateName = self.template;
|
var templateName = self.template;
|
||||||
self.schema = qualityProfileSchemaCollection.first();
|
self.schema = qualityProfileSchemaCollection.first();
|
||||||
|
|
||||||
var selected = _.find(self.schema.get('available'), { 'id': self.model.get(self.column.get('name')).quality.id });
|
var selected = _.find(self.schema.get('items'), function (model) {
|
||||||
|
return model.quality.id === self.model.get(self.column.get('name')).quality.id;
|
||||||
|
});
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
selected.selected = true;
|
selected.quality.selected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.templateFunction = Marionette.TemplateCache.get(templateName);
|
self.templateFunction = Marionette.TemplateCache.get(templateName);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
{{#each available}}
|
{{#each items}}
|
||||||
{{#if selected}}
|
{{#with quality}}
|
||||||
<option value="{{id}}" selected="selected">{{name}}</option>
|
{{#if selected}}
|
||||||
{{else}}
|
<option value="{{id}}" selected="selected">{{name}}</option>
|
||||||
<option value="{{id}}">{{name}}</option>
|
{{else}}
|
||||||
{{/if}}
|
<option value="{{id}}">{{name}}</option>
|
||||||
|
{{/if}}
|
||||||
|
{{/with}}
|
||||||
{{/each}}
|
{{/each}}
|
After Width: | Height: | Size: 206 KiB |
After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 137 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.9 KiB |
|
@ -107,8 +107,10 @@ a, .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color : #1c1c1c;
|
background:
|
||||||
background-image : url('../Content/Images/pattern.png');
|
url('../Content/Images/background/logo.png') 50px center no-repeat fixed,
|
||||||
|
#1c1c1c url('../Content/Images/background/black.png') top center e('/') cover repeat;
|
||||||
|
|
||||||
margin-bottom : 100px;
|
margin-bottom : 100px;
|
||||||
p {
|
p {
|
||||||
font-size : 0.9em;
|
font-size : 0.9em;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
@nzbdroneRed: #c4273c;
|
@nzbdroneRed: #c4273c;
|
||||||
@nzbdronePurple: #7932ea;
|
@nzbdronePurple: #7932ea;
|
||||||
|
@droneTeal: #35c5f4;
|
|
@ -4,7 +4,7 @@
|
||||||
<ul id="main-menu-region">
|
<ul id="main-menu-region">
|
||||||
<div class="pull-left logo">
|
<div class="pull-left logo">
|
||||||
<a href="{{UrlBase}}/">
|
<a href="{{UrlBase}}/">
|
||||||
<img src="{{UrlBase}}/Content/Images/logo.png" alt="NzbDrone">
|
<img src="{{UrlBase}}/Content/Images/logo.png?v=2" alt="NzbDrone">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -7,7 +7,7 @@ define(
|
||||||
|
|
||||||
var QualityProfileCollection = Backbone.Collection.extend({
|
var QualityProfileCollection = Backbone.Collection.extend({
|
||||||
model: QualityProfileModel,
|
model: QualityProfileModel,
|
||||||
url : window.NzbDrone.ApiRoot + '/qualityprofiles'
|
url : window.NzbDrone.ApiRoot + '/qualityprofile'
|
||||||
});
|
});
|
||||||
|
|
||||||
var profiles = new QualityProfileCollection();
|
var profiles = new QualityProfileCollection();
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'vent',
|
||||||
|
'marionette'
|
||||||
|
], function (vent, Marionette) {
|
||||||
|
return Marionette.ItemView.extend({
|
||||||
|
template: 'Settings/Indexers/DeleteViewTemplate',
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .x-confirm-delete': '_removeNotification'
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeNotification: function () {
|
||||||
|
this.model.destroy({
|
||||||
|
wait : true,
|
||||||
|
success: function () {
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,7 +4,7 @@ define(
|
||||||
[
|
[
|
||||||
'AppLayout',
|
'AppLayout',
|
||||||
'marionette',
|
'marionette',
|
||||||
'Settings/Notifications/DeleteView',
|
'Settings/Indexers/DeleteView',
|
||||||
'Mixins/AsModelBoundView',
|
'Mixins/AsModelBoundView',
|
||||||
'Mixins/AsValidatedView'
|
'Mixins/AsValidatedView'
|
||||||
], function (AppLayout, Marionette, DeleteView, AsModelBoundView, AsValidatedView) {
|
], function (AppLayout, Marionette, DeleteView, AsModelBoundView, AsValidatedView) {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backbone',
|
||||||
|
'Settings/Metadata/MetadataModel'
|
||||||
|
], function (Backbone, MetadataModel) {
|
||||||
|
|
||||||
|
return Backbone.Collection.extend({
|
||||||
|
model: MetadataModel,
|
||||||
|
url : window.NzbDrone.ApiRoot + '/metadata'
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'AppLayout',
|
||||||
|
'marionette',
|
||||||
|
'Settings/Metadata/MetadataItemView'
|
||||||
|
], function (AppLayout, Marionette, MetadataItemView) {
|
||||||
|
return Marionette.CompositeView.extend({
|
||||||
|
itemView : MetadataItemView,
|
||||||
|
itemViewContainer: '#x-metadata',
|
||||||
|
template : 'Settings/Metadata/MetadataCollectionViewTemplate'
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
<fieldset>
|
||||||
|
<legend>Metadata</legend>
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<ul id="x-metadata" class="metadata-list"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|