mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-10-30 15:32:31 +01:00
New: XBMC Metadata (Frodo+)
This commit is contained in:
parent
6493622ebc
commit
a6361d0bbd
@ -2,9 +2,9 @@
|
||||
|
||||
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")
|
||||
{
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using NzbDrone.Test.Common;
|
||||
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();
|
||||
|
||||
@ -14,7 +15,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
|
||||
{
|
||||
var path = @"C:\Another Folder".AsOsAgnostic();
|
||||
|
||||
Subject.IsParent(_parent, path).Should().BeFalse();
|
||||
DiskProvider.IsParent(_parent, path).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -22,7 +23,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
|
||||
{
|
||||
var path = @"C:\Test\TV".AsOsAgnostic();
|
||||
|
||||
Subject.IsParent(_parent, path).Should().BeTrue();
|
||||
DiskProvider.IsParent(_parent, path).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -30,7 +31,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
|
||||
{
|
||||
var path = @"C:\Test\30.Rock.S01E01.Pilot.avi".AsOsAgnostic();
|
||||
|
||||
Subject.IsParent(_parent, path).Should().BeTrue();
|
||||
DiskProvider.IsParent(_parent, path).Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace NzbDrone.Common.Composition
|
||||
Container = new Container(new TinyIoCContainer(), _loadedTypes);
|
||||
AutoRegisterInterfaces();
|
||||
Container.Register(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoRegisterInterfaces()
|
||||
{
|
||||
|
@ -5,12 +5,14 @@ using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
public abstract class DiskProviderBase : IDiskProvider
|
||||
{
|
||||
void CopyFile(string source, string destination, bool overwrite = false);
|
||||
enum TransferAction
|
||||
{
|
||||
Copy,
|
||||
@ -24,6 +26,41 @@ namespace NzbDrone.Common.Disk
|
||||
public abstract void SetPermissions(string path, string mask, string user, string group);
|
||||
public abstract long? GetTotalSize(string path);
|
||||
|
||||
//TODO: this needs tests
|
||||
public static string GetRelativePath(string parentPath, string childPath)
|
||||
{
|
||||
if (!IsParent(parentPath, childPath))
|
||||
{
|
||||
throw new NotParentException("{0} is not a child of {1}", childPath, parentPath);
|
||||
}
|
||||
|
||||
var parentUri = new Uri(parentPath, UriKind.Absolute);
|
||||
var childUri = new Uri(childPath, UriKind.Absolute);
|
||||
|
||||
return childUri.MakeRelativeUri(parentUri).ToString();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
@ -238,6 +275,11 @@ namespace NzbDrone.Common.Disk
|
||||
File.Move(source, destination);
|
||||
}
|
||||
|
||||
public void CopyFile(string source, string destination, bool overwrite = false)
|
||||
{
|
||||
File.Copy(source, destination, overwrite);
|
||||
}
|
||||
|
||||
public void DeleteFolder(string path, bool recursive)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
@ -333,27 +375,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)
|
||||
{
|
||||
Directory.SetLastWriteTimeUtc(path, time);
|
||||
|
18
src/NzbDrone.Common/Exceptions/NotParentException.cs
Normal file
18
src/NzbDrone.Common/Exceptions/NotParentException.cs
Normal file
@ -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\RuntimeInfo.cs" />
|
||||
<Compile Include="EnvironmentInfo\OsInfo.cs" />
|
||||
<Compile Include="Exceptions\NotParentException.cs" />
|
||||
<Compile Include="Exceptions\NzbDroneException.cs" />
|
||||
<Compile Include="IEnumerableExtensions.cs" />
|
||||
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
|
||||
|
@ -0,0 +1,22 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(36)]
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(37)]
|
||||
public class add_episode_file_metadata : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Create.TableForModel("EpisodeFileMetaData")
|
||||
.WithColumn("SeriesId").AsInt32().NotNullable()
|
||||
.WithColumn("EpisodeFileId").AsInt32().NotNullable()
|
||||
.WithColumn("Provider").AsString().NotNullable()
|
||||
.WithColumn("Type").AsInt32().NotNullable()
|
||||
.WithColumn("LastUpdated").AsDateTime().NotNullable()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(37)]
|
||||
public class add_metadata_consumers : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Create.TableForModel("MetadataConsumers")
|
||||
.WithColumn("Enable").AsBoolean().NotNullable()
|
||||
.WithColumn("Name").AsString().NotNullable()
|
||||
.WithColumn("Implementation").AsString().NotNullable()
|
||||
.WithColumn("Settings").AsString().NotNullable()
|
||||
.WithColumn("ConfigContract").AsString().NotNullable();
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Instrumentation;
|
||||
using NzbDrone.Core.Jobs;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Metadata;
|
||||
using NzbDrone.Core.Notifications;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Qualities;
|
||||
@ -35,8 +36,8 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
Mapper.Entity<IndexerDefinition>().RegisterModel("Indexers");
|
||||
Mapper.Entity<ScheduledTask>().RegisterModel("ScheduledTasks");
|
||||
Mapper.Entity<NotificationDefinition>()
|
||||
.RegisterModel("Notifications");
|
||||
Mapper.Entity<NotificationDefinition>().RegisterModel("Notifications");
|
||||
Mapper.Entity<MetadataDefinition>().RegisterModel("MetadataConsumers");
|
||||
|
||||
Mapper.Entity<SceneMapping>().RegisterModel("SceneMappings");
|
||||
|
||||
@ -85,6 +86,7 @@ namespace NzbDrone.Core.Datastore
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
|
||||
}
|
||||
|
||||
private static void RegisterProviderSettingConverter()
|
||||
|
@ -120,6 +120,11 @@ namespace NzbDrone.Core.History
|
||||
|
||||
public void Handle(EpisodeImportedEvent message)
|
||||
{
|
||||
if (message.NewDownload)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var episode in message.EpisodeInfo.Episodes)
|
||||
{
|
||||
var history = new History
|
||||
|
@ -14,14 +14,12 @@ namespace NzbDrone.Core.Indexers
|
||||
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory
|
||||
{
|
||||
private readonly IIndexerRepository _providerRepository;
|
||||
private readonly IEnumerable<IIndexer> _providers;
|
||||
private readonly INewznabTestService _newznabTestService;
|
||||
|
||||
public IndexerFactory(IIndexerRepository providerRepository, IEnumerable<IIndexer> providers, IContainer container, INewznabTestService newznabTestService, Logger logger)
|
||||
: base(providerRepository, providers, container, logger)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_providers = providers;
|
||||
_newznabTestService = newznabTestService;
|
||||
}
|
||||
|
||||
|
@ -8,12 +8,24 @@ namespace NzbDrone.Core.MediaCover
|
||||
Unknown = 0,
|
||||
Poster = 1,
|
||||
Banner = 2,
|
||||
Fanart = 3
|
||||
Fanart = 3,
|
||||
Screenshot = 4,
|
||||
Headshot = 5
|
||||
}
|
||||
|
||||
public class MediaCover : IEmbeddedDocument
|
||||
{
|
||||
public MediaCoverTypes CoverType { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
public MediaCover()
|
||||
{
|
||||
}
|
||||
|
||||
public MediaCover(MediaCoverTypes coverType, string url)
|
||||
{
|
||||
CoverType = coverType;
|
||||
Url = url;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,12 @@ using NzbDrone.Core.Tv.Events;
|
||||
|
||||
namespace NzbDrone.Core.MediaCover
|
||||
{
|
||||
public interface IMapCoversToLocal
|
||||
{
|
||||
void ConvertToLocalUrls(int seriesId, IEnumerable<MediaCover> covers);
|
||||
string GetCoverPath(int seriesId, MediaCoverTypes mediaCoverTypes);
|
||||
}
|
||||
|
||||
public class MediaCoverService :
|
||||
IHandleAsync<SeriesUpdatedEvent>,
|
||||
IHandleAsync<SeriesDeletedEvent>,
|
||||
@ -38,9 +44,30 @@ namespace NzbDrone.Core.MediaCover
|
||||
_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)
|
||||
@ -75,6 +102,11 @@ namespace NzbDrone.Core.MediaCover
|
||||
|
||||
}
|
||||
|
||||
public void HandleAsync(SeriesUpdatedEvent message)
|
||||
{
|
||||
EnsureCovers(message.Series);
|
||||
}
|
||||
|
||||
public void HandleAsync(SeriesDeletedEvent message)
|
||||
{
|
||||
var path = GetSeriesCoverPath(message.Series.Id);
|
||||
@ -83,36 +115,5 @@ namespace NzbDrone.Core.MediaCover
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
@ -87,9 +87,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
_mediaFileService.Add(episodeFile);
|
||||
imported.Add(importDecision);
|
||||
|
||||
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload));
|
||||
|
||||
if (newDownload)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile));
|
||||
_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;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Events
|
||||
@ -7,11 +8,13 @@ namespace NzbDrone.Core.MediaFiles.Events
|
||||
{
|
||||
public LocalEpisode EpisodeInfo { 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;
|
||||
ImportedEpisode = importedEpisode;
|
||||
NewDownload = newDownload;
|
||||
}
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_diskProvider.IsParent(series.Path, episodeFile.Path))
|
||||
if (!DiskProvider.IsParent(series.Path, episodeFile.Path))
|
||||
{
|
||||
_logger.Trace("File [{0}] does not belong to this series, removing from db", episodeFile.Path);
|
||||
_mediaFileService.Delete(episodeFile);
|
||||
|
43
src/NzbDrone.Core/MetaData/Consumers/Fake/Fake.cs
Normal file
43
src/NzbDrone.Core/MetaData/Consumers/Fake/Fake.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.Fake
|
||||
{
|
||||
public class FakeMetadata : MetadataConsumerBase<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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
41
src/NzbDrone.Core/MetaData/Consumers/Fake/FakeSettings.cs
Normal file
41
src/NzbDrone.Core/MetaData/Consumers/Fake/FakeSettings.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
232
src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs
Normal file
232
src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs
Normal file
@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Metadata.Events;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||
{
|
||||
public class XbmcMetadata : MetadataConsumerBase<XbmcMetadataSettings>
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMapCoversToLocal _mediaCoverService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public XbmcMetadata(IEventAggregator eventAggregator,
|
||||
IMapCoversToLocal mediaCoverService,
|
||||
IDiskProvider diskProvider,
|
||||
IHttpProvider httpProvider,
|
||||
Logger logger)
|
||||
: base(diskProvider, httpProvider, logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_mediaCoverService = mediaCoverService;
|
||||
_diskProvider = diskProvider;
|
||||
_httpProvider = httpProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override void OnSeriesUpdated(Series series)
|
||||
{
|
||||
if (Settings.SeriesMetadata)
|
||||
{
|
||||
EnsureFolder(series.Path);
|
||||
WriteTvShowNfo(series);
|
||||
}
|
||||
|
||||
if (Settings.SeriesImages)
|
||||
{
|
||||
EnsureFolder(series.Path);
|
||||
WriteSeriesImages(series);
|
||||
}
|
||||
|
||||
if (Settings.SeasonImages)
|
||||
{
|
||||
EnsureFolder(series.Path);
|
||||
WriteSeasonImages(series);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
|
||||
{
|
||||
if (Settings.EpisodeMetadata)
|
||||
{
|
||||
WriteEpisodeNfo(episodeFile);
|
||||
}
|
||||
|
||||
if (Settings.EpisodeImages)
|
||||
{
|
||||
WriteEpisodeImages(series, episodeFile);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AfterRename(Series series)
|
||||
{
|
||||
//TODO: Rename media files to match episode files
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void WriteTvShowNfo(Series series)
|
||||
{
|
||||
_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());
|
||||
|
||||
_eventAggregator.PublishEvent(new SeriesMetadataUpdated(series, GetType().Name, MetadataType.SeriesMetadata, DiskProvider.GetRelativePath(series.Path, path)));
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteSeriesImages(Series series)
|
||||
{
|
||||
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);
|
||||
_eventAggregator.PublishEvent(new SeriesMetadataUpdated(series, GetType().Name, MetadataType.SeriesImage, DiskProvider.GetRelativePath(series.Path, destination)));
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteSeasonImages(Series series)
|
||||
{
|
||||
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);
|
||||
_eventAggregator.PublishEvent(new SeasonMetadataUpdated(series, season.SeasonNumber, GetType().Name, MetadataType.SeasonImage, DiskProvider.GetRelativePath(series.Path, path)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteEpisodeNfo(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("credits", tvdbEpisode.Writer.FirstOrDefault()));
|
||||
// details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault()));
|
||||
details.Add(new XElement("rating", episode.Ratings.Percentage));
|
||||
|
||||
//Todo: get guest stars, will need trakt to have them
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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, "jpg");
|
||||
|
||||
DownloadImage(series, screenshot.Url, filename);
|
||||
_eventAggregator.PublishEvent(new EpisodeMetadataUpdated(series, episodeFile, GetType().Name, MetadataType.SeasonImage, DiskProvider.GetRelativePath(series.Path, filename)));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/NzbDrone.Core/MetaData/Events/EpisodeMetadataUpdated.cs
Normal file
25
src/NzbDrone.Core/MetaData/Events/EpisodeMetadataUpdated.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Events
|
||||
{
|
||||
public class EpisodeMetadataUpdated : IEvent
|
||||
{
|
||||
public Series Series { get; set; }
|
||||
public EpisodeFile EpisodeFile { get; set; }
|
||||
public String Consumer { get; set; }
|
||||
public MetadataType MetadataType { get; set; }
|
||||
public String Path { get; set; }
|
||||
|
||||
public EpisodeMetadataUpdated(Series series, EpisodeFile episodeFile, string consumer, MetadataType metadataType, string path)
|
||||
{
|
||||
Series = series;
|
||||
EpisodeFile = episodeFile;
|
||||
Consumer = consumer;
|
||||
MetadataType = metadataType;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
}
|
24
src/NzbDrone.Core/MetaData/Events/SeasonMetadataUpdated.cs
Normal file
24
src/NzbDrone.Core/MetaData/Events/SeasonMetadataUpdated.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Events
|
||||
{
|
||||
public class SeasonMetadataUpdated : IEvent
|
||||
{
|
||||
public Series Series { get; set; }
|
||||
public Int32 SeasonNumber { get; set; }
|
||||
public String Consumer { get; set; }
|
||||
public MetadataType MetadataType { get; set; }
|
||||
public String Path { get; set; }
|
||||
|
||||
public SeasonMetadataUpdated(Series series, int seasonNumber, string consumer, MetadataType metadataType, string path)
|
||||
{
|
||||
Series = series;
|
||||
SeasonNumber = seasonNumber;
|
||||
Consumer = consumer;
|
||||
MetadataType = metadataType;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
}
|
22
src/NzbDrone.Core/MetaData/Events/SeriesMetadataUpdated.cs
Normal file
22
src/NzbDrone.Core/MetaData/Events/SeriesMetadataUpdated.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Events
|
||||
{
|
||||
public class SeriesMetadataUpdated : IEvent
|
||||
{
|
||||
public Series Series { get; set; }
|
||||
public String Consumer { get; set; }
|
||||
public MetadataType MetadataType { get; set; }
|
||||
public String Path { get; set; }
|
||||
|
||||
public SeriesMetadataUpdated(Series series, string consumer, MetadataType metadataType, string path)
|
||||
{
|
||||
Series = series;
|
||||
Consumer = consumer;
|
||||
MetadataType = metadataType;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
}
|
13
src/NzbDrone.Core/MetaData/IMetadata.cs
Normal file
13
src/NzbDrone.Core/MetaData/IMetadata.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
{
|
||||
public interface IMetadata : IProvider
|
||||
{
|
||||
void OnSeriesUpdated(Series series);
|
||||
void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
|
||||
void AfterRename(Series series);
|
||||
}
|
||||
}
|
88
src/NzbDrone.Core/MetaData/MetadataConsumerBase.cs
Normal file
88
src/NzbDrone.Core/MetaData/MetadataConsumerBase.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
{
|
||||
public abstract class MetadataConsumerBase<TSettings> : IMetadata where TSettings : IProviderConfig, new()
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
protected MetadataConsumerBase(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);
|
||||
public abstract void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
|
||||
public abstract void AfterRename(Series series);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
10
src/NzbDrone.Core/MetaData/MetadataDefinition.cs
Normal file
10
src/NzbDrone.Core/MetaData/MetadataDefinition.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
{
|
||||
public class MetadataDefinition : ProviderDefinition
|
||||
{
|
||||
public Boolean Enable { get; set; }
|
||||
}
|
||||
}
|
58
src/NzbDrone.Core/MetaData/MetadataFactory.cs
Normal file
58
src/NzbDrone.Core/MetaData/MetadataFactory.cs
Normal file
@ -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,
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
20
src/NzbDrone.Core/MetaData/MetadataRepository.cs
Normal file
20
src/NzbDrone.Core/MetaData/MetadataRepository.cs
Normal file
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
48
src/NzbDrone.Core/MetaData/MetadataService.cs
Normal file
48
src/NzbDrone.Core/MetaData/MetadataService.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
{
|
||||
public class NotificationService
|
||||
: IHandle<SeriesUpdatedEvent>,
|
||||
IHandle<EpisodeImportedEvent>,
|
||||
IHandle<SeriesRenamedEvent>
|
||||
{
|
||||
private readonly IMetadataFactory _metadataFactory;
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NotificationService(IMetadataFactory metadataFactory, IMetadataRepository metadataRepository, Logger logger)
|
||||
{
|
||||
_metadataFactory = metadataFactory;
|
||||
_metadataRepository = metadataRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Handle(SeriesUpdatedEvent message)
|
||||
{
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
{
|
||||
consumer.OnSeriesUpdated(message.Series);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
src/NzbDrone.Core/MetaData/MetadataType.cs
Normal file
13
src/NzbDrone.Core/MetaData/MetadataType.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
{
|
||||
public enum MetadataType
|
||||
{
|
||||
SeriesMetadata = 0,
|
||||
EpisodeMetadata = 1,
|
||||
SeriesImage = 2,
|
||||
SeasonImage = 3,
|
||||
EpisodeImage = 4
|
||||
}
|
||||
}
|
14
src/NzbDrone.Core/MetadataSource/Trakt/Actor.cs
Normal file
14
src/NzbDrone.Core/MetadataSource/Trakt/Actor.cs
Normal file
@ -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 string url { 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 List<string> genres { get; set; }
|
||||
public List<Season> seasons { get; set; }
|
||||
public Ratings ratings { get; set; }
|
||||
public People people { get; set; }
|
||||
}
|
||||
|
||||
public class SearchShow
|
||||
|
@ -5,5 +5,7 @@
|
||||
public string poster { get; set; }
|
||||
public string fanart { get; set; }
|
||||
public string banner { get; set; }
|
||||
public string screen { get; set; }
|
||||
public string headshot { get; set; }
|
||||
}
|
||||
}
|
9
src/NzbDrone.Core/MetadataSource/Trakt/People.cs
Normal file
9
src/NzbDrone.Core/MetadataSource/Trakt/People.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.MetadataSource.Trakt
|
||||
{
|
||||
public class People
|
||||
{
|
||||
public List<Actor> actors { get; set; }
|
||||
}
|
||||
}
|
12
src/NzbDrone.Core/MetadataSource/Trakt/Ratings.cs
Normal file
12
src/NzbDrone.Core/MetadataSource/Trakt/Ratings.cs
Normal file
@ -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 string url { get; set; }
|
||||
public string poster { get; set; }
|
||||
public Images images { get; set; }
|
||||
}
|
||||
}
|
@ -8,7 +8,9 @@ using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource.Trakt;
|
||||
using NzbDrone.Core.Notifications.Xbmc.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
using Omu.ValueInjecter;
|
||||
using RestSharp;
|
||||
using Episode = NzbDrone.Core.Tv.Episode;
|
||||
using NzbDrone.Core.Rest;
|
||||
@ -79,15 +81,16 @@ namespace NzbDrone.Core.MetadataSource
|
||||
series.AirTime = show.air_time_utc;
|
||||
series.TitleSlug = show.url.ToLower().Replace("http://trakt.tv/show/", "");
|
||||
series.Status = GetSeriesStatus(show.status, show.ended);
|
||||
|
||||
series.Seasons = show.seasons.Select(s => new Tv.Season
|
||||
{
|
||||
SeasonNumber = s.season
|
||||
}).OrderByDescending(s => s.SeasonNumber).ToList();
|
||||
series.Ratings = GetRatings(show.ratings);
|
||||
series.Genres = show.genres;
|
||||
series.Certification = show.certification;
|
||||
series.Actors = GetActors(show.people);
|
||||
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.Poster, Url = GetPosterThumbnailUrl(show.images.poster) });
|
||||
series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Fanart, Url = show.images.fanart });
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
@ -101,6 +104,9 @@ namespace NzbDrone.Core.MetadataSource
|
||||
episode.Title = traktEpisode.title;
|
||||
episode.AirDate = FromIsoToString(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;
|
||||
}
|
||||
@ -175,5 +181,60 @@ namespace NzbDrone.Core.MetadataSource
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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\037_add_configurable_qualities.cs" />
|
||||
<Compile Include="Datastore\Migration\038_add_on_upgrade_to_notifications.cs" />
|
||||
<Compile Include="Datastore\Migration\036_add_metadata_to_episodes_and_series.cs" />
|
||||
<Compile Include="Datastore\Migration\037_add_metadata_consumers.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
||||
@ -310,7 +312,24 @@
|
||||
<Compile Include="Messaging\Events\EventAggregator.cs" />
|
||||
<Compile Include="Messaging\Events\IEventAggregator.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\Events\EpisodeMetadataUpdated.cs" />
|
||||
<Compile Include="Metadata\Events\SeasonMetadataUpdated.cs" />
|
||||
<Compile Include="Metadata\Events\SeriesMetadataUpdated.cs" />
|
||||
<Compile Include="Metadata\IMetadata.cs" />
|
||||
<Compile Include="Metadata\MetadataConsumerBase.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\NotificationService.cs" />
|
||||
<Compile Include="Notifications\DownloadMessage.cs" />
|
||||
@ -489,6 +508,7 @@
|
||||
<Compile Include="ThingiProvider\ProviderDefinition.cs" />
|
||||
<Compile Include="ThingiProvider\ProviderRepository.cs" />
|
||||
<Compile Include="ThingiProvider\ProviderFactory.cs" />
|
||||
<Compile Include="Tv\Actor.cs" />
|
||||
<Compile Include="Tv\EpisodeService.cs" />
|
||||
<Compile Include="Tv\Events\SeriesEditedEvent.cs" />
|
||||
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
||||
@ -500,6 +520,7 @@
|
||||
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
|
||||
<Compile Include="Datastore\ResultSet.cs" />
|
||||
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
|
||||
<Compile Include="Tv\Ratings.cs" />
|
||||
<Compile Include="Tv\RefreshEpisodeService.cs" />
|
||||
<Compile Include="Tv\SeriesRepository.cs" />
|
||||
<Compile Include="Qualities\QualityModel.cs" />
|
||||
@ -675,6 +696,9 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Download\Clients\uTorrent\" />
|
||||
<Folder Include="Metadata\EpisodeFiles\" />
|
||||
<Folder Include="Metadata\Seasons\" />
|
||||
<Folder Include="Metadata\Series\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
@ -64,7 +64,7 @@ namespace NzbDrone.Core.Parser
|
||||
Episodes = episodes,
|
||||
Path = filename,
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
ExistingFile = _diskProvider.IsParent(series.Path, filename)
|
||||
ExistingFile = DiskProvider.IsParent(series.Path, filename)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace NzbDrone.Core.ThingiProvider
|
||||
private readonly IContainer _container;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly List<TProvider> _providers;
|
||||
protected readonly List<TProvider> _providers;
|
||||
|
||||
protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository,
|
||||
IEnumerable<TProvider> providers,
|
||||
|
18
src/NzbDrone.Core/Tv/Actor.cs
Normal file
18
src/NzbDrone.Core/Tv/Actor.cs
Normal file
@ -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.Collections.Generic;
|
||||
using Marr.Data;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Common;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
public class Episode : ModelBase
|
||||
{
|
||||
public Episode()
|
||||
{
|
||||
Images = new List<MediaCover.MediaCover>();
|
||||
}
|
||||
|
||||
public const string AIR_DATE_FORMAT = "yyyy-MM-dd";
|
||||
|
||||
public int SeriesId { get; set; }
|
||||
@ -18,12 +23,13 @@ namespace NzbDrone.Core.Tv
|
||||
public string Title { get; set; }
|
||||
public string AirDate { get; set; }
|
||||
public DateTime? AirDateUtc { get; set; }
|
||||
|
||||
public string Overview { get; set; }
|
||||
public Boolean Monitored { get; set; }
|
||||
public Nullable<Int32> AbsoluteEpisodeNumber { get; set; }
|
||||
public int SceneSeasonNumber { 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; }
|
||||
|
||||
|
13
src/NzbDrone.Core/Tv/Ratings.cs
Normal file
13
src/NzbDrone.Core/Tv/Ratings.cs
Normal file
@ -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.AirDate = episode.AirDate;
|
||||
episodeToUpdate.AirDateUtc = episode.AirDateUtc;
|
||||
episodeToUpdate.Ratings = episode.Ratings;
|
||||
episodeToUpdate.Images = episode.Images;
|
||||
|
||||
successCount++;
|
||||
}
|
||||
|
@ -50,6 +50,10 @@ namespace NzbDrone.Core.Tv
|
||||
series.Images = seriesInfo.Images;
|
||||
series.Network = seriesInfo.Network;
|
||||
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))
|
||||
{
|
||||
|
@ -1,11 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
public class Season : IEmbeddedDocument
|
||||
{
|
||||
public Season()
|
||||
{
|
||||
Images = new List<MediaCover.MediaCover>();
|
||||
}
|
||||
|
||||
public int SeasonNumber { get; set; }
|
||||
public Boolean Monitored { get; set; }
|
||||
public List<MediaCover.MediaCover> Images { get; set; }
|
||||
}
|
||||
}
|
@ -13,6 +13,8 @@ namespace NzbDrone.Core.Tv
|
||||
public Series()
|
||||
{
|
||||
Images = new List<MediaCover.MediaCover>();
|
||||
Genres = new List<String>();
|
||||
Actors = new List<Actor>();
|
||||
}
|
||||
|
||||
public int TvdbId { get; set; }
|
||||
@ -35,6 +37,10 @@ namespace NzbDrone.Core.Tv
|
||||
public string TitleSlug { get; set; }
|
||||
public string Path { 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; }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user