From 0ee8b75b54eee04d16c47b7417db76ab245c5f6d Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Wed, 11 Jan 2017 15:42:37 -0500 Subject: [PATCH 01/52] initial autoimporter commit --- .../AutoImport.derp/AutoImportBase.cs | 35 +++++++ .../AutoImport.derp/AutoImportDefinition.cs | 18 ++++ .../AutoImport.derp/AutoImportRepository.cs | 20 ++++ .../AutoImport.derp/IAutoImport.cs | 12 +++ .../IMDbWatchList/IMDbWatchList.cs | 43 +++++++++ .../IMDbWatchList/IMDbWatchListProxy.cs | 79 ++++++++++++++++ .../IMDbWatchList/IMDbWatchListSettings.cs | 30 ++++++ .../AutoImporter/AutoImporterBase.cs | 91 +++++++++++++++++++ .../AutoImporter/AutoImporterDefinition.cs | 17 ++++ .../AutoImporter/IAutoImporter.cs | 15 +++ src/NzbDrone.Core/NzbDrone.Core.csproj | 10 ++ 11 files changed, 370 insertions(+) create mode 100644 src/NzbDrone.Core/AutoImport.derp/AutoImportBase.cs create mode 100644 src/NzbDrone.Core/AutoImport.derp/AutoImportDefinition.cs create mode 100644 src/NzbDrone.Core/AutoImport.derp/AutoImportRepository.cs create mode 100644 src/NzbDrone.Core/AutoImport.derp/IAutoImport.cs create mode 100644 src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchList.cs create mode 100644 src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListProxy.cs create mode 100644 src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListSettings.cs create mode 100644 src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs create mode 100644 src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs create mode 100644 src/NzbDrone.Core/AutoImporter/IAutoImporter.cs diff --git a/src/NzbDrone.Core/AutoImport.derp/AutoImportBase.cs b/src/NzbDrone.Core/AutoImport.derp/AutoImportBase.cs new file mode 100644 index 000000000..9dc51714c --- /dev/null +++ b/src/NzbDrone.Core/AutoImport.derp/AutoImportBase.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.AutoImport +{ + public abstract class AutoImportBase : IAutoImport where TSettings : IProviderConfig, new() + { + public abstract string Name { get; } + + public Type ConfigContract => typeof(TSettings); + + public virtual ProviderMessage Message => null; + + public IEnumerable DefaultDefinitions => new List(); + + public ProviderDefinition Definition { get; set; } + public abstract ValidationResult Test(); + + public abstract string Link { get; } + + protected TSettings Settings => (TSettings)Definition.Settings; + + public override string ToString() + { + return GetType().Name; + } + + public abstract bool Enabled { get; } + + public virtual object RequestAction(string action, IDictionary query) { return null; } + } +} diff --git a/src/NzbDrone.Core/AutoImport.derp/AutoImportDefinition.cs b/src/NzbDrone.Core/AutoImport.derp/AutoImportDefinition.cs new file mode 100644 index 000000000..f1d53e72e --- /dev/null +++ b/src/NzbDrone.Core/AutoImport.derp/AutoImportDefinition.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.AutoImport +{ + public class AutoImportDefinition : ProviderDefinition + { + public AutoImportDefinition() + { + Tags = new HashSet(); + } + + public bool Enabled { get; set; } + public HashSet Tags { get; set; } + + public override bool Enable => Enabled; + } +} diff --git a/src/NzbDrone.Core/AutoImport.derp/AutoImportRepository.cs b/src/NzbDrone.Core/AutoImport.derp/AutoImportRepository.cs new file mode 100644 index 000000000..398bcaf99 --- /dev/null +++ b/src/NzbDrone.Core/AutoImport.derp/AutoImportRepository.cs @@ -0,0 +1,20 @@ +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.ThingiProvider; + + +namespace NzbDrone.Core.AutoImport +{ + public interface IAutoImportRepository : IProviderRepository + { + + } + + public class AutoImportRepository : ProviderRepository, IAutoImportRepository + { + public AutoImportRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/AutoImport.derp/IAutoImport.cs b/src/NzbDrone.Core/AutoImport.derp/IAutoImport.cs new file mode 100644 index 000000000..5597ddee8 --- /dev/null +++ b/src/NzbDrone.Core/AutoImport.derp/IAutoImport.cs @@ -0,0 +1,12 @@ +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.AutoImport +{ + public interface IAutoImport : IProvider + { + string Link { get; } + bool Enabled { get; } + + // void OnGrab(GrabMessage grabMessage); + } +} diff --git a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchList.cs b/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchList.cs new file mode 100644 index 000000000..d060e1362 --- /dev/null +++ b/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchList.cs @@ -0,0 +1,43 @@ + +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Tv; +using NzbDrone.Core.AutoImport; +using NzbDrone.Core.AutoImport.IMDbWatchList; +using System; + +namespace NzbDrone.Core.AutoImpoter.IMDbWatchList +{ + public class IMDbWatchList : AutoImportBase + { + public override bool Enabled + { + get + { + throw new NotImplementedException(); + } + } + + //private readonly INotifyMyAndroidProxy _proxy; + + //public NotifyMyAndroid(INotifyMyAndroidProxy proxy) + //{ + // _proxy = proxy; + //} + + public override string Link => "http://rss.imdb.com/"; + + public override string Name => "IMDb Public Watchlist"; + + + public override ValidationResult Test() + { + var failures = new List(); + + // failures.AddIfNotNull(_proxy.Test(Settings)); + + return new ValidationResult(failures); + } + } +} diff --git a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListProxy.cs b/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListProxy.cs new file mode 100644 index 000000000..28561ef66 --- /dev/null +++ b/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListProxy.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using System.Net; +using System.Xml.Linq; +using FluentValidation.Results; +using NLog; +using NzbDrone.Core.Exceptions; +using RestSharp; +using NzbDrone.Core.Rest; + +namespace NzbDrone.Core.AutoImport.IMDbWatchList +{ + public interface IIMDbWatchListProxy + { + void ImportMovies(string url); + ValidationFailure Test(IMDbWatchListSettings settings); + } + + public class IMDbWatchListProxy : IIMDbWatchListProxy + { + private readonly Logger _logger; + private const string URL = "http://rss.imdb.com"; + + public IMDbWatchListProxy(Logger logger) + { + _logger = logger; + } + + public void ImportMovies(string id) + { + var client = RestClientFactory.BuildClient(URL); + var request = new RestRequest("/list/{id}", Method.GET); + request.RequestFormat = DataFormat.Xml; + request.AddParameter("id", id, ParameterType.UrlSegment); + + var response = client.ExecuteAndValidate(request); + ValidateResponse(response); + } + + private void Verify(string id) + { + var client = RestClientFactory.BuildClient(URL); + var request = new RestRequest("/list/{id}", Method.GET); + request.RequestFormat = DataFormat.Xml; + request.AddParameter("id", id, ParameterType.UrlSegment); + + var response = client.ExecuteAndValidate(request); + ValidateResponse(response); + } + + private void ValidateResponse(IRestResponse response) + { + var xDoc = XDocument.Parse(response.Content); + var nma = xDoc.Descendants("nma").Single(); + var error = nma.Descendants("error").SingleOrDefault(); + + if (error != null) + { + ((HttpStatusCode)Convert.ToInt32(error.Attribute("code").Value)).VerifyStatusCode(error.Value); + } + } + + public ValidationFailure Test(IMDbWatchListSettings settings) + { + try + { + Verify(settings.IMDbWatchListId); + ImportMovies(settings.IMDbWatchListId); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to import movies: " + ex.Message); + return new ValidationFailure("IMDbWatchListId", "Unable to import movies"); + } + + return null; + } + } +} diff --git a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListSettings.cs b/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListSettings.cs new file mode 100644 index 000000000..4ecd75c72 --- /dev/null +++ b/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListSettings.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.AutoImport.IMDbWatchList +{ + public class IMDbWatchListSettingsValidator : AbstractValidator + { + public IMDbWatchListSettingsValidator() + { + RuleFor(c => c.IMDbWatchListId).NotEmpty(); + } + } + + public class IMDbWatchListSettings : IProviderConfig + { + private static readonly IMDbWatchListSettingsValidator Validator = new IMDbWatchListSettingsValidator(); + + [FieldDefinition(0, Label = "Watch List Id", HelpLink = "http://rss.imdb.com/list/")] + public string IMDbWatchListId { get; set; } + + public bool IsValid => !string.IsNullOrWhiteSpace(IMDbWatchListId); + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} diff --git a/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs b/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs new file mode 100644 index 000000000..23f6c4aba --- /dev/null +++ b/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.AutoImporter +{ + public abstract class AutoImporterBase : IAutoImporter + where TSettings : IProviderConfig, new() + { + // protected readonly IAutoImporterStatusService _autoImporterStatusService; + protected readonly IConfigService _configService; + protected readonly IParsingService _parsingService; + protected readonly Logger _logger; + + public abstract string Name { get; } + // public abstract DownloadProtocol Protocol { get; } + public abstract string Link { get; } + + public abstract bool Enabled { get; } + // public abstract bool SupportsSearch { get; } + + public AutoImporterBase(/*IAutoImporterStatusService autoImporterStatusService, */IConfigService configService, IParsingService parsingService, Logger logger) + { + //_autoImporterStatusService = autoImporterStatusService; + _configService = configService; + _parsingService = parsingService; + _logger = logger; + } + + public Type ConfigContract => typeof(TSettings); + + public virtual ProviderMessage Message => null; + + public virtual IEnumerable DefaultDefinitions + { + get + { + var config = (IProviderConfig)new TSettings(); + + yield return new AutoImporterDefinition + { + Name = GetType().Name, + Link = Link, + Enabled = config.Validate().IsValid && Enabled, + Implementation = GetType().Name, + Settings = config + }; + } + } + + public virtual ProviderDefinition Definition { get; set; } + + public virtual object RequestAction(string action, IDictionary query) { return null; } + + protected TSettings Settings => (TSettings)Definition.Settings; + + public abstract IList Fetch(); + + public ValidationResult Test() + { + var failures = new List(); + + try + { + Test(failures); + } + catch (Exception ex) + { + _logger.Error(ex, "Test aborted due to exception"); + failures.Add(new ValidationFailure(string.Empty, "Test was aborted due to an error: " + ex.Message)); + } + + return new ValidationResult(failures); + } + + protected abstract void Test(List failures); + + public override string ToString() + { + return Definition.Name; + } + } +} diff --git a/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs b/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs new file mode 100644 index 000000000..2446fb817 --- /dev/null +++ b/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs @@ -0,0 +1,17 @@ +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.AutoImporter +{ + public class AutoImporterDefinition : ProviderDefinition + { + public bool Enabled { get; set; } + public string Link { get; set; } + //public DownloadProtocol Protocol { get; set; } + //public bool SupportsRss { get; set; } + //public bool SupportsSearch { get; set; } + + public override bool Enable => Enabled; + + // public IndexerStatus Status { get; set; } + } +} diff --git a/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs b/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs new file mode 100644 index 000000000..246e94f92 --- /dev/null +++ b/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.AutoImporter +{ + public interface IAutoImporter : IProvider + { + string Link { get; } + bool Enabled { get; } + + IList Fetch(); + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 3ea6a0a76..3a6153c86 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -122,6 +122,16 @@ + + + + + + + + + + From d123ca606308183806d6874529ca6be5bf7fa432 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Wed, 11 Jan 2017 16:12:17 -0500 Subject: [PATCH 02/52] updates --- src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs | 6 ------ src/NzbDrone.Core/AutoImporter/IAutoImporter.cs | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs b/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs index 2446fb817..b3fad1178 100644 --- a/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs +++ b/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs @@ -6,12 +6,6 @@ namespace NzbDrone.Core.AutoImporter { public bool Enabled { get; set; } public string Link { get; set; } - //public DownloadProtocol Protocol { get; set; } - //public bool SupportsRss { get; set; } - //public bool SupportsSearch { get; set; } - public override bool Enable => Enabled; - - // public IndexerStatus Status { get; set; } } } diff --git a/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs b/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs index 246e94f92..f78f90141 100644 --- a/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs +++ b/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.AutoImporter { @@ -10,6 +11,6 @@ namespace NzbDrone.Core.AutoImporter string Link { get; } bool Enabled { get; } - IList Fetch(); + IList Fetch(); } } \ No newline at end of file From 4ded288c5d9b9057dab02a87ec59440c6faa2a91 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Thu, 12 Jan 2017 18:33:30 -0500 Subject: [PATCH 03/52] few changes --- src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs b/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs index 23f6c4aba..7d95bdfe8 100644 --- a/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs +++ b/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs @@ -9,27 +9,24 @@ using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.AutoImporter { public abstract class AutoImporterBase : IAutoImporter where TSettings : IProviderConfig, new() { - // protected readonly IAutoImporterStatusService _autoImporterStatusService; protected readonly IConfigService _configService; protected readonly IParsingService _parsingService; protected readonly Logger _logger; public abstract string Name { get; } - // public abstract DownloadProtocol Protocol { get; } public abstract string Link { get; } public abstract bool Enabled { get; } - // public abstract bool SupportsSearch { get; } - public AutoImporterBase(/*IAutoImporterStatusService autoImporterStatusService, */IConfigService configService, IParsingService parsingService, Logger logger) + public AutoImporterBase(IConfigService configService, IParsingService parsingService, Logger logger) { - //_autoImporterStatusService = autoImporterStatusService; _configService = configService; _parsingService = parsingService; _logger = logger; @@ -62,7 +59,7 @@ namespace NzbDrone.Core.AutoImporter protected TSettings Settings => (TSettings)Definition.Settings; - public abstract IList Fetch(); + public abstract IList Fetch(); public ValidationResult Test() { @@ -87,5 +84,6 @@ namespace NzbDrone.Core.AutoImporter { return Definition.Name; } + } } From ec1c81e3ed1ea29ab8b350ac77c3cb8ff952594d Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sun, 15 Jan 2017 15:28:35 -0500 Subject: [PATCH 04/52] updates and compile-able --- .../AutoImport.derp/AutoImportBase.cs | 35 ------------ .../AutoImport.derp/AutoImportDefinition.cs | 18 ------ .../AutoImport.derp/AutoImportRepository.cs | 20 ------- .../AutoImport.derp/IAutoImport.cs | 12 ---- .../IMDbWatchList/IMDbWatchList.cs | 43 -------------- .../Migration/119_create_netimport_table.cs | 18 ++++++ .../Exceptions/NetImportException.cs | 23 ++++++++ .../NetImport/HttpNetImportBase.cs | 56 +++++++++++++++++++ .../NetImport/IMDbWatchList/IMDbWatchList.cs | 35 ++++++++++++ .../IMDbWatchList/IMDbWatchListAPI.cs | 40 +++++++++++++ .../IMDbWatchList/IMDbWatchListParser.cs | 54 ++++++++++++++++++ .../IMDbWatchList/IMDbWatchListProxy.cs | 6 +- .../IMDbWatchListRequestGenerator.cs | 34 +++++++++++ .../IMDbWatchList/IMDbWatchListSettings.cs | 15 +++-- .../INetImport.cs} | 4 +- .../NetImport/INetImportRequestGenerator.cs | 9 +++ .../NetImport/IProcessNetImportResponse.cs | 11 ++++ .../NetImportBase.cs} | 8 +-- .../NetImportDefinition.cs} | 4 +- .../NetImport/NetImportPageableRequest.cs | 25 +++++++++ .../NetImportPageableRequestChain.cs | 48 ++++++++++++++++ .../NetImport/NetImportRepository.cs | 20 +++++++ .../NetImport/NetImportRequest.cs | 21 +++++++ .../NetImport/NetImportResponse.cs | 24 ++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 28 ++++++---- 25 files changed, 457 insertions(+), 154 deletions(-) delete mode 100644 src/NzbDrone.Core/AutoImport.derp/AutoImportBase.cs delete mode 100644 src/NzbDrone.Core/AutoImport.derp/AutoImportDefinition.cs delete mode 100644 src/NzbDrone.Core/AutoImport.derp/AutoImportRepository.cs delete mode 100644 src/NzbDrone.Core/AutoImport.derp/IAutoImport.cs delete mode 100644 src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchList.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs create mode 100644 src/NzbDrone.Core/NetImport/Exceptions/NetImportException.cs create mode 100644 src/NzbDrone.Core/NetImport/HttpNetImportBase.cs create mode 100644 src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs create mode 100644 src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListAPI.cs create mode 100644 src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs rename src/NzbDrone.Core/{AutoImport.derp => NetImport}/IMDbWatchList/IMDbWatchListProxy.cs (93%) create mode 100644 src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListRequestGenerator.cs rename src/NzbDrone.Core/{AutoImport.derp => NetImport}/IMDbWatchList/IMDbWatchListSettings.cs (60%) rename src/NzbDrone.Core/{AutoImporter/IAutoImporter.cs => NetImport/INetImport.cs} (77%) create mode 100644 src/NzbDrone.Core/NetImport/INetImportRequestGenerator.cs create mode 100644 src/NzbDrone.Core/NetImport/IProcessNetImportResponse.cs rename src/NzbDrone.Core/{AutoImporter/AutoImporterBase.cs => NetImport/NetImportBase.cs} (89%) rename src/NzbDrone.Core/{AutoImporter/AutoImporterDefinition.cs => NetImport/NetImportDefinition.cs} (65%) create mode 100644 src/NzbDrone.Core/NetImport/NetImportPageableRequest.cs create mode 100644 src/NzbDrone.Core/NetImport/NetImportPageableRequestChain.cs create mode 100644 src/NzbDrone.Core/NetImport/NetImportRepository.cs create mode 100644 src/NzbDrone.Core/NetImport/NetImportRequest.cs create mode 100644 src/NzbDrone.Core/NetImport/NetImportResponse.cs diff --git a/src/NzbDrone.Core/AutoImport.derp/AutoImportBase.cs b/src/NzbDrone.Core/AutoImport.derp/AutoImportBase.cs deleted file mode 100644 index 9dc51714c..000000000 --- a/src/NzbDrone.Core/AutoImport.derp/AutoImportBase.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using FluentValidation.Results; -using NzbDrone.Core.ThingiProvider; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.AutoImport -{ - public abstract class AutoImportBase : IAutoImport where TSettings : IProviderConfig, new() - { - public abstract string Name { get; } - - public Type ConfigContract => typeof(TSettings); - - public virtual ProviderMessage Message => null; - - public IEnumerable DefaultDefinitions => new List(); - - public ProviderDefinition Definition { get; set; } - public abstract ValidationResult Test(); - - public abstract string Link { get; } - - protected TSettings Settings => (TSettings)Definition.Settings; - - public override string ToString() - { - return GetType().Name; - } - - public abstract bool Enabled { get; } - - public virtual object RequestAction(string action, IDictionary query) { return null; } - } -} diff --git a/src/NzbDrone.Core/AutoImport.derp/AutoImportDefinition.cs b/src/NzbDrone.Core/AutoImport.derp/AutoImportDefinition.cs deleted file mode 100644 index f1d53e72e..000000000 --- a/src/NzbDrone.Core/AutoImport.derp/AutoImportDefinition.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Core.ThingiProvider; - -namespace NzbDrone.Core.AutoImport -{ - public class AutoImportDefinition : ProviderDefinition - { - public AutoImportDefinition() - { - Tags = new HashSet(); - } - - public bool Enabled { get; set; } - public HashSet Tags { get; set; } - - public override bool Enable => Enabled; - } -} diff --git a/src/NzbDrone.Core/AutoImport.derp/AutoImportRepository.cs b/src/NzbDrone.Core/AutoImport.derp/AutoImportRepository.cs deleted file mode 100644 index 398bcaf99..000000000 --- a/src/NzbDrone.Core/AutoImport.derp/AutoImportRepository.cs +++ /dev/null @@ -1,20 +0,0 @@ -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.ThingiProvider; - - -namespace NzbDrone.Core.AutoImport -{ - public interface IAutoImportRepository : IProviderRepository - { - - } - - public class AutoImportRepository : ProviderRepository, IAutoImportRepository - { - public AutoImportRepository(IMainDatabase database, IEventAggregator eventAggregator) - : base(database, eventAggregator) - { - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/AutoImport.derp/IAutoImport.cs b/src/NzbDrone.Core/AutoImport.derp/IAutoImport.cs deleted file mode 100644 index 5597ddee8..000000000 --- a/src/NzbDrone.Core/AutoImport.derp/IAutoImport.cs +++ /dev/null @@ -1,12 +0,0 @@ -using NzbDrone.Core.ThingiProvider; - -namespace NzbDrone.Core.AutoImport -{ - public interface IAutoImport : IProvider - { - string Link { get; } - bool Enabled { get; } - - // void OnGrab(GrabMessage grabMessage); - } -} diff --git a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchList.cs b/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchList.cs deleted file mode 100644 index d060e1362..000000000 --- a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchList.cs +++ /dev/null @@ -1,43 +0,0 @@ - -using System.Collections.Generic; -using FluentValidation.Results; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Tv; -using NzbDrone.Core.AutoImport; -using NzbDrone.Core.AutoImport.IMDbWatchList; -using System; - -namespace NzbDrone.Core.AutoImpoter.IMDbWatchList -{ - public class IMDbWatchList : AutoImportBase - { - public override bool Enabled - { - get - { - throw new NotImplementedException(); - } - } - - //private readonly INotifyMyAndroidProxy _proxy; - - //public NotifyMyAndroid(INotifyMyAndroidProxy proxy) - //{ - // _proxy = proxy; - //} - - public override string Link => "http://rss.imdb.com/"; - - public override string Name => "IMDb Public Watchlist"; - - - public override ValidationResult Test() - { - var failures = new List(); - - // failures.AddIfNotNull(_proxy.Test(Settings)); - - return new ValidationResult(failures); - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs b/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs new file mode 100644 index 000000000..7784ad1c4 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs @@ -0,0 +1,18 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(119)] + public class create_netimport_table : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Create.TableForModel("NetImport") + .WithColumn("Enabled").AsBoolean() + .WithColumn("Name").AsString().Unique() + .WithColumn("Implementation").AsString() + .WithColumn("Settings").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/Exceptions/NetImportException.cs b/src/NzbDrone.Core/NetImport/Exceptions/NetImportException.cs new file mode 100644 index 000000000..d3444d991 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Exceptions/NetImportException.cs @@ -0,0 +1,23 @@ +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.NetImport.Exceptions +{ + public class NetImportException : NzbDroneException + { + private readonly NetImportResponse _netImportResponse; + + public NetImportException(NetImportResponse response, string message, params object[] args) + : base(message, args) + { + _netImportResponse = response; + } + + public NetImportException(NetImportResponse response, string message) + : base(message) + { + _netImportResponse = response; + } + + public NetImportResponse Response => _netImportResponse; + } +} diff --git a/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs b/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs new file mode 100644 index 000000000..9d5582a9c --- /dev/null +++ b/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Http.CloudFlare; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.NetImport +{ + public abstract class HttpNetImportBase : NetImportBase + where TSettings : IProviderConfig, new() + { + protected const int MaxNumResultsPerQuery = 1000; + + protected readonly IHttpClient _httpClient; + + public override bool Enabled => true; + + public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2); + + public abstract INetImportRequestGenerator GetRequestGenerator(); + public abstract IParseNetImportResponse GetParser(); + + public HttpNetImportBase(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(configService, parsingService, logger) + { + _httpClient = httpClient; + } + + public override IList Fetch() + { + return new List(); + } + + protected override void Test(List failures) + { + throw new NotImplementedException(); + } + + protected virtual ValidationFailure TestConnection() + { + throw new NotImplementedException(); + } + } + +} diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs new file mode 100644 index 000000000..d97fb99c2 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.PassThePopcorn; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.NetImport.IMDbWatchList +{ + public class IMDbWatchList : HttpNetImportBase + { + public override string Name => "IMDbWatchList"; + public override string Link => "http://rss.imdb.com/list/"; + public override bool Enabled => true; + + + public IMDbWatchList(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) + { } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new IMDbWatchListRequestGenerator() { Settings = Settings }; + } + + public override IParseNetImportResponse GetParser() + { + return new IMDbWatchListParser(Settings); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListAPI.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListAPI.cs new file mode 100644 index 000000000..73570c897 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListAPI.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace NzbDrone.Core.NetImport.IMDbWatchList +{ + class IMDbWatchListAPI + { + [XmlRoot(ElementName = "item")] + public class Movie + { + [XmlElement(ElementName = "pubDate")] + public string PublishDate { get; set; } + [XmlElement(ElementName = "title")] + public string Title { get; set; } + [XmlElement(ElementName = "link")] + public string Link { get; set; } + [XmlElement(ElementName = "guid")] + public string Guid { get; set; } + [XmlElement(ElementName = "description")] + public string Description { get; set; } + } + + [XmlRoot(ElementName = "channel")] + public class Channel + { + [XmlElement(ElementName = "title")] + public string Title { get; set; } + [XmlElement(ElementName = "link")] + public string Link { get; set; } + [XmlElement(ElementName = "description")] + public string Description { get; set; } + [XmlElement(ElementName = "pubDate")] + public string PublishDate { get; set; } + [XmlElement(ElementName = "lastBuildDate")] + public string LastBuildDate { get; set; } + [XmlElement(ElementName = "item")] + public List Movie { get; set; } + } + } +} diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs new file mode 100644 index 000000000..7fd666141 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs @@ -0,0 +1,54 @@ +using Newtonsoft.Json; +using NzbDrone.Core.NetImport.Exceptions; +using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; + +namespace NzbDrone.Core.NetImport.IMDbWatchList +{ + public class IMDbWatchListParser : IParseNetImportResponse + { + private readonly IMDbWatchListSettings _settings; + + public IMDbWatchListParser(IMDbWatchListSettings settings) + { + _settings = settings; + } + + public IList ParseResponse(NetImportResponse netImportResponse) + { + var torrentInfos = new List(); + + if (netImportResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + { + throw new NetImportException(netImportResponse, + "Unexpected response status {0} code from API request", + netImportResponse.HttpResponse.StatusCode); + } + + var jsonResponse = JsonConvert.DeserializeObject(netImportResponse.Content); + + var responseData = jsonResponse.Movie; + if (responseData == null) + { + throw new NetImportException(netImportResponse, + "This list has no movies"); + } + + foreach (var result in responseData) + { + torrentInfos.Add(new Movie() + { + Title = Parser.Parser.ParseMovieTitle(result.Title, false).MovieTitle, + Year = Parser.Parser.ParseMovieTitle(result.Title, false).Year, + ImdbId = Parser.Parser.ParseImdbId(result.Link).ToString() + }); + } + + return torrentInfos.ToArray(); + } + } +} diff --git a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListProxy.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListProxy.cs similarity index 93% rename from src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListProxy.cs rename to src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListProxy.cs index 28561ef66..664919c52 100644 --- a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListProxy.cs +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListProxy.cs @@ -8,7 +8,7 @@ using NzbDrone.Core.Exceptions; using RestSharp; using NzbDrone.Core.Rest; -namespace NzbDrone.Core.AutoImport.IMDbWatchList +namespace NzbDrone.Core.NetImport.IMDbWatchList { public interface IIMDbWatchListProxy { @@ -64,8 +64,8 @@ namespace NzbDrone.Core.AutoImport.IMDbWatchList { try { - Verify(settings.IMDbWatchListId); - ImportMovies(settings.IMDbWatchListId); + Verify(settings.Link); + ImportMovies(settings.Link); } catch (Exception ex) { diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListRequestGenerator.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListRequestGenerator.cs new file mode 100644 index 000000000..cfd92810b --- /dev/null +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListRequestGenerator.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.NetImport.IMDbWatchList +{ + public class IMDbWatchListRequestGenerator : INetImportRequestGenerator + { + public IMDbWatchListSettings Settings { get; set; } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + pageableRequests.Add(GetMovies(null)); + + return pageableRequests; + } + + public NetImportPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) + { + return new NetImportPageableRequestChain(); + } + + private IEnumerable GetMovies(string searchParameters) + { + var request = new NetImportRequest($"{Settings.Link.Trim()}", HttpAccept.Rss); + yield return request; + } + } +} diff --git a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListSettings.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs similarity index 60% rename from src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListSettings.cs rename to src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs index 4ecd75c72..f63e7206f 100644 --- a/src/NzbDrone.Core/AutoImport.derp/IMDbWatchList/IMDbWatchListSettings.cs +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs @@ -3,13 +3,13 @@ using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; -namespace NzbDrone.Core.AutoImport.IMDbWatchList +namespace NzbDrone.Core.NetImport.IMDbWatchList { public class IMDbWatchListSettingsValidator : AbstractValidator { public IMDbWatchListSettingsValidator() { - RuleFor(c => c.IMDbWatchListId).NotEmpty(); + RuleFor(c => c.Link).NotEmpty(); } } @@ -17,10 +17,15 @@ namespace NzbDrone.Core.AutoImport.IMDbWatchList { private static readonly IMDbWatchListSettingsValidator Validator = new IMDbWatchListSettingsValidator(); - [FieldDefinition(0, Label = "Watch List Id", HelpLink = "http://rss.imdb.com/list/")] - public string IMDbWatchListId { get; set; } + public IMDbWatchListSettings() + { + Link = "http://rss.imdb.com/list/"; + } + + [FieldDefinition(0, Label = "Watch List RSS link", HelpLink = "http://rss.imdb.com/list/")] + public string Link { get; set; } - public bool IsValid => !string.IsNullOrWhiteSpace(IMDbWatchListId); + public bool IsValid => !string.IsNullOrWhiteSpace(Link); public NzbDroneValidationResult Validate() { diff --git a/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs b/src/NzbDrone.Core/NetImport/INetImport.cs similarity index 77% rename from src/NzbDrone.Core/AutoImporter/IAutoImporter.cs rename to src/NzbDrone.Core/NetImport/INetImport.cs index f78f90141..82ec79629 100644 --- a/src/NzbDrone.Core/AutoImporter/IAutoImporter.cs +++ b/src/NzbDrone.Core/NetImport/INetImport.cs @@ -4,9 +4,9 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Tv; -namespace NzbDrone.Core.AutoImporter +namespace NzbDrone.Core.NetImport { - public interface IAutoImporter : IProvider + public interface INetImport : IProvider { string Link { get; } bool Enabled { get; } diff --git a/src/NzbDrone.Core/NetImport/INetImportRequestGenerator.cs b/src/NzbDrone.Core/NetImport/INetImportRequestGenerator.cs new file mode 100644 index 000000000..d2c8107a4 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/INetImportRequestGenerator.cs @@ -0,0 +1,9 @@ +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.NetImport +{ + public interface INetImportRequestGenerator + { + NetImportPageableRequestChain GetMovies(); + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/IProcessNetImportResponse.cs b/src/NzbDrone.Core/NetImport/IProcessNetImportResponse.cs new file mode 100644 index 000000000..4776f551e --- /dev/null +++ b/src/NzbDrone.Core/NetImport/IProcessNetImportResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.NetImport +{ + public interface IParseNetImportResponse + { + IList ParseResponse(NetImportResponse netMovieImporterResponse); + } +} diff --git a/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs b/src/NzbDrone.Core/NetImport/NetImportBase.cs similarity index 89% rename from src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs rename to src/NzbDrone.Core/NetImport/NetImportBase.cs index 7d95bdfe8..a3d2723e2 100644 --- a/src/NzbDrone.Core/AutoImporter/AutoImporterBase.cs +++ b/src/NzbDrone.Core/NetImport/NetImportBase.cs @@ -11,9 +11,9 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Tv; -namespace NzbDrone.Core.AutoImporter +namespace NzbDrone.Core.NetImport { - public abstract class AutoImporterBase : IAutoImporter + public abstract class NetImportBase : INetImport where TSettings : IProviderConfig, new() { protected readonly IConfigService _configService; @@ -25,7 +25,7 @@ namespace NzbDrone.Core.AutoImporter public abstract bool Enabled { get; } - public AutoImporterBase(IConfigService configService, IParsingService parsingService, Logger logger) + public NetImportBase(IConfigService configService, IParsingService parsingService, Logger logger) { _configService = configService; _parsingService = parsingService; @@ -42,7 +42,7 @@ namespace NzbDrone.Core.AutoImporter { var config = (IProviderConfig)new TSettings(); - yield return new AutoImporterDefinition + yield return new NetImportDefinition { Name = GetType().Name, Link = Link, diff --git a/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs b/src/NzbDrone.Core/NetImport/NetImportDefinition.cs similarity index 65% rename from src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs rename to src/NzbDrone.Core/NetImport/NetImportDefinition.cs index b3fad1178..6d2442eb7 100644 --- a/src/NzbDrone.Core/AutoImporter/AutoImporterDefinition.cs +++ b/src/NzbDrone.Core/NetImport/NetImportDefinition.cs @@ -1,8 +1,8 @@ using NzbDrone.Core.ThingiProvider; -namespace NzbDrone.Core.AutoImporter +namespace NzbDrone.Core.NetImport { - public class AutoImporterDefinition : ProviderDefinition + public class NetImportDefinition : ProviderDefinition { public bool Enabled { get; set; } public string Link { get; set; } diff --git a/src/NzbDrone.Core/NetImport/NetImportPageableRequest.cs b/src/NzbDrone.Core/NetImport/NetImportPageableRequest.cs new file mode 100644 index 000000000..50a43fce9 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/NetImportPageableRequest.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; + +namespace NzbDrone.Core.NetImport +{ + public class NetImportPageableRequest : IEnumerable + { + private readonly IEnumerable _enumerable; + + public NetImportPageableRequest(IEnumerable enumerable) + { + _enumerable = enumerable; + } + + public IEnumerator GetEnumerator() + { + return _enumerable.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _enumerable.GetEnumerator(); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/NetImportPageableRequestChain.cs b/src/NzbDrone.Core/NetImport/NetImportPageableRequestChain.cs new file mode 100644 index 000000000..080b8727a --- /dev/null +++ b/src/NzbDrone.Core/NetImport/NetImportPageableRequestChain.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.NetImport +{ + public class NetImportPageableRequestChain + { + private List> _chains; + + public NetImportPageableRequestChain() + { + _chains = new List>(); + _chains.Add(new List()); + } + + public int Tiers => _chains.Count; + + public IEnumerable GetAllTiers() + { + return _chains.SelectMany(v => v); + } + + public IEnumerable GetTier(int index) + { + return _chains[index]; + } + + public void Add(IEnumerable request) + { + if (request == null) return; + + _chains.Last().Add(new NetImportPageableRequest(request)); + } + + public void AddTier(IEnumerable request) + { + AddTier(); + Add(request); + } + + public void AddTier() + { + if (_chains.Last().Count == 0) return; + + _chains.Add(new List()); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/NetImportRepository.cs b/src/NzbDrone.Core/NetImport/NetImportRepository.cs new file mode 100644 index 000000000..8efa8a4a8 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/NetImportRepository.cs @@ -0,0 +1,20 @@ +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.ThingiProvider; + + +namespace NzbDrone.Core.NetImport +{ + public interface INetImportRepository : IProviderRepository + { + + } + + public class NetImportRepository : ProviderRepository, INetImportRepository + { + public NetImportRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/NetImportRequest.cs b/src/NzbDrone.Core/NetImport/NetImportRequest.cs new file mode 100644 index 000000000..e00fe316f --- /dev/null +++ b/src/NzbDrone.Core/NetImport/NetImportRequest.cs @@ -0,0 +1,21 @@ +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.NetImport +{ + public class NetImportRequest + { + public HttpRequest HttpRequest { get; private set; } + + public NetImportRequest(string url, HttpAccept httpAccept) + { + HttpRequest = new HttpRequest(url, httpAccept); + } + + public NetImportRequest(HttpRequest httpRequest) + { + HttpRequest = httpRequest; + } + + public HttpUri Url => HttpRequest.Url; + } +} diff --git a/src/NzbDrone.Core/NetImport/NetImportResponse.cs b/src/NzbDrone.Core/NetImport/NetImportResponse.cs new file mode 100644 index 000000000..3174b0775 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/NetImportResponse.cs @@ -0,0 +1,24 @@ +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.NetImport +{ + public class NetImportResponse + { + private readonly NetImportRequest _netImport; + private readonly HttpResponse _httpResponse; + + public NetImportResponse(NetImportRequest netImport, HttpResponse httpResponse) + { + _netImport = netImport; + _httpResponse = httpResponse; + } + + public NetImportRequest Request => _netImport; + + public HttpRequest HttpRequest => _httpResponse.Request; + + public HttpResponse HttpResponse => _httpResponse; + + public string Content => _httpResponse.Content; + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 563e08c8e..ebbf9c761 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -122,16 +122,24 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + From 6878abe2a20f8f1f32e93601325642ea906fd98f Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sun, 15 Jan 2017 15:35:38 -0500 Subject: [PATCH 05/52] whoops, only parse title once --- .../NetImport/IMDbWatchList/IMDbWatchListParser.cs | 8 +++++--- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs index 7fd666141..4042eff95 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs @@ -40,11 +40,13 @@ namespace NzbDrone.Core.NetImport.IMDbWatchList foreach (var result in responseData) { + var title = Parser.Parser.ParseMovieTitle(result.Title, false); + torrentInfos.Add(new Movie() { - Title = Parser.Parser.ParseMovieTitle(result.Title, false).MovieTitle, - Year = Parser.Parser.ParseMovieTitle(result.Title, false).Year, - ImdbId = Parser.Parser.ParseImdbId(result.Link).ToString() + Title = title.MovieTitle, + Year = title.Year, + ImdbId = Parser.Parser.ParseImdbId(result.Link) }); } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index ebbf9c761..f26bca9f4 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -122,6 +122,7 @@ + From b02944a3b2eec7d5ff6b80ea0e9bbe3b10895a3a Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Tue, 17 Jan 2017 20:30:21 -0500 Subject: [PATCH 06/52] Added Qualties to Settings --- .../Datastore/Migration/119_create_netimport_table.cs | 9 +++++---- .../NetImport/IMDbWatchList/IMDbWatchList.cs | 3 ++- .../NetImport/IMDbWatchList/IMDbWatchListParser.cs | 1 + .../NetImport/IMDbWatchList/IMDbWatchListSettings.cs | 5 +++++ src/NzbDrone.Core/NetImport/NetImportBase.cs | 2 ++ src/NzbDrone.Core/NetImport/NetImportDefinition.cs | 3 ++- src/NzbDrone.Core/NzbDrone.Core.csproj | 4 +++- 7 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs b/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs index 7784ad1c4..086464746 100644 --- a/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs +++ b/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs @@ -9,10 +9,11 @@ namespace NzbDrone.Core.Datastore.Migration protected override void MainDbUpgrade() { Create.TableForModel("NetImport") - .WithColumn("Enabled").AsBoolean() - .WithColumn("Name").AsString().Unique() - .WithColumn("Implementation").AsString() - .WithColumn("Settings").AsString().Nullable(); + .WithColumn("Enabled").AsBoolean() + .WithColumn("ProfileId").AsInt32() + .WithColumn("Name").AsString().Unique() + .WithColumn("Implementation").AsString() + .WithColumn("Settings").AsString().Nullable(); } } } diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs index d97fb99c2..ed5c06da4 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Xml.Serialization; using FluentValidation.Results; using NLog; using NzbDrone.Common.Http; @@ -15,9 +16,9 @@ namespace NzbDrone.Core.NetImport.IMDbWatchList { public override string Name => "IMDbWatchList"; public override string Link => "http://rss.imdb.com/list/"; + public override int ProfileId => 1; public override bool Enabled => true; - public IMDbWatchList(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) : base(httpClient, configService, parsingService, logger) { } diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs index 4042eff95..20fb368ec 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs @@ -46,6 +46,7 @@ namespace NzbDrone.Core.NetImport.IMDbWatchList { Title = title.MovieTitle, Year = title.Year, + ProfileId = _settings.ProfileId, ImdbId = Parser.Parser.ParseImdbId(result.Link) }); } diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs index f63e7206f..3ca24faf3 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs @@ -1,5 +1,6 @@ using FluentValidation; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Profiles; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; @@ -20,10 +21,14 @@ namespace NzbDrone.Core.NetImport.IMDbWatchList public IMDbWatchListSettings() { Link = "http://rss.imdb.com/list/"; + ProfileId = 1; } [FieldDefinition(0, Label = "Watch List RSS link", HelpLink = "http://rss.imdb.com/list/")] public string Link { get; set; } + + [FieldDefinition(1, Label = "Quality", Type = FieldType.Select, SelectOptions = typeof(Profile), HelpText = "Quality of all imported movies")] + public int ProfileId { get; set; } public bool IsValid => !string.IsNullOrWhiteSpace(Link); diff --git a/src/NzbDrone.Core/NetImport/NetImportBase.cs b/src/NzbDrone.Core/NetImport/NetImportBase.cs index a3d2723e2..0f3f6fc0f 100644 --- a/src/NzbDrone.Core/NetImport/NetImportBase.cs +++ b/src/NzbDrone.Core/NetImport/NetImportBase.cs @@ -22,6 +22,7 @@ namespace NzbDrone.Core.NetImport public abstract string Name { get; } public abstract string Link { get; } + public abstract int ProfileId { get; } public abstract bool Enabled { get; } @@ -46,6 +47,7 @@ namespace NzbDrone.Core.NetImport { Name = GetType().Name, Link = Link, + ProfileId = ProfileId, Enabled = config.Validate().IsValid && Enabled, Implementation = GetType().Name, Settings = config diff --git a/src/NzbDrone.Core/NetImport/NetImportDefinition.cs b/src/NzbDrone.Core/NetImport/NetImportDefinition.cs index 6d2442eb7..b4af0c4a2 100644 --- a/src/NzbDrone.Core/NetImport/NetImportDefinition.cs +++ b/src/NzbDrone.Core/NetImport/NetImportDefinition.cs @@ -4,8 +4,9 @@ namespace NzbDrone.Core.NetImport { public class NetImportDefinition : ProviderDefinition { - public bool Enabled { get; set; } public string Link { get; set; } + public int ProfileId { get; set; } + public bool Enabled { get; set; } public override bool Enable => Enabled; } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index f26bca9f4..d09f0a840 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -1273,7 +1273,9 @@ - + + + From 734a36de065d035a82e81afc1d5791dd06125ca8 Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Sat, 21 Jan 2017 18:42:58 +0100 Subject: [PATCH 07/52] Added some abstraction for settings. --- .../IMDbWatchList/IMDbWatchListSettings.cs | 25 ++---------- .../NetImport/NetImportBaseSettings.cs | 40 +++++++++++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + 3 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 src/NzbDrone.Core/NetImport/NetImportBaseSettings.cs diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs index 3ca24faf3..6d10fa78c 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs +++ b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs @@ -6,17 +6,10 @@ using NzbDrone.Core.Validation; namespace NzbDrone.Core.NetImport.IMDbWatchList { - public class IMDbWatchListSettingsValidator : AbstractValidator - { - public IMDbWatchListSettingsValidator() - { - RuleFor(c => c.Link).NotEmpty(); - } - } - public class IMDbWatchListSettings : IProviderConfig + public class IMDbWatchListSettings : NetImportBaseSettings { - private static readonly IMDbWatchListSettingsValidator Validator = new IMDbWatchListSettingsValidator(); + //private const string helpLink = "https://imdb.com"; public IMDbWatchListSettings() { @@ -24,17 +17,7 @@ namespace NzbDrone.Core.NetImport.IMDbWatchList ProfileId = 1; } - [FieldDefinition(0, Label = "Watch List RSS link", HelpLink = "http://rss.imdb.com/list/")] - public string Link { get; set; } - - [FieldDefinition(1, Label = "Quality", Type = FieldType.Select, SelectOptions = typeof(Profile), HelpText = "Quality of all imported movies")] - public int ProfileId { get; set; } - - public bool IsValid => !string.IsNullOrWhiteSpace(Link); - - public NzbDroneValidationResult Validate() - { - return new NzbDroneValidationResult(Validator.Validate(this)); - } + [FieldDefinition(0, Label = "RSS Link", HelpText = "Link to the rss feed of movies.")] + public new string Link { get; set; } } } diff --git a/src/NzbDrone.Core/NetImport/NetImportBaseSettings.cs b/src/NzbDrone.Core/NetImport/NetImportBaseSettings.cs new file mode 100644 index 000000000..cdbbd0baf --- /dev/null +++ b/src/NzbDrone.Core/NetImport/NetImportBaseSettings.cs @@ -0,0 +1,40 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.NetImport +{ + public class NetImportBaseSettingsValidator : AbstractValidator + { + public NetImportBaseSettingsValidator() + { + RuleFor(c => c.Link).NotEmpty(); + } + } + + public class NetImportBaseSettings : IProviderConfig + { + private static readonly NetImportBaseSettingsValidator Validator = new NetImportBaseSettingsValidator(); + + public NetImportBaseSettings() + { + Link = "http://rss.imdb.com/list/"; + ProfileId = 1; + } + + [FieldDefinition(0, Label = "Link", HelpText = "Link to the list of movies.")] + public string Link { get; set; } + + [FieldDefinition(1, Label = "Quality", Type = FieldType.Select, SelectOptions = typeof(Profile), HelpText = "Quality of all imported movies")] + public int ProfileId { get; set; } + + public bool IsValid => !string.IsNullOrWhiteSpace(Link); + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index d09f0a840..06402f783 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -126,6 +126,7 @@ + From a98b69859c7dbd19ebc3476ca9d7cc4f349d56af Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Sat, 21 Jan 2017 20:29:31 +0100 Subject: [PATCH 08/52] Big Abstraction for IMDBWatchlist -> RSSImport (With a test) --- .../Files/imdb_watchlist.xml | 1760 +++++++++++++++++ .../IndexerTests/BasicRssParserFixture.cs | 1 + .../NetImport/RSSImportFixture.cs | 34 + .../NzbDrone.Core.Test.csproj | 4 + .../Migration/119_create_netimport_table.cs | 1 - .../NetImport/IMDbWatchList/IMDbWatchList.cs | 36 - .../IMDbWatchList/IMDbWatchListParser.cs | 57 - src/NzbDrone.Core/NetImport/INetImport.cs | 1 - src/NzbDrone.Core/NetImport/NetImportBase.cs | 4 - .../NetImport/NetImportDefinition.cs | 2 - .../IMDbWatchListAPI.cs | 2 +- .../IMDbWatchListProxy.cs | 6 +- .../NetImport/RSSImport/RSSImport.cs | 52 + .../NetImport/RSSImport/RSSImportParser.cs | 236 +++ .../RSSImportRequestGenerator.cs} | 6 +- .../RSSImportSettings.cs} | 8 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 12 +- 17 files changed, 2104 insertions(+), 118 deletions(-) create mode 100644 src/NzbDrone.Core.Test/Files/imdb_watchlist.xml create mode 100644 src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs delete mode 100644 src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs delete mode 100644 src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs rename src/NzbDrone.Core/NetImport/{IMDbWatchList => RSSImport}/IMDbWatchListAPI.cs (96%) rename src/NzbDrone.Core/NetImport/{IMDbWatchList => RSSImport}/IMDbWatchListProxy.cs (92%) create mode 100644 src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs create mode 100644 src/NzbDrone.Core/NetImport/RSSImport/RSSImportParser.cs rename src/NzbDrone.Core/NetImport/{IMDbWatchList/IMDbWatchListRequestGenerator.cs => RSSImport/RSSImportRequestGenerator.cs} (82%) rename src/NzbDrone.Core/NetImport/{IMDbWatchList/IMDbWatchListSettings.cs => RSSImport/RSSImportSettings.cs} (68%) diff --git a/src/NzbDrone.Core.Test/Files/imdb_watchlist.xml b/src/NzbDrone.Core.Test/Files/imdb_watchlist.xml new file mode 100644 index 000000000..b49485a6c --- /dev/null +++ b/src/NzbDrone.Core.Test/Files/imdb_watchlist.xml @@ -0,0 +1,1760 @@ + + + + Movie Watchlist + http://www.imdb.com/list/ls005547488/ + + Fri, 15 Jul 2011 05:14:06 GMT + Tue, 25 Mar 2014 02:22:29 GMT + + Tue, 25 Mar 2014 02:22:29 GMT + Think Like a Man Too (2014) + http://www.imdb.com/title/tt2239832/ + http://www.imdb.com/title/tt2239832/ + + + + Tue, 25 Mar 2014 00:30:49 GMT + The Machine (2013) + http://www.imdb.com/title/tt2317225/ + http://www.imdb.com/title/tt2317225/ + + + + Sun, 23 Mar 2014 07:51:40 GMT + The Great Beauty (2013) + http://www.imdb.com/title/tt2358891/ + http://www.imdb.com/title/tt2358891/ + + + + Sun, 23 Mar 2014 07:51:03 GMT + A Touch of Sin (2013) + http://www.imdb.com/title/tt2852400/ + http://www.imdb.com/title/tt2852400/ + + + + Sun, 23 Mar 2014 07:49:12 GMT + All Is Lost (2013) + http://www.imdb.com/title/tt2017038/ + http://www.imdb.com/title/tt2017038/ + + + + Sat, 22 Mar 2014 05:07:32 GMT + Nymphomaniac: Vol. II (2013) + http://www.imdb.com/title/tt2382009/ + http://www.imdb.com/title/tt2382009/ + + + + Sat, 22 Mar 2014 05:07:18 GMT + The Maze Runner (2014) + http://www.imdb.com/title/tt1790864/ + http://www.imdb.com/title/tt1790864/ + + + + Thu, 16 Jan 2014 04:57:39 GMT + Winter's Tale (2014) + http://www.imdb.com/title/tt1837709/ + http://www.imdb.com/title/tt1837709/ + + + + Thu, 16 Jan 2014 04:50:58 GMT + Love at First Sight (2010 Short Film) + http://www.imdb.com/title/tt1735878/ + http://www.imdb.com/title/tt1735878/ + + + + Thu, 16 Jan 2014 04:47:51 GMT + Run & Jump (2013) + http://www.imdb.com/title/tt2343158/ + http://www.imdb.com/title/tt2343158/ + + + + Thu, 16 Jan 2014 04:45:23 GMT + The Railway Man (2013) + http://www.imdb.com/title/tt2058107/ + http://www.imdb.com/title/tt2058107/ + + + + Thu, 16 Jan 2014 04:41:47 GMT + Welcome to the Jungle (2013) + http://www.imdb.com/title/tt2193265/ + http://www.imdb.com/title/tt2193265/ + + + + Thu, 16 Jan 2014 04:38:26 GMT + Le Week-End (2013) + http://www.imdb.com/title/tt2392326/ + http://www.imdb.com/title/tt2392326/ + + + + Thu, 16 Jan 2014 04:31:57 GMT + Labor Day (2013) + http://www.imdb.com/title/tt1967545/ + http://www.imdb.com/title/tt1967545/ + + + + Thu, 16 Jan 2014 04:05:40 GMT + Grand Piano (2013) + http://www.imdb.com/title/tt2039345/ + http://www.imdb.com/title/tt2039345/ + + + + Thu, 16 Jan 2014 04:05:05 GMT + Gloria (2013) + http://www.imdb.com/title/tt2425486/ + http://www.imdb.com/title/tt2425486/ + + + + Thu, 16 Jan 2014 04:04:21 GMT + Gimme Shelter (2013) + http://www.imdb.com/title/tt1657510/ + http://www.imdb.com/title/tt1657510/ + + + + Thu, 16 Jan 2014 04:01:29 GMT + The Past (2013) + http://www.imdb.com/title/tt2404461/ + http://www.imdb.com/title/tt2404461/ + + + + Thu, 16 Jan 2014 04:00:49 GMT + Fading Gigolo (2013) + http://www.imdb.com/title/tt2258345/ + http://www.imdb.com/title/tt2258345/ + + + + Thu, 16 Jan 2014 04:00:18 GMT + Edge of Tomorrow (2014) + http://www.imdb.com/title/tt1631867/ + http://www.imdb.com/title/tt1631867/ + + + + Thu, 16 Jan 2014 03:58:29 GMT + Earth to Echo (2014) + http://www.imdb.com/title/tt2183034/ + http://www.imdb.com/title/tt2183034/ + + + + Thu, 16 Jan 2014 03:56:30 GMT + Drew: The Man Behind the Poster (2013 Documentary) + http://www.imdb.com/title/tt1486843/ + http://www.imdb.com/title/tt1486843/ + + + + Thu, 16 Jan 2014 03:55:16 GMT + Doomsdays (2013) + http://www.imdb.com/title/tt2395146/ + http://www.imdb.com/title/tt2395146/ + + + + Thu, 16 Jan 2014 03:52:31 GMT + Design Is One: The Vignellis (2012 Documentary) + http://www.imdb.com/title/tt2610862/ + http://www.imdb.com/title/tt2610862/ + + + + Thu, 16 Jan 2014 03:51:37 GMT + Eastern Promises (2007) + http://www.imdb.com/title/tt0765443/ + http://www.imdb.com/title/tt0765443/ + + + + Thu, 16 Jan 2014 03:50:43 GMT + The Machinist (2004) + http://www.imdb.com/title/tt0361862/ + http://www.imdb.com/title/tt0361862/ + + + + Thu, 16 Jan 2014 03:49:51 GMT + eXistenZ (1999) + http://www.imdb.com/title/tt0120907/ + http://www.imdb.com/title/tt0120907/ + + + + Thu, 16 Jan 2014 03:49:26 GMT + Courage Under Fire (1996) + http://www.imdb.com/title/tt0115956/ + http://www.imdb.com/title/tt0115956/ + + + + Thu, 16 Jan 2014 03:45:04 GMT + Cosmopolis (2012) + http://www.imdb.com/title/tt1480656/ + http://www.imdb.com/title/tt1480656/ + + + + Thu, 16 Jan 2014 03:44:27 GMT + Concussion (2013) + http://www.imdb.com/title/tt2296697/ + http://www.imdb.com/title/tt2296697/ + + + + Thu, 16 Jan 2014 03:43:05 GMT + Closed Curtain (2013) + http://www.imdb.com/title/tt2626926/ + http://www.imdb.com/title/tt2626926/ + + + + Thu, 16 Jan 2014 03:42:25 GMT + Charlie Countryman (2013) + http://www.imdb.com/title/tt1196948/ + http://www.imdb.com/title/tt1196948/ + + + + Thu, 16 Jan 2014 03:41:49 GMT + Captain America: The Winter Soldier (2014) + http://www.imdb.com/title/tt1843866/ + http://www.imdb.com/title/tt1843866/ + + + + Thu, 16 Jan 2014 03:40:59 GMT + Blue Is the Warmest Color (2013) + http://www.imdb.com/title/tt2278871/ + http://www.imdb.com/title/tt2278871/ + + + + Thu, 16 Jan 2014 03:39:37 GMT + Blind Detective (2013) + http://www.imdb.com/title/tt2332707/ + http://www.imdb.com/title/tt2332707/ + + + + Thu, 16 Jan 2014 03:38:05 GMT + Blended (2014) + http://www.imdb.com/title/tt1086772/ + http://www.imdb.com/title/tt1086772/ + + + + Thu, 16 Jan 2014 03:37:38 GMT + Big Bad Wolves (2013) + http://www.imdb.com/title/tt2309224/ + http://www.imdb.com/title/tt2309224/ + + + + Thu, 16 Jan 2014 03:36:35 GMT + Barefoot (2014) + http://www.imdb.com/title/tt2355495/ + http://www.imdb.com/title/tt2355495/ + + + + Thu, 16 Jan 2014 03:35:13 GMT + Bad Words (2013) + http://www.imdb.com/title/tt2170299/ + http://www.imdb.com/title/tt2170299/ + + + + Thu, 16 Jan 2014 03:34:27 GMT + A Fantastic Fear of Everything (2012) + http://www.imdb.com/title/tt2006040/ + http://www.imdb.com/title/tt2006040/ + + + + Thu, 16 Jan 2014 01:21:34 GMT + A Field in England (2013) + http://www.imdb.com/title/tt2375574/ + http://www.imdb.com/title/tt2375574/ + + + + Thu, 16 Jan 2014 01:21:14 GMT + Odd Thomas (2013) + http://www.imdb.com/title/tt1767354/ + http://www.imdb.com/title/tt1767354/ + + + + Thu, 16 Jan 2014 01:14:36 GMT + The Pretty One (2013) + http://www.imdb.com/title/tt2140577/ + http://www.imdb.com/title/tt2140577/ + + + + Thu, 16 Jan 2014 01:08:37 GMT + Awful Nice (2013) + http://www.imdb.com/title/tt1414449/ + http://www.imdb.com/title/tt1414449/ + + + + Wed, 15 Jan 2014 23:10:34 GMT + 50 to 1 (2014) + http://www.imdb.com/title/tt1777595/ + http://www.imdb.com/title/tt1777595/ + + + + Wed, 15 Jan 2014 23:09:57 GMT + $50K and a Call Girl: A Love Story (2014) + http://www.imdb.com/title/tt2106284/ + http://www.imdb.com/title/tt2106284/ + + + + Fri, 10 Jan 2014 04:48:44 GMT + Interstellar (2014) + http://www.imdb.com/title/tt0816692/ + http://www.imdb.com/title/tt0816692/ + + + + Fri, 10 Jan 2014 04:44:18 GMT + 3 Days to Kill (2014) + http://www.imdb.com/title/tt2172934/ + http://www.imdb.com/title/tt2172934/ + + + + Fri, 10 Jan 2014 04:40:50 GMT + Back in the Day (2014) + http://www.imdb.com/title/tt2246887/ + http://www.imdb.com/title/tt2246887/ + + + + Fri, 10 Jan 2014 04:36:30 GMT + 300: Rise of an Empire (2014) + http://www.imdb.com/title/tt1253863/ + http://www.imdb.com/title/tt1253863/ + + + + Fri, 10 Jan 2014 04:28:56 GMT + Small Time (2014) + http://www.imdb.com/title/tt2310109/ + http://www.imdb.com/title/tt2310109/ + + + + Fri, 10 Jan 2014 04:24:20 GMT + The Grand Budapest Hotel (2014) + http://www.imdb.com/title/tt2278388/ + http://www.imdb.com/title/tt2278388/ + + + + Fri, 10 Jan 2014 04:10:34 GMT + Dumbbells (2014) + http://www.imdb.com/title/tt1978428/ + http://www.imdb.com/title/tt1978428/ + + + + Fri, 10 Jan 2014 04:05:22 GMT + Dawn of the Planet of the Apes (2014) + http://www.imdb.com/title/tt2103281/ + http://www.imdb.com/title/tt2103281/ + + + + Fri, 22 Nov 2013 02:30:55 GMT + Beyond Outrage (2012) + http://www.imdb.com/title/tt1724962/ + http://www.imdb.com/title/tt1724962/ + + + + Fri, 22 Nov 2013 02:30:06 GMT + Belle (2013) + http://www.imdb.com/title/tt2404181/ + http://www.imdb.com/title/tt2404181/ + + + + Fri, 22 Nov 2013 02:29:41 GMT + A Simple Plan (1998) + http://www.imdb.com/title/tt0120324/ + http://www.imdb.com/title/tt0120324/ + + + + Fri, 22 Nov 2013 02:29:11 GMT + Approved for Adoption (2012) + http://www.imdb.com/title/tt1621766/ + http://www.imdb.com/title/tt1621766/ + + + + Fri, 22 Nov 2013 02:28:37 GMT + A Fierce Green Fire (2012 Documentary) + http://www.imdb.com/title/tt1539489/ + http://www.imdb.com/title/tt1539489/ + + + + Fri, 22 Nov 2013 02:28:01 GMT + Mother of George (2013) + http://www.imdb.com/title/tt2094890/ + http://www.imdb.com/title/tt2094890/ + + + + Tue, 20 Aug 2013 02:45:42 GMT + What Maisie Knew (2012) + http://www.imdb.com/title/tt1932767/ + http://www.imdb.com/title/tt1932767/ + + + + Tue, 20 Aug 2013 02:45:22 GMT + We're the Millers (2013) + http://www.imdb.com/title/tt1723121/ + http://www.imdb.com/title/tt1723121/ + + + + Tue, 20 Aug 2013 02:44:53 GMT + Visitors (2013 Documentary) + http://www.imdb.com/title/tt2936174/ + http://www.imdb.com/title/tt2936174/ + + + + Tue, 20 Aug 2013 02:43:58 GMT + Twenty Feet from Stardom (2013 Documentary) + http://www.imdb.com/title/tt2396566/ + http://www.imdb.com/title/tt2396566/ + + + + Tue, 20 Aug 2013 02:43:40 GMT + Trance (2013) + http://www.imdb.com/title/tt1924429/ + http://www.imdb.com/title/tt1924429/ + + + + Tue, 20 Aug 2013 02:42:19 GMT + This Is Martin Bonner (2013) + http://www.imdb.com/title/tt1798291/ + http://www.imdb.com/title/tt1798291/ + + + + Tue, 20 Aug 2013 02:41:50 GMT + The Purge (2013) + http://www.imdb.com/title/tt2184339/ + http://www.imdb.com/title/tt2184339/ + + + + Tue, 20 Aug 2013 02:41:27 GMT + The Place Beyond the Pines (2012) + http://www.imdb.com/title/tt1817273/ + http://www.imdb.com/title/tt1817273/ + + + + Tue, 20 Aug 2013 02:41:08 GMT + The Pervert's Guide to Ideology (2012 Documentary) + http://www.imdb.com/title/tt2152198/ + http://www.imdb.com/title/tt2152198/ + + + + Tue, 20 Aug 2013 02:40:36 GMT + The Monuments Men (2014) + http://www.imdb.com/title/tt2177771/ + http://www.imdb.com/title/tt2177771/ + + + + Tue, 20 Aug 2013 02:40:09 GMT + The Kids Are All Right (2010) + http://www.imdb.com/title/tt0842926/ + http://www.imdb.com/title/tt0842926/ + + + + Tue, 20 Aug 2013 02:39:46 GMT + The Internship (2013) + http://www.imdb.com/title/tt2234155/ + http://www.imdb.com/title/tt2234155/ + + + + Tue, 20 Aug 2013 02:39:26 GMT + The Incredible Burt Wonderstone (2013) + http://www.imdb.com/title/tt0790628/ + http://www.imdb.com/title/tt0790628/ + + + + Tue, 20 Aug 2013 02:39:03 GMT + The Company You Keep (2012) + http://www.imdb.com/title/tt1381404/ + http://www.imdb.com/title/tt1381404/ + + + + Tue, 20 Aug 2013 02:38:44 GMT + The Boxtrolls (2014) + http://www.imdb.com/title/tt0787474/ + http://www.imdb.com/title/tt0787474/ + + + + Tue, 20 Aug 2013 02:37:58 GMT + The Artist and the Model (2012) + http://www.imdb.com/title/tt1990217/ + http://www.imdb.com/title/tt1990217/ + + + + Tue, 20 Aug 2013 02:37:34 GMT + Spark: A Burning Man Story (2013 Documentary) + http://www.imdb.com/title/tt2554648/ + http://www.imdb.com/title/tt2554648/ + + + + Tue, 20 Aug 2013 02:36:42 GMT + Smash & Grab: The Story of the Pink Panthers (2013 Documentary) + http://www.imdb.com/title/tt2250032/ + http://www.imdb.com/title/tt2250032/ + + + + Tue, 20 Aug 2013 02:36:16 GMT + A Single Shot (2013) + http://www.imdb.com/title/tt1540741/ + http://www.imdb.com/title/tt1540741/ + + + + Tue, 20 Aug 2013 02:35:49 GMT + Side Effects (2013) + http://www.imdb.com/title/tt2053463/ + http://www.imdb.com/title/tt2053463/ + + + + Tue, 20 Aug 2013 02:34:43 GMT + Paradise (2013) + http://www.imdb.com/title/tt1262990/ + http://www.imdb.com/title/tt1262990/ + + + + Tue, 20 Aug 2013 02:34:00 GMT + Paperman (2012 Short Film) + http://www.imdb.com/title/tt2388725/ + http://www.imdb.com/title/tt2388725/ + + + + Tue, 20 Aug 2013 02:33:23 GMT + Once (2007) + http://www.imdb.com/title/tt0907657/ + http://www.imdb.com/title/tt0907657/ + + + + Tue, 20 Aug 2013 02:32:30 GMT + Mud (2012) + http://www.imdb.com/title/tt1935179/ + http://www.imdb.com/title/tt1935179/ + + + + Tue, 20 Aug 2013 02:31:52 GMT + Much Ado About Nothing (2012) + http://www.imdb.com/title/tt2094064/ + http://www.imdb.com/title/tt2094064/ + + + + Tue, 20 Aug 2013 02:31:32 GMT + Mama (2013) + http://www.imdb.com/title/tt2023587/ + http://www.imdb.com/title/tt2023587/ + + + + Tue, 20 Aug 2013 02:30:22 GMT + Ip Man: The Final Fight (2013) + http://www.imdb.com/title/tt2495118/ + http://www.imdb.com/title/tt2495118/ + + + + Tue, 20 Aug 2013 02:29:58 GMT + Intolerance: Love's Struggle Throughout the Ages (1916) + http://www.imdb.com/title/tt0006864/ + http://www.imdb.com/title/tt0006864/ + + + + Tue, 20 Aug 2013 02:29:26 GMT + Instructions Not Included (2013) + http://www.imdb.com/title/tt2378281/ + http://www.imdb.com/title/tt2378281/ + + + + Tue, 20 Aug 2013 02:29:02 GMT + Insidious: Chapter 2 (2013) + http://www.imdb.com/title/tt2226417/ + http://www.imdb.com/title/tt2226417/ + + + + Tue, 20 Aug 2013 02:27:50 GMT + Inequality for All (2013 Documentary) + http://www.imdb.com/title/tt2215151/ + http://www.imdb.com/title/tt2215151/ + + + + Tue, 20 Aug 2013 02:27:28 GMT + Her (2013) + http://www.imdb.com/title/tt1798709/ + http://www.imdb.com/title/tt1798709/ + + + + Tue, 20 Aug 2013 02:02:50 GMT + The Gatekeepers (2012 Documentary) + http://www.imdb.com/title/tt2309788/ + http://www.imdb.com/title/tt2309788/ + + + + Tue, 20 Aug 2013 02:02:32 GMT + Greetings from Tim Buckley (2012) + http://www.imdb.com/title/tt1823125/ + http://www.imdb.com/title/tt1823125/ + + + + Tue, 20 Aug 2013 02:02:16 GMT + Good Ol' Freda (2013 Documentary) + http://www.imdb.com/title/tt2505938/ + http://www.imdb.com/title/tt2505938/ + + + + Tue, 20 Aug 2013 02:01:56 GMT + Standing Up (2013) + http://www.imdb.com/title/tt1905042/ + http://www.imdb.com/title/tt1905042/ + + + + Tue, 20 Aug 2013 02:01:35 GMT + Gimme the Loot (2012) + http://www.imdb.com/title/tt2139919/ + http://www.imdb.com/title/tt2139919/ + + + + Tue, 20 Aug 2013 01:55:45 GMT + Frozen (2013) + http://www.imdb.com/title/tt2294629/ + http://www.imdb.com/title/tt2294629/ + + + + Tue, 20 Aug 2013 01:54:33 GMT + Enough Said (2013) + http://www.imdb.com/title/tt2390361/ + http://www.imdb.com/title/tt2390361/ + + + + Tue, 20 Aug 2013 01:53:53 GMT + Disconnect (2012) + http://www.imdb.com/title/tt1433811/ + http://www.imdb.com/title/tt1433811/ + + + + Tue, 20 Aug 2013 01:53:18 GMT + The Seventh Dwarf (2014) + http://www.imdb.com/title/tt2914892/ + http://www.imdb.com/title/tt2914892/ + + + + Tue, 20 Aug 2013 01:52:48 GMT + Delicatessen (1991) + http://www.imdb.com/title/tt0101700/ + http://www.imdb.com/title/tt0101700/ + + + + Tue, 20 Aug 2013 01:52:21 GMT + Cold Comes the Night (2013) + http://www.imdb.com/title/tt2511428/ + http://www.imdb.com/title/tt2511428/ + + + + Tue, 20 Aug 2013 01:51:51 GMT + CBGB (2013) + http://www.imdb.com/title/tt1786751/ + http://www.imdb.com/title/tt1786751/ + + + + Tue, 20 Aug 2013 01:51:25 GMT + C.O.G. (2013) + http://www.imdb.com/title/tt1650393/ + http://www.imdb.com/title/tt1650393/ + + + + Tue, 20 Aug 2013 01:50:38 GMT + Beyond the Hills (2012) + http://www.imdb.com/title/tt2258281/ + http://www.imdb.com/title/tt2258281/ + + + + Tue, 20 Aug 2013 01:49:52 GMT + Bears (2014 Documentary) + http://www.imdb.com/title/tt2458776/ + http://www.imdb.com/title/tt2458776/ + + + + Tue, 20 Aug 2013 01:47:45 GMT + A Teacher (2013) + http://www.imdb.com/title/tt2201548/ + http://www.imdb.com/title/tt2201548/ + + + + Tue, 20 Aug 2013 01:37:42 GMT + At Any Price (2012) + http://www.imdb.com/title/tt1937449/ + http://www.imdb.com/title/tt1937449/ + + + + Tue, 20 Aug 2013 01:37:18 GMT + A Strange Brand of Happy (2013) + http://www.imdb.com/title/tt2014168/ + http://www.imdb.com/title/tt2014168/ + + + + Tue, 20 Aug 2013 01:36:35 GMT + American Milkshake (2013) + http://www.imdb.com/title/tt2254364/ + http://www.imdb.com/title/tt2254364/ + + + + Tue, 20 Aug 2013 01:36:14 GMT + American Hustle (2013) + http://www.imdb.com/title/tt1800241/ + http://www.imdb.com/title/tt1800241/ + + + + Tue, 20 Aug 2013 01:33:58 GMT + Airplane! (1980) + http://www.imdb.com/title/tt0080339/ + http://www.imdb.com/title/tt0080339/ + + + + Tue, 20 Aug 2013 01:33:27 GMT + A.C.O.D. (2013) + http://www.imdb.com/title/tt1311060/ + http://www.imdb.com/title/tt1311060/ + + + + Tue, 20 Aug 2013 01:33:07 GMT + 12 O'Clock Boys (2013 Documentary) + http://www.imdb.com/title/tt2420006/ + http://www.imdb.com/title/tt2420006/ + + + + Tue, 20 Aug 2013 01:31:45 GMT + Unfinished Song (2012) + http://www.imdb.com/title/tt1047011/ + http://www.imdb.com/title/tt1047011/ + + + + Tue, 20 Aug 2013 01:31:25 GMT + The Sapphires (2012) + http://www.imdb.com/title/tt1673697/ + http://www.imdb.com/title/tt1673697/ + + + + Tue, 20 Aug 2013 01:30:59 GMT + Stories We Tell (2012 Documentary) + http://www.imdb.com/title/tt2366450/ + http://www.imdb.com/title/tt2366450/ + + + + Tue, 20 Aug 2013 01:30:29 GMT + Morning (2010) + http://www.imdb.com/title/tt1320103/ + http://www.imdb.com/title/tt1320103/ + + + + Tue, 20 Aug 2013 01:28:57 GMT + Kon-Tiki (2012) + http://www.imdb.com/title/tt1613750/ + http://www.imdb.com/title/tt1613750/ + + + + Tue, 20 Aug 2013 01:27:42 GMT + Kelly's Heroes (1970) + http://www.imdb.com/title/tt0065938/ + http://www.imdb.com/title/tt0065938/ + + + + Tue, 20 Aug 2013 01:20:13 GMT + Il Futuro (2013) + http://www.imdb.com/title/tt1992156/ + http://www.imdb.com/title/tt1992156/ + + + + Tue, 20 Aug 2013 01:18:48 GMT + Dear Zachary: A Letter to a Son About His Father (2008 Documentary) + http://www.imdb.com/title/tt1152758/ + http://www.imdb.com/title/tt1152758/ + + + + Tue, 20 Aug 2013 01:17:34 GMT + August: Osage County (2013) + http://www.imdb.com/title/tt1322269/ + http://www.imdb.com/title/tt1322269/ + + + + Tue, 20 Aug 2013 01:17:02 GMT + A Thousand Clowns (1965) + http://www.imdb.com/title/tt0059798/ + http://www.imdb.com/title/tt0059798/ + + + + Fri, 16 Aug 2013 05:39:41 GMT + The Naked Gun 2½: The Smell of Fear (1991) + http://www.imdb.com/title/tt0102510/ + http://www.imdb.com/title/tt0102510/ + + + + Fri, 16 Aug 2013 02:11:27 GMT + Blazing Saddles (1974) + http://www.imdb.com/title/tt0071230/ + http://www.imdb.com/title/tt0071230/ + + + + Wed, 14 Aug 2013 23:11:34 GMT + Super High Me (2007 Documentary) + http://www.imdb.com/title/tt1111833/ + http://www.imdb.com/title/tt1111833/ + + + + Fri, 26 Jul 2013 06:26:43 GMT + I Am Love (2009) + http://www.imdb.com/title/tt1226236/ + http://www.imdb.com/title/tt1226236/ + + + + Fri, 26 Jul 2013 06:26:20 GMT + The Wind Rises (2013) + http://www.imdb.com/title/tt2013293/ + http://www.imdb.com/title/tt2013293/ + + + + Fri, 26 Jul 2013 06:25:56 GMT + Melancholia (2011) + http://www.imdb.com/title/tt1527186/ + http://www.imdb.com/title/tt1527186/ + + + + Fri, 26 Jul 2013 06:14:53 GMT + The Patience Stone (2012) + http://www.imdb.com/title/tt1638353/ + http://www.imdb.com/title/tt1638353/ + + + + Fri, 26 Jul 2013 06:12:55 GMT + The Hunger Games (2012) + http://www.imdb.com/title/tt1392170/ + http://www.imdb.com/title/tt1392170/ + + + + Fri, 26 Jul 2013 06:10:37 GMT + Salinger (2013 Documentary) + http://www.imdb.com/title/tt1596753/ + http://www.imdb.com/title/tt1596753/ + + + + Fri, 26 Jul 2013 06:09:51 GMT + 47 Ronin (2013) + http://www.imdb.com/title/tt1335975/ + http://www.imdb.com/title/tt1335975/ + + + + Fri, 26 Jul 2013 06:06:53 GMT + Kick-Ass 2 (2013) + http://www.imdb.com/title/tt1650554/ + http://www.imdb.com/title/tt1650554/ + + + + Fri, 26 Jul 2013 06:05:54 GMT + Blackfish (2013 Documentary) + http://www.imdb.com/title/tt2545118/ + http://www.imdb.com/title/tt2545118/ + + + + Fri, 26 Jul 2013 06:05:32 GMT + Cockneys vs Zombies (2012) + http://www.imdb.com/title/tt1362058/ + http://www.imdb.com/title/tt1362058/ + + + + Fri, 26 Jul 2013 06:05:11 GMT + Blue Exorcist: The Movie (2012) + http://www.imdb.com/title/tt3028018/ + http://www.imdb.com/title/tt3028018/ + + + + Fri, 26 Jul 2013 06:04:31 GMT + Computer Chess (2013) + http://www.imdb.com/title/tt2007360/ + http://www.imdb.com/title/tt2007360/ + + + + Fri, 26 Jul 2013 06:03:22 GMT + Girl Most Likely (2012) + http://www.imdb.com/title/tt1698648/ + http://www.imdb.com/title/tt1698648/ + + + + Fri, 26 Jul 2013 05:31:00 GMT + Frankenweenie (2012) + http://www.imdb.com/title/tt1142977/ + http://www.imdb.com/title/tt1142977/ + + + + Thu, 18 Jul 2013 07:41:08 GMT + Nowhere Boy (2009) + http://www.imdb.com/title/tt1266029/ + http://www.imdb.com/title/tt1266029/ + + + + Thu, 18 Jul 2013 07:40:41 GMT + Amistad (1997) + http://www.imdb.com/title/tt0118607/ + http://www.imdb.com/title/tt0118607/ + + + + Thu, 18 Jul 2013 07:40:19 GMT + Angus, Thongs and Perfect Snogging (2008) + http://www.imdb.com/title/tt0963743/ + http://www.imdb.com/title/tt0963743/ + + + + Thu, 18 Jul 2013 07:31:50 GMT + Year One (2009) + http://www.imdb.com/title/tt1045778/ + http://www.imdb.com/title/tt1045778/ + + + + Thu, 18 Jul 2013 07:31:23 GMT + RocknRolla (2008) + http://www.imdb.com/title/tt1032755/ + http://www.imdb.com/title/tt1032755/ + + + + Thu, 18 Jul 2013 07:31:07 GMT + World War Z (2013) + http://www.imdb.com/title/tt0816711/ + http://www.imdb.com/title/tt0816711/ + + + + Thu, 18 Jul 2013 07:30:27 GMT + Welcome to the Punch (2013) + http://www.imdb.com/title/tt1684233/ + http://www.imdb.com/title/tt1684233/ + + + + Thu, 18 Jul 2013 07:30:01 GMT + Ways to Live Forever (2010) + http://www.imdb.com/title/tt1446208/ + http://www.imdb.com/title/tt1446208/ + + + + Thu, 18 Jul 2013 07:29:43 GMT + The Rise (2012) + http://www.imdb.com/title/tt1981140/ + http://www.imdb.com/title/tt1981140/ + + + + Thu, 18 Jul 2013 07:29:19 GMT + Warm Bodies (2013) + http://www.imdb.com/title/tt1588173/ + http://www.imdb.com/title/tt1588173/ + + + + Thu, 18 Jul 2013 07:27:30 GMT + Violet & Daisy (2011) + http://www.imdb.com/title/tt1634136/ + http://www.imdb.com/title/tt1634136/ + + + + Thu, 18 Jul 2013 07:24:58 GMT + Tiger Eyes (2012) + http://www.imdb.com/title/tt1748260/ + http://www.imdb.com/title/tt1748260/ + + + + Thu, 18 Jul 2013 07:24:37 GMT + This Is the End (2013) + http://www.imdb.com/title/tt1245492/ + http://www.imdb.com/title/tt1245492/ + + + + Thu, 18 Jul 2013 07:24:19 GMT + The Wolf of Wall Street (2013) + http://www.imdb.com/title/tt0993846/ + http://www.imdb.com/title/tt0993846/ + + + + Thu, 18 Jul 2013 07:24:01 GMT + The Way Way Back (2013) + http://www.imdb.com/title/tt1727388/ + http://www.imdb.com/title/tt1727388/ + + + + Thu, 18 Jul 2013 07:20:15 GMT + The Time Being (2012) + http://www.imdb.com/title/tt1916749/ + http://www.imdb.com/title/tt1916749/ + + + + Thu, 18 Jul 2013 07:19:57 GMT + The Sweeney (2012) + http://www.imdb.com/title/tt0857190/ + http://www.imdb.com/title/tt0857190/ + + + + Thu, 18 Jul 2013 07:19:26 GMT + The Spectacular Now (2013) + http://www.imdb.com/title/tt1714206/ + http://www.imdb.com/title/tt1714206/ + + + + Thu, 18 Jul 2013 07:18:41 GMT + Thérèse (2012) + http://www.imdb.com/title/tt1654829/ + http://www.imdb.com/title/tt1654829/ + + + + Thu, 18 Jul 2013 07:18:17 GMT + The Mortal Instruments: City of Bones (2013) + http://www.imdb.com/title/tt1538403/ + http://www.imdb.com/title/tt1538403/ + + + + Thu, 18 Jul 2013 07:17:15 GMT + The Lifeguard (2013) + http://www.imdb.com/title/tt2265534/ + http://www.imdb.com/title/tt2265534/ + + + + Thu, 18 Jul 2013 07:16:58 GMT + The Lego Movie (2014) + http://www.imdb.com/title/tt1490017/ + http://www.imdb.com/title/tt1490017/ + + + + Thu, 18 Jul 2013 07:05:06 GMT + The Hobbit: The Battle of the Five Armies (2014) + http://www.imdb.com/title/tt2310332/ + http://www.imdb.com/title/tt2310332/ + + + + Thu, 18 Jul 2013 07:04:28 GMT + The Hobbit: The Desolation of Smaug (2013) + http://www.imdb.com/title/tt1170358/ + http://www.imdb.com/title/tt1170358/ + + + + Thu, 18 Jul 2013 07:02:54 GMT + Silver Linings Playbook (2012) + http://www.imdb.com/title/tt1045658/ + http://www.imdb.com/title/tt1045658/ + + + + Thu, 18 Jul 2013 07:02:22 GMT + The Heat (2013) + http://www.imdb.com/title/tt2404463/ + http://www.imdb.com/title/tt2404463/ + + + + Thu, 18 Jul 2013 06:59:40 GMT + The Frozen Ground (2013) + http://www.imdb.com/title/tt2005374/ + http://www.imdb.com/title/tt2005374/ + + + + Thu, 18 Jul 2013 06:59:19 GMT + The Fifth Estate (2013) + http://www.imdb.com/title/tt1837703/ + http://www.imdb.com/title/tt1837703/ + + + + Thu, 18 Jul 2013 06:58:18 GMT + The Counselor (2013) + http://www.imdb.com/title/tt2193215/ + http://www.imdb.com/title/tt2193215/ + + + + Thu, 18 Jul 2013 06:57:39 GMT + The Conjuring (2013) + http://www.imdb.com/title/tt1457767/ + http://www.imdb.com/title/tt1457767/ + + + + Thu, 18 Jul 2013 06:56:31 GMT + The Act of Killing (2012 Documentary) + http://www.imdb.com/title/tt2375605/ + http://www.imdb.com/title/tt2375605/ + + + + Thu, 18 Jul 2013 06:56:11 GMT + Thanks for Sharing (2012) + http://www.imdb.com/title/tt1932718/ + http://www.imdb.com/title/tt1932718/ + + + + Thu, 18 Jul 2013 06:55:46 GMT + Stuck in Love (2012) + http://www.imdb.com/title/tt2205697/ + http://www.imdb.com/title/tt2205697/ + + + + Thu, 18 Jul 2013 06:54:11 GMT + Some Girl(s) (2013) + http://www.imdb.com/title/tt2201221/ + http://www.imdb.com/title/tt2201221/ + + + + Thu, 18 Jul 2013 06:53:27 GMT + Snowpiercer (2013) + http://www.imdb.com/title/tt1706620/ + http://www.imdb.com/title/tt1706620/ + + + + Thu, 18 Jul 2013 06:51:58 GMT + Arbitrage (2012) + http://www.imdb.com/title/tt1764183/ + http://www.imdb.com/title/tt1764183/ + + + + Thu, 18 Jul 2013 06:39:19 GMT + Seventh Son (2014) + http://www.imdb.com/title/tt1121096/ + http://www.imdb.com/title/tt1121096/ + + + + Thu, 18 Jul 2013 06:38:57 GMT + Saving Mr. Banks (2013) + http://www.imdb.com/title/tt2140373/ + http://www.imdb.com/title/tt2140373/ + + + + Thu, 18 Jul 2013 06:38:14 GMT + Runner Runner (2013) + http://www.imdb.com/title/tt2364841/ + http://www.imdb.com/title/tt2364841/ + + + + Thu, 18 Jul 2013 06:37:47 GMT + Rigor Mortis (2013) + http://www.imdb.com/title/tt2771800/ + http://www.imdb.com/title/tt2771800/ + + + + Thu, 18 Jul 2013 06:37:24 GMT + Ride Along (2014) + http://www.imdb.com/title/tt1408253/ + http://www.imdb.com/title/tt1408253/ + + + + Thu, 18 Jul 2013 06:35:37 GMT + Rush (2013) + http://www.imdb.com/title/tt1979320/ + http://www.imdb.com/title/tt1979320/ + + + + Thu, 18 Jul 2013 06:35:07 GMT + Prisoners (2013) + http://www.imdb.com/title/tt1392214/ + http://www.imdb.com/title/tt1392214/ + + + + Thu, 18 Jul 2013 06:34:50 GMT + Prince Avalanche (2013) + http://www.imdb.com/title/tt2195548/ + http://www.imdb.com/title/tt2195548/ + + + + Thu, 18 Jul 2013 06:34:28 GMT + Populaire (2012) + http://www.imdb.com/title/tt2070776/ + http://www.imdb.com/title/tt2070776/ + + + + Thu, 18 Jul 2013 06:34:06 GMT + Pitch Perfect (2012) + http://www.imdb.com/title/tt1981677/ + http://www.imdb.com/title/tt1981677/ + + + + Thu, 18 Jul 2013 06:33:17 GMT + Percy Jackson: Sea of Monsters (2013) + http://www.imdb.com/title/tt1854564/ + http://www.imdb.com/title/tt1854564/ + + + + Thu, 18 Jul 2013 06:33:00 GMT + Percy Jackson & the Olympians: The Lightning Thief (2010) + http://www.imdb.com/title/tt0814255/ + http://www.imdb.com/title/tt0814255/ + + + + Thu, 18 Jul 2013 06:32:39 GMT + Pawn Shop Chronicles (2013) + http://www.imdb.com/title/tt1741243/ + http://www.imdb.com/title/tt1741243/ + + + + Thu, 18 Jul 2013 06:32:04 GMT + Pacific Rim (2013) + http://www.imdb.com/title/tt1663662/ + http://www.imdb.com/title/tt1663662/ + + + + Thu, 18 Jul 2013 06:31:41 GMT + Oz the Great and Powerful (2013) + http://www.imdb.com/title/tt1623205/ + http://www.imdb.com/title/tt1623205/ + + + + Thu, 18 Jul 2013 06:31:21 GMT + Out of the Furnace (2013) + http://www.imdb.com/title/tt1206543/ + http://www.imdb.com/title/tt1206543/ + + + + Thu, 18 Jul 2013 06:30:54 GMT + Anchorman: The Legend of Ron Burgundy (2004) + http://www.imdb.com/title/tt0357413/ + http://www.imdb.com/title/tt0357413/ + + + + Thu, 18 Jul 2013 06:29:59 GMT + Now You See Me (2013) + http://www.imdb.com/title/tt1670345/ + http://www.imdb.com/title/tt1670345/ + + + + Thu, 18 Jul 2013 06:29:33 GMT + No (2012) + http://www.imdb.com/title/tt2059255/ + http://www.imdb.com/title/tt2059255/ + + + + Thu, 18 Jul 2013 06:28:06 GMT + Monsters University (2013) + http://www.imdb.com/title/tt1453405/ + http://www.imdb.com/title/tt1453405/ + + + + Thu, 18 Jul 2013 06:26:52 GMT + Magic Magic (2013) + http://www.imdb.com/title/tt1929308/ + http://www.imdb.com/title/tt1929308/ + + + + Thu, 18 Jul 2013 06:25:38 GMT + Like Someone in Love (2012) + http://www.imdb.com/title/tt1843287/ + http://www.imdb.com/title/tt1843287/ + + + + Thu, 18 Jul 2013 06:24:48 GMT + Jug Face (2013) + http://www.imdb.com/title/tt2620736/ + http://www.imdb.com/title/tt2620736/ + + + + Thu, 18 Jul 2013 06:24:25 GMT + Inside Llewyn Davis (2013) + http://www.imdb.com/title/tt2042568/ + http://www.imdb.com/title/tt2042568/ + + + + Thu, 18 Jul 2013 06:23:39 GMT + I Give It a Year (2013) + http://www.imdb.com/title/tt2244901/ + http://www.imdb.com/title/tt2244901/ + + + + Thu, 18 Jul 2013 06:23:14 GMT + I Declare War (2012) + http://www.imdb.com/title/tt2133239/ + http://www.imdb.com/title/tt2133239/ + + + + Thu, 18 Jul 2013 06:22:51 GMT + How to Train Your Dragon 2 (2014) + http://www.imdb.com/title/tt1646971/ + http://www.imdb.com/title/tt1646971/ + + + + Thu, 18 Jul 2013 06:22:32 GMT + How to Make Money Selling Drugs (2012 Documentary) + http://www.imdb.com/title/tt1276962/ + http://www.imdb.com/title/tt1276962/ + + + + Thu, 18 Jul 2013 06:22:07 GMT + Hell Baby (2013) + http://www.imdb.com/title/tt2318527/ + http://www.imdb.com/title/tt2318527/ + + + + Thu, 18 Jul 2013 06:16:54 GMT + Hannah Arendt (2012) + http://www.imdb.com/title/tt1674773/ + http://www.imdb.com/title/tt1674773/ + + + + Thu, 18 Jul 2013 06:16:01 GMT + Gravity (2013) + http://www.imdb.com/title/tt1454468/ + http://www.imdb.com/title/tt1454468/ + + + + Thu, 18 Jul 2013 06:15:42 GMT + Getaway (2013) + http://www.imdb.com/title/tt2167202/ + http://www.imdb.com/title/tt2167202/ + + + + Thu, 18 Jul 2013 06:15:24 GMT + Generation Um... (2012) + http://www.imdb.com/title/tt1718158/ + http://www.imdb.com/title/tt1718158/ + + + + Thu, 18 Jul 2013 06:14:29 GMT + Fruitvale Station (2013) + http://www.imdb.com/title/tt2334649/ + http://www.imdb.com/title/tt2334649/ + + + + Thu, 18 Jul 2013 06:13:55 GMT + Free Birds (2013) + http://www.imdb.com/title/tt1621039/ + http://www.imdb.com/title/tt1621039/ + + + + Thu, 18 Jul 2013 06:13:32 GMT + Billy Elliot (2000) + http://www.imdb.com/title/tt0249462/ + http://www.imdb.com/title/tt0249462/ + + + + Thu, 18 Jul 2013 06:13:03 GMT + Filth (2013) + http://www.imdb.com/title/tt1450321/ + http://www.imdb.com/title/tt1450321/ + + + + Thu, 18 Jul 2013 06:12:44 GMT + Ferris Bueller's Day Off (1986) + http://www.imdb.com/title/tt0091042/ + http://www.imdb.com/title/tt0091042/ + + + + Thu, 18 Jul 2013 06:12:22 GMT + Fast & Furious 6 (2013) + http://www.imdb.com/title/tt1905041/ + http://www.imdb.com/title/tt1905041/ + + + + Thu, 18 Jul 2013 06:11:49 GMT + Extraction (2013) + http://www.imdb.com/title/tt2823574/ + http://www.imdb.com/title/tt2823574/ + + + + Thu, 18 Jul 2013 06:11:13 GMT + Europa Report (2013) + http://www.imdb.com/title/tt2051879/ + http://www.imdb.com/title/tt2051879/ + + + + Thu, 18 Jul 2013 06:10:52 GMT + Escape Plan (2013) + http://www.imdb.com/title/tt1211956/ + http://www.imdb.com/title/tt1211956/ + + + + Thu, 18 Jul 2013 06:10:30 GMT + Epic (2013) + http://www.imdb.com/title/tt0848537/ + http://www.imdb.com/title/tt0848537/ + + + + Thu, 18 Jul 2013 06:09:42 GMT + Elysium (2013) + http://www.imdb.com/title/tt1535108/ + http://www.imdb.com/title/tt1535108/ + + + + Thu, 18 Jul 2013 06:09:19 GMT + Drift (2013) + http://www.imdb.com/title/tt1714833/ + http://www.imdb.com/title/tt1714833/ + + + + Thu, 18 Jul 2013 06:08:49 GMT + Dragon (2011) + http://www.imdb.com/title/tt1718199/ + http://www.imdb.com/title/tt1718199/ + + + + Thu, 18 Jul 2013 06:08:46 GMT + Dragon (2011) + http://www.imdb.com/title/tt1718199/ + http://www.imdb.com/title/tt1718199/ + + + + Thu, 18 Jul 2013 06:07:34 GMT + Don Jon (2013) + http://www.imdb.com/title/tt2229499/ + http://www.imdb.com/title/tt2229499/ + + + + Thu, 18 Jul 2013 06:07:01 GMT + Despicable Me 2 (2013) + http://www.imdb.com/title/tt1690953/ + http://www.imdb.com/title/tt1690953/ + + + + Thu, 18 Jul 2013 05:55:51 GMT + All the Real Girls (2003) + http://www.imdb.com/title/tt0299458/ + http://www.imdb.com/title/tt0299458/ + + + + Thu, 18 Jul 2013 05:55:35 GMT + The Assassination of Jesse James by the Coward Robert Ford (2007) + http://www.imdb.com/title/tt0443680/ + http://www.imdb.com/title/tt0443680/ + + + + Thu, 18 Jul 2013 05:55:29 GMT + Lars and the Real Girl (2007) + http://www.imdb.com/title/tt0805564/ + http://www.imdb.com/title/tt0805564/ + + + + Thu, 18 Jul 2013 05:48:45 GMT + Cutie and the Boxer (2013 Documentary) + http://www.imdb.com/title/tt2355540/ + http://www.imdb.com/title/tt2355540/ + + + + Thu, 18 Jul 2013 05:48:23 GMT + Superbad (2007) + http://www.imdb.com/title/tt0829482/ + http://www.imdb.com/title/tt0829482/ + + + + Thu, 18 Jul 2013 05:48:03 GMT + Crystal Fairy & the Magical Cactus (2013) + http://www.imdb.com/title/tt2332579/ + http://www.imdb.com/title/tt2332579/ + + + + Thu, 18 Jul 2013 05:47:45 GMT + Cloudy with a Chance of Meatballs 2 (2013) + http://www.imdb.com/title/tt1985966/ + http://www.imdb.com/title/tt1985966/ + + + + Thu, 18 Jul 2013 05:47:26 GMT + Cloudy with a Chance of Meatballs (2009) + http://www.imdb.com/title/tt0844471/ + http://www.imdb.com/title/tt0844471/ + + + + Thu, 18 Jul 2013 05:47:03 GMT + Captain Phillips (2013) + http://www.imdb.com/title/tt1535109/ + http://www.imdb.com/title/tt1535109/ + + + + Thu, 18 Jul 2013 05:46:03 GMT + Byzantium (2012) + http://www.imdb.com/title/tt1531901/ + http://www.imdb.com/title/tt1531901/ + + + + Thu, 18 Jul 2013 05:45:36 GMT + Broken (2012) + http://www.imdb.com/title/tt1441940/ + http://www.imdb.com/title/tt1441940/ + + + + Thu, 18 Jul 2013 05:45:13 GMT + Blue Jasmine (2013) + http://www.imdb.com/title/tt2334873/ + http://www.imdb.com/title/tt2334873/ + + + + Thu, 18 Jul 2013 05:44:53 GMT + Before Midnight (2013) + http://www.imdb.com/title/tt2209418/ + http://www.imdb.com/title/tt2209418/ + + + + Thu, 18 Jul 2013 05:44:21 GMT + Dirty Pretty Things (2002) + http://www.imdb.com/title/tt0301199/ + http://www.imdb.com/title/tt0301199/ + + + + Thu, 18 Jul 2013 05:43:52 GMT + Inside Man (2006) + http://www.imdb.com/title/tt0454848/ + http://www.imdb.com/title/tt0454848/ + + + + Thu, 18 Jul 2013 05:43:40 GMT + About Time (2013) + http://www.imdb.com/title/tt2194499/ + http://www.imdb.com/title/tt2194499/ + + + + Thu, 18 Jul 2013 05:43:26 GMT + Adore (2013) + http://www.imdb.com/title/tt2103267/ + http://www.imdb.com/title/tt2103267/ + + + + Thu, 18 Jul 2013 05:43:07 GMT + After Earth (2013) + http://www.imdb.com/title/tt1815862/ + http://www.imdb.com/title/tt1815862/ + + + + Thu, 18 Jul 2013 05:42:45 GMT + The Kings of Summer (2013) + http://www.imdb.com/title/tt2179116/ + http://www.imdb.com/title/tt2179116/ + + + + Thu, 18 Jul 2013 05:42:37 GMT + Afternoon Delight (2013) + http://www.imdb.com/title/tt2312890/ + http://www.imdb.com/title/tt2312890/ + + + + Thu, 18 Jul 2013 05:42:29 GMT + Ain't Them Bodies Saints (2013) + http://www.imdb.com/title/tt2388637/ + http://www.imdb.com/title/tt2388637/ + + + + Thu, 18 Jul 2013 05:42:21 GMT + Alan Partridge (2013) + http://www.imdb.com/title/tt0469021/ + http://www.imdb.com/title/tt0469021/ + + + + Thu, 18 Jul 2013 05:42:12 GMT + And Now a Word from Our Sponsor (2013) + http://www.imdb.com/title/tt2094762/ + http://www.imdb.com/title/tt2094762/ + + + + diff --git a/src/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs index b2819434d..802744c96 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs @@ -45,6 +45,7 @@ namespace NzbDrone.Core.Test.IndexerTests return new IndexerResponse(new IndexerRequest(httpRequest), httpResponse); } + [Test] public void should_handle_relative_url() { diff --git a/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs b/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs new file mode 100644 index 000000000..9eb154b0f --- /dev/null +++ b/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs @@ -0,0 +1,34 @@ +using System.Linq; +using System.Text; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.NetImport; +using NzbDrone.Core.NetImport.RSSImport; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.NetImport +{ + public class RSSImportTest : CoreTest + { + private NetImportResponse CreateResponse(string url, string content) + { + var httpRequest = new HttpRequest(url); + var httpResponse = new HttpResponse(httpRequest, new HttpHeader(), Encoding.UTF8.GetBytes(content)); + + return new NetImportResponse(new NetImportRequest(httpRequest), httpResponse); + } + + + [Test] + public void should_handle_relative_url() + { + var xml = ReadAllText("Files/imdb_watchlist.xml"); + + var result = Subject.ParseResponse(CreateResponse("http://my.indexer.com/api?q=My+Favourite+Show", xml)); + + result.First().Title.Should().Be("Think Like a Man Too"); + } + } +} \ 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 70e548681..d65fed883 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -284,6 +284,7 @@ + @@ -409,6 +410,9 @@ sqlite3.dll Always + + Always + Always diff --git a/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs b/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs index 086464746..f324ee925 100644 --- a/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs +++ b/src/NzbDrone.Core/Datastore/Migration/119_create_netimport_table.cs @@ -10,7 +10,6 @@ namespace NzbDrone.Core.Datastore.Migration { Create.TableForModel("NetImport") .WithColumn("Enabled").AsBoolean() - .WithColumn("ProfileId").AsInt32() .WithColumn("Name").AsString().Unique() .WithColumn("Implementation").AsString() .WithColumn("Settings").AsString().Nullable(); diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs deleted file mode 100644 index ed5c06da4..000000000 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchList.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Serialization; -using FluentValidation.Results; -using NLog; -using NzbDrone.Common.Http; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Indexers; -using NzbDrone.Core.Indexers.PassThePopcorn; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.NetImport.IMDbWatchList -{ - public class IMDbWatchList : HttpNetImportBase - { - public override string Name => "IMDbWatchList"; - public override string Link => "http://rss.imdb.com/list/"; - public override int ProfileId => 1; - public override bool Enabled => true; - - public IMDbWatchList(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, configService, parsingService, logger) - { } - - public override INetImportRequestGenerator GetRequestGenerator() - { - return new IMDbWatchListRequestGenerator() { Settings = Settings }; - } - - public override IParseNetImportResponse GetParser() - { - return new IMDbWatchListParser(Settings); - } - } -} diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs b/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs deleted file mode 100644 index 20fb368ec..000000000 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListParser.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Newtonsoft.Json; -using NzbDrone.Core.NetImport.Exceptions; -using NzbDrone.Core.Tv; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; - -namespace NzbDrone.Core.NetImport.IMDbWatchList -{ - public class IMDbWatchListParser : IParseNetImportResponse - { - private readonly IMDbWatchListSettings _settings; - - public IMDbWatchListParser(IMDbWatchListSettings settings) - { - _settings = settings; - } - - public IList ParseResponse(NetImportResponse netImportResponse) - { - var torrentInfos = new List(); - - if (netImportResponse.HttpResponse.StatusCode != HttpStatusCode.OK) - { - throw new NetImportException(netImportResponse, - "Unexpected response status {0} code from API request", - netImportResponse.HttpResponse.StatusCode); - } - - var jsonResponse = JsonConvert.DeserializeObject(netImportResponse.Content); - - var responseData = jsonResponse.Movie; - if (responseData == null) - { - throw new NetImportException(netImportResponse, - "This list has no movies"); - } - - foreach (var result in responseData) - { - var title = Parser.Parser.ParseMovieTitle(result.Title, false); - - torrentInfos.Add(new Movie() - { - Title = title.MovieTitle, - Year = title.Year, - ProfileId = _settings.ProfileId, - ImdbId = Parser.Parser.ParseImdbId(result.Link) - }); - } - - return torrentInfos.ToArray(); - } - } -} diff --git a/src/NzbDrone.Core/NetImport/INetImport.cs b/src/NzbDrone.Core/NetImport/INetImport.cs index 82ec79629..e32eb2ed9 100644 --- a/src/NzbDrone.Core/NetImport/INetImport.cs +++ b/src/NzbDrone.Core/NetImport/INetImport.cs @@ -8,7 +8,6 @@ namespace NzbDrone.Core.NetImport { public interface INetImport : IProvider { - string Link { get; } bool Enabled { get; } IList Fetch(); diff --git a/src/NzbDrone.Core/NetImport/NetImportBase.cs b/src/NzbDrone.Core/NetImport/NetImportBase.cs index 0f3f6fc0f..165cd7375 100644 --- a/src/NzbDrone.Core/NetImport/NetImportBase.cs +++ b/src/NzbDrone.Core/NetImport/NetImportBase.cs @@ -21,8 +21,6 @@ namespace NzbDrone.Core.NetImport protected readonly Logger _logger; public abstract string Name { get; } - public abstract string Link { get; } - public abstract int ProfileId { get; } public abstract bool Enabled { get; } @@ -46,8 +44,6 @@ namespace NzbDrone.Core.NetImport yield return new NetImportDefinition { Name = GetType().Name, - Link = Link, - ProfileId = ProfileId, Enabled = config.Validate().IsValid && Enabled, Implementation = GetType().Name, Settings = config diff --git a/src/NzbDrone.Core/NetImport/NetImportDefinition.cs b/src/NzbDrone.Core/NetImport/NetImportDefinition.cs index b4af0c4a2..fb2313353 100644 --- a/src/NzbDrone.Core/NetImport/NetImportDefinition.cs +++ b/src/NzbDrone.Core/NetImport/NetImportDefinition.cs @@ -4,8 +4,6 @@ namespace NzbDrone.Core.NetImport { public class NetImportDefinition : ProviderDefinition { - public string Link { get; set; } - public int ProfileId { get; set; } public bool Enabled { get; set; } public override bool Enable => Enabled; } diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListAPI.cs b/src/NzbDrone.Core/NetImport/RSSImport/IMDbWatchListAPI.cs similarity index 96% rename from src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListAPI.cs rename to src/NzbDrone.Core/NetImport/RSSImport/IMDbWatchListAPI.cs index 73570c897..ccc618e9c 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListAPI.cs +++ b/src/NzbDrone.Core/NetImport/RSSImport/IMDbWatchListAPI.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace NzbDrone.Core.NetImport.IMDbWatchList +namespace NzbDrone.Core.NetImport.RSSImport { class IMDbWatchListAPI { diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListProxy.cs b/src/NzbDrone.Core/NetImport/RSSImport/IMDbWatchListProxy.cs similarity index 92% rename from src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListProxy.cs rename to src/NzbDrone.Core/NetImport/RSSImport/IMDbWatchListProxy.cs index 664919c52..0d46198f4 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListProxy.cs +++ b/src/NzbDrone.Core/NetImport/RSSImport/IMDbWatchListProxy.cs @@ -8,12 +8,12 @@ using NzbDrone.Core.Exceptions; using RestSharp; using NzbDrone.Core.Rest; -namespace NzbDrone.Core.NetImport.IMDbWatchList +namespace NzbDrone.Core.NetImport.RSSImport { public interface IIMDbWatchListProxy { void ImportMovies(string url); - ValidationFailure Test(IMDbWatchListSettings settings); + ValidationFailure Test(RSSImportSettings settings); } public class IMDbWatchListProxy : IIMDbWatchListProxy @@ -60,7 +60,7 @@ namespace NzbDrone.Core.NetImport.IMDbWatchList } } - public ValidationFailure Test(IMDbWatchListSettings settings) + public ValidationFailure Test(RSSImportSettings settings) { try { diff --git a/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs b/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs new file mode 100644 index 000000000..a945e4b77 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.PassThePopcorn; +using NzbDrone.Core.Parser; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.NetImport.RSSImport +{ + public class RSSImport : HttpNetImportBase + { + public override string Name => "RSSList"; + public override bool Enabled => true; + + public RSSImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) + { } + + public new virtual IEnumerable DefaultDefinitions + { + get + { + var config = (RSSImportSettings)new RSSImportSettings(); + config.Link = "https://rss.imdb.com/list/YOURLISTID"; + + yield return new NetImportDefinition + { + Name = GetType().Name, + Enabled = config.Validate().IsValid && Enabled, + Implementation = GetType().Name, + Settings = config + }; + } + } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new RSSImportRequestGenerator() { Settings = Settings }; + } + + public override IParseNetImportResponse GetParser() + { + return new RSSImportParser(Settings); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/RSSImport/RSSImportParser.cs b/src/NzbDrone.Core/NetImport/RSSImport/RSSImportParser.cs new file mode 100644 index 000000000..f3244beec --- /dev/null +++ b/src/NzbDrone.Core/NetImport/RSSImport/RSSImportParser.cs @@ -0,0 +1,236 @@ +using Newtonsoft.Json; +using NzbDrone.Core.NetImport.Exceptions; +using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Linq; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.NetImport.RSSImport +{ + public class RSSImportParser : IParseNetImportResponse + { + private readonly RSSImportSettings _settings; + private NetImportResponse _importResponse; + private readonly Logger _logger; + + private static readonly Regex ReplaceEntities = new Regex("&[a-z]+;", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public RSSImportParser(RSSImportSettings settings) + { + _settings = settings; + } + + public virtual IList ParseResponse(NetImportResponse importResponse) + { + _importResponse = importResponse; + + var movies = new List(); + + if (!PreProcess(importResponse)) + { + return movies; + } + + var document = LoadXmlDocument(importResponse); + var items = GetItems(document); + + foreach (var item in items) + { + try + { + var reportInfo = ProcessItem(item); + + movies.AddIfNotNull(reportInfo); + } + catch (Exception itemEx) + { + //itemEx.Data.Add("Item", item.Title()); + _logger.Error(itemEx, "An error occurred while processing feed item from " + importResponse.Request.Url); + } + } + + return movies; + } + + protected virtual XDocument LoadXmlDocument(NetImportResponse indexerResponse) + { + try + { + var content = indexerResponse.Content; + content = ReplaceEntities.Replace(content, ReplaceEntity); + + using (var xmlTextReader = XmlReader.Create(new StringReader(content), new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true })) + { + return XDocument.Load(xmlTextReader); + } + } + catch (XmlException ex) + { + var contentSample = indexerResponse.Content.Substring(0, Math.Min(indexerResponse.Content.Length, 512)); + _logger.Debug("Truncated response content (originally {0} characters): {1}", indexerResponse.Content.Length, contentSample); + + ex.Data.Add("ContentLength", indexerResponse.Content.Length); + ex.Data.Add("ContentSample", contentSample); + + throw; + } + } + + protected virtual string ReplaceEntity(Match match) + { + try + { + var character = WebUtility.HtmlDecode(match.Value); + return string.Concat("&#", (int)character[0], ";"); + } + catch + { + return match.Value; + } + } + + protected virtual Movie CreateNewMovie() + { + return new Movie(); + } + + protected virtual bool PreProcess(NetImportResponse indexerResponse) + { + if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + { + throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode); + } + + if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/html") && + indexerResponse.HttpRequest.Headers.Accept != null && !indexerResponse.HttpRequest.Headers.Accept.Contains("text/html")) + { + throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable."); + } + + return true; + } + + protected Movie ProcessItem(XElement item) + { + var releaseInfo = CreateNewMovie(); + + releaseInfo = ProcessItem(item, releaseInfo); + + //_logger.Trace("Parsed: {0}", releaseInfo.Title); + + return PostProcess(item, releaseInfo); + } + + protected virtual Movie ProcessItem(XElement item, Movie releaseInfo) + { + var result = Parser.Parser.ParseMovieTitle(GetTitle(item)); + + releaseInfo.Title = GetTitle(item); + + if (result != null) + { + releaseInfo.Title = result.MovieTitle; + releaseInfo.Year = result.Year; + releaseInfo.ImdbId = result.ImdbId; + } + + try + { + if (releaseInfo.ImdbId.IsNullOrWhiteSpace()) + { + releaseInfo.ImdbId = GetImdbId(item); + } + + } + catch (Exception) + { + _logger.Debug("Unable to extract Imdb Id :(."); + } + + return releaseInfo; + } + + protected virtual Movie PostProcess(XElement item, Movie releaseInfo) + { + return releaseInfo; + } + + protected virtual string GetTitle(XElement item) + { + return item.TryGetValue("title", "Unknown"); + } + + protected virtual DateTime GetPublishDate(XElement item) + { + var dateString = item.TryGetValue("pubDate"); + + if (dateString.IsNullOrWhiteSpace()) + { + throw new UnsupportedFeedException("Rss feed must have a pubDate element with a valid publish date."); + } + + return XElementExtensions.ParseDate(dateString); + } + + protected virtual string GetImdbId(XElement item) + { + var url = item.TryGetValue("link"); + if (url.IsNullOrWhiteSpace()) + { + return ""; + } + return Parser.Parser.ParseImdbId(url); + } + + protected IEnumerable GetItems(XDocument document) + { + var root = document.Root; + + if (root == null) + { + return Enumerable.Empty(); + } + + var channel = root.Element("channel"); + + if (channel == null) + { + return Enumerable.Empty(); + } + + return channel.Elements("item"); + } + + protected virtual string ParseUrl(string value) + { + if (value.IsNullOrWhiteSpace()) + { + return null; + } + + try + { + var url = _importResponse.HttpRequest.Url + new HttpUri(value); + + return url.FullUri; + } + catch (Exception ex) + { + _logger.Debug(ex, string.Format("Failed to parse Url {0}, ignoring.", value)); + return null; + } + } + } +} diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListRequestGenerator.cs b/src/NzbDrone.Core/NetImport/RSSImport/RSSImportRequestGenerator.cs similarity index 82% rename from src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListRequestGenerator.cs rename to src/NzbDrone.Core/NetImport/RSSImport/RSSImportRequestGenerator.cs index cfd92810b..a28068a07 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListRequestGenerator.cs +++ b/src/NzbDrone.Core/NetImport/RSSImport/RSSImportRequestGenerator.cs @@ -5,11 +5,11 @@ using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.IndexerSearch.Definitions; -namespace NzbDrone.Core.NetImport.IMDbWatchList +namespace NzbDrone.Core.NetImport.RSSImport { - public class IMDbWatchListRequestGenerator : INetImportRequestGenerator + public class RSSImportRequestGenerator : INetImportRequestGenerator { - public IMDbWatchListSettings Settings { get; set; } + public RSSImportSettings Settings { get; set; } public virtual NetImportPageableRequestChain GetMovies() { diff --git a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs b/src/NzbDrone.Core/NetImport/RSSImport/RSSImportSettings.cs similarity index 68% rename from src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs rename to src/NzbDrone.Core/NetImport/RSSImport/RSSImportSettings.cs index 6d10fa78c..f4fb783c7 100644 --- a/src/NzbDrone.Core/NetImport/IMDbWatchList/IMDbWatchListSettings.cs +++ b/src/NzbDrone.Core/NetImport/RSSImport/RSSImportSettings.cs @@ -4,16 +4,16 @@ using NzbDrone.Core.Profiles; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; -namespace NzbDrone.Core.NetImport.IMDbWatchList +namespace NzbDrone.Core.NetImport.RSSImport { - public class IMDbWatchListSettings : NetImportBaseSettings + public class RSSImportSettings : NetImportBaseSettings { //private const string helpLink = "https://imdb.com"; - public IMDbWatchListSettings() + public RSSImportSettings() { - Link = "http://rss.imdb.com/list/"; + Link = "http://rss.yoursite.com"; ProfileId = 1; } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 06402f783..a2607ca25 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -130,18 +130,18 @@ - - - + + + - - - + + + From 5aaba98c5785591dcb69666cc7ce075b9a7a5e3f Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Sat, 21 Jan 2017 20:37:08 +0100 Subject: [PATCH 09/52] Imdbid parsing works now from url --- src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs | 1 + src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs | 2 +- src/NzbDrone.Core/Parser/Parser.cs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs b/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs index 9eb154b0f..f724a1b8d 100644 --- a/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs +++ b/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs @@ -29,6 +29,7 @@ namespace NzbDrone.Core.Test.NetImport var result = Subject.ParseResponse(CreateResponse("http://my.indexer.com/api?q=My+Favourite+Show", xml)); result.First().Title.Should().Be("Think Like a Man Too"); + result.First().ImdbId.Should().Be("tt2239832"); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs b/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs index a945e4b77..28bb1ddee 100644 --- a/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs +++ b/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs @@ -31,7 +31,7 @@ namespace NzbDrone.Core.NetImport.RSSImport yield return new NetImportDefinition { - Name = GetType().Name, + Name = "IMDb Watchlist", Enabled = config.Validate().IsValid && Enabled, Implementation = GetType().Name, Settings = config diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index d133f4e86..80d0f6407 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -266,7 +266,7 @@ namespace NzbDrone.Core.Parser private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$", RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex ReportImdbId = new Regex(@"(?tt\d{9})", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex ReportImdbId = new Regex(@"(?tt\d{7})", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex SimpleTitleRegex = new Regex(@"(?:480[ip]|576[ip]|720[ip]|1080[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|(8|10)b(it)?)\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled); @@ -454,7 +454,7 @@ namespace NzbDrone.Core.Parser { if (match.Groups["imdbid"].Value != null) { - if (match.Groups["imdbid"].Length == 11) + if (match.Groups["imdbid"].Length == 9) { return match.Groups["imdbid"].Value; } From 4f37a36619600ebffaa5aedd8a1458e0a2aaf76e Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Sat, 21 Jan 2017 20:57:29 +0100 Subject: [PATCH 10/52] Updated HttpNetImporterBase. Still needs work to correctly handle failures. --- .../NetImport/RSSImportFixture.cs | 34 ++-- .../NetImport/RSSImportParserFixture.cs | 36 ++++ .../NzbDrone.Core.Test.csproj | 1 + .../NetImport/HttpNetImportBase.cs | 188 +++++++++++++++++- 4 files changed, 246 insertions(+), 13 deletions(-) create mode 100644 src/NzbDrone.Core.Test/NetImport/RSSImportParserFixture.cs diff --git a/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs b/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs index f724a1b8d..de13c40bf 100644 --- a/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs +++ b/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs @@ -1,32 +1,42 @@ -using System.Linq; -using System.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; using FluentAssertions; +using Moq; using NUnit.Framework; using NzbDrone.Common.Http; -using NzbDrone.Core.Indexers; +using NzbDrone.Core.Datastore; using NzbDrone.Core.NetImport; using NzbDrone.Core.NetImport.RSSImport; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.NetImport { - public class RSSImportTest : CoreTest + [TestFixture] + public class RSSImportFixture : CoreTest { - private NetImportResponse CreateResponse(string url, string content) - { - var httpRequest = new HttpRequest(url); - var httpResponse = new HttpResponse(httpRequest, new HttpHeader(), Encoding.UTF8.GetBytes(content)); - return new NetImportResponse(new NetImportRequest(httpRequest), httpResponse); + [SetUp] + public void Setup() + { + Subject.Definition = Subject.DefaultDefinitions.First(); } + private void GivenRecentFeedResponse(string rssXmlFile) + { + var recentFeed = ReadAllText(@"Files/" + rssXmlFile); + Mocker.GetMock() + .Setup(o => o.Execute(It.IsAny())) + .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); + } [Test] - public void should_handle_relative_url() + public void should_fetch_imdb_list() { - var xml = ReadAllText("Files/imdb_watchlist.xml"); + GivenRecentFeedResponse("imdb_watchlist.xml"); - var result = Subject.ParseResponse(CreateResponse("http://my.indexer.com/api?q=My+Favourite+Show", xml)); + var result = Subject.Fetch(); result.First().Title.Should().Be("Think Like a Man Too"); result.First().ImdbId.Should().Be("tt2239832"); diff --git a/src/NzbDrone.Core.Test/NetImport/RSSImportParserFixture.cs b/src/NzbDrone.Core.Test/NetImport/RSSImportParserFixture.cs new file mode 100644 index 000000000..cde97c653 --- /dev/null +++ b/src/NzbDrone.Core.Test/NetImport/RSSImportParserFixture.cs @@ -0,0 +1,36 @@ +using System.Linq; +using System.Text; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.NetImport; +using NzbDrone.Core.NetImport.RSSImport; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.NetImport +{ + public class RSSImportTest : CoreTest + { + private NetImportResponse CreateResponse(string url, string content) + { + var httpRequest = new HttpRequest(url); + var httpResponse = new HttpResponse(httpRequest, new HttpHeader(), Encoding.UTF8.GetBytes(content)); + + return new NetImportResponse(new NetImportRequest(httpRequest), httpResponse); + } + + + [Test] + public void should_parse_xml_of_imdb() + { + var xml = ReadAllText("Files/imdb_watchlist.xml"); + + var result = Subject.ParseResponse(CreateResponse("http://my.indexer.com/api?q=My+Favourite+Show", xml)); + + result.First().Title.Should().Be("Think Like a Man Too"); + result.First().ImdbId.Should().Be("tt2239832"); + } + } +} \ 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 d65fed883..a563c9f31 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -285,6 +285,7 @@ + diff --git a/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs b/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs index 9d5582a9c..5b82fca5f 100644 --- a/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs +++ b/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs @@ -26,6 +26,10 @@ namespace NzbDrone.Core.NetImport public override bool Enabled => true; + public bool SupportsPaging => PageSize > 0; + + public virtual int PageSize => 0; + public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2); public abstract INetImportRequestGenerator GetRequestGenerator(); @@ -39,7 +43,189 @@ namespace NzbDrone.Core.NetImport public override IList Fetch() { - return new List(); + var generator = GetRequestGenerator(); + + return FetchMovies(generator.GetMovies()); + } + + protected virtual IList FetchMovies(NetImportPageableRequestChain pageableRequestChain, bool isRecent = false) + { + var movies = new List(); + var url = string.Empty; + + var parser = GetParser(); + + try + { + var fullyUpdated = false; + Movie lastMovie = null; + if (isRecent) + { + //lastReleaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id); + } + + for (int i = 0; i < pageableRequestChain.Tiers; i++) + { + var pageableRequests = pageableRequestChain.GetTier(i); + + foreach (var pageableRequest in pageableRequests) + { + var pagedReleases = new List(); + + foreach (var request in pageableRequest) + { + url = request.Url.FullUri; + + var page = FetchPage(request, parser); + + pagedReleases.AddRange(page); + + if (isRecent && page.Any()) + { + if (lastMovie == null) + { + fullyUpdated = true; + break; + }/* + var oldestReleaseDate = page.Select(v => v.PublishDate).Min(); + if (oldestReleaseDate < lastReleaseInfo.PublishDate || page.Any(v => v.DownloadUrl == lastReleaseInfo.DownloadUrl)) + { + fullyUpdated = true; + break; + } + + if (pagedReleases.Count >= MaxNumResultsPerQuery && + oldestReleaseDate < DateTime.UtcNow - TimeSpan.FromHours(24)) + { + fullyUpdated = false; + break; + }*///update later + } + else if (pagedReleases.Count >= MaxNumResultsPerQuery) + { + break; + } + + if (!IsFullPage(page)) + { + break; + } + } + + movies.AddRange(pagedReleases); + } + + if (movies.Any()) + { + break; + } + } + + if (isRecent && !movies.Empty()) + { + var ordered = movies.OrderByDescending(v => v.Title).ToList(); + + lastMovie = ordered.First(); + //_indexerStatusService.UpdateRssSyncStatus(Definition.Id, lastReleaseInfo); + } + + //_indexerStatusService.RecordSuccess(Definition.Id); + } + catch (WebException webException) + { + if (webException.Status == WebExceptionStatus.NameResolutionFailure || + webException.Status == WebExceptionStatus.ConnectFailure) + { + //_indexerStatusService.RecordConnectionFailure(Definition.Id); + } + else + { + //_indexerStatusService.RecordFailure(Definition.Id); + } + + if (webException.Message.Contains("502") || webException.Message.Contains("503") || + webException.Message.Contains("timed out")) + { + _logger.Warn("{0} server is currently unavailable. {1} {2}", this, url, webException.Message); + } + else + { + _logger.Warn("{0} {1} {2}", this, url, webException.Message); + } + } + catch (HttpException httpException) + { + if ((int)httpException.Response.StatusCode == 429) + { + //_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1)); + _logger.Warn("API Request Limit reached for {0}", this); + } + else + { + //_indexerStatusService.RecordFailure(Definition.Id); + _logger.Warn("{0} {1}", this, httpException.Message); + } + } + catch (RequestLimitReachedException) + { + //_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1)); + _logger.Warn("API Request Limit reached for {0}", this); + } + catch (ApiKeyException) + { + //_indexerStatusService.RecordFailure(Definition.Id); + _logger.Warn("Invalid API Key for {0} {1}", this, url); + } + catch (CloudFlareCaptchaException ex) + { + //_indexerStatusService.RecordFailure(Definition.Id); + if (ex.IsExpired) + { + _logger.Error(ex, "Expired CAPTCHA token for {0}, please refresh in indexer settings.", this); + } + else + { + _logger.Error(ex, "CAPTCHA token required for {0}, check indexer settings.", this); + } + } + catch (IndexerException ex) + { + //_indexerStatusService.RecordFailure(Definition.Id); + var message = string.Format("{0} - {1}", ex.Message, url); + _logger.Warn(ex, message); + } + catch (Exception feedEx) + { + //_indexerStatusService.RecordFailure(Definition.Id); + feedEx.Data.Add("FeedUrl", url); + _logger.Error(feedEx, "An error occurred while processing feed. " + url); + } + + return movies; + } + + protected virtual bool IsFullPage(IList page) + { + return PageSize != 0 && page.Count >= PageSize; + } + + protected virtual IList FetchPage(NetImportRequest request, IParseNetImportResponse parser) + { + var response = FetchIndexerResponse(request); + + return parser.ParseResponse(response).ToList(); + } + + protected virtual NetImportResponse FetchIndexerResponse(NetImportRequest request) + { + _logger.Debug("Downloading List " + request.HttpRequest.ToString(false)); + + if (request.HttpRequest.RateLimit < RateLimit) + { + request.HttpRequest.RateLimit = RateLimit; + } + + return new NetImportResponse(request, _httpClient.Execute(request.HttpRequest)); } protected override void Test(List failures) From 2c52795822277d3dbfa261649fd7c25fc48a9836 Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Sat, 21 Jan 2017 21:09:02 +0100 Subject: [PATCH 11/52] Add base for netimport api. Still nothing on the UI side. --- src/NzbDrone.Api/NetImport/NetImportModule.cs | 32 +++++++++ .../NetImport/NetImportResource.cs | 10 +++ .../NetImport/NetImportFactory.cs | 70 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 src/NzbDrone.Api/NetImport/NetImportModule.cs create mode 100644 src/NzbDrone.Api/NetImport/NetImportResource.cs create mode 100644 src/NzbDrone.Core/NetImport/NetImportFactory.cs diff --git a/src/NzbDrone.Api/NetImport/NetImportModule.cs b/src/NzbDrone.Api/NetImport/NetImportModule.cs new file mode 100644 index 000000000..b128b061d --- /dev/null +++ b/src/NzbDrone.Api/NetImport/NetImportModule.cs @@ -0,0 +1,32 @@ +using NzbDrone.Core.NetImport; + +namespace NzbDrone.Api.NetImport +{ + public class NetImportModule : ProviderModuleBase + { + public NetImportModule(NetImportFactory indexerFactory) + : base(indexerFactory, "indexer") + { + } + + protected override void MapToResource(NetImportResource resource, NetImportDefinition definition) + { + base.MapToResource(resource, definition); + + resource.Enabled = definition.Enabled; + } + + protected override void MapToModel(NetImportDefinition definition, NetImportResource resource) + { + base.MapToModel(definition, resource); + + resource.Enabled = definition.Enabled; + } + + protected override void Validate(NetImportDefinition definition, bool includeWarnings) + { + if (!definition.Enable) return; + base.Validate(definition, includeWarnings); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/NetImport/NetImportResource.cs b/src/NzbDrone.Api/NetImport/NetImportResource.cs new file mode 100644 index 000000000..880b95ef0 --- /dev/null +++ b/src/NzbDrone.Api/NetImport/NetImportResource.cs @@ -0,0 +1,10 @@ +using NzbDrone.Core.NetImport; + +namespace NzbDrone.Api.NetImport +{ + public class NetImportResource : ProviderResource + { + public bool Enabled { get; set; } + public bool EnableSearch { get; set; } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/NetImportFactory.cs b/src/NzbDrone.Core/NetImport/NetImportFactory.cs new file mode 100644 index 000000000..489d8682e --- /dev/null +++ b/src/NzbDrone.Core/NetImport/NetImportFactory.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common.Composition; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.NetImport +{ + public interface INetImportFactory : IProviderFactory + { + List Enabled(); + } + + public class NetImportFactory : ProviderFactory, INetImportFactory + { + //private readonly IIndexerStatusService _indexerStatusService; + private readonly INetImportRepository _providerRepository; + private readonly Logger _logger; + + public NetImportFactory(//IIndexerStatusService indexerStatusService, + INetImportRepository providerRepository, + IEnumerable providers, + IContainer container, + IEventAggregator eventAggregator, + Logger logger) + : base(providerRepository, providers, container, eventAggregator, logger) + { + //_indexerStatusService = indexerStatusService; + _providerRepository = providerRepository; + _logger = logger; + } + + protected override List Active() + { + return base.Active().Where(c => c.Enabled).ToList(); + } + + public override void SetProviderCharacteristics(INetImport provider, NetImportDefinition definition) + { + base.SetProviderCharacteristics(provider, definition); + } + + public List Enabled() + { + var enabledIndexers = GetAvailableProviders().Where(n => ((NetImportDefinition)n.Definition).Enabled); + + var indexers = FilterBlockedIndexers(enabledIndexers); + + return indexers.ToList(); + } + + private IEnumerable FilterBlockedIndexers(IEnumerable indexers) + { + //var blockedIndexers = _indexerStatusService.GetBlockedIndexers().ToDictionary(v => v.IndexerId, v => v); + + foreach (var indexer in indexers) + { + /*IndexerStatus blockedIndexerStatus; + if (blockedIndexers.TryGetValue(indexer.Definition.Id, out blockedIndexerStatus)) + { + _logger.Debug("Temporarily ignoring indexer {0} till {1} due to recent failures.", indexer.Definition.Name, blockedIndexerStatus.DisabledTill.Value.ToLocalTime()); + continue; + }*/ + + yield return indexer; + } + } + } +} \ No newline at end of file From 463d85e4798ff169fdd55344f0331aed40fb7c2b Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Sat, 21 Jan 2017 21:28:14 +0100 Subject: [PATCH 12/52] Basis of UI Update. --- .../Config/NetImportConfigModule.cs | 20 + .../Config/NetImportConfigResource.cs | 19 + src/NzbDrone.Api/NetImport/NetImportModule.cs | 2 +- src/NzbDrone.Api/NzbDrone.Api.csproj | 4 + src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + .../NetImport/Add/IndexerAddCollectionView.js | 9 + .../Add/IndexerAddCollectionViewTemplate.hbs | 18 + .../NetImport/Add/IndexerAddItemView.js | 52 ++ .../Add/IndexerAddItemViewTemplate.hbs | 30 ++ .../NetImport/Add/IndexerSchemaModal.js | 39 ++ .../NetImport/Delete/IndexerDeleteView.js | 19 + .../Delete/IndexerDeleteViewTemplate.hbs | 13 + .../NetImport/Edit/IndexerEditView.js | 122 +++++ .../Edit/IndexerEditViewTemplate.hbs | 92 ++++ .../NetImport/IndexerCollectionView.js | 25 + .../IndexerCollectionViewTemplate.hbs | 16 + src/UI/Settings/NetImport/IndexerItemView.js | 24 + .../NetImport/IndexerItemViewTemplate.hbs | 27 + src/UI/Settings/NetImport/IndexerLayout.js | 30 ++ .../NetImport/IndexerLayoutTemplate.hbs | 5 + .../Settings/NetImport/NetImportCollection.js | 13 + src/UI/Settings/NetImport/NetImportModel.js | 3 + .../NetImport/NetImportSettingsModel.js | 7 + .../NetImport/Options/IndexerOptionsView.js | 12 + .../Options/IndexerOptionsViewTemplate.hbs | 40 ++ .../Restriction/RestrictionCollection.js | 7 + .../Restriction/RestrictionCollectionView.js | 26 + .../RestrictionCollectionViewTemplate.hbs | 24 + .../Restriction/RestrictionDeleteView.js | 19 + .../RestrictionDeleteViewTemplate.hbs | 13 + .../Restriction/RestrictionEditView.js | 55 ++ .../RestrictionEditViewTemplate.hbs | 60 +++ .../Restriction/RestrictionItemView.js | 28 ++ .../RestrictionItemViewTemplate.hbs | 12 + .../NetImport/Restriction/RestrictionModel.js | 4 + src/UI/Settings/NetImport/indexers.less | 33 ++ src/UI/Settings/SettingsLayout.js | 469 +++++++++--------- src/UI/Settings/SettingsLayoutTemplate.hbs | 82 +-- 38 files changed, 1207 insertions(+), 267 deletions(-) create mode 100644 src/NzbDrone.Api/Config/NetImportConfigModule.cs create mode 100644 src/NzbDrone.Api/Config/NetImportConfigResource.cs create mode 100644 src/UI/Settings/NetImport/Add/IndexerAddCollectionView.js create mode 100644 src/UI/Settings/NetImport/Add/IndexerAddCollectionViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/Add/IndexerAddItemView.js create mode 100644 src/UI/Settings/NetImport/Add/IndexerAddItemViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/Add/IndexerSchemaModal.js create mode 100644 src/UI/Settings/NetImport/Delete/IndexerDeleteView.js create mode 100644 src/UI/Settings/NetImport/Delete/IndexerDeleteViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/Edit/IndexerEditView.js create mode 100644 src/UI/Settings/NetImport/Edit/IndexerEditViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/IndexerCollectionView.js create mode 100644 src/UI/Settings/NetImport/IndexerCollectionViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/IndexerItemView.js create mode 100644 src/UI/Settings/NetImport/IndexerItemViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/IndexerLayout.js create mode 100644 src/UI/Settings/NetImport/IndexerLayoutTemplate.hbs create mode 100644 src/UI/Settings/NetImport/NetImportCollection.js create mode 100644 src/UI/Settings/NetImport/NetImportModel.js create mode 100644 src/UI/Settings/NetImport/NetImportSettingsModel.js create mode 100644 src/UI/Settings/NetImport/Options/IndexerOptionsView.js create mode 100644 src/UI/Settings/NetImport/Options/IndexerOptionsViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionCollection.js create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionCollectionView.js create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionCollectionViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionDeleteView.js create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionDeleteViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionEditView.js create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionEditViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionItemView.js create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionItemViewTemplate.hbs create mode 100644 src/UI/Settings/NetImport/Restriction/RestrictionModel.js create mode 100644 src/UI/Settings/NetImport/indexers.less diff --git a/src/NzbDrone.Api/Config/NetImportConfigModule.cs b/src/NzbDrone.Api/Config/NetImportConfigModule.cs new file mode 100644 index 000000000..3cd194116 --- /dev/null +++ b/src/NzbDrone.Api/Config/NetImportConfigModule.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using NzbDrone.Api.Validation; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Api.Config +{ + public class NetImportConfigModule : NzbDroneConfigModule + { + + public NetImportConfigModule(IConfigService configService) + : base(configService) + { + } + + protected override NetImportConfigResource ToResource(IConfigService model) + { + return NetImportConfigResourceMapper.ToResource(model); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Config/NetImportConfigResource.cs b/src/NzbDrone.Api/Config/NetImportConfigResource.cs new file mode 100644 index 000000000..7b32aefe3 --- /dev/null +++ b/src/NzbDrone.Api/Config/NetImportConfigResource.cs @@ -0,0 +1,19 @@ +using NzbDrone.Api.REST; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Api.Config +{ + public class NetImportConfigResource : RestResource + { + } + + public static class NetImportConfigResourceMapper + { + public static NetImportConfigResource ToResource(IConfigService model) + { + return new NetImportConfigResource + { + }; + } + } +} diff --git a/src/NzbDrone.Api/NetImport/NetImportModule.cs b/src/NzbDrone.Api/NetImport/NetImportModule.cs index b128b061d..6130191d7 100644 --- a/src/NzbDrone.Api/NetImport/NetImportModule.cs +++ b/src/NzbDrone.Api/NetImport/NetImportModule.cs @@ -5,7 +5,7 @@ namespace NzbDrone.Api.NetImport public class NetImportModule : ProviderModuleBase { public NetImportModule(NetImportFactory indexerFactory) - : base(indexerFactory, "indexer") + : base(indexerFactory, "netimport") { } diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 6a61d84fc..47a433a54 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -109,6 +109,8 @@ + + @@ -121,6 +123,8 @@ + + diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index a2607ca25..c8dd24853 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -125,6 +125,7 @@ + diff --git a/src/UI/Settings/NetImport/Add/IndexerAddCollectionView.js b/src/UI/Settings/NetImport/Add/IndexerAddCollectionView.js new file mode 100644 index 000000000..5a4102cf2 --- /dev/null +++ b/src/UI/Settings/NetImport/Add/IndexerAddCollectionView.js @@ -0,0 +1,9 @@ +var ThingyAddCollectionView = require('../../ThingyAddCollectionView'); +var ThingyHeaderGroupView = require('../../ThingyHeaderGroupView'); +var AddItemView = require('./IndexerAddItemView'); + +module.exports = ThingyAddCollectionView.extend({ + itemView : ThingyHeaderGroupView.extend({ itemView : AddItemView }), + itemViewContainer : '.add-indexer .items', + template : 'Settings/Indexers/Add/IndexerAddCollectionViewTemplate' +}); \ No newline at end of file diff --git a/src/UI/Settings/NetImport/Add/IndexerAddCollectionViewTemplate.hbs b/src/UI/Settings/NetImport/Add/IndexerAddCollectionViewTemplate.hbs new file mode 100644 index 000000000..3d581b5e4 --- /dev/null +++ b/src/UI/Settings/NetImport/Add/IndexerAddCollectionViewTemplate.hbs @@ -0,0 +1,18 @@ + diff --git a/src/UI/Settings/NetImport/Add/IndexerAddItemView.js b/src/UI/Settings/NetImport/Add/IndexerAddItemView.js new file mode 100644 index 000000000..3a8b0493a --- /dev/null +++ b/src/UI/Settings/NetImport/Add/IndexerAddItemView.js @@ -0,0 +1,52 @@ +var _ = require('underscore'); +var $ = require('jquery'); +var AppLayout = require('../../../AppLayout'); +var Marionette = require('marionette'); +var EditView = require('../Edit/IndexerEditView'); + +module.exports = Marionette.ItemView.extend({ + template : 'Settings/Indexers/Add/IndexerAddItemViewTemplate', + tagName : 'li', + className : 'add-thingy-item', + + events : { + 'click .x-preset' : '_addPreset', + 'click' : '_add' + }, + + initialize : function(options) { + this.targetCollection = options.targetCollection; + }, + + _addPreset : function(e) { + var presetName = $(e.target).closest('.x-preset').attr('data-id'); + var presetData = _.where(this.model.get('presets'), { name : presetName })[0]; + + this.model.set(presetData); + + this._openEdit(); + }, + + _add : function(e) { + if ($(e.target).closest('.btn,.btn-group').length !== 0 && $(e.target).closest('.x-custom').length === 0) { + return; + } + + this._openEdit(); + }, + + _openEdit : function() { + this.model.set({ + id : undefined, + enableRss : this.model.get('supportsRss'), + enableSearch : this.model.get('supportsSearch') + }); + + var editView = new EditView({ + model : this.model, + targetCollection : this.targetCollection + }); + + AppLayout.modalRegion.show(editView); + } +}); \ No newline at end of file diff --git a/src/UI/Settings/NetImport/Add/IndexerAddItemViewTemplate.hbs b/src/UI/Settings/NetImport/Add/IndexerAddItemViewTemplate.hbs new file mode 100644 index 000000000..40bcb4391 --- /dev/null +++ b/src/UI/Settings/NetImport/Add/IndexerAddItemViewTemplate.hbs @@ -0,0 +1,30 @@ +
+
+ {{implementationName}} +
+
+ {{#if_gt presets.length compare=0}} + +
+ + +
+ {{/if_gt}} + {{#if infoLink}} + + + + {{/if}} +
+
\ No newline at end of file diff --git a/src/UI/Settings/NetImport/Add/IndexerSchemaModal.js b/src/UI/Settings/NetImport/Add/IndexerSchemaModal.js new file mode 100644 index 000000000..52b430e89 --- /dev/null +++ b/src/UI/Settings/NetImport/Add/IndexerSchemaModal.js @@ -0,0 +1,39 @@ +var _ = require('underscore'); +var AppLayout = require('../../../AppLayout'); +var Backbone = require('backbone'); +var SchemaCollection = require('../IndexerCollection'); +var AddCollectionView = require('./IndexerAddCollectionView'); + +module.exports = { + open : function(collection) { + var schemaCollection = new SchemaCollection(); + var originalUrl = schemaCollection.url; + schemaCollection.url = schemaCollection.url + '/schema'; + schemaCollection.fetch(); + schemaCollection.url = originalUrl; + + var groupedSchemaCollection = new Backbone.Collection(); + + schemaCollection.on('sync', function() { + + var groups = schemaCollection.groupBy(function(model, iterator) { + return model.get('protocol'); + }); + var modelCollection = _.map(groups, function(values, key, list) { + return { + "header" : key, + collection : values + }; + }); + + groupedSchemaCollection.reset(modelCollection); + }); + + var view = new AddCollectionView({ + collection : groupedSchemaCollection, + targetCollection : collection + }); + + AppLayout.modalRegion.show(view); + } +}; \ No newline at end of file diff --git a/src/UI/Settings/NetImport/Delete/IndexerDeleteView.js b/src/UI/Settings/NetImport/Delete/IndexerDeleteView.js new file mode 100644 index 000000000..58e7e3eb5 --- /dev/null +++ b/src/UI/Settings/NetImport/Delete/IndexerDeleteView.js @@ -0,0 +1,19 @@ +var vent = require('vent'); +var Marionette = require('marionette'); + +module.exports = Marionette.ItemView.extend({ + template : 'Settings/Indexers/Delete/IndexerDeleteViewTemplate', + + events : { + 'click .x-confirm-delete' : '_delete' + }, + + _delete : function() { + this.model.destroy({ + wait : true, + success : function() { + vent.trigger(vent.Commands.CloseModalCommand); + } + }); + } +}); \ No newline at end of file diff --git a/src/UI/Settings/NetImport/Delete/IndexerDeleteViewTemplate.hbs b/src/UI/Settings/NetImport/Delete/IndexerDeleteViewTemplate.hbs new file mode 100644 index 000000000..c5c7ad7db --- /dev/null +++ b/src/UI/Settings/NetImport/Delete/IndexerDeleteViewTemplate.hbs @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/src/UI/Settings/NetImport/Edit/IndexerEditView.js b/src/UI/Settings/NetImport/Edit/IndexerEditView.js new file mode 100644 index 000000000..616c863a7 --- /dev/null +++ b/src/UI/Settings/NetImport/Edit/IndexerEditView.js @@ -0,0 +1,122 @@ +var _ = require('underscore'); +var $ = require('jquery'); +var vent = require('vent'); +var Marionette = require('marionette'); +var DeleteView = require('../Delete/IndexerDeleteView'); +var AsModelBoundView = require('../../../Mixins/AsModelBoundView'); +var AsValidatedView = require('../../../Mixins/AsValidatedView'); +var AsEditModalView = require('../../../Mixins/AsEditModalView'); +require('../../../Form/FormBuilder'); +require('../../../Mixins/AutoComplete'); +require('bootstrap'); + +var view = Marionette.ItemView.extend({ + template : 'Settings/Indexers/Edit/IndexerEditViewTemplate', + + events : { + 'click .x-back' : '_back', + 'click .x-captcha-refresh' : '_onRefreshCaptcha' + }, + + _deleteView : DeleteView, + + initialize : function(options) { + this.targetCollection = options.targetCollection; + }, + + _onAfterSave : function() { + this.targetCollection.add(this.model, { merge : true }); + vent.trigger(vent.Commands.CloseModalCommand); + }, + + _onAfterSaveAndAdd : function() { + this.targetCollection.add(this.model, { merge : true }); + + require('../Add/IndexerSchemaModal').open(this.targetCollection); + }, + + _back : function() { + if (this.model.isNew()) { + this.model.destroy(); + } + + require('../Add/IndexerSchemaModal').open(this.targetCollection); + }, + + _onRefreshCaptcha : function(event) { + var self = this; + + var target = $(event.target).parents('.input-group'); + + this.ui.indicator.show(); + + this.model.requestAction("checkCaptcha") + .then(function(result) { + if (!result.captchaRequest) { + self.model.setFieldValue('CaptchaToken', ''); + + return result; + } + + return self._showCaptcha(target, result.captchaRequest); + }) + .always(function() { + self.ui.indicator.hide(); + }); + }, + + _showCaptcha : function(target, captchaRequest) { + var self = this; + + var widget = $('
').insertAfter(target); + + return this._loadRecaptchaWidget(widget[0], captchaRequest.siteKey, captchaRequest.secretToken) + .then(function(captchaResponse) { + target.parents('.form-group').removeAllErrors(); + widget.remove(); + + var queryParams = { + responseUrl : captchaRequest.responseUrl, + ray : captchaRequest.ray, + captchaResponse: captchaResponse + }; + + return self.model.requestAction("getCaptchaCookie", queryParams); + }) + .then(function(response) { + self.model.setFieldValue('CaptchaToken', response.captchaToken); + }); + }, + + _loadRecaptchaWidget : function(widget, sitekey, stoken) { + var promise = $.Deferred(); + + var renderWidget = function() { + window.grecaptcha.render(widget, { + 'sitekey' : sitekey, + 'stoken' : stoken, + 'callback' : promise.resolve + }); + }; + + if (window.grecaptcha) { + renderWidget(); + } else { + window.grecaptchaLoadCallback = function() { + delete window.grecaptchaLoadCallback; + renderWidget(); + }; + + $.getScript('https://www.google.com/recaptcha/api.js?onload=grecaptchaLoadCallback&render=explicit') + .fail(function() { promise.reject(); }); + } + + return promise; + } +}); + +AsModelBoundView.call(view); +AsValidatedView.call(view); +AsEditModalView.call(view); + +module.exports = view; \ No newline at end of file diff --git a/src/UI/Settings/NetImport/Edit/IndexerEditViewTemplate.hbs b/src/UI/Settings/NetImport/Edit/IndexerEditViewTemplate.hbs new file mode 100644 index 000000000..acfb62cbb --- /dev/null +++ b/src/UI/Settings/NetImport/Edit/IndexerEditViewTemplate.hbs @@ -0,0 +1,92 @@ + +
+ + +
+
+ + + + + +
+
+
+
From 8b3b46b724631622b26e771aedc0741b04f2f4cc Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Mon, 23 Jan 2017 16:04:52 +0100 Subject: [PATCH 34/52] Added easy to use List Selection for manual import use later. The place where this resides will change. --- src/UI/AddMovies/AddMoviesLayout.js | 111 ++++++++++-------- src/UI/AddMovies/AddMoviesLayoutTemplate.hbs | 76 ++++++------ .../NetImport/ListSelectionPartial.hbs | 10 ++ 3 files changed, 114 insertions(+), 83 deletions(-) create mode 100644 src/UI/Settings/NetImport/ListSelectionPartial.hbs diff --git a/src/UI/AddMovies/AddMoviesLayout.js b/src/UI/AddMovies/AddMoviesLayout.js index 30cbc74b3..127e0f0c0 100644 --- a/src/UI/AddMovies/AddMoviesLayout.js +++ b/src/UI/AddMovies/AddMoviesLayout.js @@ -5,57 +5,70 @@ var RootFolderLayout = require('./RootFolders/RootFolderLayout'); var ExistingMoviesCollectionView = require('./Existing/AddExistingMovieCollectionView'); var AddMoviesView = require('./AddMoviesView'); var ProfileCollection = require('../Profile/ProfileCollection'); +var ListCollection = require("../Settings/NetImport/NetImportCollection"); var RootFolderCollection = require('./RootFolders/RootFolderCollection'); require('../Movies/MoviesCollection'); module.exports = Marionette.Layout.extend({ - template : 'AddMovies/AddMoviesLayoutTemplate', - - regions : { - workspace : '#add-movies-workspace' - }, - - events : { - 'click .x-import' : '_importMovies', - 'click .x-add-new' : '_addMovies', - 'click .x-show-existing' : '_toggleExisting' - }, - - attributes : { - id : 'add-movies-screen' - }, - - initialize : function() { - ProfileCollection.fetch(); - RootFolderCollection.fetch().done(function() { - RootFolderCollection.synced = true; - }); - }, - - _toggleExisting : function(e) { - var showExisting = e.target.checked; - - vent.trigger(vent.Commands.ShowExistingCommand, { - showExisting: showExisting - }); - }, - - onShow : function() { - this.workspace.show(new AddMoviesView()); - }, - - _folderSelected : function(options) { - vent.trigger(vent.Commands.CloseModalCommand); - this.workspace.show(new ExistingMoviesCollectionView({ model : options.model })); - }, - - _importMovies : function() { - this.rootFolderLayout = new RootFolderLayout(); - this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected); - AppLayout.modalRegion.show(this.rootFolderLayout); - }, - - _addMovies : function() { - this.workspace.show(new AddMoviesView()); - } + template : 'AddMovies/AddMoviesLayoutTemplate', + + regions : { + workspace : '#add-movies-workspace' + }, + + events : { + 'click .x-import' : '_importMovies', + 'click .x-add-new' : '_addMovies', + 'click .x-show-existing' : '_toggleExisting' + }, + + attributes : { + id : 'add-movies-screen' + }, + + initialize : function() { + ProfileCollection.fetch(); + RootFolderCollection.fetch().done(function() { + RootFolderCollection.synced = true; + }); + this.templateHelpers = {} + this.listCollection = new ListCollection(); + this.templateHelpers.lists = this.listCollection.toJSON(); + + this.listenTo(this.listCollection, 'all', this._listsUpdated); + this.listCollection.fetch(); + + }, + + _toggleExisting : function(e) { + var showExisting = e.target.checked; + + vent.trigger(vent.Commands.ShowExistingCommand, { + showExisting: showExisting + }); + }, + + onShow : function() { + this.workspace.show(new AddMoviesView()); + }, + + _listsUpdated : function() { + this.templateHelpers.lists = this.listCollection.toJSON(); + this.render(); + }, + + _folderSelected : function(options) { + vent.trigger(vent.Commands.CloseModalCommand); + this.workspace.show(new ExistingMoviesCollectionView({ model : options.model })); + }, + + _importMovies : function() { + this.rootFolderLayout = new RootFolderLayout(); + this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected); + AppLayout.modalRegion.show(this.rootFolderLayout); + }, + + _addMovies : function() { + this.workspace.show(new AddMoviesView()); + } }); diff --git a/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs b/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs index c2f9ce419..c4a375918 100644 --- a/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs +++ b/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs @@ -1,43 +1,51 @@
-
-
- - -
-
+
+
+ + +
+
-
-
-
- +
+
+
+ -
-
-
+ + + +
+
+
+ +
+ + +
+ {{> ListSelectionPartial lists}} +
+
+
+
-
-
-
+
+
+
diff --git a/src/UI/Settings/NetImport/ListSelectionPartial.hbs b/src/UI/Settings/NetImport/ListSelectionPartial.hbs new file mode 100644 index 000000000..dd93cc17d --- /dev/null +++ b/src/UI/Settings/NetImport/ListSelectionPartial.hbs @@ -0,0 +1,10 @@ + From 87da542758bbd8ea4d90553c7e05be069f073227 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Mon, 23 Jan 2017 12:54:31 -0500 Subject: [PATCH 35/52] Add import from http://movies.stevenlu.com/ --- .../NetImport/StevenLu/StevenLuAPI.cs | 18 ++++ .../NetImport/StevenLu/StevenLuImport.cs | 36 ++++++++ .../NetImport/StevenLu/StevenLuParser.cs | 82 +++++++++++++++++++ .../StevenLu/StevenLuRequestGenerator.cs | 28 +++++++ .../NetImport/StevenLu/StevenLuSettings.cs | 22 +++++ 5 files changed, 186 insertions(+) create mode 100644 src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs create mode 100644 src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs create mode 100644 src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs create mode 100644 src/NzbDrone.Core/NetImport/StevenLu/StevenLuRequestGenerator.cs create mode 100644 src/NzbDrone.Core/NetImport/StevenLu/StevenLuSettings.cs diff --git a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs new file mode 100644 index 000000000..e56655278 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Windows.Forms; +using System.Xml.Serialization; + +namespace NzbDrone.Core.NetImport.StevenLu +{ + public class StevenLuResponse + { + public Movie[] Movie { get; set; } + } + + public class Movie + { + public string title { get; set; } + public string imdb_id { get; set; } + public string poster_url { get; set; } + } +} diff --git a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs new file mode 100644 index 000000000..1f3b76039 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.PassThePopcorn; +using NzbDrone.Core.Parser; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.NetImport.StevenLu +{ + public class StevenLuImport : HttpNetImportBase + { + public override string Name => "Popular movies from StevenLu"; + public override bool Enabled => true; + public override bool EnableAuto => true; + + public StevenLuImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) + { } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new StevenLuRequestGenerator() { Settings = Settings }; + } + + public override IParseNetImportResponse GetParser() + { + return new StevenLuParser(Settings); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs new file mode 100644 index 000000000..a73c190aa --- /dev/null +++ b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs @@ -0,0 +1,82 @@ +using Newtonsoft.Json; +using NzbDrone.Core.NetImport.Exceptions; +using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Forms; +using System.Xml; +using System.Xml.Linq; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.NetImport.StevenLu +{ + public class StevenLuParser : IParseNetImportResponse + { + private readonly StevenLuSettings _settings; + private NetImportResponse _importResponse; + private readonly Logger _logger; + + public StevenLuParser(StevenLuSettings settings) + { + _settings = settings; + } + + public IList ParseResponse(NetImportResponse importResponse) + { + _importResponse = importResponse; + + var movies = new List(); + + if (!PreProcess(_importResponse)) + { + return movies; + } + + var jsonResponse = JsonConvert.DeserializeObject(_importResponse.Content); + + // no movies were return + if (jsonResponse == null) + { + return movies; + } + + foreach (var item in jsonResponse.Movie) + { + movies.AddIfNotNull(new Tv.Movie() + { + Title = item.title, + ImdbId = item.imdb_id + }); + } + + return movies; + } + + protected virtual bool PreProcess(NetImportResponse indexerResponse) + { + if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + { + throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode); + } + + if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/json") && + indexerResponse.HttpRequest.Headers.Accept != null && !indexerResponse.HttpRequest.Headers.Accept.Contains("text/json")) + { + throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable."); + } + + return true; + } + + } +} diff --git a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuRequestGenerator.cs b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuRequestGenerator.cs new file mode 100644 index 000000000..9e573bcf4 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuRequestGenerator.cs @@ -0,0 +1,28 @@ +using NzbDrone.Common.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.NetImport.StevenLu +{ + public class StevenLuRequestGenerator : INetImportRequestGenerator + { + public StevenLuSettings Settings { get; set; } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + pageableRequests.Add(GetMovies(null)); + + return pageableRequests; + } + + private IEnumerable GetMovies(string searchParameters) + { + var request = new NetImportRequest($"{Settings.Link.Trim()}", HttpAccept.Json); + yield return request; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuSettings.cs b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuSettings.cs new file mode 100644 index 000000000..a0829c868 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuSettings.cs @@ -0,0 +1,22 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.NetImport.StevenLu +{ + + public class StevenLuSettings : NetImportBaseSettings + { + public StevenLuSettings() + { + Link = "https://s3.amazonaws.com/popular-movies/movies.json"; + } + + [FieldDefinition(0, Label = "URL", HelpText = "Don't change this unless you know what you are doing.")] + public new string Link { get; set; } + + } + +} From fbe9ad6582dc4a358a13d0a3a3a80cd764aa6a13 Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Mon, 23 Jan 2017 20:00:31 +0100 Subject: [PATCH 36/52] First pass at ui for manually importing from lists. --- .../CouchPotato/CouchPotatoSettings.cs | 2 +- src/UI/AddMovies/AddMoviesLayout.js | 16 +- src/UI/AddMovies/AddMoviesLayoutTemplate.hbs | 11 +- .../AddMovies/List/AddFromListCollection.js | 18 ++ .../List/AddFromListCollectionView.js | 51 +++++ .../AddFromListCollectionViewTemplate.hbs | 5 + src/UI/AddMovies/List/AddFromListView.js | 177 ++++++++++++++++++ .../List/AddFromListViewTemplate.hbs | 15 ++ .../NetImport/ListSelectionPartial.hbs | 3 +- 9 files changed, 277 insertions(+), 21 deletions(-) create mode 100644 src/UI/AddMovies/List/AddFromListCollection.js create mode 100644 src/UI/AddMovies/List/AddFromListCollectionView.js create mode 100644 src/UI/AddMovies/List/AddFromListCollectionViewTemplate.hbs create mode 100644 src/UI/AddMovies/List/AddFromListView.js create mode 100644 src/UI/AddMovies/List/AddFromListViewTemplate.hbs diff --git a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs index fcebef860..073213247 100644 --- a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs +++ b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Core.NetImport.CouchPotato [FieldDefinition(3, Label = "CouchPotato API Key", HelpText = "CoouchPootato API Key.")] public string ApiKey { get; set; } - [FieldDefinition(4, Label = "Only Active", HelpText = "Should only active (not yet downloaded) movies be fetched")] + [FieldDefinition(4, Label = "Only Active", HelpText = "Should only active (not yet downloaded) movies be fetched", Type = FieldType.Checkbox)] public bool OnlyActive { get; set; } } diff --git a/src/UI/AddMovies/AddMoviesLayout.js b/src/UI/AddMovies/AddMoviesLayout.js index 127e0f0c0..00344bfdb 100644 --- a/src/UI/AddMovies/AddMoviesLayout.js +++ b/src/UI/AddMovies/AddMoviesLayout.js @@ -5,7 +5,7 @@ var RootFolderLayout = require('./RootFolders/RootFolderLayout'); var ExistingMoviesCollectionView = require('./Existing/AddExistingMovieCollectionView'); var AddMoviesView = require('./AddMoviesView'); var ProfileCollection = require('../Profile/ProfileCollection'); -var ListCollection = require("../Settings/NetImport/NetImportCollection"); +var AddFromListView = require("./List/AddFromListView"); var RootFolderCollection = require('./RootFolders/RootFolderCollection'); require('../Movies/MoviesCollection'); @@ -19,6 +19,7 @@ module.exports = Marionette.Layout.extend({ events : { 'click .x-import' : '_importMovies', 'click .x-add-new' : '_addMovies', + "click .x-add-lists" : "_addFromList", 'click .x-show-existing' : '_toggleExisting' }, @@ -31,12 +32,7 @@ module.exports = Marionette.Layout.extend({ RootFolderCollection.fetch().done(function() { RootFolderCollection.synced = true; }); - this.templateHelpers = {} - this.listCollection = new ListCollection(); - this.templateHelpers.lists = this.listCollection.toJSON(); - this.listenTo(this.listCollection, 'all', this._listsUpdated); - this.listCollection.fetch(); }, @@ -52,10 +48,6 @@ module.exports = Marionette.Layout.extend({ this.workspace.show(new AddMoviesView()); }, - _listsUpdated : function() { - this.templateHelpers.lists = this.listCollection.toJSON(); - this.render(); - }, _folderSelected : function(options) { vent.trigger(vent.Commands.CloseModalCommand); @@ -70,5 +62,9 @@ module.exports = Marionette.Layout.extend({ _addMovies : function() { this.workspace.show(new AddMoviesView()); + }, + + _addFromList : function() { + this.workspace.show(new AddFromListView()); } }); diff --git a/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs b/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs index c4a375918..0e251dcb7 100644 --- a/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs +++ b/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs @@ -1,11 +1,12 @@
- +
@@ -33,14 +34,6 @@
- -
- - -
- {{> ListSelectionPartial lists}} -
-
diff --git a/src/UI/AddMovies/List/AddFromListCollection.js b/src/UI/AddMovies/List/AddFromListCollection.js new file mode 100644 index 000000000..12f5cb7f0 --- /dev/null +++ b/src/UI/AddMovies/List/AddFromListCollection.js @@ -0,0 +1,18 @@ +var Backbone = require('backbone'); +var MovieModel = require('../../Movies/MovieModel'); +var _ = require('underscore'); + +module.exports = Backbone.Collection.extend({ + url : window.NzbDrone.ApiRoot + '/netimport/movies', + model : MovieModel, + + parse : function(response) { + var self = this; + + _.each(response, function(model) { + model.id = undefined; + }); + + return response; + } +}); diff --git a/src/UI/AddMovies/List/AddFromListCollectionView.js b/src/UI/AddMovies/List/AddFromListCollectionView.js new file mode 100644 index 000000000..4177bd995 --- /dev/null +++ b/src/UI/AddMovies/List/AddFromListCollectionView.js @@ -0,0 +1,51 @@ +var Marionette = require('marionette'); +var AddMoviesView = require('../AddMoviesView'); +var vent = require('vent'); + +module.exports = Marionette.CompositeView.extend({ + itemView : AddMoviesView, + itemViewContainer : '.x-loading-folders', + template : 'AddMovies/List/AddFromListCollectionViewTemplate', + + ui : { + loadingFolders : '.x-loading-list' + }, + + initialize : function() { + this.collection = new UnmappedFolderCollection(); + this.collection.importItems(this.model); + }, + + showCollection : function() { + this._showAndSearch(0); + }, + + appendHtml : function(collectionView, itemView, index) { + collectionView.ui.loadingFolders.before(itemView.el); + }, + + _showAndSearch : function(index) { + var self = this; + var model = this.collection.at(index); + + if (model) { + var currentIndex = index; + var folderName = model.get('folder').name; + this.addItemView(model, this.getItemView(), index); + this.children.findByModel(model).search({ term : folderName }).always(function() { + if (!self.isClosed) { + self._showAndSearch(currentIndex + 1); + } + }); + } + + else { + this.ui.loadingFolders.hide(); + } + }, + + itemViewOptions : { + isExisting : true + } + +}); diff --git a/src/UI/AddMovies/List/AddFromListCollectionViewTemplate.hbs b/src/UI/AddMovies/List/AddFromListCollectionViewTemplate.hbs new file mode 100644 index 000000000..dc812c87f --- /dev/null +++ b/src/UI/AddMovies/List/AddFromListCollectionViewTemplate.hbs @@ -0,0 +1,5 @@ +
+
+ Loading search results from TheTVDB for your movies, this may take a few minutes. +
+
diff --git a/src/UI/AddMovies/List/AddFromListView.js b/src/UI/AddMovies/List/AddFromListView.js new file mode 100644 index 000000000..199240007 --- /dev/null +++ b/src/UI/AddMovies/List/AddFromListView.js @@ -0,0 +1,177 @@ +var _ = require('underscore'); +var vent = require('vent'); +var Marionette = require('marionette'); +var AddFromListCollection = require('./AddFromListCollection'); +//var SearchResultCollectionView = require('./SearchResultCollectionView'); +var AddListView = require("../../Settings/NetImport/Add/NetImportAddItemView"); +var EmptyView = require('../EmptyView'); +var NotFoundView = require('../NotFoundView'); +var ListCollection = require("../../Settings/NetImport/NetImportCollection"); +var ErrorView = require('../ErrorView'); +var LoadingView = require('../../Shared/LoadingView'); +var AppLayout = require('../../AppLayout'); +var SchemaModal = require('../../Settings/NetImport/Add/NetImportSchemaModal'); + +module.exports = Marionette.Layout.extend({ + template : 'AddMovies/List/AddFromListViewTemplate', + + regions : { + fetchResult : '#fetch-result' + }, + + ui : { + moviesSearch : '.x-movies-search', + listSelection : ".x-list-selection", + + }, + + events : { + 'click .x-load-more' : '_onLoadMore', + "change .x-list-selection" : "_listSelected", + "click .x-fetch-list" : "_fetchList" + }, + + initialize : function(options) { + console.log(options); + + this.isExisting = options.isExisting; + //this.collection = new AddFromListCollection(); + + this.templateHelpers = {} + this.listCollection = new ListCollection(); + this.templateHelpers.lists = this.listCollection.toJSON(); + + this.listenTo(this.listCollection, 'all', this._listsUpdated); + this.listCollection.fetch(); + + this.collection = new AddFromListCollection(); + + /*this.listenTo(this.collection, 'sync', this._showResults); + + this.resultCollectionView = new SearchResultCollectionView({ + collection : this.collection, + isExisting : this.isExisting + });*/ + + //this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this); + }, + + onRender : function() { + var self = this; + }, + + onShow : function() { + this.ui.moviesSearch.focus(); + }, + + search : function(options) { + var self = this; + + this.collection.reset(); + + if (!options.term || options.term === this.collection.term) { + return Marionette.$.Deferred().resolve(); + } + + this.searchResult.show(new LoadingView()); + this.collection.term = options.term; + this.currentSearchPromise = this.collection.fetch({ + data : { term : options.term } + }); + + this.currentSearchPromise.fail(function() { + self._showError(); + }); + + return this.currentSearchPromise; + }, + + _onMoviesAdded : function(options) { + if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) { + this.close(); + } + + else if (!this.isExisting) { + this.resultCollectionView.setExisting(options.movie.get('tmdbId')); + /*this.collection.term = ''; + this.collection.reset(); + this._clearResults(); + this.ui.moviesSearch.val(''); + this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result. + } + }, + + _onLoadMore : function() { + var showingAll = this.resultCollectionView.showMore(); + this.ui.searchBar.show(); + + if (showingAll) { + this.ui.loadMore.hide(); + } + }, + + _listSelected : function() { + var rootFolderValue = this.ui.listSelection.val(); + if (rootFolderValue === 'addNew') { + //var rootFolderLayout = new SchemaModal(this.listCollection); + //AppLayout.modalRegion.show(rootFolderLayout); + SchemaModal.open(this.listCollection) + } + }, + + _fetchList : function() { + var self = this; + var listId = this.ui.listSelection.val(); + + this.fetchResult.show(new LoadingView()); + + this.currentFetchPromise = this.collection.fetch( + { data : { profileId : listId} } + ) + this.currentFetchPromise.fail(function() { + self._showError(); + }); + + }, + + _listsUpdated : function() { + this.templateHelpers.lists = this.listCollection.toJSON(); + this.render(); + }, + + _clearResults : function() { + + if (!this.isExisting) { + this.searchResult.show(new EmptyView()); + } else { + this.searchResult.close(); + } + }, + + _showResults : function() { + if (!this.isClosed) { + if (this.collection.length === 0) { + this.ui.searchBar.show(); + this.searchResult.show(new NotFoundView({ term : this.collection.term })); + } else { + this.searchResult.show(this.resultCollectionView); + if (!this.showingAll) { + this.ui.loadMore.show(); + } + } + } + }, + + _abortExistingSearch : function() { + if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) { + console.log('aborting previous pending search request.'); + this.currentSearchPromise.abort(); + } else { + this._clearResults(); + } + }, + + _showError : function() { + this.fetchResult.show(new ErrorView({ term : "" })); + } +}); diff --git a/src/UI/AddMovies/List/AddFromListViewTemplate.hbs b/src/UI/AddMovies/List/AddFromListViewTemplate.hbs new file mode 100644 index 000000000..fcf4e2611 --- /dev/null +++ b/src/UI/AddMovies/List/AddFromListViewTemplate.hbs @@ -0,0 +1,15 @@ + +
+
+
diff --git a/src/UI/Settings/NetImport/ListSelectionPartial.hbs b/src/UI/Settings/NetImport/ListSelectionPartial.hbs index dd93cc17d..d2b37459d 100644 --- a/src/UI/Settings/NetImport/ListSelectionPartial.hbs +++ b/src/UI/Settings/NetImport/ListSelectionPartial.hbs @@ -1,4 +1,5 @@ - + {{#if this}} {{#each this}} From e59db74cada839c347b698c086934b6a8655ae5f Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Mon, 23 Jan 2017 14:39:27 -0500 Subject: [PATCH 37/52] Add StevenLu to csproj --- src/NzbDrone.Core/NzbDrone.Core.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 704687008..4f2c6c742 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -128,6 +128,11 @@ + + + + + From 032bc2d5c4474710c21d2ab0cbe332bcebb0a539 Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Mon, 23 Jan 2017 20:51:33 +0100 Subject: [PATCH 38/52] Add basic ui of manual import. --- src/NzbDrone.Api/NzbDrone.Api.csproj | 3 +- .../Series/FetchMovieListModule.cs | 45 ++++++++++++ .../List/AddFromListCollectionView.js | 72 +++++++++---------- .../AddFromListCollectionViewTemplate.hbs | 5 +- src/UI/AddMovies/List/AddFromListView.js | 53 +++++++++++--- src/UI/AddMovies/List/ListItemView.js | 22 ++++++ .../AddMovies/List/ListItemViewTemplate.hbs | 3 + 7 files changed, 151 insertions(+), 52 deletions(-) create mode 100644 src/NzbDrone.Api/Series/FetchMovieListModule.cs create mode 100644 src/UI/AddMovies/List/ListItemView.js create mode 100644 src/UI/AddMovies/List/ListItemViewTemplate.hbs diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index de7b4faab..097933cdc 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -238,6 +238,7 @@ + @@ -299,4 +300,4 @@ --> - + \ No newline at end of file diff --git a/src/NzbDrone.Api/Series/FetchMovieListModule.cs b/src/NzbDrone.Api/Series/FetchMovieListModule.cs new file mode 100644 index 000000000..a6ec92f76 --- /dev/null +++ b/src/NzbDrone.Api/Series/FetchMovieListModule.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Nancy; +using NzbDrone.Api.Extensions; +using NzbDrone.Core.MediaCover; +using NzbDrone.Core.MetadataSource; +using System.Linq; +using NzbDrone.Core.NetImport; + +namespace NzbDrone.Api.Movie +{ + public class FetchMovieListModule : NzbDroneRestModule + { + private readonly IFetchNetImport _fetchNetImport; + + public FetchMovieListModule(IFetchNetImport netImport) + : base("/netimport/movies") + { + _fetchNetImport = netImport; + Get["/"] = x => Search(); + } + + + private Response Search() + { + var results = _fetchNetImport.FetchAndFilter((int) Request.Query.listId, false); + return MapToResource(results).AsResponse(); + } + + + private static IEnumerable MapToResource(IEnumerable movies) + { + foreach (var currentSeries in movies) + { + var resource = currentSeries.ToResource(); + var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster); + if (poster != null) + { + resource.RemotePoster = poster.Url; + } + + yield return resource; + } + } + } +} \ No newline at end of file diff --git a/src/UI/AddMovies/List/AddFromListCollectionView.js b/src/UI/AddMovies/List/AddFromListCollectionView.js index 4177bd995..91a963601 100644 --- a/src/UI/AddMovies/List/AddFromListCollectionView.js +++ b/src/UI/AddMovies/List/AddFromListCollectionView.js @@ -1,51 +1,47 @@ var Marionette = require('marionette'); -var AddMoviesView = require('../AddMoviesView'); +var ListItemView = require('./ListItemView'); var vent = require('vent'); -module.exports = Marionette.CompositeView.extend({ - itemView : AddMoviesView, - itemViewContainer : '.x-loading-folders', - template : 'AddMovies/List/AddFromListCollectionViewTemplate', +module.exports = Marionette.CollectionView.extend({ + itemView : ListItemView, ui : { - loadingFolders : '.x-loading-list' + loadingList : '.x-loading-list' }, initialize : function() { - this.collection = new UnmappedFolderCollection(); - this.collection.importItems(this.model); - }, - showCollection : function() { - this._showAndSearch(0); }, - appendHtml : function(collectionView, itemView, index) { - collectionView.ui.loadingFolders.before(itemView.el); - }, - - _showAndSearch : function(index) { - var self = this; - var model = this.collection.at(index); - - if (model) { - var currentIndex = index; - var folderName = model.get('folder').name; - this.addItemView(model, this.getItemView(), index); - this.children.findByModel(model).search({ term : folderName }).always(function() { - if (!self.isClosed) { - self._showAndSearch(currentIndex + 1); - } - }); - } - - else { - this.ui.loadingFolders.hide(); - } - }, - - itemViewOptions : { - isExisting : true - } + showCollection : function() { + }, + // + // appendHtml : function(collectionView, itemView, index) { + // collectionView.ui.loadingFolders.before(itemView.el); + // }, + // + // _showAndSearch : function(index) { + // var self = this; + // var model = this.collection.at(index); + // + // if (model) { + // var currentIndex = index; + // var folderName = model.get('folder').name; + // this.addItemView(model, this.getItemView(), index); + // this.children.findByModel(model).search({ term : folderName }).always(function() { + // if (!self.isClosed) { + // self._showAndSearch(currentIndex + 1); + // } + // }); + // } + // + // else { + // this.ui.loadingFolders.hide(); + // } + // }, + // + // itemViewOptions : { + // isExisting : true + // } }); diff --git a/src/UI/AddMovies/List/AddFromListCollectionViewTemplate.hbs b/src/UI/AddMovies/List/AddFromListCollectionViewTemplate.hbs index dc812c87f..34a766b7a 100644 --- a/src/UI/AddMovies/List/AddFromListCollectionViewTemplate.hbs +++ b/src/UI/AddMovies/List/AddFromListCollectionViewTemplate.hbs @@ -1,5 +1,4 @@
-
- Loading search results from TheTVDB for your movies, this may take a few minutes. -
+
+
diff --git a/src/UI/AddMovies/List/AddFromListView.js b/src/UI/AddMovies/List/AddFromListView.js index 199240007..292d02665 100644 --- a/src/UI/AddMovies/List/AddFromListView.js +++ b/src/UI/AddMovies/List/AddFromListView.js @@ -1,8 +1,9 @@ var _ = require('underscore'); var vent = require('vent'); var Marionette = require('marionette'); +var Backgrid = require('backgrid'); var AddFromListCollection = require('./AddFromListCollection'); -//var SearchResultCollectionView = require('./SearchResultCollectionView'); +var AddFromListCollectionView = require('./AddFromListCollectionView'); var AddListView = require("../../Settings/NetImport/Add/NetImportAddItemView"); var EmptyView = require('../EmptyView'); var NotFoundView = require('../NotFoundView'); @@ -10,6 +11,16 @@ var ListCollection = require("../../Settings/NetImport/NetImportCollection"); var ErrorView = require('../ErrorView'); var LoadingView = require('../../Shared/LoadingView'); var AppLayout = require('../../AppLayout'); +var InCinemasCell = require('../../Cells/InCinemasCell'); +var MovieTitleCell = require('../../Cells/MovieTitleCell'); +var TemplatedCell = require('../../Cells/TemplatedCell'); +var ProfileCell = require('../../Cells/ProfileCell'); +var MovieLinksCell = require('../../Cells/MovieLinksCell'); +var MovieActionCell = require('../../Cells/MovieActionCell'); +var MovieStatusCell = require('../../Cells/MovieStatusCell'); +var MovieDownloadStatusCell = require('../../Cells/MovieDownloadStatusCell'); +var DownloadedQualityCell = require('../../Cells/DownloadedQualityCell'); + var SchemaModal = require('../../Settings/NetImport/Add/NetImportSchemaModal'); module.exports = Marionette.Layout.extend({ @@ -25,6 +36,27 @@ module.exports = Marionette.Layout.extend({ }, + columns : [ + { + name : 'title', + label : 'Title', + cell : MovieTitleCell, + cellValue : 'this', + }, + { + name : 'profileId', + label : 'Profile', + cell : ProfileCell + }, + { + name : 'this', + label : 'Links', + cell : MovieLinksCell, + className : "movie-links-cell", + sortable : false, + } + ], + events : { 'click .x-load-more' : '_onLoadMore', "change .x-list-selection" : "_listSelected", @@ -46,6 +78,8 @@ module.exports = Marionette.Layout.extend({ this.collection = new AddFromListCollection(); + this.listenTo(this.collection, 'sync', this._showResults); + /*this.listenTo(this.collection, 'sync', this._showResults); this.resultCollectionView = new SearchResultCollectionView({ @@ -126,7 +160,7 @@ module.exports = Marionette.Layout.extend({ this.fetchResult.show(new LoadingView()); this.currentFetchPromise = this.collection.fetch( - { data : { profileId : listId} } + { data : { listId : listId} } ) this.currentFetchPromise.fail(function() { self._showError(); @@ -149,17 +183,16 @@ module.exports = Marionette.Layout.extend({ }, _showResults : function() { - if (!this.isClosed) { if (this.collection.length === 0) { - this.ui.searchBar.show(); - this.searchResult.show(new NotFoundView({ term : this.collection.term })); + this.fetchResult.show(new NotFoundView({ term : "" })); } else { - this.searchResult.show(this.resultCollectionView); - if (!this.showingAll) { - this.ui.loadMore.show(); - } + this.fetchResult.show(new Backgrid.Grid({ + collection : this.collection, + columns : this.columns, + className : 'table table-hover' + })); } - } + }, _abortExistingSearch : function() { diff --git a/src/UI/AddMovies/List/ListItemView.js b/src/UI/AddMovies/List/ListItemView.js new file mode 100644 index 000000000..f93b2778e --- /dev/null +++ b/src/UI/AddMovies/List/ListItemView.js @@ -0,0 +1,22 @@ +var _ = require('underscore'); +var vent = require('vent'); +var AppLayout = require('../../AppLayout'); +var Backbone = require('backbone'); +var Marionette = require('marionette'); +var Config = require('../../Config'); +var Messenger = require('../../Shared/Messenger'); +var AsValidatedView = require('../../Mixins/AsValidatedView'); + +require('jquery.dotdotdot'); + +var view = Marionette.ItemView.extend({ + + template : 'AddMovies/SearchResultViewTemplate', + + +}) + + +AsValidatedView.apply(view); + +module.exports = view; diff --git a/src/UI/AddMovies/List/ListItemViewTemplate.hbs b/src/UI/AddMovies/List/ListItemViewTemplate.hbs new file mode 100644 index 000000000..70d974ae7 --- /dev/null +++ b/src/UI/AddMovies/List/ListItemViewTemplate.hbs @@ -0,0 +1,3 @@ +
+ ASDF +
From e182d8b964fb7bca97357b7691141853293a41f3 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Mon, 23 Jan 2017 15:18:04 -0500 Subject: [PATCH 39/52] fix importing for StevenLu --- src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs | 5 ----- src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs index e56655278..1dedc8719 100644 --- a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs +++ b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuAPI.cs @@ -5,11 +5,6 @@ using System.Xml.Serialization; namespace NzbDrone.Core.NetImport.StevenLu { public class StevenLuResponse - { - public Movie[] Movie { get; set; } - } - - public class Movie { public string title { get; set; } public string imdb_id { get; set; } diff --git a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs index a73c190aa..9c032c162 100644 --- a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs +++ b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuParser.cs @@ -42,7 +42,7 @@ namespace NzbDrone.Core.NetImport.StevenLu return movies; } - var jsonResponse = JsonConvert.DeserializeObject(_importResponse.Content); + var jsonResponse = JsonConvert.DeserializeObject>(_importResponse.Content); // no movies were return if (jsonResponse == null) @@ -50,7 +50,7 @@ namespace NzbDrone.Core.NetImport.StevenLu return movies; } - foreach (var item in jsonResponse.Movie) + foreach (var item in jsonResponse) { movies.AddIfNotNull(new Tv.Movie() { From 43d904d20b1ec99aa55ed973acd8c9fc94a46d24 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Mon, 23 Jan 2017 18:06:42 -0500 Subject: [PATCH 40/52] added trakt user list importing --- src/NzbDrone.Core/NetImport/Trakt/TraktAPI.cs | 29 +++++++ .../NetImport/Trakt/TraktImport.cs | 36 ++++++++ .../NetImport/Trakt/TraktParser.cs | 84 +++++++++++++++++++ .../NetImport/Trakt/TraktRequestGenerator.cs | 35 ++++++++ .../NetImport/Trakt/TraktSettings.cs | 30 +++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 7 +- 6 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core/NetImport/Trakt/TraktAPI.cs create mode 100644 src/NzbDrone.Core/NetImport/Trakt/TraktImport.cs create mode 100644 src/NzbDrone.Core/NetImport/Trakt/TraktParser.cs create mode 100644 src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs create mode 100644 src/NzbDrone.Core/NetImport/Trakt/TraktSettings.cs diff --git a/src/NzbDrone.Core/NetImport/Trakt/TraktAPI.cs b/src/NzbDrone.Core/NetImport/Trakt/TraktAPI.cs new file mode 100644 index 000000000..175ff0555 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Trakt/TraktAPI.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Windows.Forms; +using System.Xml.Serialization; + +namespace NzbDrone.Core.NetImport.Trakt +{ + public class Ids + { + public int trakt { get; set; } + public string slug { get; set; } + public string imdb { get; set; } + public int tmdb { get; set; } + } + + public class Movie + { + public string title { get; set; } + public int year { get; set; } + public Ids ids { get; set; } + } + + public class TraktResponse + { + public int rank { get; set; } + public string listed_at { get; set; } + public string type { get; set; } + public Movie movie { get; set; } + } +} diff --git a/src/NzbDrone.Core/NetImport/Trakt/TraktImport.cs b/src/NzbDrone.Core/NetImport/Trakt/TraktImport.cs new file mode 100644 index 000000000..1e0e083ae --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Trakt/TraktImport.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.PassThePopcorn; +using NzbDrone.Core.Parser; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.NetImport.Trakt +{ + public class TraktImport : HttpNetImportBase + { + public override string Name => "Trakt User List"; + public override bool Enabled => true; + public override bool EnableAuto => false; + + public TraktImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) + { } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new TraktRequestGenerator() { Settings = Settings }; + } + + public override IParseNetImportResponse GetParser() + { + return new TraktParser(Settings); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/Trakt/TraktParser.cs b/src/NzbDrone.Core/NetImport/Trakt/TraktParser.cs new file mode 100644 index 000000000..611dfc5b8 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Trakt/TraktParser.cs @@ -0,0 +1,84 @@ +using Newtonsoft.Json; +using NzbDrone.Core.NetImport.Exceptions; +using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Forms; +using System.Xml; +using System.Xml.Linq; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.NetImport.Trakt +{ + public class TraktParser : IParseNetImportResponse + { + private readonly TraktSettings _settings; + private NetImportResponse _importResponse; + private readonly Logger _logger; + + public TraktParser(TraktSettings settings) + { + _settings = settings; + } + + public IList ParseResponse(NetImportResponse importResponse) + { + _importResponse = importResponse; + + var movies = new List(); + + if (!PreProcess(_importResponse)) + { + return movies; + } + + var jsonResponse = JsonConvert.DeserializeObject>(_importResponse.Content); + + // no movies were return + if (jsonResponse == null) + { + return movies; + } + + foreach (var movie in jsonResponse) + { + movies.AddIfNotNull(new Tv.Movie() + { + Title = movie.movie.title, + ImdbId = movie.movie.ids.imdb, + TmdbId = movie.movie.ids.tmdb, + Year = movie.movie.year + }); + } + + return movies; + } + + protected virtual bool PreProcess(NetImportResponse indexerResponse) + { + if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + { + throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode); + } + + if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/json") && + indexerResponse.HttpRequest.Headers.Accept != null && !indexerResponse.HttpRequest.Headers.Accept.Contains("text/json")) + { + throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable."); + } + + return true; + } + + } +} diff --git a/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs b/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs new file mode 100644 index 000000000..152da8986 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs @@ -0,0 +1,35 @@ +using NzbDrone.Common.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.NetImport.Trakt +{ + public class TraktRequestGenerator : INetImportRequestGenerator + { + public TraktSettings Settings { get; set; } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + pageableRequests.Add(GetMovies(null)); + + return pageableRequests; + } + + private IEnumerable GetMovies(string searchParameters) + { + // https://api.trakt.tv/users/timdturner/lists/custom1/items/movies + // trakt-api-version = 2 + // trakt-api-key = 657bb899dcb81ec8ee838ff09f6e013ff7c740bf0ccfa54dd41e791b9a70b2f0 + + var request = new NetImportRequest($"{Settings.Link.Trim()}{Settings.Username.Trim()}/lists/{Settings.Listname.Trim()}/items/movies", HttpAccept.Json); + request.HttpRequest.Headers.Add("trakt-api-version", "2"); + request.HttpRequest.Headers.Add("trakt-api-key", "657bb899dcb81ec8ee838ff09f6e013ff7c740bf0ccfa54dd41e791b9a70b2f0"); + + yield return request; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/Trakt/TraktSettings.cs b/src/NzbDrone.Core/NetImport/Trakt/TraktSettings.cs new file mode 100644 index 000000000..2279cf373 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Trakt/TraktSettings.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.NetImport.Trakt +{ + + public class TraktSettings : NetImportBaseSettings + { + public TraktSettings() + { + Link = "https://api.trakt.tv/users/"; + Username = ""; + Listname = ""; + } + + [FieldDefinition(0, Label = "Trakt API URL", HelpText = "Link to to Trakt API URL, do not change unless you know what you are doing.")] + public new string Link { get; set; } + + [FieldDefinition(1, Label = "Trakt Username", HelpText = "Trakt Username the list belongs to.")] + public string Username { get; set; } + + [FieldDefinition(2, Label = "Trakt List Name", HelpText = "Trakt List Name")] + public string Listname { get; set; } + + } + +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 4f2c6c742..1b6ef7f6f 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -123,6 +123,11 @@ + + + + + @@ -132,7 +137,7 @@ - + From d09d30544f37f12c2f13ceb06ce1f3c16a8439a3 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Tue, 24 Jan 2017 13:22:02 -0500 Subject: [PATCH 41/52] allow null value for seed time. --- src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs index 1da2039ed..6a11b87b4 100644 --- a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs +++ b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs @@ -59,7 +59,7 @@ namespace NzbDrone.Core.NetImport.CouchPotato public int leechers { get; set; } public int score { get; set; } public string provider { get; set; } - public int seed_time { get; set; } + public int? seed_time { get; set; } public string provider_extra { get; set; } public string detail_url { get; set; } public string type { get; set; } From a75f3e1f8ea80d87a2a45276c82b3e5459a37bd3 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Tue, 24 Jan 2017 13:42:27 -0500 Subject: [PATCH 42/52] monitored to false for movies already downloaded on CP --- src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs index fbaf7055e..424e5cc71 100644 --- a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs +++ b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs @@ -77,7 +77,8 @@ namespace NzbDrone.Core.NetImport.CouchPotato { Title = item.title, ImdbId = item.info.imdb, - TmdbId = item.info.tmdb_id + TmdbId = item.info.tmdb_id, + Monitored = false }); } } From 95d97c59d798f0a838cdc6c2d10a26977d8a17e7 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Tue, 24 Jan 2017 13:47:44 -0500 Subject: [PATCH 43/52] rephrase wording --- src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs index 073213247..d299e1b92 100644 --- a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs +++ b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Core.NetImport.CouchPotato [FieldDefinition(3, Label = "CouchPotato API Key", HelpText = "CoouchPootato API Key.")] public string ApiKey { get; set; } - [FieldDefinition(4, Label = "Only Active", HelpText = "Should only active (not yet downloaded) movies be fetched", Type = FieldType.Checkbox)] + [FieldDefinition(4, Label = "Only Wanted", HelpText = "Only add wanted movies.", Type = FieldType.Checkbox)] public bool OnlyActive { get; set; } } From e2c2bdb65b19e0731e8e2a9ce81d9c9367aa2a68 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Wed, 25 Jan 2017 11:35:10 -0500 Subject: [PATCH 44/52] nullable all the fields.. --- .../NetImport/CouchPotato/CouchPotatoAPI.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs index 6a11b87b4..4982e02da 100644 --- a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs +++ b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoAPI.cs @@ -29,47 +29,47 @@ namespace NzbDrone.Core.NetImport.CouchPotato public class Info { public string[] genres { get; set; } - public int tmdb_id { get; set; } + public int? tmdb_id { get; set; } public string plot { get; set; } public string tagline { get; set; } - public int year { get; set; } + public int? year { get; set; } public string original_title { get; set; } - public bool via_imdb { get; set; } + public bool? via_imdb { get; set; } public string[] directors { get; set; } public string[] titles { get; set; } public string imdb { get; set; } public string mpaa { get; set; } - public bool via_tmdb { get; set; } + public bool? via_tmdb { get; set; } public string[] actors { get; set; } public string[] writers { get; set; } - public int runtime { get; set; } + public int? runtime { get; set; } public string type { get; set; } public string released { get; set; } } public class ReleaseInfo { - public double size { get; set; } - public int seeders { get; set; } + public double? size { get; set; } + public int? seeders { get; set; } public string protocol { get; set; } public string description { get; set; } public string url { get; set; } - public int age { get; set; } + public int? age { get; set; } public string id { get; set; } - public int leechers { get; set; } - public int score { get; set; } + public int? leechers { get; set; } + public int? score { get; set; } public string provider { get; set; } public int? seed_time { get; set; } public string provider_extra { get; set; } public string detail_url { get; set; } public string type { get; set; } - public double seed_ratio { get; set; } + public double? seed_ratio { get; set; } public string name { get; set; } } public class DownloadInfo { - public bool status_support { get; set; } + public bool? status_support { get; set; } public string id { get; set; } public string downloader { get; set; } } @@ -83,8 +83,8 @@ namespace NzbDrone.Core.NetImport.CouchPotato public string media_id { get; set; } public string _rev { get; set; } public string _t { get; set; } - public bool is_3d { get; set; } - public int last_edit { get; set; } + public bool? is_3d { get; set; } + public int? last_edit { get; set; } public string identifier { get; set; } public string quality { get; set; } } From 44b4e71c05dfa87618e78b85b8f76c23845e6957 Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Thu, 26 Jan 2017 14:21:35 +0100 Subject: [PATCH 45/52] Manual importing almost done. Needs fixing for mapping movies. --- .../Series/FetchMovieListModule.cs | 19 +++++++++-- .../CouchPotato/CouchPotatoParser.cs | 6 ++-- src/UI/AddMovies/List/AddFromListView.js | 33 ++++++++++++++++--- .../List/AddFromListViewTemplate.hbs | 5 ++- src/UI/Cells/MovieListTitleCell.js | 7 ++++ src/UI/Cells/MovieListTitleTemplate.hbs | 1 + 6 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 src/UI/Cells/MovieListTitleCell.js create mode 100644 src/UI/Cells/MovieListTitleTemplate.hbs diff --git a/src/NzbDrone.Api/Series/FetchMovieListModule.cs b/src/NzbDrone.Api/Series/FetchMovieListModule.cs index a6ec92f76..52475d0cb 100644 --- a/src/NzbDrone.Api/Series/FetchMovieListModule.cs +++ b/src/NzbDrone.Api/Series/FetchMovieListModule.cs @@ -11,11 +11,13 @@ namespace NzbDrone.Api.Movie public class FetchMovieListModule : NzbDroneRestModule { private readonly IFetchNetImport _fetchNetImport; + private readonly ISearchForNewMovie _movieSearch; - public FetchMovieListModule(IFetchNetImport netImport) + public FetchMovieListModule(IFetchNetImport netImport, ISearchForNewMovie movieSearch) : base("/netimport/movies") { _fetchNetImport = netImport; + _movieSearch = movieSearch; Get["/"] = x => Search(); } @@ -23,7 +25,20 @@ namespace NzbDrone.Api.Movie private Response Search() { var results = _fetchNetImport.FetchAndFilter((int) Request.Query.listId, false); - return MapToResource(results).AsResponse(); + + List realResults = new List(); + + foreach (var movie in results) + { + var mapped = _movieSearch.MapMovieToTmdbMovie(movie); + + if (mapped != null) + { + realResults.Add(mapped); + } + } + + return MapToResource(realResults).AsResponse(); } diff --git a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs index 424e5cc71..48b7b76d7 100644 --- a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs +++ b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoParser.cs @@ -56,6 +56,8 @@ namespace NzbDrone.Core.NetImport.CouchPotato foreach (var item in responseData) { + int tmdbid = item.info.tmdb_id ?? 0; + // if there are no releases at all the movie wasn't found on CP, so return movies if (!item.releases.Any() && item.type == "movie") { @@ -63,7 +65,7 @@ namespace NzbDrone.Core.NetImport.CouchPotato { Title = item.title, ImdbId = item.info.imdb, - TmdbId = item.info.tmdb_id + TmdbId = tmdbid }); } else @@ -77,7 +79,7 @@ namespace NzbDrone.Core.NetImport.CouchPotato { Title = item.title, ImdbId = item.info.imdb, - TmdbId = item.info.tmdb_id, + TmdbId = tmdbid, Monitored = false }); } diff --git a/src/UI/AddMovies/List/AddFromListView.js b/src/UI/AddMovies/List/AddFromListView.js index 292d02665..05d60dcad 100644 --- a/src/UI/AddMovies/List/AddFromListView.js +++ b/src/UI/AddMovies/List/AddFromListView.js @@ -12,7 +12,8 @@ var ErrorView = require('../ErrorView'); var LoadingView = require('../../Shared/LoadingView'); var AppLayout = require('../../AppLayout'); var InCinemasCell = require('../../Cells/InCinemasCell'); -var MovieTitleCell = require('../../Cells/MovieTitleCell'); +var MovieTitleCell = require('../../Cells/MovieListTitleCell'); +var SelectAllCell = require('../../Cells/SelectAllCell'); var TemplatedCell = require('../../Cells/TemplatedCell'); var ProfileCell = require('../../Cells/ProfileCell'); var MovieLinksCell = require('../../Cells/MovieLinksCell'); @@ -20,6 +21,7 @@ var MovieActionCell = require('../../Cells/MovieActionCell'); var MovieStatusCell = require('../../Cells/MovieStatusCell'); var MovieDownloadStatusCell = require('../../Cells/MovieDownloadStatusCell'); var DownloadedQualityCell = require('../../Cells/DownloadedQualityCell'); +var MoviesCollection = require('../../Movies/MoviesCollection'); var SchemaModal = require('../../Settings/NetImport/Add/NetImportSchemaModal'); @@ -37,6 +39,12 @@ module.exports = Marionette.Layout.extend({ }, columns : [ + { + name : '', + cell : SelectAllCell, + headerCell : 'select-all', + sortable : false + }, { name : 'title', label : 'Title', @@ -60,7 +68,8 @@ module.exports = Marionette.Layout.extend({ events : { 'click .x-load-more' : '_onLoadMore', "change .x-list-selection" : "_listSelected", - "click .x-fetch-list" : "_fetchList" + "click .x-fetch-list" : "_fetchList", + "click .x-import-selected" : "_importSelected" }, initialize : function(options) { @@ -173,6 +182,21 @@ module.exports = Marionette.Layout.extend({ this.render(); }, + _importSelected : function() { + var selected = this.importGrid.getSelectedModels(); + console.log(selected); + _.each(selected, function(elem){ + elem.save(); + }) + /*for (m in selected) { + debugger; + m.save() + MoviesCollection.add(m); + }*/ + + //MoviesCollection.save(); + }, + _clearResults : function() { if (!this.isExisting) { @@ -186,11 +210,12 @@ module.exports = Marionette.Layout.extend({ if (this.collection.length === 0) { this.fetchResult.show(new NotFoundView({ term : "" })); } else { - this.fetchResult.show(new Backgrid.Grid({ + this.importGrid = new Backgrid.Grid({ collection : this.collection, columns : this.columns, className : 'table table-hover' - })); + }); + this.fetchResult.show(this.importGrid); } }, diff --git a/src/UI/AddMovies/List/AddFromListViewTemplate.hbs b/src/UI/AddMovies/List/AddFromListViewTemplate.hbs index fcf4e2611..cad2b19bc 100644 --- a/src/UI/AddMovies/List/AddFromListViewTemplate.hbs +++ b/src/UI/AddMovies/List/AddFromListViewTemplate.hbs @@ -5,9 +5,12 @@
{{> ListSelectionPartial lists}}
-
+
+
+ +
diff --git a/src/UI/Cells/MovieListTitleCell.js b/src/UI/Cells/MovieListTitleCell.js new file mode 100644 index 000000000..6d9142131 --- /dev/null +++ b/src/UI/Cells/MovieListTitleCell.js @@ -0,0 +1,7 @@ +var TemplatedCell = require('./TemplatedCell'); + +module.exports = TemplatedCell.extend({ + className : 'series-title-cell', + template : 'Cells/MovieListTitleTemplate', + +}); diff --git a/src/UI/Cells/MovieListTitleTemplate.hbs b/src/UI/Cells/MovieListTitleTemplate.hbs new file mode 100644 index 000000000..6c4bb964b --- /dev/null +++ b/src/UI/Cells/MovieListTitleTemplate.hbs @@ -0,0 +1 @@ +{{title}} From dbe5946d10cee158bfa7c07d05d1be4882c35d99 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Thu, 26 Jan 2017 17:31:27 -0500 Subject: [PATCH 46/52] Only wanted is default for CP --- src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs index d299e1b92..98ab00bff 100644 --- a/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs +++ b/src/NzbDrone.Core/NetImport/CouchPotato/CouchPotatoSettings.cs @@ -14,7 +14,7 @@ namespace NzbDrone.Core.NetImport.CouchPotato Link = "http://localhost"; Port = 5050; UrlBase = ""; - OnlyActive = false; + OnlyActive = true; } [FieldDefinition(0, Label = "CouchPotato URL", HelpText = "Link to your CoouchPootato.")] From dd8af0ad8c0d35d661f8dc7d66b847b60f22dc5f Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Fri, 27 Jan 2017 19:36:25 +0100 Subject: [PATCH 47/52] Manual Import works now! Also fixed a few bugs. --- .../NetImport/ListImportModule.cs | 34 ++ src/NzbDrone.Api/NzbDrone.Api.csproj | 1 + .../Series/FetchMovieListModule.cs | 6 +- .../SkyHook/Resource/TMDBResources.cs | 2 + .../MetadataSource/SkyHook/SkyHookProxy.cs | 19 +- .../NetImport/NetImportSearchService.cs | 6 - src/NzbDrone.Core/Tv/MovieService.cs | 30 ++ src/UI/AddMovies/List/AddFromListView.js | 26 +- .../List/AddFromListViewTemplate.hbs | 2 +- src/UI/Movies/MoviesCollection.js | 293 ++++++++++-------- 10 files changed, 264 insertions(+), 155 deletions(-) create mode 100644 src/NzbDrone.Api/NetImport/ListImportModule.cs diff --git a/src/NzbDrone.Api/NetImport/ListImportModule.cs b/src/NzbDrone.Api/NetImport/ListImportModule.cs new file mode 100644 index 000000000..f1d81aefd --- /dev/null +++ b/src/NzbDrone.Api/NetImport/ListImportModule.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using Nancy; +using Nancy.Extensions; +using NzbDrone.Api.Extensions; +using NzbDrone.Api.Movie; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Api.NetImport +{ + public class ListImportModule : NzbDroneApiModule + { + private readonly IMovieService _movieService; + private readonly ISearchForNewMovie _movieSearch; + + public ListImportModule(IMovieService movieService, ISearchForNewMovie movieSearch) + : base("/movie/import") + { + _movieService = movieService; + _movieSearch = movieSearch; + Put["/"] = Movie => SaveAll(); + } + + private Response SaveAll() + { + var resources = Request.Body.FromJson>(); + + var Movies = resources.Select(MovieResource => _movieSearch.MapMovieToTmdbMovie(MovieResource.ToModel())).Where(m => m != null).DistinctBy(m => m.TmdbId).ToList(); + + return _movieService.AddMovies(Movies).ToResource().AsResponse(HttpStatusCode.Accepted); + } + } +} diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 097933cdc..1f0542788 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -123,6 +123,7 @@ + diff --git a/src/NzbDrone.Api/Series/FetchMovieListModule.cs b/src/NzbDrone.Api/Series/FetchMovieListModule.cs index 52475d0cb..871ebd7bc 100644 --- a/src/NzbDrone.Api/Series/FetchMovieListModule.cs +++ b/src/NzbDrone.Api/Series/FetchMovieListModule.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Api.Movie List realResults = new List(); - foreach (var movie in results) + /*foreach (var movie in results) { var mapped = _movieSearch.MapMovieToTmdbMovie(movie); @@ -36,9 +36,9 @@ namespace NzbDrone.Api.Movie { realResults.Add(mapped); } - } + }*/ - return MapToResource(realResults).AsResponse(); + return MapToResource(results).AsResponse(); } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs index 469e72776..182992c38 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs @@ -42,6 +42,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public bool adult { get; set; } public string backdrop_path { get; set; } public Belongs_To_Collection belongs_to_collection { get; set; } + public int? status_code { get; set; } + public string status_message { get; set; } public int budget { get; set; } public Genre[] genres { get; set; } public string homepage { get; set; } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index b6151d46b..70d3dda89 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -84,6 +84,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var resource = response.Resource; + if (resource.status_message != null) + { + if (resource.status_code == 34) + { + _logger.Warn("Movie with TmdbId {0} could not be found. This is probably the case when the movie was deleted from TMDB.", TmdbId); + return null; + } + + _logger.Warn(resource.status_message); + return null; + } + var movie = new Movie(); movie.TmdbId = TmdbId; @@ -567,10 +579,9 @@ namespace NzbDrone.Core.MetadataSource.SkyHook Movie newMovie = movie; if (movie.TmdbId > 0) { - return newMovie; + newMovie = GetMovieInfo(movie.TmdbId); } - - if (movie.ImdbId.IsNotNullOrWhiteSpace()) + else if (movie.ImdbId.IsNotNullOrWhiteSpace()) { newMovie = GetMovieInfo(movie.ImdbId); } @@ -586,7 +597,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook if (newMovie == null) { - _logger.Warn("Couldn't map movie {0} to a movie on The Movie DB."); + _logger.Warn("Couldn't map movie {0} to a movie on The Movie DB. It will not be added :(", movie.Title); return null; } diff --git a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs index 49500cd4c..e119d8b36 100644 --- a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs +++ b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs @@ -25,7 +25,6 @@ namespace NzbDrone.Core.NetImport private readonly IMovieService _movieService; private readonly ISearchForNewMovie _movieSearch; private readonly IRootFolderService _rootFolder; - private string defaultRootFolder; public NetImportSearchService(INetImportFactory netImportFactory, IMovieService movieService, ISearchForNewMovie movieSearch, IRootFolderService rootFolder, Logger logger) @@ -34,11 +33,6 @@ namespace NzbDrone.Core.NetImport _movieService = movieService; _movieSearch = movieSearch; _rootFolder = rootFolder; - var folder = _rootFolder.All().FirstOrDefault(); - if (folder != null) - { - defaultRootFolder = folder.Path; - } _logger = logger; } diff --git a/src/NzbDrone.Core/Tv/MovieService.cs b/src/NzbDrone.Core/Tv/MovieService.cs index 03ba48e4f..9c33e1ecd 100644 --- a/src/NzbDrone.Core/Tv/MovieService.cs +++ b/src/NzbDrone.Core/Tv/MovieService.cs @@ -21,6 +21,7 @@ namespace NzbDrone.Core.Tv Movie GetMovie(int movieId); List GetMovies(IEnumerable movieIds); Movie AddMovie(Movie newMovie); + List AddMovies(List newMovies); Movie FindByImdbId(string imdbid); Movie FindByTitle(string title); Movie FindByTitle(string title, int year); @@ -92,6 +93,35 @@ namespace NzbDrone.Core.Tv return newMovie; } + public List AddMovies(List newMovies) + { + _logger.Debug("Adding {0} movies", newMovies.Count); + + newMovies.ForEach(m => Ensure.That(m, () => m).IsNotNull()); + + newMovies.ForEach(m => + { + if (string.IsNullOrWhiteSpace(m.Path)) + { + var folderName = _fileNameBuilder.GetMovieFolder(m); + m.Path = Path.Combine(m.RootFolderPath, folderName); + } + + m.CleanTitle = m.Title.CleanSeriesTitle(); + m.SortTitle = MovieTitleNormalizer.Normalize(m.Title, m.TmdbId); + m.Added = DateTime.UtcNow; + }); + + _movieRepository.InsertMany(newMovies); + + newMovies.ForEach(m => + { + _eventAggregator.PublishEvent(new MovieAddedEvent(m)); + }); + + return newMovies; + } + public Movie FindByTitle(string title) { return _movieRepository.FindByTitle(title.CleanSeriesTitle()); diff --git a/src/UI/AddMovies/List/AddFromListView.js b/src/UI/AddMovies/List/AddFromListView.js index 05d60dcad..d2605c74f 100644 --- a/src/UI/AddMovies/List/AddFromListView.js +++ b/src/UI/AddMovies/List/AddFromListView.js @@ -22,7 +22,8 @@ var MovieStatusCell = require('../../Cells/MovieStatusCell'); var MovieDownloadStatusCell = require('../../Cells/MovieDownloadStatusCell'); var DownloadedQualityCell = require('../../Cells/DownloadedQualityCell'); var MoviesCollection = require('../../Movies/MoviesCollection'); - +var Messenger = require('../../Shared/Messenger'); +require('jquery.dotdotdot'); var SchemaModal = require('../../Settings/NetImport/Add/NetImportSchemaModal'); module.exports = Marionette.Layout.extend({ @@ -35,7 +36,7 @@ module.exports = Marionette.Layout.extend({ ui : { moviesSearch : '.x-movies-search', listSelection : ".x-list-selection", - + importSelected : ".x-import-selected" }, columns : [ @@ -185,9 +186,24 @@ module.exports = Marionette.Layout.extend({ _importSelected : function() { var selected = this.importGrid.getSelectedModels(); console.log(selected); - _.each(selected, function(elem){ - elem.save(); - }) + var promise = MoviesCollection.importFromList(selected); + this.ui.importSelected.spinForPromise(promise); + this.ui.importSelected.addClass('disabled'); + + Messenger.show({ + message : "Importing {0} movies. This can take multiple minutes depending on how many movies should be imported. Don't close this browser window until it is finished!".format(selected.length), + hideOnNavigate : false, + hideAfter : 30, + type : "error" + }); + + promise.done(function() { + Messenger.show({ + message : "Imported movies from list.", + hideAfter : 8, + hideOnNavigate : true + }); + }); /*for (m in selected) { debugger; m.save() diff --git a/src/UI/AddMovies/List/AddFromListViewTemplate.hbs b/src/UI/AddMovies/List/AddFromListViewTemplate.hbs index cad2b19bc..f63c95573 100644 --- a/src/UI/AddMovies/List/AddFromListViewTemplate.hbs +++ b/src/UI/AddMovies/List/AddFromListViewTemplate.hbs @@ -9,7 +9,7 @@
- +
diff --git a/src/UI/Movies/MoviesCollection.js b/src/UI/Movies/MoviesCollection.js index 193f47ef6..779fb4a0d 100644 --- a/src/UI/Movies/MoviesCollection.js +++ b/src/UI/Movies/MoviesCollection.js @@ -10,142 +10,163 @@ var moment = require('moment'); require('../Mixins/backbone.signalr.mixin'); var Collection = PageableCollection.extend({ - url : window.NzbDrone.ApiRoot + '/movie', - model : MovieModel, - tableName : 'movie', - - state : { - sortKey : 'sortTitle', - order : 1, - pageSize : 100000, - secondarySortKey : 'sortTitle', - secondarySortOrder : -1 - }, - - mode : 'client', - - save : function() { - var self = this; - - var proxy = _.extend(new Backbone.Model(), { - id : '', - - url : self.url + '/editor', - - toJSON : function() { - return self.filter(function(model) { - return model.edited; - }); - } - }); - - this.listenTo(proxy, 'sync', function(proxyModel, models) { - this.add(models, { merge : true }); - this.trigger('save', this); - }); - - return proxy.save(); - }, - - filterModes : { - 'all' : [ - null, - null - ], - 'continuing' : [ - 'status', - 'continuing' - ], - 'ended' : [ - 'status', - 'ended' - ], - 'monitored' : [ - 'monitored', - true - ], - 'missing' : [ - 'downloaded', - false - ] - }, - - sortMappings : { - title : { - sortKey : 'sortTitle' - }, - statusWeight : { - sortValue : function(model, attr) { - if (model.getStatus() == "released") { - return 1; - } - if (model.getStatus() == "inCinemas") { - return 0; - } - return -1; - } - }, - downloadedQuality : { - sortValue : function(model, attr) { - if (model.get("movieFile")) { - return 1000-model.get("movieFile").quality.quality.id; - } - - return -1; - } - }, - nextAiring : { - sortValue : function(model, attr, order) { - var nextAiring = model.get(attr); - - if (nextAiring) { - return moment(nextAiring).unix(); - } - - if (order === 1) { - return 0; - } - - return Number.MAX_VALUE; - } - }, - status: { - sortValue : function(model, attr) { - debugger; - if (model.get("downloaded")) { - return -1; - } - return 0; - } - }, - percentOfEpisodes : { - sortValue : function(model, attr) { - var percentOfEpisodes = model.get(attr); - var episodeCount = model.get('episodeCount'); - - return percentOfEpisodes + episodeCount / 1000000; - } - }, - inCinemas : { - - sortValue : function(model, attr) { - var monthNames = ["January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - ]; - if (model.get("inCinemas")) { - return model.get("inCinemas"); - } - return "2100-01-01"; - } - }, - path : { - sortValue : function(model) { - var path = model.get('path'); - - return path.toLowerCase(); - } - } - } + url : window.NzbDrone.ApiRoot + '/movie', + model : MovieModel, + tableName : 'movie', + + state : { + sortKey : 'sortTitle', + order : 1, + pageSize : 100000, + secondarySortKey : 'sortTitle', + secondarySortOrder : -1 + }, + + mode : 'client', + + save : function() { + var self = this; + + var proxy = _.extend(new Backbone.Model(), { + id : '', + + url : self.url + '/editor', + + toJSON : function() { + return self.filter(function(model) { + return model.edited; + }); + } + }); + + this.listenTo(proxy, 'sync', function(proxyModel, models) { + this.add(models, { merge : true }); + this.trigger('save', this); + }); + + return proxy.save(); + }, + + importFromList : function(models) { + var self = this; + + var proxy = _.extend(new Backbone.Model(), { + id : "", + + url : self.url + "/import", + + toJSON : function() { + return models; + } + }); + + this.listenTo(proxy, "sync", function(proxyModel, models) { + this.add(models, { merge : true}); + this.trigger("save", this); + }); + + return proxy.save(); + }, + + filterModes : { + 'all' : [ + null, + null + ], + 'continuing' : [ + 'status', + 'continuing' + ], + 'ended' : [ + 'status', + 'ended' + ], + 'monitored' : [ + 'monitored', + true + ], + 'missing' : [ + 'downloaded', + false + ] + }, + + sortMappings : { + title : { + sortKey : 'sortTitle' + }, + statusWeight : { + sortValue : function(model, attr) { + if (model.getStatus() == "released") { + return 1; + } + if (model.getStatus() == "inCinemas") { + return 0; + } + return -1; + } + }, + downloadedQuality : { + sortValue : function(model, attr) { + if (model.get("movieFile")) { + return 1000-model.get("movieFile").quality.quality.id; + } + + return -1; + } + }, + nextAiring : { + sortValue : function(model, attr, order) { + var nextAiring = model.get(attr); + + if (nextAiring) { + return moment(nextAiring).unix(); + } + + if (order === 1) { + return 0; + } + + return Number.MAX_VALUE; + } + }, + status: { + sortValue : function(model, attr) { + debugger; + if (model.get("downloaded")) { + return -1; + } + return 0; + } + }, + percentOfEpisodes : { + sortValue : function(model, attr) { + var percentOfEpisodes = model.get(attr); + var episodeCount = model.get('episodeCount'); + + return percentOfEpisodes + episodeCount / 1000000; + } + }, + inCinemas : { + + sortValue : function(model, attr) { + var monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ]; + if (model.get("inCinemas")) { + return model.get("inCinemas"); + } + return "2100-01-01"; + } + }, + path : { + sortValue : function(model) { + var path = model.get('path'); + + return path.toLowerCase(); + } + } + } }); Collection = AsFilteredCollection.call(Collection); From e3e67d109824b949c70559f6f81462b7004c27dc Mon Sep 17 00:00:00 2001 From: Tim Turner Date: Sat, 28 Jan 2017 12:03:45 -0500 Subject: [PATCH 48/52] Clean up settings UI --- src/UI/Settings/SettingsLayoutTemplate.hbs | 2 +- src/UI/Settings/settings.less | 10 +--------- src/UI/Settings/thingy.less | 6 +++++- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/UI/Settings/SettingsLayoutTemplate.hbs b/src/UI/Settings/SettingsLayoutTemplate.hbs index 440f25895..c605e061f 100644 --- a/src/UI/Settings/SettingsLayoutTemplate.hbs +++ b/src/UI/Settings/SettingsLayoutTemplate.hbs @@ -4,7 +4,7 @@
  • Quality
  • Indexers
  • Download Client
  • -
  • Net Import
  • +
  • Lists
  • Connect
  • General
  • diff --git a/src/UI/Settings/settings.less b/src/UI/Settings/settings.less index cd2ee76b5..b5b788971 100644 --- a/src/UI/Settings/settings.less +++ b/src/UI/Settings/settings.less @@ -112,15 +112,7 @@ li.save-and-add:hover { .settings-tabs { li>a { padding : 10px; - } - - @media (min-width: @screen-sm-min) and (max-width: @screen-md-max) { - li { - a { - white-space : nowrap; - padding : 10px; - } - } + white-space : nowrap; } } diff --git a/src/UI/Settings/thingy.less b/src/UI/Settings/thingy.less index 2368240b7..3b7a46b19 100644 --- a/src/UI/Settings/thingy.less +++ b/src/UI/Settings/thingy.less @@ -8,6 +8,10 @@ font-weight: lighter; text-align: center; height: 85px; + + .long-title { + font-size: 16px; + } } .add-thingies { @@ -36,7 +40,7 @@ h3 { margin-top: 0px; - display: inline-block; + //display: inline-block; //this stops the text-overflow from applying white-space: nowrap; overflow: hidden; line-height: 30px; From 7ca53d10732d63e9d7d221e3125431e353a94d4b Mon Sep 17 00:00:00 2001 From: Tim Turner Date: Sat, 28 Jan 2017 12:16:50 -0500 Subject: [PATCH 49/52] Only show "Display Existing Movies" toggle after selecting a folder --- src/UI/AddMovies/AddMoviesLayout.js | 20 ++++++---- src/UI/AddMovies/AddMoviesLayoutTemplate.hbs | 40 ++++++++++---------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/UI/AddMovies/AddMoviesLayout.js b/src/UI/AddMovies/AddMoviesLayout.js index 00344bfdb..a219833e5 100644 --- a/src/UI/AddMovies/AddMoviesLayout.js +++ b/src/UI/AddMovies/AddMoviesLayout.js @@ -13,7 +13,11 @@ module.exports = Marionette.Layout.extend({ template : 'AddMovies/AddMoviesLayoutTemplate', regions : { - workspace : '#add-movies-workspace' + workspace : '#add-movies-workspace', + }, + + ui : { + $existing : '#show-existing-movies-toggle' }, events : { @@ -32,25 +36,26 @@ module.exports = Marionette.Layout.extend({ RootFolderCollection.fetch().done(function() { RootFolderCollection.synced = true; }); - - }, _toggleExisting : function(e) { - var showExisting = e.target.checked; + var showExisting = e.target.checked; - vent.trigger(vent.Commands.ShowExistingCommand, { - showExisting: showExisting - }); + vent.trigger(vent.Commands.ShowExistingCommand, { + showExisting: showExisting + }); }, onShow : function() { + this.workspace.show(new AddMoviesView()); + this.ui.$existing.hide(); }, _folderSelected : function(options) { vent.trigger(vent.Commands.CloseModalCommand); + this.ui.$existing.show(); this.workspace.show(new ExistingMoviesCollectionView({ model : options.model })); }, @@ -65,6 +70,7 @@ module.exports = Marionette.Layout.extend({ }, _addFromList : function() { + this.ui.$existing.hide(); this.workspace.show(new AddFromListView()); } }); diff --git a/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs b/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs index 0e251dcb7..48667d34c 100644 --- a/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs +++ b/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs @@ -11,31 +11,33 @@
    -
    -
    -
    - +
    +
    +
    +
    + -
    -
    -
    +
    +
    From 1fd909cff6f0d4a1025e7b0d2fd5ef792e4446d1 Mon Sep 17 00:00:00 2001 From: Tim Turner Date: Sat, 28 Jan 2017 13:27:54 -0500 Subject: [PATCH 50/52] Net Import UI Updates - Change name to "StevenLu" to fix - Hide header on New List modal - Show "Import Selected" only after a list has been fetched --- src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs | 2 +- src/UI/AddMovies/List/AddFromListView.js | 3 +++ src/UI/Settings/NetImport/Add/NetImportSchemaModal.js | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs index 1f3b76039..95c1d9b9e 100644 --- a/src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs +++ b/src/NzbDrone.Core/NetImport/StevenLu/StevenLuImport.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.NetImport.StevenLu { public class StevenLuImport : HttpNetImportBase { - public override string Name => "Popular movies from StevenLu"; + public override string Name => "StevenLu"; public override bool Enabled => true; public override bool EnableAuto => true; diff --git a/src/UI/AddMovies/List/AddFromListView.js b/src/UI/AddMovies/List/AddFromListView.js index d2605c74f..335380f94 100644 --- a/src/UI/AddMovies/List/AddFromListView.js +++ b/src/UI/AddMovies/List/AddFromListView.js @@ -102,10 +102,12 @@ module.exports = Marionette.Layout.extend({ onRender : function() { var self = this; + this.ui.importSelected.hide(); }, onShow : function() { this.ui.moviesSearch.focus(); + }, search : function(options) { @@ -232,6 +234,7 @@ module.exports = Marionette.Layout.extend({ className : 'table table-hover' }); this.fetchResult.show(this.importGrid); + this.ui.importSelected.show(); } }, diff --git a/src/UI/Settings/NetImport/Add/NetImportSchemaModal.js b/src/UI/Settings/NetImport/Add/NetImportSchemaModal.js index bd75e704c..42423ef18 100644 --- a/src/UI/Settings/NetImport/Add/NetImportSchemaModal.js +++ b/src/UI/Settings/NetImport/Add/NetImportSchemaModal.js @@ -19,9 +19,10 @@ module.exports = { var groups = schemaCollection.groupBy(function(model, iterator) { return model.get('protocol'); }); + //key is "undefined", which is being placed in the header var modelCollection = _.map(groups, function(values, key, list) { return { - "header" : key, + //"header" : key, collection : values }; }); From b6e4f5359790b275e3c194d99264401645af12d2 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sat, 28 Jan 2017 14:59:21 -0500 Subject: [PATCH 51/52] Make NetImport sync interval work (needs some testing) --- .../Config/NetImportConfigModule.cs | 2 + .../Config/NetImportConfigResource.cs | 2 + src/NzbDrone.Api/NzbDrone.Api.csproj | 1 + .../NetImportSyncIntervalValidator.cs | 34 ++ .../Validation/RuleBuilderExtensions.cs | 5 + .../Configuration/ConfigService.cs | 7 + .../Configuration/IConfigService.cs | 2 + src/NzbDrone.Core/Jobs/TaskManager.cs | 24 +- .../Organizer/FileNameBuilder.cs | 370 ++---------------- .../Options/NetImportOptionsViewTemplate.hbs | 2 +- 10 files changed, 102 insertions(+), 347 deletions(-) create mode 100644 src/NzbDrone.Api/Validation/NetImportSyncIntervalValidator.cs diff --git a/src/NzbDrone.Api/Config/NetImportConfigModule.cs b/src/NzbDrone.Api/Config/NetImportConfigModule.cs index 3cd194116..f805e8c2d 100644 --- a/src/NzbDrone.Api/Config/NetImportConfigModule.cs +++ b/src/NzbDrone.Api/Config/NetImportConfigModule.cs @@ -10,6 +10,8 @@ namespace NzbDrone.Api.Config public NetImportConfigModule(IConfigService configService) : base(configService) { + SharedValidator.RuleFor(c => c.NetImportSyncInterval) + .IsValidNetImportSyncInterval(); } protected override NetImportConfigResource ToResource(IConfigService model) diff --git a/src/NzbDrone.Api/Config/NetImportConfigResource.cs b/src/NzbDrone.Api/Config/NetImportConfigResource.cs index 7b32aefe3..a1502375c 100644 --- a/src/NzbDrone.Api/Config/NetImportConfigResource.cs +++ b/src/NzbDrone.Api/Config/NetImportConfigResource.cs @@ -5,6 +5,7 @@ namespace NzbDrone.Api.Config { public class NetImportConfigResource : RestResource { + public int NetImportSyncInterval { get; set; } } public static class NetImportConfigResourceMapper @@ -13,6 +14,7 @@ namespace NzbDrone.Api.Config { return new NetImportConfigResource { + NetImportSyncInterval = model.NetImportSyncInterval }; } } diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 1f0542788..d467e397e 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -260,6 +260,7 @@ + diff --git a/src/NzbDrone.Api/Validation/NetImportSyncIntervalValidator.cs b/src/NzbDrone.Api/Validation/NetImportSyncIntervalValidator.cs new file mode 100644 index 000000000..b44b3f9e4 --- /dev/null +++ b/src/NzbDrone.Api/Validation/NetImportSyncIntervalValidator.cs @@ -0,0 +1,34 @@ +using FluentValidation.Validators; + +namespace NzbDrone.Api.Validation +{ + public class NetImportSyncIntervalValidator : PropertyValidator + { + public NetImportSyncIntervalValidator() + : base("Must be between 10 and 1440 or 0 to disable") + { + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) + { + return true; + } + + var value = (int)context.PropertyValue; + + if (value == 0) + { + return true; + } + + if (value >= 10 && value <= 1440) + { + return true; + } + + return false; + } + } +} diff --git a/src/NzbDrone.Api/Validation/RuleBuilderExtensions.cs b/src/NzbDrone.Api/Validation/RuleBuilderExtensions.cs index 01a3a4f75..4684d3f12 100644 --- a/src/NzbDrone.Api/Validation/RuleBuilderExtensions.cs +++ b/src/NzbDrone.Api/Validation/RuleBuilderExtensions.cs @@ -36,5 +36,10 @@ namespace NzbDrone.Api.Validation { return ruleBuilder.SetValidator(new RssSyncIntervalValidator()); } + + public static IRuleBuilderOptions IsValidNetImportSyncInterval(this IRuleBuilder ruleBuilder) + { + return ruleBuilder.SetValidator(new NetImportSyncIntervalValidator()); + } } } diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index d19cddd67..639bb69d6 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -105,6 +105,13 @@ namespace NzbDrone.Core.Configuration set { SetValue("RssSyncInterval", value); } } + public int NetImportSyncInterval + { + get { return GetValueInt("NetImportSyncInterval", 60); } + + set { SetValue("NetImportSyncInterval", value); } + } + public int MinimumAge { get { return GetValueInt("MinimumAge", 0); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index e17d8d6dc..a2d56e778 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -46,6 +46,8 @@ namespace NzbDrone.Core.Configuration int RssSyncInterval { get; set; } int MinimumAge { get; set; } + int NetImportSyncInterval { get; set; } + //UI int FirstDayOfWeek { get; set; } string CalendarWeekColumnHeader { get; set; } diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs index 52e564e5d..184dfedb9 100644 --- a/src/NzbDrone.Core/Jobs/TaskManager.cs +++ b/src/NzbDrone.Core/Jobs/TaskManager.cs @@ -75,7 +75,6 @@ namespace NzbDrone.Core.Jobs new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName}, new ScheduledTask{ Interval = updateInterval, TypeName = typeof(ApplicationUpdateCommand).FullName}, // new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName}, - new ScheduledTask{ Interval = 12*60, TypeName = typeof(NetImportSyncCommand).FullName}, new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName}, new ScheduledTask{ Interval = 24*60, TypeName = typeof(RefreshMovieCommand).FullName}, new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName}, @@ -87,6 +86,12 @@ namespace NzbDrone.Core.Jobs TypeName = typeof(RssSyncCommand).FullName }, + new ScheduledTask + { + Interval = GetNetImportSyncInterval(), + TypeName = typeof(NetImportSyncCommand).FullName + }, + new ScheduledTask { Interval = _configService.DownloadedEpisodesScanInterval, @@ -140,6 +145,23 @@ namespace NzbDrone.Core.Jobs return interval; } + private int GetNetImportSyncInterval() + { + var interval = _configService.NetImportSyncInterval; + + if (interval > 0 && interval < 10) + { + return 10; + } + + if (interval < 0) + { + return 0; + } + + return interval; + } + public void Handle(CommandExecutedEvent message) { var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.Command.Body.GetType().FullName); diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index f6d5b1ecd..32422092e 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -1,337 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using NLog; -using NzbDrone.Common.Cache; -using NzbDrone.Common.EnsureThat; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Organizer -{ - public interface IBuildFileNames - { - string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null); - string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null); - string BuildFilePath(Movie movie, string fileName, string extension); - string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); - string BuildSeasonPath(Series series, int seasonNumber); - BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); - string GetSeriesFolder(Series series, NamingConfig namingConfig = null); - string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null); - string GetMovieFolder(Movie movie, NamingConfig namingConfig = null); - } - - public class FileNameBuilder : IBuildFileNames - { - private readonly INamingConfigService _namingConfigService; - private readonly IQualityDefinitionService _qualityDefinitionService; - private readonly ICached _episodeFormatCache; - private readonly ICached _absoluteEpisodeFormatCache; - private readonly Logger _logger; - - private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._\[(]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._)\]]*)\}", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex EpisodeRegex = new Regex(@"(?\{episode(?:\:0+)?})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex SeasonRegex = new Regex(@"(?\{season(?:\:0+)?})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex AbsoluteEpisodeRegex = new Regex(@"(?\{absolute(?:\:0+)?})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?s?{season(?:\:0+)?}(?[- ._]?[ex])(?{episode(?:\:0+)?}))(?[- ._]+?(?={))?", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?{absolute(?:\:0+)?})(?[- ._]+?(?={))?", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(Clean)?Title\})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex MovieTitleRegex = new Regex(@"(?\{((?:(Movie|Original))(?[- ._])(Clean)?Title(The)?)\})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); - private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled); - - private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - //TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc) - private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' }; - - public FileNameBuilder(INamingConfigService namingConfigService, - IQualityDefinitionService qualityDefinitionService, - ICacheManager cacheManager, - Logger logger) - { - _namingConfigService = namingConfigService; - _qualityDefinitionService = qualityDefinitionService; - //_movieFormatCache = cacheManager.GetCache(GetType(), "movieFormat"); - _episodeFormatCache = cacheManager.GetCache(GetType(), "episodeFormat"); - _absoluteEpisodeFormatCache = cacheManager.GetCache(GetType(), "absoluteEpisodeFormat"); - _logger = logger; - } - - public string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - if (!namingConfig.RenameEpisodes) - { - return GetOriginalTitle(episodeFile); - } - - if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard) - { - throw new NamingFormatException("Standard episode format cannot be empty"); - } - - if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily) - { - throw new NamingFormatException("Daily episode format cannot be empty"); - } - - if (namingConfig.AnimeEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Anime) - { - throw new NamingFormatException("Anime episode format cannot be empty"); - } - - var pattern = namingConfig.StandardEpisodeFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList(); - - if (series.SeriesType == SeriesTypes.Daily) - { - pattern = namingConfig.DailyEpisodeFormat; - } - - if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber.HasValue)) - { - pattern = namingConfig.AnimeEpisodeFormat; - } - - pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig); - pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig); - - AddSeriesTokens(tokenHandlers, series); - AddEpisodeTokens(tokenHandlers, episodes); - AddEpisodeFileTokens(tokenHandlers, episodeFile); - AddQualityTokens(tokenHandlers, series, episodeFile); - AddMediaInfoTokens(tokenHandlers, episodeFile); - - var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); - fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); - fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); - - return fileName; - } - - public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - if (!namingConfig.RenameEpisodes) - { - return GetOriginalTitle(movieFile); - } - - //TODO: Update namingConfig for Movies! - var pattern = namingConfig.StandardMovieFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year - AddImdbIdTokens(tokenHandlers, movie.ImdbId); - AddQualityTokens(tokenHandlers, movie, movieFile); - AddMediaInfoTokens(tokenHandlers, movieFile); - AddMovieFileTokens(tokenHandlers, movieFile); - - var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); - fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); - fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); - - return fileName; - } - - public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) - { - Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); - - var path = BuildSeasonPath(series, seasonNumber); - - return Path.Combine(path, fileName + extension); - } - - public string BuildFilePath(Movie movie, string fileName, string extension) - { - Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); - - var path = movie.Path; - - return Path.Combine(path, fileName + extension); - } - - public string BuildSeasonPath(Series series, int seasonNumber) - { - var path = series.Path; - - if (series.SeasonFolder) - { - if (seasonNumber == 0) - { - path = Path.Combine(path, "Specials"); - } - else - { - var seasonFolder = GetSeasonFolder(series, seasonNumber); - - seasonFolder = CleanFileName(seasonFolder); - - path = Path.Combine(path, seasonFolder); - } - } - - return path; - } - - public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) - { - return new BasicNamingConfig(); //For now let's be lazy - - var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault(); - - if (episodeFormat == null) - { - return new BasicNamingConfig(); - } - - var basicNamingConfig = new BasicNamingConfig - { - Separator = episodeFormat.Separator, - NumberStyle = episodeFormat.SeasonEpisodePattern - }; - - var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat); - - foreach (Match match in titleTokens) - { - var separator = match.Groups["separator"].Value; - var token = match.Groups["token"].Value; - - if (!separator.Equals(" ")) - { - basicNamingConfig.ReplaceSpaces = true; - } - - if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase)) - { - basicNamingConfig.IncludeSeriesTitle = true; - } - - if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase)) - { - basicNamingConfig.IncludeEpisodeTitle = true; - } - - if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase)) - { - basicNamingConfig.IncludeQuality = true; - } - } - - return basicNamingConfig; - } - - public string GetSeriesFolder(Series series, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddSeriesTokens(tokenHandlers, series); - - return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig)); - } - - public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddSeriesTokens(tokenHandlers, series); - AddSeasonTokens(tokenHandlers, seasonNumber); - - return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); - } - - public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null) - { - if(namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); - AddImdbIdTokens(tokenHandlers, movie.ImdbId); - - return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig)); - } - - public static string CleanTitle(string title) - { - title = title.Replace("&", "and"); - title = ScenifyReplaceChars.Replace(title, " "); - title = ScenifyRemoveChars.Replace(title, string.Empty); - - return title; - } - - public static string TitleThe(string title) - { - string[] prefixes = { "The ", "An ", "A " }; - foreach (string prefix in prefixes) - { - int prefix_length = prefix.Length; - if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower()) - { - title = title.Substring(prefix_length) + ", " + prefix.Trim(); - break; - } - } - - return title.Trim(); - } - using System; using System.Collections.Generic; using System.Globalization; @@ -472,7 +138,7 @@ namespace NzbDrone.Core.Organizer AddEpisodeFileTokens(tokenHandlers, episodeFile); AddQualityTokens(tokenHandlers, series, episodeFile); AddMediaInfoTokens(tokenHandlers, episodeFile); - + var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); @@ -564,10 +230,10 @@ namespace NzbDrone.Core.Organizer } var basicNamingConfig = new BasicNamingConfig - { - Separator = episodeFormat.Separator, - NumberStyle = episodeFormat.SeasonEpisodePattern - }; + { + Separator = episodeFormat.Separator, + NumberStyle = episodeFormat.SeasonEpisodePattern + }; var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat); @@ -631,7 +297,7 @@ namespace NzbDrone.Core.Organizer public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null) { - if(namingConfig == null) + if (namingConfig == null) { namingConfig = _namingConfigService.GetConfig(); } @@ -654,6 +320,20 @@ namespace NzbDrone.Core.Organizer return title; } + public static string TitleThe(string title) + { + string[] prefixes = { "The ", "An ", "A " }; + foreach (string prefix in prefixes) + { + int prefix_length = prefix.Length; + if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower()) + { + title = title.Substring(prefix_length) + ", " + prefix.Trim(); + break; + } + } + + return title.Trim(); } public static string CleanFileName(string name, bool replace = true) @@ -763,7 +443,7 @@ namespace NzbDrone.Core.Organizer var absoluteEpisodePattern = absoluteEpisodeFormat.AbsoluteEpisodePattern; string formatPattern; - switch ((MultiEpisodeStyle) namingConfig.MultiEpisodeStyle) + switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle) { case MultiEpisodeStyle.Duplicate: @@ -786,14 +466,14 @@ namespace NzbDrone.Core.Organizer case MultiEpisodeStyle.Range: case MultiEpisodeStyle.PrefixedRange: formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern; - var eps = new List {episodes.First()}; + var eps = new List { episodes.First() }; if (episodes.Count > 1) eps.Add(episodes.Last()); absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, eps); break; - //MultiEpisodeStyle.Extend + //MultiEpisodeStyle.Extend default: formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern; absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes); @@ -1241,7 +921,7 @@ namespace NzbDrone.Core.Organizer private AbsoluteEpisodeFormat[] GetAbsoluteFormat(string pattern) { - return _absoluteEpisodeFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType() + return _absoluteEpisodeFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType() .Select(match => new AbsoluteEpisodeFormat { Separator = match.Groups["separator"].Value.IsNotNullOrWhiteSpace() ? match.Groups["separator"].Value : "-", @@ -1396,4 +1076,4 @@ namespace NzbDrone.Core.Organizer Range = 4, PrefixedRange = 5 } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs b/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs index ab42a0351..6e6072609 100644 --- a/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs +++ b/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs @@ -10,7 +10,7 @@
    - +
    From d458c4ecc819b8b3e327eb8d637c8b34b70ba0ad Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sat, 28 Jan 2017 15:30:46 -0500 Subject: [PATCH 52/52] update taskscheduler when config is saved with netimportsynccommand --- src/NzbDrone.Core/Jobs/TaskManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs index 184dfedb9..c2739b506 100644 --- a/src/NzbDrone.Core/Jobs/TaskManager.cs +++ b/src/NzbDrone.Core/Jobs/TaskManager.cs @@ -181,7 +181,10 @@ namespace NzbDrone.Core.Jobs var downloadedEpisodes = _scheduledTaskRepository.GetDefinition(typeof(DownloadedEpisodesScanCommand)); downloadedEpisodes.Interval = _configService.DownloadedEpisodesScanInterval; - _scheduledTaskRepository.UpdateMany(new List { rss, downloadedEpisodes }); + var netImport = _scheduledTaskRepository.GetDefinition(typeof(NetImportSyncCommand)); + netImport.Interval = _configService.NetImportSyncInterval; + + _scheduledTaskRepository.UpdateMany(new List { rss, downloadedEpisodes, netImport }); } } }