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