Skip to content

fix(collections): credit reclaimed bytes when sizeBytes is not yet cached#2855

Merged
enoch85 merged 2 commits into
developmentfrom
fix/handle-bytes-fetch-size-on-null
May 6, 2026
Merged

fix(collections): credit reclaimed bytes when sizeBytes is not yet cached#2855
enoch85 merged 2 commits into
developmentfrom
fix/handle-bytes-fetch-size-on-null

Conversation

@enoch85

@enoch85 enoch85 commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

CollectionHandler.handleMedia reads media.sizeBytes directly off the collection_media row and only increments handledMediaSizeBytes when it's non-null. Per-item sizes are populated lazily by CollectionsService.updateCollectionTotalSize, so any item handled before its first successful size sync falls into a null window — the *arr action runs, the file is deleted, and the reclaimed bytes are silently dropped from the per-collection counter and from the Cleanup totals breakdown. Once the file is gone the size is unrecoverable.

Symptom in the wild (a sized 232 GB episodes collection with arrAction = 0 / DELETE, 128 currently-sized items, but handledMediaAmount = 2 / handledMediaSizeBytes = 0 for two earlier handles): the two handled items got processed in the gap before the size cache was backfilled.

Fix

Resolve the on-disk size before the action runs:

  • Use media.sizeBytes directly when it's already populated.
  • Otherwise call CollectionsService.resolveItemSize(mediaServer, mediaServerId) — a new public helper that wraps the same getMetadatasumMediaSourceSizesgetChildrenTotalSize chain updateCollectionTotalSize already uses (refactored to share the helper, no behavior change there).
  • Stash the resolved value, run the action, and use the stashed value for the increment. Doing it before the action matters because for delete-style actions the file is gone afterwards and Plex/Jellyfin metadata loses the size on the next library scan.

Unmonitor / quality-change actions still don't contribute, by design — the lookup is skipped entirely for them.

Cost

At most one extra getMetadata call per handled item, only when sizeBytes is null. After the first sync runs the cache is populated and subsequent handles take the cached path.

Tests

Four new unit tests on CollectionHandler:

  • Cached sizeBytes is credited and resolveItemSize is not called.
  • Null sizeBytes on a delete-style action triggers the lookup, the lookup result is credited, and it runs before the action.
  • Unmonitor actions skip the lookup and credit zero.
  • A failed lookup (returns null) leaves the counter at zero without throwing.

Full collections + storage-metrics suites pass: 129/129.

enoch85 added 2 commits May 6, 2026 18:38
…ched

The handler reads media.sizeBytes directly off the row and skips the
counter increment when it's null. Per-item sizes are populated lazily
by the collection size sync, so an item handled before its first size
sync falls into the null window — the action runs, the file is deleted,
and the bytes never get credited. Once the file is gone the size is
unrecoverable.

Resolve the size before running the action: read the cached value when
present, otherwise call CollectionsService.resolveItemSize against the
media server (the same metadata path updateCollectionTotalSize already
uses, refactored into a public helper so the handler shares it). Use
the resolved value for the post-action increment. Unmonitor and
quality-change actions still don't contribute, by design.

Cost: at most one extra getMetadata call per handled item, only when
sizeBytes is null; bounded because the cache is populated as a side
effect of subsequent syncs.
@enoch85 enoch85 merged commit fca5f84 into development May 6, 2026
12 checks passed
@enoch85 enoch85 deleted the fix/handle-bytes-fetch-size-on-null branch May 6, 2026 18:45
maintainerr-automation Bot added a commit that referenced this pull request May 8, 2026
* feat(storage-metrics): split potential reclaim into movie/show/season/episode panels (#2854)

* fix(collections): credit reclaimed bytes when sizeBytes is not yet cached (#2855)

* Feature/plex collection sort (#2856)

* feat: implement plex adapter collection sorting logic

* feat: apply collection sort orchestration in collections service

* feat: add collection items sort dropdown to rule form

* feat: complete UI and full stack implementation for collection sorting

* fix(collections): address review feedback for collection sort

- Generate proper TypeORM migration for mediaServerSort column
  (previous PR shipped the entity change without one — upgrades would
  fail on the missing column).
- Extend collection sort comparator for addDate and releaseDate; the
  UI offered seven sort options but four (addDate.{asc,desc},
  releaseDate.{asc,desc}) fell through to the default branch and
  silently preserved input order.
- Replace fake MediaServerCollectionSort union with a real template
  literal (\${CollectionMediaSortField}.\${MediaSortOrder}); add
  parseCollectionSortKey helper and remove \`as any\` casts.
- Add smart-collection guard before reorder (Plex rejects move on
  smart) and membership-changed gate so we only push order to Plex
  when newMedia.length > 0.
- Filter items with missing metadata before sorting; replace the
  \`mediaData!\` non-null assertion.
- Move MEDIA_SERVER_FEATURES + supportsFeature into contracts and gate
  the UI dropdown via supportsFeature(COLLECTION_SORT) so the feature
  becomes available automatically if Jellyfin ever gains a boxset
  reorder API.
- Add ICollection.mediaServerSort field; tighten zod-to-payload
  parsing in AddModal.
- Revert unrelated edits introduced by this PR (rules.service return
  values, scheduler defensive guards, cosmetic setContext changes).
- Tests: setCollectionCustomSort and moveCollectionItem URL shapes,
  PlexAdapter.reorderCollectionItems orchestration, JellyfinAdapter
  feature flag and reorder rejection.

---------

Co-authored-by: Steven Seitz <steven.seitz@omron.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>

* fix(collections): apply collection sort on save, harden Plex reorder (#2860)

- Apply the collection sort immediately when a rule group is saved with
  a newly enabled or changed sort value, so users no longer see a
  configured sort that hasn't been pushed to Plex until a later add
  cycle. Previous behaviour gated the push on `newMedia.length > 0`,
  which left save-time changes unapplied indefinitely.

- Continue past per-item move failures in the Plex adapter
  reorderCollectionItems loop. A single rejected move no longer aborts
  subsequent moves; failures are accumulated and logged in a summary
  warning so the rest of the collection still gets ordered.

- Short-circuit the Plex reorder when the collection is already in the
  requested order. The adapter reads current child IDs first via
  PlexApiService.getCollectionChildren and returns early when they
  match `orderedItemIds`, skipping both the prefs PUT and every move
  PUT. The membership-changed gate in addToCollectionInternal is kept
  as a coarse pre-filter; the adapter check is the safety net.

- Reuse the existing `case 'title':` branch in compareMediaItemsBySort
  as a tiebreaker for airDate / rating / watchCount / deleteSoonest, so
  items sharing the same primary value fall back to alphabetical
  ordering. Status sorts (manual / excluded) intentionally skip the
  fallback to preserve their partition contract. Affects every
  consumer of the comparator (Maintainerr UI overview/library/
  collection display and the Plex collection-sort push) so on-disk
  Plex order matches what users see in Maintainerr for the same key.

Tests: new sort-utils.spec covering the timelordx tiebreaker scenario,
updated plex-adapter.service.spec for short-circuit and per-item
failure paths, and rules.service.updateRules.spec covering the
save-time apply transition.

* fix(metadata): align Jellyfin retry id check with pre-filter (#2853)

The metadata refresh retry path validated the verified id with
isBlankMediaServerId, while the pre-filter used shouldRefreshMetadataItemId.
When getMetadata returned an item with the Jellyfin empty GUID
(00000000-0000-0000-0000-000000000000), the asymmetric check let the
retry call refreshItemMetadata with that id, producing the
"Guid can't be empty" spam in Jellyfin's ProviderManager queue.

Use the same predicate in both places so foreign-shaped and empty-GUID
ids are rejected consistently before any refresh request is issued.

* Add Maintainerr architecture overview (#2817)

---------

Co-authored-by: enoch85 <mailto@danielhansson.nu>
Co-authored-by: 00Scooby <72069678+00Scooby@users.noreply.github.com>
Co-authored-by: Steven Seitz <steven.seitz@omron.com>
Co-authored-by: maintainerr-automation[bot] <261505141+maintainerr-automation[bot]@users.noreply.github.com>
Co-authored-by: Kristian Matthews-Kennington <kristian@matthews-kennington.com>
@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.11.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@enoch85 enoch85 added this to the 3.11.0 milestone May 12, 2026
doonga pushed a commit to greyrock-labs/home-ops that referenced this pull request May 13, 2026
…11.1 ) (#29)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/maintainerr/maintainerr](https://github.com/Maintainerr/Maintainerr) | minor | `3.10.1` → `3.11.1` |

---

### Release Notes

<details>
<summary>Maintainerr/Maintainerr (ghcr.io/maintainerr/maintainerr)</summary>

### [`v3.11.1`](https://github.com/Maintainerr/Maintainerr/blob/HEAD/CHANGELOG.md#3111-2026-05-12)

[Compare Source](Maintainerr/Maintainerr@v3.11.0...v3.11.1)

#### Highlights

- Fixed an issue where Jellyfin libraries with "Group films into collections" enabled caused BoxSet members to incorrectly toggle in and out of rule results ([#&#8203;2870](Maintainerr/Maintainerr#2870)).
- Improved error message for invalid Plex library section IDs to better guide users in resolving configuration issues ([#&#8203;2883](Maintainerr/Maintainerr#2883)).
- Enhanced custom collection UX by renaming tags for clarity and adding tooltips to explain collection handling options ([#&#8203;2882](Maintainerr/Maintainerr#2882)).

#### Fixes

- Validated Jellyfin IDs before refresh to prevent errors ([#&#8203;2853](Maintainerr/Maintainerr#2853)).
- Improved error message when Plex library section ID is invalid ([#&#8203;2883](Maintainerr/Maintainerr#2883)).
- Resolved Jellyfin collection add/remove loop for BoxSet items ([#&#8203;2870](Maintainerr/Maintainerr#2870)).
- Clarified custom collection terminology and added warnings for disabling collection handling ([#&#8203;2882](Maintainerr/Maintainerr#2882)).
- Added explicit token permissions to the Fider move job to address CodeQL findings.
- Added environment gate to mitigate TOCTOU vulnerability in the release\_pr workflow ([#&#8203;2879](Maintainerr/Maintainerr#2879)).
- Addressed transitive dependency vulnerabilities by adding Yarn resolutions for specific packages ([#&#8203;2881](Maintainerr/Maintainerr#2881)).

#### Dependencies

- Updated 10 dependencies, including notable packages: vite, typeorm, and [@&#8203;typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/eslint-plugin).

#### New Contributors

- [@&#8203;blixten85](https://github.com/blixten85) made their first contribution in [#&#8203;2881](Maintainerr/Maintainerr#2881)

### [`v3.11.0`](https://github.com/Maintainerr/Maintainerr/blob/HEAD/CHANGELOG.md#3110-2026-05-11)

[Compare Source](Maintainerr/Maintainerr@v3.10.2...v3.11.0)

#### Highlights

- Added support for force-processing overlays and gated reset operations against concurrent processing runs ([#&#8203;2827](Maintainerr/Maintainerr#2827)).
- Improved collection sorting: collections now apply sort order on save and synchronize with the media server ([#&#8203;2860](Maintainerr/Maintainerr#2860)).
- Enhanced storage metrics: potential reclaimable storage is now split into movie, show, season, and episode panels ([#&#8203;2854](Maintainerr/Maintainerr#2854)).

#### Breaking Changes

- None.

#### Features

- Added force-processing support for overlay operations and gated reset operations against concurrent processing runs ([#&#8203;2827](Maintainerr/Maintainerr#2827)).
- Improved storage metrics by splitting potential reclaimable storage into movie, show, season, and episode panels ([#&#8203;2854](Maintainerr/Maintainerr#2854)).

#### Fixes

- Fixed collection sorting to apply on save and synchronize with the media server ([#&#8203;2860](Maintainerr/Maintainerr#2860)).
- Resolved issue where excluding a single episode incorrectly excluded all episodes of the same show ([#&#8203;2867](Maintainerr/Maintainerr#2867)).
- Fixed storage metrics to merge shared volumes across hosts when byte-exact ([#&#8203;2852](Maintainerr/Maintainerr#2852)).
- Fixed Jellyfin retry ID check to align with pre-filter logic ([#&#8203;2853](Maintainerr/Maintainerr#2853)).
- Addressed issue where reclaimed bytes were not credited when `sizeBytes` was not yet cached ([#&#8203;2855](Maintainerr/Maintainerr#2855)).

#### Performance

- None.

#### Database migrations

- Added a new `mediaServerSort` column to the `collection` table to support media server-specific sorting.

#### Internal

- Added typed TanStack Query test helpers and migrated UI spec mocks to use typed builders ([#&#8203;2863](Maintainerr/Maintainerr#2863)).
- Added an architecture overview document detailing the monorepo structure, runtime flow, and core components ([#&#8203;2817](Maintainerr/Maintainerr#2817)).

#### Dependencies

- Updated 5 dependencies, including notable updates to TypeScript and typescript-eslint.

### [`v3.10.2`](https://github.com/Maintainerr/Maintainerr/blob/HEAD/CHANGELOG.md#3102-2026-05-08)

[Compare Source](Maintainerr/Maintainerr@v3.10.1...v3.10.2)

#### Highlights

- Fixed incorrect version comparison logic that caused update notifications to fail for multi-digit version segments ([#&#8203;2838](Maintainerr/Maintainerr#2838)).
- Improved storage metrics by deduplicating reclaimable bytes and adding per-type cleanup byte counters ([#&#8203;2833](Maintainerr/Maintainerr#2833)).

</details>

---

### Configuration

📅 **Schedule**: (in timezone America/New_York)

- 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 [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjAuNyIsInVwZGF0ZWRJblZlciI6IjQzLjE2MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant