1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-05 02:22:31 +01:00

Completely overhauled how import exclusions work.

Currently new exclusions can only be added when adding new movies or deleting old ones. Not manually in the settings menu.
Movies can now be hidden in the new discover feature by using the new import exclusions!
This commit is contained in:
Leonardo Galli 2017-05-09 20:44:07 +02:00
parent ab28bfead2
commit fc1585e900
28 changed files with 478 additions and 110 deletions

View File

@ -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<ImportExclusionsResource>
{
private readonly IImportExclusionsService _exclusionService;
public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusionsService exclusionService) : base("exclusions")
{
_exclusionService = exclusionService;
GetResourceAll = GetAll;
CreateResource = AddExclusion;
DeleteResource = RemoveExclusion;
GetResourceById = GetById;
}
public List<ImportExclusionsResource> 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 });
}
}
}

View File

@ -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<ImportExclusionsResource> ToResource(this IEnumerable<Core.NetImport.ImportExclusions.ImportExclusion> 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
};
}
}
}

View File

@ -271,6 +271,8 @@
<Compile Include="Wanted\MovieCutoffModule.cs" /> <Compile Include="Wanted\MovieCutoffModule.cs" />
<Compile Include="Wanted\MovieMissingModule.cs" /> <Compile Include="Wanted\MovieMissingModule.cs" />
<Compile Include="Series\MovieDiscoverModule.cs" /> <Compile Include="Series\MovieDiscoverModule.cs" />
<Compile Include="NetImport\ImportExclusionsModule.cs" />
<Compile Include="NetImport\ImportExclusionsResource.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -80,5 +80,30 @@ public static List<TResult> SelectList<TSource, TResult>(this IEnumerable<TSourc
{ {
return source.Select(predicate).ToList(); return source.Select(predicate).ToList();
} }
public static IEnumerable<T> DropLast<T>(this IEnumerable<T> 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<T> InternalDropLast<T>(IEnumerable<T> source, int n)
{
Queue<T> buffer = new Queue<T>(n + 1);
foreach (T x in source)
{
buffer.Enqueue(x);
if (buffer.Count == n + 1)
yield return buffer.Dequeue();
}
}
} }
} }

View File

@ -6,6 +6,8 @@
using System.Collections; using System.Collections;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Globalization;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Datastore.Migration namespace NzbDrone.Core.Datastore.Migration
{ {
@ -16,7 +18,10 @@ protected override void MainDbUpgrade()
{ {
if (!this.Schema.Schema("dbo").Table("ImportExclusions").Exists()) 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); Execute.WithConnection(AddExisting);
} }
@ -27,18 +32,23 @@ private void AddExisting(IDbConnection conn, IDbTransaction tran)
{ {
getSeriesCmd.Transaction = tran; getSeriesCmd.Transaction = tran;
getSeriesCmd.CommandText = @"SELECT Key, Value FROM Config WHERE Key = 'importexclusions'"; getSeriesCmd.CommandText = @"SELECT Key, Value FROM Config WHERE Key = 'importexclusions'";
TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;
using (IDataReader seriesReader = getSeriesCmd.ExecuteReader()) using (IDataReader seriesReader = getSeriesCmd.ExecuteReader())
{ {
while (seriesReader.Read()) while (seriesReader.Read())
{ {
var Key = seriesReader.GetString(0); var Key = seriesReader.GetString(0);
var Value = seriesReader.GetString(1); 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()) using (IDbCommand updateCmd = conn.CreateCommand())
{ {
updateCmd.Transaction = tran; 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(); updateCmd.ExecuteNonQuery();
} }

View File

@ -35,6 +35,7 @@
using NzbDrone.Core.Extras.Subtitles; using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.NetImport; using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
@ -105,6 +106,8 @@ public static void Map()
.Relationship() .Relationship()
.HasOne(s => s.Profile, s => s.ProfileId) .HasOne(s => s.Profile, s => s.ProfileId)
.HasOne(m => m.MovieFile, m => m.MovieFileId); .HasOne(m => m.MovieFile, m => m.MovieFileId);
Mapper.Entity<ImportExclusion>().RegisterModel("ImportExclusions");
Mapper.Entity<Episode>().RegisterModel("Episodes") Mapper.Entity<Episode>().RegisterModel("Episodes")

View File

@ -16,6 +16,7 @@
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.MetadataSource.SkyHook namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
@ -29,8 +30,10 @@ public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries, IProvideMov
private readonly ITmdbConfigService _configService; private readonly ITmdbConfigService _configService;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IPreDBService _predbService; 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; _httpClient = httpClient;
_requestBuilder = requestBuilder.SkyHookTvdb; _requestBuilder = requestBuilder.SkyHookTvdb;
@ -38,6 +41,7 @@ public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBu
_configService = configService; _configService = configService;
_movieService = movieService; _movieService = movieService;
_predbService = predbService; _predbService = predbService;
_exclusionService = exclusionService;
_logger = logger; _logger = logger;
} }
@ -351,7 +355,10 @@ public Movie GetMovieInfo(string imdbId)
public List<Movie> DiscoverNewMovies(string action) public List<Movie> 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; HttpRequest request;
List<MovieResult> results; List<MovieResult> results;
@ -387,7 +394,7 @@ public List<Movie> DiscoverNewMovies(string action)
request.AllowAutoRedirect = true; request.AllowAutoRedirect = true;
request.Method = HttpMethod.POST; request.Method = HttpMethod.POST;
request.Headers.ContentType = "application/x-www-form-urlencoded"; request.Headers.ContentType = "application/x-www-form-urlencoded";
request.SetContent($"tmdbids={allIds}"); request.SetContent($"tmdbids={allIds}&ignoredIds={ignoredIds}");
var response = _httpClient.Post<List<MovieResult>>(request); var response = _httpClient.Post<List<MovieResult>>(request);
@ -399,6 +406,8 @@ public List<Movie> DiscoverNewMovies(string action)
results = response.Resource; 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); return results.SelectList(MapMovie);
} }

View File

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

View File

@ -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<ImportExclusion>
{
bool IsMovieExcluded(int tmdbid);
}
public class ImportExclusionsRepository : BasicRepository<ImportExclusion>, 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();
}
}
}

View File

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

View File

@ -13,6 +13,7 @@
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.NetImport namespace NzbDrone.Core.NetImport
{ {
@ -32,11 +33,14 @@ public class NetImportSearchService : IFetchNetImport, IExecute<NetImportSyncCom
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly ISearchForNzb _nzbSearchService; private readonly ISearchForNzb _nzbSearchService;
private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly IImportExclusionsService _exclusionService;
public NetImportSearchService(INetImportFactory netImportFactory, IMovieService movieService, public NetImportSearchService(INetImportFactory netImportFactory, IMovieService movieService,
ISearchForNewMovie movieSearch, IRootFolderService rootFolder, ISearchForNzb nzbSearchService, ISearchForNewMovie movieSearch, IRootFolderService rootFolder, ISearchForNzb nzbSearchService,
IProcessDownloadDecisions processDownloadDecisions, IConfigService configService, Logger logger) IProcessDownloadDecisions processDownloadDecisions, IConfigService configService,
IImportExclusionsService exclusionService,
Logger logger)
{ {
_netImportFactory = netImportFactory; _netImportFactory = netImportFactory;
_movieService = movieService; _movieService = movieService;
@ -44,6 +48,7 @@ public NetImportSearchService(INetImportFactory netImportFactory, IMovieService
_nzbSearchService = nzbSearchService; _nzbSearchService = nzbSearchService;
_processDownloadDecisions = processDownloadDecisions; _processDownloadDecisions = processDownloadDecisions;
_rootFolder = rootFolder; _rootFolder = rootFolder;
_exclusionService = exclusionService;
_logger = logger; _logger = logger;
_configService = configService; _configService = configService;
} }
@ -119,18 +124,12 @@ public void Execute(NetImportSyncCommand message)
var importExclusions = new List<string>(); var importExclusions = new List<string>();
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; //var downloadedCount = 0;
foreach (var movie in listedMovies) foreach (var movie in listedMovies)
{ {
var mapped = _movieSearch.MapMovieToTmdbMovie(movie); var mapped = _movieSearch.MapMovieToTmdbMovie(movie);
if (mapped != null && !importExclusions.Any(x => x == mapped.TmdbId.ToString())) if (mapped != null && !_exclusionService.IsMovieExcluded(mapped.TmdbId))
{ {
//List<DownloadDecision> decisions; //List<DownloadDecision> decisions;
mapped.AddOptions = new AddMovieOptions {SearchForMovie = true}; mapped.AddOptions = new AddMovieOptions {SearchForMovie = true};

View File

@ -1281,6 +1281,9 @@
<Compile Include="Datastore\Migration\136_add_pathstate_to_movies.cs" /> <Compile Include="Datastore\Migration\136_add_pathstate_to_movies.cs" />
<Compile Include="MetadataSource\IDiscoverNewMovies.cs" /> <Compile Include="MetadataSource\IDiscoverNewMovies.cs" />
<Compile Include="Datastore\Migration\137_add_import_exclusions_table.cs" /> <Compile Include="Datastore\Migration\137_add_import_exclusions_table.cs" />
<Compile Include="NetImport\ImportExclusions\ImportExclusion.cs" />
<Compile Include="NetImport\ImportExclusions\ImportExclusionsRepository.cs" />
<Compile Include="NetImport\ImportExclusions\ImportExclusionsService.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client"> <BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
@ -1350,6 +1353,9 @@
<Compile Include="Notifications\Telegram\TelegramError.cs" /> <Compile Include="Notifications\Telegram\TelegramError.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup>
<Folder Include="NetImport\ImportExclusions\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent> <PostBuildEvent>

View File

@ -14,6 +14,7 @@
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
@ -51,6 +52,7 @@ public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>,
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IBuildFileNames _fileNameBuilder; private readonly IBuildFileNames _fileNameBuilder;
private readonly IImportExclusionsService _exclusionService;
private readonly Logger _logger; private readonly Logger _logger;
@ -60,12 +62,14 @@ public MovieService(IMovieRepository movieRepository,
IEpisodeService episodeService, IEpisodeService episodeService,
IBuildFileNames fileNameBuilder, IBuildFileNames fileNameBuilder,
IConfigService configService, IConfigService configService,
IImportExclusionsService exclusionService,
Logger logger) Logger logger)
{ {
_movieRepository = movieRepository; _movieRepository = movieRepository;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_fileNameBuilder = fileNameBuilder; _fileNameBuilder = fileNameBuilder;
_configService = configService; _configService = configService;
_exclusionService = exclusionService;
_logger = logger; _logger = logger;
} }
@ -286,14 +290,7 @@ public void DeleteMovie(int movieId, bool deleteFiles, bool addExclusion = false
var movie = _movieRepository.Get(movieId); var movie = _movieRepository.Get(movieId);
if (addExclusion) if (addExclusion)
{ {
if (_configService.ImportExclusions.Empty()) _exclusionService.AddExclusion(new ImportExclusion {TmdbId = movie.TmdbId, MovieTitle = movie.Title, MovieYear = movie.Year } );
{
_configService.ImportExclusions = movie.TitleSlug;
}
else if (!_configService.ImportExclusions.Contains(movie.TitleSlug))
{
_configService.ImportExclusions += ',' + movie.TitleSlug;
}
} }
_movieRepository.Delete(movieId); _movieRepository.Delete(movieId);
_eventAggregator.PublishEvent(new MovieDeletedEvent(movie, deleteFiles)); _eventAggregator.PublishEvent(new MovieDeletedEvent(movie, deleteFiles));

View File

@ -6,6 +6,7 @@ var AddMoviesCollection = require('./AddMoviesCollection');
var SearchResultCollectionView = require('./SearchResultCollectionView'); var SearchResultCollectionView = require('./SearchResultCollectionView');
var EmptyView = require('./EmptyView'); var EmptyView = require('./EmptyView');
var NotFoundView = require('./NotFoundView'); var NotFoundView = require('./NotFoundView');
var DiscoverEmptyView = require('./DiscoverEmptyView');
var ErrorView = require('./ErrorView'); var ErrorView = require('./ErrorView');
var LoadingView = require('../Shared/LoadingView'); var LoadingView = require('../Shared/LoadingView');
var FullMovieCollection = require("../Movies/FullMovieCollection"); var FullMovieCollection = require("../Movies/FullMovieCollection");
@ -112,14 +113,10 @@ module.exports = Marionette.Layout.extend({
if (this.isDiscover) { if (this.isDiscover) {
this.ui.searchBar.hide(); this.ui.searchBar.hide();
if (FullMovieCollection.length > 0) { this._discoverRecos();
this._discoverRecos(); /*if (this.collection.length == 0) {
} else {
this.listenTo(FullMovieCollection, "sync", this._discover);
}
if (this.collection.length == 0) {
this.searchResult.show(new LoadingView()); this.searchResult.show(new LoadingView());
} }*/
} }
}, },
@ -192,8 +189,13 @@ module.exports = Marionette.Layout.extend({
_showResults : function() { _showResults : function() {
if (!this.isClosed) { if (!this.isClosed) {
if (this.collection.length === 0) { if (this.collection.length === 0) {
this.ui.searchBar.show(); if (this.isDiscover) {
this.searchResult.show(new NotFoundView({ term : this.collection.term })); this.searchResult.show(new DiscoverEmptyView());
} else {
this.ui.searchBar.show();
this.searchResult.show(new NotFoundView({ term : this.collection.term }));
}
} else { } else {
this.searchResult.show(this.resultCollectionView); this.searchResult.show(this.resultCollectionView);
if (!this.showingAll) { if (!this.showingAll) {
@ -224,11 +226,10 @@ module.exports = Marionette.Layout.extend({
if (this.collection.action === action) { if (this.collection.action === action) {
return return
} }
this.collection.reset();
this.searchResult.show(new LoadingView()); this.searchResult.show(new LoadingView());
this.collection.action = action; this.collection.action = action;
this.collection.fetch({ this.currentSearchPromise = this.collection.fetch();
data : { action : action }
});
}, },
_discoverRecos : function() { _discoverRecos : function() {

View File

@ -0,0 +1,5 @@
var Marionette = require('marionette');
module.exports = Marionette.CompositeView.extend({
template : 'AddMovies/DiscoverEmptyViewTemplate'
});

View File

@ -0,0 +1,6 @@
<div class="text-center col-md-12">
<h3>
No movies left to discover. Come back at another time :)
</h3>
</div>

View File

@ -7,6 +7,7 @@ var Profiles = require('../Profile/ProfileCollection');
var RootFolders = require('./RootFolders/RootFolderCollection'); var RootFolders = require('./RootFolders/RootFolderCollection');
var RootFolderLayout = require('./RootFolders/RootFolderLayout'); var RootFolderLayout = require('./RootFolders/RootFolderLayout');
var FullMovieCollection = require('../Movies/FullMovieCollection'); var FullMovieCollection = require('../Movies/FullMovieCollection');
var ImportExclusionModel = require("../Settings/NetImport/ImportExclusionModel");
var Config = require('../Config'); var Config = require('../Config');
var Messenger = require('../Shared/Messenger'); var Messenger = require('../Shared/Messenger');
var AsValidatedView = require('../Mixins/AsValidatedView'); var AsValidatedView = require('../Mixins/AsValidatedView');
@ -33,6 +34,7 @@ var view = Marionette.ItemView.extend({
events : { events : {
'click .x-add' : '_addWithoutSearch', 'click .x-add' : '_addWithoutSearch',
'click .x-add-search' : '_addAndSearch', 'click .x-add-search' : '_addAndSearch',
"click .x-ignore" : "_ignoreMovie",
'change .x-profile' : '_profileChanged', 'change .x-profile' : '_profileChanged',
'change .x-root-folder' : '_rootFolderChanged', 'change .x-root-folder' : '_rootFolderChanged',
'change .x-season-folder' : '_seasonFolderChanged', '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() { _rootFoldersUpdated : function() {
this._configureTemplateHelpers(); this._configureTemplateHelpers();
this.render(); this.render();

View File

@ -106,6 +106,10 @@
<button class="btn btn-success add x-add-search" title="Add and Search for movie"> <button class="btn btn-success add x-add-search" title="Add and Search for movie">
<i class="icon-sonarr-search"></i> <i class="icon-sonarr-search"></i>
</button> </button>
<button class="btn btn-warning ignore x-ignore" title="Ignore this movie, so it does not show up anymore">
<i class="icon-sonarr-ignore"></i>
</button>
</div> </div>
</div> </div>
{{else}} {{else}}

View File

@ -304,6 +304,10 @@
.fa-icon-color(@brand-danger); .fa-icon-color(@brand-danger);
} }
.icon-sonarr-ignore {
.fa-icon-content(@fa-var-eye-slash);
}
.icon-sonarr-deleted { .icon-sonarr-deleted {
.fa-icon-content(@fa-var-trash); .fa-icon-content(@fa-var-trash);
} }

View File

@ -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('<i class="icon-sonarr-delete" title="Delete exclusion."></i>');
return this;
},
_onClick : function() {
var self = this;
this.model.destroy();
}
});

View File

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

View File

@ -0,0 +1,7 @@
var Backbone = require('backbone');
var _ = require('underscore');
module.exports = Backbone.Model.extend({
urlRoot : window.NzbDrone.ApiRoot + '/exclusions',
});

View File

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

View File

@ -3,6 +3,14 @@ var NetImportCollection = require('./NetImportCollection');
var CollectionView = require('./NetImportCollectionView'); var CollectionView = require('./NetImportCollectionView');
var OptionsView = require('./Options/NetImportOptionsView'); var OptionsView = require('./Options/NetImportOptionsView');
var RootFolderCollection = require('../../AddMovies/RootFolders/RootFolderCollection'); 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({ module.exports = Marionette.Layout.extend({
template : 'Settings/NetImport/NetImportLayoutTemplate', template : 'Settings/NetImport/NetImportLayoutTemplate',
@ -10,18 +18,58 @@ module.exports = Marionette.Layout.extend({
regions : { regions : {
lists : '#x-lists-region', lists : '#x-lists-region',
listOption : '#x-list-options-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() { initialize : function() {
this.indexersCollection = new NetImportCollection(); this.indexersCollection = new NetImportCollection();
this.indexersCollection.fetch(); this.indexersCollection.fetch();
RootFolderCollection.fetch().done(function() { RootFolderCollection.fetch().done(function() {
RootFolderCollection.synced = true; RootFolderCollection.synced = true;
}); });
ImportExclusionsCollection.fetch().done(function() {
ImportExclusionsCollection.synced = true;
});
}, },
onShow : function() { onShow : function() {
this.listenTo(ImportExclusionsCollection, "sync", this._showExclusions);
if (ImportExclusionsCollection.synced === true) {
this._showExclusions();
}
this.lists.show(new CollectionView({ collection : this.indexersCollection })); this.lists.show(new CollectionView({ collection : this.indexersCollection }));
this.listOption.show(new OptionsView({ model : this.model })); 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);
} }
}); });

View File

@ -1,4 +1,9 @@
<div id="x-lists-region"></div> <div id="x-lists-region"></div>
<div class="form-horizontal"> <div class="form-horizontal">
<div id="x-list-options-region"></div> <div id="x-list-options-region"></div>
<fieldset>
<legend>Import Exclusions</legend>
<div id="exclusions">
</div>
</fieldset>
</div> </div>

View File

@ -1,6 +1,11 @@
var Marionette = require('marionette'); var Marionette = require('marionette');
var AsModelBoundView = require('../../../Mixins/AsModelBoundView'); var AsModelBoundView = require('../../../Mixins/AsModelBoundView');
var AsValidatedView = require('../../../Mixins/AsValidatedView'); 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'); var $ = require('jquery');
require('../../../Mixins/TagInput'); require('../../../Mixins/TagInput');
require('bootstrap'); require('bootstrap');
@ -22,10 +27,10 @@ var view = Marionette.ItemView.extend({
'click .x-revoke-trakt-tokens' : '_revokeTraktTokens' 'click .x-revoke-trakt-tokens' : '_revokeTraktTokens'
}, },
initialize : function() { initialize : function() {
}, },
onShow : function() { onShow : function() {
var params = new URLSearchParams(window.location.search); var params = new URLSearchParams(window.location.search);
var oauth = params.get('access'); var oauth = params.get('access');
@ -39,78 +44,25 @@ var view = Marionette.ItemView.extend({
//Config.setValue("traktRefreshToken", refresh); //Config.setValue("traktRefreshToken", refresh);
var tokenExpiry = Math.floor(Date.now() / 1000) + 4838400; 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) 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; //this.model.isSaved = false;
//window.alert("Trakt Authentication Complete - Click Save to make the change take effect"); //window.alert("Trakt Authentication Complete - Click Save to make the change take effect");
} }
if (this.ui.authToken.val() && this.ui.refreshToken.val()){ if (this.ui.authToken.val() && this.ui.refreshToken.val()){
this.ui.resetTokensButton.hide(); this.ui.resetTokensButton.hide();
this.ui.revokeTokensButton.show(); this.ui.revokeTokensButton.show();
} else { } else {
this.ui.resetTokensButton.show(); this.ui.resetTokensButton.show();
this.ui.revokeTokensButton.hide(); this.ui.revokeTokensButton.hide();
} }
}, },
onRender : function() { 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 : { ui : {
resetTraktTokens : '.x-reset-trakt-tokens', resetTraktTokens : '.x-reset-trakt-tokens',
@ -118,7 +70,7 @@ var view = Marionette.ItemView.extend({
refreshToken : '.x-trakt-refresh-token', refreshToken : '.x-trakt-refresh-token',
resetTokensButton : '.x-reset-trakt-tokens', resetTokensButton : '.x-reset-trakt-tokens',
revokeTokensButton : '.x-revoke-trakt-tokens', revokeTokensButton : '.x-revoke-trakt-tokens',
tokenExpiry : '.x-trakt-token-expiry', tokenExpiry : '.x-trakt-token-expiry',
importExclusions : '.x-import-exclusions' importExclusions : '.x-import-exclusions'
}, },
@ -131,15 +83,16 @@ var view = Marionette.ItemView.extend({
_revokeTraktTokens : function() { _revokeTraktTokens : function() {
if (window.confirm("Log out of trakt.tv?")){ 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.authToken.val('').trigger('change');
this.ui.refreshToken.val('').trigger('change'); this.ui.refreshToken.val('').trigger('change');
this.ui.tokenExpiry.val(0).trigger('change'); this.ui.tokenExpiry.val(0).trigger('change');
this.ui.resetTokensButton.show(); this.ui.resetTokensButton.show();
this.ui.revokeTokensButton.hide(); this.ui.revokeTokensButton.hide();
window.alert("Logged out of Trakt.tv - Click Save to make the change take effect"); window.alert("Logged out of Trakt.tv - Click Save to make the change take effect");
} }
} },
}); });

View File

@ -30,17 +30,17 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <!--<div class="form-group">
<label class="col-sm-3 control-label">Import Exclusions</label> <label class="col-sm-3 control-label">Import Exclusions</label>
<div class="col-sm-1 col-sm-push-4 help-inline"> <div class="col-sm-1 col-sm-push-4 help-inline">
<i class="icon-sonarr-form-warning" title="Movies in this field will not be imported even if they exist on your lists."/> <i class="icon-sonarr-form-warning" title="Movies in this field will not be imported even if they exist on your lists."/>
<i class="icon-sonarr-form-info" title="Comma separated imdbid or tmdbid: tt0120915,216138,tt0121765"/> <i class="icon-sonarr-form-info" title="Comma separated imdbid or tmdbid: tt0120915,216138,tt0121765"/>
</div> </div>
<div class="col-sm-4 col-sm-pull-1"> <div class="col-sm-4 col-sm-pull-1">
<input type="text" name="importExclusions" class="form-control x-import-exclusions"/> <input type="text" name="importExclusions" class="form-control x-import-exclusions"/>
</div> </div>
</div> </div>-->
<legend>Trakt Authentication</legend> <legend>Trakt Authentication</legend>
<div class="form-group"> <div class="form-group">
<label class="col-sm-1 control-label">Auth Token</label> <label class="col-sm-1 control-label">Auth Token</label>
@ -58,5 +58,6 @@
<button class="btn btn-danger btn-icon-only x-reset-trakt-tokens" title="Reset Trakt Tokens"><i class="icon-sonarr-refresh"></i></button> <button class="btn btn-danger btn-icon-only x-reset-trakt-tokens" title="Reset Trakt Tokens"><i class="icon-sonarr-refresh"></i></button>
<button class="btn btn-danger btn-icon-only x-revoke-trakt-tokens" title="Revoke Trakt Tokens"><i class="icon-sonarr-logout"></i></button> <button class="btn btn-danger btn-icon-only x-revoke-trakt-tokens" title="Revoke Trakt Tokens"><i class="icon-sonarr-logout"></i></button>
</div > </div >
</div> </div>
</fieldset> </fieldset>

View File

@ -14,6 +14,7 @@ var IndexerCollection = require('./Indexers/IndexerCollection');
var IndexerSettingsModel = require('./Indexers/IndexerSettingsModel'); var IndexerSettingsModel = require('./Indexers/IndexerSettingsModel');
var NetImportSettingsModel = require("./NetImport/NetImportSettingsModel"); var NetImportSettingsModel = require("./NetImport/NetImportSettingsModel");
var NetImportCollection = require('./NetImport/NetImportCollection'); var NetImportCollection = require('./NetImport/NetImportCollection');
var ImportExclusionsCollection = require('./NetImport/ImportExclusionsCollection');
var NetImportLayout = require('./NetImport/NetImportLayout'); var NetImportLayout = require('./NetImport/NetImportLayout');
var DownloadClientLayout = require('./DownloadClient/DownloadClientLayout'); var DownloadClientLayout = require('./DownloadClient/DownloadClientLayout');
var DownloadClientSettingsModel = require('./DownloadClient/DownloadClientSettingsModel'); var DownloadClientSettingsModel = require('./DownloadClient/DownloadClientSettingsModel');