mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
refactor: split modules and harden scraping workflows
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { libraryAPI } from '../api/library'
|
||||
import { storageAPI } from '../api/storage_config'
|
||||
import { useWebSocket } from '../hooks/useWebSocket'
|
||||
import { formatCloudScanStatus, formatDuration } from './libraryPageModel'
|
||||
|
||||
type UseLibraryScanStatusInput = {
|
||||
libraryID: string
|
||||
isAdmin: boolean
|
||||
onLibraryChanged: () => void
|
||||
}
|
||||
|
||||
export function useLibraryScanStatus({
|
||||
libraryID,
|
||||
isAdmin,
|
||||
onLibraryChanged,
|
||||
}: UseLibraryScanStatusInput) {
|
||||
const [scanning, setScanning] = useState(false)
|
||||
const [scanProgress, setScanProgress] = useState('')
|
||||
|
||||
const onRealtimeEvent = useCallback((topic: string, payload: unknown) => {
|
||||
if (!isAdmin) return
|
||||
if (topic !== 'scan' || !payload || typeof payload !== 'object') return
|
||||
const event = payload as Record<string, unknown>
|
||||
if (event.library_id !== libraryID) return
|
||||
if (event.error) {
|
||||
setScanning(false)
|
||||
setScanProgress(`扫描失败:${String(event.error)}`)
|
||||
return
|
||||
}
|
||||
if (event.finished) {
|
||||
setScanning(false)
|
||||
setScanProgress(finishedScanMessage(event))
|
||||
onLibraryChanged()
|
||||
return
|
||||
}
|
||||
if (event.queued) {
|
||||
setScanning(true)
|
||||
setScanProgress(String(event.message ?? '扫描已排队,后台会自动入库'))
|
||||
return
|
||||
}
|
||||
if (event.cloud && event.stage) {
|
||||
setScanning(true)
|
||||
setScanProgress(runningCloudScanMessage(event))
|
||||
}
|
||||
}, [isAdmin, libraryID, onLibraryChanged])
|
||||
|
||||
useWebSocket(onRealtimeEvent)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAdmin || !libraryID) return
|
||||
let cancelled = false
|
||||
let terminal = false
|
||||
let timer: number | undefined
|
||||
const restoreCloudScanStatus = async () => {
|
||||
const response = await storageAPI.cloudScanStatus()
|
||||
if (cancelled) return
|
||||
const status = (response.items ?? []).find((item) => item.library_id === libraryID)
|
||||
if (!status) return
|
||||
if (status.state === 'running' || status.state === 'queued' || status.state === 'canceling') {
|
||||
setScanning(true)
|
||||
setScanProgress(formatCloudScanStatus(status))
|
||||
return
|
||||
}
|
||||
if (status.state === 'finished') {
|
||||
setScanning(false)
|
||||
setScanProgress(formatCloudScanStatus(status))
|
||||
onLibraryChanged()
|
||||
terminal = true
|
||||
if (timer) window.clearInterval(timer)
|
||||
return
|
||||
}
|
||||
if (status.state === 'error' && status.error) {
|
||||
setScanning(false)
|
||||
setScanProgress(`扫描失败:${status.error}`)
|
||||
terminal = true
|
||||
if (timer) window.clearInterval(timer)
|
||||
}
|
||||
}
|
||||
restoreCloudScanStatus()
|
||||
.catch(() => undefined)
|
||||
.finally(() => {
|
||||
if (!cancelled && !terminal) {
|
||||
timer = window.setInterval(() => {
|
||||
restoreCloudScanStatus().catch(() => undefined)
|
||||
}, 5000)
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
if (timer) window.clearInterval(timer)
|
||||
}
|
||||
}, [isAdmin, libraryID, onLibraryChanged])
|
||||
|
||||
const handleScan = useCallback(async () => {
|
||||
setScanning(true)
|
||||
setScanProgress('正在提交扫描任务…')
|
||||
let keepScanning = false
|
||||
try {
|
||||
const result = await libraryAPI.scan(libraryID)
|
||||
if (result.queued) {
|
||||
keepScanning = true
|
||||
setScanProgress(`${result.message ?? '云盘扫描已在后台运行,发现的媒体会自动加入当前媒体库'};${result.estimate_message ?? '大目录耗时取决于网盘接口速度'}`)
|
||||
toast.success('云盘扫描已加入后台队列')
|
||||
} else {
|
||||
toast.success(`扫描完成:新增 ${result.added} 项,更新 ${result.updated ?? 0} 项`)
|
||||
setScanProgress(`扫描完成:新增 ${result.added} · 更新 ${result.updated ?? 0}`)
|
||||
onLibraryChanged()
|
||||
setScanning(false)
|
||||
}
|
||||
} catch {
|
||||
toast.error('扫描失败')
|
||||
setScanProgress('扫描失败,请查看日志或稍后重试')
|
||||
setScanning(false)
|
||||
} finally {
|
||||
if (!keepScanning) setScanning(false)
|
||||
}
|
||||
}, [libraryID, onLibraryChanged])
|
||||
|
||||
return {
|
||||
scanning,
|
||||
scanProgress,
|
||||
handleScan,
|
||||
}
|
||||
}
|
||||
|
||||
function finishedScanMessage(event: Record<string, unknown>) {
|
||||
const elapsed = Number(event.elapsed_seconds ?? event.elapsed ?? 0)
|
||||
const elapsedText = elapsed > 0 ? ` · 耗时 ${formatDuration(elapsed)}` : ''
|
||||
return `扫描完成:发现 ${event.discovered ?? event.visited ?? 0} · 新增 ${event.added ?? 0} · 更新 ${event.updated ?? 0} · 跳过 ${event.skipped ?? 0}${elapsedText}`
|
||||
}
|
||||
|
||||
function runningCloudScanMessage(event: Record<string, unknown>) {
|
||||
const stage = event.stage === 'importing' ? '正在入库' : '正在遍历目录'
|
||||
const speed = Number(event.files_per_second ?? 0)
|
||||
const speedText = speed > 0 ? ` · ${speed.toFixed(speed >= 10 ? 0 : 1)} 个/秒` : ''
|
||||
return `${stage}:目录 ${event.dirs ?? 0} · 已发现 ${event.discovered ?? 0} · 已入库 ${event.visited ?? 0}${speedText}`
|
||||
}
|
||||
Reference in New Issue
Block a user