177 lines
6.7 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 * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabaseSync('upload_status.db');
// Set a busy timeout to handle concurrent writes and avoid "database is locked" errors.
// This will make SQLite wait for 5 seconds if the database is locked by another process.
db.execSync('PRAGMA busy_timeout = 5000;');
export type UploadTask = {
uri: string;
filename: string;
status: 'pending' | 'uploading' | 'success' | 'failed' | 'skipped';
progress: number; // 0-100
file_id?: string; // 后端返回的文件ID
created_at: number; // unix timestamp
};
// 初始化表
export async function initUploadTable() {
console.log('Initializing upload tasks table...');
await db.execAsync(`
CREATE TABLE IF NOT EXISTS upload_tasks (
uri TEXT PRIMARY KEY NOT NULL,
filename TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
progress INTEGER NOT NULL DEFAULT 0,
file_id TEXT,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);
`);
// Add created_at column to existing table if it doesn't exist
const columns = await db.getAllAsync('PRAGMA table_info(upload_tasks);');
const columnExists = columns.some((column: any) => column.name === 'created_at');
if (!columnExists) {
console.log('Adding created_at column to upload_tasks table...');
// SQLite doesn't support non-constant DEFAULT values on ALTER TABLE.
// So we add the column, then update existing rows.
await db.execAsync(`ALTER TABLE upload_tasks ADD COLUMN created_at INTEGER;`);
await db.execAsync(`UPDATE upload_tasks SET created_at = (strftime('%s', 'now')) WHERE created_at IS NULL;`);
console.log('created_at column added and populated.');
}
console.log('Upload tasks table initialized');
await db.execAsync(`
CREATE TABLE IF NOT EXISTS app_state (
key TEXT PRIMARY KEY NOT NULL,
value TEXT
);
`);
console.log('App state table initialized');
}
// 插入新的上传任务
export async function insertUploadTask(task: Omit<UploadTask, 'created_at'>) {
console.log('Inserting upload task:', task.uri);
await db.runAsync(
'INSERT OR REPLACE INTO upload_tasks (uri, filename, status, progress, file_id, created_at) VALUES (?, ?, ?, ?, ?, ?)',
[task.uri, task.filename, task.status, task.progress, task.file_id ?? null, Math.floor(Date.now() / 1000)]
);
}
// 检查文件是否已上传或正在上传
export async function getUploadTaskStatus(uri: string): Promise<UploadTask | null> {
console.log('Checking upload task status for:', uri);
const result = await db.getFirstAsync<UploadTask>(
'SELECT uri, filename, status, progress, file_id, created_at FROM upload_tasks WHERE uri = ?;',
uri
);
return result || null;
}
// 更新上传任务的状态
export async function updateUploadTaskStatus(uri: string, status: UploadTask['status'], file_id?: string) {
if (file_id) {
await db.runAsync('UPDATE upload_tasks SET status = ?, file_id = ? WHERE uri = ?', [status, file_id, uri]);
} else {
await db.runAsync('UPDATE upload_tasks SET status = ? WHERE uri = ?', [status, uri]);
}
}
// 更新上传任务的进度
export async function updateUploadTaskProgress(uri: string, progress: number) {
await db.runAsync('UPDATE upload_tasks SET progress = ? WHERE uri = ?', [progress, uri]);
}
// 获取所有上传任务
export async function getUploadTasks(): Promise<UploadTask[]> {
console.log('Fetching all upload tasks... time:', new Date().toLocaleString());
const results = await db.getAllAsync<UploadTask>(
'SELECT uri, filename, status, progress, file_id, created_at FROM upload_tasks ORDER BY created_at DESC;'
);
return results;
}
// 清理已完成或失败的任务 (可选,根据需求添加)
export async function cleanUpUploadTasks(): Promise<void> {
console.log('Cleaning up completed/failed upload tasks...');
await db.runAsync(
"DELETE FROM upload_tasks WHERE status = 'success' OR status = 'failed' OR status = 'skipped';"
);
}
// 获取某个时间点之后的所有上传任务
export async function getUploadTasksSince(timestamp: number): Promise<UploadTask[]> {
const rows = await db.getAllAsync<UploadTask>(
'SELECT * FROM upload_tasks WHERE created_at >= ? ORDER BY created_at DESC',
[timestamp]
);
return rows;
}
export async function exist_pending_tasks(): Promise<boolean> {
const rows = await db.getAllAsync<UploadTask>(
'SELECT * FROM upload_tasks WHERE status = "pending" OR status = "uploading"'
);
return rows.length > 0;
}
// 检查一组文件URI返回那些在数据库中不存在或是未成功上传的文件的URI
export async function filterExistingFiles(fileUris: string[]): Promise<string[]> {
if (fileUris.length === 0) {
return [];
}
// 创建占位符字符串 '?,?,?'
const placeholders = fileUris.map(() => '?').join(',');
// 查询已经存在且状态为 'success' 的任务
const query = `SELECT uri FROM upload_tasks WHERE uri IN (${placeholders}) AND status = 'success'`;
const existingFiles = await db.getAllAsync<{ uri: string }>(query, fileUris);
const existingUris = new Set(existingFiles.map(f => f.uri));
// 过滤出新文件
const newFileUris = fileUris.filter(uri => !existingUris.has(uri));
console.log(`[DB] Total files: ${fileUris.length}, Existing successful files: ${existingUris.size}, New files to upload: ${newFileUris.length}`);
return newFileUris;
}
// 设置全局状态值
export async function setAppState(key: string, value: string | null): Promise<void> {
console.log(`Setting app state: ${key} = ${value}`);
await db.runAsync('INSERT OR REPLACE INTO app_state (key, value) VALUES (?, ?)', [key, value]);
}
// 获取全局状态值
export async function getAppState(key: string): Promise<string | null> {
const result = await db.getFirstAsync<{ value: string }>('SELECT value FROM app_state WHERE key = ?;', key);
return result?.value ?? null;
}
// for debug page
export async function executeSql(sql: string, params: any[] = []): Promise<any> {
try {
// Trim and check if it's a SELECT query
const isSelect = sql.trim().toLowerCase().startsWith('select');
if (isSelect) {
const results = db.getAllSync(sql, params);
return results;
} else {
const result = db.runSync(sql, params);
return {
changes: result.changes,
lastInsertRowId: result.lastInsertRowId,
};
}
} catch (error: any) {
console.error("Error executing SQL:", error);
return { error: error.message };
}
}