From 7db92c6bcfd9fad57e3f6ad368ce8cd0d35e4c68 Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Wed, 26 Apr 2017 13:31:55 +0200 Subject: [PATCH] Enable automatic renaming, according to naming scheme, of movie folder after creation of the movie. (#1349) Please test everything you can about this and report back if everything still works correctly. --- .../Config/MediaManagementConfigResource.cs | 4 + src/NzbDrone.Api/Movies/MovieFileModule.cs | 5 +- src/NzbDrone.Api/Series/MovieResource.cs | 4 + .../NzbDrone.Core.Test.csproj | 1 + .../FileNameBuilderFixture.cs | 4 +- .../OrganizerTests/GetMovieFolderFixture.cs | 40 ++++++++ .../Configuration/ConfigService.cs | 14 +++ .../Configuration/IConfigService.cs | 2 + .../Migration/136_add_pathstate_to_movies.cs | 16 ++++ .../Commands/RenameMovieFolderCommand.cs | 20 ++++ .../MediaFiles/DiskScanService.cs | 6 ++ .../Events/MovieFileUpdatedEvent.cs | 18 ++++ .../MediaFiles/MovieFileMovingService.cs | 24 ++++- .../MediaFiles/RenameMovieFileService.cs | 91 +++++++++++++++++-- .../MediaFiles/UpgradeMediaFileService.cs | 9 ++ src/NzbDrone.Core/NzbDrone.Core.csproj | 3 + .../Organizer/FileNameBuilder.cs | 72 ++++++++++++++- .../Organizer/FileNameSampleService.cs | 22 +++-- src/NzbDrone.Core/Tv/Movie.cs | 8 ++ src/NzbDrone.Core/Tv/MovieService.cs | 22 ++++- src/NzbDrone.Core/Tv/RefreshMovieService.cs | 5 + src/UI/Movies/Edit/EditMovieTemplate.hbs | 16 ++-- src/UI/Movies/Edit/EditMovieView.js | 13 +++ src/UI/Movies/Editor/MovieEditorFooterView.js | 18 ++-- .../Editor/MovieEditorFooterViewTemplate.hbs | 16 ++-- src/UI/Movies/Editor/MovieEditorLayout.js | 2 +- src/UI/Movies/Files/Edit/EditFileView.js | 1 + src/UI/Movies/Files/FilesLayout.js | 13 ++- src/UI/Rename/RenamePreviewFormatView.js | 1 + .../RenamePreviewFormatViewTemplate.hbs | 1 + src/UI/Rename/RenamePreviewLayoutTemplate.hbs | 2 +- .../Naming/NamingViewTemplate.hbs | 4 + .../Sorting/SortingViewTemplate.hbs | 46 ++++++++++ src/UI/vent.js | 3 +- 34 files changed, 469 insertions(+), 57 deletions(-) create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/136_add_pathstate_to_movies.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Commands/RenameMovieFolderCommand.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Events/MovieFileUpdatedEvent.cs diff --git a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs index 097ecc693..39b451050 100644 --- a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs +++ b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs @@ -11,6 +11,8 @@ namespace NzbDrone.Api.Config public bool AutoDownloadPropers { get; set; } public bool CreateEmptySeriesFolders { get; set; } public FileDateType FileDate { get; set; } + public bool AutoRenameFolders { get; set; } + public bool PathsDefaultStatic { get; set; } public bool SetPermissionsLinux { get; set; } public string FileChmod { get; set; } @@ -35,6 +37,8 @@ namespace NzbDrone.Api.Config AutoDownloadPropers = model.AutoDownloadPropers, CreateEmptySeriesFolders = model.CreateEmptySeriesFolders, FileDate = model.FileDate, + AutoRenameFolders = model.AutoRenameFolders, + PathsDefaultStatic = model.PathsDefaultStatic, SetPermissionsLinux = model.SetPermissionsLinux, FileChmod = model.FileChmod, diff --git a/src/NzbDrone.Api/Movies/MovieFileModule.cs b/src/NzbDrone.Api/Movies/MovieFileModule.cs index 5d37ac911..1356182b9 100644 --- a/src/NzbDrone.Api/Movies/MovieFileModule.cs +++ b/src/NzbDrone.Api/Movies/MovieFileModule.cs @@ -46,11 +46,14 @@ namespace NzbDrone.Api.EpisodeFiles return movie.ToResource(); } + private void SetQuality(MovieFileResource movieFileResource) - { + { var movieFile = _mediaFileService.GetMovie(movieFileResource.Id); movieFile.Quality = movieFileResource.Quality; _mediaFileService.Update(movieFile); + + BroadcastResourceChange(ModelAction.Updated, movieFile.Id); } private void DeleteMovieFile(int id) diff --git a/src/NzbDrone.Api/Series/MovieResource.cs b/src/NzbDrone.Api/Series/MovieResource.cs index d5e77bb73..5491c636a 100644 --- a/src/NzbDrone.Api/Series/MovieResource.cs +++ b/src/NzbDrone.Api/Series/MovieResource.cs @@ -40,6 +40,7 @@ namespace NzbDrone.Api.Movie //View & Edit public string Path { get; set; } public int ProfileId { get; set; } + public MoviePathState PathState { get; set; } //Editing Only public bool Monitored { get; set; } @@ -131,6 +132,7 @@ namespace NzbDrone.Api.Movie Path = model.Path, ProfileId = model.ProfileId, + PathState = model.PathState, Monitored = model.Monitored, MinimumAvailability = model.MinimumAvailability, @@ -187,6 +189,7 @@ namespace NzbDrone.Api.Movie Path = resource.Path, ProfileId = resource.ProfileId, + PathState = resource.PathState, Monitored = resource.Monitored, MinimumAvailability = resource.MinimumAvailability, @@ -217,6 +220,7 @@ namespace NzbDrone.Api.Movie movie.Path = resource.Path; movie.ProfileId = resource.ProfileId; + movie.PathState = resource.PathState; movie.Monitored = resource.Monitored; movie.MinimumAvailability = resource.MinimumAvailability; diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index bc14282b7..54b4a1b5f 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -296,6 +296,7 @@ + diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs index e95787168..b5777716e 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using FizzWare.NBuilder; @@ -734,5 +734,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) .Should().Be(releaseGroup); } + + } } diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs new file mode 100644 index 000000000..d609b34ff --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs @@ -0,0 +1,40 @@ +using NUnit.Framework; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Test.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Core.Tv; +using FluentAssertions; + +namespace NzbDrone.Core.Test.OrganizerTests +{ + [TestFixture] + + public class GetMovieFolderFixture : CoreTest + { + private NamingConfig namingConfig; + + [SetUp] + public void Setup() + { + namingConfig = NamingConfig.Default; + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(namingConfig); + } + + [TestCase("Arrival", 2016, "{Movie Title} ({Release Year})", "Arrival (2016)")] + [TestCase("The Big Short", 2015, "{Movie TitleThe} ({Release Year})", "Big Short, The (2015)")] + [TestCase("The Big Short", 2015, "{Movie Title} ({Release Year})", "The Big Short (2015)")] + public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, int year, string format, string expected) + { + namingConfig.MovieFolderFormat = format; + + var movie = new Movie { Title = movieTitle, Year = year }; + + Subject.GetMovieFolder(movie).Should().Be(expected); + } + } +} diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 3b6ca0913..f25ae165d 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -294,6 +294,20 @@ namespace NzbDrone.Core.Configuration set { SetValue("ExtraFileExtensions", value); } } + public bool AutoRenameFolders + { + get { return GetValueBoolean("AutoRenameFolders", false); } + + set { SetValue("AutoRenameFolders", value); } + } + + public bool PathsDefaultStatic + { + get { return GetValueBoolean("PathsDefaultStatic", true); } + + set { SetValue("PathsDefaultStatic", value); } + } + public bool SetPermissionsLinux { get { return GetValueBoolean("SetPermissionsLinux", false); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 7fcea6482..08a1c0572 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -33,6 +33,8 @@ namespace NzbDrone.Core.Configuration bool CopyUsingHardlinks { get; set; } bool EnableMediaInfo { get; set; } string ExtraFileExtensions { get; set; } + bool AutoRenameFolders { get; set; } + bool PathsDefaultStatic { get; set; } //Permissions (Media Management) bool SetPermissionsLinux { get; set; } diff --git a/src/NzbDrone.Core/Datastore/Migration/136_add_pathstate_to_movies.cs b/src/NzbDrone.Core/Datastore/Migration/136_add_pathstate_to_movies.cs new file mode 100644 index 000000000..004bd1b16 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/136_add_pathstate_to_movies.cs @@ -0,0 +1,16 @@ +using System.Data; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(136)] + public class add_pathstate_to_movies : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Movies").AddColumn("PathState").AsInt32().WithDefaultValue(2); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieFolderCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieFolderCommand.cs new file mode 100644 index 000000000..e5997a14c --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameMovieFolderCommand.cs @@ -0,0 +1,20 @@ +using NzbDrone.Core.Messaging.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.Commands +{ + public class RenameMovieFolderCommand : Command + { + public List MovieIds { get; set; } + + public override bool SendUpdatesToClient => true; + + public RenameMovieFolderCommand(List ids) + { + MovieIds = ids; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs index e78092576..471b6ebb8 100644 --- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -45,6 +45,7 @@ namespace NzbDrone.Core.MediaFiles private readonly IEventAggregator _eventAggregator; private readonly IMovieService _movieService; private readonly IMovieFileRepository _movieFileRepository; + private readonly IRenameMovieFileService _renameMovieFiles; private readonly Logger _logger; public DiskScanService(IDiskProvider diskProvider, @@ -57,6 +58,7 @@ namespace NzbDrone.Core.MediaFiles IEventAggregator eventAggregator, IMovieService movieService, IMovieFileRepository movieFileRepository, + IRenameMovieFileService renameMovieFiles, Logger logger) { _diskProvider = diskProvider; @@ -69,6 +71,7 @@ namespace NzbDrone.Core.MediaFiles _eventAggregator = eventAggregator; _movieService = movieService; _movieFileRepository = movieFileRepository; + _renameMovieFiles = renameMovieFiles; _logger = logger; } @@ -135,6 +138,9 @@ namespace NzbDrone.Core.MediaFiles public void Scan(Movie movie) { + //Try renaming the movie path in case anything changed such as year, title or something else. + _renameMovieFiles.RenameMoviePath(movie, true); + var rootFolder = _diskProvider.GetParentFolder(movie.Path); if (!_diskProvider.FolderExists(rootFolder)) diff --git a/src/NzbDrone.Core/MediaFiles/Events/MovieFileUpdatedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/MovieFileUpdatedEvent.cs new file mode 100644 index 000000000..df7096b28 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Events/MovieFileUpdatedEvent.cs @@ -0,0 +1,18 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.Events +{ + public class MovieFileUpdatedEvent : IEvent + { + public MovieFile MovieFile { get; private set; } + + public MovieFileUpdatedEvent(MovieFile movieFile) + { + MovieFile = movieFile; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/MovieFileMovingService.cs index cf8acd6f9..32679afe5 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieFileMovingService.cs @@ -30,6 +30,7 @@ namespace NzbDrone.Core.MediaFiles private readonly IDiskTransferService _diskTransferService; private readonly IDiskProvider _diskProvider; private readonly IMediaFileAttributeService _mediaFileAttributeService; + private readonly IRecycleBinProvider _recycleBinProvider; private readonly IEventAggregator _eventAggregator; private readonly IConfigService _configService; private readonly Logger _logger; @@ -40,6 +41,7 @@ namespace NzbDrone.Core.MediaFiles IDiskTransferService diskTransferService, IDiskProvider diskProvider, IMediaFileAttributeService mediaFileAttributeService, + IRecycleBinProvider recycleBinProvider, IEventAggregator eventAggregator, IConfigService configService, Logger logger) @@ -50,6 +52,7 @@ namespace NzbDrone.Core.MediaFiles _diskTransferService = diskTransferService; _diskProvider = diskProvider; _mediaFileAttributeService = mediaFileAttributeService; + _recycleBinProvider = recycleBinProvider; _eventAggregator = eventAggregator; _configService = configService; _logger = logger; @@ -116,6 +119,15 @@ namespace NzbDrone.Core.MediaFiles _diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode); + var oldMoviePath = movie.Path; + + var newMoviePath = new OsPath(destinationFilePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar); + movie.Path = newMoviePath; + if (oldMoviePath != newMoviePath) + { + _movieService.UpdateMovie(movie); + } + movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath); _updateMovieFileService.ChangeFileDateForFile(movieFile, movie); @@ -132,6 +144,14 @@ namespace NzbDrone.Core.MediaFiles _mediaFileAttributeService.SetFilePermissions(destinationFilePath); + if(oldMoviePath != newMoviePath) + { + if (_diskProvider.GetFiles(oldMoviePath, SearchOption.AllDirectories).Count() == 0) + { + _recycleBinProvider.DeleteFolder(oldMoviePath); + } + } + return movieFile; } @@ -143,7 +163,9 @@ namespace NzbDrone.Core.MediaFiles private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath) { var movieFolder = Path.GetDirectoryName(filePath); + movie.Path = movieFolder; var rootFolder = new OsPath(movieFolder).Directory.FullPath; + var fileName = Path.GetFileName(filePath); if (!_diskProvider.FolderExists(rootFolder)) { @@ -156,7 +178,7 @@ namespace NzbDrone.Core.MediaFiles if (!_diskProvider.FolderExists(movieFolder)) { CreateFolder(movieFolder); - newEvent.SeriesFolder = movieFolder; + newEvent.MovieFolder = movieFolder; changed = true; } diff --git a/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs index 52822cedf..a1a24a626 100644 --- a/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs @@ -13,23 +13,29 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Organizer; using NzbDrone.Core.Tv; +using NzbDrone.Core.Configuration; namespace NzbDrone.Core.MediaFiles { public interface IRenameMovieFileService { List GetRenamePreviews(int movieId); + void RenameMoviePath(Movie movie, bool shouldRenameFiles); } public class RenameMovieFileService : IRenameMovieFileService, IExecute, - IExecute + IExecute, + IExecute { private readonly IMovieService _movieService; private readonly IMediaFileService _mediaFileService; private readonly IMoveMovieFiles _movieFileMover; private readonly IEventAggregator _eventAggregator; private readonly IBuildFileNames _filenameBuilder; + private readonly IConfigService _configService; + private readonly IDiskProvider _diskProvider; + private readonly IRecycleBinProvider _recycleBinProvider; private readonly Logger _logger; public RenameMovieFileService(IMovieService movieService, @@ -37,6 +43,9 @@ namespace NzbDrone.Core.MediaFiles IMoveMovieFiles movieFileMover, IEventAggregator eventAggregator, IBuildFileNames filenameBuilder, + IConfigService configService, + IRecycleBinProvider recycleBinProvider, + IDiskProvider diskProvider, Logger logger) { _movieService = movieService; @@ -44,6 +53,9 @@ namespace NzbDrone.Core.MediaFiles _movieFileMover = movieFileMover; _eventAggregator = eventAggregator; _filenameBuilder = filenameBuilder; + _configService = configService; + _recycleBinProvider = recycleBinProvider; + _diskProvider = diskProvider; _logger = logger; } @@ -71,8 +83,9 @@ namespace NzbDrone.Core.MediaFiles { MovieId = movie.Id, MovieFileId = file.Id, - ExistingPath = file.RelativePath, - NewPath = movie.Path.GetRelativePath(newPath) + ExistingPath = movieFilePath, + //NewPath = movie.Path.GetRelativePath(newPath) + NewPath = newPath }; } @@ -80,13 +93,19 @@ namespace NzbDrone.Core.MediaFiles } - private void RenameFiles(List movieFiles, Movie movie) + private void RenameFiles(List movieFiles, Movie movie, string oldMoviePath = null) { var renamed = new List(); - foreach(var movieFile in movieFiles) + if (oldMoviePath == null) { - var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath); + oldMoviePath = movie.Path; + } + + foreach (var movieFile in movieFiles) + { + var oldMovieFilePath = Path.Combine(oldMoviePath, movieFile.RelativePath); + movieFile.Path = oldMovieFilePath; try { @@ -94,22 +113,64 @@ namespace NzbDrone.Core.MediaFiles _movieFileMover.MoveMovieFile(movieFile, movie); _mediaFileService.Update(movieFile); + _movieService.UpdateMovie(movie); renamed.Add(movieFile); _logger.Debug("Renamed movie file: {0}", movieFile); } - catch(SameFilenameException ex) + catch (SameFilenameException ex) { _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename); } - catch(Exception ex) + catch (Exception ex) { - _logger.Error(ex, "Failed to rename file: " + movieFilePath); + _logger.Error(ex, "Failed to rename file: " + oldMovieFilePath); } } } + public void RenameMoviePath(Movie movie, bool shouldRenameFiles = true) + { + var newFolder = _filenameBuilder.BuildMoviePath(movie); + if (newFolder != movie.Path && movie.PathState == MoviePathState.Dynamic) + { + + if (!_configService.AutoRenameFolders) + { + _logger.Info("{0}'s movie should be {1} according to your naming config.", movie, newFolder); + return; + } + + _logger.Info("{0}'s movie folder changed to: {1}", movie, newFolder); + var oldFolder = movie.Path; + movie.Path = newFolder; + + if (shouldRenameFiles) + { + var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id); + _logger.ProgressInfo("Renaming movie files for {0}", movie.Title); + RenameFiles(movieFiles, movie, oldFolder); + _logger.ProgressInfo("All movie files renamed for {0}", movie.Title); + } + + _movieService.UpdateMovie(movie); + + if (_diskProvider.GetFiles(oldFolder, SearchOption.AllDirectories).Count() == 0) + { + _recycleBinProvider.DeleteFolder(oldFolder); + } + + + } + + if (movie.PathState == MoviePathState.StaticOnce) + { + movie.PathState = MoviePathState.Dynamic; + _movieService.UpdateMovie(movie); + } + } + public void Execute(RenameMovieFilesCommand message) { var movie = _movieService.GetMovie(message.MovieId); @@ -134,5 +195,17 @@ namespace NzbDrone.Core.MediaFiles } } + + public void Execute(RenameMovieFolderCommand message) + { + _logger.Debug("Renaming movie folder for selected movie if necessary"); + var moviesToRename = _movieService.GetMovies(message.MovieIds); + foreach(var movie in moviesToRename) + { + var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id); + _logger.ProgressInfo("Renaming movie folder for {0}", movie.Title); + RenameMoviePath(movie); + } + } } } diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index 53dcd6462..b0bcffb26 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.MediaFiles private readonly IMediaFileService _mediaFileService; private readonly IMoveEpisodeFiles _episodeFileMover; private readonly IMoveMovieFiles _movieFileMover; + private readonly IRenameMovieFileService _movieFileRenamer; private readonly IDiskProvider _diskProvider; private readonly Logger _logger; @@ -26,6 +27,7 @@ namespace NzbDrone.Core.MediaFiles IMoveEpisodeFiles episodeFileMover, IMoveMovieFiles movieFileMover, IDiskProvider diskProvider, + IRenameMovieFileService movieFileRenamer, Logger logger) { _recycleBinProvider = recycleBinProvider; @@ -33,6 +35,7 @@ namespace NzbDrone.Core.MediaFiles _episodeFileMover = episodeFileMover; _movieFileMover = movieFileMover; _diskProvider = diskProvider; + _movieFileRenamer = movieFileRenamer; _logger = logger; } @@ -57,6 +60,10 @@ namespace NzbDrone.Core.MediaFiles _mediaFileService.Delete(existingFile, DeleteMediaFileReason.Upgrade); } + //Temporary for correctly getting path + localMovie.Movie.MovieFileId = 1; + localMovie.Movie.MovieFile = movieFile; + if (copyOnly) { moveFileResult.MovieFile = _movieFileMover.CopyMovieFile(movieFile, localMovie); @@ -66,6 +73,8 @@ namespace NzbDrone.Core.MediaFiles moveFileResult.MovieFile = _movieFileMover.MoveMovieFile(movieFile, localMovie); } + //_movieFileRenamer.RenameMoviePath(localMovie.Movie, false); + return moveFileResult; } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 623128055..850e06687 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -125,6 +125,7 @@ + @@ -1275,7 +1276,9 @@ + + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index b6f953e61..dfc1e17f6 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.Organizer string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null); string BuildFilePath(Movie movie, string fileName, string extension); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); + string BuildMoviePath(Movie movie, NamingConfig namingConfig = null); string BuildSeasonPath(Series series, int seasonNumber); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); string GetSeriesFolder(Series series, NamingConfig namingConfig = null); @@ -158,12 +159,11 @@ namespace NzbDrone.Core.Organizer return GetOriginalTitle(movieFile); } - //TODO: Update namingConfig for Movies! var pattern = namingConfig.StandardMovieFormat; var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year + AddReleaseDateTokens(tokenHandlers, movie.Year); AddImdbIdTokens(tokenHandlers, movie.ImdbId); AddQualityTokens(tokenHandlers, movie, movieFile); AddMediaInfoTokens(tokenHandlers, movieFile); @@ -190,11 +190,61 @@ namespace NzbDrone.Core.Organizer { Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); - var path = movie.Path; + var path = ""; + + if (movie.PathState > 0) + { + path = movie.Path; + } + else + { + path = BuildMoviePath(movie); + } return Path.Combine(path, fileName + extension); } + public string BuildMoviePath(Movie movie, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var path = movie.Path; + var directory = new DirectoryInfo(path).Name; + var parentDirectoryPath = new DirectoryInfo(path).Parent.FullName; + + var movieFile = movie.MovieFile; + + var pattern = namingConfig.MovieFolderFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + + if(movie.MovieFileId != 0) + { + movieFile.LazyLoad(); + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); + } + else + { + AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" }); + } + + + var directoryName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); + directoryName = FileNameCleanupRegex.Replace(directoryName, match => match.Captures[0].Value[0].ToString()); + directoryName = TrimSeparatorsRegex.Replace(directoryName, string.Empty); + + return Path.Combine(parentDirectoryPath, directoryName); + } + public string BuildSeasonPath(Series series, int seasonNumber) { var path = series.Path; @@ -302,12 +352,28 @@ namespace NzbDrone.Core.Organizer namingConfig = _namingConfigService.GetConfig(); } + var movieFile = movie.MovieFile; + + var pattern = namingConfig.MovieFolderFormat; var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); AddMovieTokens(tokenHandlers, movie); AddReleaseDateTokens(tokenHandlers, movie.Year); AddImdbIdTokens(tokenHandlers, movie.ImdbId); + if (movie.MovieFileId != 0) + { + movieFile.LazyLoad(); + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); + } + else + { + AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}"}); + } + return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig)); } diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index b06c4964f..f48c49701 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -44,13 +44,6 @@ namespace NzbDrone.Core.Organizer { _buildFileNames = buildFileNames; - _movie = new Movie - { - Title = "The Movie Title", - Year = 2010, - ImdbId = "tt0066921" - }; - _standardSeries = new Series { SeriesType = SeriesTypes.Standard, @@ -122,13 +115,22 @@ namespace NzbDrone.Core.Organizer _movieFile = new MovieFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(2)), - RelativePath = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE.mkv", - SceneName = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE", - ReleaseGroup = "RlsGrp", + RelativePath = "The.Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE.mkv", + SceneName = "The.Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE", + ReleaseGroup = "EVOLVE", MediaInfo = mediaInfo, Edition = "Ultimate extended edition", }; + _movie = new Movie + { + Title = "The Movie: Title", + Year = 2010, + ImdbId = "tt0066921", + MovieFile = _movieFile, + MovieFileId = 1, + }; + _singleEpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p, new Revision(2)), diff --git a/src/NzbDrone.Core/Tv/Movie.cs b/src/NzbDrone.Core/Tv/Movie.cs index 6a2fd8113..5a859a847 100644 --- a/src/NzbDrone.Core/Tv/Movie.cs +++ b/src/NzbDrone.Core/Tv/Movie.cs @@ -41,6 +41,7 @@ namespace NzbDrone.Core.Tv public List Actors { get; set; } public string Certification { get; set; } public string RootFolderPath { get; set; } + public MoviePathState PathState { get; set; } public DateTime Added { get; set; } public DateTime? InCinemas { get; set; } public DateTime? PhysicalRelease { get; set; } @@ -117,4 +118,11 @@ namespace NzbDrone.Core.Tv { public bool SearchForMovie { get; set; } } + + public enum MoviePathState + { + Dynamic, + StaticOnce, + Static, + } } diff --git a/src/NzbDrone.Core/Tv/MovieService.cs b/src/NzbDrone.Core/Tv/MovieService.cs index 0d6fca997..f998ab578 100644 --- a/src/NzbDrone.Core/Tv/MovieService.cs +++ b/src/NzbDrone.Core/Tv/MovieService.cs @@ -150,13 +150,23 @@ namespace NzbDrone.Core.Tv { Ensure.That(newMovie, () => newMovie).IsNotNull(); + MoviePathState defaultState = MoviePathState.Static; + if (!_configService.PathsDefaultStatic) + { + defaultState = MoviePathState.Dynamic; + } if (string.IsNullOrWhiteSpace(newMovie.Path)) { var folderName = _fileNameBuilder.GetMovieFolder(newMovie); newMovie.Path = Path.Combine(newMovie.RootFolderPath, folderName); + newMovie.PathState = defaultState; + } + else + { + newMovie.PathState = defaultState == MoviePathState.Dynamic ? MoviePathState.StaticOnce : MoviePathState.Static; } - _logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path); + _logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path); newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle(); newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.TmdbId); @@ -174,10 +184,20 @@ namespace NzbDrone.Core.Tv newMovies.ForEach(m => { + MoviePathState defaultState = MoviePathState.Static; + if (!_configService.PathsDefaultStatic) + { + defaultState = MoviePathState.Dynamic; + } if (string.IsNullOrWhiteSpace(m.Path)) { var folderName = _fileNameBuilder.GetMovieFolder(m); m.Path = Path.Combine(m.RootFolderPath, folderName); + m.PathState = defaultState; + } + else + { + m.PathState = defaultState == MoviePathState.Dynamic ? MoviePathState.StaticOnce : MoviePathState.Static; } m.CleanTitle = m.Title.CleanSeriesTitle(); diff --git a/src/NzbDrone.Core/Tv/RefreshMovieService.cs b/src/NzbDrone.Core/Tv/RefreshMovieService.cs index 8c12c9a36..4f8cd8935 100644 --- a/src/NzbDrone.Core/Tv/RefreshMovieService.cs +++ b/src/NzbDrone.Core/Tv/RefreshMovieService.cs @@ -13,6 +13,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Tv.Commands; using NzbDrone.Core.Tv.Events; +using NzbDrone.Core.MediaFiles.Commands; namespace NzbDrone.Core.Tv { @@ -22,6 +23,7 @@ namespace NzbDrone.Core.Tv private readonly IMovieService _movieService; private readonly IRefreshEpisodeService _refreshEpisodeService; private readonly IEventAggregator _eventAggregator; + private readonly IManageCommandQueue _commandQueueManager; private readonly IDiskScanService _diskScanService; private readonly ICheckIfMovieShouldBeRefreshed _checkIfMovieShouldBeRefreshed; private readonly Logger _logger; @@ -32,12 +34,14 @@ namespace NzbDrone.Core.Tv IEventAggregator eventAggregator, IDiskScanService diskScanService, ICheckIfMovieShouldBeRefreshed checkIfMovieShouldBeRefreshed, + IManageCommandQueue commandQueue, Logger logger) { _movieInfo = movieInfo; _movieService = movieService; _refreshEpisodeService = refreshEpisodeService; _eventAggregator = eventAggregator; + _commandQueueManager = commandQueue; _diskScanService = diskScanService; _checkIfMovieShouldBeRefreshed = checkIfMovieShouldBeRefreshed; _logger = logger; @@ -136,6 +140,7 @@ namespace NzbDrone.Core.Tv try { _logger.Info("Skipping refresh of movie: {0}", movie.Title); + _commandQueueManager.Push(new RenameMovieFolderCommand(new List{movie.Id})); _diskScanService.Scan(movie); } catch (Exception e) diff --git a/src/UI/Movies/Edit/EditMovieTemplate.hbs b/src/UI/Movies/Edit/EditMovieTemplate.hbs index 185ad3af8..440a6439b 100644 --- a/src/UI/Movies/Edit/EditMovieTemplate.hbs +++ b/src/UI/Movies/Edit/EditMovieTemplate.hbs @@ -47,27 +47,29 @@ - + +
diff --git a/src/UI/Movies/Edit/EditMovieView.js b/src/UI/Movies/Edit/EditMovieView.js index d8861d59d..08542c207 100644 --- a/src/UI/Movies/Edit/EditMovieView.js +++ b/src/UI/Movies/Edit/EditMovieView.js @@ -22,6 +22,12 @@ var view = Marionette.ItemView.extend({ initialize : function() { this.model.set('profiles', Profiles); + var pathState = this.model.get("pathState"); + if (pathState == "static") { + this.model.set("pathState", true); + } else { + this.model.set("pathState", false); + } }, onRender : function() { @@ -30,11 +36,18 @@ var view = Marionette.ItemView.extend({ model : this.model, property : 'tags' }); + }, _onBeforeSave : function() { var profileId = this.ui.profile.val(); this.model.set({ profileId : profileId }); + var pathState = this.model.get("pathState"); + if (pathState === true) { + this.model.set("pathState", "static"); + } else { + this.model.set("pathState", "dynamic"); + } }, _onAfterSave : function() { diff --git a/src/UI/Movies/Editor/MovieEditorFooterView.js b/src/UI/Movies/Editor/MovieEditorFooterView.js index 3a513f35b..ff24b2493 100644 --- a/src/UI/Movies/Editor/MovieEditorFooterView.js +++ b/src/UI/Movies/Editor/MovieEditorFooterView.js @@ -15,7 +15,7 @@ module.exports = Marionette.ItemView.extend({ monitored : '.x-monitored', profile : '.x-profiles', minimumAvailability : '.x-minimumavailability', - seasonFolder : '.x-season-folder', + staticPath : '.x-static-path', rootFolder : '.x-root-folder', selectedCount : '.x-selected-count', container : '.series-editor-footer', @@ -52,7 +52,7 @@ module.exports = Marionette.ItemView.extend({ this.listenTo(FullMovieCollection, 'save', function() { window.alert(' Done Saving'); - + var selected = FullMovieCollection.where({ selected : true }); }); @@ -71,7 +71,7 @@ module.exports = Marionette.ItemView.extend({ var monitored = this.ui.monitored.val(); var minAvail = this.ui.minimumAvailability.val(); var profile = this.ui.profile.val(); - var seasonFolder = this.ui.seasonFolder.val(); + var staticPath = this.ui.staticPath.val(); var rootFolder = this.ui.rootFolder.val(); var i = 0; @@ -94,10 +94,8 @@ module.exports = Marionette.ItemView.extend({ model.set('profileId', parseInt(profile, 10)); } - if (seasonFolder === 'true') { - model.set('seasonFolder', true); - } else if (seasonFolder === 'false') { - model.set('seasonFolder', false); + if (staticPath !== 'noChange') { + model.set('pathState', staticPath); } if (rootFolder !== 'noChange') { @@ -129,10 +127,8 @@ module.exports = Marionette.ItemView.extend({ m.set('profileId', parseInt(profile, 10)); } - if (seasonFolder === 'true') { - m.set('seasonFolder', true); - } else if (seasonFolder === 'false') { - m.set('seasonFolder', false); + if (staticPath !== 'noChange') { + m.set('pathState', staticPath); } if (rootFolder !== 'noChange') { diff --git a/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs b/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs index c53c818be..cd443eb93 100644 --- a/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs +++ b/src/UI/Movies/Editor/MovieEditorFooterViewTemplate.hbs @@ -1,6 +1,6 @@