mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-19 07:17:00 +08:00
fix(electron): remove all migration code in electron (#6969)
The migration code import blocksuite in the dependency tree and prevent affine from running because electron's helper process tries to run browser only code that is part of the side effect of `@blocksuite/blocks`.  [The side effect free trick in esbuild config](https://github.com/toeverything/AFFiNE/pull/6415) does not clean up these - not sure why. It has been already 6 month since we introduced the migration code in DB. Instead of finding out the real root cause, I think may be better to remove the migration code completely so that no blocksuite code will be in the import paths in helper.js. 
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
import type { InsertRow } from '@affine/native';
|
||||
import { SqliteConnection, ValidationResult } from '@affine/native';
|
||||
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
||||
import { SqliteConnection } from '@affine/native';
|
||||
import type { ByteKVBehavior } from '@toeverything/infra/storage';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import { applyGuidCompatibilityFix, migrateToLatest } from './migration';
|
||||
|
||||
/**
|
||||
* A base class for SQLite DB adapter that provides basic methods around updates & blobs
|
||||
@@ -15,17 +13,8 @@ export class SQLiteAdapter {
|
||||
|
||||
async connectIfNeeded() {
|
||||
if (!this.db) {
|
||||
const validation = await SqliteConnection.validate(this.path);
|
||||
if (validation === ValidationResult.MissingVersionColumn) {
|
||||
await migrateToLatest(this.path, WorkspaceVersion.SubDoc);
|
||||
}
|
||||
this.db = new SqliteConnection(this.path);
|
||||
await this.db.connect();
|
||||
const maxVersion = await this.db.getMaxVersion();
|
||||
if (maxVersion !== WorkspaceVersion.Surface) {
|
||||
await migrateToLatest(this.path, WorkspaceVersion.Surface);
|
||||
}
|
||||
await applyGuidCompatibilityFix(this.db);
|
||||
logger.info(`[SQLiteAdapter]`, 'connected:', this.path);
|
||||
}
|
||||
return this.db;
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import { SqliteConnection } from '@affine/native';
|
||||
import { AffineSchemas } from '@blocksuite/blocks/schemas';
|
||||
import { Schema } from '@blocksuite/store';
|
||||
import {
|
||||
forceUpgradePages,
|
||||
migrateGuidCompatibility,
|
||||
migrateToSubdoc,
|
||||
WorkspaceVersion,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { applyUpdate, Doc as YDoc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { mainRPC } from '../main-rpc';
|
||||
|
||||
export const migrateToSubdocAndReplaceDatabase = async (path: string) => {
|
||||
const db = new SqliteConnection(path);
|
||||
await db.connect();
|
||||
|
||||
const rows = await db.getAllUpdates();
|
||||
const originalDoc = new YDoc();
|
||||
|
||||
// 1. apply all updates to the root doc
|
||||
rows.forEach(row => {
|
||||
applyUpdate(originalDoc, row.data);
|
||||
});
|
||||
|
||||
// 2. migrate using migrateToSubdoc
|
||||
const migratedDoc = migrateToSubdoc(originalDoc);
|
||||
|
||||
// 3. replace db rows with the migrated doc
|
||||
await replaceRows(db, migratedDoc, true);
|
||||
|
||||
// 4. close db
|
||||
await db.close();
|
||||
};
|
||||
|
||||
// v1 v2 -> v3
|
||||
// v3 -> v4
|
||||
export const migrateToLatest = async (
|
||||
path: string,
|
||||
version: WorkspaceVersion
|
||||
) => {
|
||||
const connection = new SqliteConnection(path);
|
||||
await connection.connect();
|
||||
if (version === WorkspaceVersion.SubDoc) {
|
||||
await connection.initVersion();
|
||||
} else {
|
||||
await connection.setVersion(version);
|
||||
}
|
||||
const schema = new Schema();
|
||||
schema.register(AffineSchemas);
|
||||
const rootDoc = new YDoc();
|
||||
const downloadBinary = async (doc: YDoc, isRoot: boolean): Promise<void> => {
|
||||
const update = (
|
||||
await connection.getUpdates(isRoot ? undefined : doc.guid)
|
||||
).map(update => update.data);
|
||||
// Buffer[] -> Uint8Array[]
|
||||
const data = update.map(update => new Uint8Array(update));
|
||||
data.forEach(data => {
|
||||
applyUpdate(doc, data);
|
||||
});
|
||||
// trigger data manually
|
||||
if (isRoot) {
|
||||
doc.getMap('meta');
|
||||
doc.getMap('spaces');
|
||||
} else {
|
||||
doc.getMap('blocks');
|
||||
}
|
||||
await Promise.all(
|
||||
[...doc.subdocs].map(subdoc => {
|
||||
return downloadBinary(subdoc, false);
|
||||
})
|
||||
);
|
||||
};
|
||||
await downloadBinary(rootDoc, true);
|
||||
const result = await forceUpgradePages(rootDoc, schema);
|
||||
if (result) {
|
||||
const uploadBinary = async (doc: YDoc, isRoot: boolean) => {
|
||||
await connection.replaceUpdates(doc.guid, [
|
||||
{
|
||||
docId: isRoot ? undefined : doc.guid,
|
||||
data: encodeStateAsUpdate(doc),
|
||||
},
|
||||
]);
|
||||
// connection..applyUpdate(encodeStateAsUpdate(doc), 'self', doc.guid)
|
||||
await Promise.all(
|
||||
[...doc.subdocs].map(subdoc => {
|
||||
return uploadBinary(subdoc, false);
|
||||
})
|
||||
);
|
||||
};
|
||||
await uploadBinary(rootDoc, true);
|
||||
}
|
||||
await connection.close();
|
||||
};
|
||||
|
||||
export const copyToTemp = async (path: string) => {
|
||||
const tmpDirPath = resolve(await mainRPC.getPath('sessionData'), 'tmp');
|
||||
const tmpFilePath = resolve(tmpDirPath, nanoid());
|
||||
await fs.ensureDir(tmpDirPath);
|
||||
await fs.copyFile(path, tmpFilePath);
|
||||
return tmpFilePath;
|
||||
};
|
||||
|
||||
async function replaceRows(
|
||||
db: SqliteConnection,
|
||||
doc: YDoc,
|
||||
isRoot: boolean
|
||||
): Promise<void> {
|
||||
const migratedUpdates = encodeStateAsUpdate(doc);
|
||||
const docId = isRoot ? undefined : doc.guid;
|
||||
const rows = [{ data: migratedUpdates, docId: docId }];
|
||||
await db.replaceUpdates(docId, rows);
|
||||
await Promise.all(
|
||||
[...doc.subdocs].map(async subdoc => {
|
||||
await replaceRows(db, subdoc, false);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export const applyGuidCompatibilityFix = async (db: SqliteConnection) => {
|
||||
const oldRows = await db.getUpdates(undefined);
|
||||
|
||||
const rootDoc = new YDoc();
|
||||
oldRows.forEach(row => applyUpdate(rootDoc, row.data));
|
||||
|
||||
// see comments of migrateGuidCompatibility
|
||||
migrateGuidCompatibility(rootDoc);
|
||||
|
||||
// todo: backup?
|
||||
await db.replaceUpdates(undefined, [
|
||||
{
|
||||
docId: undefined,
|
||||
data: encodeStateAsUpdate(rootDoc),
|
||||
},
|
||||
]);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AsyncLock } from '@toeverything/infra';
|
||||
import { AsyncLock } from '@toeverything/infra/utils';
|
||||
import { Subject } from 'rxjs';
|
||||
import { applyUpdate, Doc as YDoc } from 'yjs';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user