fix: Fix corrupt database, Add soundcloud and spotify embeds
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { randomBytes } from 'crypto';
|
||||
import { app } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as fsp from 'fs/promises';
|
||||
@@ -20,23 +21,93 @@ import {
|
||||
import { settings } from '../settings';
|
||||
|
||||
let applicationDataSource: DataSource | undefined;
|
||||
let dbFilePath = '';
|
||||
let dbBackupPath = '';
|
||||
|
||||
// SQLite files start with this 16-byte header string.
|
||||
const SQLITE_MAGIC = 'SQLite format 3\0';
|
||||
|
||||
export function getDataSource(): DataSource | undefined {
|
||||
return applicationDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when `data` looks like a valid SQLite file
|
||||
* (correct header magic and at least one complete page).
|
||||
*/
|
||||
function isValidSqlite(data: Uint8Array): boolean {
|
||||
if (data.length < 100)
|
||||
return false;
|
||||
|
||||
const header = Buffer.from(data.buffer, data.byteOffset, 16).toString('ascii');
|
||||
|
||||
return header === SQLITE_MAGIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Back up the current DB file so there is always a recovery point.
|
||||
* If the main file is corrupted/empty but a valid backup exists,
|
||||
* restore the backup before the app loads the database.
|
||||
*/
|
||||
function safeguardDbFile(): Uint8Array | undefined {
|
||||
if (!fs.existsSync(dbFilePath))
|
||||
return undefined;
|
||||
|
||||
const data = new Uint8Array(fs.readFileSync(dbFilePath));
|
||||
|
||||
if (isValidSqlite(data)) {
|
||||
fs.copyFileSync(dbFilePath, dbBackupPath);
|
||||
console.log('[DB] Backed up database to', dbBackupPath);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
console.warn(`[DB] ${dbFilePath} appears corrupt (${data.length} bytes) - checking backup`);
|
||||
|
||||
if (fs.existsSync(dbBackupPath)) {
|
||||
const backup = new Uint8Array(fs.readFileSync(dbBackupPath));
|
||||
|
||||
if (isValidSqlite(backup)) {
|
||||
fs.copyFileSync(dbBackupPath, dbFilePath);
|
||||
console.warn('[DB] Restored database from backup', dbBackupPath);
|
||||
|
||||
return backup;
|
||||
}
|
||||
|
||||
console.error('[DB] Backup is also invalid - starting with a fresh database');
|
||||
} else {
|
||||
console.error('[DB] No backup available - starting with a fresh database');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the database to disk atomically: write a temp file first,
|
||||
* then rename it over the real file. rename() is atomic on the same
|
||||
* filesystem, so a crash mid-write can never leave a half-written DB.
|
||||
*/
|
||||
async function atomicSave(data: Uint8Array): Promise<void> {
|
||||
const tmpPath = dbFilePath + '.tmp-' + randomBytes(6).toString('hex');
|
||||
|
||||
try {
|
||||
await fsp.writeFile(tmpPath, Buffer.from(data));
|
||||
await fsp.rename(tmpPath, dbFilePath);
|
||||
} catch (err) {
|
||||
await fsp.unlink(tmpPath).catch(() => {});
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function initializeDatabase(): Promise<void> {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const dbDir = path.join(userDataPath, 'metoyou');
|
||||
|
||||
await fsp.mkdir(dbDir, { recursive: true });
|
||||
const databaseFilePath = path.join(dbDir, settings.databaseName);
|
||||
dbFilePath = path.join(dbDir, settings.databaseName);
|
||||
dbBackupPath = dbFilePath + '.bak';
|
||||
|
||||
let database: Uint8Array | undefined;
|
||||
|
||||
if (fs.existsSync(databaseFilePath)) {
|
||||
database = fs.readFileSync(databaseFilePath);
|
||||
}
|
||||
const database = safeguardDbFile();
|
||||
|
||||
applicationDataSource = new DataSource({
|
||||
type: 'sqljs',
|
||||
@@ -59,12 +130,12 @@ export async function initializeDatabase(): Promise<void> {
|
||||
synchronize: false,
|
||||
logging: false,
|
||||
autoSave: true,
|
||||
location: databaseFilePath
|
||||
autoSaveCallback: atomicSave
|
||||
});
|
||||
|
||||
try {
|
||||
await applicationDataSource.initialize();
|
||||
console.log('[DB] Connection initialised at:', databaseFilePath);
|
||||
console.log('[DB] Connection initialised at:', dbFilePath);
|
||||
|
||||
try {
|
||||
await applicationDataSource.runMigrations();
|
||||
|
||||
Reference in New Issue
Block a user