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
This commit is contained in:
Tim Turner 2017-02-21 15:31:31 -05:00 committed by GitHub
parent 3edc2b80cf
commit 056fb154a8
9 changed files with 121 additions and 46 deletions

View File

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

View File

@ -95,8 +95,6 @@ namespace NzbDrone.Core.Tv
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 =>
@ -112,8 +110,16 @@ namespace NzbDrone.Core.Tv
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);
//_logger.Debug("Adding {0} movies, {1} duplicates detected", newMovies.Count, potentialMovieCount - newMovies.Count);
_logger.Debug("Adding {0} movies", newMovies.Count);
newMovies.ForEach(m =>
{
_eventAggregator.PublishEvent(new MovieAddedEvent(m));

View File

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

View File

@ -7,7 +7,7 @@ var BulkImportCollection = require("./BulkImportCollection");
var QualityCell = require('./QualityCell');
var TmdbIdCell = require('./TmdbIdCell');
var GridPager = require('../../Shared/Grid/Pager');
var SelectAllCell = require('../../Cells/SelectAllCell');
var SelectAllCell = require('./BulkImportSelectAllCell');
var ProfileCell = require('./BulkImportProfileCellT');
var MonitorCell = require('./BulkImportMonitorCell');
var MoviePathCell = require("./MoviePathCell");
@ -33,7 +33,7 @@ module.exports = Marionette.Layout.extend({
ui : {
addSelectdBtn : '.x-add-selected',
addAllBtn : '.x-add-all',
//addAllBtn : '.x-add-all',
pageSizeSelector : '.x-page-size'
},
@ -66,7 +66,8 @@ module.exports = Marionette.Layout.extend({
name : '',
cell : SelectAllCell,
headerCell : 'select-all',
sortable : false
sortable : false,
cellValue : 'this'
},
{
name : 'movie',
@ -107,7 +108,6 @@ module.exports = Marionette.Layout.extend({
cell : QualityCell,
cellValue : 'this',
sortable : false
}
],
@ -132,14 +132,14 @@ module.exports = Marionette.Layout.extend({
callback : this._addSelected,
ownerContext : this,
className : 'x-add-selected'
},
{
title : 'Add All',
icon : 'icon-sonarr-add',
callback : this._addAll,
ownerContext : this,
className : 'x-add-all'
}
}//,
// {
// title : 'Add All',
// icon : 'icon-sonarr-add',
// callback : this._addAll,
// ownerContext : this,
// className : 'x-add-all'
// }
]
};
@ -155,13 +155,13 @@ module.exports = Marionette.Layout.extend({
_addSelected : function() {
var selected = _.filter(this.bulkImportCollection.fullCollection.models, function(elem){
return elem.selected;
})
});
console.log(selected);
var promise = MoviesCollection.importFromList(selected);
this.ui.addSelectdBtn.spinForPromise(promise);
this.ui.addSelectdBtn.addClass('disabled');
this.ui.addAllBtn.addClass('disabled');
//this.ui.addAllBtn.addClass('disabled');
if (selected.length === 0) {
Messenger.show({
@ -169,7 +169,7 @@ module.exports = Marionette.Layout.extend({
message : 'No movies selected'
});
return;
}
}
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),
@ -178,12 +178,19 @@ module.exports = Marionette.Layout.extend({
type : "error"
});
var _this = this;
promise.done(function() {
Messenger.show({
message : "Imported movies from list.",
hideAfter : 8,
hideOnNavigate : true
});
Messenger.show({
message : "Imported movies from folder.",
hideAfter : 8,
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) {
if (event_name == "sync" || event_name == "content") {
this._showContent()
if (event_name === "sync" || event_name === "content") {
this._showContent();
}
},
@ -207,6 +214,7 @@ module.exports = Marionette.Layout.extend({
return;
}
//TODO: override row in order to set an opacity based on duplication state of the movie
this.importGrid = new Backgrid.Grid({
columns : this.columns,
collection : this.bulkImportCollection,

View File

@ -1,5 +1,12 @@
<div id="x-toolbar"/>
{{> 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="col-md-12">
<div id="x-movies-bulk" class="queue table-responsive"/>

View File

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

View File

@ -24,9 +24,9 @@ var Layout = Marionette.Layout.extend({
initialize : function() {
this.collection = RootFolderCollection;
this.rootfolderListView = new RootFolderCollectionView({ collection : RootFolderCollection });
this.rootfolderListView = null;
this.listenTo(this.rootfolderListView, 'itemview:folderSelected', this._onFolderSelected);
},
onShow : function() {
@ -60,7 +60,12 @@ var Layout = Marionette.Layout.extend({
},
_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) {

View File

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

View File

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