Patch/bulk import qol (#785)

* Filter out existing movies upon import
* Update collection based on what is imported
* Ensure root folders are loaded before collectionview

TODO:
* Ensure grid region exists
* Return information about what wasn't imported
* Filter collection based on duplicates
pull/2/head
Tim Turner 8 years ago committed by GitHub
parent 3edc2b80cf
commit 056fb154a8

@ -34,9 +34,10 @@ namespace NzbDrone.Api.Movie
private readonly IMakeImportDecision _importDecisionMaker; private readonly IMakeImportDecision _importDecisionMaker;
private readonly IDiskScanService _diskScanService; private readonly IDiskScanService _diskScanService;
private readonly ICached<Core.Tv.Movie> _mappedMovies; private readonly ICached<Core.Tv.Movie> _mappedMovies;
private readonly IMovieService _movieService;
public MovieBulkImportModule(ISearchForNewMovie searchProxy, IRootFolderService rootFolderService, IMakeImportDecision importDecisionMaker, public MovieBulkImportModule(ISearchForNewMovie searchProxy, IRootFolderService rootFolderService, IMakeImportDecision importDecisionMaker,
IDiskScanService diskScanService, ICacheManager cacheManager) IDiskScanService diskScanService, ICacheManager cacheManager, IMovieService movieService)
: base("/movies/bulkimport") : base("/movies/bulkimport")
{ {
_searchProxy = searchProxy; _searchProxy = searchProxy;
@ -44,6 +45,7 @@ namespace NzbDrone.Api.Movie
_importDecisionMaker = importDecisionMaker; _importDecisionMaker = importDecisionMaker;
_diskScanService = diskScanService; _diskScanService = diskScanService;
_mappedMovies = cacheManager.GetCache<Core.Tv.Movie>(GetType(), "mappedMoviesCache"); _mappedMovies = cacheManager.GetCache<Core.Tv.Movie>(GetType(), "mappedMoviesCache");
_movieService = movieService;
Get["/"] = x => Search(); Get["/"] = x => Search();
} }
@ -55,6 +57,8 @@ namespace NzbDrone.Api.Movie
//Todo error handling //Todo error handling
} }
//var existingMovieTmdbIds = _movieService.GetAllMovies().Select(m => m.TmdbId);
RootFolder rootFolder = _rootFolderService.Get(Request.Query.Id); RootFolder rootFolder = _rootFolderService.Get(Request.Query.Id);
int page = Request.Query.page; int page = Request.Query.page;
@ -108,8 +112,6 @@ namespace NzbDrone.Api.Movie
}; };
} }
var files = _diskScanService.GetVideoFiles(f.Path); var files = _diskScanService.GetVideoFiles(f.Path);
var decisions = _importDecisionMaker.GetImportDecisions(files.ToList(), m); var decisions = _importDecisionMaker.GetImportDecisions(files.ToList(), m);
@ -133,8 +135,10 @@ namespace NzbDrone.Api.Movie
mappedMovie = _searchProxy.MapMovieToTmdbMovie(m); mappedMovie = _searchProxy.MapMovieToTmdbMovie(m);
if (mappedMovie != null) if (mappedMovie != null /*&& !existingMovieTmdbIds.Contains(mappedMovie.TmdbId)*/)
{ {
//Could split these checks to flag movie as possible duplicate
mappedMovie.Monitored = true; mappedMovie.Monitored = true;
_mappedMovies.Set(f.Name, mappedMovie, TimeSpan.FromDays(2)); _mappedMovies.Set(f.Name, mappedMovie, TimeSpan.FromDays(2));
@ -144,7 +148,7 @@ namespace NzbDrone.Api.Movie
return null; return null;
}); });
return new PagingResource<MovieResource> return new PagingResource<MovieResource>
{ {
Page = page, Page = page,
@ -159,10 +163,10 @@ namespace NzbDrone.Api.Movie
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies) private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
{ {
foreach (var currentSeries in movies) foreach (var currentMovie in movies)
{ {
var resource = currentSeries.ToResource(); var resource = currentMovie.ToResource();
var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster); var poster = currentMovie.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null) if (poster != null)
{ {
resource.RemotePoster = poster.Url; resource.RemotePoster = poster.Url;

@ -95,8 +95,6 @@ namespace NzbDrone.Core.Tv
public List<Movie> AddMovies(List<Movie> newMovies) public List<Movie> AddMovies(List<Movie> newMovies)
{ {
_logger.Debug("Adding {0} movies", newMovies.Count);
newMovies.ForEach(m => Ensure.That(m, () => m).IsNotNull()); newMovies.ForEach(m => Ensure.That(m, () => m).IsNotNull());
newMovies.ForEach(m => newMovies.ForEach(m =>
@ -112,8 +110,16 @@ namespace NzbDrone.Core.Tv
m.Added = DateTime.UtcNow; m.Added = DateTime.UtcNow;
}); });
//var existingMovies = GetAllMovies();
//var potentialMovieCount = newMovies.Count;
//newMovies = newMovies.ExceptBy(n => n.TitleSlug, existingMovies, e => e.TitleSlug, StringComparer.InvariantCultureIgnoreCase).ToList();
_movieRepository.InsertMany(newMovies); _movieRepository.InsertMany(newMovies);
//_logger.Debug("Adding {0} movies, {1} duplicates detected", newMovies.Count, potentialMovieCount - newMovies.Count);
_logger.Debug("Adding {0} movies", newMovies.Count);
newMovies.ForEach(m => newMovies.ForEach(m =>
{ {
_eventAggregator.PublishEvent(new MovieAddedEvent(m)); _eventAggregator.PublishEvent(new MovieAddedEvent(m));

@ -0,0 +1,43 @@
var $ = require('jquery');
var _ = require('underscore');
var SelectAllCell = require('../../Cells/SelectAllCell');
var Backgrid = require('backgrid');
var MoviesCollection = require('../../Movies/MoviesCollection');
module.exports = SelectAllCell.extend({
_originalRender : SelectAllCell.prototype.render,
_originalInit : SelectAllCell.prototype.initialize,
initialize : function() {
this._originalInit.apply(this, arguments);
var tmdbId = this.model.get('tmdbId');
var existingMovie = MoviesCollection.where({ tmdbId: tmdbId });
this.isDuplicate = existingMovie.length > 0 ? true : false;
this.listenTo(this.model, 'change', this._refresh);
},
onChange : function(e) {
if(!this.isDuplicate) {
var checked = $(e.target).prop('checked');
this.$el.parent().toggleClass('selected', checked);
this.model.trigger('backgrid:selected', this.model, checked);
} else {
$(e.target).prop('checked', false);
}
},
render : function() {
this._originalRender.apply(this, arguments);
this.$el.children(':first').prop('disabled', this.isDuplicate);
return this;
},
_refresh: function() {
this.render();
}
});

@ -7,7 +7,7 @@ var BulkImportCollection = require("./BulkImportCollection");
var QualityCell = require('./QualityCell'); var QualityCell = require('./QualityCell');
var TmdbIdCell = require('./TmdbIdCell'); var TmdbIdCell = require('./TmdbIdCell');
var GridPager = require('../../Shared/Grid/Pager'); var GridPager = require('../../Shared/Grid/Pager');
var SelectAllCell = require('../../Cells/SelectAllCell'); var SelectAllCell = require('./BulkImportSelectAllCell');
var ProfileCell = require('./BulkImportProfileCellT'); var ProfileCell = require('./BulkImportProfileCellT');
var MonitorCell = require('./BulkImportMonitorCell'); var MonitorCell = require('./BulkImportMonitorCell');
var MoviePathCell = require("./MoviePathCell"); var MoviePathCell = require("./MoviePathCell");
@ -33,7 +33,7 @@ module.exports = Marionette.Layout.extend({
ui : { ui : {
addSelectdBtn : '.x-add-selected', addSelectdBtn : '.x-add-selected',
addAllBtn : '.x-add-all', //addAllBtn : '.x-add-all',
pageSizeSelector : '.x-page-size' pageSizeSelector : '.x-page-size'
}, },
@ -66,7 +66,8 @@ module.exports = Marionette.Layout.extend({
name : '', name : '',
cell : SelectAllCell, cell : SelectAllCell,
headerCell : 'select-all', headerCell : 'select-all',
sortable : false sortable : false,
cellValue : 'this'
}, },
{ {
name : 'movie', name : 'movie',
@ -107,7 +108,6 @@ module.exports = Marionette.Layout.extend({
cell : QualityCell, cell : QualityCell,
cellValue : 'this', cellValue : 'this',
sortable : false sortable : false
} }
], ],
@ -132,14 +132,14 @@ module.exports = Marionette.Layout.extend({
callback : this._addSelected, callback : this._addSelected,
ownerContext : this, ownerContext : this,
className : 'x-add-selected' className : 'x-add-selected'
}, }//,
{ // {
title : 'Add All', // title : 'Add All',
icon : 'icon-sonarr-add', // icon : 'icon-sonarr-add',
callback : this._addAll, // callback : this._addAll,
ownerContext : this, // ownerContext : this,
className : 'x-add-all' // className : 'x-add-all'
} // }
] ]
}; };
@ -155,13 +155,13 @@ module.exports = Marionette.Layout.extend({
_addSelected : function() { _addSelected : function() {
var selected = _.filter(this.bulkImportCollection.fullCollection.models, function(elem){ var selected = _.filter(this.bulkImportCollection.fullCollection.models, function(elem){
return elem.selected; return elem.selected;
}) });
console.log(selected); console.log(selected);
var promise = MoviesCollection.importFromList(selected); var promise = MoviesCollection.importFromList(selected);
this.ui.addSelectdBtn.spinForPromise(promise); this.ui.addSelectdBtn.spinForPromise(promise);
this.ui.addSelectdBtn.addClass('disabled'); this.ui.addSelectdBtn.addClass('disabled');
this.ui.addAllBtn.addClass('disabled'); //this.ui.addAllBtn.addClass('disabled');
if (selected.length === 0) { if (selected.length === 0) {
Messenger.show({ Messenger.show({
@ -169,7 +169,7 @@ module.exports = Marionette.Layout.extend({
message : 'No movies selected' message : 'No movies selected'
}); });
return; return;
} }
Messenger.show({ Messenger.show({
message : "Importing {0} movies. This can take multiple minutes depending on how many movies should be imported. Don't close this browser window until it is finished!".format(selected.length), message : "Importing {0} movies. This can take multiple minutes depending on how many movies should be imported. Don't close this browser window until it is finished!".format(selected.length),
@ -178,12 +178,19 @@ module.exports = Marionette.Layout.extend({
type : "error" type : "error"
}); });
var _this = this;
promise.done(function() { promise.done(function() {
Messenger.show({ Messenger.show({
message : "Imported movies from list.", message : "Imported movies from folder.",
hideAfter : 8, hideAfter : 8,
hideOnNavigate : true hideOnNavigate : true
}); });
_.forEach(selected, function(movie) {
movie.destroy(); //update the collection without the added movies
});
}); });
}, },
@ -192,8 +199,8 @@ module.exports = Marionette.Layout.extend({
}, },
_handleEvent : function(event_name, data) { _handleEvent : function(event_name, data) {
if (event_name == "sync" || event_name == "content") { if (event_name === "sync" || event_name === "content") {
this._showContent() this._showContent();
} }
}, },
@ -207,6 +214,7 @@ module.exports = Marionette.Layout.extend({
return; return;
} }
//TODO: override row in order to set an opacity based on duplication state of the movie
this.importGrid = new Backgrid.Grid({ this.importGrid = new Backgrid.Grid({
columns : this.columns, columns : this.columns,
collection : this.bulkImportCollection, collection : this.bulkImportCollection,

@ -1,5 +1,12 @@
<div id="x-toolbar"/> <div id="x-toolbar"/>
{{> PageSizePartial }} {{> PageSizePartial }}
<div class="row">
<div class="col-md-12">
<span><b>Disabled movies are possible duplicates. If the match is incorrect, update the Tmdb Id cell to import the proper movie.</b><span>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div id="x-movies-bulk" class="queue table-responsive"/> <div id="x-movies-bulk" class="queue table-responsive"/>

@ -21,7 +21,7 @@ module.exports = NzbDroneCell.extend({
}, },
_updateId : function() { _updateId : function() {
var field = this.$el.find('.x-tmdbId'); var field = this.$el.find('.x-tmdbId');
var data = field.val(); var data = field.val();
var promise = $.ajax({ var promise = $.ajax({
@ -31,7 +31,7 @@ module.exports = NzbDroneCell.extend({
//field.spinForPromise(promise); //field.spinForPromise(promise);
field.prop("disabled", true) field.prop("disabled", true);
var icon = this.$(".icon-sonarr-info"); var icon = this.$(".icon-sonarr-info");
@ -48,15 +48,15 @@ module.exports = NzbDroneCell.extend({
promise.success(function(response) { promise.success(function(response) {
_self.model.set(response); _self.model.set(response);
_self.model.set('monitored', cacheMonitored); //reset to the previous monitored value _self.model.set('monitored', cacheMonitored); //reset to the previous monitored value
_self.model.set('profileId', cacheProfile); _self.model.set('profileId', cacheProfile);
_self.model.set('path', cachePath); _self.model.set('path', cachePath);
_self.model.set('movieFile', cacheFile); // may be unneccessary. _self.model.set('movieFile', cacheFile); // may be unneccessary.
field.prop("disabled", false) field.prop("disabled", false);
}); });
promise.error(function(request, status, error) { promise.error(function(request, status, error) {
console.error("Status: " + status, "Error: " + error); console.error("Status: " + status, "Error: " + error);
field.prop("disabled", false) field.prop("disabled", false);
}); });
} }
}); });

@ -24,9 +24,9 @@ var Layout = Marionette.Layout.extend({
initialize : function() { initialize : function() {
this.collection = RootFolderCollection; this.collection = RootFolderCollection;
this.rootfolderListView = new RootFolderCollectionView({ collection : RootFolderCollection }); this.rootfolderListView = null;
this.listenTo(this.rootfolderListView, 'itemview:folderSelected', this._onFolderSelected);
}, },
onShow : function() { onShow : function() {
@ -60,7 +60,12 @@ var Layout = Marionette.Layout.extend({
}, },
_showCurrentDirs : function() { _showCurrentDirs : function() {
this.currentDirs.show(this.rootfolderListView); if(!this.rootfolderListView)
{
this.rootfolderListView = new RootFolderCollectionView({ collection : RootFolderCollection });
this.currentDirs.show(this.rootfolderListView);
this.listenTo(this.rootfolderListView, 'itemview:folderSelected', this._onFolderSelected);
}
}, },
_keydown : function(e) { _keydown : function(e) {

@ -42,9 +42,9 @@ module.exports = Marionette.Layout.extend({
cell : FileTitleCell cell : FileTitleCell
}, },
{ {
name : "mediaInfo", name : "mediaInfo",
label : "Media Info", label : "Media Info",
cell : MediaInfoCell cell : MediaInfoCell
}, },
{ {
name : 'edition', name : 'edition',

@ -1 +1,3 @@
<div id="movie-files-region"><div id="movie-files-grid" class="table-responsive"></div></div> <div id="movie-files-region">
<div id="movie-files-grid" class="table-responsive"></div>
</div>

Loading…
Cancel
Save