mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-23 23:05:18 -07:00
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:
parent
3edc2b80cf
commit
056fb154a8
9 changed files with 121 additions and 46 deletions
|
@ -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));
|
||||||
|
|
43
src/UI/AddMovies/BulkImport/BulkImportSelectAllCell.js
Normal file
43
src/UI/AddMovies/BulkImport/BulkImportSelectAllCell.js
Normal 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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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…
Add table
Add a link
Reference in a new issue