157 lines
6.3 KiB
TypeScript
157 lines
6.3 KiB
TypeScript
import { DatabaseInterface, UploadTask } from './types';
|
||
|
||
export class SQLiteDatabase implements DatabaseInterface {
|
||
private db: any;
|
||
|
||
constructor() {
|
||
// 动态导入,避免在Web环境下加载
|
||
try {
|
||
const SQLite = require('expo-sqlite');
|
||
this.db = SQLite.openDatabaseSync('upload_status.db');
|
||
this.db.execSync('PRAGMA busy_timeout = 5000;');
|
||
} catch (error) {
|
||
console.error('Failed to initialize SQLite:', error);
|
||
throw new Error('SQLite is not available in this environment');
|
||
}
|
||
}
|
||
|
||
async initUploadTable(): Promise<void> {
|
||
console.log('Initializing upload tasks table...');
|
||
await this.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 this.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...');
|
||
await this.db.execAsync(`ALTER TABLE upload_tasks ADD COLUMN created_at INTEGER;`);
|
||
await this.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 this.db.execAsync(`
|
||
CREATE TABLE IF NOT EXISTS app_state (
|
||
key TEXT PRIMARY KEY NOT NULL,
|
||
value TEXT
|
||
);
|
||
`);
|
||
console.log('App state table initialized');
|
||
}
|
||
|
||
async insertUploadTask(task: Omit<UploadTask, 'created_at'>): Promise<void> {
|
||
console.log('Inserting upload task:', task.uri);
|
||
await this.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)]
|
||
);
|
||
}
|
||
|
||
async getUploadTaskStatus(uri: string): Promise<UploadTask | null> {
|
||
console.log('Checking upload task status for:', uri);
|
||
const result = await this.db.getFirstAsync(
|
||
'SELECT uri, filename, status, progress, file_id, created_at FROM upload_tasks WHERE uri = ?;',
|
||
uri
|
||
);
|
||
return result || null;
|
||
}
|
||
|
||
async updateUploadTaskStatus(uri: string, status: UploadTask['status'], file_id?: string): Promise<void> {
|
||
if (file_id) {
|
||
await this.db.runAsync('UPDATE upload_tasks SET status = ?, file_id = ? WHERE uri = ?', [status, file_id, uri]);
|
||
} else {
|
||
await this.db.runAsync('UPDATE upload_tasks SET status = ? WHERE uri = ?', [status, uri]);
|
||
}
|
||
}
|
||
|
||
async updateUploadTaskProgress(uri: string, progress: number): Promise<void> {
|
||
await this.db.runAsync('UPDATE upload_tasks SET progress = ? WHERE uri = ?', [progress, uri]);
|
||
}
|
||
|
||
async getUploadTasks(): Promise<UploadTask[]> {
|
||
console.log('Fetching all upload tasks... time:', new Date().toLocaleString());
|
||
const results = await this.db.getAllAsync(
|
||
'SELECT uri, filename, status, progress, file_id, created_at FROM upload_tasks ORDER BY created_at DESC;'
|
||
);
|
||
return results;
|
||
}
|
||
|
||
async cleanUpUploadTasks(): Promise<void> {
|
||
console.log('Cleaning up completed/failed upload tasks...');
|
||
await this.db.runAsync(
|
||
"DELETE FROM upload_tasks WHERE status = 'success' OR status = 'failed' OR status = 'skipped';"
|
||
);
|
||
}
|
||
|
||
async getUploadTasksSince(timestamp: number): Promise<UploadTask[]> {
|
||
const rows = await this.db.getAllAsync(
|
||
'SELECT * FROM upload_tasks WHERE created_at >= ? ORDER BY created_at DESC',
|
||
[timestamp]
|
||
);
|
||
return rows;
|
||
}
|
||
|
||
async exist_pending_tasks(): Promise<boolean> {
|
||
const rows = await this.db.getAllAsync(
|
||
'SELECT * FROM upload_tasks WHERE status = "pending" OR status = "uploading"'
|
||
);
|
||
return rows.length > 0;
|
||
}
|
||
|
||
async filterExistingFiles(fileUris: string[]): Promise<string[]> {
|
||
if (fileUris.length === 0) {
|
||
return [];
|
||
}
|
||
|
||
const placeholders = fileUris.map(() => '?').join(',');
|
||
const query = `SELECT uri FROM upload_tasks WHERE uri IN (${placeholders}) AND status = 'success'`;
|
||
|
||
const existingFiles = await this.db.getAllAsync(query, fileUris);
|
||
const existingUris = new Set(existingFiles.map((f: { uri: string }) => 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;
|
||
}
|
||
|
||
async setAppState(key: string, value: string | null): Promise<void> {
|
||
console.log(`Setting app state: ${key} = ${value}`);
|
||
await this.db.runAsync('INSERT OR REPLACE INTO app_state (key, value) VALUES (?, ?)', [key, value]);
|
||
}
|
||
|
||
async getAppState(key: string): Promise<string | null> {
|
||
const result = await this.db.getFirstAsync('SELECT value FROM app_state WHERE key = ?;', key);
|
||
return result?.value ?? null;
|
||
}
|
||
|
||
async executeSql(sql: string, params: any[] = []): Promise<any> {
|
||
try {
|
||
const isSelect = sql.trim().toLowerCase().startsWith('select');
|
||
if (isSelect) {
|
||
const results = this.db.getAllSync(sql, params);
|
||
return results;
|
||
} else {
|
||
const result = this.db.runSync(sql, params);
|
||
return {
|
||
changes: result.changes,
|
||
lastInsertRowId: result.lastInsertRowId,
|
||
};
|
||
}
|
||
} catch (error: any) {
|
||
console.error("Error executing SQL:", error);
|
||
return { error: error.message };
|
||
}
|
||
}
|
||
}
|