Skip to content

Fixing naming pattern adding {Subtitle}#579

Open
therobbiedavis wants to merge 7 commits into
canaryfrom
bugfix/naming-pattern
Open

Fixing naming pattern adding {Subtitle}#579
therobbiedavis wants to merge 7 commits into
canaryfrom
bugfix/naming-pattern

Conversation

@therobbiedavis
Copy link
Copy Markdown
Collaborator

Summary

Fixes #571 by making configured naming patterns apply exactly as written. {Title} now resolves to only the audiobook title instead of implicitly appending the subtitle, while {Subtitle} remains available when users explicitly include it.

Changes

Added

  • Added backend regression coverage for single-file and multi-file manual imports using {Title} with audiobook subtitles present.
  • Added organize/rename preview regression coverage to ensure title-only patterns do not include subtitles.
  • Added frontend coverage for {Subtitle} replacement in the audiobook detail path estimate.

Changed

  • Updated audiobook detail path estimation to replace backend-supported metadata tokens including {Subtitle}, {Edition}, {Narrator}, {Publisher}, {Language}, {Asin}, and {Quality}.

Fixed

  • Removed implicit title/subtitle merging from manual import naming.
  • Removed implicit title/subtitle merging from organize/rename preview naming.

Testing

  • dotnet test tests/Listenarr.Tests.csproj --filter "FullyQualifiedName~ManualImportControllerTests|FullyQualifiedName~RenameServiceTests|FullyQualifiedName~FileNamingService_PatternSelectionTests" /p:DefaultItemExcludes=**/obj/**
  • npm run test:unit -- --run src/__tests__/AudiobookDetailView.spec.ts

Notes

{Subtitle} was already listed in the File Management settings help. This change makes the backend and detail-view preview honor that token explicitly without changing {Title} output.

Improve pre-commit and pre-push hooks: add Windows Node PATH workaround, switch pre-commit to run node scripts/lint-staged.mjs (instead of npm), and add enforcement checks for layering violations and banning `async void`. Update pre-push to sync frontend version from csproj, run dotnet format verification, and run frontend type-check and tests. Adjust scripts/lint-staged.mjs to invoke local eslint/prettier via process.execPath (avoid npx) so tooling runs reliably across platforms/CI.
Stop implicitly appending the audiobook subtitle to Title when naming files/folders and instead expose Subtitle as its own token. Frontend: add Subtitle and several other metadata tokens to displayBasePath replacement so patterns containing {Subtitle} render correctly. Backend: remove legacy Title+Subtitle concatenation, update BuildNamingVariables signature and RenameService call to use the raw Title and separate Subtitle. Tests: add unit tests covering title-only patterns for manual import and preview rename, and a frontend spec to assert the subtitle is replaced in the estimated base path. Files changed include AudiobookDetailView, RenameService, ManualImportController, and their corresponding tests.
@therobbiedavis therobbiedavis requested review from a team and Copilot May 11, 2026 14:16
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef1c05a5a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fe/src/views/library/AudiobookDetailView.vue Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes file/folder naming pattern token behavior so configured patterns are applied literally: {Title} no longer implicitly includes the subtitle, and {Subtitle} must be explicitly included to appear. It also extends path-estimation token replacement in the audiobook detail view and adds regression coverage around these behaviors.

Changes:

  • Backend: removed implicit title+subtitle merging in manual import path generation and organize/rename preview variables.
  • Tests: added backend regression tests for manual import + rename preview using {Title} when subtitles exist, plus a frontend test covering {Subtitle} replacement in the estimated path.
  • Tooling: adjusted staged lint execution and expanded Husky pre-commit/pre-push hook checks.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Features/Api/Services/RenameServiceTests.cs Adds regression test ensuring rename preview {Title} does not append subtitle.
tests/Features/Api/Controllers/ManualImportControllerTests.cs Adds regression tests ensuring manual import {Title} patterns don’t append subtitle (single + multi-file).
scripts/lint-staged.mjs Runs ESLint/Prettier via the current Node executable instead of npx.
listenarr.api/Services/RenameService.cs Removes implicit title/subtitle concatenation from rename preview variable building.
listenarr.api/Controllers/ManualImportController.cs Removes implicit title/subtitle concatenation from manual import naming variables.
fe/src/views/library/AudiobookDetailView.vue Expands token replacement for estimated paths to include additional backend-supported tokens.
fe/src/tests/AudiobookDetailView.spec.ts Adds unit test asserting {Subtitle} replacement appears in the estimated path.
.husky/pre-push Replaces npm test with version sync, dotnet format verify, FE typecheck, and FE tests.
.husky/pre-commit Replaces npm run lint:staged with direct node script and adds repo enforcement greps.
Comments suppressed due to low confidence (1)

scripts/lint-staged.mjs:80

  • lint-staged.mjs switched to running ESLint/Prettier via process.execPath (good for reduced PATH environments), but the Vue template handler check still hardcodes run('node', ...). In the same reduced-PATH scenario on Windows, this can fail even though the other commands are now robust. Use the same nodeCommand for the Vue handler check as well (or otherwise resolve Node consistently).
  run(nodeCommand, ['node_modules/eslint/bin/eslint.js', ...frontendLintFiles], { cwd: 'fe' })
}

if (frontendVueFiles.length > 0) {
  console.log('Checking staged Vue template handlers...')
  run('node', ['scripts/check-vue-template-handlers.mjs', ...frontendVueFiles], { cwd: 'fe' })
}

Comment thread .husky/pre-commit Outdated
Comment thread .husky/pre-push Outdated
Comment thread fe/src/views/library/AudiobookDetailView.vue Outdated
Refactor folder/file naming pattern handling to properly handle optional/empty tokens and normalize paths. Split folderNamingPattern and fileNamingPattern selection, introduce replacePatternVariable, sanitizeOptionalPathComponent and cleanupEmptyPatternVariables to replace variables with placeholders, remove empty tokens and surrounding separators, and normalize slashes. Ensure folder pattern returns the full combined path while file patterns continue to return the parent directory. Updated tests to assert exact expected paths and add a new test to verify empty optional tokens (e.g. {Subtitle}) are removed from the estimated base path.
@therobbiedavis therobbiedavis added the patch patch version bump - backward compatible bug fixes label May 11, 2026
Comment thread fe/src/views/library/AudiobookDetailView.vue
Comment thread .husky/pre-commit
Comment thread .husky/pre-commit Outdated
Comment thread .husky/pre-push
Comment thread tests/Features/Api/Controllers/ManualImportControllerTests.cs
Replace inline shell logic in .husky/pre-commit and .husky/pre-push with a resolve_node helper and invoke scripts/run-hook-check.mjs (pre-commit/pre-push). Add a new scripts/check-architecture.mjs to encapsulate grafted git-grep enforcement checks (layering and async void) and exit non‑zero on violations. Add related npm scripts (version:check, type-check:frontend, test:frontend:hook, test:arch, check:pre-commit, check:pre-push) to package.json to expose the new checks and frontend tooling. These changes centralize hook behavior in Node-based scripts and improve cross-platform node resolution.
Centralize folder/file naming and path preview logic on the backend and update the frontend to consume the preview. Introduces IAudiobookPathPreviewService/INamingPatternService and registers AudiobookPathPreviewService/NamingPatternService; LibraryController now delegates base-path computation to the path preview service and returns a PreviewPathResponse. FileNamingService no longer contains pattern parsing/sanitization internals and delegates to INamingPatternService. Frontend AudiobookDetailView now requests the backend preview for displayBasePath (removes local pattern expansion) and handles preview updates; tests and builders updated to use new builders and test infrastructure, and mocks adjusted accordingly. Also removes obsolete controller base-path unit tests and updates various API controller tests to align with the new services.
@therobbiedavis therobbiedavis requested a review from T4g1 May 11, 2026 22:35
Copy link
Copy Markdown
Contributor

@T4g1 T4g1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we can improve the separation of concerns and thus avoid code duplication even further here

Comment thread .husky/pre-commit Outdated
Comment thread tests/Builders/QualityProfileBuilder.cs
@@ -161,174 +156,15 @@ public async Task<string> GenerateFilePathAsync(
/// </summary>
public string ApplyNamingPattern(string pattern, Dictionary<string, object> variables, bool treatAsFilename = false)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to drop this method as it's no longer the responsability of this file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd say this should go in Naming directory too but this will probably conflict with the PR that takes care of that then :x

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this will be merged after the file re-structuration, i leave this open as a reminder to make sure we no longer have a Services directory once this has been rebased

Comment thread listenarr.application/Audiobooks/AudiobookPathPreviewService.cs Outdated
Comment thread listenarr.application/Audiobooks/AudiobookPathPreviewService.cs Outdated
{
Task<PathPreviewResult> PreviewAsync(
Audiobook audiobook,
string? destinationRoot = null,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this a bit confusing, I assume this is the prefered root folder the user wants for this audiobook ? Maybe add a comment or explicitely name it selectedRootFolder ? Also, why do we allow null values ? To create a base path for an audiobook, we absolutely need a root folder right ? The controller should probably safeguard that so user "trust" component stays in API layer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we could directly use the RootFolder object if controller does the preliminary checks

: namingPatternService.SanitizePathComponent(value);
}

private static string BuildDirectoryPattern(string fileNamingPattern, Audiobook audiobook)
Copy link
Copy Markdown
Contributor

@T4g1 T4g1 May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels strange to have here, i expect the responsibility of AudioobookPathPreviewService to be limited to building a path from a pre-configured pattern.
This method should probably be used while configuring those patterns instead to validate them eventually (I haven't look at that part of the code yet but I expect that to be in the IConfigurationService probably)

return directoryPattern;
}

private static string ResolvePathWithOptionalBase(string? basePath, string candidatePath)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put that in FileUtils or something as this is static
Also, why not put basePath as an optional argument instead of a mandatory null when we do not use it ? (method name should then be updated as ResolvePath)

Comment thread listenarr.application/Naming/INamingPatternService.cs Outdated
Move and split naming pattern functionality into a Common implementation and a new INamingPatternService interface under Application.Interfaces, adding ApplyAudiobookNamingPattern and audiobook-specific variable building. Update consumers (FileNamingService, AudiobookPathPreviewService, AppServiceRegistrationExtensions, LibraryController) to depend on the new interface and adjust sanitization/variable handling so empty folder patterns return the selected root (no synthesized "Unknown" segments) and file naming patterns do not affect base directories. Normalize folder pattern processing and simplify path preview computation. Add resolve-node.sh and source it from husky pre-commit/pre-push hooks. Add and adjust tests covering path preview, naming behavior, and a test builder fix (QualityProfileBuilder IdCounter).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch patch version bump - backward compatible bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implicit patterns added to configured naming patterns

3 participants