mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
6fcf2259f6
Four-cut cluster pulling the remaining lifecycle code out of DeviceSync.tsx. 928 → 638 LOC (−290). useDeviceSyncBrowser — playlists / randomAlbums / artists state + their three loaders + the tab-switch useEffect that lazy-loads on first visit + the 300 ms debounced album-search useEffect + the expandedArtistIds / artistAlbumsMap / loadingArtistIds state with toggleArtistExpand. Takes activeTab + search + a resetSearch callback (so the tab-switch effect can clear the search input the page still owns). useDeviceSyncDeviceScan — scanDevice useCallback + the on-mount useEffect + the auto-import-manifest useEffect (with the manifestImportedRef gate so it only fires once per drive plug-in) + the clean-on-unplug useEffect that clears deviceFilePaths and resets the import flag. useDeviceSyncJobEvents — the device:sync:progress and device:sync:complete event listeners. Complete handler dispatches the toast, writes the manifest, generates per-playlist m3u8 files (through fetchTracksForSource + trackToSyncInfo), and triggers scanDevice. Cancelled state is preserved by re-calling useDeviceSyncJobStore.cancel() after complete(). runDeviceSyncChooseFolder — the openDialog → setTargetDir → optional manifest auto-import → scanDevice timer flow. DeviceSync drops every direct import that those hooks now own (getPlaylists, getArtists, getArtist, getAlbumList, searchSubsonic, listen, openDialog, useEffect, useRef, SubsonicPlaylist / SubsonicArtist / SubsonicAlbum type imports). Pure code move otherwise — no behaviour change.
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
import { useCallback, useEffect, useRef } from 'react';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import type { TFunction } from 'i18next';
|
|
import { useDeviceSyncStore, type DeviceSyncSource } from '../store/deviceSyncStore';
|
|
import { showToast } from '../utils/toast';
|
|
|
|
export interface DeviceSyncDeviceScanResult {
|
|
scanDevice: () => Promise<void>;
|
|
}
|
|
|
|
export function useDeviceSyncDeviceScan(
|
|
targetDir: string | null,
|
|
sourcesLength: number,
|
|
driveDetected: boolean,
|
|
t: TFunction,
|
|
): DeviceSyncDeviceScanResult {
|
|
const setDeviceFilePaths = useDeviceSyncStore.getState().setDeviceFilePaths;
|
|
const setScanning = useDeviceSyncStore.getState().setScanning;
|
|
|
|
const scanDevice = useCallback(async () => {
|
|
if (!targetDir || sourcesLength === 0) {
|
|
setDeviceFilePaths([]);
|
|
return;
|
|
}
|
|
setScanning(true);
|
|
try {
|
|
const files = await invoke<string[]>('list_device_dir_files', { dir: targetDir });
|
|
setDeviceFilePaths(files);
|
|
} catch {
|
|
setDeviceFilePaths([]);
|
|
} finally {
|
|
setScanning(false);
|
|
}
|
|
}, [targetDir, sourcesLength, setDeviceFilePaths, setScanning]);
|
|
|
|
// Scan device on mount and when targetDir changes
|
|
useEffect(() => { scanDevice(); }, [scanDevice]);
|
|
|
|
// Auto-import manifest when page loads and drive is already connected
|
|
const manifestImportedRef = useRef(false);
|
|
useEffect(() => {
|
|
if (!targetDir || !driveDetected || manifestImportedRef.current) return;
|
|
manifestImportedRef.current = true;
|
|
invoke<{ version: number; sources: DeviceSyncSource[] } | null>(
|
|
'read_device_manifest', { destDir: targetDir }
|
|
).then(manifest => {
|
|
if (manifest?.sources?.length) {
|
|
useDeviceSyncStore.getState().clearSources();
|
|
manifest.sources.forEach(s => useDeviceSyncStore.getState().addSource(s));
|
|
showToast(t('deviceSync.manifestImported', { count: manifest.sources.length }), 4000, 'info');
|
|
}
|
|
}).catch(() => {});
|
|
}, [targetDir, driveDetected, t]);
|
|
|
|
// Clear device file list and reset import flag when stick is unplugged
|
|
useEffect(() => {
|
|
if (!driveDetected) {
|
|
setDeviceFilePaths([]);
|
|
manifestImportedRef.current = false;
|
|
}
|
|
}, [driveDetected, setDeviceFilePaths]);
|
|
|
|
return { scanDevice };
|
|
}
|