Merge branch 'develop' into custom-jellyfin-password-reset

This commit is contained in:
fallenbagel
2023-11-30 09:26:14 +05:00
10 changed files with 61 additions and 24 deletions

View File

@@ -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(),
});
}

View File

@@ -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,
});

View File

@@ -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();

View File

@@ -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}
/>
</li>
);

View File

@@ -30,6 +30,7 @@ const DiscoverWatchlist = () => {
titles,
fetchMore,
error,
mutate,
} = useDiscover<WatchlistItem>(
`/api/v1/${
router.pathname.startsWith('/profile')
@@ -76,6 +77,7 @@ const DiscoverWatchlist = () => {
}
isReachingEnd={isReachingEnd}
onScrollBottom={fetchMore}
mutateParent={mutate}
/>
</>
);

View File

@@ -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<TestResponse>({
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)}
</label>
<div className="form-input-area">
<div
className={`form-input-area ${
settings.currentSettings.mediaServerType ===
MediaServerType.JELLYFIN ||
settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? 'opacity-50'
: 'opacity-100'
}`}
>
<Field
type="checkbox"
id="enableSeasonFolders"
name="enableSeasonFolders"
disabled={
settings.currentSettings.mediaServerType !==
MediaServerType.PLEX
}
/>
</div>
</div>

View File

@@ -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}
/>
) : (
<TitleCard
@@ -87,6 +90,7 @@ const TmdbTitleCard = ({
year={title.firstAirDate}
mediaType={'tv'}
canExpand={canExpand}
mutateParent={mutateParent}
/>
);
};

View File

@@ -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);
}
};

View File

@@ -25,6 +25,7 @@ interface DiscoverResult<T, S> {
error: unknown;
titles: T[];
firstResultData?: BaseSearchResult<T> & S;
mutate?: () => void;
}
const extraEncodes: [RegExp, string][] = [
@@ -54,7 +55,7 @@ const useDiscover = <
{ hideAvailable = true } = {}
): DiscoverResult<T, S> => {
const settings = useSettings();
const { data, error, size, setSize, isValidating } = useSWRInfinite<
const { data, error, size, setSize, isValidating, mutate } = useSWRInfinite<
BaseSearchResult<T> & S
>(
(pageIndex: number, previousPageData) => {
@@ -119,6 +120,7 @@ const useDiscover = <
error,
titles,
firstResultData: data?.[0],
mutate,
};
};

View File

@@ -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',