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
pull/744/head
Devin Buhl 8 years ago committed by GitHub
parent c5bb259555
commit 1e28a2e5d4

@ -150,4 +150,41 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public string size { get; set; } public string size { get; set; }
public string type { 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; }
}
} }

@ -27,9 +27,9 @@ namespace NzbDrone.Core.NetImport
public override bool Enabled => true; 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); public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2);
@ -58,13 +58,6 @@ namespace NzbDrone.Core.NetImport
try try
{ {
var fullyUpdated = false;
Movie lastMovie = null;
if (isRecent)
{
//lastReleaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id);
}
for (int i = 0; i < pageableRequestChain.Tiers; i++) for (int i = 0; i < pageableRequestChain.Tiers; i++)
{ {
var pageableRequests = pageableRequestChain.GetTier(i); var pageableRequests = pageableRequestChain.GetTier(i);
@ -80,37 +73,6 @@ namespace NzbDrone.Core.NetImport
var page = FetchPage(request, parser); var page = FetchPage(request, parser);
pagedReleases.AddRange(page); 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); movies.AddRange(pagedReleases);
@ -121,29 +83,9 @@ namespace NzbDrone.Core.NetImport
break; 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) 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") || if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
webException.Message.Contains("timed out")) webException.Message.Contains("timed out"))
{ {
@ -158,28 +100,23 @@ namespace NzbDrone.Core.NetImport
{ {
if ((int)httpException.Response.StatusCode == 429) if ((int)httpException.Response.StatusCode == 429)
{ {
//_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1));
_logger.Warn("API Request Limit reached for {0}", this); _logger.Warn("API Request Limit reached for {0}", this);
} }
else else
{ {
//_indexerStatusService.RecordFailure(Definition.Id);
_logger.Warn("{0} {1}", this, httpException.Message); _logger.Warn("{0} {1}", this, httpException.Message);
} }
} }
catch (RequestLimitReachedException) catch (RequestLimitReachedException)
{ {
//_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1));
_logger.Warn("API Request Limit reached for {0}", this); _logger.Warn("API Request Limit reached for {0}", this);
} }
catch (ApiKeyException) catch (ApiKeyException)
{ {
//_indexerStatusService.RecordFailure(Definition.Id);
_logger.Warn("Invalid API Key for {0} {1}", this, url); _logger.Warn("Invalid API Key for {0} {1}", this, url);
} }
catch (CloudFlareCaptchaException ex) catch (CloudFlareCaptchaException ex)
{ {
//_indexerStatusService.RecordFailure(Definition.Id);
if (ex.IsExpired) if (ex.IsExpired)
{ {
_logger.Error(ex, "Expired CAPTCHA token for {0}, please refresh in indexer settings.", this); _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) catch (IndexerException ex)
{ {
//_indexerStatusService.RecordFailure(Definition.Id);
var message = string.Format("{0} - {1}", ex.Message, url); var message = string.Format("{0} - {1}", ex.Message, url);
_logger.Warn(ex, message); _logger.Warn(ex, message);
} }
catch (Exception feedEx) catch (Exception feedEx)
{ {
//_indexerStatusService.RecordFailure(Definition.Id);
feedEx.Data.Add("FeedUrl", url); feedEx.Data.Add("FeedUrl", url);
_logger.Error(feedEx, "An error occurred while processing feed. " + url); _logger.Error(feedEx, "An error occurred while processing feed. " + url);
} }
@ -205,11 +140,6 @@ namespace NzbDrone.Core.NetImport
return movies; return movies;
} }
protected virtual bool IsFullPage(IList<Movie> page)
{
return PageSize != 0 && page.Count >= PageSize;
}
protected virtual IList<Movie> FetchPage(NetImportRequest request, IParseNetImportResponse parser) protected virtual IList<Movie> FetchPage(NetImportRequest request, IParseNetImportResponse parser)
{ {
var response = FetchIndexerResponse(request); var response = FetchIndexerResponse(request);

@ -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<TMDbSettings>
{
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);
}
}
}

@ -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
}
}

@ -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
}
}

@ -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<Tv.Movie> ParseResponse(NetImportResponse importResponse)
{
_importResponse = importResponse;
var movies = new List<Tv.Movie>();
if (!PreProcess(_importResponse))
{
return movies;
}
if (_settings.ListType != (int) TMDbListType.List)
{
var jsonResponse = JsonConvert.DeserializeObject<MovieSearchRoot>(_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<ListResponseRoot>(_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;
}
}
}

@ -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<MovieSearchRoot>(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<NetImportRequest> 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);
}
}
}
}

@ -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<TMDbSettings>
{
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));
}
}
}

@ -127,6 +127,12 @@
<Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" /> <Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" />
<Compile Include="Datastore\Migration\128_remove_kickass.cs" /> <Compile Include="Datastore\Migration\128_remove_kickass.cs" />
<Compile Include="Datastore\Migration\130_remove_wombles_kickass.cs" /> <Compile Include="Datastore\Migration\130_remove_wombles_kickass.cs" />
<Compile Include="NetImport\TMDb\TMDbLanguageCodes.cs" />
<Compile Include="NetImport\TMDb\TMDbSettings.cs" />
<Compile Include="NetImport\TMDb\TMDbListType.cs" />
<Compile Include="NetImport\TMDb\TMDbImport.cs" />
<Compile Include="NetImport\TMDb\TMDbParser.cs" />
<Compile Include="NetImport\TMDb\TMDbRequestGenerator.cs" />
<Compile Include="NetImport\Trakt\TraktAPI.cs" /> <Compile Include="NetImport\Trakt\TraktAPI.cs" />
<Compile Include="NetImport\Trakt\TraktImport.cs" /> <Compile Include="NetImport\Trakt\TraktImport.cs" />
<Compile Include="NetImport\Trakt\TraktListType.cs" /> <Compile Include="NetImport\Trakt\TraktListType.cs" />

@ -27,228 +27,220 @@ require('jquery.dotdotdot');
var SchemaModal = require('../../Settings/NetImport/Add/NetImportSchemaModal'); var SchemaModal = require('../../Settings/NetImport/Add/NetImportSchemaModal');
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
template : 'AddMovies/List/AddFromListViewTemplate', template: 'AddMovies/List/AddFromListViewTemplate',
regions : { regions: {
fetchResult : '#fetch-result' fetchResult: '#fetch-result'
}, },
ui : { ui: {
moviesSearch : '.x-movies-search', moviesSearch: '.x-movies-search',
listSelection : ".x-list-selection", listSelection: ".x-list-selection",
importSelected : ".x-import-selected" importSelected: ".x-import-selected"
}, },
columns : [ columns: [{
{ name: '',
name : '', cell: SelectAllCell,
cell : SelectAllCell, headerCell: 'select-all',
headerCell : 'select-all', sortable: false
sortable : false }, {
}, name: 'title',
{ label: 'Title',
name : 'title', cell: MovieTitleCell,
label : 'Title', cellValue: 'this',
cell : MovieTitleCell, }, {
cellValue : 'this', name: 'profileId',
}, label: 'Profile',
{ cell: ProfileCell,
name : 'profileId', sortable: false,
label : 'Profile', }, {
cell : ProfileCell name: 'this',
}, label: 'Links',
{ cell: MovieLinksCell,
name : 'this', className: "movie-links-cell",
label : 'Links', sortable: false,
cell : MovieLinksCell, }],
className : "movie-links-cell",
sortable : false, events: {
} 'click .x-load-more': '_onLoadMore',
], "change .x-list-selection": "_listSelected",
"click .x-fetch-list": "_fetchList",
events : { "click .x-import-selected": "_importSelected"
'click .x-load-more' : '_onLoadMore', },
"change .x-list-selection" : "_listSelected",
"click .x-fetch-list" : "_fetchList", initialize: function(options) {
"click .x-import-selected" : "_importSelected" console.log(options);
},
this.isExisting = options.isExisting;
initialize : function(options) { //this.collection = new AddFromListCollection();
console.log(options);
this.templateHelpers = {}
this.isExisting = options.isExisting; this.listCollection = new ListCollection();
//this.collection = new AddFromListCollection(); this.templateHelpers.lists = this.listCollection.toJSON();
this.templateHelpers = {} this.listenTo(this.listCollection, 'all', this._listsUpdated);
this.listCollection = new ListCollection(); this.listCollection.fetch();
this.templateHelpers.lists = this.listCollection.toJSON();
this.collection = new AddFromListCollection();
this.listenTo(this.listCollection, 'all', this._listsUpdated);
this.listCollection.fetch(); this.listenTo(this.collection, 'sync', this._showResults);
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,
/*this.listenTo(this.collection, 'sync', this._showResults); isExisting : this.isExisting
});*/
this.resultCollectionView = new SearchResultCollectionView({
collection : this.collection, //this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
isExisting : this.isExisting },
});*/
onRender: function() {
//this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this); var self = this;
}, this.ui.importSelected.hide();
},
onRender : function() {
var self = this; onShow: function() {
this.ui.importSelected.hide(); this.ui.moviesSearch.focus();
},
},
onShow : function() {
this.ui.moviesSearch.focus(); search: function(options) {
var self = this;
},
this.collection.reset();
search : function(options) {
var self = this; if (!options.term || options.term === this.collection.term) {
return Marionette.$.Deferred().resolve();
this.collection.reset(); }
if (!options.term || options.term === this.collection.term) { this.searchResult.show(new LoadingView());
return Marionette.$.Deferred().resolve(); this.collection.term = options.term;
} this.currentSearchPromise = this.collection.fetch({
data: { term: options.term }
this.searchResult.show(new LoadingView()); });
this.collection.term = options.term;
this.currentSearchPromise = this.collection.fetch({ this.currentSearchPromise.fail(function() {
data : { term : options.term } self._showError();
}); });
this.currentSearchPromise.fail(function() { return this.currentSearchPromise;
self._showError(); },
});
_onMoviesAdded: function(options) {
return this.currentSearchPromise; if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) {
}, this.close();
} else if (!this.isExisting) {
_onMoviesAdded : function(options) { this.resultCollectionView.setExisting(options.movie.get('tmdbId'));
if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) { /*this.collection.term = '';
this.close(); this.collection.reset();
} this._clearResults();
this.ui.moviesSearch.val('');
else if (!this.isExisting) { this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result.
this.resultCollectionView.setExisting(options.movie.get('tmdbId')); }
/*this.collection.term = ''; },
this.collection.reset();
this._clearResults(); _onLoadMore: function() {
this.ui.moviesSearch.val(''); var showingAll = this.resultCollectionView.showMore();
this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result. this.ui.searchBar.show();
}
}, if (showingAll) {
this.ui.loadMore.hide();
_onLoadMore : function() { }
var showingAll = this.resultCollectionView.showMore(); },
this.ui.searchBar.show();
_listSelected: function() {
if (showingAll) { var rootFolderValue = this.ui.listSelection.val();
this.ui.loadMore.hide(); if (rootFolderValue === 'addNew') {
} //var rootFolderLayout = new SchemaModal(this.listCollection);
}, //AppLayout.modalRegion.show(rootFolderLayout);
SchemaModal.open(this.listCollection)
_listSelected : function() { }
var rootFolderValue = this.ui.listSelection.val(); },
if (rootFolderValue === 'addNew') {
//var rootFolderLayout = new SchemaModal(this.listCollection); _fetchList: function() {
//AppLayout.modalRegion.show(rootFolderLayout); var self = this;
SchemaModal.open(this.listCollection) var listId = this.ui.listSelection.val();
}
}, this.fetchResult.show(new LoadingView());
_fetchList : function() { this.currentFetchPromise = this.collection.fetch({ data: { listId: listId } })
var self = this; this.currentFetchPromise.fail(function() {
var listId = this.ui.listSelection.val(); self._showError();
});
this.fetchResult.show(new LoadingView());
},
this.currentFetchPromise = this.collection.fetch(
{ data : { listId : listId} } _listsUpdated: function() {
) this.templateHelpers.lists = this.listCollection.toJSON();
this.currentFetchPromise.fail(function() { this.render();
self._showError(); },
});
_importSelected: function() {
}, var selected = this.importGrid.getSelectedModels();
// console.log(selected);
_listsUpdated : function() { var promise = MoviesCollection.importFromList(selected);
this.templateHelpers.lists = this.listCollection.toJSON(); this.ui.importSelected.spinForPromise(promise);
this.render(); this.ui.importSelected.addClass('disabled');
},
Messenger.show({
_importSelected : function() { message: "Importing {0} movies. Don't close this browser window until it has finished".format(selected.length),
var selected = this.importGrid.getSelectedModels(); hideOnNavigate: false,
// console.log(selected); hideAfter: 30,
var promise = MoviesCollection.importFromList(selected); type: "error"
this.ui.importSelected.spinForPromise(promise); });
this.ui.importSelected.addClass('disabled');
promise.done(function() {
Messenger.show({ Messenger.show({
message : "Importing {0} movies. Don't close this browser window until it has finished".format(selected.length), message: "Imported movies from list.",
hideOnNavigate : false, hideAfter: 8,
hideAfter : 30, hideOnNavigate: true
type : "error" });
}); });
/*for (m in selected) {
promise.done(function() { debugger;
Messenger.show({ m.save()
message : "Imported movies from list.", MoviesCollection.add(m);
hideAfter : 8, }*/
hideOnNavigate : true
}); //MoviesCollection.save();
}); },
/*for (m in selected) {
debugger; _clearResults: function() {
m.save()
MoviesCollection.add(m); if (!this.isExisting) {
}*/ this.searchResult.show(new EmptyView());
} else {
//MoviesCollection.save(); this.searchResult.close();
}, }
},
_clearResults : function() {
_showResults: function() {
if (!this.isExisting) { if (this.collection.length === 0) {
this.searchResult.show(new EmptyView()); this.fetchResult.show(new NotFoundView({ term: "" }));
} else { } else {
this.searchResult.close(); this.importGrid = new Backgrid.Grid({
} collection: this.collection,
}, columns: this.columns,
className: 'table table-hover'
_showResults : function() { });
if (this.collection.length === 0) { this.fetchResult.show(this.importGrid);
this.fetchResult.show(new NotFoundView({ term : "" })); this.ui.importSelected.show();
} else { }
this.importGrid = new Backgrid.Grid({
collection : this.collection, },
columns : this.columns,
className : 'table table-hover' _abortExistingSearch: function() {
}); if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
this.fetchResult.show(this.importGrid); console.log('aborting previous pending search request.');
this.ui.importSelected.show(); this.currentSearchPromise.abort();
} } else {
this._clearResults();
}, }
},
_abortExistingSearch : function() {
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) { _showError: function() {
console.log('aborting previous pending search request.'); this.fetchResult.show(new ErrorView({ term: "" }));
this.currentSearchPromise.abort(); }
} else {
this._clearResults();
}
},
_showError : function() {
this.fetchResult.show(new ErrorView({ term : "" }));
}
}); });

@ -1,10 +1,13 @@
<span class="series-info-links"> <span class="series-info-links">
<a href="{{traktUrl}}" class="label label-info">Trakt</a> {{#if tmdbId}}
<a href="{{traktUrl}}" class="label label-info">Trakt</a>
{{/if}}
{{#if website}} {{#if website}}
<a href="{{homepage}}" class="label label-info">Homepage</a> <a href="{{homepage}}" class="label label-info">Homepage</a>
{{/if}}
{{#if tmdbId}}
<a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a>
{{/if}} {{/if}}
<a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a>
{{#if imdbId}} {{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a> <a href="{{imdbUrl}}" class="label label-info">IMDB</a>
{{/if}} {{/if}}

@ -1 +1,5 @@
<a href="{{imdbUrl}}">{{title}}</a> {{#if imdbId}}
<a href="{{imdbUrl}}">{{title}}</a>
{{else}}
<a href="{{tmdbUrl}}">{{title}}</a>
{{/if}}
Loading…
Cancel
Save