Skip to content

fix(relocation): allow case-only renames on case-sensitive file systems - #1451

Merged
revam merged 4 commits into
ShokoAnime:masterfrom
Foowy:fix/relocation-case-only-rename
Oct 11, 2026
Merged

revam merged 4 commits into
ShokoAnime:masterfrom
Foowy:fix/relocation-case-only-rename

Conversation

@Foowy

@Foowy Foowy commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Problem

InternalDirectlyRelocateFile aborts any relocation where the old and new paths are equal ignoring case but not exactly equal, with:

Resolved to relocate X to Y. Which is the same location in a case-insensitive file system. Aborting.

That is only true on a case-insensitive file system. On a case-sensitive system the two paths are different locations and the rename is valid, so the guard blocks legitimate renames. The only symptom is an Error-level log line per file. The files keep their old capitalization and are never renamed.

This matters for anyone whose managed folder currently lives on a case-sensitive file system:

  • Btrfs and ext4 on Linux, including NAS appliances. Synology DSM runs on Linux with Btrfs or ext4 volumes. The affected path in my case is a Synology Btrfs share mounted over NFSv4.1, where Foo and foo are separate names. DSM keeps shared folder names case-insensitive (see the DSM shared folder spec), but that does not extend to the paths inside a share. Other NAS stacks built on Linux file systems (QNAP, Unraid on XFS, TrueNAS on ZFS) may have the same property, depending on how the volume or dataset was created.
  • ext4. Case-insensitive lookup is an opt-in, per-directory feature (+F on an empty directory) per the kernel ext4 docs, so ext4 is case-sensitive unless someone enables it.
  • ZFS. The casesensitivity property defaults to sensitive and cannot be changed after the dataset is created per the OpenZFS docs, so existing datasets stay case-sensitive.

Real case from my library: the relocation target for a series folder changed capitalization (...so I Tried... vs ...So I Tried...) after a title update. Rerunning rename logged 14 aborts and changed nothing.

Change

  • The guard now aborts only when the new path exists and is the same file as the old one (same inode, via GetVideoFileUID, or either id unavailable). That keeps the abort on case-insensitive file systems, including Windows, macOS and SMB-backed mounts, and lets a case-only file rename through on case-sensitive ones.
  • Added ReuseExistingFolderCase. Before the path is used, each directory in the new relative path is matched against what exists on disk. If a directory differing only by case exists, it is reused instead of creating a second, case-variant folder. In the case above the new path resolves to the existing one and the relocation becomes a no-op.

Behavior notes

  • An existing folder's capitalization is never changed by a relocation. It is reused as-is, which avoids two case-variant folders existing at once and the files being split between them.
  • If several case-variant folders already exist, the first in ordinal order is used so the choice is deterministic.
  • A case-only change to a file name now renames the file on case-sensitive file systems.
  • When a directory in the new path does not exist, its parent is listed once to look for a case match. After the first file creates the folder, later files take the plain existence check.

Testing

Unit tests in VideoRelocationGuardTests cover folder reuse, no reuse when nothing matches, and the abort when it can't be verified that a case-variant path is the same file. Full Shoko.Tests passes.

Run against a real server on a Synology Btrfs share mounted over NFSv4.1, using truncated copies of the affected files from the log (real names, 2 MiB each), through the File/Location/{id} relocate endpoint.

Review changes

  • A case-only rename of the same file (matching file IDs) now goes through a .tmp-name temp path, with file watcher exclusions, so it also works on case-insensitive file systems.
  • If either file ID is unavailable, it aborts with a separate message. If the IDs differ, it logs a warning that the file is being moved over a different one.

Rebase history

  • 2026-10-08: rebased onto 80f1a7a6c, no conflicts
  • 2026-10-09: rebased onto 6b4bfca9d, no conflicts

@Foowy
Foowy marked this pull request as ready for review October 6, 2026 20:52
Comment thread Shoko.Server/Services/VideoRelocationService.cs Outdated
@Foowy
Foowy force-pushed the fix/relocation-case-only-rename branch from 9881f1d to a254f03 Compare October 8, 2026 13:54

@revam revam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two small ones. 😔

Comment thread Shoko.Server/Services/VideoRelocationService.cs Outdated
Comment thread Shoko.Server/Services/VideoRelocationService.cs Outdated
@Foowy
Foowy force-pushed the fix/relocation-case-only-rename branch from a254f03 to a4a4b3c Compare October 8, 2026 17:51
@Foowy
Foowy requested a review from revam October 9, 2026 15:19
Foowy added 3 commits October 9, 2026 17:15
The same-location guard in `InternalDirectlyRelocateFile` aborted every path that differed only by case, even where the file system treats them as different paths.

- Aborted only when the new path exists and is the same file as the old one
- Added `ReuseExistingFolderCase` so a folder differing only by case from an existing one is reused rather than duplicated
@Foowy
Foowy force-pushed the fix/relocation-case-only-rename branch from a4a4b3c to 1f29659 Compare October 9, 2026 17:15

@revam revam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Small nit, otherwise okay.

Comment thread Shoko.Server/Services/VideoRelocationService.cs Outdated
@sonarqubecloud

Copy link
Copy Markdown

@Foowy
Foowy requested a review from revam October 11, 2026 12:50
@revam
revam merged commit ddb25b2 into ShokoAnime:master Oct 11, 2026
6 checks passed
@Foowy
Foowy deleted the fix/relocation-case-only-rename branch October 11, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants