From e954b019218259b831f1d08844d01585185fe715 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 11 Jan 2020 22:13:28 -0500 Subject: [PATCH] Fixed: Remove Static/Dynamic Settings, Allow Folder Move from Editor --- .../src/Movie/MoveMovie/MoveMovieModal.js | 7 ++ .../Config/MediaManagementConfigResource.cs | 1 - src/NzbDrone.Api/Movies/MovieEditorModule.cs | 2 +- src/NzbDrone.Api/Movies/MovieResource.cs | 3 - .../Datastore/WhereBuilderFixture.cs | 16 ++-- .../MovieFolderPathBuilderFixture.cs | 94 +++++++++++++++++++ .../UpdateMultipleMoviesFixture.cs | 18 +++- .../NetImportSearchServiceFixture.cs | 16 ++-- .../Configuration/ConfigService.cs | 7 -- .../Configuration/IConfigService.cs | 1 - .../Migration/167_remove_movie_pathstate.cs | 21 +++++ .../MediaFiles/RenameMovieFileService.cs | 92 ++---------------- .../Movies/Commands/MoveMovieCommand.cs | 1 + src/NzbDrone.Core/Movies/Movie.cs | 9 -- src/NzbDrone.Core/Movies/MoviePathBuilder.cs | 48 ++++++++++ src/NzbDrone.Core/Movies/MovieService.cs | 44 +++------ .../Movies/RefreshMovieService.cs | 2 +- .../NetImport/NetImportSearchService.cs | 2 +- .../Organizer/FileNameBuilder.cs | 51 +--------- .../Config/MediaManagementConfigResource.cs | 1 - src/Radarr.Api.V3/Movies/MovieEditorModule.cs | 2 +- src/Radarr.Api.V3/Movies/MovieModule.cs | 24 ++++- src/Radarr.Api.V3/Movies/MovieResource.cs | 4 - 23 files changed, 251 insertions(+), 215 deletions(-) create mode 100644 src/NzbDrone.Core.Test/MovieTests/MovieFolderPathBuilderFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/167_remove_movie_pathstate.cs create mode 100644 src/NzbDrone.Core/Movies/MoviePathBuilder.cs diff --git a/frontend/src/Movie/MoveMovie/MoveMovieModal.js b/frontend/src/Movie/MoveMovie/MoveMovieModal.js index 6fa01ec76..04bb45e37 100644 --- a/frontend/src/Movie/MoveMovie/MoveMovieModal.js +++ b/frontend/src/Movie/MoveMovie/MoveMovieModal.js @@ -49,6 +49,13 @@ function MoveMovieModal(props) { `Would you like to move the movie folders to '${destinationRootFolder}'?` : `Would you like to move the movie files from '${originalPath}' to '${destinationPath}'?` } + { + destinationRootFolder ? +
+ This will also rename the movie folder per the movie folder format in settings. +
: + null + } diff --git a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs index 534cda2d6..c62d239ce 100644 --- a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs +++ b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs @@ -39,7 +39,6 @@ namespace NzbDrone.Api.Config CreateEmptySeriesFolders = model.CreateEmptyMovieFolders, FileDate = model.FileDate, AutoRenameFolders = model.AutoRenameFolders, - PathsDefaultStatic = model.PathsDefaultStatic, SetPermissionsLinux = model.SetPermissionsLinux, FileChmod = model.FileChmod, diff --git a/src/NzbDrone.Api/Movies/MovieEditorModule.cs b/src/NzbDrone.Api/Movies/MovieEditorModule.cs index 31c184a40..a97443385 100644 --- a/src/NzbDrone.Api/Movies/MovieEditorModule.cs +++ b/src/NzbDrone.Api/Movies/MovieEditorModule.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Api.Movies var movie = resources.Select(movieResource => movieResource.ToModel(_movieService.GetMovie(movieResource.Id))).ToList(); - return ResponseWithCode(_movieService.UpdateMovie(movie) + return ResponseWithCode(_movieService.UpdateMovie(movie, true) .ToResource(), HttpStatusCode.Accepted); } diff --git a/src/NzbDrone.Api/Movies/MovieResource.cs b/src/NzbDrone.Api/Movies/MovieResource.cs index 7dc9f40b4..9d745b50d 100644 --- a/src/NzbDrone.Api/Movies/MovieResource.cs +++ b/src/NzbDrone.Api/Movies/MovieResource.cs @@ -43,7 +43,6 @@ namespace NzbDrone.Api.Movies //View & Edit public string Path { get; set; } public int ProfileId { get; set; } - public MoviePathState PathState { get; set; } //Editing Only public bool Monitored { get; set; } @@ -144,7 +143,6 @@ namespace NzbDrone.Api.Movies Path = model.Path, ProfileId = model.ProfileId, - PathState = model.PathState, Monitored = model.Monitored, MinimumAvailability = model.MinimumAvailability, @@ -209,7 +207,6 @@ namespace NzbDrone.Api.Movies Path = resource.Path, ProfileId = resource.ProfileId, - PathState = resource.PathState, Monitored = resource.Monitored, MinimumAvailability = resource.MinimumAvailability, diff --git a/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs b/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs index f6882efb8..e954bdfde 100644 --- a/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs @@ -153,30 +153,30 @@ namespace NzbDrone.Core.Test.Datastore [Test] public void enum_as_int() { - _subject = Where(x => x.PathState == MoviePathState.Static); + _subject = Where(x => x.Status == MovieStatusType.Released); var name = _subject.Parameters.ParameterNames.First(); - _subject.ToString().Should().Be($"(\"Movies\".\"PathState\" = @{name})"); + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" = @{name})"); } [Test] public void enum_in_list() { - var allowed = new List { MoviePathState.Dynamic, MoviePathState.Static }; - _subject = Where(x => allowed.Contains(x.PathState)); + var allowed = new List { MovieStatusType.InCinemas, MovieStatusType.Released }; + _subject = Where(x => allowed.Contains(x.Status)); var name = _subject.Parameters.ParameterNames.First(); - _subject.ToString().Should().Be($"(\"Movies\".\"PathState\" IN @{name})"); + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" IN @{name})"); } [Test] public void enum_in_array() { - var allowed = new MoviePathState[] { MoviePathState.Dynamic, MoviePathState.Static }; - _subject = Where(x => allowed.Contains(x.PathState)); + var allowed = new MovieStatusType[] { MovieStatusType.InCinemas, MovieStatusType.Released }; + _subject = Where(x => allowed.Contains(x.Status)); var name = _subject.Parameters.ParameterNames.First(); - _subject.ToString().Should().Be($"(\"Movies\".\"PathState\" IN @{name})"); + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" IN @{name})"); } } } diff --git a/src/NzbDrone.Core.Test/MovieTests/MovieFolderPathBuilderFixture.cs b/src/NzbDrone.Core.Test/MovieTests/MovieFolderPathBuilderFixture.cs new file mode 100644 index 000000000..bcc2981c9 --- /dev/null +++ b/src/NzbDrone.Core.Test/MovieTests/MovieFolderPathBuilderFixture.cs @@ -0,0 +1,94 @@ +using System.IO; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.RootFolders; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MovieTests +{ + [TestFixture] + public class MovieFolderPathBuilderFixture : CoreTest + { + private Movie _movie; + + [SetUp] + public void Setup() + { + _movie = Builder.CreateNew() + .With(s => s.Title = "Movie Title") + .With(s => s.Path = @"C:\Test\Movies\Movie.Title".AsOsAgnostic()) + .With(s => s.RootFolderPath = null) + .Build(); + } + + public void GivenMovieFolderName(string name) + { + Mocker.GetMock() + .Setup(s => s.GetMovieFolder(_movie, null)) + .Returns(name); + } + + public void GivenExistingRootFolder(string rootFolder) + { + Mocker.GetMock() + .Setup(s => s.GetBestRootFolderPath(It.IsAny())) + .Returns(rootFolder); + } + + [Test] + public void should_create_new_movie_path() + { + var rootFolder = @"C:\Test\Movies2".AsOsAgnostic(); + + GivenMovieFolderName(_movie.Title); + _movie.RootFolderPath = rootFolder; + + Subject.BuildPath(_movie, false).Should().Be(Path.Combine(rootFolder, _movie.Title)); + } + + [Test] + public void should_reuse_existing_relative_folder_name() + { + var folderName = Path.GetFileName(_movie.Path); + var rootFolder = @"C:\Test\Movies2".AsOsAgnostic(); + + GivenExistingRootFolder(Path.GetDirectoryName(_movie.Path)); + GivenMovieFolderName(_movie.Title); + _movie.RootFolderPath = rootFolder; + + Subject.BuildPath(_movie, true).Should().Be(Path.Combine(rootFolder, folderName)); + } + + [Test] + public void should_reuse_existing_relative_folder_structure() + { + var existingRootFolder = @"C:\Test\Movies".AsOsAgnostic(); + var existingRelativePath = @"M\Movie.Title"; + var rootFolder = @"C:\Test\Movies2".AsOsAgnostic(); + + GivenExistingRootFolder(existingRootFolder); + GivenMovieFolderName(_movie.Title); + _movie.RootFolderPath = rootFolder; + _movie.Path = Path.Combine(existingRootFolder, existingRelativePath); + + Subject.BuildPath(_movie, true).Should().Be(Path.Combine(rootFolder, existingRelativePath)); + } + + [Test] + public void should_use_built_path_for_new_movie() + { + var rootFolder = @"C:\Test\Movies2".AsOsAgnostic(); + + GivenMovieFolderName(_movie.Title); + _movie.RootFolderPath = rootFolder; + _movie.Path = null; + + Subject.BuildPath(_movie, true).Should().Be(Path.Combine(rootFolder, _movie.Title)); + } + } +} diff --git a/src/NzbDrone.Core.Test/MovieTests/MovieServiceTests/UpdateMultipleMoviesFixture.cs b/src/NzbDrone.Core.Test/MovieTests/MovieServiceTests/UpdateMultipleMoviesFixture.cs index e5606c670..0f518244f 100644 --- a/src/NzbDrone.Core.Test/MovieTests/MovieServiceTests/UpdateMultipleMoviesFixture.cs +++ b/src/NzbDrone.Core.Test/MovieTests/MovieServiceTests/UpdateMultipleMoviesFixture.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Core.Movies; +using NzbDrone.Core.Organizer; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; @@ -30,7 +32,7 @@ namespace NzbDrone.Core.Test.MovieTests.MovieServiceTests [Test] public void should_call_repo_updateMany() { - Subject.UpdateMovie(_movies); + Subject.UpdateMovie(_movies, false); Mocker.GetMock().Verify(v => v.UpdateMany(_movies), Times.Once()); } @@ -41,13 +43,17 @@ namespace NzbDrone.Core.Test.MovieTests.MovieServiceTests var newRoot = @"C:\Test\TV2".AsOsAgnostic(); _movies.ForEach(s => s.RootFolderPath = newRoot); - Subject.UpdateMovie(_movies).ForEach(s => s.Path.Should().StartWith(newRoot)); + Mocker.GetMock() + .Setup(s => s.BuildPath(It.IsAny(), false)) + .Returns((s, u) => Path.Combine(s.RootFolderPath, s.Title)); + + Subject.UpdateMovie(_movies, false).ForEach(s => s.Path.Should().StartWith(newRoot)); } [Test] public void should_not_update_path_when_rootFolderPath_is_empty() { - Subject.UpdateMovie(_movies).ForEach(s => + Subject.UpdateMovie(_movies, false).ForEach(s => { var expectedPath = _movies.Single(ser => ser.Id == s.Id).Path; s.Path.Should().Be(expectedPath); @@ -66,7 +72,11 @@ namespace NzbDrone.Core.Test.MovieTests.MovieServiceTests var newRoot = @"C:\Test\Movies2".AsOsAgnostic(); movies.ForEach(s => s.RootFolderPath = newRoot); - Subject.UpdateMovie(movies); + Mocker.GetMock() + .Setup(s => s.GetMovieFolder(It.IsAny(), (NamingConfig)null)) + .Returns((s, n) => s.Title); + + Subject.UpdateMovie(movies, false); } } } diff --git a/src/NzbDrone.Core.Test/NetImport/NetImportSearchServiceFixture.cs b/src/NzbDrone.Core.Test/NetImport/NetImportSearchServiceFixture.cs index 88e1cb3b9..ae605ac1d 100644 --- a/src/NzbDrone.Core.Test/NetImport/NetImportSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/NetImport/NetImportSearchServiceFixture.cs @@ -116,7 +116,7 @@ namespace NzbDrone.Core.Test.NetImport .Verify(v => v.GetAllMovies(), Times.Never()); Mocker.GetMock() - .Verify(v => v.UpdateMovie(new List()), Times.Once()); + .Verify(v => v.UpdateMovie(new List(), true), Times.Once()); } [Test] @@ -139,7 +139,7 @@ namespace NzbDrone.Core.Test.NetImport .Verify(v => v.DeleteMovie(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); Mocker.GetMock() - .Verify(v => v.UpdateMovie(new List()), Times.Once()); + .Verify(v => v.UpdateMovie(new List(), true), Times.Once()); } [Test] @@ -162,7 +162,7 @@ namespace NzbDrone.Core.Test.NetImport .Verify(v => v.DeleteMovie(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); Mocker.GetMock() - .Verify(v => v.UpdateMovie(It.Is>(s => s.Count == 3 && s.All(m => !m.Monitored))), Times.Once()); + .Verify(v => v.UpdateMovie(It.Is>(s => s.Count == 3 && s.All(m => !m.Monitored)), true), Times.Once()); } [Test] @@ -181,7 +181,7 @@ namespace NzbDrone.Core.Test.NetImport Subject.Execute(_command); Mocker.GetMock() - .Verify(v => v.UpdateMovie(It.Is>(s => s.Count == 2 && s.All(m => !m.Monitored))), Times.Once()); + .Verify(v => v.UpdateMovie(It.Is>(s => s.Count == 2 && s.All(m => !m.Monitored)), true), Times.Once()); } [Test] @@ -201,7 +201,7 @@ namespace NzbDrone.Core.Test.NetImport Subject.Execute(_command); Mocker.GetMock() - .Verify(v => v.UpdateMovie(It.Is>(s => s.Count == 2 && s.All(m => !m.Monitored))), Times.Once()); + .Verify(v => v.UpdateMovie(It.Is>(s => s.Count == 2 && s.All(m => !m.Monitored)), true), Times.Once()); } [Test] @@ -227,7 +227,7 @@ namespace NzbDrone.Core.Test.NetImport .Verify(v => v.DeleteMovie(It.IsAny(), true, It.IsAny()), Times.Never()); Mocker.GetMock() - .Verify(v => v.UpdateMovie(new List()), Times.Once()); + .Verify(v => v.UpdateMovie(new List(), true), Times.Once()); } [Test] @@ -253,7 +253,7 @@ namespace NzbDrone.Core.Test.NetImport .Verify(v => v.DeleteMovie(It.IsAny(), true, It.IsAny()), Times.Exactly(3)); Mocker.GetMock() - .Verify(v => v.UpdateMovie(new List()), Times.Once()); + .Verify(v => v.UpdateMovie(new List(), true), Times.Once()); } [Test] @@ -267,7 +267,7 @@ namespace NzbDrone.Core.Test.NetImport Subject.Execute(_command); Mocker.GetMock() - .Verify(v => v.UpdateMovie(new List()), Times.Never()); + .Verify(v => v.UpdateMovie(new List(), true), Times.Never()); } [Test] diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 664978595..303f83232 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -299,13 +299,6 @@ namespace NzbDrone.Core.Configuration set { SetValue("AutoRenameFolders", value); } } - public bool PathsDefaultStatic - { - get { return GetValueBoolean("PathsDefaultStatic", true); } - - set { SetValue("PathsDefaultStatic", value); } - } - public RescanAfterRefreshType RescanAfterRefresh { get { return GetValueEnum("RescanAfterRefresh", RescanAfterRefreshType.Always); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 8da36b895..1fd9d9992 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -40,7 +40,6 @@ namespace NzbDrone.Core.Configuration string ExtraFileExtensions { get; set; } RescanAfterRefreshType RescanAfterRefresh { 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/167_remove_movie_pathstate.cs b/src/NzbDrone.Core/Datastore/Migration/167_remove_movie_pathstate.cs new file mode 100644 index 000000000..a3f191b48 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/167_remove_movie_pathstate.cs @@ -0,0 +1,21 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(167)] + public class remove_movie_pathstate : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Delete.Column("PathState").FromTable("Movies"); + + Execute.Sql("DELETE FROM Config WHERE [KEY] IN ('pathsdefaultstatic')"); + + Alter.Table("MovieFiles").AddColumn("OriginalFilePath").AsString().Nullable(); + + //This is Ignored in mapping, should not be in DB + Delete.Column("Path").FromTable("MovieFiles"); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs index ed398f23f..0650700b1 100644 --- a/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/RenameMovieFileService.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.Data.SQLite; using System.IO; using System.Linq; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation.Extensions; -using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Commands; @@ -20,22 +18,18 @@ 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, @@ -43,8 +37,6 @@ namespace NzbDrone.Core.MediaFiles IMoveMovieFiles movieFileMover, IEventAggregator eventAggregator, IBuildFileNames filenameBuilder, - IConfigService configService, - IRecycleBinProvider recycleBinProvider, IDiskProvider diskProvider, Logger logger) { @@ -53,8 +45,6 @@ namespace NzbDrone.Core.MediaFiles _movieFileMover = movieFileMover; _eventAggregator = eventAggregator; _filenameBuilder = filenameBuilder; - _configService = configService; - _recycleBinProvider = recycleBinProvider; _diskProvider = diskProvider; _logger = logger; } @@ -82,28 +72,21 @@ namespace NzbDrone.Core.MediaFiles { MovieId = movie.Id, MovieFileId = file.Id, - ExistingPath = movieFilePath, + ExistingPath = file.RelativePath, - //NewPath = movie.Path.GetRelativePath(newPath) - NewPath = newPath + NewPath = movie.Path.GetRelativePath(newPath) }; } } } - private void RenameFiles(List movieFiles, Movie movie, string oldMoviePath = null) + private void RenameFiles(List movieFiles, Movie movie) { var renamed = new List(); - if (oldMoviePath == null) - { - oldMoviePath = movie.Path; - } - foreach (var movieFile in movieFiles) { - var oldMovieFilePath = Path.Combine(oldMoviePath, movieFile.RelativePath); - movieFile.Path = oldMovieFilePath; + var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath); try { @@ -116,7 +99,7 @@ namespace NzbDrone.Core.MediaFiles _logger.Debug("Renamed movie file: {0}", movieFile); - _eventAggregator.PublishEvent(new MovieFileRenamedEvent(movie, movieFile, oldMovieFilePath)); + _eventAggregator.PublishEvent(new MovieFileRenamedEvent(movie, movieFile, movieFilePath)); } catch (SameFilenameException ex) { @@ -124,52 +107,15 @@ namespace NzbDrone.Core.MediaFiles } catch (Exception ex) { - _logger.Error(ex, "Failed to rename file: {0}", oldMovieFilePath); + _logger.Error(ex, "Failed to rename file: {0}", movieFilePath); } } if (renamed.Any()) { - _eventAggregator.PublishEvent(new MovieRenamedEvent(movie)); - } - } - - 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; - - _diskProvider.MoveFolder(oldFolder, movie.Path); - - // if (false) - // { - // 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); - } - } + _diskProvider.RemoveEmptySubfolders(movie.Path); - if (movie.PathState == MoviePathState.StaticOnce) - { - movie.PathState = MoviePathState.Dynamic; - _movieService.UpdateMovie(movie); + _eventAggregator.PublishEvent(new MovieRenamedEvent(movie)); } } @@ -196,25 +142,5 @@ namespace NzbDrone.Core.MediaFiles _logger.ProgressInfo("All movie files renamed for {0}", movie.Title); } } - - public void Execute(RenameMovieFolderCommand message) - { - try - { - _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); - } - } - catch (SQLiteException ex) - { - _logger.Warn(ex, "wtf: {0}, {1}", ex.ResultCode, ex.Data); - } - } } } diff --git a/src/NzbDrone.Core/Movies/Commands/MoveMovieCommand.cs b/src/NzbDrone.Core/Movies/Commands/MoveMovieCommand.cs index a742ff66a..d99ed2932 100644 --- a/src/NzbDrone.Core/Movies/Commands/MoveMovieCommand.cs +++ b/src/NzbDrone.Core/Movies/Commands/MoveMovieCommand.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.Movies.Commands public string DestinationPath { get; set; } public string DestinationRootFolder { get; set; } + public override bool SendUpdatesToClient => true; public override bool RequiresDiskAccess => true; } } diff --git a/src/NzbDrone.Core/Movies/Movie.cs b/src/NzbDrone.Core/Movies/Movie.cs index 4428db360..01d05706a 100644 --- a/src/NzbDrone.Core/Movies/Movie.cs +++ b/src/NzbDrone.Core/Movies/Movie.cs @@ -42,7 +42,6 @@ namespace NzbDrone.Core.Movies 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; } @@ -154,7 +153,6 @@ namespace NzbDrone.Core.Movies Path = otherMovie.Path; ProfileId = otherMovie.ProfileId; - PathState = otherMovie.PathState; Monitored = otherMovie.Monitored; MinimumAvailability = otherMovie.MinimumAvailability; @@ -164,11 +162,4 @@ namespace NzbDrone.Core.Movies AddOptions = otherMovie.AddOptions; } } - - public enum MoviePathState - { - Dynamic, - StaticOnce, - Static, - } } diff --git a/src/NzbDrone.Core/Movies/MoviePathBuilder.cs b/src/NzbDrone.Core/Movies/MoviePathBuilder.cs new file mode 100644 index 000000000..8d9c520ee --- /dev/null +++ b/src/NzbDrone.Core/Movies/MoviePathBuilder.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.RootFolders; + +namespace NzbDrone.Core.Movies +{ + public interface IBuildMoviePaths + { + string BuildPath(Movie movie, bool useExistingRelativeFolder); + } + + public class MoviePathBuilder : IBuildMoviePaths + { + private readonly IBuildFileNames _fileNameBuilder; + private readonly IRootFolderService _rootFolderService; + + public MoviePathBuilder(IBuildFileNames fileNameBuilder, IRootFolderService rootFolderService) + { + _fileNameBuilder = fileNameBuilder; + _rootFolderService = rootFolderService; + } + + public string BuildPath(Movie movie, bool useExistingRelativeFolder) + { + if (movie.RootFolderPath.IsNullOrWhiteSpace()) + { + throw new ArgumentException("Root folder was not provided", nameof(movie)); + } + + if (useExistingRelativeFolder && movie.Path.IsNotNullOrWhiteSpace()) + { + var relativePath = GetExistingRelativePath(movie); + return Path.Combine(movie.RootFolderPath, relativePath); + } + + return Path.Combine(movie.RootFolderPath, _fileNameBuilder.GetMovieFolder(movie)); + } + + private string GetExistingRelativePath(Movie movie) + { + var rootFolderPath = _rootFolderService.GetBestRootFolderPath(movie.Path); + + return rootFolderPath.GetRelativePath(movie.Path); + } + } +} diff --git a/src/NzbDrone.Core/Movies/MovieService.cs b/src/NzbDrone.Core/Movies/MovieService.cs index 3ad94ba11..0ab43ba3f 100644 --- a/src/NzbDrone.Core/Movies/MovieService.cs +++ b/src/NzbDrone.Core/Movies/MovieService.cs @@ -43,7 +43,7 @@ namespace NzbDrone.Core.Movies List GetAllMovies(); List AllForTag(int tagId); Movie UpdateMovie(Movie movie); - List UpdateMovie(List movie); + List UpdateMovie(List movie, bool useExistingRelativeFolder); List FilterExistingMovies(List movies); bool MoviePathExists(string folder); void RemoveAddOptions(Movie movie); @@ -57,6 +57,7 @@ namespace NzbDrone.Core.Movies private readonly IEventAggregator _eventAggregator; private readonly IBuildFileNames _fileNameBuilder; private readonly IImportExclusionsService _exclusionService; + private readonly IBuildMoviePaths _moviePathBuilder; private readonly Logger _logger; public MovieService(IMovieRepository movieRepository, @@ -64,6 +65,7 @@ namespace NzbDrone.Core.Movies IBuildFileNames fileNameBuilder, IConfigService configService, IImportExclusionsService exclusionService, + IBuildMoviePaths moviePathBuilder, Logger logger) { _movieRepository = movieRepository; @@ -71,6 +73,7 @@ namespace NzbDrone.Core.Movies _fileNameBuilder = fileNameBuilder; _configService = configService; _exclusionService = exclusionService; + _moviePathBuilder = moviePathBuilder; _logger = logger; } @@ -93,21 +96,10 @@ namespace NzbDrone.Core.Movies { 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); @@ -128,21 +120,10 @@ namespace NzbDrone.Core.Movies 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(); @@ -324,21 +305,22 @@ namespace NzbDrone.Core.Movies return updatedMovie; } - public List UpdateMovie(List movie) + public List UpdateMovie(List movie, bool useExistingRelativeFolder) { _logger.Debug("Updating {0} movie", movie.Count); - foreach (var s in movie) + foreach (var m in movie) { - _logger.Trace("Updating: {0}", s.Title); - if (!s.RootFolderPath.IsNullOrWhiteSpace()) + _logger.Trace("Updating: {0}", m.Title); + + if (!m.RootFolderPath.IsNullOrWhiteSpace()) { - var folderName = new DirectoryInfo(s.Path).Name; - s.Path = Path.Combine(s.RootFolderPath, folderName); - _logger.Trace("Changing path for {0} to {1}", s.Title, s.Path); + m.Path = _moviePathBuilder.BuildPath(m, useExistingRelativeFolder); + + _logger.Trace("Changing path for {0} to {1}", m.Title, m.Path); } else { - _logger.Trace("Not changing path for: {0}", s.Title); + _logger.Trace("Not changing path for: {0}", m.Title); } } diff --git a/src/NzbDrone.Core/Movies/RefreshMovieService.cs b/src/NzbDrone.Core/Movies/RefreshMovieService.cs index 5b2e37e92..4a8327ac3 100644 --- a/src/NzbDrone.Core/Movies/RefreshMovieService.cs +++ b/src/NzbDrone.Core/Movies/RefreshMovieService.cs @@ -138,7 +138,7 @@ namespace NzbDrone.Core.Movies _logger.Info(ex, "Unable to communicate with Mappings Server."); } - _movieService.UpdateMovie(new List { movie }); + _movieService.UpdateMovie(new List { movie }, true); _creditService.UpdateCredits(tuple.Item2, movie); try diff --git a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs index 4c22caa99..a4fcdf607 100644 --- a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs +++ b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs @@ -194,7 +194,7 @@ namespace NzbDrone.Core.NetImport } } - _movieService.UpdateMovie(moviesToUpdate); + _movieService.UpdateMovie(moviesToUpdate, true); } } } diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 0c4b57f38..83c7fd50b 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -18,7 +18,6 @@ namespace NzbDrone.Core.Organizer { string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null); string BuildFilePath(Movie movie, string fileName, string extension); - string BuildMoviePath(Movie movie, NamingConfig namingConfig = null); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); string GetMovieFolder(Movie movie, NamingConfig namingConfig = null); } @@ -110,57 +109,9 @@ namespace NzbDrone.Core.Organizer { Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); - 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); - AddIdTokens(tokenHandlers, movie); - - if (movie.MovieFile != null) - { - 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); + return Path.Combine(path, fileName + extension); } public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) diff --git a/src/Radarr.Api.V3/Config/MediaManagementConfigResource.cs b/src/Radarr.Api.V3/Config/MediaManagementConfigResource.cs index 5880b889b..b25746e0b 100644 --- a/src/Radarr.Api.V3/Config/MediaManagementConfigResource.cs +++ b/src/Radarr.Api.V3/Config/MediaManagementConfigResource.cs @@ -46,7 +46,6 @@ namespace Radarr.Api.V3.Config FileDate = model.FileDate, RescanAfterRefresh = model.RescanAfterRefresh, AutoRenameFolders = model.AutoRenameFolders, - PathsDefaultStatic = model.PathsDefaultStatic, SetPermissionsLinux = model.SetPermissionsLinux, FileChmod = model.FileChmod, diff --git a/src/Radarr.Api.V3/Movies/MovieEditorModule.cs b/src/Radarr.Api.V3/Movies/MovieEditorModule.cs index 66f22257a..499d4a8e6 100644 --- a/src/Radarr.Api.V3/Movies/MovieEditorModule.cs +++ b/src/Radarr.Api.V3/Movies/MovieEditorModule.cs @@ -85,7 +85,7 @@ namespace Radarr.Api.V3.Movies }); } - return ResponseWithCode(_movieService.UpdateMovie(moviesToUpdate) + return ResponseWithCode(_movieService.UpdateMovie(moviesToUpdate, !resource.MoveFiles) .ToResource(), HttpStatusCode.Accepted); } diff --git a/src/Radarr.Api.V3/Movies/MovieModule.cs b/src/Radarr.Api.V3/Movies/MovieModule.cs index a0312d738..1ae827006 100644 --- a/src/Radarr.Api.V3/Movies/MovieModule.cs +++ b/src/Radarr.Api.V3/Movies/MovieModule.cs @@ -7,8 +7,10 @@ using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; +using NzbDrone.Core.Movies.Commands; using NzbDrone.Core.Movies.Events; using NzbDrone.Core.Validation; using NzbDrone.Core.Validation.Paths; @@ -29,11 +31,13 @@ namespace Radarr.Api.V3.Movies { protected readonly IMovieService _moviesService; private readonly IMapCoversToLocal _coverMapper; + private readonly IManageCommandQueue _commandQueueManager; private readonly IUpgradableSpecification _qualityUpgradableSpecification; public MovieModule(IBroadcastSignalRMessage signalRBroadcaster, IMovieService moviesService, IMapCoversToLocal coverMapper, + IManageCommandQueue commandQueueManager, IUpgradableSpecification qualityUpgradableSpecification, RootFolderValidator rootFolderValidator, MoviePathValidator moviesPathValidator, @@ -46,6 +50,7 @@ namespace Radarr.Api.V3.Movies _moviesService = moviesService; _qualityUpgradableSpecification = qualityUpgradableSpecification; _coverMapper = coverMapper; + _commandQueueManager = commandQueueManager; GetResourceAll = AllMovie; GetResourceById = GetMovie; @@ -114,7 +119,24 @@ namespace Radarr.Api.V3.Movies private void UpdateMovie(MovieResource moviesResource) { - var model = moviesResource.ToModel(_moviesService.GetMovie(moviesResource.Id)); + var moveFiles = Request.GetBooleanQueryParameter("moveFiles"); + var movie = _moviesService.GetMovie(moviesResource.Id); + + if (moveFiles) + { + var sourcePath = movie.Path; + var destinationPath = moviesResource.Path; + + _commandQueueManager.Push(new MoveMovieCommand + { + MovieId = movie.Id, + SourcePath = sourcePath, + DestinationPath = destinationPath, + Trigger = CommandTrigger.Manual + }); + } + + var model = moviesResource.ToModel(movie); _moviesService.UpdateMovie(model); diff --git a/src/Radarr.Api.V3/Movies/MovieResource.cs b/src/Radarr.Api.V3/Movies/MovieResource.cs index d04ca508d..66c1076cb 100644 --- a/src/Radarr.Api.V3/Movies/MovieResource.cs +++ b/src/Radarr.Api.V3/Movies/MovieResource.cs @@ -45,7 +45,6 @@ namespace Radarr.Api.V3.Movies //View & Edit public string Path { get; set; } public int QualityProfileId { get; set; } - public MoviePathState PathState { get; set; } //Editing Only public bool Monitored { get; set; } @@ -105,7 +104,6 @@ namespace Radarr.Api.V3.Movies Path = model.Path, QualityProfileId = model.ProfileId, - PathState = model.PathState, Monitored = model.Monitored, MinimumAvailability = model.MinimumAvailability, @@ -166,7 +164,6 @@ namespace Radarr.Api.V3.Movies Path = model.Path, QualityProfileId = model.ProfileId, - PathState = model.PathState, Monitored = model.Monitored, MinimumAvailability = model.MinimumAvailability, @@ -221,7 +218,6 @@ namespace Radarr.Api.V3.Movies Path = resource.Path, ProfileId = resource.QualityProfileId, - PathState = resource.PathState, Monitored = resource.Monitored, MinimumAvailability = resource.MinimumAvailability,