<think>Let me analyze the staged changes to generate a proper conventional commit message.
Looking at the changes: 1. **New files**: - `app/(app)/(tabs)/profile/account-deletion.tsx` - account deletion screen - `app/(app)/(tabs)/profile/privacy-settings.tsx` - privacy settings screen - `src/screens/profile/AccountDeletionScreen.tsx` - account deletion implementation - `src/screens/profile/PrivacySettingsScreen.tsx` - privacy settings implementation 2. **Modified files**: - Database changes: `LocalDataSource.ts`, `DatabaseManager.ts`, `DatabaseConfig.ts`, `MigrationManager.ts` - refactoring database layer (removing LockManager, simplified initialization, added retry logic) - Auth store: `authStore.ts` - changed `initDatabase` to `switchDatabase` - Auth service: `authService.ts` - added privacy settings and account deletion methods - DTOs: `dto.ts` - added PrivacySettingsDTO, DeletionStatusDTO, VoteOptionDTO, VoteResultDTO types - Navigation: `hrefs.ts` - added new href functions for privacy settings and account deletion - Profile screens: `SettingsScreen.tsx`, `index.ts`, `_layout.tsx` - added new menu items and routes - Various message screens - added keyboard handling props - `app/_layout.tsx` - added CSS for mobile touch handling - `CreatePostScreen.tsx` - fixed vote_options format - `PostDetailScreen.tsx` - added type annotations for vote handling - `MediaCacheManager.ts` - updated to use new expo-file-system API - `VerificationSettingsScreen.tsx` - fixed type safety - `AccountSecurityScreen.tsx` - styling refactoring This is a multi-faceted change involving: Database refactoring, new privacy/account deletion features, bug fixes, and UI improvements. The database changes appear to be refactoring without new functionality. The most significant changes are the privacy settings and account deletion features, plus bug fixes like the vote options format and mobile touch handling. </think> feat(profile): add privacy settings and account deletion screens Add privacy settings and account deletion functionality with new screens and APIs. Refactor database initialization to use switchDatabase for user switching. Fix vote options format in post creation and add type safety to vote handling. Improve mobile touch handling in layout and message screens. Update media cache to use new expo-file-system API. BREAKING CHANGE: Database initialization now uses switchDatabase instead of initDatabase for user switching
This commit is contained in:
@@ -1,123 +1,88 @@
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import { ILocalDataSource, DataSourceError } from '@/data/datasources/interfaces';
|
||||
import type { ILocalDataSource } from '@/data/datasources/interfaces';
|
||||
import { databaseManager } from './core/DatabaseManager';
|
||||
|
||||
let writeQueue: Promise<void> = Promise.resolve();
|
||||
class OperationQueue {
|
||||
private queue: Promise<unknown> = Promise.resolve();
|
||||
|
||||
export class LocalDataSource implements ILocalDataSource {
|
||||
async initialize(userId?: string): Promise<void> {
|
||||
await databaseManager.initialize(userId);
|
||||
enqueue<T>(operation: () => Promise<T>): Promise<T> {
|
||||
const result = this.queue.then(() => operation(), () => operation());
|
||||
this.queue = result.catch(() => {});
|
||||
return result;
|
||||
}
|
||||
|
||||
private async ensureDb(): Promise<SQLite.SQLiteDatabase> {
|
||||
return databaseManager.getDb();
|
||||
}
|
||||
|
||||
async query<T>(sql: string, params?: any[]): Promise<T[]> {
|
||||
try {
|
||||
const db = await this.ensureDb();
|
||||
if (params && params.length > 0) {
|
||||
return await db.getAllAsync<T>(sql, params as any);
|
||||
}
|
||||
return await db.getAllAsync<T>(sql);
|
||||
} catch (error) {
|
||||
this.handleError(error, 'QUERY');
|
||||
}
|
||||
}
|
||||
|
||||
async getFirst<T>(sql: string, params?: any[]): Promise<T | null> {
|
||||
try {
|
||||
const db = await this.ensureDb();
|
||||
if (params && params.length > 0) {
|
||||
return await db.getFirstAsync<T>(sql, params as any);
|
||||
}
|
||||
return await db.getFirstAsync<T>(sql);
|
||||
} catch (error) {
|
||||
this.handleError(error, 'GET_FIRST');
|
||||
}
|
||||
}
|
||||
|
||||
async execute(sql: string, params?: any[]): Promise<void> {
|
||||
try {
|
||||
const db = await this.ensureDb();
|
||||
await db.execAsync(sql);
|
||||
} catch (error) {
|
||||
this.handleError(error, 'EXECUTE');
|
||||
}
|
||||
}
|
||||
|
||||
async run(sql: string, params?: any[]): Promise<{ lastInsertRowId: number; changes: number }> {
|
||||
try {
|
||||
const db = await this.ensureDb();
|
||||
if (params && params.length > 0) {
|
||||
return await db.runAsync(sql, params as any);
|
||||
}
|
||||
return await db.runAsync(sql);
|
||||
} catch (error) {
|
||||
this.handleError(error, 'RUN');
|
||||
}
|
||||
}
|
||||
|
||||
async transaction(operations: () => Promise<void>): Promise<void> {
|
||||
try {
|
||||
const db = await this.ensureDb();
|
||||
await db.withTransactionAsync(async () => {
|
||||
await operations();
|
||||
});
|
||||
} catch (error) {
|
||||
this.handleError(error, 'TRANSACTION');
|
||||
}
|
||||
}
|
||||
|
||||
async batch(operations: (db: ILocalDataSource) => Promise<void>): Promise<void> {
|
||||
try {
|
||||
await this.transaction(async () => {
|
||||
await operations(this);
|
||||
});
|
||||
} catch (error) {
|
||||
this.handleError(error, 'BATCH');
|
||||
}
|
||||
}
|
||||
|
||||
async enqueueWrite<T>(operation: () => Promise<T>): Promise<T> {
|
||||
const wrappedOperation = async () => {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
if (this.isRecoverableError(error)) {
|
||||
console.error('[LocalDataSource] 数据库写入异常,尝试重连后重试:', error);
|
||||
await databaseManager.initialize(databaseManager.getCurrentUserId());
|
||||
return operation();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const queued = writeQueue.then(wrappedOperation, wrappedOperation);
|
||||
writeQueue = queued.then(() => undefined, () => undefined);
|
||||
return queued;
|
||||
}
|
||||
|
||||
private isRecoverableError(error: unknown): boolean {
|
||||
const message = String(error);
|
||||
return (
|
||||
message.includes('NativeDatabase.prepareAsync') ||
|
||||
message.includes('NullPointerException') ||
|
||||
message.includes('database is locked') ||
|
||||
message.includes('finalizing statement') ||
|
||||
message.includes('cannot create file')
|
||||
);
|
||||
}
|
||||
|
||||
private handleError(error: unknown, operation: string): never {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
throw new DataSourceError(
|
||||
`Database ${operation} failed: ${message}`,
|
||||
'DB_ERROR',
|
||||
'LocalDataSource',
|
||||
error instanceof Error ? error : undefined
|
||||
);
|
||||
async drain(): Promise<void> {
|
||||
await this.queue;
|
||||
}
|
||||
}
|
||||
|
||||
export const localDataSource = new LocalDataSource();
|
||||
const operationQueue = new OperationQueue();
|
||||
|
||||
export class LocalDataSource implements ILocalDataSource {
|
||||
private async getDb(): Promise<SQLite.SQLiteDatabase> {
|
||||
return databaseManager.getDb();
|
||||
}
|
||||
|
||||
async initialize(userId?: string): Promise<void> {
|
||||
if (databaseManager.isReady()) {
|
||||
return;
|
||||
}
|
||||
await operationQueue.enqueue(() => databaseManager.initialize(userId));
|
||||
}
|
||||
|
||||
async query<T>(sql: string, params?: any[]): Promise<T[]> {
|
||||
const db = await this.getDb();
|
||||
if (params && params.length > 0) {
|
||||
return db.getAllAsync<T>(sql, params as any);
|
||||
}
|
||||
return db.getAllAsync<T>(sql);
|
||||
}
|
||||
|
||||
async getFirst<T>(sql: string, params?: any[]): Promise<T | null> {
|
||||
const db = await this.getDb();
|
||||
if (params && params.length > 0) {
|
||||
return db.getFirstAsync<T>(sql, params as any);
|
||||
}
|
||||
return db.getFirstAsync<T>(sql);
|
||||
}
|
||||
|
||||
async execute(sql: string, params?: any[]): Promise<void> {
|
||||
const db = await this.getDb();
|
||||
await db.execAsync(sql);
|
||||
}
|
||||
|
||||
async run(sql: string, params?: any[]): Promise<{ lastInsertRowId: number; changes: number }> {
|
||||
const db = await this.getDb();
|
||||
if (params && params.length > 0) {
|
||||
return db.runAsync(sql, params as any);
|
||||
}
|
||||
return db.runAsync(sql);
|
||||
}
|
||||
|
||||
async transaction(operations: () => Promise<void>): Promise<void> {
|
||||
const db = await this.getDb();
|
||||
await db.withTransactionAsync(operations);
|
||||
}
|
||||
|
||||
async batch(operations: (db: ILocalDataSource) => Promise<void>): Promise<void> {
|
||||
await this.transaction(async () => {
|
||||
await operations(this);
|
||||
});
|
||||
}
|
||||
|
||||
async enqueueWrite<T>(operation: () => Promise<T>): Promise<T> {
|
||||
return operationQueue.enqueue(operation);
|
||||
}
|
||||
|
||||
async switchDatabase(userId?: string | null): Promise<void> {
|
||||
await operationQueue.drain();
|
||||
await operationQueue.enqueue(() => databaseManager.initialize(userId));
|
||||
}
|
||||
|
||||
async closeDatabase(): Promise<void> {
|
||||
await operationQueue.drain();
|
||||
await operationQueue.enqueue(() => databaseManager.close());
|
||||
}
|
||||
}
|
||||
|
||||
export const localDataSource = new LocalDataSource();
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
export const DB_CONFIG = {
|
||||
MAX_RETRIES: 4,
|
||||
WEB_RETRY_DELAY_BASE: 500,
|
||||
NATIVE_RETRY_DELAY: 100,
|
||||
WEB_CLOSE_DELAY: 300,
|
||||
WEB_FINAL_CLOSE_DELAY: 200,
|
||||
LOCK_PREFIX: 'db-',
|
||||
DEFAULT_DB_NAME: 'carrot_bbs.db',
|
||||
DB_NAME_PREFIX: 'carrot_bbs_',
|
||||
} as const;
|
||||
const DEFAULT_DB_NAME = 'carrot_bbs.db';
|
||||
const DB_NAME_PREFIX = 'carrot_bbs_';
|
||||
|
||||
export function getDbName(userId: string | null): string {
|
||||
return userId ? `${DB_CONFIG.DB_NAME_PREFIX}${userId}.db` : DB_CONFIG.DEFAULT_DB_NAME;
|
||||
return userId ? `${DB_NAME_PREFIX}${userId}.db` : DEFAULT_DB_NAME;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import { LockManager } from './LockManager';
|
||||
import { DB_CONFIG, getDbName } from './DatabaseConfig';
|
||||
import { Platform } from 'react-native';
|
||||
import { getDbName } from './DatabaseConfig';
|
||||
import { runMigrations } from '../schema/MigrationManager';
|
||||
|
||||
type SQLiteDatabase = SQLite.SQLiteDatabase;
|
||||
@@ -10,8 +10,6 @@ class DatabaseManager {
|
||||
private db: SQLiteDatabase | null = null;
|
||||
private currentUserId: string | null = null;
|
||||
private initPromise: Promise<void> | null = null;
|
||||
private initTargetUserId: string | null = null;
|
||||
private isInitialized = false;
|
||||
|
||||
static getInstance(): DatabaseManager {
|
||||
if (!DatabaseManager.instance) {
|
||||
@@ -22,119 +20,81 @@ class DatabaseManager {
|
||||
|
||||
async initialize(userId?: string | null): Promise<void> {
|
||||
const targetUserId = userId || null;
|
||||
const callId = Date.now();
|
||||
|
||||
console.log(`[DatabaseManager ${callId}] initialize 开始, userId=${targetUserId}, 当前db=${!!this.db}, 当前userId=${this.currentUserId}`);
|
||||
|
||||
if (this.db && this.currentUserId === targetUserId) {
|
||||
console.log(`[DatabaseManager ${callId}] 已是目标数据库,直接返回`);
|
||||
this.isInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.initPromise && this.initTargetUserId === targetUserId) {
|
||||
console.log(`[DatabaseManager ${callId}] 等待进行中的初始化`);
|
||||
await this.initPromise;
|
||||
console.log(`[DatabaseManager ${callId}] 初始化完成`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.initPromise) {
|
||||
console.log(`[DatabaseManager ${callId}] 等待其他初始化完成`);
|
||||
await this.initPromise.catch(() => {});
|
||||
await this.initPromise;
|
||||
if (this.db && this.currentUserId === targetUserId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.db && this.currentUserId === targetUserId) {
|
||||
console.log(`[DatabaseManager ${callId}] 其他初始化已完成目标,直接返回`);
|
||||
this.isInitialized = true;
|
||||
this.initPromise = this.doInitialize(targetUserId);
|
||||
|
||||
try {
|
||||
await this.initPromise;
|
||||
} finally {
|
||||
this.initPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async doInitialize(targetUserId: string | null): Promise<void> {
|
||||
if (this.db) {
|
||||
this.forceClose(this.db);
|
||||
this.db = null;
|
||||
this.currentUserId = null;
|
||||
}
|
||||
|
||||
const dbName = getDbName(targetUserId);
|
||||
const isWeb = Platform.OS === 'web';
|
||||
|
||||
if (isWeb) {
|
||||
try {
|
||||
const db = await this.tryOpenDatabase(dbName);
|
||||
await runMigrations(db);
|
||||
this.db = db;
|
||||
this.currentUserId = targetUserId;
|
||||
} catch (error) {
|
||||
console.warn('[DatabaseManager] Web数据库打开失败,使用内存数据库:', error);
|
||||
const db = await SQLite.openDatabaseAsync(':memory:');
|
||||
await runMigrations(db);
|
||||
this.db = db;
|
||||
this.currentUserId = targetUserId;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.initTargetUserId = targetUserId;
|
||||
console.log(`[DatabaseManager ${callId}] 开始实际初始化`);
|
||||
|
||||
this.initPromise = LockManager.withMutex(async () => {
|
||||
const mutexId = callId;
|
||||
console.log(`[DatabaseManager ${mutexId}] 获得互斥锁`);
|
||||
|
||||
if (this.db && this.currentUserId === targetUserId) {
|
||||
console.log(`[DatabaseManager ${mutexId}] 互斥锁内检查:已是目标数据库`);
|
||||
this.isInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const isWeb = typeof window !== 'undefined';
|
||||
|
||||
if (this.db) {
|
||||
console.log(`[DatabaseManager ${mutexId}] 关闭旧数据库连接...`);
|
||||
try {
|
||||
await this.db.closeAsync();
|
||||
console.log(`[DatabaseManager ${mutexId}] 旧连接已关闭`);
|
||||
} catch (e) {
|
||||
console.warn(`[DatabaseManager ${mutexId}] 关闭旧连接失败:`, e);
|
||||
}
|
||||
this.db = null;
|
||||
this.currentUserId = null;
|
||||
this.isInitialized = false;
|
||||
|
||||
if (isWeb) {
|
||||
await new Promise(r => setTimeout(r, DB_CONFIG.WEB_CLOSE_DELAY));
|
||||
}
|
||||
}
|
||||
|
||||
const dbName = getDbName(targetUserId);
|
||||
console.log(`[DatabaseManager ${mutexId}] 打开数据库: ${dbName}`);
|
||||
|
||||
const database = await this.openDatabaseWithRetry(dbName);
|
||||
console.log(`[DatabaseManager ${mutexId}] 数据库已打开,执行迁移`);
|
||||
|
||||
await runMigrations(database);
|
||||
console.log(`[DatabaseManager ${mutexId}] 迁移完成`);
|
||||
|
||||
this.db = database;
|
||||
this.currentUserId = targetUserId;
|
||||
this.isInitialized = true;
|
||||
console.log(`[DatabaseManager ${mutexId}] 初始化完成`);
|
||||
}).finally(() => {
|
||||
console.log(`[DatabaseManager ${callId}] 清理初始化状态`);
|
||||
this.initPromise = null;
|
||||
this.initTargetUserId = null;
|
||||
});
|
||||
|
||||
await this.initPromise;
|
||||
console.log(`[DatabaseManager ${callId}] initialize 返回`);
|
||||
const db = await this.tryOpenDatabase(dbName);
|
||||
await runMigrations(db);
|
||||
this.db = db;
|
||||
this.currentUserId = targetUserId;
|
||||
}
|
||||
|
||||
private async openDatabaseWithRetry(dbName: string): Promise<SQLiteDatabase> {
|
||||
const isWeb = typeof window !== 'undefined';
|
||||
|
||||
return LockManager.withWebLock(`${DB_CONFIG.LOCK_PREFIX}${dbName}`, async () => {
|
||||
let lastError: unknown;
|
||||
for (let attempt = 0; attempt <= DB_CONFIG.MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const database = await SQLite.openDatabaseAsync(dbName);
|
||||
console.log(`[DatabaseManager] 成功打开数据库 (尝试 ${attempt + 1}/${DB_CONFIG.MAX_RETRIES + 1})`);
|
||||
return database;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
const msg = String(error);
|
||||
console.error(`[DatabaseManager] 打开失败 (尝试 ${attempt + 1}/${DB_CONFIG.MAX_RETRIES + 1}):`, msg);
|
||||
|
||||
if (isWeb && msg.includes('cannot create file')) {
|
||||
console.log('[DatabaseManager] OPFS 不可用,回退到内存数据库');
|
||||
const memoryDb = await SQLite.openDatabaseAsync(':memory:');
|
||||
return memoryDb;
|
||||
}
|
||||
|
||||
if (attempt < DB_CONFIG.MAX_RETRIES) {
|
||||
const delay = isWeb ? DB_CONFIG.WEB_RETRY_DELAY_BASE * (attempt + 1) : DB_CONFIG.NATIVE_RETRY_DELAY;
|
||||
console.warn(`[DatabaseManager] ${delay}ms 后重试...`);
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
}
|
||||
}
|
||||
private async tryOpenDatabase(dbName: string): Promise<SQLiteDatabase> {
|
||||
try {
|
||||
return await SQLite.openDatabaseAsync(dbName, {
|
||||
useNewConnection: true,
|
||||
});
|
||||
} catch (error) {
|
||||
const msg = String(error);
|
||||
if (msg.includes('cannot create file') || msg.includes('SQLITE_CANTOPEN') || msg.includes('database is locked')) {
|
||||
return await SQLite.openDatabaseAsync(':memory:');
|
||||
}
|
||||
throw lastError;
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private forceClose(db: SQLiteDatabase): void {
|
||||
try {
|
||||
db.closeSync();
|
||||
} catch {
|
||||
try {
|
||||
db.closeAsync().catch(() => {});
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
@@ -142,43 +102,22 @@ class DatabaseManager {
|
||||
await this.initPromise.catch(() => {});
|
||||
}
|
||||
|
||||
const dbName = getDbName(this.currentUserId);
|
||||
|
||||
await LockManager.withWebLock(`${DB_CONFIG.LOCK_PREFIX}${dbName}`, async () => {
|
||||
await LockManager.withMutex(async () => {
|
||||
if (!this.db) return;
|
||||
|
||||
try {
|
||||
await this.db.closeAsync();
|
||||
console.log('[DatabaseManager] 数据库已关闭');
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
await new Promise(r => setTimeout(r, DB_CONFIG.WEB_FINAL_CLOSE_DELAY));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[DatabaseManager] 关闭失败:', e);
|
||||
}
|
||||
this.db = null;
|
||||
this.currentUserId = null;
|
||||
this.isInitialized = false;
|
||||
});
|
||||
});
|
||||
if (this.db) {
|
||||
this.forceClose(this.db);
|
||||
this.db = null;
|
||||
this.currentUserId = null;
|
||||
}
|
||||
}
|
||||
|
||||
async getDb(timeout: number = 10000): Promise<SQLiteDatabase> {
|
||||
if (this.isInitialized && this.db) return this.db;
|
||||
async getDb(): Promise<SQLiteDatabase> {
|
||||
if (this.db) return this.db;
|
||||
|
||||
if (this.initPromise) {
|
||||
const timeoutPromise = new Promise<null>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('等待数据库初始化超时')), timeout)
|
||||
);
|
||||
|
||||
await Promise.race([this.initPromise, timeoutPromise]);
|
||||
|
||||
await this.initPromise;
|
||||
if (this.db) return this.db;
|
||||
}
|
||||
|
||||
throw new Error('数据库未初始化,请先调用 initialize(userId)');
|
||||
throw new Error('数据库未初始化');
|
||||
}
|
||||
|
||||
getDbSync(): SQLiteDatabase | null {
|
||||
@@ -190,11 +129,7 @@ class DatabaseManager {
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.isInitialized && this.db !== null;
|
||||
}
|
||||
|
||||
async withMutex<T>(fn: () => Promise<T>): Promise<T> {
|
||||
return LockManager.withMutex(fn);
|
||||
return this.db !== null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,12 @@ export const initDatabase = async (userId?: string | null): Promise<void> => {
|
||||
await databaseManager.initialize(userId);
|
||||
};
|
||||
|
||||
export const switchDatabase = async (userId?: string | null): Promise<void> => {
|
||||
await localDataSource.switchDatabase(userId);
|
||||
};
|
||||
|
||||
export const closeDatabase = async (): Promise<void> => {
|
||||
await databaseManager.close();
|
||||
await localDataSource.closeDatabase();
|
||||
};
|
||||
|
||||
export const getCurrentDbUserId = (): string | null => {
|
||||
@@ -49,6 +53,6 @@ export const getDbInstance = () => {
|
||||
return databaseManager.getDbSync();
|
||||
};
|
||||
|
||||
export const getDb = async (timeout?: number) => {
|
||||
return databaseManager.getDb(timeout);
|
||||
export const getDb = async () => {
|
||||
return databaseManager.getDb();
|
||||
};
|
||||
@@ -23,33 +23,42 @@ import {
|
||||
CREATE_POSTS_CACHE_TABLE,
|
||||
} from './PostSchema';
|
||||
|
||||
export async function runMigrations(db: SQLiteDatabase): Promise<void> {
|
||||
if (typeof window !== 'undefined') {
|
||||
async function execWithRetry(db: SQLiteDatabase, sql: string, maxRetries = 5): Promise<void> {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
await db.execAsync(`PRAGMA locking_mode = EXCLUSIVE;`);
|
||||
await db.execAsync(`PRAGMA journal_mode = MEMORY;`);
|
||||
await db.execAsync(`PRAGMA temp_store = MEMORY;`);
|
||||
console.log('[MigrationManager] Web: 已设置 EXCLUSIVE locking + MEMORY journal/temp 模式');
|
||||
} catch (e) {
|
||||
console.warn('[MigrationManager] 设置 PRAGMA 失败:', e);
|
||||
await db.execAsync(sql);
|
||||
return;
|
||||
} catch (error) {
|
||||
const msg = String(error);
|
||||
if (msg.includes('database is locked') && i < maxRetries - 1) {
|
||||
const delay = 200 * (i + 1);
|
||||
console.warn(`[MigrationManager] 数据库锁定,${delay}ms 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await db.execAsync(CREATE_MESSAGES_TABLE);
|
||||
await db.execAsync(CREATE_CONVERSATIONS_TABLE);
|
||||
await db.execAsync(CREATE_CONVERSATION_CACHE_TABLE);
|
||||
await db.execAsync(CREATE_CONVERSATION_LIST_CACHE_TABLE);
|
||||
await db.execAsync(CREATE_USERS_TABLE);
|
||||
await db.execAsync(CREATE_CURRENT_USER_CACHE_TABLE);
|
||||
await db.execAsync(CREATE_GROUPS_TABLE);
|
||||
await db.execAsync(CREATE_GROUP_MEMBERS_TABLE);
|
||||
await db.execAsync(CREATE_POSTS_CACHE_TABLE);
|
||||
export async function runMigrations(db: SQLiteDatabase): Promise<void> {
|
||||
await execWithRetry(db, 'PRAGMA busy_timeout = 10000;');
|
||||
|
||||
await execWithRetry(db, CREATE_MESSAGES_TABLE);
|
||||
await execWithRetry(db, CREATE_CONVERSATIONS_TABLE);
|
||||
await execWithRetry(db, CREATE_CONVERSATION_CACHE_TABLE);
|
||||
await execWithRetry(db, CREATE_CONVERSATION_LIST_CACHE_TABLE);
|
||||
await execWithRetry(db, CREATE_USERS_TABLE);
|
||||
await execWithRetry(db, CREATE_CURRENT_USER_CACHE_TABLE);
|
||||
await execWithRetry(db, CREATE_GROUPS_TABLE);
|
||||
await execWithRetry(db, CREATE_GROUP_MEMBERS_TABLE);
|
||||
await execWithRetry(db, CREATE_POSTS_CACHE_TABLE);
|
||||
|
||||
for (const indexSql of CREATE_MESSAGES_INDEXES) {
|
||||
await db.execAsync(indexSql);
|
||||
await execWithRetry(db, indexSql);
|
||||
}
|
||||
for (const indexSql of CREATE_GROUP_INDEXES) {
|
||||
await db.execAsync(indexSql);
|
||||
await execWithRetry(db, indexSql);
|
||||
}
|
||||
|
||||
await MIGRATE_MESSAGES_TABLE(db);
|
||||
|
||||
Reference in New Issue
Block a user