|
|
@ -3,20 +3,16 @@ |
|
|
|
* Handles database path setup, plugin initialization, and IPC handlers |
|
|
|
* |
|
|
|
* Database Path Handling: |
|
|
|
* - Uses XDG Base Directory Specification for data storage |
|
|
|
* - Uses plugin's default path (/home/matthew/Databases/TimeSafari) |
|
|
|
* - Uses modern plugin conventions (Capacitor 6+ / Plugin 6.x+) |
|
|
|
* - Supports custom database names without enforced extensions |
|
|
|
* - Maintains backward compatibility with legacy paths |
|
|
|
* |
|
|
|
* XDG Base Directory Specification: |
|
|
|
* - Uses $XDG_DATA_HOME (defaults to ~/.local/share) for data files |
|
|
|
* - Falls back to legacy path if XDG environment variables are not set |
|
|
|
* - Lets plugin handle SQLite suffix and database naming |
|
|
|
* |
|
|
|
* @author Matthew Raymer |
|
|
|
*/ |
|
|
|
|
|
|
|
import { app, ipcMain } from 'electron'; |
|
|
|
import { CapacitorSQLite } from '@capacitor-community/sqlite/electron/dist/plugin.js'; |
|
|
|
import * as SQLiteModule from '@capacitor-community/sqlite/electron/dist/plugin.js'; |
|
|
|
import fs from 'fs'; |
|
|
|
import path from 'path'; |
|
|
|
import os from 'os'; |
|
|
@ -30,50 +26,64 @@ const logger = { |
|
|
|
debug: (...args: unknown[]) => console.debug('[SQLite]', ...args), |
|
|
|
}; |
|
|
|
|
|
|
|
// Database path resolution utilities following XDG Base Directory Specification
|
|
|
|
// Add debug logging utility
|
|
|
|
const debugLog = (stage: string, data?: any) => { |
|
|
|
const timestamp = new Date().toISOString(); |
|
|
|
if (data) { |
|
|
|
logger.debug(`[${timestamp}] ${stage}:`, data); |
|
|
|
} else { |
|
|
|
logger.debug(`[${timestamp}] ${stage}`); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// Helper to get all methods from an object, including prototype chain
|
|
|
|
const getAllMethods = (obj: any): string[] => { |
|
|
|
if (!obj) return []; |
|
|
|
const methods = new Set<string>(); |
|
|
|
|
|
|
|
// Get own methods
|
|
|
|
Object.getOwnPropertyNames(obj).forEach(prop => { |
|
|
|
if (typeof obj[prop] === 'function') { |
|
|
|
methods.add(prop); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// Get prototype methods
|
|
|
|
let proto = Object.getPrototypeOf(obj); |
|
|
|
while (proto && proto !== Object.prototype) { |
|
|
|
Object.getOwnPropertyNames(proto).forEach(prop => { |
|
|
|
if (typeof proto[prop] === 'function' && !methods.has(prop)) { |
|
|
|
methods.add(prop); |
|
|
|
} |
|
|
|
}); |
|
|
|
proto = Object.getPrototypeOf(proto); |
|
|
|
} |
|
|
|
|
|
|
|
return Array.from(methods); |
|
|
|
}; |
|
|
|
|
|
|
|
// Database path resolution utilities
|
|
|
|
const getAppDataPath = async (): Promise<string> => { |
|
|
|
try { |
|
|
|
// Get XDG_DATA_HOME or fallback to default
|
|
|
|
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'); |
|
|
|
logger.info('XDG_DATA_HOME:', xdgDataHome); |
|
|
|
|
|
|
|
// Create app data directory following XDG spec
|
|
|
|
const appDataDir = path.join(xdgDataHome, 'timesafari'); |
|
|
|
// Use plugin's actual default path
|
|
|
|
const appDataDir = path.join(os.homedir(), 'Databases', 'TimeSafari'); |
|
|
|
logger.info('App data directory:', appDataDir); |
|
|
|
|
|
|
|
// Ensure directory exists with proper permissions (700)
|
|
|
|
// Ensure directory exists with proper permissions (755)
|
|
|
|
if (!fs.existsSync(appDataDir)) { |
|
|
|
await fs.promises.mkdir(appDataDir, { |
|
|
|
recursive: true, |
|
|
|
mode: 0o700 // rwx------ for security
|
|
|
|
mode: 0o755 // rwxr-xr-x to match plugin's expectations
|
|
|
|
}); |
|
|
|
} else { |
|
|
|
// Ensure existing directory has correct permissions
|
|
|
|
await fs.promises.chmod(appDataDir, 0o700); |
|
|
|
await fs.promises.chmod(appDataDir, 0o755); |
|
|
|
} |
|
|
|
|
|
|
|
return appDataDir; |
|
|
|
} catch (error) { |
|
|
|
logger.error('Error getting app data path:', error); |
|
|
|
// Fallback to legacy path in user's home
|
|
|
|
const fallbackDir = path.join(os.homedir(), '.timesafari'); |
|
|
|
logger.warn('Using fallback app data directory:', fallbackDir); |
|
|
|
|
|
|
|
try { |
|
|
|
if (!fs.existsSync(fallbackDir)) { |
|
|
|
await fs.promises.mkdir(fallbackDir, { |
|
|
|
recursive: true, |
|
|
|
mode: 0o700 |
|
|
|
}); |
|
|
|
} else { |
|
|
|
await fs.promises.chmod(fallbackDir, 0o700); |
|
|
|
} |
|
|
|
} catch (mkdirError) { |
|
|
|
logger.error('Failed to create fallback directory:', mkdirError); |
|
|
|
throw new Error('Could not create any suitable data directory'); |
|
|
|
} |
|
|
|
|
|
|
|
return fallbackDir; |
|
|
|
throw error; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
@ -89,40 +99,45 @@ const initializeDatabasePaths = async (): Promise<void> => { |
|
|
|
|
|
|
|
dbPathInitializationPromise = (async () => { |
|
|
|
try { |
|
|
|
// Get the app data directory in user's home
|
|
|
|
// Get the absolute app data directory
|
|
|
|
const absolutePath = await getAppDataPath(); |
|
|
|
logger.info('Absolute database path:', absolutePath); |
|
|
|
|
|
|
|
// For the plugin, we need to use a path relative to the electron folder
|
|
|
|
// So we'll use a relative path from the electron folder to the home directory
|
|
|
|
const electronPath = app.getAppPath(); |
|
|
|
const relativeToElectron = path.relative(electronPath, absolutePath); |
|
|
|
dbDir = relativeToElectron; |
|
|
|
// Use absolute paths for everything
|
|
|
|
dbDir = absolutePath; |
|
|
|
|
|
|
|
logger.info('Database directory (relative to electron):', dbDir); |
|
|
|
// Use the exact format the plugin expects
|
|
|
|
const dbFileName = 'timesafariSQLite.db'; |
|
|
|
dbPath = path.join(dbDir, dbFileName); |
|
|
|
|
|
|
|
// Ensure directory exists using absolute path
|
|
|
|
logger.info('Database directory:', dbDir); |
|
|
|
logger.info('Database path:', dbPath); |
|
|
|
|
|
|
|
// Ensure directory exists
|
|
|
|
if (!fs.existsSync(absolutePath)) { |
|
|
|
await fs.promises.mkdir(absolutePath, { recursive: true }); |
|
|
|
} |
|
|
|
|
|
|
|
// Use modern plugin conventions - no enforced extension
|
|
|
|
const baseName = 'timesafariSQLite'; |
|
|
|
dbPath = path.join(dbDir, baseName); |
|
|
|
logger.info('Database path initialized (relative to electron):', dbPath); |
|
|
|
|
|
|
|
// Verify we can write to the directory using absolute path
|
|
|
|
// Verify we can write to the directory
|
|
|
|
const testFile = path.join(absolutePath, '.write-test'); |
|
|
|
try { |
|
|
|
await fs.promises.writeFile(testFile, 'test'); |
|
|
|
await fs.promises.unlink(testFile); |
|
|
|
logger.info('Directory write test successful'); |
|
|
|
} catch (error) { |
|
|
|
throw new Error(`Cannot write to database directory: ${error instanceof Error ? error.message : 'Unknown error'}`); |
|
|
|
} |
|
|
|
|
|
|
|
// Set environment variable for the plugin using relative path
|
|
|
|
process.env.CAPACITOR_SQLITE_DB_PATH = dbDir; |
|
|
|
logger.info('Set CAPACITOR_SQLITE_DB_PATH:', process.env.CAPACITOR_SQLITE_DB_PATH); |
|
|
|
// Verify the path exists and is writable
|
|
|
|
logger.info('Verifying database directory permissions...'); |
|
|
|
const stats = await fs.promises.stat(absolutePath); |
|
|
|
logger.info('Directory permissions:', { |
|
|
|
mode: stats.mode.toString(8), |
|
|
|
uid: stats.uid, |
|
|
|
gid: stats.gid, |
|
|
|
isDirectory: stats.isDirectory(), |
|
|
|
isWritable: !!(stats.mode & 0o200) |
|
|
|
}); |
|
|
|
|
|
|
|
dbPathInitialized = true; |
|
|
|
} catch (error) { |
|
|
@ -144,6 +159,37 @@ let sqlitePlugin: any = null; |
|
|
|
let sqliteInitialized = false; |
|
|
|
let sqliteInitializationPromise: Promise<void> | null = null; |
|
|
|
|
|
|
|
// Helper to get the actual plugin instance
|
|
|
|
const getActualPluginInstance = (plugin: any): any => { |
|
|
|
// Try to get the actual instance through various means
|
|
|
|
const possibleInstances = [ |
|
|
|
plugin, // The object itself
|
|
|
|
plugin.default, // If it's a module
|
|
|
|
plugin.CapacitorSQLite, // If it's a namespace
|
|
|
|
Object.getPrototypeOf(plugin), // Its prototype
|
|
|
|
plugin.constructor?.prototype, // Constructor's prototype
|
|
|
|
Object.getPrototypeOf(Object.getPrototypeOf(plugin)) // Grandparent prototype
|
|
|
|
]; |
|
|
|
|
|
|
|
// Find the first instance that has createConnection
|
|
|
|
const instance = possibleInstances.find(inst => |
|
|
|
inst && typeof inst === 'object' && typeof inst.createConnection === 'function' |
|
|
|
); |
|
|
|
|
|
|
|
if (!instance) { |
|
|
|
debugLog('No valid plugin instance found in:', { |
|
|
|
possibleInstances: possibleInstances.map(inst => ({ |
|
|
|
type: typeof inst, |
|
|
|
constructor: inst?.constructor?.name, |
|
|
|
hasCreateConnection: inst && typeof inst.createConnection === 'function' |
|
|
|
})) |
|
|
|
}); |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
return instance; |
|
|
|
}; |
|
|
|
|
|
|
|
export async function initializeSQLite(): Promise<void> { |
|
|
|
if (sqliteInitializationPromise) { |
|
|
|
logger.info('SQLite initialization already in progress, waiting...'); |
|
|
@ -168,12 +214,67 @@ export async function initializeSQLite(): Promise<void> { |
|
|
|
|
|
|
|
// Create plugin instance
|
|
|
|
logger.info('Creating SQLite plugin instance...'); |
|
|
|
sqlitePlugin = new CapacitorSQLite(); |
|
|
|
debugLog('SQLite module:', { |
|
|
|
hasDefault: !!SQLiteModule.default, |
|
|
|
defaultType: typeof SQLiteModule.default, |
|
|
|
defaultKeys: SQLiteModule.default ? Object.keys(SQLiteModule.default) : null, |
|
|
|
hasCapacitorSQLite: !!SQLiteModule.CapacitorSQLite, |
|
|
|
CapacitorSQLiteType: typeof SQLiteModule.CapacitorSQLite |
|
|
|
}); |
|
|
|
|
|
|
|
if (!sqlitePlugin) { |
|
|
|
// Try both the class and default export
|
|
|
|
let rawPlugin; |
|
|
|
try { |
|
|
|
// Try default export first
|
|
|
|
if (SQLiteModule.default?.CapacitorSQLite) { |
|
|
|
debugLog('Using default export CapacitorSQLite'); |
|
|
|
rawPlugin = new SQLiteModule.default.CapacitorSQLite(); |
|
|
|
} else { |
|
|
|
debugLog('Using direct CapacitorSQLite class'); |
|
|
|
rawPlugin = new CapacitorSQLite(); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
debugLog('Error creating plugin instance:', error); |
|
|
|
throw error; |
|
|
|
} |
|
|
|
|
|
|
|
if (!rawPlugin) { |
|
|
|
throw new Error('Failed to create SQLite plugin instance'); |
|
|
|
} |
|
|
|
|
|
|
|
// Get the actual plugin instance
|
|
|
|
sqlitePlugin = getActualPluginInstance(rawPlugin); |
|
|
|
|
|
|
|
if (!sqlitePlugin) { |
|
|
|
throw new Error('Failed to get valid SQLite plugin instance'); |
|
|
|
} |
|
|
|
|
|
|
|
// Debug plugin instance
|
|
|
|
debugLog('Plugin instance details:', { |
|
|
|
type: typeof sqlitePlugin, |
|
|
|
constructor: sqlitePlugin.constructor?.name, |
|
|
|
prototype: Object.getPrototypeOf(sqlitePlugin)?.constructor?.name, |
|
|
|
allMethods: getAllMethods(sqlitePlugin), |
|
|
|
hasCreateConnection: typeof sqlitePlugin.createConnection === 'function', |
|
|
|
createConnectionType: typeof sqlitePlugin.createConnection, |
|
|
|
createConnectionProto: Object.getPrototypeOf(sqlitePlugin.createConnection)?.constructor?.name, |
|
|
|
// Add more details about the instance
|
|
|
|
isProxy: sqlitePlugin.constructor?.name === 'Proxy', |
|
|
|
descriptors: Object.getOwnPropertyDescriptors(sqlitePlugin), |
|
|
|
prototypeChain: (() => { |
|
|
|
const chain = []; |
|
|
|
let proto = sqlitePlugin; |
|
|
|
while (proto && proto !== Object.prototype) { |
|
|
|
chain.push({ |
|
|
|
name: proto.constructor?.name, |
|
|
|
methods: Object.getOwnPropertyNames(proto) |
|
|
|
}); |
|
|
|
proto = Object.getPrototypeOf(proto); |
|
|
|
} |
|
|
|
return chain; |
|
|
|
})() |
|
|
|
}); |
|
|
|
|
|
|
|
// Test the plugin
|
|
|
|
logger.info('Testing SQLite plugin...'); |
|
|
|
const echoResult = await sqlitePlugin.echo({ value: 'test' }); |
|
|
@ -182,47 +283,239 @@ export async function initializeSQLite(): Promise<void> { |
|
|
|
} |
|
|
|
logger.info('SQLite plugin echo test successful'); |
|
|
|
|
|
|
|
// Initialize database connection using modern plugin conventions
|
|
|
|
// Initialize database connection using plugin's default format
|
|
|
|
debugLog('Starting connection creation'); |
|
|
|
debugLog('Plugin utilities:', { |
|
|
|
sqliteUtil: sqlitePlugin.sqliteUtil ? Object.keys(sqlitePlugin.sqliteUtil) : null, |
|
|
|
fileUtil: sqlitePlugin.fileUtil ? Object.keys(sqlitePlugin.fileUtil) : null, |
|
|
|
globalUtil: sqlitePlugin.globalUtil ? Object.keys(sqlitePlugin.globalUtil) : null, |
|
|
|
prototype: Object.getOwnPropertyNames(Object.getPrototypeOf(sqlitePlugin)) |
|
|
|
}); |
|
|
|
|
|
|
|
// Get the database path using Path.join
|
|
|
|
const fullDbPath = sqlitePlugin.fileUtil?.Path?.join(dbDir, 'timesafariSQLite.db'); |
|
|
|
debugLog('Database path from Path.join:', { |
|
|
|
path: fullDbPath, |
|
|
|
exists: fullDbPath ? fs.existsSync(fullDbPath) : false, |
|
|
|
pathType: typeof sqlitePlugin.fileUtil?.Path?.join |
|
|
|
}); |
|
|
|
|
|
|
|
// Let plugin handle database naming and suffix
|
|
|
|
const connectionOptions = { |
|
|
|
database: 'timesafariSQLite', |
|
|
|
database: 'timesafari', // Base name only
|
|
|
|
version: 1, |
|
|
|
readOnly: false, |
|
|
|
encryption: 'no-encryption', |
|
|
|
useNative: true, |
|
|
|
mode: 'rwc', |
|
|
|
location: dbDir // Use path relative to electron folder
|
|
|
|
location: 'default', // Let plugin handle path resolution
|
|
|
|
path: fullDbPath // Add explicit path
|
|
|
|
}; |
|
|
|
|
|
|
|
logger.info('Creating initial database connection with options:', { |
|
|
|
debugLog('Connection options:', { |
|
|
|
...connectionOptions, |
|
|
|
expectedPath: dbPath // Path relative to electron folder
|
|
|
|
absoluteLocation: dbDir, |
|
|
|
fullDbPath, |
|
|
|
expectedBehavior: 'Using prototype methods with explicit path' |
|
|
|
}); |
|
|
|
|
|
|
|
const db = await sqlitePlugin.createConnection(connectionOptions); |
|
|
|
// Verify directory state before connection
|
|
|
|
try { |
|
|
|
const dirStats = await fs.promises.stat(dbDir); |
|
|
|
debugLog('Directory state before connection:', { |
|
|
|
exists: true, |
|
|
|
isDirectory: dirStats.isDirectory(), |
|
|
|
mode: dirStats.mode.toString(8), |
|
|
|
uid: dirStats.uid, |
|
|
|
gid: dirStats.gid, |
|
|
|
size: dirStats.size, |
|
|
|
atime: dirStats.atime, |
|
|
|
mtime: dirStats.mtime, |
|
|
|
ctime: dirStats.ctime |
|
|
|
}); |
|
|
|
|
|
|
|
if (!db || typeof db !== 'object') { |
|
|
|
throw new Error(`Failed to create database connection - invalid response. Path: ${dbPath}`); |
|
|
|
// Check if database file already exists
|
|
|
|
try { |
|
|
|
const dbStats = await fs.promises.stat(fullDbPath); |
|
|
|
debugLog('Database file exists:', { |
|
|
|
size: dbStats.size, |
|
|
|
mode: dbStats.mode.toString(8), |
|
|
|
mtime: dbStats.mtime |
|
|
|
}); |
|
|
|
} catch (error) { |
|
|
|
debugLog('Database file does not exist yet (this is expected)'); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
debugLog('Error checking directory state:', error); |
|
|
|
} |
|
|
|
|
|
|
|
// Verify connection with a simple query
|
|
|
|
logger.info('Verifying database connection...'); |
|
|
|
// Create connection using prototype methods
|
|
|
|
debugLog('Calling createConnection...'); |
|
|
|
let db; |
|
|
|
try { |
|
|
|
const result = await db.query({ statement: 'SELECT 1 as test;' }); |
|
|
|
if (!result || !result.values || result.values.length === 0) { |
|
|
|
throw new Error('Database connection test query returned no results'); |
|
|
|
// Get the prototype methods
|
|
|
|
const proto = Object.getPrototypeOf(sqlitePlugin); |
|
|
|
debugLog('Prototype methods:', { |
|
|
|
methods: Object.getOwnPropertyNames(proto), |
|
|
|
createConnectionType: typeof proto.createConnection, |
|
|
|
openType: typeof proto.open, |
|
|
|
createConnectionProto: proto.createConnection?.prototype?.constructor?.name |
|
|
|
}); |
|
|
|
|
|
|
|
// Try to create connection using prototype method
|
|
|
|
if (typeof proto.createConnection === 'function') { |
|
|
|
debugLog('Using prototype createConnection'); |
|
|
|
|
|
|
|
// First try to create the connection
|
|
|
|
const boundCreateConnection = proto.createConnection.bind(sqlitePlugin); |
|
|
|
const result = await boundCreateConnection(connectionOptions); |
|
|
|
|
|
|
|
debugLog('createConnection raw result:', { |
|
|
|
type: typeof result, |
|
|
|
isNull: result === null, |
|
|
|
isUndefined: result === undefined, |
|
|
|
value: result |
|
|
|
}); |
|
|
|
|
|
|
|
// Try to open the database directly
|
|
|
|
debugLog('Attempting to open database directly...'); |
|
|
|
try { |
|
|
|
const openResult = await sqlitePlugin.open({ |
|
|
|
database: connectionOptions.database, |
|
|
|
path: fullDbPath, |
|
|
|
version: connectionOptions.version, |
|
|
|
readOnly: connectionOptions.readOnly, |
|
|
|
encryption: connectionOptions.encryption, |
|
|
|
useNative: connectionOptions.useNative, |
|
|
|
mode: connectionOptions.mode |
|
|
|
}); |
|
|
|
|
|
|
|
debugLog('open result:', { |
|
|
|
type: typeof openResult, |
|
|
|
isNull: openResult === null, |
|
|
|
isUndefined: openResult === undefined, |
|
|
|
value: openResult |
|
|
|
}); |
|
|
|
|
|
|
|
// Try to get the connection after opening
|
|
|
|
if (sqlitePlugin.getDatabaseConnectionOrThrowError) { |
|
|
|
debugLog('Getting connection after open'); |
|
|
|
db = await sqlitePlugin.getDatabaseConnectionOrThrowError(connectionOptions.database); |
|
|
|
} |
|
|
|
|
|
|
|
// Verify the database exists
|
|
|
|
const exists = await sqlitePlugin.isDBExists({ |
|
|
|
database: connectionOptions.database, |
|
|
|
path: fullDbPath |
|
|
|
}); |
|
|
|
|
|
|
|
debugLog('Database exists check:', { |
|
|
|
exists, |
|
|
|
path: fullDbPath, |
|
|
|
fileExists: fs.existsSync(fullDbPath) |
|
|
|
}); |
|
|
|
|
|
|
|
// If database doesn't exist, try to create it
|
|
|
|
if (!exists) { |
|
|
|
debugLog('Database does not exist, attempting to create...'); |
|
|
|
// Create an empty file to ensure the directory is writable
|
|
|
|
await fs.promises.writeFile(fullDbPath, ''); |
|
|
|
|
|
|
|
// Try opening again
|
|
|
|
await sqlitePlugin.open({ |
|
|
|
database: connectionOptions.database, |
|
|
|
path: fullDbPath, |
|
|
|
version: connectionOptions.version, |
|
|
|
readOnly: false, // Force read-write for creation
|
|
|
|
encryption: connectionOptions.encryption, |
|
|
|
useNative: connectionOptions.useNative, |
|
|
|
mode: 'rwc' // Force create mode
|
|
|
|
}); |
|
|
|
|
|
|
|
// Verify creation
|
|
|
|
const created = await sqlitePlugin.isDBExists({ |
|
|
|
database: connectionOptions.database, |
|
|
|
path: fullDbPath |
|
|
|
}); |
|
|
|
|
|
|
|
debugLog('Database creation result:', { |
|
|
|
created, |
|
|
|
path: fullDbPath, |
|
|
|
fileExists: fs.existsSync(fullDbPath), |
|
|
|
fileSize: fs.existsSync(fullDbPath) ? fs.statSync(fullDbPath).size : 0 |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// Get final connection
|
|
|
|
if (sqlitePlugin.getDatabaseConnectionOrThrowError) { |
|
|
|
debugLog('Getting final connection'); |
|
|
|
db = await sqlitePlugin.getDatabaseConnectionOrThrowError(connectionOptions.database); |
|
|
|
} |
|
|
|
|
|
|
|
debugLog('Final connection state:', { |
|
|
|
hasConnection: !!db, |
|
|
|
type: typeof db, |
|
|
|
isNull: db === null, |
|
|
|
isUndefined: db === undefined, |
|
|
|
keys: db ? Object.keys(db) : null, |
|
|
|
methods: db ? Object.getOwnPropertyNames(Object.getPrototypeOf(db)) : null, |
|
|
|
prototype: db ? Object.getPrototypeOf(db)?.constructor?.name : null |
|
|
|
}); |
|
|
|
|
|
|
|
} catch (openError) { |
|
|
|
debugLog('Error during open/create:', { |
|
|
|
name: openError.name, |
|
|
|
message: openError.message, |
|
|
|
stack: openError.stack, |
|
|
|
code: (openError as any).code, |
|
|
|
errno: (openError as any).errno, |
|
|
|
syscall: (openError as any).syscall |
|
|
|
}); |
|
|
|
throw openError; |
|
|
|
} |
|
|
|
} else { |
|
|
|
throw new Error('No valid createConnection method found on prototype'); |
|
|
|
} |
|
|
|
logger.info('Database connection verified successfully'); |
|
|
|
} catch (error) { |
|
|
|
throw new Error(`Database connection test failed: ${error instanceof Error ? error.message : 'Unknown error'}`); |
|
|
|
debugLog('createConnection/open error:', { |
|
|
|
name: error.name, |
|
|
|
message: error.message, |
|
|
|
stack: error.stack, |
|
|
|
code: (error as any).code, |
|
|
|
errno: (error as any).errno, |
|
|
|
syscall: (error as any).syscall |
|
|
|
}); |
|
|
|
throw error; |
|
|
|
} |
|
|
|
|
|
|
|
if (!db || typeof db !== 'object') { |
|
|
|
debugLog('Invalid database connection response:', { |
|
|
|
value: db, |
|
|
|
type: typeof db, |
|
|
|
isNull: db === null, |
|
|
|
isUndefined: db === undefined |
|
|
|
}); |
|
|
|
throw new Error(`Failed to create database connection - invalid response. Path: ${fullDbPath}`); |
|
|
|
} |
|
|
|
|
|
|
|
// Verify connection state
|
|
|
|
debugLog('Verifying connection state...'); |
|
|
|
try { |
|
|
|
const isOpen = await db.isDBOpen(); |
|
|
|
debugLog('Connection state:', { |
|
|
|
isOpen, |
|
|
|
methods: Object.keys(db), |
|
|
|
prototype: Object.getOwnPropertyNames(Object.getPrototypeOf(db)) |
|
|
|
}); |
|
|
|
} catch (error) { |
|
|
|
debugLog('Error checking connection state:', error); |
|
|
|
} |
|
|
|
|
|
|
|
sqliteInitialized = true; |
|
|
|
logger.info('SQLite plugin initialization completed successfully'); |
|
|
|
debugLog('SQLite plugin initialization completed successfully'); |
|
|
|
} catch (error) { |
|
|
|
logger.error('SQLite plugin initialization failed:', error); |
|
|
|
// Store the error but don't throw
|
|
|
|
initializationError = error instanceof Error ? error : new Error(String(error)); |
|
|
|
// Reset state on failure but allow app to continue
|
|
|
|
sqlitePlugin = null; |
|
|
|
sqliteInitialized = false; |
|
|
|
} finally { |
|
|
@ -291,18 +584,22 @@ export function setupSQLiteHandlers(): void { |
|
|
|
throw new Error('Database path not initialized'); |
|
|
|
} |
|
|
|
|
|
|
|
// Use modern plugin conventions for connection options
|
|
|
|
// Use same connection options format
|
|
|
|
const connectionOptions = { |
|
|
|
...options, |
|
|
|
database: 'timesafariSQLite', |
|
|
|
database: 'timesafari', // Base name only
|
|
|
|
readOnly: false, |
|
|
|
mode: 'rwc', |
|
|
|
encryption: 'no-encryption', |
|
|
|
useNative: true, |
|
|
|
location: dbDir // Use path relative to electron folder
|
|
|
|
location: 'default' // Let plugin handle path resolution
|
|
|
|
}; |
|
|
|
|
|
|
|
logger.info('Creating database connection with options:', connectionOptions); |
|
|
|
logger.info('Creating database connection with options:', { |
|
|
|
...connectionOptions, |
|
|
|
expectedBehavior: 'Plugin will append SQLite suffix and handle path resolution' |
|
|
|
}); |
|
|
|
|
|
|
|
const result = await sqlitePlugin.createConnection(connectionOptions); |
|
|
|
|
|
|
|
if (!result || typeof result !== 'object') { |
|
|
|