diff --git a/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs b/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs new file mode 100644 index 000000000..c4e1d995d --- /dev/null +++ b/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using FluentValidation; +using NzbDrone.Api.ClientSchema; +using NzbDrone.Core.NetImport; +using NzbDrone.Core.NetImport.ImportExclusions; +using NzbDrone.Core.Validation.Paths; + +namespace NzbDrone.Api.NetImport +{ + public class ImportExclusionsModule : NzbDroneRestModule + { + private readonly IImportExclusionsService _exclusionService; + + public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusionsService exclusionService) : base("exclusions") + { + _exclusionService = exclusionService; + GetResourceAll = GetAll; + CreateResource = AddExclusion; + DeleteResource = RemoveExclusion; + GetResourceById = GetById; + } + + public List GetAll() + { + return _exclusionService.GetAllExclusions().ToResource(); + } + + public ImportExclusionsResource GetById(int id) + { + return _exclusionService.GetById(id).ToResource(); + } + + public int AddExclusion(ImportExclusionsResource exclusionResource) + { + var model = exclusionResource.ToModel(); + + return _exclusionService.AddExclusion(model).Id; + } + + public void RemoveExclusion (int id) + { + _exclusionService.RemoveExclusion(new ImportExclusion { Id = id }); + } + } +} diff --git a/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs b/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs new file mode 100644 index 000000000..a3cab77a7 --- /dev/null +++ b/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.NetImport; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Api.NetImport +{ + public class ImportExclusionsResource : ProviderResource + { + //public int Id { get; set; } + public int TmdbId { get; set; } + public string MovieTitle { get; set; } + public int MovieYear { get; set; } + } + + public static class ImportExclusionsResourceMapper + { + public static ImportExclusionsResource ToResource(this Core.NetImport.ImportExclusions.ImportExclusion model) + { + if (model == null) return null; + + return new ImportExclusionsResource + { + Id = model.Id, + TmdbId = model.TmdbId, + MovieTitle = model.MovieTitle, + MovieYear = model.MovieYear + }; + } + + public static List ToResource(this IEnumerable exclusions) + { + return exclusions.Select(ToResource).ToList(); + } + + public static Core.NetImport.ImportExclusions.ImportExclusion ToModel(this ImportExclusionsResource resource) + { + return new Core.NetImport.ImportExclusions.ImportExclusion + { + TmdbId = resource.TmdbId, + MovieTitle = resource.MovieTitle, + MovieYear = resource.MovieYear + }; + } + } +} diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 01e5fcb23..2e44197df 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -271,6 +271,8 @@ + + diff --git a/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs b/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs index a1beecaa9..b6fca0ea2 100644 --- a/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs +++ b/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -80,5 +80,30 @@ namespace NzbDrone.Common.Extensions { return source.Select(predicate).ToList(); } + + public static IEnumerable DropLast(this IEnumerable source, int n) + { + if (source == null) + throw new ArgumentNullException("source"); + + if (n < 0) + throw new ArgumentOutOfRangeException("n", + "Argument n should be non-negative."); + + return InternalDropLast(source, n); + } + + private static IEnumerable InternalDropLast(IEnumerable source, int n) + { + Queue buffer = new Queue(n + 1); + + foreach (T x in source) + { + buffer.Enqueue(x); + + if (buffer.Count == n + 1) + yield return buffer.Dequeue(); + } + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/137_add_import_exclusions_table.cs b/src/NzbDrone.Core/Datastore/Migration/137_add_import_exclusions_table.cs index f865e5754..4fe6c3e6b 100644 --- a/src/NzbDrone.Core/Datastore/Migration/137_add_import_exclusions_table.cs +++ b/src/NzbDrone.Core/Datastore/Migration/137_add_import_exclusions_table.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Collections; using System.Linq; using System.Text.RegularExpressions; +using System.Globalization; +using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Datastore.Migration { @@ -16,7 +18,10 @@ namespace NzbDrone.Core.Datastore.Migration { if (!this.Schema.Schema("dbo").Table("ImportExclusions").Exists()) { - Create.Table("ImportExclusions").WithColumn("tmdbid").AsInt64().NotNullable().Unique().PrimaryKey(); + Create.TableForModel("ImportExclusions") + .WithColumn("TmdbId").AsInt64().NotNullable().Unique().PrimaryKey() + .WithColumn("MovieTitle").AsString().Nullable() + .WithColumn("MovieYear").AsInt64().Nullable().WithDefault(0); } Execute.WithConnection(AddExisting); } @@ -27,18 +32,23 @@ namespace NzbDrone.Core.Datastore.Migration { getSeriesCmd.Transaction = tran; getSeriesCmd.CommandText = @"SELECT Key, Value FROM Config WHERE Key = 'importexclusions'"; + TextInfo textInfo = new CultureInfo("en-US", false).TextInfo; using (IDataReader seriesReader = getSeriesCmd.ExecuteReader()) { while (seriesReader.Read()) { var Key = seriesReader.GetString(0); var Value = seriesReader.GetString(1); - var importExclusions = Value.Split(',').Select(x => "(\""+Regex.Replace(x, @"^.*\-(.*)$", "$1")+"\")").ToList(); + + var importExclusions = Value.Split(',').Select(x => { + return string.Format("(\"{0}\", \"{1}\")", Regex.Replace(x, @"^.*\-(.*)$", "$1"), + textInfo.ToTitleCase(string.Join(" ", x.Split('-').DropLast(1)))); + }).ToList(); using (IDbCommand updateCmd = conn.CreateCommand()) { updateCmd.Transaction = tran; - updateCmd.CommandText = "INSERT INTO ImportExclusions (tmdbid) VALUES " + string.Join(", ", importExclusions); + updateCmd.CommandText = "INSERT INTO ImportExclusions (tmdbid, MovieTitle) VALUES " + string.Join(", ", importExclusions); updateCmd.ExecuteNonQuery(); } diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 839404bf8..5ee687fe3 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -35,6 +35,7 @@ using NzbDrone.Core.Extras.Others; using NzbDrone.Core.Extras.Subtitles; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.NetImport; +using NzbDrone.Core.NetImport.ImportExclusions; namespace NzbDrone.Core.Datastore { @@ -105,6 +106,8 @@ namespace NzbDrone.Core.Datastore .Relationship() .HasOne(s => s.Profile, s => s.ProfileId) .HasOne(m => m.MovieFile, m => m.MovieFileId); + + Mapper.Entity().RegisterModel("ImportExclusions"); Mapper.Entity().RegisterModel("Episodes") diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index d52cbc4b5..2f7b5f2a6 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -16,6 +16,7 @@ using System.Threading; using NzbDrone.Core.Parser; using NzbDrone.Core.Profiles; using NzbDrone.Common.Serializer; +using NzbDrone.Core.NetImport.ImportExclusions; namespace NzbDrone.Core.MetadataSource.SkyHook { @@ -29,8 +30,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook private readonly ITmdbConfigService _configService; private readonly IMovieService _movieService; private readonly IPreDBService _predbService; + private readonly IImportExclusionsService _exclusionService; - public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, ITmdbConfigService configService, IMovieService movieService, IPreDBService predbService, Logger logger) + public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, ITmdbConfigService configService, IMovieService movieService, + IPreDBService predbService, IImportExclusionsService exclusionService, Logger logger) { _httpClient = httpClient; _requestBuilder = requestBuilder.SkyHookTvdb; @@ -38,6 +41,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook _configService = configService; _movieService = movieService; _predbService = predbService; + _exclusionService = exclusionService; _logger = logger; } @@ -351,7 +355,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook public List DiscoverNewMovies(string action) { - string allIds = string.Join(",", _movieService.GetAllMovies().Select(m => m.TmdbId)); + var allMovies = _movieService.GetAllMovies(); + var allExclusions = _exclusionService.GetAllExclusions(); + string allIds = string.Join(",", allMovies.Select(m => m.TmdbId)); + string ignoredIds = string.Join(",", allExclusions.Select(ex => ex.TmdbId)); HttpRequest request; List results; @@ -387,7 +394,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook request.AllowAutoRedirect = true; request.Method = HttpMethod.POST; request.Headers.ContentType = "application/x-www-form-urlencoded"; - request.SetContent($"tmdbids={allIds}"); + request.SetContent($"tmdbids={allIds}&ignoredIds={ignoredIds}"); var response = _httpClient.Post>(request); @@ -399,6 +406,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook results = response.Resource; } + results = results.Where(m => allMovies.None(mo => mo.TmdbId == m.id) && allExclusions.None(ex => ex.TmdbId == m.id)).ToList(); + return results.SelectList(MapMovie); } diff --git a/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusion.cs b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusion.cs new file mode 100644 index 000000000..1f8f1bdae --- /dev/null +++ b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusion.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using Marr.Data; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.MediaFiles; +using System.IO; + +namespace NzbDrone.Core.NetImport.ImportExclusions +{ + public class ImportExclusion : ModelBase + { + public int TmdbId { get; set; } + public string MovieTitle { get; set; } + public int MovieYear { get; set; } + + new public string ToString() + { + return string.Format("Excluded Movie: [{0}][{1} {2}]", TmdbId, MovieTitle, MovieYear); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsRepository.cs b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsRepository.cs new file mode 100644 index 000000000..35689b679 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsRepository.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Datastore.Extensions; +using Marr.Data.QGen; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Parser.RomanNumerals; +using NzbDrone.Core.Qualities; +using CoreParser = NzbDrone.Core.Parser.Parser; + +namespace NzbDrone.Core.NetImport.ImportExclusions +{ + public interface IImportExclusionsRepository : IBasicRepository + { + bool IsMovieExcluded(int tmdbid); + } + + public class ImportExclusionsRepository : BasicRepository, IImportExclusionsRepository + { + protected IMainDatabase _database; + + public ImportExclusionsRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + _database = database; + } + + public bool IsMovieExcluded(int tmdbid) + { + return Query.Where(ex => ex.TmdbId == tmdbid).Any(); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsService.cs b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsService.cs new file mode 100644 index 000000000..34ff8125d --- /dev/null +++ b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsService.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Tv.Events; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Core.NetImport.ImportExclusions +{ + public interface IImportExclusionsService + { + List GetAllExclusions(); + bool IsMovieExcluded(int tmdbid); + ImportExclusion AddExclusion(ImportExclusion exclusion); + void RemoveExclusion(ImportExclusion exclusion); + ImportExclusion GetById(int id); + } + + public class ImportExclusionsService : IImportExclusionsService + { + private readonly IImportExclusionsRepository _exclusionRepository; + private readonly IConfigService _configService; + private readonly IEventAggregator _eventAggregator; + private readonly Logger _logger; + + + public ImportExclusionsService(IImportExclusionsRepository exclusionRepository, + IEventAggregator eventAggregator, + IConfigService configService, + Logger logger) + { + _exclusionRepository = exclusionRepository; + _eventAggregator = eventAggregator; + _configService = configService; + _logger = logger; + } + + public ImportExclusion AddExclusion(ImportExclusion exclusion) + { + return _exclusionRepository.Insert(exclusion); + } + + public List GetAllExclusions() + { + return _exclusionRepository.All().ToList(); + } + + public bool IsMovieExcluded(int tmdbid) + { + return _exclusionRepository.IsMovieExcluded(tmdbid); + } + + public void RemoveExclusion(ImportExclusion exclusion) + { + _exclusionRepository.Delete(exclusion); + } + + public ImportExclusion GetById(int id) + { + return _exclusionRepository.Get(id); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs index 0fea4812d..b54352cef 100644 --- a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs +++ b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs @@ -13,6 +13,7 @@ using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.IndexerSearch; +using NzbDrone.Core.NetImport.ImportExclusions; namespace NzbDrone.Core.NetImport { @@ -32,11 +33,14 @@ namespace NzbDrone.Core.NetImport private readonly IConfigService _configService; private readonly ISearchForNzb _nzbSearchService; private readonly IProcessDownloadDecisions _processDownloadDecisions; + private readonly IImportExclusionsService _exclusionService; public NetImportSearchService(INetImportFactory netImportFactory, IMovieService movieService, ISearchForNewMovie movieSearch, IRootFolderService rootFolder, ISearchForNzb nzbSearchService, - IProcessDownloadDecisions processDownloadDecisions, IConfigService configService, Logger logger) + IProcessDownloadDecisions processDownloadDecisions, IConfigService configService, + IImportExclusionsService exclusionService, + Logger logger) { _netImportFactory = netImportFactory; _movieService = movieService; @@ -44,6 +48,7 @@ namespace NzbDrone.Core.NetImport _nzbSearchService = nzbSearchService; _processDownloadDecisions = processDownloadDecisions; _rootFolder = rootFolder; + _exclusionService = exclusionService; _logger = logger; _configService = configService; } @@ -119,18 +124,12 @@ namespace NzbDrone.Core.NetImport var importExclusions = new List(); - if (_configService.ImportExclusions != null) - { - // Replace `movie-title-tmdbid` with just tmdbid in exclusions - importExclusions = _configService.ImportExclusions.Split(',').Select(x => Regex.Replace(x, @"^.*\-(.*)$", "$1")).ToList(); - // listedMovies = listedMovies.Where(ah => importExclusions.Any(h => ah.TmdbId.ToString() != h)).ToList(); - } //var downloadedCount = 0; foreach (var movie in listedMovies) { var mapped = _movieSearch.MapMovieToTmdbMovie(movie); - if (mapped != null && !importExclusions.Any(x => x == mapped.TmdbId.ToString())) + if (mapped != null && !_exclusionService.IsMovieExcluded(mapped.TmdbId)) { //List decisions; mapped.AddOptions = new AddMovieOptions {SearchForMovie = true}; diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 770261960..d25400946 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -1281,6 +1281,9 @@ + + + @@ -1350,6 +1353,9 @@ + + + diff --git a/src/NzbDrone.Core/Tv/MovieService.cs b/src/NzbDrone.Core/Tv/MovieService.cs index f998ab578..1d005bced 100644 --- a/src/NzbDrone.Core/Tv/MovieService.cs +++ b/src/NzbDrone.Core/Tv/MovieService.cs @@ -14,6 +14,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Datastore; using NzbDrone.Core.Configuration; +using NzbDrone.Core.NetImport.ImportExclusions; namespace NzbDrone.Core.Tv { @@ -51,6 +52,7 @@ namespace NzbDrone.Core.Tv private readonly IConfigService _configService; private readonly IEventAggregator _eventAggregator; private readonly IBuildFileNames _fileNameBuilder; + private readonly IImportExclusionsService _exclusionService; private readonly Logger _logger; @@ -60,12 +62,14 @@ namespace NzbDrone.Core.Tv IEpisodeService episodeService, IBuildFileNames fileNameBuilder, IConfigService configService, + IImportExclusionsService exclusionService, Logger logger) { _movieRepository = movieRepository; _eventAggregator = eventAggregator; _fileNameBuilder = fileNameBuilder; _configService = configService; + _exclusionService = exclusionService; _logger = logger; } @@ -286,14 +290,7 @@ namespace NzbDrone.Core.Tv var movie = _movieRepository.Get(movieId); if (addExclusion) { - if (_configService.ImportExclusions.Empty()) - { - _configService.ImportExclusions = movie.TitleSlug; - } - else if (!_configService.ImportExclusions.Contains(movie.TitleSlug)) - { - _configService.ImportExclusions += ',' + movie.TitleSlug; - } + _exclusionService.AddExclusion(new ImportExclusion {TmdbId = movie.TmdbId, MovieTitle = movie.Title, MovieYear = movie.Year } ); } _movieRepository.Delete(movieId); _eventAggregator.PublishEvent(new MovieDeletedEvent(movie, deleteFiles)); diff --git a/src/UI/AddMovies/AddMoviesView.js b/src/UI/AddMovies/AddMoviesView.js index c5f583803..97aad1c04 100644 --- a/src/UI/AddMovies/AddMoviesView.js +++ b/src/UI/AddMovies/AddMoviesView.js @@ -6,6 +6,7 @@ var AddMoviesCollection = require('./AddMoviesCollection'); var SearchResultCollectionView = require('./SearchResultCollectionView'); var EmptyView = require('./EmptyView'); var NotFoundView = require('./NotFoundView'); +var DiscoverEmptyView = require('./DiscoverEmptyView'); var ErrorView = require('./ErrorView'); var LoadingView = require('../Shared/LoadingView'); var FullMovieCollection = require("../Movies/FullMovieCollection"); @@ -112,14 +113,10 @@ module.exports = Marionette.Layout.extend({ if (this.isDiscover) { this.ui.searchBar.hide(); - if (FullMovieCollection.length > 0) { - this._discoverRecos(); - } else { - this.listenTo(FullMovieCollection, "sync", this._discover); - } - if (this.collection.length == 0) { + this._discoverRecos(); + /*if (this.collection.length == 0) { this.searchResult.show(new LoadingView()); - } + }*/ } }, @@ -192,8 +189,13 @@ 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 })); + if (this.isDiscover) { + this.searchResult.show(new DiscoverEmptyView()); + } else { + this.ui.searchBar.show(); + this.searchResult.show(new NotFoundView({ term : this.collection.term })); + } + } else { this.searchResult.show(this.resultCollectionView); if (!this.showingAll) { @@ -224,11 +226,10 @@ module.exports = Marionette.Layout.extend({ if (this.collection.action === action) { return } + this.collection.reset(); this.searchResult.show(new LoadingView()); this.collection.action = action; - this.collection.fetch({ - data : { action : action } - }); + this.currentSearchPromise = this.collection.fetch(); }, _discoverRecos : function() { diff --git a/src/UI/AddMovies/DiscoverEmptyView.js b/src/UI/AddMovies/DiscoverEmptyView.js new file mode 100644 index 000000000..77fc1f139 --- /dev/null +++ b/src/UI/AddMovies/DiscoverEmptyView.js @@ -0,0 +1,5 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.CompositeView.extend({ + template : 'AddMovies/DiscoverEmptyViewTemplate' +}); diff --git a/src/UI/AddMovies/DiscoverEmptyViewTemplate.hbs b/src/UI/AddMovies/DiscoverEmptyViewTemplate.hbs new file mode 100644 index 000000000..b9ae586fc --- /dev/null +++ b/src/UI/AddMovies/DiscoverEmptyViewTemplate.hbs @@ -0,0 +1,6 @@ +
+

+ No movies left to discover. Come back at another time :) +

+ +
diff --git a/src/UI/AddMovies/SearchResultView.js b/src/UI/AddMovies/SearchResultView.js index 0b67a7e7a..2840b5307 100644 --- a/src/UI/AddMovies/SearchResultView.js +++ b/src/UI/AddMovies/SearchResultView.js @@ -7,6 +7,7 @@ var Profiles = require('../Profile/ProfileCollection'); var RootFolders = require('./RootFolders/RootFolderCollection'); var RootFolderLayout = require('./RootFolders/RootFolderLayout'); var FullMovieCollection = require('../Movies/FullMovieCollection'); +var ImportExclusionModel = require("../Settings/NetImport/ImportExclusionModel"); var Config = require('../Config'); var Messenger = require('../Shared/Messenger'); var AsValidatedView = require('../Mixins/AsValidatedView'); @@ -33,6 +34,7 @@ var view = Marionette.ItemView.extend({ events : { 'click .x-add' : '_addWithoutSearch', 'click .x-add-search' : '_addAndSearch', + "click .x-ignore" : "_ignoreMovie", 'change .x-profile' : '_profileChanged', 'change .x-root-folder' : '_rootFolderChanged', 'change .x-season-folder' : '_seasonFolderChanged', @@ -239,6 +241,13 @@ var view = Marionette.ItemView.extend({ }); }, + _ignoreMovie : function() { + var exclusion = new ImportExclusionModel({tmdbId : this.model.get("tmdbId"), + movieTitle : this.model.get("title"), movieYear : this.model.get("year")}); + exclusion.save(); + this.remove(); + }, + _rootFoldersUpdated : function() { this._configureTemplateHelpers(); this.render(); diff --git a/src/UI/AddMovies/SearchResultViewTemplate.hbs b/src/UI/AddMovies/SearchResultViewTemplate.hbs index cf58e5df1..901654547 100644 --- a/src/UI/AddMovies/SearchResultViewTemplate.hbs +++ b/src/UI/AddMovies/SearchResultViewTemplate.hbs @@ -106,6 +106,10 @@ + + {{else}} diff --git a/src/UI/Content/icons.less b/src/UI/Content/icons.less index cbe72d3a1..a135e0182 100644 --- a/src/UI/Content/icons.less +++ b/src/UI/Content/icons.less @@ -304,6 +304,10 @@ .fa-icon-color(@brand-danger); } +.icon-sonarr-ignore { + .fa-icon-content(@fa-var-eye-slash); +} + .icon-sonarr-deleted { .fa-icon-content(@fa-var-trash); } diff --git a/src/UI/Settings/NetImport/DeleteExclusionCell.js b/src/UI/Settings/NetImport/DeleteExclusionCell.js new file mode 100644 index 000000000..9a8fa010e --- /dev/null +++ b/src/UI/Settings/NetImport/DeleteExclusionCell.js @@ -0,0 +1,24 @@ +var vent = require('vent'); +var Backgrid = require('backgrid'); + +module.exports = Backgrid.Cell.extend({ + className : 'delete-episode-file-cell', + + events : { + 'click' : '_onClick' + }, + + render : function() { + this.$el.empty(); + this.$el.html(''); + + return this; + }, + + _onClick : function() { + var self = this; + + this.model.destroy(); + + } +}); diff --git a/src/UI/Settings/NetImport/ExclusionTitleCell.js b/src/UI/Settings/NetImport/ExclusionTitleCell.js new file mode 100644 index 000000000..371f2ad76 --- /dev/null +++ b/src/UI/Settings/NetImport/ExclusionTitleCell.js @@ -0,0 +1,18 @@ +var NzbDroneCell = require('../../Cells/NzbDroneCell'); + +module.exports = NzbDroneCell.extend({ + className : 'exclusion-title-cell', + + render : function() { + this.$el.empty(); + var title = this.model.get("movieTitle"); + var year = this.model.get("movieYear"); + var str = title; + if (year > 1800) { + str += " ("+year+")"; + } + this.$el.html(str); + + return this; + } +}); diff --git a/src/UI/Settings/NetImport/ImportExclusionModel.js b/src/UI/Settings/NetImport/ImportExclusionModel.js new file mode 100644 index 000000000..1adb1f19d --- /dev/null +++ b/src/UI/Settings/NetImport/ImportExclusionModel.js @@ -0,0 +1,7 @@ +var Backbone = require('backbone'); +var _ = require('underscore'); + +module.exports = Backbone.Model.extend({ + urlRoot : window.NzbDrone.ApiRoot + '/exclusions', + +}); diff --git a/src/UI/Settings/NetImport/ImportExclusionsCollection.js b/src/UI/Settings/NetImport/ImportExclusionsCollection.js new file mode 100644 index 000000000..66f911fb3 --- /dev/null +++ b/src/UI/Settings/NetImport/ImportExclusionsCollection.js @@ -0,0 +1,9 @@ +var Backbone = require('backbone'); +var NetImportModel = require('./ImportExclusionModel'); + +var ImportExclusionsCollection = Backbone.Collection.extend({ + model : NetImportModel, + url : window.NzbDrone.ApiRoot + '/exclusions', +}); + +module.exports = new ImportExclusionsCollection(); diff --git a/src/UI/Settings/NetImport/NetImportLayout.js b/src/UI/Settings/NetImport/NetImportLayout.js index def13963e..0da3d1af0 100644 --- a/src/UI/Settings/NetImport/NetImportLayout.js +++ b/src/UI/Settings/NetImport/NetImportLayout.js @@ -3,6 +3,14 @@ var NetImportCollection = require('./NetImportCollection'); var CollectionView = require('./NetImportCollectionView'); var OptionsView = require('./Options/NetImportOptionsView'); var RootFolderCollection = require('../../AddMovies/RootFolders/RootFolderCollection'); +var ImportExclusionsCollection = require('./ImportExclusionsCollection'); +var SelectAllCell = require('../../Cells/SelectAllCell'); +var DeleteExclusionCell = require('./DeleteExclusionCell'); +var ExclusionTitleCell = require("./ExclusionTitleCell"); +var _ = require('underscore'); +var vent = require('vent'); +var Backgrid = require('backgrid'); +var $ = require('jquery'); module.exports = Marionette.Layout.extend({ template : 'Settings/NetImport/NetImportLayoutTemplate', @@ -10,18 +18,58 @@ module.exports = Marionette.Layout.extend({ regions : { lists : '#x-lists-region', listOption : '#x-list-options-region', + importExclusions : "#exclusions" }, + columns: [{ + name: '', + cell: SelectAllCell, + headerCell: 'select-all', + sortable: false + }, { + name: 'tmdbId', + label: 'TMDBID', + cell: Backgrid.StringCell, + sortable: false, + }, { + name: 'movieTitle', + label: 'Title', + cell: ExclusionTitleCell, + cellValue: 'this', + }, { + name: 'this', + label: '', + cell: DeleteExclusionCell, + sortable: false, + }], + + initialize : function() { this.indexersCollection = new NetImportCollection(); this.indexersCollection.fetch(); RootFolderCollection.fetch().done(function() { RootFolderCollection.synced = true; }); + ImportExclusionsCollection.fetch().done(function() { + ImportExclusionsCollection.synced = true; + }); }, onShow : function() { + this.listenTo(ImportExclusionsCollection, "sync", this._showExclusions); + if (ImportExclusionsCollection.synced === true) { + this._showExclusions(); + } this.lists.show(new CollectionView({ collection : this.indexersCollection })); this.listOption.show(new OptionsView({ model : this.model })); + }, + + _showExclusions : function() { + this.exclusionGrid = new Backgrid.Grid({ + collection: ImportExclusionsCollection, + columns: this.columns, + className: 'table table-hover' + }); + this.importExclusions.show(this.exclusionGrid); } }); diff --git a/src/UI/Settings/NetImport/NetImportLayoutTemplate.hbs b/src/UI/Settings/NetImport/NetImportLayoutTemplate.hbs index c97943aa1..0869d3efa 100644 --- a/src/UI/Settings/NetImport/NetImportLayoutTemplate.hbs +++ b/src/UI/Settings/NetImport/NetImportLayoutTemplate.hbs @@ -1,4 +1,9 @@
+
+ Import Exclusions +
+
+
diff --git a/src/UI/Settings/NetImport/Options/NetImportOptionsView.js b/src/UI/Settings/NetImport/Options/NetImportOptionsView.js index d6d1924a4..b5a505830 100644 --- a/src/UI/Settings/NetImport/Options/NetImportOptionsView.js +++ b/src/UI/Settings/NetImport/Options/NetImportOptionsView.js @@ -1,6 +1,11 @@ var Marionette = require('marionette'); var AsModelBoundView = require('../../../Mixins/AsModelBoundView'); var AsValidatedView = require('../../../Mixins/AsValidatedView'); +var ImportExclusionsCollection = require('./../ImportExclusionsCollection'); +var SelectAllCell = require('../../../Cells/SelectAllCell'); +var _ = require('underscore'); +var vent = require('vent'); +var Backgrid = require('backgrid'); var $ = require('jquery'); require('../../../Mixins/TagInput'); require('bootstrap'); @@ -22,10 +27,10 @@ var view = Marionette.ItemView.extend({ 'click .x-revoke-trakt-tokens' : '_revokeTraktTokens' }, - initialize : function() { + initialize : function() { }, - + onShow : function() { var params = new URLSearchParams(window.location.search); var oauth = params.get('access'); @@ -39,78 +44,25 @@ var view = Marionette.ItemView.extend({ //Config.setValue("traktRefreshToken", refresh); var tokenExpiry = Math.floor(Date.now() / 1000) + 4838400; this.ui.tokenExpiry.val(tokenExpiry).trigger('change'); // this means the token will expire in 8 weeks (4838400 seconds) - //Config.setValue("traktTokenExpiry",tokenExpiry); + //Config.setValue("traktTokenExpiry",tokenExpiry); //this.model.isSaved = false; //window.alert("Trakt Authentication Complete - Click Save to make the change take effect"); } if (this.ui.authToken.val() && this.ui.refreshToken.val()){ - this.ui.resetTokensButton.hide(); - this.ui.revokeTokensButton.show(); + this.ui.resetTokensButton.hide(); + this.ui.revokeTokensButton.show(); } else { - this.ui.resetTokensButton.show(); - this.ui.revokeTokensButton.hide(); + this.ui.resetTokensButton.show(); + this.ui.revokeTokensButton.hide(); } - + + + }, onRender : function() { - this.ui.importExclusions.tagsinput({ - trimValue : true, - tagClass : 'label label-danger', - /*itemText : function(item) { - var uri; - var text; - if (item.startsWith('tt')) { - uri = window.NzbDrone.ApiRoot + '/movies/lookup/imdb?imdbId='+item; - } - else { - uri = window.NzbDrone.ApiRoot + '/movies/lookup/tmdb?tmdbId='+item; - } - var promise = $.ajax({ - url : uri, - type : 'GET', - async : false, - }); - promise.success(function(response) { - text=response['title']+' ('+response['year']+')'; - }); - - promise.error(function(request, status, error) { - text=item; - }); - return text; - }*/ - }); - this.ui.importExclusions.on('beforeItemAdd', function(event) { - var uri; - if (event.item.startsWith('tt')) { - uri = window.NzbDrone.ApiRoot + '/movies/lookup/imdb?imdbId='+event.item; - } - else { - uri = window.NzbDrone.ApiRoot + '/movies/lookup/tmdb?tmdbId='+event.item; - } - var promise = $.ajax({ - url : uri, - type : 'GET', - async : false, - }); - promise.success(function(response) { - event.cancel=false; - - //var newText = response['tmdbId']+'-'; - //if (event.item.startsWith('tt')) { - // newText = newText+'['+event.item+']'; - //} - event.item = response.titleSlug;//+' ('+response['year']+')-'+response['tmdbId']; - }); - - promise.error(function(request, status, error) { - event.cancel = true; - window.alert(event.item+' is not a valid! Must be valid tt#### IMDB ID or #### TMDB ID'); - }); - return event; - }); - }, + + }, ui : { resetTraktTokens : '.x-reset-trakt-tokens', @@ -118,7 +70,7 @@ var view = Marionette.ItemView.extend({ refreshToken : '.x-trakt-refresh-token', resetTokensButton : '.x-reset-trakt-tokens', revokeTokensButton : '.x-revoke-trakt-tokens', - tokenExpiry : '.x-trakt-token-expiry', + tokenExpiry : '.x-trakt-token-expiry', importExclusions : '.x-import-exclusions' }, @@ -131,15 +83,16 @@ var view = Marionette.ItemView.extend({ _revokeTraktTokens : function() { if (window.confirm("Log out of trakt.tv?")){ - //TODO: need to implement this: http://docs.trakt.apiary.io/#reference/authentication-oauth/revoke-token/revoke-an-access_token + //TODO: need to implement this: http://docs.trakt.apiary.io/#reference/authentication-oauth/revoke-token/revoke-an-access_token this.ui.authToken.val('').trigger('change'); - this.ui.refreshToken.val('').trigger('change'); - this.ui.tokenExpiry.val(0).trigger('change'); + this.ui.refreshToken.val('').trigger('change'); + this.ui.tokenExpiry.val(0).trigger('change'); this.ui.resetTokensButton.show(); - this.ui.revokeTokensButton.hide(); - window.alert("Logged out of Trakt.tv - Click Save to make the change take effect"); + this.ui.revokeTokensButton.hide(); + window.alert("Logged out of Trakt.tv - Click Save to make the change take effect"); } - } + }, + }); diff --git a/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs b/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs index bd429de47..03bea2134 100644 --- a/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs +++ b/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs @@ -30,17 +30,17 @@ -
+ Trakt Authentication
@@ -58,5 +58,6 @@
-
+ + diff --git a/src/UI/Settings/SettingsLayout.js b/src/UI/Settings/SettingsLayout.js index 8d852ea5f..c22ae8609 100644 --- a/src/UI/Settings/SettingsLayout.js +++ b/src/UI/Settings/SettingsLayout.js @@ -14,6 +14,7 @@ var IndexerCollection = require('./Indexers/IndexerCollection'); var IndexerSettingsModel = require('./Indexers/IndexerSettingsModel'); var NetImportSettingsModel = require("./NetImport/NetImportSettingsModel"); var NetImportCollection = require('./NetImport/NetImportCollection'); +var ImportExclusionsCollection = require('./NetImport/ImportExclusionsCollection'); var NetImportLayout = require('./NetImport/NetImportLayout'); var DownloadClientLayout = require('./DownloadClient/DownloadClientLayout'); var DownloadClientSettingsModel = require('./DownloadClient/DownloadClientSettingsModel');