memowake-front/lib/database/sqlite-database.ts
2025-07-21 16:28:22 +08:00

157 lines
6.3 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 { 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 };
}
}
}