Second Pass at rename/organize

pull/2/head
Tim Turner 8 years ago
parent cd4863b974
commit b5d932866a

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Api.Movies
{
class MovieModule
{
}
}

@ -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<RenameMovieResource>
{
private readonly IRenameMovieFileService _renameMovieFileService;
public RenameMovieModule(IRenameMovieFileService renameMovieFileService)
: base("rename")
{
_renameMovieFileService = renameMovieFileService;
GetResourceAll = GetMovies; //TODO: GetResourceSingle?
}
private List<RenameMovieResource> GetMovies()
{
if(!Request.Query.MovieId.HasValue)
{
throw new BadRequestException("movieId is missing");
}
var movieId = (int)Request.Query.MovieId;
return _renameMovieFileService.GetRenamePreviews(movieId).ToResource();
}
}
}

@ -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<RenameMovieResource> ToResource(this IEnumerable<Core.MediaFiles.RenameMovieFilePreview> models)
{
return models.Select(ToResource).ToList();
}
}
}

@ -116,6 +116,9 @@
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
<Compile Include="Indexers\ReleaseModuleBase.cs" />
<Compile Include="Indexers\ReleasePushModule.cs" />
<Compile Include="Movies\MovieModule.cs" />
<Compile Include="Movies\RenameMovieModule.cs" />
<Compile Include="Movies\RenameMovieResource.cs" />
<Compile Include="Parse\ParseModule.cs" />
<Compile Include="Parse\ParseResource.cs" />
<Compile Include="ManualImport\ManualImportModule.cs" />

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands

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

@ -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<int> Files { get; set; }
public override bool SendUpdatesToClient => true;
public RenameMovieFilesCommand()
{
}
public RenameMovieFilesCommand(int movieId, List<int> files)
{
MovieId = movieId;
Files = files;
}
}
}

@ -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<string> FilterExistingFiles(List<string> files, Movie movie);
EpisodeFile Get(int id);
List<EpisodeFile> Get(IEnumerable<int> ids);
//List<MovieFile> Get(IEnumerable<int> ids);
}
public class MediaFileService : IMediaFileService, IHandleAsync<SeriesDeletedEvent>
@ -125,6 +125,11 @@ namespace NzbDrone.Core.MediaFiles
return _mediaFileRepository.Get(ids).ToList();
}
//public List<MovieFile> Get(IEnumerable<int> ids)
//{
// return _mediaFileRepository.Get(ids).ToList();
//}
public void HandleAsync(SeriesDeletedEvent message)
{
var files = GetFilesBySeries(message.Series.Id);

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

@ -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<RenameMovieFilePreview> GetRenamePreviews(int movieId);
}
public class RenameMovieFileService : IRenameMovieFileService,
//IExecute<RenameMovieFilesCommand>,
IExecute<RenameMovieCommand>
{
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<RenameMovieFilePreview> 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<RenameMovieFilePreview> GetPreviews(Movie movie, List<MovieFile> 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<MovieFile> movieFiles, Movie movie)
{
var renamed = new List<MovieFile>();
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);
}
}
}

@ -710,6 +710,8 @@
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieFilesCommand.cs" />
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
<Compile Include="MediaFiles\DownloadedMovieCommandService.cs" />
<Compile Include="MediaFiles\MovieFileMovingService.cs" />
@ -783,6 +785,8 @@
<Compile Include="MediaFiles\RecycleBinProvider.cs" />
<Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" />
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
<Compile Include="MediaFiles\RenameMovieFilePreview.cs" />
<Compile Include="MediaFiles\RenameMovieFileService.cs" />
<Compile Include="MediaFiles\SameFilenameException.cs" />
<Compile Include="MediaFiles\UpdateMovieFileService.cs" />
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />

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

@ -44,10 +44,10 @@
</div>
<div class="form-group col-md-3 actions">
<label class="x-selected-count">0 series selected</label>
<label class="x-selected-count">0 movies selected</label>
<div>
<button class="btn btn-primary x-action x-save">Save</button>
<button class="btn btn-danger x-action x-organize-files" title="Organize and rename episode files">Organize</button>
<button class="btn btn-danger x-action x-organize-files" title="Organize and rename movie files">Organize</button>
</div>
</div>
</div>

@ -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');

@ -1,7 +1,7 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Organize of Selected Movies</h3>
<h3>Organize Selected Movies</h3>
</div>
<div class="modal-body update-files-series-modal">
<div class="alert alert-info">
@ -9,11 +9,11 @@
Tip: To preview a rename... select "Cancel" then any movie title and use the <i data-original-title="" class="icon-sonarr-rename" title=""></i>
</div>
Are you sure you want to update all files in the {{numberOfSeries}} selected movie?
Are you sure you want to update all files in the {{numberOfMovies}} selected movies?
{{debug}}
<ul class="selected-series">
{{#each series}}
{{#each movie}}
<li>{{title}}</li>
{{/each}}
</ul>

@ -1,3 +1,38 @@
// var Backbone = require('backbone');
// var RenamePreviewModel = require('./RenamePreviewModel');
// module.exports = Backbone.Collection.extend({
// url : window.NzbDrone.ApiRoot + '/rename',
// model : RenamePreviewModel,
// originalFetch : Backbone.Collection.prototype.fetch,
// initialize : function(options) {
// if (!options.seriesId) {
// throw 'seriesId is required';
// }
// this.seriesId = options.seriesId;
// this.seasonNumber = options.seasonNumber;
// },
// fetch : function(options) {
// if (!this.seriesId) {
// throw 'seriesId is required';
// }
// options = options || {};
// options.data = {};
// options.data.seriesId = this.seriesId;
// if (this.seasonNumber !== undefined) {
// options.data.seasonNumber = this.seasonNumber;
// }
// return this.originalFetch.call(this, options);
// }
// });
var Backbone = require('backbone');
var RenamePreviewModel = require('./RenamePreviewModel');
@ -8,26 +43,26 @@ module.exports = Backbone.Collection.extend({
originalFetch : Backbone.Collection.prototype.fetch,
initialize : function(options) {
if (!options.seriesId) {
throw 'seriesId is required';
if (!options.movieId) {
throw 'movieId is required';
}
this.seriesId = options.seriesId;
this.seasonNumber = options.seasonNumber;
this.movieId = options.movieId;
//this.seasonNumber = options.seasonNumber;
},
fetch : function(options) {
if (!this.seriesId) {
throw 'seriesId is required';
if (!this.movieId) {
throw 'movieId is required';
}
options = options || {};
options.data = {};
options.data.seriesId = this.seriesId;
options.data.movieId = this.movieId;
if (this.seasonNumber !== undefined) {
options.data.seasonNumber = this.seasonNumber;
}
// if (this.seasonNumber !== undefined) {
// options.data.seasonNumber = this.seasonNumber;
//}
return this.originalFetch.call(this, options);
}

@ -29,12 +29,13 @@ module.exports = Marionette.Layout.extend({
},
initialize : function(options) {
this.model = options.series;
this.model = options.movie;
this.seasonNumber = options.seasonNumber;
var viewOptions = {};
viewOptions.seriesId = this.model.id;
viewOptions.seasonNumber = this.seasonNumber;
//viewOptions.seriesId = this.model.id;
//viewOptions.seasonNumber = this.seasonNumber;
viewOptions.movieId = this.model.id;
this.collection = new RenamePreviewCollection(viewOptions);
this.listenTo(this.collection, 'sync', this._showPreviews);
@ -74,21 +75,26 @@ module.exports = Marionette.Layout.extend({
return;
}
if (this.seasonNumber) {
// if (this.seasonNumber) {
// CommandController.Execute('renameFiles', {
// name : 'renameFiles',
// movieId : this.model.id,
// //seasonNumber : this.seasonNumber,
// files : files
// });
// } else {
// CommandController.Execute('renameFiles', {
// name : 'renameFiles',
// seriesId : this.model.id,
// seasonNumber : -1,
// files : files
// });
CommandController.Execute('renameFiles', {
name : 'renameFiles',
seriesId : this.model.id,
seasonNumber : this.seasonNumber,
movieId : this.model.id,
files : files
});
} else {
CommandController.Execute('renameFiles', {
name : 'renameFiles',
seriesId : this.model.id,
seasonNumber : -1,
files : files
});
}
//}
vent.trigger(vent.Commands.CloseModalCommand);
},

@ -5,6 +5,7 @@ var Profiles = require('../../Profile/ProfileCollection');
var RootFolders = require('../../AddSeries/RootFolders/RootFolderCollection');
var RootFolderLayout = require('../../AddSeries/RootFolders/RootFolderLayout');
var UpdateFilesSeriesView = require('./Organize/OrganizeFilesView');
var UPdateFilesMoviesView = require('./Organize/OrganizeFilesView');
var Config = require('../../Config');
module.exports = Marionette.ItemView.extend({
@ -118,8 +119,11 @@ 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 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);
}

Loading…
Cancel
Save