import { proxyRefs, reactive, toRefs } from 'vue'; import { useStorage } from '@vueuse/core'; import type { ConfigState } from '~/types/config'; import type { DLField } from '~/types/dl_fields'; import type { Preset } from '~/types/presets'; import type { ConfigFeature, ConfigUpdateAction } from '~/types/sockets'; import { useQueueState } from '~/composables/useQueueState'; import { request } from '~/utils'; let last_reload = 0; const CONFIG_TTL = 10; const state = reactive({ showForm: useStorage('showForm', true), app: { download_path: '/downloads', remove_files: false, output_template: '', ytdlp_version: '', max_workers: 20, max_workers_per_extractor: 2, default_preset: 'default', instance_title: null, console_enabled: false, browser_control_enabled: false, file_logging: false, is_native: false, app_version: '', app_commit_sha: '', app_build_date: '', app_branch: '', started: 0, app_env: 'production', simple_mode: false, default_pagination: 50, log_level: '', runtime_log_level: '', check_for_updates: true, new_version: '', yt_new_version: '', }, presets: [ { name: 'default', description: 'Default preset', folder: '', template: '', cookies: '', cli: '', default: true, priority: 0, }, ], dl_fields: [], folders: [], ytdlp_options: [], paused: false, is_loaded: false, is_loading: false, }); const loadConfig = async (force: boolean = false) => { if (state.is_loading) { return; } const now = Date.now(); if (state.is_loaded && !force && last_reload > 0) { const age = (now - last_reload) / 1000; if (age < CONFIG_TTL) { return; } } state.is_loaded = false; state.is_loading = true; try { const resp = await request('/api/system/configuration', { timeout: 10 }); if (!resp.ok) { return; } const data = await resp.json(); const queueState = useQueueState(); if ('number' === typeof data.history_count) { delete data.history_count; } if (data.queue) { queueState.addAll(data.queue); delete data.queue; } setAll(data); state.is_loaded = true; last_reload = now; } catch (e: any) { console.error(`Failed to load configuration: ${e}`); } finally { state.is_loading = false; } }; const add = (key: string, value: any) => { if (key.includes('.')) { const [parentKey, subKey] = key.split('.') as [keyof ConfigState, string]; state[parentKey][subKey] = value; return; } (state as any)[key] = value; }; const get = (key: string, defaultValue: any = null): any => { if (key.includes('.')) { const [parentKey, subKey] = key.split('.') as [keyof ConfigState, string]; const parent = state[parentKey] as any; return parent?.[subKey] ?? defaultValue; } return (state as any)[key] ?? defaultValue; }; const isLoaded = () => state.is_loaded; const update = add; const getAll = (): ConfigState => state; const setAll = (data: Record) => { Object.keys(data).forEach((key) => { if (key.includes('.')) { const [parentKey, subKey] = key.split('.') as [keyof ConfigState, string]; const parent = state[parentKey] as any; parent[subKey] = data[key]; return; } (state as any)[key] = data[key]; }); state.is_loaded = true; }; const patch = (feature: ConfigFeature, action: ConfigUpdateAction, data: unknown): void => { const supportedFeatures: ConfigFeature[] = ['dl_fields', 'presets']; if (!supportedFeatures.includes(feature)) { return; } if ('presets' === feature) { const item = data as Preset; const current = get(feature, []) as Array; if ('create' === action) { current.push(item); return; } if ('delete' === action) { const index = current.findIndex((i) => i.id === item.id); if (-1 !== index) { current.splice(index, 1); } return; } if ('update' === action) { const target = current.find((i) => i.id === item.id); if (target) { Object.assign(target, item); } return; } return; } if ('dl_fields' === feature) { const item = data as DLField; const current = get(feature, []) as Array; if ('create' === action) { current.push(item); return; } if ('delete' === action) { const index = current.findIndex((i) => i.id === item.id); if (-1 !== index) { current.splice(index, 1); } return; } if ('update' === action) { const target = current.find((i) => i.id === item.id); if (target) { Object.assign(target, item); } return; } if ('replace' === action) { state.dl_fields = data as Array; } } }; const ytpConfigApi = proxyRefs({ ...toRefs(state), add, get, update, getAll, setAll, isLoaded, patch, loadConfig, }); export const useYtpConfig = () => ytpConfigApi;