From cd4863b974f91224121ebabaf1a7a2c70fceb883 Mon Sep 17 00:00:00 2001 From: Tim Turner Date: Sun, 8 Jan 2017 09:16:24 -0500 Subject: [PATCH 1/5] Display UI for MovieEditor, remove reference to SeasonPass --- src/UI/Cells/MovieTitleCell.js | 8 +- src/UI/Controller.js | 6 + src/UI/Movies/Editor/MovieEditorFooterView.js | 126 +++++++++++++++ .../Editor/MovieEditorFooterViewTemplate.hbs | 54 +++++++ src/UI/Movies/Editor/MovieEditorLayout.js | 153 ++++++++++++++++++ .../Editor/MovieEditorLayoutTemplate.hbs | 7 + .../Editor/Organize/OrganizeFilesView.js | 33 ++++ .../Organize/OrganizeFilesViewTemplate.hbs | 25 +++ src/UI/Movies/Index/MoviesIndexLayout.js | 9 +- src/UI/Router.js | 2 +- 10 files changed, 411 insertions(+), 12 deletions(-) create mode 100644 src/UI/Movies/Editor/MovieEditorFooterView.js create mode 100644 src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs create mode 100644 src/UI/Movies/Editor/MovieEditorLayout.js create mode 100644 src/UI/Movies/Editor/MovieEditorLayoutTemplate.hbs create mode 100644 src/UI/Movies/Editor/Organize/OrganizeFilesView.js create mode 100644 src/UI/Movies/Editor/Organize/OrganizeFilesViewTemplate.hbs diff --git a/src/UI/Cells/MovieTitleCell.js b/src/UI/Cells/MovieTitleCell.js index 4ae8fe20c..652d1ebf3 100644 --- a/src/UI/Cells/MovieTitleCell.js +++ b/src/UI/Cells/MovieTitleCell.js @@ -4,8 +4,8 @@ module.exports = TemplatedCell.extend({ className : 'series-title-cell', template : 'Cells/SeriesTitleTemplate', - render : function() { - this.$el.html(this.model.get("movie").get("title")); //Hack, but somehow handlebar helper does not work. - return this; - } + // render : function() { + // this.$el.html(this.model.get("movie").get("title")); //Hack, but somehow handlebar helper does not work. + // return this; + // } }); diff --git a/src/UI/Controller.js b/src/UI/Controller.js index eb5168daa..89775a3ba 100644 --- a/src/UI/Controller.js +++ b/src/UI/Controller.js @@ -11,6 +11,7 @@ var ReleaseLayout = require('./Release/ReleaseLayout'); var SystemLayout = require('./System/SystemLayout'); var SeasonPassLayout = require('./SeasonPass/SeasonPassLayout'); var SeriesEditorLayout = require('./Series/Editor/SeriesEditorLayout'); +var MovieEditorLayout = require('./Movies/Editor/MovieEditorLayout'); module.exports = NzbDroneController.extend({ addSeries : function(action) { @@ -61,5 +62,10 @@ module.exports = NzbDroneController.extend({ seriesEditor : function() { this.setTitle('Series Editor'); this.showMainRegion(new SeriesEditorLayout()); + }, + + movieEditor : function() { + this.setTitle('Movie Editor'); + this.showMainRegion(new MovieEditorLayout()); } }); diff --git a/src/UI/Movies/Editor/MovieEditorFooterView.js b/src/UI/Movies/Editor/MovieEditorFooterView.js new file mode 100644 index 000000000..d764b1aa1 --- /dev/null +++ b/src/UI/Movies/Editor/MovieEditorFooterView.js @@ -0,0 +1,126 @@ +var _ = require('underscore'); +var Marionette = require('marionette'); +var vent = require('vent'); +var Profiles = require('../../Profile/ProfileCollection'); +var RootFolders = require('../../AddMovies/RootFolders/RootFolderCollection'); +var RootFolderLayout = require('../../AddMovies/RootFolders/RootFolderLayout'); +var UpdateFilesSeriesView = require('./Organize/OrganizeFilesView'); +var Config = require('../../Config'); + +module.exports = Marionette.ItemView.extend({ + template : 'Movies/Editor/MovieEditorFooterViewTemplate', + + ui : { + monitored : '.x-monitored', + profile : '.x-profiles', + seasonFolder : '.x-season-folder', + rootFolder : '.x-root-folder', + selectedCount : '.x-selected-count', + container : '.series-editor-footer', + actions : '.x-action' + }, + + events : { + 'click .x-save' : '_updateAndSave', + 'change .x-root-folder' : '_rootFolderChanged', + 'click .x-organize-files' : '_organizeFiles' + }, + + templateHelpers : function() { + return { + profiles : Profiles, + rootFolders : RootFolders.toJSON() + }; + }, + + initialize : function(options) { + this.seriesCollection = options.collection; + + RootFolders.fetch().done(function() { + RootFolders.synced = true; + }); + + this.editorGrid = options.editorGrid; + this.listenTo(this.seriesCollection, 'backgrid:selected', this._updateInfo); + this.listenTo(RootFolders, 'all', this.render); + }, + + onRender : function() { + this._updateInfo(); + }, + + _updateAndSave : function() { + var selected = this.editorGrid.getSelectedModels(); + + var monitored = this.ui.monitored.val(); + var profile = this.ui.profile.val(); + var seasonFolder = this.ui.seasonFolder.val(); + var rootFolder = this.ui.rootFolder.val(); + + _.each(selected, function(model) { + if (monitored === 'true') { + model.set('monitored', true); + } else if (monitored === 'false') { + model.set('monitored', false); + } + + if (profile !== 'noChange') { + model.set('profileId', parseInt(profile, 10)); + } + + if (seasonFolder === 'true') { + model.set('seasonFolder', true); + } else if (seasonFolder === 'false') { + model.set('seasonFolder', false); + } + + if (rootFolder !== 'noChange') { + var rootFolderPath = RootFolders.get(parseInt(rootFolder, 10)); + + model.set('rootFolderPath', rootFolderPath.get('path')); + } + + model.edited = true; + }); + + this.seriesCollection.save(); + }, + + _updateInfo : function() { + var selected = this.editorGrid.getSelectedModels(); + var selectedCount = selected.length; + + this.ui.selectedCount.html('{0} series selected'.format(selectedCount)); + + if (selectedCount === 0) { + this.ui.actions.attr('disabled', 'disabled'); + } else { + this.ui.actions.removeAttr('disabled'); + } + }, + + _rootFolderChanged : function() { + var rootFolderValue = this.ui.rootFolder.val(); + if (rootFolderValue === 'addNew') { + var rootFolderLayout = new RootFolderLayout(); + this.listenToOnce(rootFolderLayout, 'folderSelected', this._setRootFolder); + vent.trigger(vent.Commands.OpenModalCommand, rootFolderLayout); + } else { + Config.setValue(Config.Keys.DefaultRootFolderId, rootFolderValue); + } + }, + + _setRootFolder : function(options) { + vent.trigger(vent.Commands.CloseModalCommand); + this.ui.rootFolder.val(options.model.id); + this._rootFolderChanged(); + }, + + _organizeFiles : function() { + var selected = this.editorGrid.getSelectedModels(); + var updateFilesSeriesView = new UpdateFilesSeriesView({ series : selected }); + this.listenToOnce(updateFilesSeriesView, 'updatingFiles', this._afterSave); + + vent.trigger(vent.Commands.OpenModalCommand, updateFilesSeriesView); + } +}); \ No newline at end of file diff --git a/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs b/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs new file mode 100644 index 000000000..ad81687d5 --- /dev/null +++ b/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs @@ -0,0 +1,54 @@ + diff --git a/src/UI/Movies/Editor/MovieEditorLayout.js b/src/UI/Movies/Editor/MovieEditorLayout.js new file mode 100644 index 000000000..dbbb5f4f5 --- /dev/null +++ b/src/UI/Movies/Editor/MovieEditorLayout.js @@ -0,0 +1,153 @@ +var vent = require('vent'); +var Marionette = require('marionette'); +var Backgrid = require('backgrid'); +var EmptyView = require('../Index/EmptyView'); +var MoviesCollection = require('../MoviesCollection'); +var MovieTitleCell = require('../../Cells/MovieTitleCell'); +var ProfileCell = require('../../Cells/ProfileCell'); +var SelectAllCell = require('../../Cells/SelectAllCell'); +var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout'); +var FooterView = require('./MovieEditorFooterView'); +require('../../Mixins/backbone.signalr.mixin'); + +module.exports = Marionette.Layout.extend({ + template : 'Movies/Editor/MovieEditorLayoutTemplate', + + regions : { + seriesRegion : '#x-series-editor', + toolbar : '#x-toolbar' + }, + + ui : { + monitored : '.x-monitored', + profiles : '.x-profiles', + rootFolder : '.x-root-folder', + selectedCount : '.x-selected-count' + }, + + events : { + 'click .x-save' : '_updateAndSave', + 'change .x-root-folder' : '_rootFolderChanged' + }, + + columns : [ + { + name : '', + cell : SelectAllCell, + headerCell : 'select-all', + sortable : false + }, + { + name : 'title', + label : 'Title', + cell : MovieTitleCell, + cellValue : 'this' + }, + { + name : 'profileId', + label : 'Profile', + cell : ProfileCell + }, + { + name : 'path', + label : 'Path', + cell : 'string' + } + ], + + leftSideButtons : { + type : 'default', + storeState : false, + items : [ + { + title : 'Update Library', + icon : 'icon-sonarr-refresh', + command : 'refreshseries', + successMessage : 'Library was updated!', + errorMessage : 'Library update failed!' + } + ] + }, + + initialize : function() { + this.movieCollection = MoviesCollection.clone(); + this.movieCollection.shadowCollection.bindSignalR(); + this.listenTo(this.movieCollection, 'save', this.render); + + this.filteringOptions = { + type : 'radio', + storeState : true, + menuKey : 'serieseditor.filterMode', + defaultAction : 'all', + items : [ + { + key : 'all', + title : '', + tooltip : 'All', + icon : 'icon-sonarr-all', + callback : this._setFilter + }, + { + key : 'monitored', + title : '', + tooltip : 'Monitored Only', + icon : 'icon-sonarr-monitored', + callback : this._setFilter + } + ] + }; + }, + + onRender : function() { + this._showToolbar(); + this._showTable(); + }, + + onClose : function() { + vent.trigger(vent.Commands.CloseControlPanelCommand); + }, + + _showTable : function() { + if (this.movieCollection.shadowCollection.length === 0) { + this.seriesRegion.show(new EmptyView()); + this.toolbar.close(); + return; + } + + this.columns[0].sortedCollection = this.movieCollection; + + this.editorGrid = new Backgrid.Grid({ + collection : this.movieCollection, + columns : this.columns, + className : 'table table-hover' + }); + + this.seriesRegion.show(this.editorGrid); + this._showFooter(); + }, + + _showToolbar : function() { + this.toolbar.show(new ToolbarLayout({ + left : [ + this.leftSideButtons + ], + right : [ + this.filteringOptions + ], + context : this + })); + }, + + _showFooter : function() { + vent.trigger(vent.Commands.OpenControlPanelCommand, new FooterView({ + editorGrid : this.editorGrid, + collection : this.movieCollection + })); + }, + + _setFilter : function(buttonContext) { + var mode = buttonContext.model.get('key'); + + this.movieCollection.setFilterMode(mode); + } +}); \ No newline at end of file diff --git a/src/UI/Movies/Editor/MovieEditorLayoutTemplate.hbs b/src/UI/Movies/Editor/MovieEditorLayoutTemplate.hbs new file mode 100644 index 000000000..1d0519894 --- /dev/null +++ b/src/UI/Movies/Editor/MovieEditorLayoutTemplate.hbs @@ -0,0 +1,7 @@ +
+ +
+
+
+
+
\ No newline at end of file diff --git a/src/UI/Movies/Editor/Organize/OrganizeFilesView.js b/src/UI/Movies/Editor/Organize/OrganizeFilesView.js new file mode 100644 index 000000000..05eeb53dd --- /dev/null +++ b/src/UI/Movies/Editor/Organize/OrganizeFilesView.js @@ -0,0 +1,33 @@ +var _ = require('underscore'); +var vent = require('vent'); +var Backbone = require('backbone'); +var Marionette = require('marionette'); +var CommandController = require('../../../Commands/CommandController'); + +module.exports = Marionette.ItemView.extend({ + template : 'Movies/Editor/Organize/OrganizeFilesViewTemplate', + + events : { + 'click .x-confirm-organize' : '_organize' + }, + + initialize : function(options) { + this.series = options.series; + this.templateHelpers = { + numberOfSeries : this.series.length, + series : new Backbone.Collection(this.series).toJSON() + }; + }, + + _organize : function() { + var seriesIds = _.pluck(this.series, 'id'); + + CommandController.Execute('renameSeries', { + name : 'renameSeries', + seriesIds : seriesIds + }); + + this.trigger('organizingFiles'); + vent.trigger(vent.Commands.CloseModalCommand); + } +}); \ No newline at end of file diff --git a/src/UI/Movies/Editor/Organize/OrganizeFilesViewTemplate.hbs b/src/UI/Movies/Editor/Organize/OrganizeFilesViewTemplate.hbs new file mode 100644 index 000000000..5258e92b5 --- /dev/null +++ b/src/UI/Movies/Editor/Organize/OrganizeFilesViewTemplate.hbs @@ -0,0 +1,25 @@ + diff --git a/src/UI/Movies/Index/MoviesIndexLayout.js b/src/UI/Movies/Index/MoviesIndexLayout.js index 3e16a7eb7..df6c7bbdd 100644 --- a/src/UI/Movies/Index/MoviesIndexLayout.js +++ b/src/UI/Movies/Index/MoviesIndexLayout.js @@ -75,14 +75,9 @@ module.exports = Marionette.Layout.extend({ route : 'addmovies' }, { - title : 'Season Pass', - icon : 'icon-sonarr-monitored', - route : 'seasonpass' - }, - { - title : 'Series Editor', + title : 'Movie Editor', icon : 'icon-sonarr-edit', - route : 'serieseditor' + route : 'movieeditor' }, { title : 'RSS Sync', diff --git a/src/UI/Router.js b/src/UI/Router.js index ba41c0e61..c04492dba 100644 --- a/src/UI/Router.js +++ b/src/UI/Router.js @@ -21,7 +21,7 @@ module.exports = Marionette.AppRouter.extend({ 'system' : 'system', 'system/:action' : 'system', 'seasonpass' : 'seasonPass', - 'serieseditor' : 'seriesEditor', + 'movieeditor' : 'movieEditor', ':whatever' : 'showNotFound' } }); From b5d932866abb56da1781ab8051a398d345c96b1b Mon Sep 17 00:00:00 2001 From: Tim Turner Date: Sun, 8 Jan 2017 17:01:37 -0500 Subject: [PATCH 2/5] Second Pass at rename/organize --- src/NzbDrone.Api/Movies/MovieModule.cs | 11 ++ src/NzbDrone.Api/Movies/RenameMovieModule.cs | 35 +++++ .../Movies/RenameMovieResource.cs | 35 +++++ src/NzbDrone.Api/NzbDrone.Api.csproj | 3 + .../MediaFiles/Commands/RenameFilesCommand.cs | 2 +- .../MediaFiles/Commands/RenameMovieCommand.cs | 19 +++ .../Commands/RenameMovieFilesCommand.cs | 26 ++++ .../MediaFiles/MediaFileService.cs | 15 +- .../MediaFiles/RenameMovieFilePreview.cs | 15 ++ .../MediaFiles/RenameMovieFileService.cs | 134 ++++++++++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 4 + src/UI/Movies/Editor/MovieEditorFooterView.js | 16 +-- .../Editor/MovieEditorFooterViewTemplate.hbs | 4 +- .../Editor/Organize/OrganizeFilesView.js | 14 +- .../Organize/OrganizeFilesViewTemplate.hbs | 6 +- src/UI/Rename/RenamePreviewCollection.js | 55 +++++-- src/UI/Rename/RenamePreviewLayout.js | 34 +++-- .../Series/Editor/SeriesEditorFooterView.js | 8 +- 18 files changed, 384 insertions(+), 52 deletions(-) create mode 100644 src/NzbDrone.Api/Movies/MovieModule.cs create mode 100644 src/NzbDrone.Api/Movies/RenameMovieModule.cs create mode 100644 src/NzbDrone.Api/Movies/RenameMovieResource.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Commands/RenameMovieCommand.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Commands/RenameMovieFilesCommand.cs create mode 100644 src/NzbDrone.Core/MediaFiles/RenameMovieFilePreview.cs create mode 100644 src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs diff --git a/src/NzbDrone.Api/Movies/MovieModule.cs b/src/NzbDrone.Api/Movies/MovieModule.cs new file mode 100644 index 000000000..2a4d405fc --- /dev/null +++ b/src/NzbDrone.Api/Movies/MovieModule.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Api.Movies +{ + class MovieModule + { + } +} diff --git a/src/NzbDrone.Api/Movies/RenameMovieModule.cs b/src/NzbDrone.Api/Movies/RenameMovieModule.cs new file mode 100644 index 000000000..8736cccb0 --- /dev/null +++ b/src/NzbDrone.Api/Movies/RenameMovieModule.cs @@ -0,0 +1,35 @@ +using NzbDrone.Api.REST; +using NzbDrone.Core.MediaFiles; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Api.Movies +{ + public class RenameMovieModule : NzbDroneRestModule + { + private readonly IRenameMovieFileService _renameMovieFileService; + + public RenameMovieModule(IRenameMovieFileService renameMovieFileService) + : base("rename") + { + _renameMovieFileService = renameMovieFileService; + + GetResourceAll = GetMovies; //TODO: GetResourceSingle? + } + + private List GetMovies() + { + if(!Request.Query.MovieId.HasValue) + { + throw new BadRequestException("movieId is missing"); + } + + var movieId = (int)Request.Query.MovieId; + + return _renameMovieFileService.GetRenamePreviews(movieId).ToResource(); + } + + } +} diff --git a/src/NzbDrone.Api/Movies/RenameMovieResource.cs b/src/NzbDrone.Api/Movies/RenameMovieResource.cs new file mode 100644 index 000000000..d71f1bbcf --- /dev/null +++ b/src/NzbDrone.Api/Movies/RenameMovieResource.cs @@ -0,0 +1,35 @@ +using NzbDrone.Api.REST; +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Api.Movies +{ + public class RenameMovieResource : RestResource + { + public int MovieId { get; set; } + public int MovieFileId { get; set; } + public string ExistingPath { get; set; } + public string NewPath { get; set; } + } + + public static class RenameMovieResourceMapper + { + public static RenameMovieResource ToResource(this Core.MediaFiles.RenameMovieFilePreview model) + { + if (model == null) return null; + + return new RenameMovieResource + { + MovieId = model.MovieId, + MovieFileId = model.MovieFileId, + ExistingPath = model.ExistingPath, + NewPath = model.NewPath + }; + } + + public static List ToResource(this IEnumerable models) + { + return models.Select(ToResource).ToList(); + } + } +} diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 455cc845a..2a3dcba5a 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -116,6 +116,9 @@ + + + diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs index e0dc34e10..5cbbe7dfb 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieCommand.cs new file mode 100644 index 000000000..fad7b76c7 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieCommand.cs @@ -0,0 +1,19 @@ +using NzbDrone.Core.Messaging.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.Commands +{ + public class RenameMovieCommand : Command + { + public int MovieId { get; set; } + + public override bool SendUpdatesToClient => true; + + public RenameMovieCommand() + { + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieFilesCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieFilesCommand.cs new file mode 100644 index 000000000..d2781e3ab --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieFilesCommand.cs @@ -0,0 +1,26 @@ +using NzbDrone.Core.Messaging.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.Commands +{ + public class RenameMovieFilesCommand : Command + { + public int MovieId { get; set; } + public List Files { get; set; } + + public override bool SendUpdatesToClient => true; + + public RenameMovieFilesCommand() + { + } + + public RenameMovieFilesCommand(int movieId, List files) + { + MovieId = movieId; + Files = files; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index e3e82daa5..4a2dd195c 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using NLog; @@ -13,9 +13,9 @@ namespace NzbDrone.Core.MediaFiles { public interface IMediaFileService { - MovieFile Add(MovieFile episodeFile); - void Update(MovieFile episodeFile); - void Delete(MovieFile episodeFile, DeleteMediaFileReason reason); + MovieFile Add(MovieFile movieFile); + void Update(MovieFile movieFile); + void Delete(MovieFile movieFile, DeleteMediaFileReason reason); EpisodeFile Add(EpisodeFile episodeFile); void Update(EpisodeFile episodeFile); void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason); @@ -27,7 +27,7 @@ namespace NzbDrone.Core.MediaFiles List FilterExistingFiles(List files, Movie movie); EpisodeFile Get(int id); List Get(IEnumerable ids); - + //List Get(IEnumerable ids); } public class MediaFileService : IMediaFileService, IHandleAsync @@ -125,6 +125,11 @@ namespace NzbDrone.Core.MediaFiles return _mediaFileRepository.Get(ids).ToList(); } + //public List Get(IEnumerable ids) + //{ + // return _mediaFileRepository.Get(ids).ToList(); + //} + public void HandleAsync(SeriesDeletedEvent message) { var files = GetFilesBySeries(message.Series.Id); diff --git a/src/NzbDrone.Core/MediaFiles/RenameMovieFilePreview.cs b/src/NzbDrone.Core/MediaFiles/RenameMovieFilePreview.cs new file mode 100644 index 000000000..12f52e42a --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/RenameMovieFilePreview.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles +{ + public class RenameMovieFilePreview + { + public int MovieId { get; set; } + public int MovieFileId { get; set; } + public string ExistingPath { get; set; } + public string NewPath { get; set; } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs new file mode 100644 index 000000000..af5e48122 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using NLog; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.MediaFiles +{ + public interface IRenameMovieFileService + { + List GetRenamePreviews(int movieId); + } + + public class RenameMovieFileService : IRenameMovieFileService, + //IExecute, + IExecute + { + private readonly IMovieService _movieService; + private readonly IMediaFileService _mediaFileService; + private readonly IMoveMovieFiles _movieFileMover; + private readonly IEventAggregator _eventAggregator; + private readonly IBuildFileNames _filenameBuilder; + private readonly Logger _logger; + + public RenameMovieFileService(IMovieService movieService, + IMediaFileService mediaFileService, + IMoveMovieFiles movieFileMover, + IEventAggregator eventAggregator, + IBuildFileNames filenameBuilder, + Logger logger) + { + _movieService = movieService; + _mediaFileService = mediaFileService; + _movieFileMover = movieFileMover; + _eventAggregator = eventAggregator; + _filenameBuilder = filenameBuilder; + _logger = logger; + } + + public List GetRenamePreviews(int movieId) + { + var movie = _movieService.GetMovie(movieId); + var file = _mediaFileService.GetFilesByMovie(movieId); + + return GetPreviews(movie, file).OrderByDescending(m => m.MovieId).ToList(); //TODO: Would really like to not have these be lists + + } + + private IEnumerable GetPreviews(Movie movie, List files) + { + foreach(var file in files) + { + var movieFilePath = Path.Combine(movie.Path, file.RelativePath); + + var newName = _filenameBuilder.BuildFileName(movie, file); + var newPath = _filenameBuilder.BuildFilePath(movie, newName, Path.GetExtension(file.Path)); + + if(!movieFilePath.PathEquals(newPath, StringComparison.Ordinal)) + { + yield return new RenameMovieFilePreview + { + MovieId = movie.Id, + MovieFileId = file.Id, + ExistingPath = file.RelativePath, + NewPath = movie.Path.GetRelativePath(newPath) + }; + } + + } + + } + + private void RenameFiles(List movieFiles, Movie movie) + { + var renamed = new List(); + + foreach(var movieFile in movieFiles) + { + var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath); + + try + { + _logger.Debug("Renaming movie file: {0}", movieFile); + _movieFileMover.MoveMovieFile(movieFile, movie); + + _mediaFileService.Update(movieFile); + renamed.Add(movieFile); + + _logger.Debug("Renamed movie file: {0}", movieFile); + + } + catch(SameFilenameException ex) + { + _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename); + } + catch(Exception ex) + { + _logger.Error(ex, "Failed to rename file: " + movieFilePath); + } + } + } + + //public void Execute(RenameMovieFilesCommand message) + //{ + // var movie = _movieService.GetMovie(message.MovieId); + // var movieFiles = _mediaFileService.Get(message.Files); + + // _logger.ProgressInfo("Renaming {0} files for {1}", movieFiles.Count, movie.Title); + // RenameFiles(movieFiles, movie); + // _logger.ProgressInfo("Selected movie files renamed for {0}", movie.Title); + //} + + public void Execute(RenameMovieCommand message) + { + _logger.Debug("Renaming all files for selected movie"); + var movieToRename = _movieService.GetMovie(message.MovieId); + + var movieFiles = _mediaFileService.GetFilesByMovie(movieToRename.Id); + _logger.ProgressInfo("Renaming all files in movie: {0}", movieToRename.Title); + RenameFiles(movieFiles, movieToRename); + _logger.ProgressInfo("All movie files renamed for {0}", movieToRename.Title); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 8720a43ff..1aa8b3bfd 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -710,6 +710,8 @@ + + @@ -783,6 +785,8 @@ + + diff --git a/src/UI/Movies/Editor/MovieEditorFooterView.js b/src/UI/Movies/Editor/MovieEditorFooterView.js index d764b1aa1..05983e3f6 100644 --- a/src/UI/Movies/Editor/MovieEditorFooterView.js +++ b/src/UI/Movies/Editor/MovieEditorFooterView.js @@ -4,7 +4,7 @@ var vent = require('vent'); var Profiles = require('../../Profile/ProfileCollection'); var RootFolders = require('../../AddMovies/RootFolders/RootFolderCollection'); var RootFolderLayout = require('../../AddMovies/RootFolders/RootFolderLayout'); -var UpdateFilesSeriesView = require('./Organize/OrganizeFilesView'); +var UpdateFilesMoviesView = require('./Organize/OrganizeFilesView'); var Config = require('../../Config'); module.exports = Marionette.ItemView.extend({ @@ -34,14 +34,14 @@ module.exports = Marionette.ItemView.extend({ }, initialize : function(options) { - this.seriesCollection = options.collection; + this.moviesCollection = options.collection; RootFolders.fetch().done(function() { RootFolders.synced = true; }); this.editorGrid = options.editorGrid; - this.listenTo(this.seriesCollection, 'backgrid:selected', this._updateInfo); + this.listenTo(this.moviesCollection, 'backgrid:selected', this._updateInfo); this.listenTo(RootFolders, 'all', this.render); }, @@ -83,14 +83,14 @@ module.exports = Marionette.ItemView.extend({ model.edited = true; }); - this.seriesCollection.save(); + this.moviesCollection.save(); }, _updateInfo : function() { var selected = this.editorGrid.getSelectedModels(); var selectedCount = selected.length; - this.ui.selectedCount.html('{0} series selected'.format(selectedCount)); + this.ui.selectedCount.html('{0} movies selected'.format(selectedCount)); if (selectedCount === 0) { this.ui.actions.attr('disabled', 'disabled'); @@ -118,9 +118,9 @@ module.exports = Marionette.ItemView.extend({ _organizeFiles : function() { var selected = this.editorGrid.getSelectedModels(); - var updateFilesSeriesView = new UpdateFilesSeriesView({ series : selected }); - this.listenToOnce(updateFilesSeriesView, 'updatingFiles', this._afterSave); + var updateFilesMoviesView = new UpdateFilesMoviesView({ movies : selected }); + this.listenToOnce(updateFilesMoviesView, 'updatingFiles', this._afterSave); - vent.trigger(vent.Commands.OpenModalCommand, updateFilesSeriesView); + vent.trigger(vent.Commands.OpenModalCommand, updateFilesMoviesView); } }); \ No newline at end of file diff --git a/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs b/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs index ad81687d5..5cefdae3a 100644 --- a/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs +++ b/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs @@ -44,10 +44,10 @@
- +
- +
diff --git a/src/UI/Movies/Editor/Organize/OrganizeFilesView.js b/src/UI/Movies/Editor/Organize/OrganizeFilesView.js index 05eeb53dd..2e03da618 100644 --- a/src/UI/Movies/Editor/Organize/OrganizeFilesView.js +++ b/src/UI/Movies/Editor/Organize/OrganizeFilesView.js @@ -12,19 +12,19 @@ module.exports = Marionette.ItemView.extend({ }, initialize : function(options) { - this.series = options.series; + this.movies = options.movies; this.templateHelpers = { - numberOfSeries : this.series.length, - series : new Backbone.Collection(this.series).toJSON() + numberOfMovies : this.movies.length, + movies : new Backbone.Collection(this.movies).toJSON() }; }, _organize : function() { - var seriesIds = _.pluck(this.series, 'id'); + var movieIds = _.pluck(this.movies, 'id'); - CommandController.Execute('renameSeries', { - name : 'renameSeries', - seriesIds : seriesIds + CommandController.Execute('renameMovie', { + name : 'renameMovie', + movieIds : movieIds }); this.trigger('organizingFiles'); diff --git a/src/UI/Movies/Editor/Organize/OrganizeFilesViewTemplate.hbs b/src/UI/Movies/Editor/Organize/OrganizeFilesViewTemplate.hbs index 5258e92b5..1d603d7a7 100644 --- a/src/UI/Movies/Editor/Organize/OrganizeFilesViewTemplate.hbs +++ b/src/UI/Movies/Editor/Organize/OrganizeFilesViewTemplate.hbs @@ -1,7 +1,7 @@