mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-28 23:06:43 -04:00
Fixed: Support nested file naming formats
This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Books;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Organizer;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class NestedFileNameBuilderFixture : CoreTest<FileNameBuilder>
|
||||||
|
{
|
||||||
|
private Author _artist;
|
||||||
|
private Book _album;
|
||||||
|
private Edition _release;
|
||||||
|
private BookFile _trackFile;
|
||||||
|
private NamingConfig _namingConfig;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_artist = Builder<Author>
|
||||||
|
.CreateNew()
|
||||||
|
.With(s => s.Name = "AuthorName")
|
||||||
|
.With(s => s.Metadata = new AuthorMetadata
|
||||||
|
{
|
||||||
|
Disambiguation = "US Author",
|
||||||
|
Name = "AuthorName"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_album = Builder<Book>
|
||||||
|
.CreateNew()
|
||||||
|
.With(s => s.Author = _artist)
|
||||||
|
.With(s => s.AuthorMetadata = _artist.Metadata.Value)
|
||||||
|
.With(s => s.Title = "A Novel")
|
||||||
|
.With(s => s.ReleaseDate = new DateTime(2020, 1, 15))
|
||||||
|
.With(s => s.SeriesLinks = new List<SeriesBookLink>())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_release = Builder<Edition>
|
||||||
|
.CreateNew()
|
||||||
|
.With(s => s.Monitored = true)
|
||||||
|
.With(s => s.Book = _album)
|
||||||
|
.With(s => s.Title = "A Novel")
|
||||||
|
.With(s => s.ReleaseDate = new DateTime(2020, 1, 15))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_namingConfig = NamingConfig.Default;
|
||||||
|
_namingConfig.RenameBooks = true;
|
||||||
|
|
||||||
|
Mocker.GetMock<INamingConfigService>()
|
||||||
|
.Setup(c => c.GetConfig()).Returns(_namingConfig);
|
||||||
|
|
||||||
|
_trackFile = Builder<BookFile>.CreateNew()
|
||||||
|
.With(e => e.Quality = new QualityModel(Quality.MOBI))
|
||||||
|
.With(e => e.ReleaseGroup = "ReadarrTest")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Mocker.GetMock<IQualityDefinitionService>()
|
||||||
|
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
|
||||||
|
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithSeries()
|
||||||
|
{
|
||||||
|
_album.SeriesLinks = new List<SeriesBookLink>
|
||||||
|
{
|
||||||
|
new SeriesBookLink
|
||||||
|
{
|
||||||
|
Series = new Series
|
||||||
|
{
|
||||||
|
Title = "A Series",
|
||||||
|
},
|
||||||
|
Position = "2-3",
|
||||||
|
SeriesPosition = 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_build_nested_standard_track_filename_with_forward_slash()
|
||||||
|
{
|
||||||
|
WithSeries();
|
||||||
|
|
||||||
|
_namingConfig.StandardBookFormat = "{Book Series}/{Book SeriesTitle - }{Book Title} {(Release Year)}";
|
||||||
|
|
||||||
|
var name = Subject.BuildBookFileName(_artist, _release, _trackFile)
|
||||||
|
.Should().Be("A Series\\A Series #2-3 - A Novel (2020)".AsOsAgnostic());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_build_standard_track_filename_with_forward_slash()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardBookFormat = "{Book Series}/{Book SeriesTitle - }{Book Title} {(Release Year)}";
|
||||||
|
|
||||||
|
Subject.BuildBookFileName(_artist, _release, _trackFile)
|
||||||
|
.Should().Be("A Novel (2020)".AsOsAgnostic());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_build_nested_standard_track_filename_with_back_slash()
|
||||||
|
{
|
||||||
|
WithSeries();
|
||||||
|
|
||||||
|
_namingConfig.StandardBookFormat = "{Book Series}\\{Book SeriesTitle - }{Book Title} {(Release Year)}";
|
||||||
|
|
||||||
|
Subject.BuildBookFileName(_artist, _release, _trackFile)
|
||||||
|
.Should().Be("A Series\\A Series #2-3 - A Novel (2020)".AsOsAgnostic());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_build_standard_track_filename_with_back_slash()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardBookFormat = "{Book Series}\\{Book SeriesTitle - }{Book Title} {(Release Year)}";
|
||||||
|
|
||||||
|
Subject.BuildBookFileName(_artist, _release, _trackFile)
|
||||||
|
.Should().Be("A Novel (2020)".AsOsAgnostic());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,9 +88,6 @@ namespace NzbDrone.Core.Organizer
|
|||||||
|
|
||||||
var pattern = namingConfig.StandardBookFormat;
|
var pattern = namingConfig.StandardBookFormat;
|
||||||
|
|
||||||
var subFolders = pattern.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var safePattern = subFolders.Aggregate("", (current, folderLevel) => Path.Combine(current, folderLevel));
|
|
||||||
|
|
||||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
AddAuthorTokens(tokenHandlers, author);
|
AddAuthorTokens(tokenHandlers, author);
|
||||||
@@ -100,13 +97,26 @@ namespace NzbDrone.Core.Organizer
|
|||||||
AddMediaInfoTokens(tokenHandlers, bookFile);
|
AddMediaInfoTokens(tokenHandlers, bookFile);
|
||||||
AddPreferredWords(tokenHandlers, author, bookFile, preferredWords);
|
AddPreferredWords(tokenHandlers, author, bookFile, preferredWords);
|
||||||
|
|
||||||
var fileName = ReplacePartTokens(safePattern, tokenHandlers, namingConfig);
|
var splitPatterns = pattern.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
fileName = ReplaceTokens(fileName, tokenHandlers, namingConfig).Trim();
|
var components = new List<string>();
|
||||||
|
|
||||||
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
foreach (var s in splitPatterns)
|
||||||
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
{
|
||||||
|
var splitPattern = s;
|
||||||
|
|
||||||
return fileName;
|
var component = ReplacePartTokens(splitPattern, tokenHandlers, namingConfig).Trim();
|
||||||
|
component = ReplaceTokens(component, 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 BuildBookFilePath(Author author, Edition edition, string fileName, string extension)
|
public string BuildBookFilePath(Author author, Edition edition, string fileName, string extension)
|
||||||
@@ -175,11 +185,28 @@ namespace NzbDrone.Core.Organizer
|
|||||||
namingConfig = _namingConfigService.GetConfig();
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pattern = namingConfig.AuthorFolderFormat;
|
||||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
AddAuthorTokens(tokenHandlers, author);
|
AddAuthorTokens(tokenHandlers, author);
|
||||||
|
|
||||||
return CleanFolderName(ReplaceTokens(namingConfig.AuthorFolderFormat, tokenHandlers, namingConfig));
|
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)
|
public static string CleanTitle(string title)
|
||||||
|
|||||||
Reference in New Issue
Block a user