Skip to content

Fix/602: root folder deletion#607

Open
T4g1 wants to merge 7 commits into
Listenarrs:canaryfrom
T4g1:fix/602-root-folder-deletion
Open

Fix/602: root folder deletion#607
T4g1 wants to merge 7 commits into
Listenarrs:canaryfrom
T4g1:fix/602-root-folder-deletion

Conversation

@T4g1
Copy link
Copy Markdown
Contributor

@T4g1 T4g1 commented May 18, 2026

Summary

  • Fix [Bug] Unable to delete root folder #602
    When removing root folders that are contained within other root folders, audiobooks can be detected as using both of them.
    I fixed that issue by making it count the amount of root folders associated with each audiobooks and allowing the deletion only if none of them is orphaned from all root folders

Testing

  • Reworked the root folders tests and added two tests to prevent regression and validate the behavior

Notes

  • Some other areas of the root folder services assumes root folders are not contained within each other, we might want to create an issue to investigate the behavior regarding those and decide if we want to prevent that use case or not

@T4g1 T4g1 self-assigned this May 18, 2026
@T4g1 T4g1 requested a review from a team May 18, 2026 13:10
@T4g1 T4g1 added the patch patch version bump - backward compatible bug fixes label May 18, 2026
@T4g1 T4g1 requested a review from therobbiedavis May 21, 2026 12:20
Comment thread listenarr.application/Audiobooks/RootFolderService.cs Outdated
@T4g1 T4g1 requested a review from therobbiedavis June 1, 2026 12:59
@T4g1 T4g1 force-pushed the fix/602-root-folder-deletion branch from 4173e88 to d6683ce Compare June 2, 2026 09:30
Comment thread listenarr.application/Audiobooks/RootFolderService.cs Outdated
Comment thread listenarr.application/Audiobooks/RootFolderService.cs Outdated
Comment thread listenarr.application/Audiobooks/RootFolderService.cs
@T4g1 T4g1 requested a review from therobbiedavis June 5, 2026 19:10
Copy link
Copy Markdown
Collaborator

@therobbiedavis therobbiedavis left a comment

Choose a reason for hiding this comment

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

Thanks for the updates here, the original comments are resolved but sorry for finding more stuff.


// Audiobooks are considered orphaned if base path is empty or no root folder can be linked to it
var orphanedAudiobooks = audiobooks
.Where(a => string.IsNullOrEmpty(a.BasePath) || !rootFolders.Any(r => a.BasePath!.StartsWith(r.Path)));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The trailing separator fix helps, but I’d still avoid raw StartsWith for path ownership. This comparison is case-sensitive, so Windows-style paths with different casing can be treated as unrelated and mark a real child audiobook as orphaned.

}
set
{
var normalized = FileUtils.NormalizeStoredPath(value);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we need to guard null/empty before normalizing here. Right now NormalizeStoredPath(null) / NormalizeStoredPath("") can turn “no BasePath” into the current working directory, which breaks callers checking for an empty BasePath.

}

_db.Audiobooks.Update(audiobook);
await _db.SaveChangesAsync();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this lost the actual update. SaveChangesAsync only persists if the audiobook is already tracked, so a detached entity can return true here while saving nothing.

Copy link
Copy Markdown
Contributor Author

@T4g1 T4g1 Jun 6, 2026

Choose a reason for hiding this comment

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

The update on tracked entities throws errors (you can check the pipelines on the MR) so i either remove the tracking or go full tracking, i went for the default tracking behavior here

So it is assumed the entities are tracked here (which is the case in the repository used currently iirc)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah ok, I think my primary concern is the method contract more than this specific call path. It now only works for already-tracked instances, but still looks like a general update method and returns true for detached entities too. Maybe instead we only call Update when the entity is detached? WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe its a matter of convention, but we have an issue: we have inconsistent behavior right now (some method do track others dont, and by default, the framework does implicit tracking)
Like I said, I went for the implicit tracking pattern but we could also:

  • Disable tracking: Which requires us to explicitely update objects
  • Explicit the tracking mode in the method name

I considered the approach to check if it's tracked or not too but it feels a bit like code smell for me as if we have to do that, it's maybe a lack of comprehension about the inner working of the EF framework from the dev that needs it, i can't really think of a use case where we would want to update an untracked object out of thin air)

Comment thread listenarr.api/Program.cs
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don’t see DisableHostedServices being read, so WebApplicationFactory tests will start the background workers when the test host boots.

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.

[Bug] Unable to delete root folder

2 participants