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