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 f3ac43291..e998db425 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 52c4a0f40..07f62f121 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 6acf6944f..5d7a4dd2f 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 16e08d830..e4a656c40 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 => {