mirror of
https://gh-proxy.org/https://github.com/tiajinsha/JKVideo
synced 2026-07-07 23:18:38 +08:00
97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
|
|
import { create } from 'zustand';
|
|||
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|||
|
|
|
|||
|
|
export interface DownloadTask {
|
|||
|
|
bvid: string;
|
|||
|
|
title: string;
|
|||
|
|
cover: string;
|
|||
|
|
qn: number;
|
|||
|
|
qdesc: string;
|
|||
|
|
status: 'downloading' | 'done' | 'error';
|
|||
|
|
progress: number; // 0-1
|
|||
|
|
fileSize?: number; // bytes
|
|||
|
|
localUri?: string;
|
|||
|
|
error?: string;
|
|||
|
|
createdAt: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface DownloadStore {
|
|||
|
|
tasks: Record<string, DownloadTask>; // key: `${bvid}_${qn}`
|
|||
|
|
hasLoaded: boolean;
|
|||
|
|
addTask: (key: string, task: DownloadTask) => void;
|
|||
|
|
updateTask: (key: string, patch: Partial<DownloadTask>) => void;
|
|||
|
|
removeTask: (key: string) => void;
|
|||
|
|
loadFromStorage: () => Promise<void>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const STORAGE_KEY = 'download_tasks';
|
|||
|
|
|
|||
|
|
export const useDownloadStore = create<DownloadStore>((set, get) => ({
|
|||
|
|
tasks: {},
|
|||
|
|
hasLoaded: false,
|
|||
|
|
|
|||
|
|
addTask: (key, task) => {
|
|||
|
|
const next = { ...get().tasks, [key]: task };
|
|||
|
|
set({ tasks: next });
|
|||
|
|
persistTasks(next);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
updateTask: (key, patch) => {
|
|||
|
|
set((s) => {
|
|||
|
|
const prev = s.tasks[key];
|
|||
|
|
if (!prev) return s;
|
|||
|
|
const updated = { ...prev, ...patch };
|
|||
|
|
const next = { ...s.tasks, [key]: updated };
|
|||
|
|
// 只在状态/localUri变化时持久化,progress 不持久化(避免高频写 AsyncStorage)
|
|||
|
|
if (patch.status !== undefined || patch.localUri !== undefined || patch.error !== undefined || patch.fileSize !== undefined) {
|
|||
|
|
persistTasks(next);
|
|||
|
|
}
|
|||
|
|
return { tasks: next };
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
removeTask: (key) => {
|
|||
|
|
set((s) => {
|
|||
|
|
const next = { ...s.tasks };
|
|||
|
|
delete next[key];
|
|||
|
|
persistTasks(next);
|
|||
|
|
return { tasks: next };
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
loadFromStorage: async () => {
|
|||
|
|
// 已加载过则跳过,防止重复调用覆盖正在进行的下载状态
|
|||
|
|
if (get().hasLoaded) return;
|
|||
|
|
try {
|
|||
|
|
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
|||
|
|
if (raw) {
|
|||
|
|
const saved: Record<string, DownloadTask> = JSON.parse(raw);
|
|||
|
|
// 重启后 downloading 任务无法续传,标记为 error
|
|||
|
|
Object.keys(saved).forEach((k) => {
|
|||
|
|
if (saved[k].status === 'downloading') {
|
|||
|
|
saved[k].status = 'error';
|
|||
|
|
saved[k].error = '下载被中断,请重试';
|
|||
|
|
saved[k].progress = 0;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
set({ tasks: saved, hasLoaded: true });
|
|||
|
|
} else {
|
|||
|
|
set({ hasLoaded: true });
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
set({ hasLoaded: true });
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
function persistTasks(tasks: Record<string, DownloadTask>) {
|
|||
|
|
// 只持久化 done/error,downloading 重启后无法续传无需保存
|
|||
|
|
const toSave: Record<string, DownloadTask> = {};
|
|||
|
|
Object.keys(tasks).forEach((k) => {
|
|||
|
|
if (tasks[k].status !== 'downloading') {
|
|||
|
|
toSave[k] = tasks[k];
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(toSave)).catch(() => {});
|
|||
|
|
}
|