From 1e28a2e5d425a3a534ef7f71d9eae6490b1f3701 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Mon, 13 Feb 2017 09:11:20 -0500 Subject: [PATCH] Feature/Add TMDb Functionality (#739) * Inital TMDb List, needs paging support and user lists, private or public * Clean up Base * TMDb grabs upto 5 pages for import, update validation, added minimum vote average * Added logic for MovieLinksTemplate * Clean up a bit * Add Public Lists --- .../SkyHook/Resource/TMDBResources.cs | 37 ++ .../NetImport/HttpNetImportBase.cs | 74 +-- .../NetImport/TMDb/TMDbImport.cs | 40 ++ .../NetImport/TMDb/TMDbLanguageCodes.cs | 28 ++ .../NetImport/TMDb/TMDbListType.cs | 18 + .../NetImport/TMDb/TMDbParser.cs | 110 +++++ .../NetImport/TMDb/TMDbRequestGenerator.cs | 104 +++++ .../NetImport/TMDb/TMDbSettings.cs | 55 +++ src/NzbDrone.Core/NzbDrone.Core.csproj | 6 + src/UI/AddMovies/List/AddFromListView.js | 440 +++++++++--------- src/UI/Cells/MovieLinksTemplate.hbs | 11 +- src/UI/Cells/MovieListTitleTemplate.hbs | 6 +- 12 files changed, 628 insertions(+), 301 deletions(-) create mode 100644 src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs create mode 100644 src/NzbDrone.Core/NetImport/TMDb/TMDbLanguageCodes.cs create mode 100644 src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs create mode 100644 src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs create mode 100644 src/NzbDrone.Core/NetImport/TMDb/TMDbRequestGenerator.cs create mode 100644 src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs index 182992c38..f7bb98909 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs @@ -150,4 +150,41 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public string size { get; set; } public string type { get; set; } } + + public class ListResponseRoot + { + public string created_by { get; set; } + public string description { get; set; } + public int favorite_count { get; set; } + public string id { get; set; } + public Item[] items { get; set; } + public int item_count { get; set; } + public string iso_639_1 { get; set; } + public string name { get; set; } + public object poster_path { get; set; } + } + + public class Item + { + public string poster_path { get; set; } + public bool adult { get; set; } + public string overview { get; set; } + public string release_date { get; set; } + public string original_title { get; set; } + public int[] genre_ids { get; set; } + public int id { get; set; } + public string media_type { get; set; } + public string original_language { get; set; } + public string title { get; set; } + public string backdrop_path { get; set; } + public float popularity { get; set; } + public int vote_count { get; set; } + public bool video { get; set; } + public float vote_average { get; set; } + public string first_air_date { get; set; } + public string[] origin_country { get; set; } + public string name { get; set; } + public string original_name { get; set; } + } + } diff --git a/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs b/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs index 4454b2317..8c1b79b1b 100644 --- a/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs +++ b/src/NzbDrone.Core/NetImport/HttpNetImportBase.cs @@ -27,9 +27,9 @@ namespace NzbDrone.Core.NetImport public override bool Enabled => true; - public bool SupportsPaging => PageSize > 0; + public bool SupportsPaging => PageSize > 20; - public virtual int PageSize => 0; + public virtual int PageSize => 20; public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2); @@ -58,13 +58,6 @@ namespace NzbDrone.Core.NetImport 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); @@ -80,37 +73,6 @@ namespace NzbDrone.Core.NetImport 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); @@ -121,29 +83,9 @@ namespace NzbDrone.Core.NetImport 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")) { @@ -158,28 +100,23 @@ namespace NzbDrone.Core.NetImport { 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); @@ -191,13 +128,11 @@ namespace NzbDrone.Core.NetImport } 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); } @@ -205,11 +140,6 @@ namespace NzbDrone.Core.NetImport 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); diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs new file mode 100644 index 000000000..733983bfc --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs @@ -0,0 +1,40 @@ +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; + + +namespace NzbDrone.Core.NetImport.TMDb +{ + public class TMDbImport : HttpNetImportBase + { + public override string Name => "TMDb Lists"; + public override bool Enabled => true; + public override bool EnableAuto => false; + private readonly IHttpClient _httpClient; + private readonly Logger _logger; + + public TMDbImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, + Logger logger) + : base(httpClient, configService, parsingService, logger) + { + _logger = logger; + _httpClient = httpClient; + } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new TMDbRequestGenerator() + { + Settings = Settings, + Logger = _logger, + HttpClient = _httpClient + }; + } + + public override IParseNetImportResponse GetParser() + { + return new TMDbParser(Settings); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbLanguageCodes.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbLanguageCodes.cs new file mode 100644 index 000000000..2c02418e9 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbLanguageCodes.cs @@ -0,0 +1,28 @@ +using System.Runtime.Serialization; + +namespace NzbDrone.Core.NetImport.TMDb +{ + public enum TMDbLanguageCodes + { + da, + nl, + en, + fi, + fr, + de, + el, + hu, + it, + ja, + ko, + no, + pl, + pt, + ru, + es, + sv, + tr, + vi, + zh + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs new file mode 100644 index 000000000..27b18d2e8 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs @@ -0,0 +1,18 @@ +using System.Runtime.Serialization; + +namespace NzbDrone.Core.NetImport.TMDb +{ + public enum TMDbListType + { + [EnumMember(Value = "List")] + List = 0, + [EnumMember(Value = "In Theaters")] + Theaters = 1, + [EnumMember(Value = "Popular")] + Popular = 2, + [EnumMember(Value = "Top Rated")] + Top = 3, + [EnumMember(Value = "Upcoming")] + Upcoming = 4 + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs new file mode 100644 index 000000000..2f8946591 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs @@ -0,0 +1,110 @@ +using Newtonsoft.Json; +using NzbDrone.Core.NetImport.Exceptions; +using System; +using System.Collections.Generic; +using System.Net; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; + +namespace NzbDrone.Core.NetImport.TMDb +{ + public class TMDbParser : IParseNetImportResponse + { + private readonly TMDbSettings _settings; + private NetImportResponse _importResponse; + private readonly Logger _logger; + + public TMDbParser(TMDbSettings settings) + { + _settings = settings; + } + + public IList ParseResponse(NetImportResponse importResponse) + { + _importResponse = importResponse; + + var movies = new List(); + + if (!PreProcess(_importResponse)) + { + return movies; + } + + if (_settings.ListType != (int) TMDbListType.List) + { + var jsonResponse = JsonConvert.DeserializeObject(_importResponse.Content); + + // no movies were return + if (jsonResponse == null) + { + return movies; + } + + foreach (var movie in jsonResponse.results) + { + if (movie.vote_average >= double.Parse(_settings.MinVoteAverage)) + { + movies.AddIfNotNull(new Tv.Movie() + { + Title = movie.title, + TmdbId = movie.id, + ImdbId = null, + Year = DateTime.Parse(movie.release_date).Year + }); + } + } + } + else + { + var jsonResponse = JsonConvert.DeserializeObject(_importResponse.Content); + + // no movies were return + if (jsonResponse == null) + { + return movies; + } + + foreach (var movie in jsonResponse.items) + { + // Skip non-movie things + if (movie.media_type != "movie") + { + continue; + } + + if (movie.vote_average >= double.Parse(_settings.MinVoteAverage)) + { + movies.AddIfNotNull(new Tv.Movie() + { + Title = movie.title, + TmdbId = movie.id, + ImdbId = null, + Year = DateTime.Parse(movie.release_date).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/TMDb/TMDbRequestGenerator.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbRequestGenerator.cs new file mode 100644 index 000000000..ceb491cd9 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbRequestGenerator.cs @@ -0,0 +1,104 @@ +using System; +using NzbDrone.Common.Http; +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; + +namespace NzbDrone.Core.NetImport.TMDb +{ + public class TMDbRequestGenerator : INetImportRequestGenerator + { + public TMDbSettings Settings { get; set; } + public IHttpClient HttpClient { get; set; } + public Logger Logger { get; set; } + + public int MaxPages { get; set; } + + public TMDbRequestGenerator() + { + MaxPages = 3; + } + + public virtual NetImportPageableRequestChain GetMovies() + { + var searchType = ""; + + switch (Settings.ListType) + { + case (int)TMDbListType.List: + searchType = $"/3/list/{Settings.ListId}"; + break; + case (int)TMDbListType.Theaters: + searchType = "/3/movie/now_playing"; + break; + case (int)TMDbListType.Popular: + searchType = "/3/movie/popular"; + break; + case (int)TMDbListType.Top: + searchType = "/3/movie/top_rated"; + break; + case (int)TMDbListType.Upcoming: + searchType = "/3/movie/upcoming"; + break; + } + + var pageableRequests = new NetImportPageableRequestChain(); + if (Settings.ListType != (int) TMDbListType.List) + { + // First query to get the total_Pages + var requestBuilder = new HttpRequestBuilder($"{Settings.Link.Trim()}") + { + LogResponseContent = true + }; + + requestBuilder.Method = HttpMethod.GET; + requestBuilder.Resource(searchType); + + var request = requestBuilder + .AddQueryParam("api_key", "1a7373301961d03f97f853a876dd1212") + .Accept(HttpAccept.Json) + .Build(); + + var response = HttpClient.Execute(request); + var result = Json.Deserialize(response.Content); + + // @TODO Prolly some error handling to do here + pageableRequests.Add(GetPagedRequests(searchType, result.total_pages)); + return pageableRequests; + } + else + { + pageableRequests.Add(GetPagedRequests(searchType, 0)); + return pageableRequests; + } + } + + private IEnumerable GetPagedRequests(string searchType, int totalPages) + { + var baseUrl = $"{Settings.Link.Trim()}{searchType}?api_key=1a7373301961d03f97f853a876dd1212"; + if (Settings.ListType != (int) TMDbListType.List) + { + for (var pageNumber = 1; pageNumber <= totalPages; pageNumber++) + { + // Limit the amount of pages + if (pageNumber >= MaxPages + 1) + { + Logger.Info( + $"Found more than {MaxPages} pages, skipping the {totalPages - (MaxPages + 1)} remaining pages"); + break; + } + + Logger.Trace($"Importing TMDb movies from: {baseUrl}&page={pageNumber}"); + yield return new NetImportRequest($"{baseUrl}&page={pageNumber}", HttpAccept.Json); + } + } + else + { + Logger.Trace($"Importing TMDb movies from: {baseUrl}"); + yield return new NetImportRequest($"{baseUrl}", HttpAccept.Json); + } + + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs new file mode 100644 index 000000000..845f9f566 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Globalization; +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.NetImport.TMDb +{ + + public class TMDbSettingsValidator : AbstractValidator + { + public TMDbSettingsValidator() + { + RuleFor(c => c.Link).ValidRootUrl(); + RuleFor(c => double.Parse(c.MinVoteAverage)).ExclusiveBetween(0, 10); + } + } + + public class TMDbSettings : NetImportBaseSettings + { + private static readonly TMDbSettingsValidator Validator = new TMDbSettingsValidator(); + + public TMDbSettings() + { + Link = "https://api.themoviedb.org"; + MinVoteAverage = "5.5"; + // Language = (int) TMDbLanguageCodes.en; + } + + [FieldDefinition(0, Label = "TMDb API URL", HelpText = "Link to to TMDb API URL, do not change unless you know what you are doing.")] + public new string Link { get; set; } + + [FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TMDbListType), HelpText = "Type of list your seeking to import from")] + public int ListType { get; set; } + + //[FieldDefinition(2, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(TMDbLanguageCodes), HelpText = "Filter movies by Language")] + //public int Language { get; set; } + + [FieldDefinition(2, Label = "Minimum Vote Average", HelpText = "Filter movies by rating (0.0-10.0)")] + public string MinVoteAverage { get; set; } + + [FieldDefinition(3, Label = "Public List ID", HelpText = "Required for List")] + public string ListId { get; set; } + + public new 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 5b4fc58f2..abe9267e5 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -127,6 +127,12 @@ + + + + + + diff --git a/src/UI/AddMovies/List/AddFromListView.js b/src/UI/AddMovies/List/AddFromListView.js index 7ec9fa5e9..9696e4ee3 100644 --- a/src/UI/AddMovies/List/AddFromListView.js +++ b/src/UI/AddMovies/List/AddFromListView.js @@ -27,228 +27,220 @@ require('jquery.dotdotdot'); 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", - importSelected : ".x-import-selected" - }, - - columns : [ - { - name : '', - cell : SelectAllCell, - headerCell : 'select-all', - sortable : false - }, - { - 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", - "click .x-fetch-list" : "_fetchList", - "click .x-import-selected" : "_importSelected" - }, - - 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.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; - this.ui.importSelected.hide(); - }, - - 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 : { listId : listId} } - ) - this.currentFetchPromise.fail(function() { - self._showError(); - }); - - }, - - _listsUpdated : function() { - this.templateHelpers.lists = this.listCollection.toJSON(); - this.render(); - }, - - _importSelected : function() { - var selected = this.importGrid.getSelectedModels(); - // console.log(selected); - var promise = MoviesCollection.importFromList(selected); - this.ui.importSelected.spinForPromise(promise); - this.ui.importSelected.addClass('disabled'); - - Messenger.show({ - message : "Importing {0} movies. Don't close this browser window until it has 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() - MoviesCollection.add(m); - }*/ - - //MoviesCollection.save(); - }, - - _clearResults : function() { - - if (!this.isExisting) { - this.searchResult.show(new EmptyView()); - } else { - this.searchResult.close(); - } - }, - - _showResults : function() { - if (this.collection.length === 0) { - this.fetchResult.show(new NotFoundView({ term : "" })); - } else { - this.importGrid = new Backgrid.Grid({ - collection : this.collection, - columns : this.columns, - className : 'table table-hover' - }); - this.fetchResult.show(this.importGrid); - this.ui.importSelected.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 : "" })); - } + template: 'AddMovies/List/AddFromListViewTemplate', + + regions: { + fetchResult: '#fetch-result' + }, + + ui: { + moviesSearch: '.x-movies-search', + listSelection: ".x-list-selection", + importSelected: ".x-import-selected" + }, + + columns: [{ + name: '', + cell: SelectAllCell, + headerCell: 'select-all', + sortable: false + }, { + name: 'title', + label: 'Title', + cell: MovieTitleCell, + cellValue: 'this', + }, { + name: 'profileId', + label: 'Profile', + cell: ProfileCell, + sortable: false, + }, { + name: 'this', + label: 'Links', + cell: MovieLinksCell, + className: "movie-links-cell", + sortable: false, + }], + + events: { + 'click .x-load-more': '_onLoadMore', + "change .x-list-selection": "_listSelected", + "click .x-fetch-list": "_fetchList", + "click .x-import-selected": "_importSelected" + }, + + 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.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; + this.ui.importSelected.hide(); + }, + + 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: { listId: listId } }) + this.currentFetchPromise.fail(function() { + self._showError(); + }); + + }, + + _listsUpdated: function() { + this.templateHelpers.lists = this.listCollection.toJSON(); + this.render(); + }, + + _importSelected: function() { + var selected = this.importGrid.getSelectedModels(); + // console.log(selected); + var promise = MoviesCollection.importFromList(selected); + this.ui.importSelected.spinForPromise(promise); + this.ui.importSelected.addClass('disabled'); + + Messenger.show({ + message: "Importing {0} movies. Don't close this browser window until it has 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() + MoviesCollection.add(m); + }*/ + + //MoviesCollection.save(); + }, + + _clearResults: function() { + + if (!this.isExisting) { + this.searchResult.show(new EmptyView()); + } else { + this.searchResult.close(); + } + }, + + _showResults: function() { + if (this.collection.length === 0) { + this.fetchResult.show(new NotFoundView({ term: "" })); + } else { + this.importGrid = new Backgrid.Grid({ + collection: this.collection, + columns: this.columns, + className: 'table table-hover' + }); + this.fetchResult.show(this.importGrid); + this.ui.importSelected.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/Cells/MovieLinksTemplate.hbs b/src/UI/Cells/MovieLinksTemplate.hbs index 710132ff5..b6386cedf 100644 --- a/src/UI/Cells/MovieLinksTemplate.hbs +++ b/src/UI/Cells/MovieLinksTemplate.hbs @@ -1,10 +1,13 @@ - Trakt + {{#if tmdbId}} + Trakt + {{/if}} {{#if website}} - Homepage + Homepage + {{/if}} + {{#if tmdbId}} + The Movie DB {{/if}} - The Movie DB - {{#if imdbId}} IMDB {{/if}} diff --git a/src/UI/Cells/MovieListTitleTemplate.hbs b/src/UI/Cells/MovieListTitleTemplate.hbs index 6c4bb964b..f9fef39da 100644 --- a/src/UI/Cells/MovieListTitleTemplate.hbs +++ b/src/UI/Cells/MovieListTitleTemplate.hbs @@ -1 +1,5 @@ -{{title}} +{{#if imdbId}} + {{title}} +{{else}} + {{title}} +{{/if}} \ No newline at end of file