Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/src/Settings/MediaManagement/Naming/NamingModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ const bookTokens = [

{ token: '{Book Disambiguation}', example: 'Disambiguation' },

{ token: '{Edition AudibleAsin}', example: 'B0ABCDEFGH' },

{ token: '{PartNumber:0}', example: '2' },
{ token: '{PartNumber:00}', example: '02' },
{ token: '{PartNumber:smart}', example: '02' },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using NLog;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Books;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;

namespace Chaptarr.Core.Test.Organizer
{
[TestFixture]
public class FileNameBuilderEditionTokensFixture
{
private class StubNamingConfigService : INamingConfigService
{
public NamingConfig GetConfig() => NamingConfig.Default;
public void Save(NamingConfig namingConfig) { }
}

private class StubQualityDefinitionService : IQualityDefinitionService
{
public void Update(QualityDefinition qualityDefinition) => throw new NotImplementedException();
public void UpdateMany(List<QualityDefinition> qualityDefinitions) => throw new NotImplementedException();
public List<QualityDefinition> All() => throw new NotImplementedException();
public QualityDefinition GetById(int id) => throw new NotImplementedException();
public QualityDefinition Get(Quality quality) => new QualityDefinition(quality);
}

private class StubCustomFormatCalculationService : ICustomFormatCalculationService
{
public List<CustomFormat> ParseCustomFormat(RemoteBook remoteBook, long size) => new();
public List<CustomFormat> ParseCustomFormat(BookFile bookFile, Author artist) => new();
public List<CustomFormat> ParseCustomFormat(BookFile bookFile) => new();
public List<CustomFormat> ParseCustomFormat(Blocklist blocklist, Author artist) => new();
public List<CustomFormat> ParseCustomFormat(EntityHistory history, Author artist) => new();
public List<CustomFormat> ParseCustomFormat(LocalBook localBook) => new();
}

private static string Build(string format, Edition edition)
{
var builder = new FileNameBuilder(
new StubNamingConfigService(),
new StubQualityDefinitionService(),
new CacheManager(),
new StubCustomFormatCalculationService(),
LogManager.GetCurrentClassLogger());

var author = new Author { Name = "Jim Butcher" };
edition.Book = new Book { Author = author };

var bookFile = new BookFile
{
Path = "/books/x.m4b",
Quality = new QualityModel(Quality.M4B)
};

var namingConfig = NamingConfig.Default;
namingConfig.RenameBooks = true;
namingConfig.StandardBookFormat = format;

return builder.BuildBookFileName(author, edition, bookFile, namingConfig, customFormats: new List<CustomFormat>());
}

[Test]
public void should_render_audible_asin_in_brackets()
{
var edition = new Edition { Title = "Grave Peril", AudibleASIN = "B0ABCDEFGH" };

Assert.That(Build("{Book Title}{ [Edition AudibleAsin]}", edition), Is.EqualTo("Grave Peril [B0ABCDEFGH]"));
}

[Test]
public void should_collapse_audible_asin_bracket_when_missing()
{
var edition = new Edition { Title = "Grave Peril", Asin = "0451457811", Isbn13 = "9780451457813" };

Assert.That(Build("{Book Title}{ [Edition AudibleAsin]}", edition), Is.EqualTo("Grave Peril"));
}

[Test]
public void should_keep_audible_asin_uppercase_when_token_is_spelled_lowercase()
{
var edition = new Edition { Title = "Grave Peril", AudibleASIN = "b0abcdefgh" };

Assert.That(Build("{Book Title}{ [edition audibleasin]}", edition), Is.EqualTo("Grave Peril [B0ABCDEFGH]"));
Assert.That(Build("{Book Title}{ [edition.audibleasin]}", edition), Is.EqualTo("Grave Peril [B0ABCDEFGH]"));
}

[Test]
public void should_not_change_formats_without_audible_asin_token()
{
var edition = new Edition { Title = "Grave Peril", AudibleASIN = "B0ABCDEFGH" };

Assert.That(Build("{Book Title}", edition), Is.EqualTo("Grave Peril"));
Assert.That(Build("{book title}", edition), Is.EqualTo("grave peril"));
}
}
}
12 changes: 11 additions & 1 deletion src/NzbDrone.Core/Organizer/FileNameBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,8 @@ private static bool IsInitialToken(string token)
return segmentCount > 0;
}

private const string AudibleAsinToken = "{Edition AudibleAsin}";

private void AddBookTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Edition edition)
{
tokenHandlers["{Book Title}"] = m => edition.Title;
Expand Down Expand Up @@ -494,6 +496,10 @@ private void AddBookTokens(Dictionary<string, Func<TokenMatch, string>> tokenHan
return bookTitle;
};

// Audible ASIN of the selected edition only (never an ISBN or a non-Audible ASIN; Edition.Asin may hold ISBN-10).
// Empty when missing so conditional brackets such as {[Edition AudibleAsin]} collapse.
tokenHandlers[AudibleAsinToken] = m => (edition.AudibleASIN ?? string.Empty).Trim().ToUpperInvariant();

if (edition.Disambiguation != null)
{
tokenHandlers["{Book Disambiguation}"] = m => edition.Disambiguation;
Expand Down Expand Up @@ -788,7 +794,11 @@ private string ReplaceToken(Match match, Dictionary<string, Func<TokenMatch, str

var replacementText = tokenHandler(tokenMatch).Trim();

if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsLower(t)))
if (FileNameBuilderTokenEqualityComparer.Instance.Equals(tokenMatch.Token, AudibleAsinToken))
{
// ASINs must stay uppercase ([A-Z0-9]{10}); do not mirror the token spelling's case.
}
else if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsLower(t)))
{
replacementText = replacementText.ToLower();
}
Expand Down
1 change: 1 addition & 0 deletions src/NzbDrone.Core/Organizer/FileNameSampleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public FileNameSampleService(IBuildFileNames buildFileNames)
_standardEdition = new Edition
{
Title = "The Edition Title",
AudibleASIN = "B0ABCDEFGH",
Book = _standardBook,
NarratorNames = new List<string> { "Alice Reader", "Bob Voice" }
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class PatternCompiler : IPatternCompiler
{ "ReleaseYear", "{Release Year}" },
{ "ReleaseYearFirst", "{Release YearFirst}" },
{ "EditionYear", "{Edition Year}" },
{ "EditionAudibleAsin", "{Edition AudibleAsin}" },

// GraphicAudio tokens
{ "GraphicAudio", "{GraphicAudio}" },
Expand Down