diff --git a/server/job/schedule.ts b/server/job/schedule.ts index 15bf033ed..f65cdebbd 100644 --- a/server/job/schedule.ts +++ b/server/job/schedule.ts @@ -1,6 +1,10 @@ import { MediaServerType } from '@server/constants/server'; import downloadTracker from '@server/lib/downloadtracker'; import ImageProxy from '@server/lib/imageproxy'; +import { + jellyfinFullScanner, + jellyfinRecentScanner, +} from '@server/lib/scanners/jellyfin'; import { plexFullScanner, plexRecentScanner } from '@server/lib/scanners/plex'; import { radarrScanner } from '@server/lib/scanners/radarr'; import { sonarrScanner } from '@server/lib/scanners/sonarr'; @@ -10,7 +14,6 @@ import watchlistSync from '@server/lib/watchlistsync'; import logger from '@server/logger'; import random from 'lodash/random'; import schedule from 'node-schedule'; -import { jobJellyfinFullSync, jobJellyfinRecentSync } from './jellyfinsync'; interface ScheduledJob { id: JobId; @@ -73,38 +76,38 @@ export const startJobs = (): void => { // Run recently added jellyfin sync every 5 minutes scheduledJobs.push({ id: 'jellyfin-recently-added-scan', - name: 'Jellyfin Recently Added Sync', + name: 'Jellyfin Recently Added Scan', type: 'process', interval: 'minutes', cronSchedule: jobs['jellyfin-recently-added-scan'].schedule, job: schedule.scheduleJob( jobs['jellyfin-recently-added-scan'].schedule, () => { - logger.info('Starting scheduled job: Jellyfin Recently Added Sync', { + logger.info('Starting scheduled job: Jellyfin Recently Added Scan', { label: 'Jobs', }); - jobJellyfinRecentSync.run(); + jellyfinRecentScanner.run(); } ), - running: () => jobJellyfinRecentSync.status().running, - cancelFn: () => jobJellyfinRecentSync.cancel(), + running: () => jellyfinRecentScanner.status().running, + cancelFn: () => jellyfinRecentScanner.cancel(), }); // Run full jellyfin sync every 24 hours scheduledJobs.push({ id: 'jellyfin-full-scan', - name: 'Jellyfin Full Library Sync', + name: 'Jellyfin Full Library Scan', type: 'process', interval: 'hours', cronSchedule: jobs['jellyfin-full-scan'].schedule, job: schedule.scheduleJob(jobs['jellyfin-full-scan'].schedule, () => { - logger.info('Starting scheduled job: Jellyfin Full Sync', { + logger.info('Starting scheduled job: Jellyfin Full Scan', { label: 'Jobs', }); - jobJellyfinFullSync.run(); + jellyfinFullScanner.run(); }), - running: () => jobJellyfinFullSync.status().running, - cancelFn: () => jobJellyfinFullSync.cancel(), + running: () => jellyfinFullScanner.status().running, + cancelFn: () => jellyfinFullScanner.cancel(), }); } diff --git a/server/job/jellyfinsync/index.ts b/server/lib/scanners/jellyfin/index.ts similarity index 99% rename from server/job/jellyfinsync/index.ts rename to server/lib/scanners/jellyfin/index.ts index b263ec6e4..c231ec0dc 100644 --- a/server/job/jellyfinsync/index.ts +++ b/server/lib/scanners/jellyfin/index.ts @@ -26,7 +26,7 @@ interface SyncStatus { libraries: Library[]; } -class JobJellyfinSync { +class JellyfinScanner { private sessionId: string; private tmdb: TheMovieDb; private jfClient: JellyfinAPI; @@ -675,7 +675,7 @@ class JobJellyfinSync { } } -export const jobJellyfinFullSync = new JobJellyfinSync(); -export const jobJellyfinRecentSync = new JobJellyfinSync({ +export const jellyfinFullScanner = new JellyfinScanner(); +export const jellyfinRecentScanner = new JellyfinScanner({ isRecentOnly: true, }); diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index dc3724207..5703cccc0 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -12,12 +12,12 @@ import type { LogsResultsResponse, SettingsAboutResponse, } from '@server/interfaces/api/settingsInterfaces'; -import { jobJellyfinFullSync } from '@server/job/jellyfinsync'; import { scheduledJobs } from '@server/job/schedule'; import type { AvailableCacheIds } from '@server/lib/cache'; import cacheManager from '@server/lib/cache'; import ImageProxy from '@server/lib/imageproxy'; import { Permission } from '@server/lib/permissions'; +import { jellyfinFullScanner } from '@server/lib/scanners/jellyfin'; import { plexFullScanner } from '@server/lib/scanners/plex'; import type { JobId, Library, MainSettings } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings'; @@ -345,16 +345,16 @@ settingsRoutes.get('/jellyfin/users', async (req, res) => { }); settingsRoutes.get('/jellyfin/sync', (_req, res) => { - return res.status(200).json(jobJellyfinFullSync.status()); + return res.status(200).json(jellyfinFullScanner.status()); }); settingsRoutes.post('/jellyfin/sync', (req, res) => { if (req.body.cancel) { - jobJellyfinFullSync.cancel(); + jellyfinFullScanner.cancel(); } else if (req.body.start) { - jobJellyfinFullSync.run(); + jellyfinFullScanner.run(); } - return res.status(200).json(jobJellyfinFullSync.status()); + return res.status(200).json(jellyfinFullScanner.status()); }); settingsRoutes.get('/tautulli', (_req, res) => { const settings = getSettings(); diff --git a/src/components/Common/ListView/index.tsx b/src/components/Common/ListView/index.tsx index 907cc8e24..46c946ae2 100644 --- a/src/components/Common/ListView/index.tsx +++ b/src/components/Common/ListView/index.tsx @@ -19,6 +19,7 @@ type ListViewProps = { isLoading?: boolean; isReachingEnd?: boolean; onScrollBottom: () => void; + mutateParent?: () => void; }; const ListView = ({ @@ -28,6 +29,7 @@ const ListView = ({ onScrollBottom, isReachingEnd, plexItems, + mutateParent, }: ListViewProps) => { const intl = useIntl(); useVerticalScroll(onScrollBottom, !isLoading && !isEmpty && !isReachingEnd); @@ -46,7 +48,9 @@ const ListView = ({ id={title.tmdbId} tmdbId={title.tmdbId} type={title.mediaType} + isAddedToWatchlist={true} canExpand + mutateParent={mutateParent} /> ); diff --git a/src/components/Discover/DiscoverWatchlist/index.tsx b/src/components/Discover/DiscoverWatchlist/index.tsx index 775da757a..61d4f5c44 100644 --- a/src/components/Discover/DiscoverWatchlist/index.tsx +++ b/src/components/Discover/DiscoverWatchlist/index.tsx @@ -30,6 +30,7 @@ const DiscoverWatchlist = () => { titles, fetchMore, error, + mutate, } = useDiscover( `/api/v1/${ router.pathname.startsWith('/profile') @@ -76,6 +77,7 @@ const DiscoverWatchlist = () => { } isReachingEnd={isReachingEnd} onScrollBottom={fetchMore} + mutateParent={mutate} /> ); diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx index 729a40a7b..5267ef4e3 100644 --- a/src/components/Settings/SonarrModal/index.tsx +++ b/src/components/Settings/SonarrModal/index.tsx @@ -1,8 +1,10 @@ import Modal from '@app/components/Common/Modal'; import SensitiveInput from '@app/components/Common/SensitiveInput'; +import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; import { Transition } from '@headlessui/react'; -import type { SonarrSettings } from '@server/lib/settings'; +import { MediaServerType } from '@server/constants/server'; +import { type SonarrSettings } from '@server/lib/settings'; import axios from 'axios'; import { Field, Formik } from 'formik'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -109,6 +111,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { const { addToast } = useToasts(); const [isValidated, setIsValidated] = useState(sonarr ? true : false); const [isTesting, setIsTesting] = useState(false); + const settings = useSettings(); const [testResponse, setTestResponse] = useState({ profiles: [], rootFolders: [], @@ -255,7 +258,9 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { animeTags: sonarr?.animeTags ?? [], isDefault: sonarr?.isDefault ?? false, is4k: sonarr?.is4k ?? false, - enableSeasonFolders: sonarr?.enableSeasonFolders ?? false, + enableSeasonFolders: + sonarr?.enableSeasonFolders ?? + settings.currentSettings.mediaServerType !== MediaServerType.PLEX, externalUrl: sonarr?.externalUrl, syncEnabled: sonarr?.syncEnabled ?? false, enableSearch: !sonarr?.preventSearch, @@ -961,11 +966,24 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { > {intl.formatMessage(messages.seasonfolders)} -
+
diff --git a/src/components/TitleCard/TmdbTitleCard.tsx b/src/components/TitleCard/TmdbTitleCard.tsx index 0764f3aa1..825e52ccf 100644 --- a/src/components/TitleCard/TmdbTitleCard.tsx +++ b/src/components/TitleCard/TmdbTitleCard.tsx @@ -12,6 +12,7 @@ export interface TmdbTitleCardProps { type: 'movie' | 'tv'; canExpand?: boolean; isAddedToWatchlist?: boolean; + mutateParent?: () => void; } const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { @@ -25,6 +26,7 @@ const TmdbTitleCard = ({ type, canExpand, isAddedToWatchlist = false, + mutateParent, }: TmdbTitleCardProps) => { const { hasPermission } = useUser(); @@ -71,6 +73,7 @@ const TmdbTitleCard = ({ year={title.releaseDate} mediaType={'movie'} canExpand={canExpand} + mutateParent={mutateParent} /> ) : ( ); }; diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx index 410036cca..30a62c16e 100644 --- a/src/components/TitleCard/index.tsx +++ b/src/components/TitleCard/index.tsx @@ -38,6 +38,7 @@ interface TitleCardProps { canExpand?: boolean; inProgress?: boolean; isAddedToWatchlist?: number | boolean; + mutateParent?: () => void; } const messages = defineMessages({ @@ -61,6 +62,7 @@ const TitleCard = ({ isAddedToWatchlist = false, inProgress = false, canExpand = false, + mutateParent, }: TitleCardProps) => { const isTouch = useIsTouch(); const intl = useIntl(); @@ -148,6 +150,9 @@ const TitleCard = ({ } finally { setIsUpdating(false); mutate('/api/v1/discover/watchlist'); + if (mutateParent) { + mutateParent(); + } setToggleWatchlist((prevState) => !prevState); } }; diff --git a/src/hooks/useDiscover.ts b/src/hooks/useDiscover.ts index f9aff8e29..2a2acd02f 100644 --- a/src/hooks/useDiscover.ts +++ b/src/hooks/useDiscover.ts @@ -25,6 +25,7 @@ interface DiscoverResult { error: unknown; titles: T[]; firstResultData?: BaseSearchResult & S; + mutate?: () => void; } const extraEncodes: [RegExp, string][] = [ @@ -54,7 +55,7 @@ const useDiscover = < { hideAvailable = true } = {} ): DiscoverResult => { const settings = useSettings(); - const { data, error, size, setSize, isValidating } = useSWRInfinite< + const { data, error, size, setSize, isValidating, mutate } = useSWRInfinite< BaseSearchResult & S >( (pageIndex: number, previousPageData) => { @@ -119,6 +120,7 @@ const useDiscover = < error, titles, firstResultData: data?.[0], + mutate, }; }; diff --git a/tailwind.config.js b/tailwind.config.js index e94cb96c5..b8b70fd54 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -3,7 +3,6 @@ const defaultTheme = require('tailwindcss/defaultTheme'); /** @type {import('tailwindcss').Config} */ module.exports = { - important: true, mode: 'jit', content: [ './node_modules/react-tailwindcss-datepicker-sct/dist/index.esm.js',