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) { 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 { console.log('Checking upload task status for:', uri); const result = await db.getFirstAsync( '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 { console.log('Fetching all upload tasks... time:', new Date().toLocaleString()); const results = await db.getAllAsync( 'SELECT uri, filename, status, progress, file_id, created_at FROM upload_tasks ORDER BY created_at DESC;' ); return results; } // 清理已完成或失败的任务 (可选,根据需求添加) export async function cleanUpUploadTasks(): Promise { 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 { const rows = await db.getAllAsync( 'SELECT * FROM upload_tasks WHERE created_at >= ? ORDER BY created_at DESC', [timestamp] ); return rows; } export async function exist_pending_tasks(): Promise { const rows = await db.getAllAsync( 'SELECT * FROM upload_tasks WHERE status = "pending" OR status = "uploading"' ); return rows.length > 0; } // 检查一组文件URI,返回那些在数据库中不存在或是未成功上传的文件的URI export async function filterExistingFiles(fileUris: string[]): Promise { 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 { 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 { 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 { 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 }; } }