From 7463e87742aecbe43cf92b3f9d061d12eba867ff Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Thu, 23 Nov 2023 07:23:16 +0000 Subject: [PATCH] fix(electron): clone db file when enable cloud for desktop (#5028) At the moment on desktop the user's local blob data will be lost after enable cloud. This is because blob data is only synced from old idb to new idb, but not sync into sqlitedb. This pr will simply clone the db file for desktop app. It should also speed up the time when enabling cloud for a large local workspace. --- packages/common/infra/src/type.ts | 1 + .../electron/src/helper/workspace/handlers.ts | 30 +++++++++++ .../electron/src/helper/workspace/index.ts | 3 +- .../frontend/workspace/src/affine/crud.ts | 53 +++++++++++-------- 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/packages/common/infra/src/type.ts b/packages/common/infra/src/type.ts index f3ac432917..e998db425b 100644 --- a/packages/common/infra/src/type.ts +++ b/packages/common/infra/src/type.ts @@ -200,6 +200,7 @@ export type WorkspaceHandlers = { list: () => Promise<[workspaceId: string, meta: WorkspaceMeta][]>; delete: (id: string) => Promise; getMeta: (id: string) => Promise; + clone: (id: string, newId: string) => Promise; }; export type UnwrapManagerHandlerToServerSide< diff --git a/packages/frontend/electron/src/helper/workspace/handlers.ts b/packages/frontend/electron/src/helper/workspace/handlers.ts index 52c4a0f400..07f62f1212 100644 --- a/packages/frontend/electron/src/helper/workspace/handlers.ts +++ b/packages/frontend/electron/src/helper/workspace/handlers.ts @@ -8,7 +8,9 @@ import type { WorkspaceMeta } from '../type'; import { getDeletedWorkspacesBasePath, getWorkspaceBasePath, + getWorkspaceDBPath, getWorkspaceMeta, + getWorkspaceMetaPath, getWorkspacesBasePath, } from './meta'; import { workspaceSubjects } from './subjects'; @@ -53,6 +55,34 @@ export async function deleteWorkspace(id: string) { } } +export async function cloneWorkspace(id: string, newId: string) { + const dbPath = await getWorkspaceDBPath(id); + const newBasePath = await getWorkspaceBasePath(newId); + const newDbPath = await getWorkspaceDBPath(newId); + const metaPath = await getWorkspaceMetaPath(newId); + // check if new workspace dir exists + if ( + await fs + .access(newBasePath) + .then(() => true) + .catch(() => false) + ) { + throw new Error(`workspace ${newId} already exists`); + } + + try { + await fs.ensureDir(newBasePath); + const meta = { + id: newId, + mainDBPath: newDbPath, + }; + await fs.writeJSON(metaPath, meta); + await fs.copy(dbPath, newDbPath); + } catch (error) { + logger.error('cloneWorkspace', error); + } +} + export async function storeWorkspaceMeta( workspaceId: string, meta: Partial diff --git a/packages/frontend/electron/src/helper/workspace/index.ts b/packages/frontend/electron/src/helper/workspace/index.ts index 6acf6944f6..5d7a4dd2fb 100644 --- a/packages/frontend/electron/src/helper/workspace/index.ts +++ b/packages/frontend/electron/src/helper/workspace/index.ts @@ -1,5 +1,5 @@ import type { MainEventRegister, WorkspaceMeta } from '../type'; -import { deleteWorkspace, listWorkspaces } from './handlers'; +import { cloneWorkspace, deleteWorkspace, listWorkspaces } from './handlers'; import { getWorkspaceMeta } from './meta'; import { workspaceSubjects } from './subjects'; @@ -23,4 +23,5 @@ export const workspaceHandlers = { getMeta: async (id: string) => { return getWorkspaceMeta(id); }, + clone: async (id: string, newId: string) => cloneWorkspace(id, newId), }; diff --git a/packages/frontend/workspace/src/affine/crud.ts b/packages/frontend/workspace/src/affine/crud.ts index 16e08d8306..e4a656c409 100644 --- a/packages/frontend/workspace/src/affine/crud.ts +++ b/packages/frontend/workspace/src/affine/crud.ts @@ -53,30 +53,41 @@ export const CRUD: WorkspaceCRUD = { WorkspaceFlavour.AFFINE_CLOUD ); - Y.applyUpdate( - newBlockSuiteWorkspace.doc, - Y.encodeStateAsUpdate(upstreamWorkspace.doc) - ); + if (environment.isDesktop) { + // this will clone all data from existing db to new db file, including docs and blobs + await window.apis.workspace.clone( + upstreamWorkspace.id, + createWorkspace.id + ); - await Promise.all( - [...upstreamWorkspace.doc.subdocs].map(async subdoc => { - subdoc.load(); - return subdoc.whenLoaded.then(() => { - newBlockSuiteWorkspace.doc.subdocs.forEach(newSubdoc => { - if (newSubdoc.guid === subdoc.guid) { - Y.applyUpdate(newSubdoc, Y.encodeStateAsUpdate(subdoc)); - } + // skip apply updates in memory and we will use providers to sync data from db + } else { + Y.applyUpdate( + newBlockSuiteWorkspace.doc, + Y.encodeStateAsUpdate(upstreamWorkspace.doc) + ); + + await Promise.all( + [...upstreamWorkspace.doc.subdocs].map(async subdoc => { + subdoc.load(); + return subdoc.whenLoaded.then(() => { + newBlockSuiteWorkspace.doc.subdocs.forEach(newSubdoc => { + if (newSubdoc.guid === subdoc.guid) { + Y.applyUpdate(newSubdoc, Y.encodeStateAsUpdate(subdoc)); + } + }); }); - }); - }) - ); + }) + ); + + migrateLocalBlobStorage(upstreamWorkspace.id, createWorkspace.id) + .then(() => deleteLocalBlobStorage(upstreamWorkspace.id)) + .catch(e => { + console.error('error when moving blob storage:', e); + }); + // todo(himself65): delete old workspace in the future + } - migrateLocalBlobStorage(upstreamWorkspace.id, createWorkspace.id) - .then(() => deleteLocalBlobStorage(upstreamWorkspace.id)) - .catch(e => { - console.error('error when moving blob storage:', e); - }); - // todo(himself65): delete old workspace in the future return createWorkspace.id; }, delete: async workspace => {