diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index b13ff285..8596dafb 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -20,7 +20,7 @@ import { getRoot } from "../ioc.js"; import AbstractScrobbleClient from "../scrobblers/AbstractScrobbleClient.js"; import AbstractSource from "../sources/AbstractSource.js"; import MemorySource from "../sources/MemorySource.js"; -import { sortByNewestPlayDate } from "../utils.js"; +import { parseBool, sortByNewestPlayDate } from "../utils.js"; import { setupAuthRoutes } from "./auth.js"; import { setupDeezerRoutes } from "./deezerRoutes.js"; import { setupJellyfinRoutes } from "./jellyfinRoutes.js"; @@ -417,26 +417,31 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: return res.json(result); }); - app.use('/api/poll', sourceRequiredMiddle); - app.getAsync('/api/poll', async (req, res) => { + app.use('/api/source/init', sourceRequiredMiddle); + app.postAsync('/api/source/init', async (req, res) => { // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message const source = req.scrobbleSource as AbstractSource; - source.logger.verbose('User requested (re)start via API call'); - if (!source.canPoll) { - source.logger.warn(`Does not support polling (${source.type})`); - return res.status(400).send(`Specified source cannot poll (${source.type})`); - } + const { + query: { + force: forceQ = false + } + } = req; + + const force = parseBool(forceQ, false); + + source.logger.verbose(`User requested${force ? ' a FORCED' :''} (re)init via API call`); res.status(200).send('OK'); + if(source.polling) { source.logger.info('Source is already polling! Restarting polling...'); const stopRes = await source.tryStopPolling(); if(stopRes === true) { - source.poll(); + source.poll({force, notify: false}).catch(e => source.logger.error(e)); } } else { - source.poll(); + source.poll({force, notify: false}).catch(e => source.logger.error(e)); } }); @@ -444,7 +449,16 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: app.postAsync('/api/client/init', async (req, res) => { // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message const client = req.scrobbleClient as AbstractScrobbleClient; - client.logger.verbose('User requested (re)start via API call'); + + const { + query: { + force: forceQ = false + } + } = req; + + const force = parseBool(forceQ, false); + + client.logger.verbose(`User requested${force ? ' a FORCED' :''} (re)init via API call`); client.logger.info('Checking (and trying) to stop scrobbler if already running...'); if(false === (await client.tryStopScrobbling())) { @@ -452,7 +466,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: } client.logger.info('Trying to start scrobbler...'); - await client.initScrobbleMonitoring(); + client.initScrobbleMonitoring({force, notify: false}).catch(e => client.logger.error(e)); res.status(200).send('OK'); }); diff --git a/src/client/components/statusCard/ClientStatusCard.tsx b/src/client/components/statusCard/ClientStatusCard.tsx index 8d48f937..3909714c 100644 --- a/src/client/components/statusCard/ClientStatusCard.tsx +++ b/src/client/components/statusCard/ClientStatusCard.tsx @@ -40,11 +40,15 @@ const ClientStatusCard = (props: ClientStatusCardData) => { const [startClientPut, startResult] = useStartClientMutation(); - const tryStart = useCallback((name: string) => startClientPut({name}), [startClientPut]); + const tryStart = useCallback((name: string, force?: boolean) => startClientPut({name, force}), [startClientPut]); let header: string | undefined = display; let body = ; - const startClientElement =
tryStart(name)} className="capitalize underline cursor-pointer">{status === 'Running' ? 'Restart' : 'Start'}
+ const startClientElement = ( + +
tryStart(name)} className="capitalize underline cursor-pointer inline mr-1">{status === 'Running' ? 'Restart' : 'Start'}
+ (
tryStart(name, true)} className="capitalize underline cursor-pointer inline">Force
) +
) if(data !== undefined) { const { hasAuth, diff --git a/src/client/components/statusCard/SourceStatusCard.tsx b/src/client/components/statusCard/SourceStatusCard.tsx index 475ae06e..e8c71ff7 100644 --- a/src/client/components/statusCard/SourceStatusCard.tsx +++ b/src/client/components/statusCard/SourceStatusCard.tsx @@ -6,6 +6,7 @@ import {sourceAdapter} from "../../status/ducks"; import {RootState} from "../../store"; import {connect, ConnectedProps} from "react-redux"; import Player from "../player/Player"; +import {useStartSourceMutation} from "./sourceDucks"; import './statusCard.scss'; export interface SourceStatusCardData extends StatusCardSkeletonData, PropsFromRedux { @@ -30,18 +31,25 @@ const SourceStatusCard = (props: SourceStatusCardData) => { data: { display, name, + type, status, } = {} } = props; let header: string | undefined = display; let body = ; - const poll = useCallback(async () => { - const params = new URLSearchParams({type: data.type, name: data.name}); - await fetch(`/api/poll?${params}`, { - method: 'GET', - }); - },[data]); - let startSourceElement = null; + + const [startPut, startResult] = useStartSourceMutation(); + + const tryStart = useCallback((name: string, type: string, force?: boolean) => startPut({name, type, force}), [startPut]); + + let startSourceElement = ( +
tryStart(name, type)} + className="capitalize underline cursor-pointer inline mr-1">{status === 'Polling' ? 'Restart' : 'Start'} +
+ (
tryStart(name, type, true)} + className="capitalize underline cursor-pointer inline">Force +
) +
); if(data !== undefined) { const { @@ -71,10 +79,6 @@ const SourceStatusCard = (props: SourceStatusCardData) => { upstreamRecent =
See Recent from Source API
; } - if((!hasAuth || authed) && canPoll) { - startSourceElement =
{status === 'Polling' ? 'Restart' : 'Start'}
- } - // TODO links body = (
{platformIds.map(x => )} diff --git a/src/client/components/statusCard/clientDucks.ts b/src/client/components/statusCard/clientDucks.ts index ea90e2a5..2a3ce5e6 100644 --- a/src/client/components/statusCard/clientDucks.ts +++ b/src/client/components/statusCard/clientDucks.ts @@ -5,13 +5,15 @@ export const scrobblerApi = createApi({ baseQuery: fetchBaseQuery({baseUrl: '/api/'}), endpoints: (builder) => ({ startClient: builder.mutation({ query: (params) => ({ url: '/client/init', method: 'POST', params: { - name: params.name + name: params.name, + force: params.force } }) }) diff --git a/src/client/components/statusCard/sourceDucks.ts b/src/client/components/statusCard/sourceDucks.ts new file mode 100644 index 00000000..538bb825 --- /dev/null +++ b/src/client/components/statusCard/sourceDucks.ts @@ -0,0 +1,25 @@ +import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/dist/query/react"; + +export const sourceApi = createApi({ + reducerPath: 'sourceApi', + baseQuery: fetchBaseQuery({baseUrl: '/api/'}), + endpoints: (builder) => ({ + startSource: builder.mutation({ + query: (params) => ({ + url: '/source/init', + method: 'POST', + params: { + name: params.name, + type: params.type, + force: params.force + } + }) + }) + }) +}); + +export const {useStartSourceMutation} = sourceApi; diff --git a/src/client/store.ts b/src/client/store.ts index 5b6a1542..7a7afcf5 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -2,6 +2,7 @@ import { configureStore } from '@reduxjs/toolkit' // Or from '@reduxjs/toolkit/query/react' import { setupListeners } from '@reduxjs/toolkit/query' import { scrobblerApi } from './components/statusCard/clientDucks'; +import { sourceApi } from './components/statusCard/sourceDucks'; import { deadApi, deadSlice } from "./deadLetter/deadLetterDucks"; import { logsReducer } from "./logs/logDucks"; import { logsApi } from "./logs/logsApi"; @@ -20,6 +21,7 @@ export const store = configureStore({ [deadApi.reducerPath]: deadApi.reducer, [scrobbledApi.reducerPath]: scrobbledApi.reducer, [scrobblerApi.reducerPath]: scrobblerApi.reducer, + [sourceApi.reducerPath]: sourceApi.reducer, [versionApi.reducerPath]: versionApi.reducer, //parts: statusReducer clients: clientSlice.reducer, @@ -30,7 +32,7 @@ export const store = configureStore({ // Adding the api middleware enables caching, invalidation, polling, // and other useful features of `rtk-query`. middleware: (getDefaultMiddleware) => - getDefaultMiddleware().concat([statusApi.middleware, logsApi.middleware, recentApi.middleware, scrobbledApi.middleware, deadApi.middleware, scrobblerApi.middleware, versionApi.middleware]), + getDefaultMiddleware().concat([statusApi.middleware, logsApi.middleware, recentApi.middleware, scrobbledApi.middleware, deadApi.middleware, scrobblerApi.middleware, sourceApi.middleware, versionApi.middleware]), }) // optional, but required for refetchOnFocus/refetchOnReconnect behaviors