177 lines
6.7 KiB
TypeScript
177 lines
6.7 KiB
TypeScript
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 };
|
||
}
|
||
}
|
||
|