245 lines
5.9 KiB
TypeScript
245 lines
5.9 KiB
TypeScript
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
|
||
}
|
||
})
|