fix(crossseed): pin explicit savepath when cross category save path diverges - #1993
Conversation
…iverges Regular (non-hardlink/reflink) mode enabled autoTMM for affixed cross-seed categories whenever the matched torrent was auto-managed, delegating save-path resolution to qBittorrent. With a category affix like ".cross", qBittorrent resolves the path from the affixed category — its configured save_path, or the implicit <default>/<category> when none is set — which need not equal the matched torrent's location. ensureCrossCategory only warns on a pre-existing category whose path diverges (never rewrites it), so the cross-seed was relocated into e.g. <base>/<category>.cross and re-downloaded instead of reusing existing files. Hardlink/reflink modes were immune because they always pin an explicit savepath with autoTMM off. ensureCrossCategory now returns the category's actual save path, and regular mode disables autoTMM + pins an explicit savepath to the matched torrent's location when that path diverges (or is unset), mirroring link modes. The matching-path case is unchanged (autoTMM stays on). Adds a regression test covering a pre-existing divergent .cross category.
WalkthroughThis PR addresses cross-seed category save-path drift by making ChangesAutoTMM safety on category save-path divergence
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/services/crossseed/service.go (1)
11251-11265:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRevalidate qBittorrent state before reusing cached
actualSavePath.Now that
ensureCrossCategory's return value drives theautoTMM/savepathchoice, these fast paths can reintroduce the relocation bug after an operator changes the category'ssave_pathwhile the service is still running. With a stalecreatedCategoriesentry, regular mode will believe the cross category still matchesprops.SavePath, keepautoTMMon, and qBittorrent will relocate the add into the new category path. Skip the session cache whenevercompareExistingSavePathistrue(both here and inside thesingleflightclosure), so the load-bearing path always comes from a freshGetCategoriesread.Proposed fix
- if cached, ok := s.createdCategories.Load(key); ok && cacheMatches(cached) { + if !compareExistingSavePath { + if cached, ok := s.createdCategories.Load(key); ok { + cachedSavePath, _ := cached.(string) + return cachedSavePath, nil + } + } - cachedSavePath, _ := cached.(string) - return cachedSavePath, nil - } actualSavePathValue, err, _ := s.categoryCreationGroup.Do(key, func() (any, error) { - if cached, ok := s.createdCategories.Load(key); ok && cacheMatches(cached) { + if !compareExistingSavePath { + if cached, ok := s.createdCategories.Load(key); ok { + cachedSavePath, _ := cached.(string) + return cachedSavePath, nil + } + } - cachedSavePath, _ := cached.(string) - return cachedSavePath, nil - }Based on learnings:
ensureCrossCategoryintentionally keeps warn-only behavior when an existing category’ssave_pathdiffers from the desired path and callers should not attempt repair.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/services/crossseed/service.go` around lines 11251 - 11265, The fast-path cache in ensureCrossCategory is being used even when compareExistingSavePath is true, which can return stale createdCategories and cause incorrect autoTMM/savepath behavior; modify ensureCrossCategory so both the initial createdCategories.Load(key) check and the duplicate check inside categoryCreationGroup.Do skip using the session cache when compareExistingSavePath == true (i.e., only use createdCategories and cacheMatches when compareExistingSavePath is false) so that the load-bearing path falls back to a fresh GetCategories read; touch the symbols ensureCrossCategory, createdCategories.Load(key), categoryCreationGroup.Do(...) and cacheMatches(...) when implementing this conditional skip.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@internal/services/crossseed/service.go`:
- Around line 11251-11265: The fast-path cache in ensureCrossCategory is being
used even when compareExistingSavePath is true, which can return stale
createdCategories and cause incorrect autoTMM/savepath behavior; modify
ensureCrossCategory so both the initial createdCategories.Load(key) check and
the duplicate check inside categoryCreationGroup.Do skip using the session cache
when compareExistingSavePath == true (i.e., only use createdCategories and
cacheMatches when compareExistingSavePath is false) so that the load-bearing
path falls back to a fresh GetCategories read; touch the symbols
ensureCrossCategory, createdCategories.Load(key), categoryCreationGroup.Do(...)
and cacheMatches(...) when implementing this conditional skip.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 024cf577-dbff-479f-a3ba-109191042e42
📒 Files selected for processing (4)
internal/services/crossseed/category_cache_test.gointernal/services/crossseed/category_divergence_test.gointernal/services/crossseed/seasonpack.gointernal/services/crossseed/service.go
… (#950) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.19.0` → `v1.20.0` | --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.20.0`](https://github.com/autobrr/qui/releases/tag/v1.20.0) [Compare Source](autobrr/qui@v1.19.0...v1.20.0) ##### Changelog ##### New Features - [`9ad7ffe`](autobrr/qui@9ad7ffe): feat(automations): add include/exclude for trackers ([#​1953](autobrr/qui#1953)) ([@​martylukyy](https://github.com/martylukyy)) - [`e73867f`](autobrr/qui@e73867f): feat(ci): build binaries for develop branch ([#​1969](autobrr/qui#1969)) ([@​martylukyy](https://github.com/martylukyy)) - [`2020847`](autobrr/qui@2020847): feat(crossseed): match cross-tracker relabels and alternate and/& title spellings ([#​1990](autobrr/qui#1990)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`719866b`](autobrr/qui@719866b): feat(crossseed): route season packs to categories by resolution and source ([#​1972](autobrr/qui#1972)) ([@​s0up4200](https://github.com/s0up4200)) - [`a6e22ba`](autobrr/qui@a6e22ba): feat(i18n): add French translation, dynamic loading, and browser locale auto-detect ([#​1956](autobrr/qui#1956)) ([@​OlziYT](https://github.com/OlziYT)) - [`77c6ea8`](autobrr/qui@77c6ea8): feat(i18n): add German (de) translation ([#​1965](autobrr/qui#1965)) ([@​s0up4200](https://github.com/s0up4200)) - [`fc8664b`](autobrr/qui@fc8664b): feat(i18n): add multi-language support with Simplified Chinese ([#​1732](autobrr/qui#1732)) ([@​s0up4200](https://github.com/s0up4200)) - [`11ace50`](autobrr/qui@11ace50): feat(sse): stream torrent and activity updates, replacing background polling ([#​551](autobrr/qui#551)) ([@​s0up4200](https://github.com/s0up4200)) - [`9b7ed6c`](autobrr/qui@9b7ed6c): feat(torrents): filter for torrents with other tracker errors ([#​1945](autobrr/qui#1945)) ([@​martylukyy](https://github.com/martylukyy)) - [`62bbdb4`](autobrr/qui@62bbdb4): feat(web): virtualized and memoized the indexer search results ([#​1944](autobrr/qui#1944)) ([@​GtwoK](https://github.com/GtwoK)) ##### Bug Fixes - [`b10ef98`](autobrr/qui@b10ef98): fix(api): remove WriteTimeout that aborted downloads and SSE at 120s ([#​1964](autobrr/qui#1964)) ([@​s0up4200](https://github.com/s0up4200)) - [`255bb3e`](autobrr/qui@255bb3e): fix(arr): make arr\_id\_cache expiry timezone-independent ([#​1986](autobrr/qui#1986)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`4c1d743`](autobrr/qui@4c1d743): fix(crossseed): harden filesystem fallback and post-add rechecks ([#​1912](autobrr/qui#1912)) ([@​Audionut](https://github.com/Audionut)) - [`8b278e5`](autobrr/qui@8b278e5): fix(crossseed): pin explicit savepath when cross category save path diverges ([#​1993](autobrr/qui#1993)) ([@​s0up4200](https://github.com/s0up4200)) - [`cab1e0f`](autobrr/qui@cab1e0f): fix(gazelle): pre-search content matching and api limits ([#​1981](autobrr/qui#1981)) ([@​Audionut](https://github.com/Audionut)) - [`c3fa169`](autobrr/qui@c3fa169): fix(i18n): translate torrent state labels ([#​1955](autobrr/qui#1955)) ([@​s0up4200](https://github.com/s0up4200)) - [`ccb601d`](autobrr/qui@ccb601d): fix(proxy): forward API key auth to qBittorrent on passthrough requests ([#​1948](autobrr/qui#1948)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`3cba046`](autobrr/qui@3cba046): fix(qbittorrent): match 'repack available'/'grab internal' as unregistered ([#​1960](autobrr/qui#1960)) ([@​s0up4200](https://github.com/s0up4200)) - [`591941a`](autobrr/qui@591941a): fix(sse): pause torrent-list stream while the tab is hidden ([#​1994](autobrr/qui#1994)) ([@​s0up4200](https://github.com/s0up4200)) - [`f75a1eb`](autobrr/qui@f75a1eb): fix(sse): stop intermittent "offline" flips that fall back to polling ([#​1992](autobrr/qui#1992)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`c720c39`](autobrr/qui@c720c39): fix(web): emit allowSubcategories from the filtered-data callback ([#​1979](autobrr/qui#1979)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`602d440`](autobrr/qui@602d440): fix(web): preserve paginated rows on unified-view SSE updates ([#​1985](autobrr/qui#1985)) ([@​nitrobass24](https://github.com/nitrobass24)) ##### Other Changes - [`1006338`](autobrr/qui@1006338): chore(deps): bump actions/checkout from 6 to 6.0.2 in the github group ([#​1999](autobrr/qui#1999)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`667d654`](autobrr/qui@667d654): chore(deps): bump rls to v0.8.1 ([#​2007](autobrr/qui#2007)) ([@​s0up4200](https://github.com/s0up4200)) - [`cc7d70a`](autobrr/qui@cc7d70a): chore(deps): bump the github group with 5 updates ([#​1937](autobrr/qui#1937)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`cc49a49`](autobrr/qui@cc49a49): chore(deps): bump the github group with 5 updates ([#​1959](autobrr/qui#1959)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`eff1a65`](autobrr/qui@eff1a65): chore(deps): bump the golang group with 14 updates ([#​1988](autobrr/qui#1988)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`11da2c7`](autobrr/qui@11da2c7): chore(deps): bump the npm group in /web with 11 updates ([#​1989](autobrr/qui#1989)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`33aad41`](autobrr/qui@33aad41): chore(deps): bump the npm group in /web with 28 updates ([#​1946](autobrr/qui#1946)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`5c74fcf`](autobrr/qui@5c74fcf): chore(refactor): remove explicit any and enable no-explicit-any rule ([#​1966](autobrr/qui#1966)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`fefa624`](autobrr/qui@fefa624): chore(web): add vitest test infrastructure and CI gating ([#​1936](autobrr/qui#1936)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`90b573a`](autobrr/qui@90b573a): chore(web): remove unused code and packages ([#​1957](autobrr/qui#1957)) ([@​martylukyy](https://github.com/martylukyy)) - [`078fae9`](autobrr/qui@078fae9): docs(agents): scope agent guidance ([#​1995](autobrr/qui#1995)) ([@​Audionut](https://github.com/Audionut)) - [`264ccfb`](autobrr/qui@264ccfb): refactor(go): remove dead code ([#​1740](autobrr/qui#1740)) ([@​s0up4200](https://github.com/s0up4200)) - [`48d6f57`](autobrr/qui@48d6f57): refactor(web): extract TorrentTableDialogs from TorrentTableOptimized ([#​1980](autobrr/qui#1980)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`022f553`](autobrr/qui@022f553): refactor(web): extract compact-sort, cross-seed, and hotkey hooks ([#​1976](autobrr/qui#1976)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`298c78c`](autobrr/qui@298c78c): refactor(web): extract torrent-table display helpers and row components ([#​1970](autobrr/qui#1970)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`a5b741a`](autobrr/qui@a5b741a): refactor(web): extract useBulkActionWrappers ([#​1977](autobrr/qui#1977)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`d4a051e`](autobrr/qui@d4a051e): refactor(web): extract useTorrentSelection ([#​1974](autobrr/qui#1974)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`cf84390`](autobrr/qui@cf84390): refactor(web): extract useTorrentSelectionDerivations ([#​1975](autobrr/qui#1975)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`18adff9`](autobrr/qui@18adff9): refactor(web): extract useTorrentTableColumns and useTorrentTableNotifications ([#​1978](autobrr/qui#1978)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`eb336ac`](autobrr/qui@eb336ac): refactor(web): extract useTorrentTableFilterExpr ([#​1973](autobrr/qui#1973)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`f1556e8`](autobrr/qui@f1556e8): refactor(web): extract useTrackerIconCache and useEffectiveServerState ([#​1971](autobrr/qui#1971)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`31f2cd9`](autobrr/qui@31f2cd9): refactor(web): extract virtualization, filter-lifecycle FSM, and column DnD from TorrentTableOptimized ([#​1982](autobrr/qui#1982)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`fdd0e56`](autobrr/qui@fdd0e56): refactor(web): split types/index.ts into domain modules behind a barrel ([#​1968](autobrr/qui#1968)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`2cca076`](autobrr/qui@2cca076): test(db): speed up migrated database tests ([#​1938](autobrr/qui#1938)) ([@​Audionut](https://github.com/Audionut)) - [`a10e697`](autobrr/qui@a10e697): test(sse): warm up subscription before coalescing burst ([#​1987](autobrr/qui#1987)) ([@​s0up4200](https://github.com/s0up4200)) - [`05fda2a`](autobrr/qui@05fda2a): test(web): add unit tests for Priority 1 pure utilities ([#​1940](autobrr/qui#1940)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`8df70a5`](autobrr/qui@8df70a5): test: fan out testdb helper to remaining database.New callers ([#​1962](autobrr/qui#1962)) ([@​nitrobass24](https://github.com/nitrobass24)) **Full Changelog**: <autobrr/qui@v1.19.0...v1.20.0> ##### Docker images - `docker pull ghcr.io/autobrr/qui:v1.20.0` - `docker pull ghcr.io/autobrr/qui:latest` ##### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/950
… ) (#78) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.19.0` → `v1.20.0` | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.20.0`](https://github.com/autobrr/qui/releases/tag/v1.20.0) [Compare Source](autobrr/qui@v1.19.0...v1.20.0) ##### Changelog ##### New Features - [`9ad7ffe`](autobrr/qui@9ad7ffe): feat(automations): add include/exclude for trackers ([#​1953](autobrr/qui#1953)) ([@​martylukyy](https://github.com/martylukyy)) - [`e73867f`](autobrr/qui@e73867f): feat(ci): build binaries for develop branch ([#​1969](autobrr/qui#1969)) ([@​martylukyy](https://github.com/martylukyy)) - [`2020847`](autobrr/qui@2020847): feat(crossseed): match cross-tracker relabels and alternate and/& title spellings ([#​1990](autobrr/qui#1990)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`719866b`](autobrr/qui@719866b): feat(crossseed): route season packs to categories by resolution and source ([#​1972](autobrr/qui#1972)) ([@​s0up4200](https://github.com/s0up4200)) - [`a6e22ba`](autobrr/qui@a6e22ba): feat(i18n): add French translation, dynamic loading, and browser locale auto-detect ([#​1956](autobrr/qui#1956)) ([@​OlziYT](https://github.com/OlziYT)) - [`77c6ea8`](autobrr/qui@77c6ea8): feat(i18n): add German (de) translation ([#​1965](autobrr/qui#1965)) ([@​s0up4200](https://github.com/s0up4200)) - [`fc8664b`](autobrr/qui@fc8664b): feat(i18n): add multi-language support with Simplified Chinese ([#​1732](autobrr/qui#1732)) ([@​s0up4200](https://github.com/s0up4200)) - [`11ace50`](autobrr/qui@11ace50): feat(sse): stream torrent and activity updates, replacing background polling ([#​551](autobrr/qui#551)) ([@​s0up4200](https://github.com/s0up4200)) - [`9b7ed6c`](autobrr/qui@9b7ed6c): feat(torrents): filter for torrents with other tracker errors ([#​1945](autobrr/qui#1945)) ([@​martylukyy](https://github.com/martylukyy)) - [`62bbdb4`](autobrr/qui@62bbdb4): feat(web): virtualized and memoized the indexer search results ([#​1944](autobrr/qui#1944)) ([@​GtwoK](https://github.com/GtwoK)) ##### Bug Fixes - [`b10ef98`](autobrr/qui@b10ef98): fix(api): remove WriteTimeout that aborted downloads and SSE at 120s ([#​1964](autobrr/qui#1964)) ([@​s0up4200](https://github.com/s0up4200)) - [`255bb3e`](autobrr/qui@255bb3e): fix(arr): make arr\_id\_cache expiry timezone-independent ([#​1986](autobrr/qui#1986)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`4c1d743`](autobrr/qui@4c1d743): fix(crossseed): harden filesystem fallback and post-add rechecks ([#​1912](autobrr/qui#1912)) ([@​Audionut](https://github.com/Audionut)) - [`8b278e5`](autobrr/qui@8b278e5): fix(crossseed): pin explicit savepath when cross category save path diverges ([#​1993](autobrr/qui#1993)) ([@​s0up4200](https://github.com/s0up4200)) - [`cab1e0f`](autobrr/qui@cab1e0f): fix(gazelle): pre-search content matching and api limits ([#​1981](autobrr/qui#1981)) ([@​Audionut](https://github.com/Audionut)) - [`c3fa169`](autobrr/qui@c3fa169): fix(i18n): translate torrent state labels ([#​1955](autobrr/qui#1955)) ([@​s0up4200](https://github.com/s0up4200)) - [`ccb601d`](autobrr/qui@ccb601d): fix(proxy): forward API key auth to qBittorrent on passthrough requests ([#​1948](autobrr/qui#1948)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`3cba046`](autobrr/qui@3cba046): fix(qbittorrent): match 'repack available'/'grab internal' as unregistered ([#​1960](autobrr/qui#1960)) ([@​s0up4200](https://github.com/s0up4200)) - [`591941a`](autobrr/qui@591941a): fix(sse): pause torrent-list stream while the tab is hidden ([#​1994](autobrr/qui#1994)) ([@​s0up4200](https://github.com/s0up4200)) - [`f75a1eb`](autobrr/qui@f75a1eb): fix(sse): stop intermittent "offline" flips that fall back to polling ([#​1992](autobrr/qui#1992)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`c720c39`](autobrr/qui@c720c39): fix(web): emit allowSubcategories from the filtered-data callback ([#​1979](autobrr/qui#1979)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`602d440`](autobrr/qui@602d440): fix(web): preserve paginated rows on unified-view SSE updates ([#​1985](autobrr/qui#1985)) ([@​nitrobass24](https://github.com/nitrobass24)) ##### Other Changes - [`1006338`](autobrr/qui@1006338): chore(deps): bump actions/checkout from 6 to 6.0.2 in the github group ([#​1999](autobrr/qui#1999)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`667d654`](autobrr/qui@667d654): chore(deps): bump rls to v0.8.1 ([#​2007](autobrr/qui#2007)) ([@​s0up4200](https://github.com/s0up4200)) - [`cc7d70a`](autobrr/qui@cc7d70a): chore(deps): bump the github group with 5 updates ([#​1937](autobrr/qui#1937)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`cc49a49`](autobrr/qui@cc49a49): chore(deps): bump the github group with 5 updates ([#​1959](autobrr/qui#1959)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`eff1a65`](autobrr/qui@eff1a65): chore(deps): bump the golang group with 14 updates ([#​1988](autobrr/qui#1988)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`11da2c7`](autobrr/qui@11da2c7): chore(deps): bump the npm group in /web with 11 updates ([#​1989](autobrr/qui#1989)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`33aad41`](autobrr/qui@33aad41): chore(deps): bump the npm group in /web with 28 updates ([#​1946](autobrr/qui#1946)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`5c74fcf`](autobrr/qui@5c74fcf): chore(refactor): remove explicit any and enable no-explicit-any rule ([#​1966](autobrr/qui#1966)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`fefa624`](autobrr/qui@fefa624): chore(web): add vitest test infrastructure and CI gating ([#​1936](autobrr/qui#1936)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`90b573a`](autobrr/qui@90b573a): chore(web): remove unused code and packages ([#​1957](autobrr/qui#1957)) ([@​martylukyy](https://github.com/martylukyy)) - [`078fae9`](autobrr/qui@078fae9): docs(agents): scope agent guidance ([#​1995](autobrr/qui#1995)) ([@​Audionut](https://github.com/Audionut)) - [`264ccfb`](autobrr/qui@264ccfb): refactor(go): remove dead code ([#​1740](autobrr/qui#1740)) ([@​s0up4200](https://github.com/s0up4200)) - [`48d6f57`](autobrr/qui@48d6f57): refactor(web): extract TorrentTableDialogs from TorrentTableOptimized ([#​1980](autobrr/qui#1980)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`022f553`](autobrr/qui@022f553): refactor(web): extract compact-sort, cross-seed, and hotkey hooks ([#​1976](autobrr/qui#1976)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`298c78c`](autobrr/qui@298c78c): refactor(web): extract torrent-table display helpers and row components ([#​1970](autobrr/qui#1970)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`a5b741a`](autobrr/qui@a5b741a): refactor(web): extract useBulkActionWrappers ([#​1977](autobrr/qui#1977)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`d4a051e`](autobrr/qui@d4a051e): refactor(web): extract useTorrentSelection ([#​1974](autobrr/qui#1974)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`cf84390`](autobrr/qui@cf84390): refactor(web): extract useTorrentSelectionDerivations ([#​1975](autobrr/qui#1975)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`18adff9`](autobrr/qui@18adff9): refactor(web): extract useTorrentTableColumns and useTorrentTableNotifications ([#​1978](autobrr/qui#1978)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`eb336ac`](autobrr/qui@eb336ac): refactor(web): extract useTorrentTableFilterExpr ([#​1973](autobrr/qui#1973)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`f1556e8`](autobrr/qui@f1556e8): refactor(web): extract useTrackerIconCache and useEffectiveServerState ([#​1971](autobrr/qui#1971)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`31f2cd9`](autobrr/qui@31f2cd9): refactor(web): extract virtualization, filter-lifecycle FSM, and column DnD from TorrentTableOptimized ([#​1982](autobrr/qui#1982)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`fdd0e56`](autobrr/qui@fdd0e56): refactor(web): split types/index.ts into domain modules behind a barrel ([#​1968](autobrr/qui#1968)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`2cca076`](autobrr/qui@2cca076): test(db): speed up migrated database tests ([#​1938](autobrr/qui#1938)) ([@​Audionut](https://github.com/Audionut)) - [`a10e697`](autobrr/qui@a10e697): test(sse): warm up subscription before coalescing burst ([#​1987](autobrr/qui#1987)) ([@​s0up4200](https://github.com/s0up4200)) - [`05fda2a`](autobrr/qui@05fda2a): test(web): add unit tests for Priority 1 pure utilities ([#​1940](autobrr/qui#1940)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`8df70a5`](autobrr/qui@8df70a5): test: fan out testdb helper to remaining database.New callers ([#​1962](autobrr/qui#1962)) ([@​nitrobass24](https://github.com/nitrobass24)) **Full Changelog**: <autobrr/qui@v1.19.0...v1.20.0> ##### Docker images - `docker pull ghcr.io/autobrr/qui:v1.20.0` - `docker pull ghcr.io/autobrr/qui:latest` ##### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/London) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjE5NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19--> Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/78
Regular (non-hardlink/reflink) cross-seed mode enabled autoTMM for affixed categories whenever the matched torrent was auto-managed, handing save-path resolution to qBittorrent. With a
.crossaffix, qBittorrent resolves the path from the affixed category (its configuredsave_path, or the implicit<default>/<category>when none is set), which need not equal the matched torrent's location — andensureCrossCategoryonly warns on a divergent pre-existing category, never rewrites it. The result: the cross-seed was relocated into e.g.<base>/<category>.crossand re-downloaded 100% instead of reusing existing files. Hardlink/reflink modes were immune because they always pin an explicitsavepathwith autoTMM off.ensureCrossCategorynow returns the category's actual save path, and regular mode disables autoTMM + pins an explicitsavepathto the matched torrent's location when that path diverges (or is unset), mirroring link modes. The matching-path case is unchanged (autoTMM stays on). Includes a regression test covering a pre-existing divergent.crosscategory. Reported on Discord: a "seedbox" instance using a category suffix re-downloaded cross-seeds while the hardlink "media" instance worked.Summary by CodeRabbit
Release Notes
Bug Fixes
Tests