fix(stats): start stats server on background app launch (#144)

This commit is contained in:
2026-07-07 00:16:57 -07:00
committed by GitHub
parent 38ddb29aa0
commit 0a58c20ad7
12 changed files with 185 additions and 1 deletions
+40
View File
@@ -374,6 +374,46 @@ test('handleCliCommand processes --start for second-instance when overlay runtim
);
});
test('handleCliCommand ensures background stats server for initial --start --background', () => {
const ensured: number[] = [];
const { deps } = createDeps({
ensureBackgroundStatsServer: () => {
ensured.push(1);
},
});
handleCliCommand(makeArgs({ start: true, background: true }), 'initial', deps);
assert.equal(ensured.length, 1);
});
test('handleCliCommand ensures background stats server for second-instance --start --background', () => {
const ensured: number[] = [];
const { deps } = createDeps({
isOverlayRuntimeInitialized: () => true,
ensureBackgroundStatsServer: () => {
ensured.push(1);
},
});
handleCliCommand(makeArgs({ start: true, background: true }), 'second-instance', deps);
assert.equal(ensured.length, 1);
});
test('handleCliCommand does not ensure background stats server for foreground --start', () => {
const ensured: number[] = [];
const { deps } = createDeps({
ensureBackgroundStatsServer: () => {
ensured.push(1);
},
});
handleCliCommand(makeArgs({ start: true }), 'initial', deps);
assert.equal(ensured.length, 0);
});
test('handleCliCommand forces setup open for second-instance setup command', () => {
const { deps, calls } = createDeps();
+7
View File
@@ -106,6 +106,7 @@ export interface CliCommandServiceDeps {
mode: NonNullable<CliArgs['youtubeMode']>;
source: CliCommandSource;
}) => Promise<void>;
ensureBackgroundStatsServer?: () => void;
printHelp: () => void;
hasMainWindow: () => boolean;
getMultiCopyTimeoutMs: () => number;
@@ -185,6 +186,7 @@ interface AnilistCliRuntime {
interface AppCliRuntime {
stop: () => void;
hasMainWindow: () => boolean;
ensureBackgroundStatsServer?: () => void;
runUpdateCommand: CliCommandServiceDeps['runUpdateCommand'];
runEnsureLinuxRuntimePluginAssetsCommand: CliCommandServiceDeps['runEnsureLinuxRuntimePluginAssetsCommand'];
runYoutubePlaybackFlow: CliCommandServiceDeps['runYoutubePlaybackFlow'];
@@ -299,6 +301,7 @@ export function createCliCommandDepsRuntime(
runUpdateCommand: options.app.runUpdateCommand,
runEnsureLinuxRuntimePluginAssetsCommand: options.app.runEnsureLinuxRuntimePluginAssetsCommand,
runYoutubePlaybackFlow: options.app.runYoutubePlaybackFlow,
ensureBackgroundStatsServer: options.app.ensureBackgroundStatsServer,
printHelp: options.ui.printHelp,
hasMainWindow: options.app.hasMainWindow,
getMultiCopyTimeoutMs: options.getMultiCopyTimeoutMs,
@@ -393,6 +396,10 @@ export function handleCliCommand(
deps.log(`Starting MPV IPC connection on socket: ${socketPath}`);
}
if (args.start && args.background) {
deps.ensureBackgroundStatsServer?.();
}
if (args.sessionAction) {
dispatchCliSessionAction(
args.sessionAction,
+10
View File
@@ -37,6 +37,7 @@ import { createAniSkipRuntime } from './main/runtime/aniskip-runtime';
import { resolveAniSkipMetadataForFile } from './main/runtime/aniskip-metadata';
import { createDiscordRpcClient } from './main/runtime/discord-rpc-client.js';
import { startAppControlServer } from './main/runtime/app-control-server';
import { createEnsureBackgroundStatsServerHandler } from './main/runtime/background-stats-startup';
import {
markJellyfinRemotePlaybackLoaded as markJellyfinRemotePlaybackLoadedState,
shouldAutoLoadSecondarySubTrackForJellyfinPlayback,
@@ -4069,6 +4070,14 @@ const statsStartupRuntime = {
}
},
} as const;
const ensureBackgroundStatsServer = createEnsureBackgroundStatsServerHandler({
isStatsAutoStartEnabled: () => getResolvedConfig().stats.autoStartServer,
isImmersionTrackingEnabled: () => getResolvedConfig().immersionTracking?.enabled !== false,
ensureBackgroundStatsServerStarted: () =>
statsStartupRuntime.ensureBackgroundStatsServerStarted(),
logInfo: (message) => logger.info(message),
logWarn: (message, error) => logger.warn(message, error),
});
const runStatsCliCommand = createRunStatsCliCommandHandler({
getResolvedConfig: () => getResolvedConfig(),
@@ -5965,6 +5974,7 @@ const { handleCliCommand, handleInitialArgs } = composeCliStartupHandlers({
);
},
runYoutubePlaybackFlow: (request) => youtubePlaybackRuntime.runYoutubePlaybackFlow(request),
ensureBackgroundStatsServer: () => ensureBackgroundStatsServer(),
openYomitanSettings: () => openYomitanSettings(),
openConfigSettingsWindow: () => openConfigSettingsWindow(),
cycleSecondarySubMode: () => handleCycleSecondarySubMode(),
+2
View File
@@ -47,6 +47,7 @@ export interface CliCommandRuntimeServiceContext {
runUpdateCommand: CliCommandRuntimeServiceDepsParams['app']['runUpdateCommand'];
runEnsureLinuxRuntimePluginAssetsCommand: CliCommandRuntimeServiceDepsParams['app']['runEnsureLinuxRuntimePluginAssetsCommand'];
runYoutubePlaybackFlow: CliCommandRuntimeServiceDepsParams['app']['runYoutubePlaybackFlow'];
ensureBackgroundStatsServer?: CliCommandRuntimeServiceDepsParams['app']['ensureBackgroundStatsServer'];
openYomitanSettings: () => void;
openConfigSettingsWindow: () => void;
cycleSecondarySubMode: () => void;
@@ -124,6 +125,7 @@ function createCliCommandDepsFromContext(
app: {
stop: context.stopApp,
hasMainWindow: context.hasMainWindow,
ensureBackgroundStatsServer: context.ensureBackgroundStatsServer,
runUpdateCommand: context.runUpdateCommand,
runEnsureLinuxRuntimePluginAssetsCommand: context.runEnsureLinuxRuntimePluginAssetsCommand,
runYoutubePlaybackFlow: context.runYoutubePlaybackFlow,
+2
View File
@@ -200,6 +200,7 @@ export interface CliCommandRuntimeServiceDepsParams {
app: {
stop: CliCommandDepsRuntimeOptions['app']['stop'];
hasMainWindow: CliCommandDepsRuntimeOptions['app']['hasMainWindow'];
ensureBackgroundStatsServer?: CliCommandDepsRuntimeOptions['app']['ensureBackgroundStatsServer'];
runUpdateCommand: CliCommandDepsRuntimeOptions['app']['runUpdateCommand'];
runEnsureLinuxRuntimePluginAssetsCommand: CliCommandDepsRuntimeOptions['app']['runEnsureLinuxRuntimePluginAssetsCommand'];
runYoutubePlaybackFlow: CliCommandDepsRuntimeOptions['app']['runYoutubePlaybackFlow'];
@@ -402,6 +403,7 @@ export function createCliCommandRuntimeServiceDeps(
app: {
stop: params.app.stop,
hasMainWindow: params.app.hasMainWindow,
ensureBackgroundStatsServer: params.app.ensureBackgroundStatsServer,
runUpdateCommand: params.app.runUpdateCommand,
runEnsureLinuxRuntimePluginAssetsCommand: params.app.runEnsureLinuxRuntimePluginAssetsCommand,
runYoutubePlaybackFlow: params.app.runYoutubePlaybackFlow,
@@ -0,0 +1,78 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createEnsureBackgroundStatsServerHandler } from './background-stats-startup';
function createDeps(
overrides: Partial<Parameters<typeof createEnsureBackgroundStatsServerHandler>[0]> = {},
) {
const calls: string[] = [];
const deps: Parameters<typeof createEnsureBackgroundStatsServerHandler>[0] = {
isStatsAutoStartEnabled: () => true,
isImmersionTrackingEnabled: () => true,
ensureBackgroundStatsServerStarted: () => {
calls.push('ensureBackgroundStatsServerStarted');
return { url: 'http://127.0.0.1:3888', runningInCurrentProcess: true };
},
logInfo: (message) => {
calls.push(`info:${message}`);
},
logWarn: (message) => {
calls.push(`warn:${message}`);
},
...overrides,
};
return { deps, calls };
}
test('ensures background stats server and logs local startup', () => {
const { deps, calls } = createDeps();
createEnsureBackgroundStatsServerHandler(deps)();
assert.ok(calls.includes('ensureBackgroundStatsServerStarted'));
assert.ok(
calls.some((value) => value.startsWith('info:') && value.includes('http://127.0.0.1:3888')),
);
});
test('logs reuse when a background stats server is already running', () => {
const { deps, calls } = createDeps({
ensureBackgroundStatsServerStarted: () => ({
url: 'http://127.0.0.1:3888',
runningInCurrentProcess: false,
}),
});
createEnsureBackgroundStatsServerHandler(deps)();
assert.ok(
calls.some((value) => value.startsWith('info:') && /already running|reusing/i.test(value)),
);
});
test('skips when stats.autoStartServer is disabled', () => {
const { deps, calls } = createDeps({ isStatsAutoStartEnabled: () => false });
createEnsureBackgroundStatsServerHandler(deps)();
assert.equal(calls.includes('ensureBackgroundStatsServerStarted'), false);
});
test('skips when immersion tracking is disabled', () => {
const { deps, calls } = createDeps({ isImmersionTrackingEnabled: () => false });
createEnsureBackgroundStatsServerHandler(deps)();
assert.equal(calls.includes('ensureBackgroundStatsServerStarted'), false);
});
test('logs a warning instead of throwing when startup fails', () => {
const { deps, calls } = createDeps({
ensureBackgroundStatsServerStarted: () => {
throw new Error('port in use');
},
});
assert.doesNotThrow(() => createEnsureBackgroundStatsServerHandler(deps)());
assert.ok(calls.some((value) => value.startsWith('warn:')));
});
@@ -0,0 +1,35 @@
export interface EnsureBackgroundStatsServerDeps {
isStatsAutoStartEnabled: () => boolean;
isImmersionTrackingEnabled: () => boolean;
ensureBackgroundStatsServerStarted: () => {
url: string;
runningInCurrentProcess: boolean;
};
logInfo: (message: string) => void;
logWarn: (message: string, error?: unknown) => void;
}
export function createEnsureBackgroundStatsServerHandler(
deps: EnsureBackgroundStatsServerDeps,
): () => void {
return () => {
if (!deps.isStatsAutoStartEnabled()) {
deps.logInfo('Background start: stats.autoStartServer is disabled; skipping stats server.');
return;
}
if (!deps.isImmersionTrackingEnabled()) {
deps.logInfo('Background start: immersion tracking is disabled; skipping stats server.');
return;
}
try {
const result = deps.ensureBackgroundStatsServerStarted();
deps.logInfo(
result.runningInCurrentProcess
? `Background start: stats server started at ${result.url}.`
: `Background start: stats server already running at ${result.url}; skipping.`,
);
} catch (error) {
deps.logWarn('Background start: failed to start stats server.', error);
}
};
}
@@ -45,6 +45,7 @@ export function createBuildCliCommandContextDepsHandler(deps: {
runUpdateCommand: CliCommandContextFactoryDeps['runUpdateCommand'];
runEnsureLinuxRuntimePluginAssetsCommand: CliCommandContextFactoryDeps['runEnsureLinuxRuntimePluginAssetsCommand'];
runYoutubePlaybackFlow: CliCommandContextFactoryDeps['runYoutubePlaybackFlow'];
ensureBackgroundStatsServer?: CliCommandContextFactoryDeps['ensureBackgroundStatsServer'];
openYomitanSettings: () => void;
openConfigSettingsWindow: () => void;
cycleSecondarySubMode: () => void;
@@ -103,6 +104,7 @@ export function createBuildCliCommandContextDepsHandler(deps: {
runUpdateCommand: deps.runUpdateCommand,
runEnsureLinuxRuntimePluginAssetsCommand: deps.runEnsureLinuxRuntimePluginAssetsCommand,
runYoutubePlaybackFlow: deps.runYoutubePlaybackFlow,
ensureBackgroundStatsServer: deps.ensureBackgroundStatsServer,
openYomitanSettings: deps.openYomitanSettings,
openConfigSettingsWindow: deps.openConfigSettingsWindow,
cycleSecondarySubMode: deps.cycleSecondarySubMode,
@@ -61,6 +61,7 @@ export function createBuildCliCommandContextMainDepsHandler(deps: {
source: CliCommandSource,
) => Promise<void>;
runYoutubePlaybackFlow: CliCommandContextFactoryDeps['runYoutubePlaybackFlow'];
ensureBackgroundStatsServer?: CliCommandContextFactoryDeps['ensureBackgroundStatsServer'];
openYomitanSettings: () => void;
openConfigSettingsWindow: () => void;
@@ -140,6 +141,7 @@ export function createBuildCliCommandContextMainDepsHandler(deps: {
runEnsureLinuxRuntimePluginAssetsCommand: (args: CliArgs, source: CliCommandSource) =>
deps.runEnsureLinuxRuntimePluginAssetsCommand(args, source),
runYoutubePlaybackFlow: (request) => deps.runYoutubePlaybackFlow(request),
ensureBackgroundStatsServer: deps.ensureBackgroundStatsServer,
openYomitanSettings: () => deps.openYomitanSettings(),
openConfigSettingsWindow: () => deps.openConfigSettingsWindow(),
cycleSecondarySubMode: () => deps.cycleSecondarySubMode(),
+2
View File
@@ -50,6 +50,7 @@ export type CliCommandContextFactoryDeps = {
runUpdateCommand: CliCommandRuntimeServiceContext['runUpdateCommand'];
runEnsureLinuxRuntimePluginAssetsCommand: CliCommandRuntimeServiceContext['runEnsureLinuxRuntimePluginAssetsCommand'];
runYoutubePlaybackFlow: CliCommandRuntimeServiceContext['runYoutubePlaybackFlow'];
ensureBackgroundStatsServer?: CliCommandRuntimeServiceContext['ensureBackgroundStatsServer'];
openYomitanSettings: () => void;
openConfigSettingsWindow: () => void;
cycleSecondarySubMode: () => void;
@@ -130,6 +131,7 @@ export function createCliCommandContext(
runUpdateCommand: deps.runUpdateCommand,
runEnsureLinuxRuntimePluginAssetsCommand: deps.runEnsureLinuxRuntimePluginAssetsCommand,
runYoutubePlaybackFlow: deps.runYoutubePlaybackFlow,
ensureBackgroundStatsServer: deps.ensureBackgroundStatsServer,
openYomitanSettings: deps.openYomitanSettings,
openConfigSettingsWindow: deps.openConfigSettingsWindow,
cycleSecondarySubMode: deps.cycleSecondarySubMode,