Separate calls in sql channel
This commit is contained in:
parent
85b130a12d
commit
9a98ae0a4e
7 changed files with 90 additions and 45 deletions
|
@ -10,7 +10,11 @@ import { remove as removeEphemeralConfig } from './ephemeral_config';
|
||||||
let sql:
|
let sql:
|
||||||
| Pick<
|
| Pick<
|
||||||
MainSQL,
|
MainSQL,
|
||||||
'sqlRead' | 'sqlWrite' | 'pauseWriteAccess' | 'resumeWriteAccess'
|
| 'sqlRead'
|
||||||
|
| 'sqlWrite'
|
||||||
|
| 'pauseWriteAccess'
|
||||||
|
| 'resumeWriteAccess'
|
||||||
|
| 'removeDB'
|
||||||
>
|
>
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
|
@ -18,6 +22,7 @@ let initialized = false;
|
||||||
|
|
||||||
const SQL_READ_KEY = 'sql-channel:read';
|
const SQL_READ_KEY = 'sql-channel:read';
|
||||||
const SQL_WRITE_KEY = 'sql-channel:write';
|
const SQL_WRITE_KEY = 'sql-channel:write';
|
||||||
|
const SQL_REMOVE_DB_KEY = 'sql-channel:remove-db';
|
||||||
const ERASE_SQL_KEY = 'erase-sql-key';
|
const ERASE_SQL_KEY = 'erase-sql-key';
|
||||||
const PAUSE_WRITE_ACCESS = 'pause-sql-writes';
|
const PAUSE_WRITE_ACCESS = 'pause-sql-writes';
|
||||||
const RESUME_WRITE_ACCESS = 'resume-sql-writes';
|
const RESUME_WRITE_ACCESS = 'resume-sql-writes';
|
||||||
|
@ -44,6 +49,13 @@ export function initialize(mainSQL: typeof sql): void {
|
||||||
return sql.sqlWrite(callName, ...args);
|
return sql.sqlWrite(callName, ...args);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(SQL_REMOVE_DB_KEY, () => {
|
||||||
|
if (!sql) {
|
||||||
|
throw new Error(`${SQL_REMOVE_DB_KEY}: Not yet initialized!`);
|
||||||
|
}
|
||||||
|
return sql.removeDB();
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle(ERASE_SQL_KEY, () => {
|
ipcMain.handle(ERASE_SQL_KEY, () => {
|
||||||
removeUserConfig();
|
removeUserConfig();
|
||||||
removeEphemeralConfig();
|
removeEphemeralConfig();
|
||||||
|
|
|
@ -24,7 +24,7 @@ import * as Errors from '../types/errors';
|
||||||
import type { StoredJob } from '../jobs/types';
|
import type { StoredJob } from '../jobs/types';
|
||||||
import { formatJobForInsert } from '../jobs/formatJobForInsert';
|
import { formatJobForInsert } from '../jobs/formatJobForInsert';
|
||||||
import { cleanupMessages } from '../util/cleanup';
|
import { cleanupMessages } from '../util/cleanup';
|
||||||
import { AccessType, ipcInvoke, doShutdown } from './channels';
|
import { AccessType, ipcInvoke, doShutdown, removeDB } from './channels';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ClientInterfaceWrap,
|
ClientInterfaceWrap,
|
||||||
|
@ -124,6 +124,7 @@ const clientOnlyWritable: ClientOnlyWritableInterface = {
|
||||||
|
|
||||||
flushUpdateConversationBatcher,
|
flushUpdateConversationBatcher,
|
||||||
|
|
||||||
|
removeDB,
|
||||||
shutdown,
|
shutdown,
|
||||||
removeMessagesInConversation,
|
removeMessagesInConversation,
|
||||||
|
|
||||||
|
|
|
@ -646,8 +646,6 @@ type ReadableInterface = {
|
||||||
type WritableInterface = {
|
type WritableInterface = {
|
||||||
close: () => void;
|
close: () => void;
|
||||||
|
|
||||||
removeDB: () => void;
|
|
||||||
|
|
||||||
removeIndexedDBFiles: () => void;
|
removeIndexedDBFiles: () => void;
|
||||||
|
|
||||||
removeIdentityKeyById: (id: IdentityKeyIdType) => number;
|
removeIdentityKeyById: (id: IdentityKeyIdType) => number;
|
||||||
|
@ -1118,6 +1116,7 @@ export type ClientOnlyWritableInterface = ClientInterfaceWrap<{
|
||||||
// Client-side only
|
// Client-side only
|
||||||
|
|
||||||
shutdown: () => void;
|
shutdown: () => void;
|
||||||
|
removeDB: () => void;
|
||||||
removeMessagesInConversation: (
|
removeMessagesInConversation: (
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
options: {
|
options: {
|
||||||
|
|
|
@ -350,7 +350,6 @@ export const DataReader: ServerReadableInterface = {
|
||||||
|
|
||||||
export const DataWriter: ServerWritableInterface = {
|
export const DataWriter: ServerWritableInterface = {
|
||||||
close: closeWritable,
|
close: closeWritable,
|
||||||
removeDB,
|
|
||||||
removeIndexedDBFiles,
|
removeIndexedDBFiles,
|
||||||
|
|
||||||
createOrUpdateIdentityKey,
|
createOrUpdateIdentityKey,
|
||||||
|
@ -629,6 +628,7 @@ function openAndMigrateDatabase(
|
||||||
// If that fails, we try to open the database with 3.x compatibility to extract the
|
// If that fails, we try to open the database with 3.x compatibility to extract the
|
||||||
// user_version (previously stored in schema_version, blown away by cipher_migrate).
|
// user_version (previously stored in schema_version, blown away by cipher_migrate).
|
||||||
db = new SQL(filePath) as WritableDB;
|
db = new SQL(filePath) as WritableDB;
|
||||||
|
try {
|
||||||
keyDatabase(db, key);
|
keyDatabase(db, key);
|
||||||
|
|
||||||
// https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/#compatability-sqlcipher-4-0-0
|
// https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/#compatability-sqlcipher-4-0-0
|
||||||
|
@ -636,13 +636,21 @@ function openAndMigrateDatabase(
|
||||||
migrateSchemaVersion(db);
|
migrateSchemaVersion(db);
|
||||||
db.close();
|
db.close();
|
||||||
|
|
||||||
// After migrating user_version -> schema_version, we reopen database, because we can't
|
// After migrating user_version -> schema_version, we reopen database, because
|
||||||
// migrate to the latest ciphers after we've modified the defaults.
|
// we can't migrate to the latest ciphers after we've modified the defaults.
|
||||||
db = new SQL(filePath) as WritableDB;
|
db = new SQL(filePath) as WritableDB;
|
||||||
keyDatabase(db, key);
|
keyDatabase(db, key);
|
||||||
|
|
||||||
db.pragma('cipher_migrate');
|
db.pragma('cipher_migrate');
|
||||||
switchToWAL(db);
|
switchToWAL(db);
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
db.close();
|
||||||
|
} catch {
|
||||||
|
// Best effort
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
@ -659,8 +667,17 @@ function openAndSetUpSQLCipher(
|
||||||
|
|
||||||
const db = openAndMigrateDatabase(filePath, key, readonly);
|
const db = openAndMigrateDatabase(filePath, key, readonly);
|
||||||
|
|
||||||
|
try {
|
||||||
// Because foreign key support is not enabled by default!
|
// Because foreign key support is not enabled by default!
|
||||||
db.pragma('foreign_keys = ON');
|
db.pragma('foreign_keys = ON');
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
db.close();
|
||||||
|
} catch {
|
||||||
|
// Best effort
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
@ -750,13 +767,7 @@ function closeWritable(db: WritableDB): void {
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDB(db: WritableDB): void {
|
export function removeDB(): void {
|
||||||
try {
|
|
||||||
db.close();
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('removeDB: Failed to close database:', error.stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!databaseFilePath) {
|
if (!databaseFilePath) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'removeDB: Cannot erase database without a databaseFilePath!'
|
'removeDB: Cannot erase database without a databaseFilePath!'
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
|
||||||
const SQL_READ_KEY = 'sql-channel:read';
|
const SQL_READ_KEY = 'sql-channel:read';
|
||||||
const SQL_WRITE_KEY = 'sql-channel:write';
|
const SQL_WRITE_KEY = 'sql-channel:write';
|
||||||
|
const SQL_REMOVE_DB_KEY = 'sql-channel:remove-db';
|
||||||
let activeJobCount = 0;
|
let activeJobCount = 0;
|
||||||
let resolveShutdown: (() => void) | undefined;
|
let resolveShutdown: (() => void) | undefined;
|
||||||
let shutdownPromise: Promise<void> | null = null;
|
let shutdownPromise: Promise<void> | null = null;
|
||||||
|
@ -77,3 +78,7 @@ export async function doShutdown(): Promise<void> {
|
||||||
log.info('data.shutdown: process complete');
|
log.info('data.shutdown: process complete');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function removeDB(): Promise<void> {
|
||||||
|
return ipcRenderer.invoke(SQL_REMOVE_DB_KEY);
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { app } from 'electron';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import { explodePromise } from '../util/explodePromise';
|
import { explodePromise } from '../util/explodePromise';
|
||||||
import type { LoggerType } from '../types/Logging';
|
import type { LoggerType } from '../types/Logging';
|
||||||
|
import * as Errors from '../types/errors';
|
||||||
import { SqliteErrorKind } from './errors';
|
import { SqliteErrorKind } from './errors';
|
||||||
import type {
|
import type {
|
||||||
ServerReadableDirectInterface,
|
ServerReadableDirectInterface,
|
||||||
|
@ -200,6 +201,16 @@ export class MainSQL {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async close(): Promise<void> {
|
public async close(): Promise<void> {
|
||||||
|
if (this.onReady) {
|
||||||
|
try {
|
||||||
|
await this.onReady;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger?.error(`MainSQL close, failed: ${Errors.toLogFormat(err)}`);
|
||||||
|
// Init failed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isReady) {
|
if (!this.isReady) {
|
||||||
throw new Error('Not initialized');
|
throw new Error('Not initialized');
|
||||||
}
|
}
|
||||||
|
@ -256,11 +267,6 @@ export class MainSQL {
|
||||||
await promise;
|
await promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case since we need to broadcast this to every pool entry.
|
|
||||||
if (method === 'removeDB') {
|
|
||||||
return (await this.removeDB()) as Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const primary = this.pool[0];
|
const primary = this.pool[0];
|
||||||
|
|
||||||
const { result, duration } = await this.send<SqlCallResult>(primary, {
|
const { result, duration } = await this.send<SqlCallResult>(primary, {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type {
|
||||||
WrappedWorkerLogEntry,
|
WrappedWorkerLogEntry,
|
||||||
} from './main';
|
} from './main';
|
||||||
import type { WritableDB } from './Interface';
|
import type { WritableDB } from './Interface';
|
||||||
import { initialize, DataReader, DataWriter } from './Server';
|
import { initialize, DataReader, DataWriter, removeDB } from './Server';
|
||||||
import { SqliteErrorKind, parseSqliteError } from './errors';
|
import { SqliteErrorKind, parseSqliteError } from './errors';
|
||||||
|
|
||||||
if (!parentPort) {
|
if (!parentPort) {
|
||||||
|
@ -102,6 +102,31 @@ port.on('message', ({ seq, request }: WrappedWorkerRequest) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removing database does not require active connection.
|
||||||
|
if (request.type === 'removeDB') {
|
||||||
|
try {
|
||||||
|
if (db) {
|
||||||
|
if (isPrimary) {
|
||||||
|
DataWriter.close(db);
|
||||||
|
} else {
|
||||||
|
DataReader.close(db);
|
||||||
|
}
|
||||||
|
db = undefined;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to close database before removal');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrimary) {
|
||||||
|
removeDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
isRemoved = true;
|
||||||
|
|
||||||
|
respond(seq, undefined, undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw new Error('Not initialized');
|
throw new Error('Not initialized');
|
||||||
}
|
}
|
||||||
|
@ -119,20 +144,6 @@ port.on('message', ({ seq, request }: WrappedWorkerRequest) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.type === 'removeDB') {
|
|
||||||
if (isPrimary) {
|
|
||||||
DataWriter.removeDB(db);
|
|
||||||
} else {
|
|
||||||
DataReader.close(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
isRemoved = true;
|
|
||||||
db = undefined;
|
|
||||||
|
|
||||||
respond(seq, undefined, undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.type === 'sqlCall:read' || request.type === 'sqlCall:write') {
|
if (request.type === 'sqlCall:read' || request.type === 'sqlCall:write') {
|
||||||
const DataInterface =
|
const DataInterface =
|
||||||
request.type === 'sqlCall:read' ? DataReader : DataWriter;
|
request.type === 'sqlCall:read' ? DataReader : DataWriter;
|
||||||
|
|
Loading…
Reference in a new issue