1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-09-11 12:02:35 +02:00

New: Allow Nested Movie Folders

This commit is contained in:
Qstick 2020-02-26 22:11:53 -05:00
parent 232682f109
commit 64382e13a4
8 changed files with 108 additions and 75 deletions

View File

@ -117,6 +117,7 @@ class NamingModal extends Component {
{ token: '{Movie Title}', example: 'Movie Title!' },
{ token: '{Movie CleanTitle}', example: 'Movie Title' },
{ token: '{Movie TitleThe}', example: 'Movie Title, The' },
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
{ token: '{Movie Certification}', example: 'R' },
{ token: '{Release Year}', example: '2009' }
];

View File

@ -0,0 +1,57 @@
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class MovieTitleFirstCharacterFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_movie = Builder<Movie>
.CreateNew()
.Build();
_namingConfig = NamingConfig.Default;
_namingConfig.RenameEpisodes = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
}
[TestCase("The Mist", "M", "The Mist")]
[TestCase("A", "A", "A")]
[TestCase("30 Rock", "3", "30 Rock")]
public void should_get_expected_folder_name_back(string title, string parent, string child)
{
_movie.Title = title;
_namingConfig.MovieFolderFormat = "{Movie TitleFirstCharacter}\\{Movie Title}";
Subject.GetMovieFolder(_movie).Should().Be(Path.Combine(parent, child));
}
[Test]
public void should_be_able_to_use_lower_case_first_character()
{
_movie.Title = "Westworld";
_namingConfig.MovieFolderFormat = "{movie titlefirstcharacter}\\{movie title}";
Subject.GetMovieFolder(_movie).Should().Be(Path.Combine("w", "westworld"));
}
}
}

View File

@ -7,6 +7,7 @@ public class MovieFolderCreatedEvent : IEvent
{
public Movie Movie { get; private set; }
public MovieFile MovieFile { get; private set; }
public string MovieFileFolder { get; set; }
public string MovieFolder { get; set; }
public MovieFolderCreatedEvent(Movie movie, MovieFile movieFile)

View File

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
@ -24,35 +23,29 @@ public interface IMoveMovieFiles
public class MovieFileMovingService : IMoveMovieFiles
{
private readonly IMovieService _movieService;
private readonly IUpdateMovieFileService _updateMovieFileService;
private readonly IBuildFileNames _buildFileNames;
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;
public MovieFileMovingService(IMovieService movieService,
IUpdateMovieFileService updateMovieFileService,
public MovieFileMovingService(IUpdateMovieFileService updateMovieFileService,
IBuildFileNames buildFileNames,
IDiskTransferService diskTransferService,
IDiskProvider diskProvider,
IMediaFileAttributeService mediaFileAttributeService,
IRecycleBinProvider recycleBinProvider,
IEventAggregator eventAggregator,
IConfigService configService,
Logger logger)
{
_movieService = movieService;
_updateMovieFileService = updateMovieFileService;
_buildFileNames = buildFileNames;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider;
_mediaFileAttributeService = mediaFileAttributeService;
_recycleBinProvider = recycleBinProvider;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
@ -119,12 +112,6 @@ private MovieFile TransferFile(MovieFile movieFile, Movie movie, string destinat
_diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode);
var oldMoviePath = movie.Path;
var newMoviePath = new OsPath(destinationFilePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar);
movie.Path = newMoviePath; //We update it when everything went well!
movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);
_updateMovieFileService.ChangeFileDateForFile(movieFile, movie);
@ -140,37 +127,6 @@ private MovieFile TransferFile(MovieFile movieFile, Movie movie, string destinat
_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
if (oldMoviePath != newMoviePath && _diskProvider.FolderExists(oldMoviePath))
{
//Let's move the old files before deleting the old folder. We could just do move folder, but the main file (movie file) is already moved, so eh.
var files = _diskProvider.GetFiles(oldMoviePath, SearchOption.AllDirectories);
foreach (var file in files)
{
try
{
var destFile = Path.Combine(newMoviePath, oldMoviePath.GetRelativePath(file));
_diskProvider.EnsureFolder(Path.GetDirectoryName(destFile));
_diskProvider.MoveFile(file, destFile);
}
catch (Exception e)
{
_logger.Warn(e, "Error while trying to move extra file {0} to new folder. Maybe it already exists? (Manual cleanup necessary!).", oldMoviePath.GetRelativePath(file));
}
}
if (_diskProvider.GetFiles(oldMoviePath, SearchOption.AllDirectories).Count() == 0)
{
_recycleBinProvider.DeleteFolder(oldMoviePath);
}
}
//Only update the movie path if we were successfull!
if (oldMoviePath != newMoviePath)
{
_movieService.UpdateMovie(movie);
}
return movieFile;
}
@ -181,11 +137,10 @@ private void EnsureMovieFolder(MovieFile movieFile, LocalMovie localMovie, strin
private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath)
{
var movieFolder = Path.GetDirectoryName(filePath);
var movieFileFolder = Path.GetDirectoryName(filePath);
//movie.Path = movieFolder;
var movieFolder = movie.Path;
var rootFolder = new OsPath(movieFolder).Directory.FullPath;
var fileName = Path.GetFileName(filePath);
if (!_diskProvider.FolderExists(rootFolder))
{
@ -202,6 +157,13 @@ private void EnsureMovieFolder(MovieFile movieFile, Movie movie, string filePath
changed = true;
}
if (movieFolder != movieFileFolder && !_diskProvider.FolderExists(movieFileFolder))
{
CreateFolder(movieFileFolder);
newEvent.MovieFileFolder = movieFileFolder;
changed = true;
}
if (changed)
{
_eventAggregator.PublishEvent(newEvent);

View File

@ -53,6 +53,7 @@ private void MoveSingleMovie(Movie movie, string sourcePath, string destinationP
try
{
_diskProvider.CreateFolder(new DirectoryInfo(destinationPath).Parent.FullName);
_diskTransferService.TransferFolder(sourcePath, destinationPath, TransferMode.Move);
_logger.ProgressInfo("{0} moved successfully to {1}", movie.Title, movie.Path);

View File

@ -1,8 +0,0 @@
namespace NzbDrone.Core.Organizer
{
public class AbsoluteEpisodeFormat
{
public string Separator { get; set; }
public string AbsoluteEpisodePattern { get; set; }
}
}

View File

@ -1,10 +0,0 @@
namespace NzbDrone.Core.Organizer
{
public class EpisodeSortingType
{
public int Id { get; set; }
public string Name { get; set; }
public string Pattern { get; set; }
public string EpisodeSeparator { get; set; }
}
}

View File

@ -105,11 +105,25 @@ public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namin
AddTagsTokens(tokenHandlers, movieFile);
AddCustomFormats(tokenHandlers, movie, movieFile, customFormats);
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
var splitPatterns = pattern.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
var components = new List<string>();
return fileName;
foreach (var s in splitPatterns)
{
var splitPattern = s;
var component = ReplaceTokens(splitPattern, tokenHandlers, namingConfig).Trim();
component = FileNameCleanupRegex.Replace(component, match => match.Captures[0].Value[0].ToString());
component = TrimSeparatorsRegex.Replace(component, string.Empty);
if (component.IsNotNullOrWhiteSpace())
{
components.Add(component);
}
}
return Path.Combine(components.ToArray());
}
public string BuildFilePath(Movie movie, string fileName, string extension)
@ -154,8 +168,23 @@ public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" });
}
string name = ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig);
return CleanFolderName(name, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat);
var splitPatterns = pattern.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
var components = new List<string>();
foreach (var s in splitPatterns)
{
var splitPattern = s;
var component = ReplaceTokens(splitPattern, tokenHandlers, namingConfig);
component = CleanFolderName(component);
if (component.IsNotNullOrWhiteSpace())
{
components.Add(component);
}
}
return Path.Combine(components.ToArray());
}
public static string CleanTitle(string title)
@ -188,12 +217,11 @@ public static string CleanFileName(string name, bool replace = true, ColonReplac
return result.Trim();
}
public static string CleanFolderName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete)
public static string CleanFolderName(string name)
{
name = FileNameCleanupRegex.Replace(name, match => match.Captures[0].Value[0].ToString());
name = name.Trim(' ', '.');
return CleanFileName(name, replace, colonReplacement);
return name.Trim(' ', '.');
}
private void AddMovieTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Movie movie)
@ -202,6 +230,7 @@ private void AddMovieTokens(Dictionary<string, Func<TokenMatch, string>> tokenHa
tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(movie.Title);
tokenHandlers["{Movie Title The}"] = m => TitleThe(movie.Title);
tokenHandlers["{Movie Certification}"] = mbox => movie.Certification;
tokenHandlers["{Movie TitleFirstCharacter}"] = m => TitleThe(movie.Title).Substring(0, 1).FirstCharToUpper();
}
private void AddTagsTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile movieFile)