diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1b274cb93 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +*text eol=lf + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index bd0bb243e..9780b6e71 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -155,6 +155,25 @@ module.exports = function (grunt) { } }, + requirejs: { + compile:{ + options: { + mainConfigFile: "_output/UI/app.js", + fileExclusionRegExp: /^.*\.(?!js$)[^.]+$/, + preserveLicenseComments: true, + dir: "rjs/", + optimize: 'none', + removeCombined: true, + inlineText: false, + modules: [{ + name: 'app', + exclude: ['JsLibraries/jquery'] + }], + + } + } + }, + watch: { options: { nospawn: false, @@ -213,6 +232,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-notify'); grunt.loadNpmTasks('grunt-curl'); + grunt.loadNpmTasks('grunt-contrib-requirejs'); grunt.registerTask('package', ['clean:output', 'copy', 'less', 'handlebars']); grunt.registerTask('default', ['package', 'watch']); diff --git a/package.json b/package.json index 4ce59dc39..13dce8658 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "grunt-contrib-copy": "*", "grunt-curl": "*", "grunt-notify": "*", - "grunt-contrib-clean": "*" + "grunt-contrib-clean": "*", + "grunt-contrib-requirejs": "*" } } diff --git a/src/NzbDrone.Api/DiskSpace/DiskSpaceModule.cs b/src/NzbDrone.Api/DiskSpace/DiskSpaceModule.cs new file mode 100644 index 000000000..5ec9d5215 --- /dev/null +++ b/src/NzbDrone.Api/DiskSpace/DiskSpaceModule.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using NzbDrone.Core.DiskSpace; + +namespace NzbDrone.Api.DiskSpace +{ + public class DiskSpaceModule :NzbDroneRestModule + { + private readonly IDiskSpaceService _diskSpaceService; + + public DiskSpaceModule(IDiskSpaceService diskSpaceService) + :base("diskspace") + { + _diskSpaceService = diskSpaceService; + GetResourceAll = GetFreeSpace; + } + + public List GetFreeSpace() + { + return ToListResource(_diskSpaceService.GetFreeSpace); + } + } +} diff --git a/src/NzbDrone.Api/DiskSpace/DiskSpaceResource.cs b/src/NzbDrone.Api/DiskSpace/DiskSpaceResource.cs new file mode 100644 index 000000000..039e4b0c3 --- /dev/null +++ b/src/NzbDrone.Api/DiskSpace/DiskSpaceResource.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.DiskSpace +{ + public class DiskSpaceResource : RestResource + { + public string Path { get; set; } + public string Label { get; set; } + public Int64 FreeSpace { get; set; } + public Int64 TotalSpace { get; set; } + } +} diff --git a/src/NzbDrone.Api/Indexers/IndexerModule.cs b/src/NzbDrone.Api/Indexers/IndexerModule.cs index 33c56a108..521ce0bfa 100644 --- a/src/NzbDrone.Api/Indexers/IndexerModule.cs +++ b/src/NzbDrone.Api/Indexers/IndexerModule.cs @@ -2,11 +2,17 @@ namespace NzbDrone.Api.Indexers { - public class IndexerModule : ProviderModuleBase + public class IndexerModule : ProviderModuleBase { public IndexerModule(IndexerFactory indexerFactory) : base(indexerFactory, "indexer") { } + + protected override void Validate(IndexerDefinition definition) + { + if (!definition.Enable) return; + base.Validate(definition); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Indexers/IndexerResource.cs b/src/NzbDrone.Api/Indexers/IndexerResource.cs index a613526fe..dbb55c3f0 100644 --- a/src/NzbDrone.Api/Indexers/IndexerResource.cs +++ b/src/NzbDrone.Api/Indexers/IndexerResource.cs @@ -1,16 +1,9 @@ using System; -using System.Collections.Generic; -using NzbDrone.Api.ClientSchema; -using NzbDrone.Api.REST; namespace NzbDrone.Api.Indexers { - public class IndexerResource : RestResource + public class IndexerResource : ProviderResource { public Boolean Enable { get; set; } - public String Name { get; set; } - public List Fields { get; set; } - public String Implementation { get; set; } - public String ConfigContract { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Indexers/IndexerSchemaModule.cs b/src/NzbDrone.Api/Indexers/IndexerSchemaModule.cs index a0312bdc4..d433102c8 100644 --- a/src/NzbDrone.Api/Indexers/IndexerSchemaModule.cs +++ b/src/NzbDrone.Api/Indexers/IndexerSchemaModule.cs @@ -19,10 +19,8 @@ namespace NzbDrone.Api.Indexers private List GetSchema() { - var indexers = _indexerFactory.Templates().Where(c => c.Implementation =="Newznab"); - var result = new List(indexers.Count()); foreach (var indexer in indexers) diff --git a/src/NzbDrone.Api/Notifications/NotificationModule.cs b/src/NzbDrone.Api/Notifications/NotificationModule.cs index c8be13f67..25bf43419 100644 --- a/src/NzbDrone.Api/Notifications/NotificationModule.cs +++ b/src/NzbDrone.Api/Notifications/NotificationModule.cs @@ -10,82 +10,17 @@ using Omu.ValueInjecter; namespace NzbDrone.Api.Notifications { - public class NotificationModule : NzbDroneRestModule + public class IndexerModule : ProviderModuleBase { - private readonly INotificationService _notificationService; - - public NotificationModule(INotificationService notificationService) + public IndexerModule(NotificationFactory notificationrFactory) + : base(notificationrFactory, "notification") { - _notificationService = notificationService; - - GetResourceAll = GetAll; - GetResourceById = GetNotification; - CreateResource = Create; - UpdateResource = Update; - DeleteResource = DeleteNotification; } - private NotificationResource GetNotification(int id) + protected override void Validate(NotificationDefinition definition) { - return _notificationService.Get(id).InjectTo(); - } - - private List GetAll() - { - var notifications = _notificationService.All(); - - var result = new List(notifications.Count); - - foreach (var notification in notifications) - { - var notificationResource = new NotificationResource(); - notificationResource.InjectFrom(notification); - notificationResource.Fields = SchemaBuilder.ToSchema(notification.Settings); - notificationResource.TestCommand = String.Format("test{0}", notification.Implementation.ToLowerInvariant()); - - result.Add(notificationResource); - } - - return result; - } - - private int Create(NotificationResource notificationResource) - { - var notification = ConvertToNotification(notificationResource); - return _notificationService.Create(notification).Id; - } - - private void Update(NotificationResource notificationResource) - { - var notification = ConvertToNotification(notificationResource); - notification.Id = notificationResource.Id; - _notificationService.Update(notification); - } - - private void DeleteNotification(int id) - { - _notificationService.Delete(id); - } - - private Notification ConvertToNotification(NotificationResource notificationResource) - { - var notification = _notificationService.Schema() - .SingleOrDefault(i => - i.Implementation.Equals(notificationResource.Implementation, - StringComparison.InvariantCultureIgnoreCase)); - - if (notification == null) - { - throw new BadRequestException("Invalid Notification Implementation"); - } - - notification.InjectFrom(notificationResource); - - //var configType = ReflectionExtensions.CoreAssembly.FindTypeByName(notification) - - //notification.Settings = SchemaBuilder.ReadFormSchema(notification.Settings, notificationResource.Fields); - - return notification; + if (!definition.OnGrab && !definition.OnDownload) return; + base.Validate(definition); } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Notifications/NotificationResource.cs b/src/NzbDrone.Api/Notifications/NotificationResource.cs index d96539de8..54ffe720a 100644 --- a/src/NzbDrone.Api/Notifications/NotificationResource.cs +++ b/src/NzbDrone.Api/Notifications/NotificationResource.cs @@ -1,19 +1,12 @@ using System; -using System.Collections.Generic; -using NzbDrone.Api.ClientSchema; -using NzbDrone.Api.REST; namespace NzbDrone.Api.Notifications { - public class NotificationResource : RestResource + public class NotificationResource : ProviderResource { - public String Name { get; set; } - public String ImplementationName { get; set; } public String Link { get; set; } public Boolean OnGrab { get; set; } public Boolean OnDownload { get; set; } - public List Fields { get; set; } - public String Implementation { get; set; } public String TestCommand { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Notifications/NotificationSchemaModule.cs b/src/NzbDrone.Api/Notifications/NotificationSchemaModule.cs index 68a5bd594..db661b5c1 100644 --- a/src/NzbDrone.Api/Notifications/NotificationSchemaModule.cs +++ b/src/NzbDrone.Api/Notifications/NotificationSchemaModule.cs @@ -7,20 +7,19 @@ using Omu.ValueInjecter; namespace NzbDrone.Api.Notifications { public class NotificationSchemaModule : NzbDroneRestModule - { - private readonly INotificationService _notificationService; + { + private readonly INotificationFactory _notificationFactory; - public NotificationSchemaModule(INotificationService notificationService) + public NotificationSchemaModule(INotificationFactory notificationFactory) : base("notification/schema") { - _notificationService = notificationService; - + _notificationFactory = notificationFactory; GetResourceAll = GetSchema; } private List GetSchema() { - var notifications = _notificationService.Schema(); + var notifications = _notificationFactory.Templates(); var result = new List(notifications.Count); @@ -29,7 +28,6 @@ namespace NzbDrone.Api.Notifications var notificationResource = new NotificationResource(); notificationResource.InjectFrom(notification); notificationResource.Fields = SchemaBuilder.ToSchema(notification.Settings); - notificationResource.TestCommand = String.Format("test{0}", notification.Implementation.ToLowerInvariant()); result.Add(notificationResource); } diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 7b885fe48..2227fbe11 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -87,6 +87,8 @@ + + @@ -109,7 +111,7 @@ - + diff --git a/src/NzbDrone.Api/ProviderModuleBase.cs b/src/NzbDrone.Api/ProviderModuleBase.cs index f379f598a..41e2ebe20 100644 --- a/src/NzbDrone.Api/ProviderModuleBase.cs +++ b/src/NzbDrone.Api/ProviderModuleBase.cs @@ -4,7 +4,6 @@ using FluentValidation; using Nancy; using NzbDrone.Api.ClientSchema; using NzbDrone.Api.Extensions; -using NzbDrone.Api.Indexers; using NzbDrone.Api.Mapping; using NzbDrone.Common.Reflection; using NzbDrone.Core.ThingiProvider; @@ -30,8 +29,6 @@ namespace NzbDrone.Api UpdateResource = UpdateProvider; DeleteResource = DeleteProvider; - - SharedValidator.RuleFor(c => c.Name).NotEmpty(); SharedValidator.RuleFor(c => c.Implementation).NotEmpty(); SharedValidator.RuleFor(c => c.ConfigContract).NotEmpty(); @@ -69,39 +66,25 @@ namespace NzbDrone.Api return indexer.Id; } - private void UpdateProvider(TProviderResource indexerResource) + private void UpdateProvider(TProviderResource providerResource) { - var indexer = GetDefinition(indexerResource); + var providerDefinition = GetDefinition(providerResource); - ValidateIndexer(indexer); + Validate(providerDefinition); - _providerFactory.Update(indexer); + _providerFactory.Update(providerDefinition); } - - private static void ValidateIndexer(ProviderDefinition definition) + private TProviderDefinition GetDefinition(TProviderResource providerResource) { - if (!definition.Enable) return; - - var validationResult = definition.Settings.Validate(); - - if (!validationResult.IsValid) - { - throw new ValidationException(validationResult.Errors); - } - } - - private TProviderDefinition GetDefinition(TProviderResource indexerResource) - { - var definition = new TProviderDefinition(); - definition.InjectFrom(indexerResource); + definition.InjectFrom(providerResource); var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract); - definition.Settings = (IProviderConfig)SchemaBuilder.ReadFormSchema(indexerResource.Fields, configContract); + definition.Settings = (IProviderConfig)SchemaBuilder.ReadFormSchema(providerResource.Fields, configContract); - ValidateIndexer(definition); + Validate(definition); return definition; } @@ -113,22 +96,30 @@ namespace NzbDrone.Api private Response GetTemplates() { + var templates = _providerFactory.Templates(); - var indexers = _providerFactory.Templates(); + var result = new List(templates.Count()); - - var result = new List(indexers.Count()); - - foreach (var indexer in indexers) + foreach (var providerDefinition in templates) { - var indexerResource = new IndexerResource(); - indexerResource.InjectFrom(indexer); - indexerResource.Fields = SchemaBuilder.ToSchema(indexer.Settings); + var providerResource = new TProviderResource(); + providerResource.InjectFrom(providerDefinition); + providerResource.Fields = SchemaBuilder.ToSchema(providerDefinition.Settings); - result.Add(indexerResource); + result.Add(providerResource); } return result.AsResponse(); } + + protected virtual void Validate(TProviderDefinition definition) + { + var validationResult = definition.Settings.Validate(); + + if (!validationResult.IsValid) + { + throw new ValidationException(validationResult.Errors); + } + } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/IndexerResource.cs b/src/NzbDrone.Api/ProviderResource.cs similarity index 89% rename from src/NzbDrone.Api/IndexerResource.cs rename to src/NzbDrone.Api/ProviderResource.cs index 65c5bad64..f866341e0 100644 --- a/src/NzbDrone.Api/IndexerResource.cs +++ b/src/NzbDrone.Api/ProviderResource.cs @@ -7,7 +7,6 @@ namespace NzbDrone.Api { public class ProviderResource : RestResource { - public Boolean Enable { get; set; } public String Name { get; set; } public List Fields { get; set; } public String Implementation { get; set; } diff --git a/src/NzbDrone.Common/DiskProvider.cs b/src/NzbDrone.Common/DiskProvider.cs index 5860d95cd..a3d9e0255 100644 --- a/src/NzbDrone.Common/DiskProvider.cs +++ b/src/NzbDrone.Common/DiskProvider.cs @@ -42,6 +42,9 @@ namespace NzbDrone.Common void SetFolderWriteTime(string path, DateTime time); FileAttributes GetFileAttributes(string path); void EmptyFolder(string path); + string[] GetFixedDrives(); + long? GetTotalSize(string path); + string GetVolumeLabel(string path); } public class DiskProvider : IDiskProvider @@ -322,30 +325,6 @@ namespace NzbDrone.Common return DriveFreeSpaceEx(root); } - private static long DriveFreeSpaceEx(string folderName) - { - if (string.IsNullOrEmpty(folderName)) - { - throw new ArgumentNullException("folderName"); - } - - if (!folderName.EndsWith("\\")) - { - folderName += '\\'; - } - - ulong free = 0; - ulong dummy1 = 0; - ulong dummy2 = 0; - - if (GetDiskFreeSpaceEx(folderName, out free, out dummy1, out dummy2)) - { - return (long)free; - } - - return 0; - } - public string ReadAllText(string filePath) { Ensure.That(() => filePath).IsValidPath(); @@ -475,5 +454,105 @@ namespace NzbDrone.Common DeleteFolder(directory, true); } } + + public string[] GetFixedDrives() + { + return (DriveInfo.GetDrives().Where(x => x.DriveType == DriveType.Fixed).Select(x => x.Name)).ToArray(); + } + + public long? GetTotalSize(string path) + { + Ensure.That(() => path).IsValidPath(); + + var root = GetPathRoot(path); + + if (!FolderExists(root)) + throw new DirectoryNotFoundException(root); + + if (OsInfo.IsLinux) + { + var drives = DriveInfo.GetDrives(); + + foreach (var drive in drives) + { + try + { + if (drive.IsReady && path.StartsWith(drive.Name, StringComparison.CurrentCultureIgnoreCase)) + { + return drive.TotalSize; + } + } + catch (InvalidOperationException e) + { + Logger.ErrorException("Couldn't get total space for " + path, e); + } + } + + return null; + } + + + return DriveTotalSizeEx(root); + } + + public string GetVolumeLabel(string path) + { + var driveInfo = DriveInfo.GetDrives().SingleOrDefault(d => d.Name == path); + + if (driveInfo == null) + { + return null; + } + + return driveInfo.VolumeLabel; + } + + private static long DriveFreeSpaceEx(string folderName) + { + if (string.IsNullOrEmpty(folderName)) + { + throw new ArgumentNullException("folderName"); + } + + if (!folderName.EndsWith("\\")) + { + folderName += '\\'; + } + + ulong free = 0; + ulong dummy1 = 0; + ulong dummy2 = 0; + + if (GetDiskFreeSpaceEx(folderName, out free, out dummy1, out dummy2)) + { + return (long)free; + } + + return 0; + } + + private static long DriveTotalSizeEx(string folderName) + { + if (string.IsNullOrEmpty(folderName)) + { + throw new ArgumentNullException("folderName"); + } + + if (!folderName.EndsWith("\\")) + { + folderName += '\\'; + } + + ulong total = 0; + ulong dummy1 = 0; + ulong dummy2 = 0; + + if (GetDiskFreeSpaceEx(folderName, out dummy1, out total, out dummy2)) + { + return (long)total; + } + + return 0; + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs new file mode 100644 index 000000000..1ca3e9c41 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs @@ -0,0 +1,242 @@ +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class NotInQueueSpecificationFixture : CoreTest + { + private Series _series; + private Episode _episode; + private RemoteEpisode _remoteEpisode; + private Mock _downloadClient; + + private Series _otherSeries; + private Episode _otherEpisode; + + [SetUp] + public void Setup() + { + _series = Builder.CreateNew().Build(); + + _episode = Builder.CreateNew() + .With(e => e.SeriesId = _series.Id) + .Build(); + + _otherSeries = Builder.CreateNew() + .With(s => s.Id = 2) + .Build(); + + _otherEpisode = Builder.CreateNew() + .With(e => e.SeriesId = _otherSeries.Id) + .With(e => e.Id = 2) + .With(e => e.SeasonNumber = 2) + .With(e => e.EpisodeNumber = 2) + .Build(); + + _remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD)}) + .Build(); + + _downloadClient = Mocker.GetMock(); + + Mocker.GetMock() + .Setup(s => s.GetDownloadClient()) + .Returns(_downloadClient.Object); + + _downloadClient.SetupGet(s => s.IsConfigured) + .Returns(true); + } + + private void GivenEmptyQueue() + { + _downloadClient.Setup(s => s.GetQueue()) + .Returns(new List()); + } + + private void GivenQueue(IEnumerable remoteEpisodes) + { + var queue = new List(); + + foreach (var remoteEpisode in remoteEpisodes) + { + queue.Add(new QueueItem + { + RemoteEpisode = remoteEpisode + }); + } + + _downloadClient.Setup(s => s.GetQueue()) + .Returns(queue); + } + + [Test] + public void should_return_true_when_queue_is_empty() + { + GivenEmptyQueue(); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_series_doesnt_match() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _otherSeries) + .With(r => r.Episodes = new List { _episode }) + .Build(); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_quality_in_queue_is_lower() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.SDTV) + }) + .Build(); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_episode_doesnt_match() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _otherEpisode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.DVD) + }) + .Build(); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue(); + } + + [Test] + public void should_return_false_when_qualities_are_the_same() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.DVD) + }) + .Build(); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_quality_in_queue_is_better() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.HDTV720p) + }) + .Build(); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_return_false_if_matching_multi_episode_is_in_queue() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode, _otherEpisode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.HDTV720p) + }) + .Build(); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_return_false_if_multi_episode_has_one_episode_in_queue() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.HDTV720p) + }) + .Build(); + + _remoteEpisode.Episodes.Add(_otherEpisode); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_return_false_if_multi_part_episode_is_already_in_queue() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode, _otherEpisode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.HDTV720p) + }) + .Build(); + + _remoteEpisode.Episodes.Add(_otherEpisode); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse(); + } + + [Test] + public void should_return_false_if_multi_part_episode_has_two_episodes_in_queue() + { + var remoteEpisodes = Builder.CreateListOfSize(2) + .All() + .With(r => r.Series = _series) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = + new QualityModel( + Quality.HDTV720p) + }) + .TheFirst(1) + .With(r => r.Episodes = new List {_episode}) + .TheNext(1) + .With(r => r.Episodes = new List {_otherEpisode}) + .Build(); + + _remoteEpisode.Episodes.Add(_otherEpisode); + GivenQueue(remoteEpisodes); + Subject.IsSatisfiedBy(_remoteEpisode, null ).Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/QueueFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/QueueFixture.cs index d4ee7aef4..6c83e0bcb 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/QueueFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/QueueFixture.cs @@ -5,11 +5,14 @@ using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Nzbget; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetProviderTests { - public class QueueFixture : CoreTest + public class QueueFixture : CoreTest { [SetUp] public void Setup() @@ -49,10 +52,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetProviderTests { WithEmptyQueue(); - Mocker.Resolve() - .GetQueue() - .Should() - .BeEmpty(); + Subject.GetQueue() + .Should() + .BeEmpty(); } [Test] @@ -60,10 +62,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetProviderTests { WithFullQueue(); - Mocker.Resolve() - .GetQueue() - .Should() - .HaveCount(1); + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), 0, null)) + .Returns(new RemoteEpisode {Series = new Series()}); + + Subject.GetQueue() + .Should() + .HaveCount(1); } } } diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs new file mode 100644 index 000000000..0c97d0ae7 --- /dev/null +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs @@ -0,0 +1,45 @@ +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Housekeeping.Housekeepers; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.Housekeeping.Housekeepers +{ + [TestFixture] + public class CleanupOrphanedEpisodeFilesFixture : DbTest + { + [Test] + public void should_delete_orphaned_episode_files() + { + var episodeFile = Builder.CreateNew() + .BuildNew(); + + Db.Insert(episodeFile); + Subject.Clean(); + AllStoredModels.Should().BeEmpty(); + } + + [Test] + public void should_not_delete_unorphaned_episode_files() + { + var episodeFiles = Builder.CreateListOfSize(2) + .BuildListOfNew(); + + Db.InsertMany(episodeFiles); + + var episode = Builder.CreateNew() + .With(e => e.EpisodeFileId = episodeFiles.First().Id) + .BuildNew(); + + Db.Insert(episode); + + Subject.Clean(); + AllStoredModels.Should().HaveCount(1); + Db.All().Should().Contain(e => e.EpisodeFileId == AllStoredModels.First().Id); + } + } +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs index fd8fa0345..9c6596be4 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs @@ -48,7 +48,6 @@ namespace NzbDrone.Core.Test.MediaFiles Episodes = new List {episode}, Path = @"C:\Test\TV\30 Rock\30 Rock - S01E01 - Pilit.avi".AsOsAgnostic(), Quality = new QualityModel(Quality.Bluray720p) - })); } @@ -125,5 +124,32 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); } + + [Test] + public void should_import_larger_files_first() + { + var fileDecision = _approvedDecisions.First(); + fileDecision.LocalEpisode.Size = 1.Gigabytes(); + + var sampleDecision = new ImportDecision + (new LocalEpisode + { + Series = fileDecision.LocalEpisode.Series, + Episodes = new List {fileDecision.LocalEpisode.Episodes.First()}, + Path = @"C:\Test\TV\30 Rock\30 Rock - S01E01 - Pilit.avi".AsOsAgnostic(), + Quality = new QualityModel(Quality.Bluray720p), + Size = 80.Megabytes() + }); + + + var all = new List(); + all.Add(fileDecision); + all.Add(sampleDecision); + + var results = Subject.Import(all); + + results.Should().HaveCount(1); + results.Should().ContainSingle(d => d.LocalEpisode.Size == fileDecision.LocalEpisode.Size); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs index b1df6db29..4577fe5c9 100644 --- a/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs @@ -80,8 +80,6 @@ namespace NzbDrone.Core.Test.MetadataSourceTests episodes.GroupBy(e => e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000")) .Max(e => e.Count()).Should().Be(1); - episodes.Select(c => c.TvDbEpisodeId).Should().OnlyHaveUniqueItems(); - episodes.Should().Contain(c => c.SeasonNumber > 0); episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Overview)); @@ -98,7 +96,6 @@ namespace NzbDrone.Core.Test.MetadataSourceTests { episode.Should().NotBeNull(); episode.EpisodeNumber.Should().NotBe(0); - episode.TvDbEpisodeId.Should().BeGreaterThan(0); episode.Should().NotBeNull(); diff --git a/src/NzbDrone.Core.Test/NotificationTests/NotificationServiceFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/NotificationServiceFixture.cs deleted file mode 100644 index 80875d24f..000000000 --- a/src/NzbDrone.Core.Test/NotificationTests/NotificationServiceFixture.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Composition; -using NzbDrone.Core.MediaFiles.Events; -using NzbDrone.Core.Notifications; -using NzbDrone.Core.Notifications.Email; -using NzbDrone.Core.Notifications.Growl; -using NzbDrone.Core.Notifications.Plex; -using NzbDrone.Core.Notifications.Prowl; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.NotificationTests -{ - public class NotificationServiceFixture : DbTest - { - private List _notifications; - - [SetUp] - public void Setup() - { - _notifications = new List(); - - _notifications.Add(new Notifications.Xbmc.Xbmc(null)); - _notifications.Add(new PlexClient(null)); - _notifications.Add(new PlexServer(null)); - _notifications.Add(new Email(null)); - _notifications.Add(new Growl(null)); - _notifications.Add(new Prowl(null)); - - Mocker.SetConstant>(_notifications); - } - - [Test] - public void getting_list_of_indexers_should_be_empty_by_default() - { - Mocker.SetConstant(Mocker.Resolve()); - - var notifications = Subject.All().ToList(); - notifications.Should().BeEmpty(); - } - - [Test] - public void should_be_able_to_get_schema_for_all_notifications() - { - Mocker.SetConstant(Mocker.Resolve()); - - Mocker.GetMock().Setup(s => s.Resolve(typeof(Notifications.Xbmc.Xbmc))) - .Returns(new Notifications.Xbmc.Xbmc(null)); - - Mocker.GetMock().Setup(s => s.Resolve(typeof(PlexClient))) - .Returns(new PlexClient(null)); - - Mocker.GetMock().Setup(s => s.Resolve(typeof(PlexServer))) - .Returns(new PlexServer(null)); - - Mocker.GetMock().Setup(s => s.Resolve(typeof(Email))) - .Returns(new Email(null)); - - Mocker.GetMock().Setup(s => s.Resolve(typeof(Growl))) - .Returns(new Growl(null)); - - Mocker.GetMock().Setup(s => s.Resolve(typeof(Prowl))) - .Returns(new Prowl(null)); - - var notifications = Subject.Schema().ToList(); - notifications.Should().NotBeEmpty(); - notifications.Should().NotContain(c => c.Settings == null); - notifications.Should().NotContain(c => c.Instance == null); - notifications.Should().NotContain(c => c.ImplementationName == null); - notifications.Select(c => c.ImplementationName).Should().OnlyHaveUniqueItems(); - notifications.Select(c => c.Instance).Should().OnlyHaveUniqueItems(); - notifications.Select(c => c.Id).Should().OnlyHaveUniqueItems(); - } - - [Test] - [Explicit] - public void should_try_other_notifiers_when_one_fails() - { - var notifications = Builder.CreateListOfSize(2) - .All() - .With(n => n.OnGrab = true) - .With(n => n.OnDownload = true) - .TheFirst(1) - .With(n => n.Implementation = "Xbmc") - .TheLast(1) - .With(n => n.Implementation = "Email") - .Build() - .ToList(); - - var series = Builder.CreateNew() - .With(s => s.SeriesType = SeriesTypes.Standard) - .Build(); - - var parsedEpisodeInfo = Builder.CreateNew() - .With(p => p.EpisodeNumbers = new int[] {1}) - .Build(); - - var localEpisode = Builder.CreateNew() - .With(e => e.Series = series) - .With(e => e.ParsedEpisodeInfo = parsedEpisodeInfo) - .With(e => e.Episodes = Builder.CreateListOfSize(1) - .Build().ToList()) - .Build(); - - Mocker.GetMock() - .Setup(s => s.All()) - .Returns(notifications); - - //Todo: How can we test this, right now without an empty constructor it won't work - Mocker.GetMock() - .Setup(s => s.OnDownload(It.IsAny(), series)) - .Throws(new SocketException()); - - Subject.Handle(new EpisodeDownloadedEvent(localEpisode)); - - Mocker.GetMock() - .Verify(v => v.OnDownload(It.IsAny(), series), Times.Once()); - - Mocker.GetMock() - .Verify(v => v.OnDownload(It.IsAny(), series), Times.Once()); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index cbe2b0f67..e75b4d0ac 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -111,6 +111,7 @@ + @@ -127,6 +128,7 @@ + @@ -154,7 +156,6 @@ - diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs index e08d77b23..e59c67dec 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs @@ -48,7 +48,6 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests var monitoredSeriesEpisodes = Builder.CreateListOfSize(3) .All() .With(e => e.Id = 0) - .With(e => e.TvDbEpisodeId = RandomNumber) .With(e => e.SeriesId = _monitoredSeries.Id) .With(e => e.EpisodeFileId = 0) .With(e => e.AirDateUtc = DateTime.Now.AddDays(-5)) @@ -62,7 +61,6 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests var unmonitoredSeriesEpisodes = Builder.CreateListOfSize(3) .All() .With(e => e.Id = 0) - .With(e => e.TvDbEpisodeId = RandomNumber) .With(e => e.SeriesId = _unmonitoredSeries.Id) .With(e => e.EpisodeFileId = 0) .With(e => e.AirDateUtc = DateTime.Now.AddDays(-5)) diff --git a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs index b02608d8d..bb89e1a8c 100644 Binary files a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs and b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs differ diff --git a/src/NzbDrone.Core/Datastore/Migration/022_move_indexer_to_generic_provider.cs b/src/NzbDrone.Core/Datastore/Migration/022_move_indexer_to_generic_provider.cs new file mode 100644 index 000000000..5d867f8a8 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/022_move_indexer_to_generic_provider.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Data; +using FluentMigrator; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(22)] + public class move_indexer_to_generic_provider : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Indexers").AddColumn("ConfigContract").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/022_move_notification_to_generic_provider.cs b/src/NzbDrone.Core/Datastore/Migration/022_move_notification_to_generic_provider.cs deleted file mode 100644 index 2b5182419..000000000 --- a/src/NzbDrone.Core/Datastore/Migration/022_move_notification_to_generic_provider.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using FluentMigrator; -using NzbDrone.Common.Serializer; -using NzbDrone.Core.Datastore.Migration.Framework; - -namespace NzbDrone.Core.Datastore.Migration -{ - [Migration(22)] - public class move_indexer_to_generic_provider : NzbDroneMigrationBase - { - protected override void MainDbUpgrade() - { - Alter.Table("Indexers").AddColumn("ConfigContract").AsString().Nullable(); - - //Execute.WithConnection(ConvertSeasons); - } - - private void ConvertSeasons(IDbConnection conn, IDbTransaction tran) - { - using (IDbCommand allSeriesCmd = conn.CreateCommand()) - { - allSeriesCmd.Transaction = tran; - allSeriesCmd.CommandText = @"SELECT Id FROM Series"; - using (IDataReader allSeriesReader = allSeriesCmd.ExecuteReader()) - { - while (allSeriesReader.Read()) - { - int seriesId = allSeriesReader.GetInt32(0); - var seasons = new List(); - - using (IDbCommand seasonsCmd = conn.CreateCommand()) - { - seasonsCmd.Transaction = tran; - seasonsCmd.CommandText = String.Format(@"SELECT SeasonNumber, Monitored FROM Seasons WHERE SeriesId = {0}", seriesId); - - using (IDataReader seasonReader = seasonsCmd.ExecuteReader()) - { - while (seasonReader.Read()) - { - int seasonNumber = seasonReader.GetInt32(0); - bool monitored = seasonReader.GetBoolean(1); - - if (seasonNumber == 0) - { - monitored = false; - } - - seasons.Add(new { seasonNumber, monitored }); - } - } - } - - using (IDbCommand updateCmd = conn.CreateCommand()) - { - var text = String.Format("UPDATE Series SET Seasons = '{0}' WHERE Id = {1}", seasons.ToJson(), seriesId); - - updateCmd.Transaction = tran; - updateCmd.CommandText = text; - updateCmd.ExecuteNonQuery(); - } - } - } - } - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Migration/024_drop_tvdb_episodeid.cs b/src/NzbDrone.Core/Datastore/Migration/024_drop_tvdb_episodeid.cs new file mode 100644 index 000000000..90144bcbb --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/024_drop_tvdb_episodeid.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(24)] + public class drop_tvdb_episodeid : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + SqLiteAlter.DropColumns("Episodes", new[] { "TvDbEpisodeId" }); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/025_move_notification_to_generic_provider.cs b/src/NzbDrone.Core/Datastore/Migration/025_move_notification_to_generic_provider.cs new file mode 100644 index 000000000..1937f76eb --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/025_move_notification_to_generic_provider.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(25)] + public class move_notification_to_generic_provider : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Notifications").AddColumn("ConfigContract").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/026_add_config_contract_to_notifications.cs b/src/NzbDrone.Core/Datastore/Migration/026_add_config_contract_to_notifications.cs new file mode 100644 index 000000000..8eb24daae --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/026_add_config_contract_to_notifications.cs @@ -0,0 +1,24 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(26)] + public class add_config_contract_to_notifications : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Update.Table("Notifications").Set(new { ConfigContract = "EmailSettings" }).Where(new { Implementation = "Email" }); + Update.Table("Notifications").Set(new { ConfigContract = "GrowlSettings" }).Where(new { Implementation = "Growl" }); + Update.Table("Notifications").Set(new { ConfigContract = "NotifyMyAndroidSettings" }).Where(new { Implementation = "NotifyMyAndroid" }); + Update.Table("Notifications").Set(new { ConfigContract = "PlexClientSettings" }).Where(new { Implementation = "PlexClient" }); + Update.Table("Notifications").Set(new { ConfigContract = "PlexServerSettings" }).Where(new { Implementation = "PlexServer" }); + Update.Table("Notifications").Set(new { ConfigContract = "ProwlSettings" }).Where(new { Implementation = "Prowl" }); + Update.Table("Notifications").Set(new { ConfigContract = "PushBulletSettings" }).Where(new { Implementation = "PushBullet" }); + Update.Table("Notifications").Set(new { ConfigContract = "PushoverSettings" }).Where(new { Implementation = "Pushover" }); + Update.Table("Notifications").Set(new { ConfigContract = "XbmcSettings" }).Where(new { Implementation = "Xbmc" }); + + Delete.FromTable("Notifications").IsNull("ConfigContract"); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/027_fix_omgwtfnzbs.cs b/src/NzbDrone.Core/Datastore/Migration/027_fix_omgwtfnzbs.cs new file mode 100644 index 000000000..d7b8b31fc --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/027_fix_omgwtfnzbs.cs @@ -0,0 +1,24 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(27)] + public class fix_omgwtfnzbs : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Update.Table("Indexers") + .Set(new {ConfigContract = "OmgwtfnzbsSettings"}) + .Where(new {Implementation = "Omgwtfnzbs"}); + + Update.Table("Indexers") + .Set(new {Settings = "{}"}) + .Where(new {Implementation = "Omgwtfnzbs", Settings = (string) null}); + + Update.Table("Indexers") + .Set(new { Settings = "{}" }) + .Where(new { Implementation = "Omgwtfnzbs", Settings = "" }); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index bade3c2d6..ec341d6dc 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -35,12 +35,13 @@ namespace NzbDrone.Core.Datastore Mapper.Entity().RegisterModel("Indexers"); Mapper.Entity().RegisterModel("ScheduledTasks"); - Mapper.Entity().RegisterModel("Notifications"); + Mapper.Entity() + .RegisterModel("Notifications"); Mapper.Entity().RegisterModel("SceneMappings"); Mapper.Entity().RegisterModel("History") - .AutoMapChildModels(); + .AutoMapChildModels(); Mapper.Entity().RegisterModel("Series") .Ignore(s => s.RootFolderPath) @@ -96,7 +97,6 @@ namespace NzbDrone.Core.Datastore { var embeddedTypes = typeof(IEmbeddedDocument).Assembly.ImplementationsOf(); - var embeddedConvertor = new EmbeddedDocumentConverter(); var genericListDefinition = typeof(List<>).GetGenericTypeDefinition(); foreach (var embeddedType in embeddedTypes) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs index 03a2b46f6..6a8adc40f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs @@ -4,6 +4,7 @@ using System.Linq; using NLog; using NzbDrone.Core.Download; using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; @@ -38,31 +39,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return true; } - var queue = downloadClient.GetQueue().Select(queueItem => Parser.Parser.ParseTitle(queueItem.Title)).Where(episodeInfo => episodeInfo != null); + var queue = downloadClient.GetQueue().Select(q => q.RemoteEpisode); return !IsInQueue(subject, queue); } - private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable queue) + private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable queue) { - var matchingTitle = queue.Where(q => String.Equals(q.SeriesTitle, newEpisode.Series.CleanTitle, StringComparison.InvariantCultureIgnoreCase)); + var matchingSeries = queue.Where(q => q.Series.Id == newEpisode.Series.Id); + var matchingSeriesAndQuality = matchingSeries.Where(q => q.ParsedEpisodeInfo.Quality >= newEpisode.ParsedEpisodeInfo.Quality); - var matchingTitleWithQuality = matchingTitle.Where(q => q.Quality >= newEpisode.ParsedEpisodeInfo.Quality); - - if (newEpisode.Series.SeriesType == SeriesTypes.Daily) - { - return matchingTitleWithQuality.Any(q => q.AirDate.Value.Date == newEpisode.ParsedEpisodeInfo.AirDate.Value.Date); - } - - var matchingSeason = matchingTitleWithQuality.Where(q => q.SeasonNumber == newEpisode.ParsedEpisodeInfo.SeasonNumber); - - if (newEpisode.ParsedEpisodeInfo.FullSeason) - { - return matchingSeason.Any(); - } - - return matchingSeason.Any(q => q.EpisodeNumbers != null && q.EpisodeNumbers.Any(e => newEpisode.ParsedEpisodeInfo.EpisodeNumbers.Contains(e))); + return matchingSeriesAndQuality.Any(q => q.Episodes.Select(e => e.Id).Intersect(newEpisode.Episodes.Select(e => e.Id)).Any()); } - } } diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpace.cs b/src/NzbDrone.Core/DiskSpace/DiskSpace.cs new file mode 100644 index 000000000..137ea6cbe --- /dev/null +++ b/src/NzbDrone.Core/DiskSpace/DiskSpace.cs @@ -0,0 +1,12 @@ +using System; + +namespace NzbDrone.Core.DiskSpace +{ + public class DiskSpace + { + public String Path { get; set; } + public String Label { get; set; } + public long FreeSpace { get; set; } + public long TotalSpace { get; set; } + } +} diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs new file mode 100644 index 000000000..def672b5d --- /dev/null +++ b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.DiskSpace +{ + public interface IDiskSpaceService + { + List GetFreeSpace(); + } + + public class DiskSpaceService : IDiskSpaceService + { + private readonly ISeriesService _seriesService; + private readonly IConfigService _configService; + private readonly IDiskProvider _diskProvider; + private readonly Logger _logger; + + public DiskSpaceService(ISeriesService seriesService, IConfigService configService, IDiskProvider diskProvider, Logger logger) + { + _seriesService = seriesService; + _configService = configService; + _diskProvider = diskProvider; + _logger = logger; + } + + public List GetFreeSpace() + { + var diskSpace = new List(); + diskSpace.AddRange(GetSeriesFreeSpace()); + diskSpace.AddRange(GetDroneFactoryFreeSpace()); + diskSpace.AddRange(GetFixedDisksFreeSpace()); + + return diskSpace.DistinctBy(d => d.Path).ToList(); + } + + private IEnumerable GetSeriesFreeSpace() + { + var seriesRootPaths = _seriesService.GetAllSeries().Select(s => _diskProvider.GetPathRoot(s.Path)).Distinct(); + + return GetDiskSpace(seriesRootPaths); + } + + private IEnumerable GetDroneFactoryFreeSpace() + { + if (!String.IsNullOrWhiteSpace(_configService.DownloadedEpisodesFolder)) + { + return GetDiskSpace(new[] { _diskProvider.GetPathRoot(_configService.DownloadedEpisodesFolder) }); + } + + return new List(); + } + + private IEnumerable GetFixedDisksFreeSpace() + { + return GetDiskSpace(_diskProvider.GetFixedDrives()); + } + + private IEnumerable GetDiskSpace(IEnumerable paths) + { + foreach (var path in paths) + { + DiskSpace diskSpace = null; + + try + { + var freeSpace = _diskProvider.GetAvailableSpace(path).Value; + var totalSpace = _diskProvider.GetTotalSize(path).Value; + + diskSpace = new DiskSpace + { + Path = path, + FreeSpace = freeSpace, + TotalSpace = totalSpace + }; + + diskSpace.Label = _diskProvider.GetVolumeLabel(path); + } + catch (Exception ex) + { + _logger.WarnException("Unable to get free space for: " + path, ex); + } + + if (diskSpace != null) + { + yield return diskSpace; + } + } + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs index e7a53fde9..f3d692d32 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetClient.cs @@ -4,6 +4,7 @@ using NLog; using NzbDrone.Common; using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Download.Clients.Nzbget @@ -12,12 +13,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget { private readonly IConfigService _configService; private readonly IHttpProvider _httpProvider; + private readonly IParsingService _parsingService; private readonly Logger _logger; - public NzbgetClient(IConfigService configService, IHttpProvider httpProvider, Logger logger) + public NzbgetClient(IConfigService configService, IHttpProvider httpProvider, IParsingService parsingService, Logger logger) { _configService = configService; _httpProvider = httpProvider; + _parsingService = parsingService; _logger = logger; } @@ -75,6 +78,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget queueItem.Size = nzbGetQueueItem.FileSizeMb; queueItem.Sizeleft = nzbGetQueueItem.RemainingSizeMb; + var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title); + if (parsedEpisodeInfo == null) continue; + + var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0); + if (remoteEpisode.Series == null) continue; + + queueItem.RemoteEpisode = remoteEpisode; + yield return queueItem; } } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs index 3e36e941d..4e1fabcfc 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdClient.cs @@ -7,6 +7,7 @@ using NzbDrone.Common; using NzbDrone.Common.Cache; using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using RestSharp; @@ -53,13 +54,19 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { private readonly IConfigService _configService; private readonly IHttpProvider _httpProvider; + private readonly IParsingService _parsingService; private readonly ICached> _queueCache; private readonly Logger _logger; - public SabnzbdClient(IConfigService configService, IHttpProvider httpProvider, ICacheManger cacheManger, Logger logger) + public SabnzbdClient(IConfigService configService, + IHttpProvider httpProvider, + ICacheManger cacheManger, + IParsingService parsingService, + Logger logger) { _configService = configService; _httpProvider = httpProvider; + _parsingService = parsingService; _queueCache = cacheManger.GetCache>(GetType(), "queue"); _logger = logger; } @@ -121,6 +128,14 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd queueItem.Timeleft = sabQueueItem.Timeleft; queueItem.Status = sabQueueItem.Status; + var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title); + if (parsedEpisodeInfo == null) continue; + + var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0); + if (remoteEpisode.Series == null) continue; + + queueItem.RemoteEpisode = remoteEpisode; + queueItems.Add(queueItem); } diff --git a/src/NzbDrone.Core/Download/IDownloadClient.cs b/src/NzbDrone.Core/Download/IDownloadClient.cs index aca724d1b..ce32b62b2 100644 --- a/src/NzbDrone.Core/Download/IDownloadClient.cs +++ b/src/NzbDrone.Core/Download/IDownloadClient.cs @@ -9,5 +9,4 @@ namespace NzbDrone.Core.Download bool IsConfigured { get; } IEnumerable GetQueue(); } - } diff --git a/src/NzbDrone.Core/Download/QueueItem.cs b/src/NzbDrone.Core/Download/QueueItem.cs index c63d98c1a..9112680b9 100644 --- a/src/NzbDrone.Core/Download/QueueItem.cs +++ b/src/NzbDrone.Core/Download/QueueItem.cs @@ -1,4 +1,5 @@ using System; +using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Download { @@ -10,5 +11,6 @@ namespace NzbDrone.Core.Download public decimal Sizeleft { get; set; } public TimeSpan Timeleft { get; set; } public String Status { get; set; } + public RemoteEpisode RemoteEpisode { get; set; } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFiles.cs new file mode 100644 index 000000000..c11da71f1 --- /dev/null +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFiles.cs @@ -0,0 +1,31 @@ +using NLog; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.Housekeeping.Housekeepers +{ + public class CleanupOrphanedEpisodeFiles : IHousekeepingTask + { + private readonly IDatabase _database; + private readonly Logger _logger; + + public CleanupOrphanedEpisodeFiles(IDatabase database, Logger logger) + { + _database = database; + _logger = logger; + } + + public void Clean() + { + _logger.Trace("Running orphaned episode files cleanup"); + + var mapper = _database.GetDataMapper(); + + mapper.ExecuteNonQuery(@"DELETE FROM EpisodeFiles + WHERE Id IN ( + SELECT EpisodeFiles.Id FROM EpisodeFiles + LEFT OUTER JOIN Episodes + ON EpisodeFiles.Id = Episodes.EpisodeFileId + WHERE Episodes.Id IS NULL)"); + } + } +} diff --git a/src/NzbDrone.Core/Housekeeping/HousekeepingService.cs b/src/NzbDrone.Core/Housekeeping/HousekeepingService.cs index fd030ffea..26725a2c9 100644 --- a/src/NzbDrone.Core/Housekeeping/HousekeepingService.cs +++ b/src/NzbDrone.Core/Housekeeping/HousekeepingService.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; using NLog; +using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Housekeeping { - public class HousekeepingService : IExecute + public class HousekeepingService : IExecute, IHandleAsync { private readonly IEnumerable _housekeepers; private readonly Logger _logger; @@ -16,7 +18,7 @@ namespace NzbDrone.Core.Housekeeping _logger = logger; } - public void Execute(HousekeepingCommand message) + private void Clean() { _logger.Info("Running housecleaning tasks"); @@ -32,5 +34,15 @@ namespace NzbDrone.Core.Housekeeping } } } + + public void Execute(HousekeepingCommand message) + { + Clean(); + } + + public void HandleAsync(ApplicationStartedEvent message) + { + Clean(); + } } } diff --git a/src/NzbDrone.Core/Indexers/FetchAndParseRssService.cs b/src/NzbDrone.Core/Indexers/FetchAndParseRssService.cs index 534668b6b..899714e67 100644 --- a/src/NzbDrone.Core/Indexers/FetchAndParseRssService.cs +++ b/src/NzbDrone.Core/Indexers/FetchAndParseRssService.cs @@ -39,7 +39,6 @@ namespace NzbDrone.Core.Indexers _logger.Debug("Available indexers {0}", indexers.Count); - var taskList = new List(); var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None); diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index 41942d35b..7d42847cd 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Indexers } } - public ProviderDefinition Definition { get; set; } + public virtual ProviderDefinition Definition { get; set; } public abstract DownloadProtocol Protocol { get; } @@ -49,7 +49,6 @@ namespace NzbDrone.Core.Indexers public abstract IEnumerable GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date); public abstract IEnumerable GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset); - public override string ToString() { return GetType().Name; diff --git a/src/NzbDrone.Core/Indexers/IndexerDefinition.cs b/src/NzbDrone.Core/Indexers/IndexerDefinition.cs index 5909532b4..4a061129e 100644 --- a/src/NzbDrone.Core/Indexers/IndexerDefinition.cs +++ b/src/NzbDrone.Core/Indexers/IndexerDefinition.cs @@ -4,5 +4,6 @@ namespace NzbDrone.Core.Indexers { public class IndexerDefinition : ProviderDefinition { + public bool Enable { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/IndexerFactory.cs b/src/NzbDrone.Core/Indexers/IndexerFactory.cs index b89f0d539..6ec09f104 100644 --- a/src/NzbDrone.Core/Indexers/IndexerFactory.cs +++ b/src/NzbDrone.Core/Indexers/IndexerFactory.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using NLog; +using NzbDrone.Common.Composition; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers @@ -14,12 +16,14 @@ namespace NzbDrone.Core.Indexers { private readonly IIndexerRepository _providerRepository; private readonly IEnumerable _providers; + private readonly INewznabTestService _newznabTestService; - public IndexerFactory(IIndexerRepository providerRepository, IEnumerable providers, Logger logger) - : base(providerRepository, providers, logger) + public IndexerFactory(IIndexerRepository providerRepository, IEnumerable providers, IContainer container, INewznabTestService newznabTestService, Logger logger) + : base(providerRepository, providers, container, logger) { _providerRepository = providerRepository; _providers = providers; + _newznabTestService = newznabTestService; } protected override void InitializeProviders() @@ -31,11 +35,26 @@ namespace NzbDrone.Core.Indexers var newProviders = definitions.Where(def => currentProviders.All(c => c.Implementation != def.Implementation)).ToList(); - if (newProviders.Any()) { _providerRepository.InsertMany(newProviders.Cast().ToList()); } } + + protected override List Active() + { + return base.Active().Where(c => c.Enable).ToList(); + } + + public override IndexerDefinition Create(IndexerDefinition definition) + { + if (definition.Implementation == typeof(Newznab.Newznab).Name) + { + var indexer = GetInstance(definition); + _newznabTestService.Test(indexer); + } + + return base.Create(definition); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index a4c106846..a9cf5eca2 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -52,6 +52,8 @@ namespace NzbDrone.Core.Indexers.Newznab } } + public override ProviderDefinition Definition { get; set; } + private NewznabSettings GetSettings(string url, List categories) { var settings = new NewznabSettings { Url = url }; diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs index 5f05ba96d..c66dcf56e 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs @@ -16,7 +16,6 @@ namespace NzbDrone.Core.Indexers.Newznab } } - public class NewznabSettings : IProviderConfig { private static readonly NewznabSettingsValidator Validator = new NewznabSettingsValidator(); diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 6b56d4d00..86d10675e 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -94,6 +94,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { return decisions.Where(c => c.Approved) .OrderByDescending(c => c.LocalEpisode.Quality) + .ThenByDescending(c => c.LocalEpisode.Size) .ToList(); } } diff --git a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs index c176ccff5..a283a32c2 100644 --- a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -119,7 +119,6 @@ namespace NzbDrone.Core.MetadataSource episode.SeasonNumber = traktEpisode.season; episode.EpisodeNumber = traktEpisode.episode; episode.EpisodeNumber = traktEpisode.number; - episode.TvDbEpisodeId = traktEpisode.tvdb_id; episode.Title = traktEpisode.title; episode.AirDate = FromIsoToString(traktEpisode.first_aired_iso); episode.AirDateUtc = FromIso(traktEpisode.first_aired_iso); diff --git a/src/NzbDrone.Core/Notifications/Email/Email.cs b/src/NzbDrone.Core/Notifications/Email/Email.cs index 87ddabc59..30a4e2d23 100644 --- a/src/NzbDrone.Core/Notifications/Email/Email.cs +++ b/src/NzbDrone.Core/Notifications/Email/Email.cs @@ -12,16 +12,6 @@ namespace NzbDrone.Core.Notifications.Email _smtpProvider = smtpProvider; } - public override string Name - { - get { return "Email"; } - } - - public override string ImplementationName - { - get { return "Email"; } - } - public override string Link { get { return null; } diff --git a/src/NzbDrone.Core/Notifications/Email/EmailSettings.cs b/src/NzbDrone.Core/Notifications/Email/EmailSettings.cs index 42aa725cd..3fd19a59f 100644 --- a/src/NzbDrone.Core/Notifications/Email/EmailSettings.cs +++ b/src/NzbDrone.Core/Notifications/Email/EmailSettings.cs @@ -1,12 +1,26 @@ using System; +using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.Email { + public class EmailSettingsValidator : AbstractValidator + { + public EmailSettingsValidator() + { + RuleFor(c => c.Server).NotEmpty(); + RuleFor(c => c.Port).GreaterThan(0); + RuleFor(c => c.From).NotEmpty(); + RuleFor(c => c.To).NotEmpty(); + } + } + public class EmailSettings : IProviderConfig { + private static readonly EmailSettingsValidator Validator = new EmailSettingsValidator(); + public EmailSettings() { Port = 25; @@ -43,7 +57,7 @@ namespace NzbDrone.Core.Notifications.Email public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/Notifications/Growl/Growl.cs b/src/NzbDrone.Core/Notifications/Growl/Growl.cs index 23c9620d8..ac8bef299 100644 --- a/src/NzbDrone.Core/Notifications/Growl/Growl.cs +++ b/src/NzbDrone.Core/Notifications/Growl/Growl.cs @@ -11,16 +11,6 @@ namespace NzbDrone.Core.Notifications.Growl _growlProvider = growlProvider; } - public override string Name - { - get { return "Growl"; } - } - - public override string ImplementationName - { - get { return "Growl"; } - } - public override string Link { get { return "http://growl.info/"; } diff --git a/src/NzbDrone.Core/Notifications/Growl/GrowlSettings.cs b/src/NzbDrone.Core/Notifications/Growl/GrowlSettings.cs index dd049268d..d6444e6cd 100644 --- a/src/NzbDrone.Core/Notifications/Growl/GrowlSettings.cs +++ b/src/NzbDrone.Core/Notifications/Growl/GrowlSettings.cs @@ -1,12 +1,24 @@ using System; +using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.Growl { + public class GrowlSettingsValidator : AbstractValidator + { + public GrowlSettingsValidator() + { + RuleFor(c => c.Host).NotEmpty(); + RuleFor(c => c.Port).GreaterThan(0); + } + } + public class GrowlSettings : IProviderConfig { + private static readonly GrowlSettingsValidator Validator = new GrowlSettingsValidator(); + public GrowlSettings() { Port = 23053; @@ -31,7 +43,7 @@ namespace NzbDrone.Core.Notifications.Growl public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/Notifications/INotification.cs b/src/NzbDrone.Core/Notifications/INotification.cs index 86f1bcc03..5b5fd5a13 100644 --- a/src/NzbDrone.Core/Notifications/INotification.cs +++ b/src/NzbDrone.Core/Notifications/INotification.cs @@ -1,15 +1,12 @@ -using NzbDrone.Core.Tv; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications { - public interface INotification + public interface INotification : IProvider { - string Name { get; } - string ImplementationName { get; } string Link { get; } - NotificationDefinition InstanceDefinition { get; set; } - void OnGrab(string message); void OnDownload(string message, Series series); void AfterRename(Series series); diff --git a/src/NzbDrone.Core/Notifications/NotificationBase.cs b/src/NzbDrone.Core/Notifications/NotificationBase.cs index d121bd44b..22e992ded 100644 --- a/src/NzbDrone.Core/Notifications/NotificationBase.cs +++ b/src/NzbDrone.Core/Notifications/NotificationBase.cs @@ -1,28 +1,47 @@ -using NzbDrone.Common.Serializer; +using System; +using System.Collections.Generic; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications { - public abstract class NotificationBase : INotification where TSetting : class, IProviderConfig, new() + public abstract class NotificationBase : INotification where TSettings : IProviderConfig, new() { - public abstract string Name { get; } - public abstract string ImplementationName { get; } - public abstract string Link { get; } + public Type ConfigContract + { + get + { + return typeof(TSettings); + } + } - public NotificationDefinition InstanceDefinition { get; set; } + public IEnumerable DefaultDefinitions + { + get + { + return new List(); + } + } + + public ProviderDefinition Definition { get; set; } + + public abstract string Link { get; } public abstract void OnGrab(string message); public abstract void OnDownload(string message, Series series); public abstract void AfterRename(Series series); - public TSetting Settings { get; private set; } - - public TSetting ImportSettingsFromJson(string json) + protected TSettings Settings { - Settings = Json.Deserialize(json) ?? new TSetting(); + get + { + return (TSettings)Definition.Settings; + } + } - return Settings; + public override string ToString() + { + return GetType().Name; } } } diff --git a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs index 41fc64509..a48d2b28d 100644 --- a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs +++ b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs @@ -1,20 +1,9 @@ using System; -using NzbDrone.Core.Datastore; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications { - public class NotificationDefinition : ModelBase - { - public String Name { get; set; } - public Boolean OnGrab { get; set; } - public Boolean OnDownload { get; set; } - public String Settings { get; set; } - public String Implementation { get; set; } - } - - - public class NotificationProviderModel : ProviderDefinition + public class NotificationDefinition : ProviderDefinition { public Boolean OnGrab { get; set; } public Boolean OnDownload { get; set; } diff --git a/src/NzbDrone.Core/Notifications/NotificationFactory.cs b/src/NzbDrone.Core/Notifications/NotificationFactory.cs new file mode 100644 index 000000000..57163b9e0 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/NotificationFactory.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common.Composition; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Notifications +{ + public interface INotificationFactory : IProviderFactory + { + List OnGrabEnabled(); + List OnDownloadEnabled(); + } + + public class NotificationFactory : ProviderFactory, INotificationFactory + { + private IEnumerable _providers; + + public NotificationFactory(INotificationRepository providerRepository, IEnumerable providers, IContainer container, Logger logger) + : base(providerRepository, providers, container, logger) + { + _providers = providers; + } + + public List OnGrabEnabled() + { + return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnGrab).ToList(); + } + + public List OnDownloadEnabled() + { + return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnDownload).ToList(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Notifications/NotificationRepository.cs b/src/NzbDrone.Core/Notifications/NotificationRepository.cs index ab6d10d09..88060bdb2 100644 --- a/src/NzbDrone.Core/Notifications/NotificationRepository.cs +++ b/src/NzbDrone.Core/Notifications/NotificationRepository.cs @@ -1,32 +1,20 @@ -using System; -using System.Linq; -using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications { - public interface INotificationRepository : IBasicRepository + public interface INotificationRepository : IProviderRepository { - NotificationDefinition Get(string name); - NotificationDefinition Find(string name); + } - public class NotificationRepository : BasicRepository, INotificationRepository + public class NotificationRepository : ProviderRepository, INotificationRepository { public NotificationRepository(IDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { } - - public NotificationDefinition Get(string name) - { - return Query.Single(i => i.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); - } - - public NotificationDefinition Find(string name) - { - return Query.SingleOrDefault(i => i.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); - } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs index 458c80d65..f0b24a264 100644 --- a/src/NzbDrone.Core/Notifications/NotificationService.cs +++ b/src/NzbDrone.Core/Notifications/NotificationService.cs @@ -13,126 +13,20 @@ using Omu.ValueInjecter; namespace NzbDrone.Core.Notifications { - public interface INotificationService - { - List All(); - Notification Get(int id); - List Schema(); - Notification Create(Notification notification); - void Update(Notification notification); - void Delete(int id); - } - public class NotificationService - : INotificationService, - IHandle, + : IHandle, IHandle, IHandle { - private readonly INotificationRepository _notificationRepository; - private readonly IContainer _container; - private readonly List _notifications; + private readonly INotificationFactory _notificationFactory; private readonly Logger _logger; - public NotificationService(INotificationRepository notificationRepository, - IEnumerable notifications, - IContainer container, - Logger logger) + public NotificationService(INotificationFactory notificationFactory, Logger logger) { - _notificationRepository = notificationRepository; - _container = container; - _notifications = notifications.ToList(); + _notificationFactory = notificationFactory; _logger = logger; } - public List All() - { - return _notificationRepository.All().Select(ToNotification).ToList(); - } - - public Notification Get(int id) - { - return ToNotification(_notificationRepository.Get(id)); - } - - public List Schema() - { - var notifications = new List(); - - int i = 1; - foreach (var notification in _notifications) - { - var type = notification.GetType(); - - var newNotification = new Notification(); - newNotification.Instance = (INotification)_container.Resolve(type); - newNotification.Id = i; - newNotification.ImplementationName = notification.ImplementationName; - newNotification.Link = notification.Link; - - var instanceType = newNotification.Instance.GetType(); - var baseGenArgs = instanceType.BaseType.GetGenericArguments(); - newNotification.Settings = (IProviderConfig)Activator.CreateInstance(baseGenArgs[0]); - newNotification.Implementation = type.Name; - - notifications.Add(newNotification); - i++; - } - - return notifications.OrderBy(n => n.Name).ToList(); - } - - public Notification Create(Notification notification) - { - var definition = new NotificationDefinition(); - definition.InjectFrom(notification); - definition.Settings = notification.Settings.ToJson(); - - definition = _notificationRepository.Insert(definition); - notification.Id = definition.Id; - - return notification; - } - - public void Update(Notification notification) - { - var definition = _notificationRepository.Get(notification.Id); - definition.InjectFrom(notification); - definition.Settings = notification.Settings.ToJson(); - - _notificationRepository.Update(definition); - } - - public void Delete(int id) - { - _notificationRepository.Delete(id); - } - - private Notification ToNotification(NotificationDefinition definition) - { - var notification = new Notification(); - notification.Id = definition.Id; - notification.OnGrab = definition.OnGrab; - notification.OnDownload = definition.OnDownload; - notification.Instance = GetInstance(definition); - notification.Name = definition.Name; - notification.Implementation = definition.Implementation; - notification.ImplementationName = notification.Instance.ImplementationName; - notification.Settings = ((dynamic)notification.Instance).ImportSettingsFromJson(definition.Settings); - - return notification; - } - - private INotification GetInstance(NotificationDefinition indexerDefinition) - { - var type = _notifications.Single(c => c.GetType().Name.Equals(indexerDefinition.Implementation, StringComparison.InvariantCultureIgnoreCase)).GetType(); - - var instance = (INotification)_container.Resolve(type); - - instance.InstanceDefinition = indexerDefinition; - return instance; - } - private string GetMessage(Series series, List episodes, QualityModel quality) { if (series.SeriesType == SeriesTypes.Daily) @@ -163,16 +57,16 @@ namespace NzbDrone.Core.Notifications { var messageBody = GetMessage(message.Episode.Series, message.Episode.Episodes, message.Episode.ParsedEpisodeInfo.Quality); - foreach (var notification in All().Where(n => n.OnGrab)) + foreach (var notification in _notificationFactory.OnGrabEnabled()) { try { - notification.Instance.OnGrab(messageBody); + notification.OnGrab(messageBody); } catch (Exception ex) { - _logger.ErrorException("Unable to send OnGrab notification to: " + notification.Name, ex); + _logger.ErrorException("Unable to send OnGrab notification to: " + notification.Definition.Name, ex); } } } @@ -181,32 +75,32 @@ namespace NzbDrone.Core.Notifications { var messageBody = GetMessage(message.Episode.Series, message.Episode.Episodes, message.Episode.ParsedEpisodeInfo.Quality); - foreach (var notification in All().Where(n => n.OnDownload)) + foreach (var notification in _notificationFactory.OnDownloadEnabled()) { try { - notification.Instance.OnDownload(messageBody, message.Episode.Series); + notification.OnDownload(messageBody, message.Episode.Series); } catch (Exception ex) { - _logger.WarnException("Unable to send OnDownload notification to: " + notification.Name, ex); + _logger.WarnException("Unable to send OnDownload notification to: " + notification.Definition.Name, ex); } } } public void Handle(SeriesRenamedEvent message) { - foreach (var notification in All().Where(n => n.OnDownload)) + foreach (var notification in _notificationFactory.OnDownloadEnabled()) { try { - notification.Instance.AfterRename(message.Series); + notification.AfterRename(message.Series); } catch (Exception ex) { - _logger.WarnException("Unable to send AfterRename notification to: " + notification.Name, ex); + _logger.WarnException("Unable to send AfterRename notification to: " + notification.Definition.Name, ex); } } } diff --git a/src/NzbDrone.Core/Notifications/NotificationSettingsProvider.cs b/src/NzbDrone.Core/Notifications/NotificationSettingsProvider.cs deleted file mode 100644 index b26afa1fd..000000000 --- a/src/NzbDrone.Core/Notifications/NotificationSettingsProvider.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NzbDrone.Common.Serializer; -using NzbDrone.Core.ThingiProvider; - -namespace NzbDrone.Core.Notifications -{ - public interface INotificationSettingsProvider - { - TSetting Get(INotification indexer) where TSetting : IProviderConfig, new(); - } - - public class NotificationSettingsProvider : INotificationSettingsProvider - { - private readonly INotificationRepository _notificationRepository; - - public NotificationSettingsProvider(INotificationRepository notificationRepository) - { - _notificationRepository = notificationRepository; - } - - public TSetting Get(INotification indexer) where TSetting : IProviderConfig, new() - { - var indexerDef = _notificationRepository.Find(indexer.Name); - - if (indexerDef == null || string.IsNullOrWhiteSpace(indexerDef.Settings)) - { - return new TSetting(); - } - - return Json.Deserialize(indexerDef.Settings); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs index 22f4656f7..d1a10a8a6 100644 --- a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs +++ b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs @@ -11,16 +11,6 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid _notifyMyAndroidProxy = notifyMyAndroidProxy; } - public override string Name - { - get { return "NotifyMyAndroid"; } - } - - public override string ImplementationName - { - get { return "NotifyMyAndroid"; } - } - public override string Link { get { return "http://www.notifymyandroid.com/"; } diff --git a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs index 48c8c6fc6..1a05d92b3 100644 --- a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs +++ b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs @@ -1,12 +1,23 @@ using System; +using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.NotifyMyAndroid { + public class NotifyMyAndroidSettingsValidator : AbstractValidator + { + public NotifyMyAndroidSettingsValidator() + { + RuleFor(c => c.ApiKey).NotEmpty(); + } + } + public class NotifyMyAndroidSettings : IProviderConfig { + private static readonly NotifyMyAndroidSettingsValidator Validator = new NotifyMyAndroidSettingsValidator(); + [FieldDefinition(0, Label = "API Key", HelpLink = "http://www.notifymyandroid.com/")] public String ApiKey { get; set; } @@ -23,7 +34,7 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs b/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs index b5d2a8d17..72023baa0 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs @@ -11,16 +11,6 @@ namespace NzbDrone.Core.Notifications.Plex _plexProvider = plexProvider; } - public override string Name - { - get { return "Plex Client"; } - } - - public override string ImplementationName - { - get { return "Plex Client"; } - } - public override string Link { get { return "http://www.plexapp.com/"; } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexClientSettings.cs b/src/NzbDrone.Core/Notifications/Plex/PlexClientSettings.cs index a0d762406..f5672c733 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexClientSettings.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexClientSettings.cs @@ -1,12 +1,24 @@ using System; +using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.Plex { + public class PlexClientSettingsValidator : AbstractValidator + { + public PlexClientSettingsValidator() + { + RuleFor(c => c.Host).NotEmpty(); + RuleFor(c => c.Port).GreaterThan(0); + } + } + public class PlexClientSettings : IProviderConfig { + private static readonly PlexClientSettingsValidator Validator = new PlexClientSettingsValidator(); + public PlexClientSettings() { Port = 3000; @@ -34,7 +46,7 @@ namespace NzbDrone.Core.Notifications.Plex public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs b/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs index 04d5df084..57d359880 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs @@ -11,16 +11,6 @@ namespace NzbDrone.Core.Notifications.Plex _plexProvider = plexProvider; } - public override string Name - { - get { return "Plex Server"; } - } - - public override string ImplementationName - { - get { return "Plex Server"; } - } - public override string Link { get { return "http://www.plexapp.com/"; } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs b/src/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs index ed410767b..020f7f65f 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs @@ -1,12 +1,24 @@ using System; +using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.Plex { + public class PlexServerSettingsValidator : AbstractValidator + { + public PlexServerSettingsValidator() + { + RuleFor(c => c.Host).NotEmpty(); + RuleFor(c => c.Port).GreaterThan(0); + } + } + public class PlexServerSettings : IProviderConfig { + private static readonly PlexServerSettingsValidator Validator = new PlexServerSettingsValidator(); + public PlexServerSettings() { Port = 32400; @@ -31,7 +43,7 @@ namespace NzbDrone.Core.Notifications.Plex public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs index 0164b222a..f8cd93e9b 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs @@ -12,16 +12,6 @@ namespace NzbDrone.Core.Notifications.Prowl _prowlProvider = prowlProvider; } - public override string Name - { - get { return "Prowl"; } - } - - public override string ImplementationName - { - get { return "Prowl"; } - } - public override string Link { get { return "http://www.prowlapp.com/"; } diff --git a/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs b/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs index 66b574179..3d68d3674 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs @@ -1,12 +1,23 @@ using System; +using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.Prowl { + public class ProwlSettingsValidator : AbstractValidator + { + public ProwlSettingsValidator() + { + RuleFor(c => c.ApiKey).NotEmpty(); + } + } + public class ProwlSettings : IProviderConfig { + private static readonly ProwlSettingsValidator Validator = new ProwlSettingsValidator(); + [FieldDefinition(0, Label = "API Key", HelpLink = "https://www.prowlapp.com/api_settings.php")] public String ApiKey { get; set; } @@ -23,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Prowl public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs index 82c058729..db64a7445 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs @@ -11,16 +11,6 @@ namespace NzbDrone.Core.Notifications.PushBullet _pushBulletProxy = pushBulletProxy; } - public override string Name - { - get { return "PushBullet"; } - } - - public override string ImplementationName - { - get { return "PushBullet"; } - } - public override string Link { get { return "https://www.pushbullet.com/"; } diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBulletSettings.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBulletSettings.cs index 1886991d8..3e1f75ca1 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBulletSettings.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBulletSettings.cs @@ -1,12 +1,24 @@ using System; +using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.PushBullet { + public class PushBulletSettingsValidator : AbstractValidator + { + public PushBulletSettingsValidator() + { + RuleFor(c => c.ApiKey).NotEmpty(); + RuleFor(c => c.DeviceId).GreaterThan(0); + } + } + public class PushBulletSettings : IProviderConfig { + private static readonly PushBulletSettingsValidator Validator = new PushBulletSettingsValidator(); + [FieldDefinition(0, Label = "API Key", HelpLink = "https://www.pushbullet.com/")] public String ApiKey { get; set; } @@ -23,7 +35,7 @@ namespace NzbDrone.Core.Notifications.PushBullet public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs index b4a3e5a56..0a8f1d1c6 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs @@ -11,16 +11,6 @@ namespace NzbDrone.Core.Notifications.Pushover _pushoverProxy = pushoverProxy; } - public override string Name - { - get { return "Pushover"; } - } - - public override string ImplementationName - { - get { return "Pushover"; } - } - public override string Link { get { return "https://pushover.net/"; } diff --git a/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs b/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs index ca4c332c7..debf640e2 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs @@ -1,12 +1,23 @@ using System; +using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.Pushover { + public class PushoverSettingsValidator : AbstractValidator + { + public PushoverSettingsValidator() + { + RuleFor(c => c.UserKey).NotEmpty(); + } + } + public class PushoverSettings : IProviderConfig { + private static readonly PushoverSettingsValidator Validator = new PushoverSettingsValidator(); + [FieldDefinition(0, Label = "User Key", HelpLink = "https://pushover.net/")] public String UserKey { get; set; } @@ -23,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Pushover public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs index f42228ee6..f6b3a6abe 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs @@ -11,16 +11,6 @@ namespace NzbDrone.Core.Notifications.Xbmc _xbmcProvider = xbmcProvider; } - public override string Name - { - get { return "XBMC"; } - } - - public override string ImplementationName - { - get { return "XBMC"; } - } - public override string Link { get { return "http://xbmc.org/"; } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs index 2c0569c89..055e02af8 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using FluentValidation; using FluentValidation.Results; using Newtonsoft.Json; using NzbDrone.Core.Annotations; @@ -7,8 +8,19 @@ using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications.Xbmc { + public class XbmcSettingsValidator : AbstractValidator + { + public XbmcSettingsValidator() + { + RuleFor(c => c.Host).NotEmpty(); + RuleFor(c => c.DisplayTime).GreaterThan(0); + } + } + public class XbmcSettings : IProviderConfig { + private static readonly XbmcSettingsValidator Validator = new XbmcSettingsValidator(); + public XbmcSettings() { DisplayTime = 5; @@ -56,7 +68,7 @@ namespace NzbDrone.Core.Notifications.Xbmc public ValidationResult Validate() { - throw new NotImplementedException(); + return Validator.Validate(this); } } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 7afcb1bb1..5c25f088e 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -173,8 +173,14 @@ - + + + + + + Code + @@ -213,6 +219,8 @@ + + @@ -227,6 +235,7 @@ + @@ -267,6 +276,8 @@ + + @@ -326,10 +337,8 @@ - - diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs index e3e78c7ac..1bc417fef 100644 --- a/src/NzbDrone.Core/Queue/QueueService.cs +++ b/src/NzbDrone.Core/Queue/QueueService.cs @@ -15,13 +15,11 @@ namespace NzbDrone.Core.Queue public class QueueService : IQueueService { private readonly IProvideDownloadClient _downloadClientProvider; - private readonly IParsingService _parsingService; private readonly Logger _logger; - public QueueService(IProvideDownloadClient downloadClientProvider, IParsingService parsingService, Logger logger) + public QueueService(IProvideDownloadClient downloadClientProvider, Logger logger) { _downloadClientProvider = downloadClientProvider; - _parsingService = parsingService; _logger = logger; } @@ -39,31 +37,19 @@ namespace NzbDrone.Core.Queue foreach (var queueItem in queueItems) { - var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title); - - if (parsedEpisodeInfo != null && !string.IsNullOrWhiteSpace(parsedEpisodeInfo.SeriesTitle)) + foreach (var episode in queueItem.RemoteEpisode.Episodes) { - var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0); - - if (remoteEpisode.Series == null) - { - continue; - } - - foreach (var episode in remoteEpisode.Episodes) - { - var queue = new Queue(); - queue.Id = queueItem.Id.GetHashCode(); - queue.Series = remoteEpisode.Series; - queue.Episode = episode; - queue.Quality = remoteEpisode.ParsedEpisodeInfo.Quality; - queue.Title = queueItem.Title; - queue.Size = queueItem.Size; - queue.Sizeleft = queueItem.Sizeleft; - queue.Timeleft = queueItem.Timeleft; - queue.Status = queueItem.Status; - queued.Add(queue); - } + var queue = new Queue(); + queue.Id = queueItem.Id.GetHashCode(); + queue.Series = queueItem.RemoteEpisode.Series; + queue.Episode = episode; + queue.Quality = queueItem.RemoteEpisode.ParsedEpisodeInfo.Quality; + queue.Title = queueItem.Title; + queue.Size = queueItem.Size; + queue.Sizeleft = queueItem.Sizeleft; + queue.Timeleft = queueItem.Timeleft; + queue.Status = queueItem.Status; + queued.Add(queue); } } diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs b/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs index ef21d3206..c9b20fe47 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs @@ -7,7 +7,6 @@ namespace NzbDrone.Core.ThingiProvider private IProviderConfig _settings; public string Name { get; set; } public string Implementation { get; set; } - public bool Enable { get; set; } public string ConfigContract { get; set; } diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs index bbd909bb3..edc273a52 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NLog; +using NzbDrone.Common.Composition; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; @@ -12,13 +13,18 @@ namespace NzbDrone.Core.ThingiProvider where TProvider : IProvider { private readonly IProviderRepository _providerRepository; + private readonly IContainer _container; private readonly Logger _logger; private readonly List _providers; - protected ProviderFactory(IProviderRepository providerRepository, IEnumerable providers, Logger logger) + protected ProviderFactory(IProviderRepository providerRepository, + IEnumerable providers, + IContainer container, + Logger logger) { _providerRepository = providerRepository; + _container = container; _providers = providers.ToList(); _logger = logger; } @@ -40,8 +46,7 @@ namespace NzbDrone.Core.ThingiProvider public List GetAvailableProviders() { - return All().Where(c => c.Enable && c.Settings.Validate().IsValid) - .Select(GetInstance).ToList(); + return Active().Select(GetInstance).ToList(); } public TProviderDefinition Get(int id) @@ -49,12 +54,12 @@ namespace NzbDrone.Core.ThingiProvider return _providerRepository.Get(id); } - public TProviderDefinition Create(TProviderDefinition provider) + public virtual TProviderDefinition Create(TProviderDefinition definition) { - return _providerRepository.Insert(provider); + return _providerRepository.Insert(definition); } - public void Update(TProviderDefinition definition) + public virtual void Update(TProviderDefinition definition) { _providerRepository.Update(definition); } @@ -64,10 +69,10 @@ namespace NzbDrone.Core.ThingiProvider _providerRepository.Delete(id); } - private TProvider GetInstance(TProviderDefinition definition) + protected TProvider GetInstance(TProviderDefinition definition) { var type = GetImplementation(definition); - var instance = (TProvider)Activator.CreateInstance(type); + var instance = (TProvider)_container.Resolve(type); instance.Definition = definition; return instance; } @@ -90,6 +95,11 @@ namespace NzbDrone.Core.ThingiProvider { } + protected virtual List Active() + { + return All().Where(c => c.Settings.Validate().IsValid).ToList(); + } + private void RemoveMissingImplementations() { var storedProvider = _providerRepository.All(); diff --git a/src/NzbDrone.Core/Tv/Episode.cs b/src/NzbDrone.Core/Tv/Episode.cs index 51775a754..854dceb9c 100644 --- a/src/NzbDrone.Core/Tv/Episode.cs +++ b/src/NzbDrone.Core/Tv/Episode.cs @@ -11,7 +11,6 @@ namespace NzbDrone.Core.Tv { public const string AIR_DATE_FORMAT = "yyyy-MM-dd"; - public int TvDbEpisodeId { get; set; } public int SeriesId { get; set; } public int EpisodeFileId { get; set; } public int SeasonNumber { get; set; } @@ -39,7 +38,7 @@ namespace NzbDrone.Core.Tv public override string ToString() { - return string.Format("[{0}]{1}", TvDbEpisodeId, Title.NullSafe()); + return string.Format("[{0}]{1}", Id, Title.NullSafe()); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs index 1d1ca0a5b..f8436dc24 100644 --- a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs +++ b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs @@ -58,7 +58,6 @@ namespace NzbDrone.Core.Tv } episodeToUpdate.SeriesId = series.Id; - episodeToUpdate.TvDbEpisodeId = episode.TvDbEpisodeId; episodeToUpdate.EpisodeNumber = episode.EpisodeNumber; episodeToUpdate.SeasonNumber = episode.SeasonNumber; episodeToUpdate.Title = episode.Title; diff --git a/src/UI/.idea/inspectionProfiles/Project_Default.xml b/src/UI/.idea/inspectionProfiles/Project_Default.xml index 195e8ea0b..623a988ad 100644 --- a/src/UI/.idea/inspectionProfiles/Project_Default.xml +++ b/src/UI/.idea/inspectionProfiles/Project_Default.xml @@ -75,7 +75,10 @@ - + +