feat: remove "requested" status of unmonitored movies during scan#817
feat: remove "requested" status of unmonitored movies during scan#817bonswouar wants to merge 1 commit intoseerr-team:developfrom
Conversation
|
Converted to draft as the title states "WIP". Once it's ready feel free to make this pr ready for review |
|
@fallenbagel Thanks I didn't think about the Draft feature This is a WIP but it's basically working as I'd expect. Only drawback is that if you have lots of Unmonitored movies in Radarr the scan should be slower, as it will request each unmonitored movie in db every time. Let me know if you have some thoughts about this |
|
FYI I've been using this for months and it works as expected. A more complete monitoring status support wouldn't be very useful imo, at least I didn't encounter any use case |
gauthier-th
left a comment
There was a problem hiding this comment.
I don't think this should be merged if it's only Radarr. Could you implement the same logic for Sonarr seasons?
And shouldn't there be a setting to enable/disable this behavior? You may have unmonitored a movie/tv show but still want to keep it as requested.
|
This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged. |
00d383a to
3e24708
Compare
This comment was marked as outdated.
This comment was marked as outdated.
3e24708 to
07ccea1
Compare
This comment was marked as outdated.
This comment was marked as outdated.
gauthier-th
left a comment
There was a problem hiding this comment.
It looks like the way things are done are not consistent between Radarr and Sonarr.
Why do you change the status of the Media entity for movies while you remove the SeasonRequest for series?
It may be linked to the issue you experienced.
| : settings.main.removeUnmonitoredFromRequestsEnabled && | ||
| !season.monitored && | ||
| season.episodes == 0 | ||
| ? MediaStatus.UNKNOWN |
There was a problem hiding this comment.
Why is this change necessary? Isn't the Sonarr scanner job enough?
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There are several jobs that update the status of a Media: Radarr/Sonarr jobs, and Jellyfin/Emby/Plex jobs, hence the comment about competing jobs. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
94313cd to
7e9d84d
Compare
There is another job, "Media Availability Sync" that runs and mark the media as unavailable if necessary.
It could come from your Let me review it one more time with the change you made and with your comments in mind. |
b9ff7d4 to
caaf13e
Compare
This comment was marked as outdated.
This comment was marked as outdated.
60e5258 to
c014ed8
Compare
|
This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged. |
c014ed8 to
a3e2de1
Compare
|
Just a little reminder, if you have an idea for a better way of handling this let me know I could probably look into it. Otherwise, I've been using my fork for more than a year and it works exactly as expected fyi |
a3e2de1 to
0d8627a
Compare
0d8627a to
31f2b83
Compare
|
This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged. |
31f2b83 to
f1fc94e
Compare
f1fc94e to
eb6ac1c
Compare
eb6ac1c to
436f121
Compare
436f121 to
4b85191
Compare
4b85191 to
5deecb3
Compare
5deecb3 to
887c5e6
Compare
887c5e6 to
6ebff78
Compare
📝 WalkthroughWalkthroughA new feature flag Changes
Sequence DiagramsequenceDiagram
participant User
participant SettingsUI as Settings UI Form
participant Backend as Settings API
participant Database as Settings Store
participant RadarrScanner as Radarr Scanner
participant MediaDB as Media Database
User->>SettingsUI: Enable "Remove Unmonitored Media"
SettingsUI->>Backend: POST /settings with removeUnmonitoredEnabled: true
Backend->>Database: Save removeUnmonitoredEnabled: true
Database-->>Backend: Confirmed
Backend-->>SettingsUI: Settings updated
Note over RadarrScanner: Next sync cycle
RadarrScanner->>Backend: Fetch current settings
Backend-->>RadarrScanner: removeUnmonitoredEnabled: true
RadarrScanner->>RadarrScanner: Check each Radarr movie
alt Movie is unmonitored & removeUnmonitoredEnabled
RadarrScanner->>MediaDB: Find Media by TMDB ID
MediaDB-->>RadarrScanner: Return existing Media
RadarrScanner->>RadarrScanner: processUnmonitoredMovie()
RadarrScanner->>MediaDB: Update status to UNKNOWN
MediaDB-->>RadarrScanner: Saved
else Movie is unmonitored & NOT removeUnmonitoredEnabled
RadarrScanner->>RadarrScanner: Log debug & skip
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@server/lib/scanners/baseScanner.ts`:
- Around line 707-719: The unmonitored Radarr flow fails to clear the 4K status
because processUnmonitoredMovie(tmdbId: number) doesn't accept an is4k flag;
change its signature to processUnmonitoredMovie(tmdbId: number, is4k = false),
propagate the is4k boolean from the Radarr unmonitored check that currently
calls it, and when you find an existing Media in processUnmonitoredMovie (using
getExisting and mediaRepository), set existing.status = MediaStatus.UNKNOWN and
also set existing.status4k = MediaStatus.UNKNOWN when is4k is true before
calling mediaRepository.save(existing); mirror the conditional update pattern
used in processMovie to ensure both regular and 4K status fields are handled.
In `@server/lib/scanners/radarr/index.ts`:
- Around line 82-89: The code calls processUnmonitoredMovie(radarrMovie.tmdbId)
without awaiting it, which can leave its Promise unhandled and let the scan
continue prematurely; update the call in the radarr scanner (where getSettings()
and settings.main.removeUnmonitoredEnabled are checked and
radarrMovie.monitored/hasFile are evaluated) to await
this.processUnmonitoredMovie(radarrMovie.tmdbId) and ensure the surrounding
function is async or properly handles the returned Promise so errors are
propagated.
In `@src/components/Settings/SettingsMain/index.tsx`:
- Around line 181-182: The prop/state field removeUnmonitoredEnabled in
SettingsMain (index.tsx) can be undefined; update its assignment to always be a
boolean (e.g., use nullish coalescing or a boolean cast) so
removeUnmonitoredEnabled is set to false when data?.removeUnmonitoredEnabled is
undefined, keeping the checkbox controlled and the payload stable.
In `@src/i18n/locale/en.json`:
- Line 989: The i18n key
"components.Settings.SettingsMain.removeUnmonitoredFromRequestsEnabled" does not
match the UI and therefore won’t be used; update the JSON to replace that
incorrect key with the UI-expected keys
"components.Settings.SettingsMain.removeUnmonitoredEnabled" and
"components.Settings.SettingsMain.removeUnmonitoredExplanation" (splitting the
current message into the concise label for removeUnmonitoredEnabled and the
descriptive text for removeUnmonitoredExplanation) so the strings are picked up
by the UI.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
cypress/config/settings.cypress.jsonseerr-api.ymlserver/interfaces/api/settingsInterfaces.tsserver/lib/scanners/baseScanner.tsserver/lib/scanners/radarr/index.tsserver/lib/settings/index.tssrc/components/Settings/SettingsMain/index.tsxsrc/context/SettingsContext.tsxsrc/i18n/locale/en.jsonsrc/pages/_app.tsx
| protected async processUnmonitoredMovie(tmdbId: number): Promise<void> { | ||
| const mediaRepository = getRepository(Media); | ||
| await this.asyncLock.dispatch(tmdbId, async () => { | ||
| const existing = await this.getExisting(tmdbId, MediaType.MOVIE); | ||
| if (existing && existing.status === MediaStatus.PROCESSING) { | ||
| existing.status = MediaStatus.UNKNOWN; | ||
| await mediaRepository.save(existing); | ||
| this.log( | ||
| `Movie TMDB ID ${tmdbId} unmonitored from Radarr. Media status set to UNKNOWN.`, | ||
| 'info' | ||
| ); | ||
| } | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find call sites of processUnmonitoredMovie
echo "=== Call sites of processUnmonitoredMovie ==="
rg -n "processUnmonitoredMovie" -S server/lib/scanners
echo -e "\n=== Unmonitored handling in Radarr ==="
rg -n "unmonitored" -S server/lib/scanners/radarr/index.ts
echo -e "\n=== General unmonitored handling ==="
rg -n "unmonitored" -S server/lib/scanners/baseScanner.tsRepository: seerr-team/seerr
Length of output: 676
🏁 Script executed:
#!/bin/bash
# Examine the Radarr unmonitored handling in context
echo "=== Radarr index.ts unmonitored handling (lines 75-95) ==="
sed -n '75,95p' server/lib/scanners/radarr/index.ts
echo -e "\n=== Check for 4K handling in Radarr ==="
rg -n "4k|is4k|status4k" -S server/lib/scanners/radarr/index.ts
echo -e "\n=== Check Media entity structure ==="
rg -n "status4k" -S server/entityRepository: seerr-team/seerr
Length of output: 2055
🏁 Script executed:
#!/bin/bash
# Check if processUnmonitoredMovie is overridden in subclasses
echo "=== Check for processUnmonitoredMovie implementation in all scanners ==="
rg -n "processUnmonitoredMovie" -S server/lib/scanners -A 5
echo -e "\n=== Check if processMovie method handles 4K ==="
rg -n "processMovie.*is4k" -S server/lib/scanners/baseScanner.ts -B 2 -A 10Repository: seerr-team/seerr
Length of output: 1177
🏁 Script executed:
#!/bin/bash
# Search for processMovie method definition
echo "=== processMovie method signature ==="
rg -n "protected.*processMovie" -S server/lib/scanners/baseScanner.ts -A 10
echo -e "\n=== Check how is4k parameter is used in processMovie ==="
rg -n "is4k:" -S server/lib/scanners/baseScanner.ts -B 2 -A 2Repository: seerr-team/seerr
Length of output: 414
🏁 Script executed:
#!/bin/bash
# See full processMovie to understand how is4k is used
echo "=== Full processMovie method (first 100 lines) ==="
sed -n '94,194p' server/lib/scanners/baseScanner.ts
echo -e "\n=== Search for status4k usage in baseScanner ==="
rg -n "status4k" -S server/lib/scanners/baseScanner.tsRepository: seerr-team/seerr
Length of output: 5055
Pass is4k flag to handle both status and status4k for unmonitored 4K Radarr movies.
The unmonitored check in Radarr (lines 84–88) returns early before determining if the server is 4K, calling processUnmonitoredMovie without an is4k flag. Since processUnmonitoredMovie only updates the regular status field to UNKNOWN, any unmonitored movie in a 4K-configured Radarr server will leave status4k unchanged (likely PROCESSING), causing it to still appear "requested" in 4K views.
Update processUnmonitoredMovie to accept an is4k parameter and update status4k when applicable, similar to how processMovie handles it (line 117).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/lib/scanners/baseScanner.ts` around lines 707 - 719, The unmonitored
Radarr flow fails to clear the 4K status because processUnmonitoredMovie(tmdbId:
number) doesn't accept an is4k flag; change its signature to
processUnmonitoredMovie(tmdbId: number, is4k = false), propagate the is4k
boolean from the Radarr unmonitored check that currently calls it, and when you
find an existing Media in processUnmonitoredMovie (using getExisting and
mediaRepository), set existing.status = MediaStatus.UNKNOWN and also set
existing.status4k = MediaStatus.UNKNOWN when is4k is true before calling
mediaRepository.save(existing); mirror the conditional update pattern used in
processMovie to ensure both regular and 4K status fields are handled.
| const settings = getSettings(); | ||
| if ( | ||
| settings.main.removeUnmonitoredEnabled && | ||
| !radarrMovie.monitored && | ||
| !radarrMovie.hasFile | ||
| ) { | ||
| this.processUnmonitoredMovie(radarrMovie.tmdbId); | ||
| return; |
There was a problem hiding this comment.
Await processUnmonitoredMovie to avoid unhandled async work.
processUnmonitoredMovie returns a Promise; without await, errors can be unhandled and the scan can advance before the state transition completes.
🛠️ Suggested fix
- this.processUnmonitoredMovie(radarrMovie.tmdbId);
+ await this.processUnmonitoredMovie(radarrMovie.tmdbId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/lib/scanners/radarr/index.ts` around lines 82 - 89, The code calls
processUnmonitoredMovie(radarrMovie.tmdbId) without awaiting it, which can leave
its Promise unhandled and let the scan continue prematurely; update the call in
the radarr scanner (where getSettings() and
settings.main.removeUnmonitoredEnabled are checked and
radarrMovie.monitored/hasFile are evaluated) to await
this.processUnmonitoredMovie(radarrMovie.tmdbId) and ensure the surrounding
function is async or properly handles the returned Promise so errors are
propagated.
| removeUnmonitoredEnabled: data?.removeUnmonitoredEnabled, | ||
| }} |
There was a problem hiding this comment.
Ensure removeUnmonitoredEnabled is always boolean.
If data?.removeUnmonitoredEnabled is undefined, the checkbox state/payload can be undefined. Defaulting to false keeps the form controlled and the payload stable.
🛠️ Suggested fix
- removeUnmonitoredEnabled: data?.removeUnmonitoredEnabled,
+ removeUnmonitoredEnabled: data?.removeUnmonitoredEnabled ?? false,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| removeUnmonitoredEnabled: data?.removeUnmonitoredEnabled, | |
| }} | |
| removeUnmonitoredEnabled: data?.removeUnmonitoredEnabled ?? false, | |
| }} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/Settings/SettingsMain/index.tsx` around lines 181 - 182, The
prop/state field removeUnmonitoredEnabled in SettingsMain (index.tsx) can be
undefined; update its assignment to always be a boolean (e.g., use nullish
coalescing or a boolean cast) so removeUnmonitoredEnabled is set to false when
data?.removeUnmonitoredEnabled is undefined, keeping the checkbox controlled and
the payload stable.
| "components.Settings.SettingsMain.partialRequestsEnabled": "Allow Partial Series Requests", | ||
| "components.Settings.SettingsMain.streamingRegion": "Streaming Region", | ||
| "components.Settings.SettingsMain.streamingRegionTip": "Show streaming sites by regional availability", | ||
| "components.Settings.SettingsMain.removeUnmonitoredFromRequestsEnabled": "Remove Request for Movies that have been un-monitored since", |
There was a problem hiding this comment.
Fix i18n key to match the UI message IDs.
The UI uses components.Settings.SettingsMain.removeUnmonitoredEnabled and components.Settings.SettingsMain.removeUnmonitoredExplanation, so this key won’t be picked up and the new strings won’t be localizable.
🛠️ Suggested fix
- "components.Settings.SettingsMain.removeUnmonitoredFromRequestsEnabled": "Remove Request for Movies that have been un-monitored since"
+ "components.Settings.SettingsMain.removeUnmonitoredEnabled": "Remove Unmonitored Media",
+ "components.Settings.SettingsMain.removeUnmonitoredExplanation": "Remove movies from Jellyseerr that are not available and have been unmonitored."📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "components.Settings.SettingsMain.removeUnmonitoredFromRequestsEnabled": "Remove Request for Movies that have been un-monitored since", | |
| "components.Settings.SettingsMain.removeUnmonitoredEnabled": "Remove Unmonitored Media", | |
| "components.Settings.SettingsMain.removeUnmonitoredExplanation": "Remove movies from Jellyseerr that are not available and have been unmonitored." |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/i18n/locale/en.json` at line 989, The i18n key
"components.Settings.SettingsMain.removeUnmonitoredFromRequestsEnabled" does not
match the UI and therefore won’t be used; update the JSON to replace that
incorrect key with the UI-expected keys
"components.Settings.SettingsMain.removeUnmonitoredEnabled" and
"components.Settings.SettingsMain.removeUnmonitoredExplanation" (splitting the
current message into the concise label for removeUnmonitoredEnabled and the
descriptive text for removeUnmonitoredExplanation) so the strings are picked up
by the UI.
|
superseded by #2757 |
Description
Quick & easy first implementation of Monitoring status support in Radarr:
When a movie - which was monitored before - is unmonitored, it won't appear as "requested" in Jellyseerr anymore
Issues Fixed or Closed
Summary by CodeRabbit
New Features
Bug Fixes