mirror of
https://github.com/Radarr/Radarr.git
synced 2025-04-23 14:07:20 -04:00
New: Allow Nested Movie Folders
This commit is contained in:
parent
232682f109
commit
64382e13a4
8 changed files with 108 additions and 75 deletions
|
@ -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' }
|
||||
];
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ namespace NzbDrone.Core.MediaFiles.Events
|
|||
{
|
||||
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)
|
||||
|
|
|
@ -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 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
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 @@ 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; //We update it when everything went well!
|
||||
|
||||
movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);
|
||||
|
||||
_updateMovieFileService.ChangeFileDateForFile(movieFile, movie);
|
||||
|
@ -140,37 +127,6 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
_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 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
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 @@ namespace NzbDrone.Core.MediaFiles
|
|||
changed = true;
|
||||
}
|
||||
|
||||
if (movieFolder != movieFileFolder && !_diskProvider.FolderExists(movieFileFolder))
|
||||
{
|
||||
CreateFolder(movieFileFolder);
|
||||
newEvent.MovieFileFolder = movieFileFolder;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
_eventAggregator.PublishEvent(newEvent);
|
||||
|
|
|
@ -53,6 +53,7 @@ namespace NzbDrone.Core.Movies
|
|||
|
||||
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);
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
namespace NzbDrone.Core.Organizer
|
||||
{
|
||||
public class AbsoluteEpisodeFormat
|
||||
{
|
||||
public string Separator { get; set; }
|
||||
public string AbsoluteEpisodePattern { get; set; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -105,11 +105,25 @@ namespace NzbDrone.Core.Organizer
|
|||
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 @@ namespace NzbDrone.Core.Organizer
|
|||
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 @@ namespace NzbDrone.Core.Organizer
|
|||
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 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue