diff --git a/app.py b/app.py index 6563056..511b344 100755 --- a/app.py +++ b/app.py @@ -51,7 +51,6 @@ if _umask_value: # Defer logging setup; avoid failing on invalid UMASK pass - # Import and initialize routes (this will start the watch manager) from routes.auth.credentials import router as credentials_router # noqa: E402 from routes.auth.auth import router as auth_router # noqa: E402 @@ -65,14 +64,8 @@ from routes.core.history import router as history_router # noqa: E402 from routes.system.progress import router as prgs_router # noqa: E402 from routes.system.config import router as config_router # noqa: E402 - -# Import Celery configuration and manager from routes.utils.celery_config import REDIS_URL # noqa: E402 -# Import authentication system - -# Import watch manager controls (start/stop) without triggering side effects - # Configure application-wide logging def setup_logging(): diff --git a/spotizerr-ui/src/components/config/ServerTab.tsx b/spotizerr-ui/src/components/config/ServerTab.tsx index fba5861..37e2e17 100644 --- a/spotizerr-ui/src/components/config/ServerTab.tsx +++ b/spotizerr-ui/src/components/config/ServerTab.tsx @@ -110,6 +110,76 @@ function SpotifyApiForm() { ); } +function UtilityConcurrencyForm() { + const queryClient = useQueryClient(); + const { data: configData, isLoading } = useQuery({ + queryKey: ["config"], + queryFn: () => authApiClient.getConfig(), + }); + + const { register, handleSubmit, reset, formState: { isDirty } } = useForm<{ utilityConcurrency: number }>(); + + useEffect(() => { + if (configData) { + reset({ utilityConcurrency: Number(configData.utilityConcurrency ?? 1) }); + } + }, [configData, reset]); + + const mutation = useMutation({ + mutationFn: (payload: { utilityConcurrency: number }) => authApiClient.updateConfig(payload), + onSuccess: () => { + toast.success("Utility worker concurrency saved!"); + queryClient.invalidateQueries({ queryKey: ["config"] }); + }, + onError: (e) => { + toast.error(`Failed to save: ${(e as any).message}`); + }, + }); + + const onSubmit = (values: { utilityConcurrency: number }) => { + const value = Math.max(1, Number(values.utilityConcurrency || 1)); + mutation.mutate({ utilityConcurrency: value }); + }; + + if (isLoading) return

Loading server settings...

; + + return ( +
+
+
+ +
+
+ +
+ + +

Controls concurrency of the utility Celery worker. Minimum 1.

+
+
+ ); +} + +// --- Components --- function WebhookForm() { const queryClient = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ["webhookConfig"], queryFn: fetchWebhookConfig }); @@ -223,6 +293,12 @@ export function ServerTab() {
+
+

Utility Worker

+

Tune background utility worker concurrency for low-powered systems.

+ +
+

Webhooks

diff --git a/spotizerr-ui/src/contexts/SettingsProvider.tsx b/spotizerr-ui/src/contexts/SettingsProvider.tsx index cc76843..4f45ab3 100644 --- a/spotizerr-ui/src/contexts/SettingsProvider.tsx +++ b/spotizerr-ui/src/contexts/SettingsProvider.tsx @@ -32,6 +32,7 @@ export type FlatAppSettings = { deezer: string; deezerQuality: "MP3_128" | "MP3_320" | "FLAC"; maxConcurrentDownloads: number; + utilityConcurrency: number; realTime: boolean; fallback: boolean; convertTo: "MP3" | "AAC" | "OGG" | "OPUS" | "FLAC" | "WAV" | "ALAC" | ""; @@ -72,6 +73,7 @@ const defaultSettings: FlatAppSettings = { deezer: "", deezerQuality: "MP3_128", maxConcurrentDownloads: 3, + utilityConcurrency: 1, realTime: false, fallback: false, convertTo: "", @@ -135,6 +137,7 @@ const fetchSettings = async (): Promise => { // Ensure required frontend-only fields exist recursiveQuality: Boolean((camelData as any).recursiveQuality ?? false), realTimeMultiplier: Number((camelData as any).realTimeMultiplier ?? 0), + utilityConcurrency: Number((camelData as any).utilityConcurrency ?? 1), // Ensure watch subkeys default if missing watch: { ...(camelData.watch as any), diff --git a/spotizerr-ui/src/contexts/settings-context.ts b/spotizerr-ui/src/contexts/settings-context.ts index 4b1c9df..ee3ec6b 100644 --- a/spotizerr-ui/src/contexts/settings-context.ts +++ b/spotizerr-ui/src/contexts/settings-context.ts @@ -8,6 +8,7 @@ export interface AppSettings { deezer: string; deezerQuality: "MP3_128" | "MP3_320" | "FLAC"; maxConcurrentDownloads: number; + utilityConcurrency: number; realTime: boolean; fallback: boolean; convertTo: "MP3" | "AAC" | "OGG" | "OPUS" | "FLAC" | "WAV" | "ALAC" | ""; diff --git a/spotizerr-ui/src/lib/api-client.ts b/spotizerr-ui/src/lib/api-client.ts index eac7711..c5d85fd 100644 --- a/spotizerr-ui/src/lib/api-client.ts +++ b/spotizerr-ui/src/lib/api-client.ts @@ -369,6 +369,17 @@ class AuthApiClient { get client() { return this.apiClient; } + + // General config helpers + async getConfig(): Promise { + const response = await this.apiClient.get("/config"); + return response.data; + } + + async updateConfig(partial: Record): Promise { + const response = await this.apiClient.put("/config", partial); + return response.data; + } } // Create and export a singleton instance