Skip to content

Commit 1e2c6f4

Browse files
authored
fix: added a refresh interval if download status is in progress (#3275)
* fix: added a refresh interval if download status is in progress * refactor: switched to a function instead of useEffect * feat: added editable download sync schedule
1 parent dd1378c commit 1e2c6f4

File tree

9 files changed

+119
-24
lines changed

9 files changed

+119
-24
lines changed

server/job/schedule.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface ScheduledJob {
1414
job: schedule.Job;
1515
name: string;
1616
type: 'process' | 'command';
17-
interval: 'short' | 'long' | 'fixed';
17+
interval: 'seconds' | 'minutes' | 'hours' | 'fixed';
1818
cronSchedule: string;
1919
running?: () => boolean;
2020
cancelFn?: () => void;
@@ -30,7 +30,7 @@ export const startJobs = (): void => {
3030
id: 'plex-recently-added-scan',
3131
name: 'Plex Recently Added Scan',
3232
type: 'process',
33-
interval: 'short',
33+
interval: 'minutes',
3434
cronSchedule: jobs['plex-recently-added-scan'].schedule,
3535
job: schedule.scheduleJob(jobs['plex-recently-added-scan'].schedule, () => {
3636
logger.info('Starting scheduled job: Plex Recently Added Scan', {
@@ -47,7 +47,7 @@ export const startJobs = (): void => {
4747
id: 'plex-full-scan',
4848
name: 'Plex Full Library Scan',
4949
type: 'process',
50-
interval: 'long',
50+
interval: 'hours',
5151
cronSchedule: jobs['plex-full-scan'].schedule,
5252
job: schedule.scheduleJob(jobs['plex-full-scan'].schedule, () => {
5353
logger.info('Starting scheduled job: Plex Full Library Scan', {
@@ -64,7 +64,7 @@ export const startJobs = (): void => {
6464
id: 'plex-watchlist-sync',
6565
name: 'Plex Watchlist Sync',
6666
type: 'process',
67-
interval: 'short',
67+
interval: 'minutes',
6868
cronSchedule: jobs['plex-watchlist-sync'].schedule,
6969
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
7070
logger.info('Starting scheduled job: Plex Watchlist Sync', {
@@ -79,7 +79,7 @@ export const startJobs = (): void => {
7979
id: 'radarr-scan',
8080
name: 'Radarr Scan',
8181
type: 'process',
82-
interval: 'long',
82+
interval: 'hours',
8383
cronSchedule: jobs['radarr-scan'].schedule,
8484
job: schedule.scheduleJob(jobs['radarr-scan'].schedule, () => {
8585
logger.info('Starting scheduled job: Radarr Scan', { label: 'Jobs' });
@@ -94,7 +94,7 @@ export const startJobs = (): void => {
9494
id: 'sonarr-scan',
9595
name: 'Sonarr Scan',
9696
type: 'process',
97-
interval: 'long',
97+
interval: 'hours',
9898
cronSchedule: jobs['sonarr-scan'].schedule,
9999
job: schedule.scheduleJob(jobs['sonarr-scan'].schedule, () => {
100100
logger.info('Starting scheduled job: Sonarr Scan', { label: 'Jobs' });
@@ -109,7 +109,7 @@ export const startJobs = (): void => {
109109
id: 'download-sync',
110110
name: 'Download Sync',
111111
type: 'command',
112-
interval: 'fixed',
112+
interval: 'seconds',
113113
cronSchedule: jobs['download-sync'].schedule,
114114
job: schedule.scheduleJob(jobs['download-sync'].schedule, () => {
115115
logger.debug('Starting scheduled job: Download Sync', {
@@ -124,7 +124,7 @@ export const startJobs = (): void => {
124124
id: 'download-sync-reset',
125125
name: 'Download Sync Reset',
126126
type: 'command',
127-
interval: 'long',
127+
interval: 'hours',
128128
cronSchedule: jobs['download-sync-reset'].schedule,
129129
job: schedule.scheduleJob(jobs['download-sync-reset'].schedule, () => {
130130
logger.info('Starting scheduled job: Download Sync Reset', {
@@ -134,12 +134,12 @@ export const startJobs = (): void => {
134134
}),
135135
});
136136

137-
// Run image cache cleanup every 5 minutes
137+
// Run image cache cleanup every 24 hours
138138
scheduledJobs.push({
139139
id: 'image-cache-cleanup',
140140
name: 'Image Cache Cleanup',
141141
type: 'process',
142-
interval: 'long',
142+
interval: 'hours',
143143
cronSchedule: jobs['image-cache-cleanup'].schedule,
144144
job: schedule.scheduleJob(jobs['image-cache-cleanup'].schedule, () => {
145145
logger.info('Starting scheduled job: Image Cache Cleanup', {

src/components/CollectionDetails/index.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import useSettings from '@app/hooks/useSettings';
1010
import { Permission, useUser } from '@app/hooks/useUser';
1111
import globalMessages from '@app/i18n/globalMessages';
1212
import Error from '@app/pages/_error';
13+
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
1314
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
1415
import { MediaStatus } from '@server/constants/media';
1516
import type { Collection } from '@server/models/Collection';
@@ -39,28 +40,39 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
3940
const [requestModal, setRequestModal] = useState(false);
4041
const [is4k, setIs4k] = useState(false);
4142

43+
const returnCollectionDownloadItems = (data: Collection | undefined) => {
44+
const [downloadStatus, downloadStatus4k] = [
45+
data?.parts.flatMap((item) =>
46+
item.mediaInfo?.downloadStatus ? item.mediaInfo?.downloadStatus : []
47+
),
48+
data?.parts.flatMap((item) =>
49+
item.mediaInfo?.downloadStatus4k ? item.mediaInfo?.downloadStatus4k : []
50+
),
51+
];
52+
53+
return { downloadStatus, downloadStatus4k };
54+
};
55+
4256
const {
4357
data,
4458
error,
4559
mutate: revalidate,
4660
} = useSWR<Collection>(`/api/v1/collection/${router.query.collectionId}`, {
4761
fallbackData: collection,
4862
revalidateOnMount: true,
63+
refreshInterval: refreshIntervalHelper(
64+
returnCollectionDownloadItems(collection),
65+
15000
66+
),
4967
});
5068

5169
const { data: genres } =
5270
useSWR<{ id: number; name: string }[]>(`/api/v1/genres/movie`);
5371

5472
const [downloadStatus, downloadStatus4k] = useMemo(() => {
55-
return [
56-
data?.parts.flatMap((item) =>
57-
item.mediaInfo?.downloadStatus ? item.mediaInfo?.downloadStatus : []
58-
),
59-
data?.parts.flatMap((item) =>
60-
item.mediaInfo?.downloadStatus4k ? item.mediaInfo?.downloadStatus4k : []
61-
),
62-
];
63-
}, [data?.parts]);
73+
const downloadItems = returnCollectionDownloadItems(data);
74+
return [downloadItems.downloadStatus, downloadItems.downloadStatus4k];
75+
}, [data]);
6476

6577
const [titles, titles4k] = useMemo(() => {
6678
return [

src/components/MovieDetails/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { Permission, useUser } from '@app/hooks/useUser';
2626
import globalMessages from '@app/i18n/globalMessages';
2727
import Error from '@app/pages/_error';
2828
import { sortCrewPriority } from '@app/utils/creditHelpers';
29+
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
2930
import {
3031
ArrowRightCircleIcon,
3132
CloudIcon,
@@ -110,6 +111,13 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
110111
mutate: revalidate,
111112
} = useSWR<MovieDetailsType>(`/api/v1/movie/${router.query.movieId}`, {
112113
fallbackData: movie,
114+
refreshInterval: refreshIntervalHelper(
115+
{
116+
downloadStatus: movie?.mediaInfo?.downloadStatus,
117+
downloadStatus4k: movie?.mediaInfo?.downloadStatus4k,
118+
},
119+
15000
120+
),
113121
});
114122

115123
const { data: ratingData } = useSWR<RTRating>(

src/components/RequestCard/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import StatusBadge from '@app/components/StatusBadge';
77
import useDeepLinks from '@app/hooks/useDeepLinks';
88
import { Permission, useUser } from '@app/hooks/useUser';
99
import globalMessages from '@app/i18n/globalMessages';
10+
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
1011
import { withProperties } from '@app/utils/typeHelpers';
1112
import {
1213
ArrowPathIcon,
@@ -220,6 +221,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
220221
request.type === 'movie'
221222
? `/api/v1/movie/${request.media.tmdbId}`
222223
: `/api/v1/tv/${request.media.tmdbId}`;
224+
223225
const { data: title, error } = useSWR<MovieDetails | TvDetails>(
224226
inView ? `${url}` : null
225227
);
@@ -229,6 +231,13 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
229231
mutate: revalidate,
230232
} = useSWR<MediaRequest>(`/api/v1/request/${request.id}`, {
231233
fallbackData: request,
234+
refreshInterval: refreshIntervalHelper(
235+
{
236+
downloadStatus: request.media.downloadStatus,
237+
downloadStatus4k: request.media.downloadStatus4k,
238+
},
239+
15000
240+
),
232241
});
233242

234243
const { plexUrl, plexUrl4k } = useDeepLinks({

src/components/RequestList/RequestItem/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import StatusBadge from '@app/components/StatusBadge';
77
import useDeepLinks from '@app/hooks/useDeepLinks';
88
import { Permission, useUser } from '@app/hooks/useUser';
99
import globalMessages from '@app/i18n/globalMessages';
10+
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
1011
import {
1112
ArrowPathIcon,
1213
CheckIcon,
@@ -293,6 +294,13 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
293294
`/api/v1/request/${request.id}`,
294295
{
295296
fallbackData: request,
297+
refreshInterval: refreshIntervalHelper(
298+
{
299+
downloadStatus: request.media.downloadStatus,
300+
downloadStatus4k: request.media.downloadStatus4k,
301+
},
302+
15000
303+
),
296304
}
297305
);
298306

src/components/Settings/SettingsJobsCache/index.tsx

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
6767
'Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}',
6868
editJobScheduleSelectorMinutes:
6969
'Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}',
70+
editJobScheduleSelectorSeconds:
71+
'Every {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}',
7072
imagecache: 'Image Cache',
7173
imagecacheDescription:
7274
'When enabled in settings, Overseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.',
@@ -78,7 +80,7 @@ interface Job {
7880
id: JobId;
7981
name: string;
8082
type: 'process' | 'command';
81-
interval: 'short' | 'long' | 'fixed';
83+
interval: 'seconds' | 'minutes' | 'hours' | 'fixed';
8284
cronSchedule: string;
8385
nextExecutionTime: string;
8486
running: boolean;
@@ -89,10 +91,11 @@ type JobModalState = {
8991
job?: Job;
9092
scheduleHours: number;
9193
scheduleMinutes: number;
94+
scheduleSeconds: number;
9295
};
9396

9497
type JobModalAction =
95-
| { type: 'set'; hours?: number; minutes?: number }
98+
| { type: 'set'; hours?: number; minutes?: number; seconds?: number }
9699
| {
97100
type: 'close';
98101
}
@@ -115,13 +118,15 @@ const jobModalReducer = (
115118
job: action.job,
116119
scheduleHours: 1,
117120
scheduleMinutes: 5,
121+
scheduleSeconds: 30,
118122
};
119123

120124
case 'set':
121125
return {
122126
...state,
123127
scheduleHours: action.hours ?? state.scheduleHours,
124128
scheduleMinutes: action.minutes ?? state.scheduleMinutes,
129+
scheduleSeconds: action.seconds ?? state.scheduleSeconds,
125130
};
126131
}
127132
};
@@ -149,6 +154,7 @@ const SettingsJobs = () => {
149154
isOpen: false,
150155
scheduleHours: 1,
151156
scheduleMinutes: 5,
157+
scheduleSeconds: 30,
152158
});
153159
const [isSaving, setIsSaving] = useState(false);
154160

@@ -200,9 +206,11 @@ const SettingsJobs = () => {
200206
const jobScheduleCron = ['0', '0', '*', '*', '*', '*'];
201207

202208
try {
203-
if (jobModalState.job?.interval === 'short') {
209+
if (jobModalState.job?.interval === 'seconds') {
210+
jobScheduleCron.splice(0, 2, `*/${jobModalState.scheduleSeconds}`, '*');
211+
} else if (jobModalState.job?.interval === 'minutes') {
204212
jobScheduleCron[1] = `*/${jobModalState.scheduleMinutes}`;
205-
} else if (jobModalState.job?.interval === 'long') {
213+
} else if (jobModalState.job?.interval === 'hours') {
206214
jobScheduleCron[2] = `*/${jobModalState.scheduleHours}`;
207215
} else {
208216
// jobs with interval: fixed should not be editable
@@ -286,7 +294,30 @@ const SettingsJobs = () => {
286294
{intl.formatMessage(messages.editJobSchedulePrompt)}
287295
</label>
288296
<div className="form-input-area">
289-
{jobModalState.job?.interval === 'short' ? (
297+
{jobModalState.job?.interval === 'seconds' ? (
298+
<select
299+
name="jobScheduleSeconds"
300+
className="inline"
301+
value={jobModalState.scheduleSeconds}
302+
onChange={(e) =>
303+
dispatch({
304+
type: 'set',
305+
seconds: Number(e.target.value),
306+
})
307+
}
308+
>
309+
{[30, 45, 60].map((v) => (
310+
<option value={v} key={`jobScheduleSeconds-${v}`}>
311+
{intl.formatMessage(
312+
messages.editJobScheduleSelectorSeconds,
313+
{
314+
jobScheduleSeconds: v,
315+
}
316+
)}
317+
</option>
318+
))}
319+
</select>
320+
) : jobModalState.job?.interval === 'minutes' ? (
290321
<select
291322
name="jobScheduleMinutes"
292323
className="inline"

src/components/TvDetails/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { Permission, useUser } from '@app/hooks/useUser';
3030
import globalMessages from '@app/i18n/globalMessages';
3131
import Error from '@app/pages/_error';
3232
import { sortCrewPriority } from '@app/utils/creditHelpers';
33+
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
3334
import { Disclosure, Transition } from '@headlessui/react';
3435
import {
3536
ArrowRightCircleIcon,
@@ -109,6 +110,13 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
109110
mutate: revalidate,
110111
} = useSWR<TvDetailsType>(`/api/v1/tv/${router.query.tvId}`, {
111112
fallbackData: tv,
113+
refreshInterval: refreshIntervalHelper(
114+
{
115+
downloadStatus: tv?.mediaInfo?.downloadStatus,
116+
downloadStatus4k: tv?.mediaInfo?.downloadStatus4k,
117+
},
118+
15000
119+
),
112120
});
113121

114122
const { data: ratingData } = useSWR<RTRating>(

src/i18n/locale/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@
739739
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "New Frequency",
740740
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}",
741741
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}",
742+
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "Every {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}",
742743
"components.Settings.SettingsJobsCache.flushcache": "Flush Cache",
743744
"components.Settings.SettingsJobsCache.image-cache-cleanup": "Image Cache Cleanup",
744745
"components.Settings.SettingsJobsCache.imagecache": "Image Cache",

src/utils/refreshIntervalHelper.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { DownloadingItem } from '@server/lib/downloadtracker';
2+
3+
export const refreshIntervalHelper = (
4+
downloadItem: {
5+
downloadStatus: DownloadingItem[] | undefined;
6+
downloadStatus4k: DownloadingItem[] | undefined;
7+
},
8+
timer: number
9+
) => {
10+
if (
11+
(downloadItem.downloadStatus ?? []).length > 0 ||
12+
(downloadItem.downloadStatus4k ?? []).length > 0
13+
) {
14+
return timer;
15+
} else {
16+
return 0;
17+
}
18+
};

0 commit comments

Comments
 (0)