Files
Thing/src/stores/downloaderStore.ts
T
2026-07-22 18:26:26 +08:00

245 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { invoke } from '@tauri-apps/api/core'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { createLogger } from '@/lib/logger'
const logger = createLogger('downloader')
// ===== 与 Rust 端对应的数据结构(camelCase =====
export type TaskStatus = 'queued' | 'active' | 'paused' | 'complete' | 'error'
export interface Segment {
index: number
start: number
end: number
completed: number
}
export interface DownloadTask {
id: string
url: string
filename: string
dir: string
status: TaskStatus
totalSize: number
completedSize: number
speed: number
supportsResume: boolean
segments: Segment[]
error: string | null
createdAt: number
headers: Record<string, string>
}
export interface DownloaderSettings {
downloadDir: string
maxConcurrent: number
maxConnections: number
continueDownload: boolean
globalSpeedLimit: number
extensionPort: number
extensionSecret: string
deleteFilesOnRemove: boolean
}
export interface DownloaderStatus {
running: boolean
}
export interface ExtensionInfo {
url: string
port: number
secret: string
hasSecret: boolean
}
/** 下载进度事件载荷 */
interface ProgressPayload {
id: string
completedSize: number
totalSize: number
speed: number
status: TaskStatus
}
/** 下载完成事件载荷 */
interface CompletePayload {
id: string
filename: string
status: TaskStatus
error: string | null
}
export const useDownloaderStore = defineStore('downloader', () => {
const tasks = ref<DownloadTask[]>([])
const status = ref<DownloaderStatus>({ running: false })
const settings = ref<DownloaderSettings | null>(null)
const extensionInfo = ref<ExtensionInfo | null>(null)
// 事件监听器
let progressUnlisten: UnlistenFn | null = null
let completeUnlisten: UnlistenFn | null = null
let addedUnlisten: UnlistenFn | null = null
// ===== 任务列表 =====
const refreshTasks = async () => {
try {
tasks.value = await invoke<DownloadTask[]>('downloader_get_tasks')
} catch (e) {
logger.error('获取任务列表失败: ' + e)
}
return tasks.value
}
/** 根据进度事件更新单个任务(不触发完整刷新) */
const updateTaskProgress = (payload: ProgressPayload) => {
const task = tasks.value.find((t) => t.id === payload.id)
if (task) {
task.completedSize = payload.completedSize
task.totalSize = payload.totalSize
task.speed = payload.speed
task.status = payload.status
}
}
/** 下载完成事件处理 */
const handleTaskComplete = async (_payload: CompletePayload) => {
// 刷新任务列表以获取最终状态
await refreshTasks()
}
// ===== 任务操作 =====
const addTask = async (
url: string,
filename?: string,
dir?: string,
headers?: Record<string, string>
): Promise<string> => {
const id = await invoke<string>('downloader_add_task', {
url,
filename: filename || null,
dir: dir || null,
headers: headers || null
})
await refreshTasks()
return id
}
const pauseTask = async (id: string) => {
await invoke('downloader_pause_task', { id })
await refreshTasks()
}
const resumeTask = async (id: string) => {
await invoke('downloader_resume_task', { id })
await refreshTasks()
}
const removeTask = async (id: string, deleteFiles = false) => {
await invoke('downloader_remove_task', { id, deleteFiles })
await refreshTasks()
}
// ===== 设置 =====
const loadSettings = async () => {
settings.value = await invoke<DownloaderSettings>('downloader_get_settings')
return settings.value
}
const saveSettings = async (s: DownloaderSettings) => {
await invoke('downloader_save_settings', { settings: s })
settings.value = s
}
// ===== 状态 =====
const refreshStatus = async () => {
try {
status.value = await invoke<DownloaderStatus>('downloader_status')
} catch (e) {
logger.error('获取状态失败: ' + e)
}
return status.value
}
// ===== 扩展信息 =====
const loadExtensionInfo = async () => {
extensionInfo.value = await invoke<ExtensionInfo>('downloader_get_extension_info')
return extensionInfo.value
}
// ===== 事件监听 =====
const startEventListeners = async () => {
if (progressUnlisten && completeUnlisten && addedUnlisten) return
if (!progressUnlisten) {
progressUnlisten = await listen<ProgressPayload>('download-progress', (e) => {
updateTaskProgress(e.payload)
})
}
if (!completeUnlisten) {
completeUnlisten = await listen<CompletePayload>('download-complete', (e) => {
handleTaskComplete(e.payload)
})
}
if (!addedUnlisten) {
addedUnlisten = await listen<{ id: string }>('download-added', () => {
// 新任务加入(可能是扩展通过 HTTP API 添加的),刷新任务列表
refreshTasks()
})
}
}
const stopEventListeners = () => {
if (progressUnlisten) {
progressUnlisten()
progressUnlisten = null
}
if (completeUnlisten) {
completeUnlisten()
completeUnlisten = null
}
if (addedUnlisten) {
addedUnlisten()
addedUnlisten = null
}
}
// ===== 初始化 =====
const init = async () => {
await Promise.all([refreshStatus(), loadSettings(), loadExtensionInfo(), refreshTasks()])
await startEventListeners()
}
// ===== 工具函数 =====
const openDir = (path: string) => invoke<void>('downloader_open_dir', { path })
return {
// state
tasks,
status,
settings,
extensionInfo,
// tasks
refreshTasks,
addTask,
pauseTask,
resumeTask,
removeTask,
// settings
loadSettings,
saveSettings,
// status
refreshStatus,
// extension
loadExtensionInfo,
// events
startEventListeners,
stopEventListeners,
// init
init,
// utils
openDir
}
})