Browse Source

fix linting

pull/134/head
Trent Larson 1 week ago
parent
commit
74989c2b64
  1. 198
      src/electron/main.ts
  2. 41
      src/electron/preload.js
  3. 21
      src/main.electron.ts
  4. 72
      src/services/platforms/ElectronPlatformService.ts

198
src/electron/main.ts

@ -75,27 +75,30 @@ try {
const getAppDataPath = async (): Promise<string> => { const getAppDataPath = async (): Promise<string> => {
try { try {
// Read config file directly // Read config file directly
const configPath = path.join(__dirname, '..', 'capacitor.config.json'); const configPath = path.join(__dirname, "..", "capacitor.config.json");
const configContent = await fs.promises.readFile(configPath, 'utf-8'); const configContent = await fs.promises.readFile(configPath, "utf-8");
const config = JSON.parse(configContent); const config = JSON.parse(configContent);
const linuxPath = config?.plugins?.CapacitorSQLite?.electronLinuxLocation; const linuxPath = config?.plugins?.CapacitorSQLite?.electronLinuxLocation;
if (linuxPath) { if (linuxPath) {
// Expand ~ to home directory // Expand ~ to home directory
const expandedPath = linuxPath.replace(/^~/, process.env.HOME || ''); const expandedPath = linuxPath.replace(/^~/, process.env.HOME || "");
logger.info("[Electron] Using configured database path:", expandedPath); logger.info("[Electron] Using configured database path:", expandedPath);
return expandedPath; return expandedPath;
} }
// Fallback to app.getPath if config path is not available // Fallback to app.getPath if config path is not available
const userDataPath = app.getPath('userData'); const userDataPath = app.getPath("userData");
logger.info("[Electron] Using fallback user data path:", userDataPath); logger.info("[Electron] Using fallback user data path:", userDataPath);
return userDataPath; return userDataPath;
} catch (error) { } catch (error) {
logger.error("[Electron] Error getting app data path:", error); logger.error("[Electron] Error getting app data path:", error);
// Fallback to app.getPath if anything fails // Fallback to app.getPath if anything fails
const userDataPath = app.getPath('userData'); const userDataPath = app.getPath("userData");
logger.info("[Electron] Using fallback user data path after error:", userDataPath); logger.info(
"[Electron] Using fallback user data path after error:",
userDataPath,
);
return userDataPath; return userDataPath;
} }
}; };
@ -112,7 +115,9 @@ const validateAndNormalizePath = async (filePath: string): Promise<string> => {
// Ensure it's within the app data directory // Ensure it's within the app data directory
const appDataPath = await getAppDataPath(); const appDataPath = await getAppDataPath();
if (!resolvedPath.startsWith(appDataPath)) { if (!resolvedPath.startsWith(appDataPath)) {
throw new Error(`Database path must be within app data directory: ${resolvedPath}`); throw new Error(
`Database path must be within app data directory: ${resolvedPath}`,
);
} }
// Normalize the path // Normalize the path
@ -124,7 +129,7 @@ const validateAndNormalizePath = async (filePath: string): Promise<string> => {
normalized: normalizedPath, normalized: normalizedPath,
appDataPath, appDataPath,
isAbsolute: path.isAbsolute(normalizedPath), isAbsolute: path.isAbsolute(normalizedPath),
isWithinAppData: normalizedPath.startsWith(appDataPath) isWithinAppData: normalizedPath.startsWith(appDataPath),
}); });
return normalizedPath; return normalizedPath;
@ -143,25 +148,37 @@ const ensureDirectoryExists = async (dirPath: string): Promise<void> => {
// Verify directory permissions // Verify directory permissions
try { try {
await fs.promises.access(normalizedPath, fs.constants.R_OK | fs.constants.W_OK); await fs.promises.access(
logger.info("[Electron] Database directory permissions verified:", normalizedPath); normalizedPath,
fs.constants.R_OK | fs.constants.W_OK,
);
logger.info(
"[Electron] Database directory permissions verified:",
normalizedPath,
);
} catch (error) { } catch (error) {
logger.error("[Electron] Database directory permission error:", error); logger.error("[Electron] Database directory permission error:", error);
throw new Error(`Database directory not accessible: ${normalizedPath}`); throw new Error(`Database directory not accessible: ${normalizedPath}`);
} }
// Test write permissions // Test write permissions
const testFile = path.join(normalizedPath, '.write-test'); const testFile = path.join(normalizedPath, ".write-test");
try { try {
await fs.promises.writeFile(testFile, 'test'); await fs.promises.writeFile(testFile, "test");
await fs.promises.unlink(testFile); await fs.promises.unlink(testFile);
logger.info("[Electron] Database directory write test passed:", normalizedPath); logger.info(
"[Electron] Database directory write test passed:",
normalizedPath,
);
} catch (error) { } catch (error) {
logger.error("[Electron] Database directory write test failed:", error); logger.error("[Electron] Database directory write test failed:", error);
throw new Error(`Database directory not writable: ${normalizedPath}`); throw new Error(`Database directory not writable: ${normalizedPath}`);
} }
} catch (error) { } catch (error) {
logger.error("[Electron] Failed to ensure database directory exists:", error); logger.error(
"[Electron] Failed to ensure database directory exists:",
error,
);
throw error; throw error;
} }
}; };
@ -192,14 +209,22 @@ const initializeDatabasePaths = async (): Promise<void> => {
await ensureDirectoryExists(dbDir); await ensureDirectoryExists(dbDir);
// Construct the database path // Construct the database path
dbPath = await validateAndNormalizePath(path.join(dbDir, 'timesafari.db')); dbPath = await validateAndNormalizePath(
path.join(dbDir, "timesafari.db"),
);
logger.info("[Electron] Database path initialized:", dbPath); logger.info("[Electron] Database path initialized:", dbPath);
// Verify the database file if it exists // Verify the database file if it exists
if (fs.existsSync(dbPath)) { if (fs.existsSync(dbPath)) {
try { try {
await fs.promises.access(dbPath, fs.constants.R_OK | fs.constants.W_OK); await fs.promises.access(
logger.info("[Electron] Existing database file permissions verified:", dbPath); dbPath,
fs.constants.R_OK | fs.constants.W_OK,
);
logger.info(
"[Electron] Existing database file permissions verified:",
dbPath,
);
} catch (error) { } catch (error) {
logger.error("[Electron] Database file permission error:", error); logger.error("[Electron] Database file permission error:", error);
throw new Error(`Database file not accessible: ${dbPath}`); throw new Error(`Database file not accessible: ${dbPath}`);
@ -256,10 +281,13 @@ async function initializeSQLite() {
readOnly: false, readOnly: false,
encryption: "no-encryption", encryption: "no-encryption",
useNative: true, useNative: true,
mode: "rwc" // Force read-write-create mode mode: "rwc", // Force read-write-create mode
}; };
logger.info("[Electron] Creating initial connection with options:", connectionOptions); logger.info(
"[Electron] Creating initial connection with options:",
connectionOptions,
);
// Log the actual path being used // Log the actual path being used
logger.info("[Electron] Using database path:", dbPath); logger.info("[Electron] Using database path:", dbPath);
@ -268,19 +296,24 @@ async function initializeSQLite() {
const db = await sqlitePlugin.createConnection(connectionOptions); const db = await sqlitePlugin.createConnection(connectionOptions);
if (!db || typeof db !== 'object') { if (!db || typeof db !== "object") {
throw new Error(`Failed to create database connection - invalid response. Path used: ${dbPath}`); throw new Error(
`Failed to create database connection - invalid response. Path used: ${dbPath}`,
);
} }
// Wait a moment for the connection to be fully established // Wait a moment for the connection to be fully established
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
// Verify the connection is working // Verify the connection is working
try { try {
const result = await db.query("PRAGMA journal_mode;"); const result = await db.query("PRAGMA journal_mode;");
logger.info("[Electron] Database connection verified:", result); logger.info("[Electron] Database connection verified:", result);
} catch (error) { } catch (error) {
logger.error("[Electron] Database connection verification failed:", error); logger.error(
"[Electron] Database connection verification failed:",
error,
);
throw error; throw error;
} }
@ -306,29 +339,32 @@ app.whenReady().then(async () => {
// Initialize database in background // Initialize database in background
initializeSQLite().catch((error) => { initializeSQLite().catch((error) => {
logger.error("[Electron] Database initialization failed, but continuing:", error); logger.error(
"[Electron] Database initialization failed, but continuing:",
error,
);
// Notify renderer about database status // Notify renderer about database status
if (!mainWindow.isDestroyed()) { if (!mainWindow.isDestroyed()) {
mainWindow.webContents.send('database-status', { mainWindow.webContents.send("database-status", {
status: 'error', status: "error",
error: error.message error: error.message,
}); });
} }
}); });
// Handle window close // Handle window close
mainWindow.on('closed', () => { mainWindow.on("closed", () => {
logger.info("[Electron] Main window closed"); logger.info("[Electron] Main window closed");
}); });
// Handle window close request // Handle window close request
mainWindow.on('close', (event) => { mainWindow.on("close", (event) => {
logger.info("[Electron] Window close requested"); logger.info("[Electron] Window close requested");
// Prevent immediate close if we're in the middle of something // Prevent immediate close if we're in the middle of something
if (mainWindow.webContents.isLoading()) { if (mainWindow.webContents.isLoading()) {
event.preventDefault(); event.preventDefault();
logger.info("[Electron] Deferring window close due to loading state"); logger.info("[Electron] Deferring window close due to loading state");
mainWindow.webContents.once('did-finish-load', () => { mainWindow.webContents.once("did-finish-load", () => {
mainWindow.close(); mainWindow.close();
}); });
} }
@ -378,25 +414,32 @@ function createWindow(): BrowserWindow {
sandbox: false, sandbox: false,
preload: preloadPath, preload: preloadPath,
webSecurity: true, webSecurity: true,
allowRunningInsecureContent: false allowRunningInsecureContent: false,
}, },
}); });
// Show window when ready // Show window when ready
mainWindow.once('ready-to-show', () => { mainWindow.once("ready-to-show", () => {
logger.info("[Electron] Window ready to show"); logger.info("[Electron] Window ready to show");
mainWindow.show(); mainWindow.show();
}); });
// Handle window errors // Handle window errors
mainWindow.webContents.on('render-process-gone', (_event, details) => { mainWindow.webContents.on("render-process-gone", (_event, details) => {
logger.error("[Electron] Render process gone:", details); logger.error("[Electron] Render process gone:", details);
}); });
mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription) => { mainWindow.webContents.on(
logger.error("[Electron] Page failed to load:", errorCode, errorDescription); "did-fail-load",
(_event, errorCode, errorDescription) => {
logger.error(
"[Electron] Page failed to load:",
errorCode,
errorDescription,
);
logger.error("[Electron] Failed URL:", mainWindow.webContents.getURL()); logger.error("[Electron] Failed URL:", mainWindow.webContents.getURL());
}); },
);
// Load the index.html // Load the index.html
let indexPath: string; let indexPath: string;
@ -405,11 +448,15 @@ function createWindow(): BrowserWindow {
if (app.isPackaged) { if (app.isPackaged) {
indexPath = path.join(process.resourcesPath, "www", "index.html"); indexPath = path.join(process.resourcesPath, "www", "index.html");
fileUrl = `file://${indexPath}`; fileUrl = `file://${indexPath}`;
logger.info("[Electron] App is packaged. Using process.resourcesPath for index.html"); logger.info(
"[Electron] App is packaged. Using process.resourcesPath for index.html",
);
} else { } else {
indexPath = path.resolve(__dirname, "www", "index.html"); indexPath = path.resolve(__dirname, "www", "index.html");
fileUrl = `file://${indexPath}`; fileUrl = `file://${indexPath}`;
logger.info("[Electron] App is not packaged. Using __dirname for index.html"); logger.info(
"[Electron] App is not packaged. Using __dirname for index.html",
);
} }
logger.info("[Electron] Resolved index.html path:", indexPath); logger.info("[Electron] Resolved index.html path:", indexPath);
@ -419,7 +466,9 @@ function createWindow(): BrowserWindow {
const loadIndexHtml = async (retryCount = 0): Promise<void> => { const loadIndexHtml = async (retryCount = 0): Promise<void> => {
try { try {
if (mainWindow.isDestroyed()) { if (mainWindow.isDestroyed()) {
logger.error("[Electron] Window was destroyed before loading index.html"); logger.error(
"[Electron] Window was destroyed before loading index.html",
);
return; return;
} }
@ -436,7 +485,7 @@ function createWindow(): BrowserWindow {
size: stats.size, size: stats.size,
mode: stats.mode, mode: stats.mode,
uid: stats.uid, uid: stats.uid,
gid: stats.gid gid: stats.gid,
}); });
// Try loadURL first // Try loadURL first
@ -445,19 +494,25 @@ function createWindow(): BrowserWindow {
await mainWindow.loadURL(fileUrl); await mainWindow.loadURL(fileUrl);
logger.info("[Electron] Successfully loaded index.html via loadURL"); logger.info("[Electron] Successfully loaded index.html via loadURL");
} catch (loadUrlError) { } catch (loadUrlError) {
logger.warn("[Electron] loadURL failed, trying loadFile:", loadUrlError); logger.warn(
"[Electron] loadURL failed, trying loadFile:",
loadUrlError,
);
// Fallback to loadFile // Fallback to loadFile
await mainWindow.loadFile(indexPath); await mainWindow.loadFile(indexPath);
logger.info("[Electron] Successfully loaded index.html via loadFile"); logger.info("[Electron] Successfully loaded index.html via loadFile");
} }
} catch (error: unknown) { } catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; const errorMessage =
error instanceof Error ? error.message : "Unknown error occurred";
logger.error("[Electron] Error loading index.html:", errorMessage); logger.error("[Electron] Error loading index.html:", errorMessage);
// Retry logic // Retry logic
if (retryCount < 3 && !mainWindow.isDestroyed()) { if (retryCount < 3 && !mainWindow.isDestroyed()) {
logger.info(`[Electron] Retrying index.html load (attempt ${retryCount + 1})`); logger.info(
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second `[Electron] Retrying index.html load (attempt ${retryCount + 1})`,
);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
return loadIndexHtml(retryCount + 1); return loadIndexHtml(retryCount + 1);
} }
@ -472,7 +527,9 @@ function createWindow(): BrowserWindow {
</body> </body>
</html> </html>
`; `;
await mainWindow.loadURL(`data:text/html,${encodeURIComponent(errorHtml)}`); await mainWindow.loadURL(
`data:text/html,${encodeURIComponent(errorHtml)}`,
);
} }
} }
}; };
@ -520,7 +577,12 @@ ipcMain.handle("sqlite-echo", async (_event, value) => {
try { try {
return await sqlitePlugin.echo({ value }); return await sqlitePlugin.echo({ value });
} catch (error) { } catch (error) {
logger.error("Error in sqlite-echo:", error, JSON.stringify(error), (error as any)?.stack); logger.error(
"Error in sqlite-echo:",
error,
JSON.stringify(error),
(error as any)?.stack,
);
throw error; throw error;
} }
}); });
@ -541,24 +603,33 @@ ipcMain.handle("sqlite-create-connection", async (_event, options) => {
readOnly: false, readOnly: false,
mode: "rwc", // Force read-write-create mode mode: "rwc", // Force read-write-create mode
encryption: "no-encryption", encryption: "no-encryption",
useNative: true useNative: true,
}; };
logger.info("[Electron] Creating database connection with options:", connectionOptions); logger.info(
"[Electron] Creating database connection with options:",
connectionOptions,
);
const result = await sqlitePlugin.createConnection(connectionOptions); const result = await sqlitePlugin.createConnection(connectionOptions);
if (!result || typeof result !== 'object') { if (!result || typeof result !== "object") {
throw new Error("Failed to create database connection - invalid response"); throw new Error(
"Failed to create database connection - invalid response",
);
} }
// Wait a moment for the connection to be fully established // Wait a moment for the connection to be fully established
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
try { try {
// Verify connection is not read-only // Verify connection is not read-only
const testResult = await result.query({ statement: "PRAGMA journal_mode;" }); const testResult = await result.query({
statement: "PRAGMA journal_mode;",
});
if (testResult?.values?.[0]?.journal_mode === "off") { if (testResult?.values?.[0]?.journal_mode === "off") {
logger.error("[Electron] Connection opened in read-only mode despite options"); logger.error(
"[Electron] Connection opened in read-only mode despite options",
);
throw new Error("Database connection opened in read-only mode"); throw new Error("Database connection opened in read-only mode");
} }
} catch (queryError) { } catch (queryError) {
@ -578,7 +649,12 @@ ipcMain.handle("sqlite-execute", async (_event, options) => {
try { try {
return await sqlitePlugin.execute(options); return await sqlitePlugin.execute(options);
} catch (error) { } catch (error) {
logger.error("Error in sqlite-execute:", error, JSON.stringify(error), (error as any)?.stack); logger.error(
"Error in sqlite-execute:",
error,
JSON.stringify(error),
(error as any)?.stack,
);
throw error; throw error;
} }
}); });
@ -587,7 +663,12 @@ ipcMain.handle("sqlite-query", async (_event, options) => {
try { try {
return await sqlitePlugin.query(options); return await sqlitePlugin.query(options);
} catch (error) { } catch (error) {
logger.error("Error in sqlite-query:", error, JSON.stringify(error), (error as any)?.stack); logger.error(
"Error in sqlite-query:",
error,
JSON.stringify(error),
(error as any)?.stack,
);
throw error; throw error;
} }
}); });
@ -596,7 +677,12 @@ ipcMain.handle("sqlite-close-connection", async (_event, options) => {
try { try {
return await sqlitePlugin.closeConnection(options); return await sqlitePlugin.closeConnection(options);
} catch (error) { } catch (error) {
logger.error("Error in sqlite-close-connection:", error, JSON.stringify(error), (error as any)?.stack); logger.error(
"Error in sqlite-close-connection:",
error,
JSON.stringify(error),
(error as any)?.stack,
);
throw error; throw error;
} }
}); });

41
src/electron/preload.js

@ -98,44 +98,55 @@ try {
} catch (error) { } catch (error) {
lastError = error; lastError = error;
if (attempt < MAX_RETRIES) { if (attempt < MAX_RETRIES) {
logger.warn(`SQLite operation failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`, error); logger.warn(
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); `SQLite operation failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`,
error,
);
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
} }
} }
} }
throw new Error(`SQLite operation failed after ${MAX_RETRIES} attempts: ${lastError?.message || 'Unknown error'}`); throw new Error(
`SQLite operation failed after ${MAX_RETRIES} attempts: ${lastError?.message || "Unknown error"}`,
);
}; };
const wrapOperation = (method) => { const wrapOperation = (method) => {
return async (...args) => { return async (...args) => {
try { try {
return await withRetry(ipcRenderer.invoke, 'sqlite-' + method, ...args); return await withRetry(
ipcRenderer.invoke,
"sqlite-" + method,
...args,
);
} catch (error) { } catch (error) {
logger.error(`SQLite ${method} failed:`, error); logger.error(`SQLite ${method} failed:`, error);
throw new Error(`Database operation failed: ${error.message || 'Unknown error'}`); throw new Error(
`Database operation failed: ${error.message || "Unknown error"}`,
);
} }
}; };
}; };
// Create a proxy that matches the CapacitorSQLite interface // Create a proxy that matches the CapacitorSQLite interface
return { return {
echo: wrapOperation('echo'), echo: wrapOperation("echo"),
createConnection: wrapOperation('create-connection'), createConnection: wrapOperation("create-connection"),
closeConnection: wrapOperation('close-connection'), closeConnection: wrapOperation("close-connection"),
execute: wrapOperation('execute'), execute: wrapOperation("execute"),
query: wrapOperation('query'), query: wrapOperation("query"),
run: wrapOperation('run'), run: wrapOperation("run"),
isAvailable: wrapOperation('is-available'), isAvailable: wrapOperation("is-available"),
getPlatform: () => Promise.resolve('electron'), getPlatform: () => Promise.resolve("electron"),
// Add other methods as needed // Add other methods as needed
}; };
}; };
// Expose only the CapacitorSQLite proxy // Expose only the CapacitorSQLite proxy
contextBridge.exposeInMainWorld('CapacitorSQLite', createSQLiteProxy()); contextBridge.exposeInMainWorld("CapacitorSQLite", createSQLiteProxy());
// Remove the duplicate electron.sqlite bridge // Remove the duplicate electron.sqlite bridge
contextBridge.exposeInMainWorld('electron', { contextBridge.exposeInMainWorld("electron", {
// Keep other electron APIs but remove sqlite // Keep other electron APIs but remove sqlite
getPath, getPath,
send: (channel, data) => { send: (channel, data) => {

21
src/main.electron.ts

@ -12,22 +12,30 @@ async function initializeSQLite() {
try { try {
const isAvailable = await window.CapacitorSQLite.isAvailable(); const isAvailable = await window.CapacitorSQLite.isAvailable();
if (isAvailable) { if (isAvailable) {
logger.info("[Electron] SQLite plugin bridge initialized successfully"); logger.info(
"[Electron] SQLite plugin bridge initialized successfully",
);
return true; return true;
} }
} catch (error) { } catch (error) {
logger.warn(`[Electron] SQLite not available yet (attempt ${retries + 1}/${maxRetries}):`, error); logger.warn(
`[Electron] SQLite not available yet (attempt ${retries + 1}/${maxRetries}):`,
error,
);
} }
retries++; retries++;
if (retries < maxRetries) { if (retries < maxRetries) {
await new Promise(resolve => setTimeout(resolve, retryDelay)); await new Promise((resolve) => setTimeout(resolve, retryDelay));
} }
} }
throw new Error("SQLite plugin not available after maximum retries"); throw new Error("SQLite plugin not available after maximum retries");
} catch (error) { } catch (error) {
logger.error("[Electron] Failed to initialize SQLite plugin bridge:", error); logger.error(
"[Electron] Failed to initialize SQLite plugin bridge:",
error,
);
throw error; throw error;
} }
} }
@ -52,11 +60,12 @@ initializeSQLite()
logger.info("[Electron] SQLite initialized, mounting app..."); logger.info("[Electron] SQLite initialized, mounting app...");
app.mount("#app"); app.mount("#app");
}) })
.catch(error => { .catch((error) => {
logger.error("[Electron] Failed to initialize app:", error); logger.error("[Electron] Failed to initialize app:", error);
// Show error to user // Show error to user
const errorDiv = document.createElement("div"); const errorDiv = document.createElement("div");
errorDiv.style.cssText = "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #ffebee; color: #c62828; padding: 20px; border-radius: 4px; text-align: center; max-width: 80%;"; errorDiv.style.cssText =
"position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #ffebee; color: #c62828; padding: 20px; border-radius: 4px; text-align: center; max-width: 80%;";
errorDiv.innerHTML = ` errorDiv.innerHTML = `
<h2>Failed to Initialize Database</h2> <h2>Failed to Initialize Database</h2>
<p>There was an error initializing the database. Please try restarting the application.</p> <p>There was an error initializing the database. Please try restarting the application.</p>

72
src/services/platforms/ElectronPlatformService.ts

@ -49,7 +49,7 @@ export class ElectronPlatformService implements PlatformService {
try { try {
await this.db.close(); await this.db.close();
} catch (e) { } catch (e) {
logger.warn('Error closing existing connection:', e); logger.warn("Error closing existing connection:", e);
} }
this.db = null; this.db = null;
} }
@ -61,9 +61,9 @@ export class ElectronPlatformService implements PlatformService {
this.dbConnectionErrorLogged = false; this.dbConnectionErrorLogged = false;
// Wait a moment for cleanup // Wait a moment for cleanup
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500));
} catch (error) { } catch (error) {
logger.error('Error resetting connection:', error); logger.error("Error resetting connection:", error);
throw error; throw error;
} }
} }
@ -71,7 +71,7 @@ export class ElectronPlatformService implements PlatformService {
private async initializeDatabase(): Promise<void> { private async initializeDatabase(): Promise<void> {
// If we have a fatal error, try to recover // If we have a fatal error, try to recover
if (this.dbFatalError) { if (this.dbFatalError) {
logger.info('Attempting to recover from fatal error state...'); logger.info("Attempting to recover from fatal error state...");
await this.resetConnection(); await this.resetConnection();
} }
@ -99,30 +99,32 @@ export class ElectronPlatformService implements PlatformService {
logger.info("Calling createConnection with:", { logger.info("Calling createConnection with:", {
dbName: this.dbName, dbName: this.dbName,
readOnly: false, readOnly: false,
encryption: 'no-encryption', encryption: "no-encryption",
version: 1, version: 1,
useNative: true useNative: true,
}); });
// Create connection // Create connection
this.db = await this.sqlite.createConnection( this.db = await this.sqlite.createConnection(
this.dbName, // database name this.dbName, // database name
false, // readOnly false, // readOnly
'no-encryption', // encryption "no-encryption", // encryption
1, // version 1, // version
true // useNative true, // useNative
); );
logger.info("createConnection result:", this.db); logger.info("createConnection result:", this.db);
if (!this.db || typeof this.db.execute !== 'function') { if (!this.db || typeof this.db.execute !== "function") {
throw new Error("Failed to create a valid database connection"); throw new Error("Failed to create a valid database connection");
} }
// Verify connection is not read-only // Verify connection is not read-only
const journalMode = await this.db.query('PRAGMA journal_mode;'); const journalMode = await this.db.query("PRAGMA journal_mode;");
if (journalMode?.values?.[0]?.journal_mode === 'off') { if (journalMode?.values?.[0]?.journal_mode === "off") {
throw new Error('Database opened in read-only mode despite options'); throw new Error(
"Database opened in read-only mode despite options",
);
} }
// Run migrations // Run migrations
@ -133,14 +135,18 @@ export class ElectronPlatformService implements PlatformService {
this.dbConnectionErrorLogged = false; this.dbConnectionErrorLogged = false;
this.initialized = true; this.initialized = true;
return; return;
} catch (error) { } catch (error) {
lastError = error instanceof Error ? error : new Error(String(error)); lastError = error instanceof Error ? error : new Error(String(error));
retryCount++; retryCount++;
if (retryCount < this.MAX_RETRIES) { if (retryCount < this.MAX_RETRIES) {
logger.warn(`Database initialization attempt ${retryCount}/${this.MAX_RETRIES} failed:`, error); logger.warn(
await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY)); `Database initialization attempt ${retryCount}/${this.MAX_RETRIES} failed:`,
error,
);
await new Promise((resolve) =>
setTimeout(resolve, this.RETRY_DELAY),
);
await this.resetConnection(); await this.resetConnection();
} }
} }
@ -149,12 +155,18 @@ export class ElectronPlatformService implements PlatformService {
// If we get here, all retries failed // If we get here, all retries failed
this.dbFatalError = true; this.dbFatalError = true;
if (!this.dbConnectionErrorLogged) { if (!this.dbConnectionErrorLogged) {
logger.error("[Electron] Error initializing SQLite database after all retries:", lastError); logger.error(
"[Electron] Error initializing SQLite database after all retries:",
lastError,
);
this.dbConnectionErrorLogged = true; this.dbConnectionErrorLogged = true;
} }
this.initialized = false; this.initialized = false;
this.initializationPromise = null; this.initializationPromise = null;
throw lastError || new Error("Failed to initialize database after all retries"); throw (
lastError ||
new Error("Failed to initialize database after all retries")
);
})(); })();
return this.initializationPromise; return this.initializationPromise;
@ -413,7 +425,9 @@ export class ElectronPlatformService implements PlatformService {
params?: unknown[], params?: unknown[],
): Promise<{ changes: number; lastId?: number }> { ): Promise<{ changes: number; lastId?: number }> {
if (this.dbFatalError) { if (this.dbFatalError) {
throw new Error("Database is in a fatal error state. Please restart the app."); throw new Error(
"Database is in a fatal error state. Please restart the app.",
);
} }
try { try {
await this.initializeDatabase(); await this.initializeDatabase();
@ -440,22 +454,26 @@ export class ElectronPlatformService implements PlatformService {
try { try {
await this.initializeDatabase(); await this.initializeDatabase();
} catch (error) { } catch (error) {
console.error('Failed to initialize database:', error); logger.error("Failed to initialize database:", error);
throw new Error(`Database initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`); throw new Error(
`Database initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`,
);
} }
} }
async query<T>(sql: string, params: any[] = []): Promise<T[]> { async query<T>(sql: string, params: any[] = []): Promise<T[]> {
if (this.dbFatalError) { if (this.dbFatalError) {
throw new Error("Database is in a fatal error state. Please restart the app."); throw new Error(
"Database is in a fatal error state. Please restart the app.",
);
} }
if (!this.initialized) { if (!this.initialized) {
throw new Error('Database not initialized. Call initialize() first.'); throw new Error("Database not initialized. Call initialize() first.");
} }
return this.initializeDatabase().then(() => { return this.initializeDatabase().then(() => {
if (!this.db) { if (!this.db) {
throw new Error('Database not initialized after initialization'); throw new Error("Database not initialized after initialization");
} }
return this.db.query(sql, params).then((result) => { return this.db.query(sql, params).then((result) => {
if (!result?.values) { if (!result?.values) {
@ -468,7 +486,7 @@ export class ElectronPlatformService implements PlatformService {
async execute(sql: string, params: any[] = []): Promise<void> { async execute(sql: string, params: any[] = []): Promise<void> {
if (!this.initialized) { if (!this.initialized) {
throw new Error('Database not initialized. Call initialize() first.'); throw new Error("Database not initialized. Call initialize() first.");
} }
await this.initializeDatabase().then(() => { await this.initializeDatabase().then(() => {
@ -486,8 +504,10 @@ export class ElectronPlatformService implements PlatformService {
this.initialized = false; this.initialized = false;
this.db = null; this.db = null;
} catch (error) { } catch (error) {
console.error('Failed to close database:', error); logger.error("Failed to close database:", error);
throw new Error(`Failed to close database: ${error instanceof Error ? error.message : 'Unknown error'}`); throw new Error(
`Failed to close database: ${error instanceof Error ? error.message : "Unknown error"}`,
);
} }
} }
} }

Loading…
Cancel
Save