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 => {