fix(core): transform workspace db when enable cloud (#7744)

This commit is contained in:
EYHN 2024-08-05 15:17:19 +00:00
parent a03831f2a2
commit cd4e462d8c
No known key found for this signature in database
GPG Key ID: 46C9E26A75AB276C
10 changed files with 110 additions and 20 deletions

View File

@ -6,6 +6,7 @@ import { WorkspaceDBService } from './services/db';
export { AFFiNE_WORKSPACE_DB_SCHEMA } from './schema';
export { WorkspaceDBService } from './services/db';
export { transformWorkspaceDBLocalToCloud } from './services/db';
export function configureWorkspaceDBModule(framework: Framework) {
framework

View File

@ -2,6 +2,7 @@ import { Doc as YDoc } from 'yjs';
import { Service } from '../../../framework';
import { createORMClient, YjsDBAdapter } from '../../../orm';
import type { DocStorage } from '../../../sync';
import { ObjectPool } from '../../../utils';
import type { WorkspaceService } from '../../workspace';
import { DB, type DBWithTables } from '../entities/db';
@ -90,3 +91,29 @@ export class WorkspaceDBService extends Service {
return docId.startsWith('db$') || docId.startsWith('userdata$');
}
}
export async function transformWorkspaceDBLocalToCloud(
localWorkspaceId: string,
cloudWorkspaceId: string,
localDocStorage: DocStorage,
cloudDocStorage: DocStorage,
accountId: string
) {
for (const tableName of Object.keys(AFFiNE_WORKSPACE_DB_SCHEMA)) {
const localDocName = `db$${localWorkspaceId}$${tableName}`;
const localDoc = await localDocStorage.doc.get(localDocName);
if (localDoc) {
const cloudDocName = `db$${cloudWorkspaceId}$${tableName}`;
await cloudDocStorage.doc.set(cloudDocName, localDoc);
}
}
for (const tableName of Object.keys(AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA)) {
const localDocName = `userdata$__local__$${localWorkspaceId}$${tableName}`;
const localDoc = await localDocStorage.doc.get(localDocName);
if (localDoc) {
const cloudDocName = `userdata$${accountId}$${cloudWorkspaceId}$${tableName}`;
await cloudDocStorage.doc.set(cloudDocName, localDoc);
}
}
}

View File

@ -28,7 +28,8 @@ export interface WorkspaceFlavourProvider {
createWorkspace(
initial: (
docCollection: DocCollection,
blobStorage: BlobStorage
blobStorage: BlobStorage,
docStorage: DocStorage
) => Promise<void>
): Promise<WorkspaceMetadata>;

View File

@ -2,7 +2,7 @@ import type { WorkspaceFlavour } from '@affine/env/workspace';
import type { DocCollection } from '@blocksuite/store';
import { Service } from '../../../framework';
import type { BlobStorage } from '../../../sync';
import type { BlobStorage, DocStorage } from '../../../sync';
import type { WorkspaceFlavourProvider } from '../providers/flavour';
export class WorkspaceFactoryService extends Service {
@ -20,7 +20,8 @@ export class WorkspaceFactoryService extends Service {
flavour: WorkspaceFlavour,
initial: (
docCollection: DocCollection,
blobStorage: BlobStorage
blobStorage: BlobStorage,
docStorage: DocStorage
) => Promise<void> = () => Promise.resolve()
) => {
const provider = this.providers.find(x => x.flavour === flavour);

View File

@ -3,6 +3,7 @@ import { assertEquals } from '@blocksuite/global/utils';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { Service } from '../../../framework';
import { transformWorkspaceDBLocalToCloud } from '../../db';
import type { Workspace } from '../entities/workspace';
import type { WorkspaceMetadata } from '../metadata';
import type { WorkspaceDestroyService } from './destroy';
@ -18,9 +19,12 @@ export class WorkspaceTransformService extends Service {
/**
* helper function to transform local workspace to cloud workspace
*
* @param accountId - all local user data will be transformed to this account
*/
transformLocalToCloud = async (
local: Workspace
local: Workspace,
accountId: string
): Promise<WorkspaceMetadata> => {
assertEquals(local.flavour, WorkspaceFlavour.LOCAL);
@ -28,23 +32,35 @@ export class WorkspaceTransformService extends Service {
const newMetadata = await this.factory.create(
WorkspaceFlavour.AFFINE_CLOUD,
async (ws, bs) => {
applyUpdate(ws.doc, encodeStateAsUpdate(local.docCollection.doc));
async (docCollection, blobStorage, docStorage) => {
applyUpdate(
docCollection.doc,
encodeStateAsUpdate(local.docCollection.doc)
);
for (const subdoc of local.docCollection.doc.getSubdocs()) {
for (const newSubdoc of ws.doc.getSubdocs()) {
for (const newSubdoc of docCollection.doc.getSubdocs()) {
if (newSubdoc.guid === subdoc.guid) {
applyUpdate(newSubdoc, encodeStateAsUpdate(subdoc));
}
}
}
// transform db
await transformWorkspaceDBLocalToCloud(
local.id,
docCollection.id,
local.engine.doc.storage.behavior,
docStorage,
accountId
);
const blobList = await local.engine.blob.list();
for (const blobKey of blobList) {
const blob = await local.engine.blob.get(blobKey);
if (blob) {
await bs.set(blobKey, blob);
await blobStorage.set(blobKey, blob);
}
}
}

View File

@ -6,7 +6,11 @@ import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { Service } from '../../../framework';
import { LiveData } from '../../../livedata';
import { wrapMemento } from '../../../storage';
import { type BlobStorage, MemoryDocStorage } from '../../../sync';
import {
type BlobStorage,
type DocStorage,
MemoryDocStorage,
} from '../../../sync';
import { MemoryBlobStorage } from '../../../sync/blob/blob';
import type { GlobalState } from '../../storage';
import type { WorkspaceProfileInfo } from '../entities/profile';
@ -39,7 +43,8 @@ export class TestingWorkspaceLocalProvider
async createWorkspace(
initial: (
docCollection: DocCollection,
blobStorage: BlobStorage
blobStorage: BlobStorage,
docStorage: DocStorage
) => Promise<void>
): Promise<WorkspaceMetadata> {
const id = nanoid();
@ -59,7 +64,7 @@ export class TestingWorkspaceLocalProvider
});
// apply initial state
await initial(docCollection, blobStorage);
await initial(docCollection, blobStorage, this.docStorage);
// save workspace to storage
await this.docStorage.doc.set(id, encodeStateAsUpdate(docCollection.doc));

View File

@ -29,6 +29,8 @@ type ConfirmEnableArgs = [Workspace, ConfirmEnableCloudOptions | undefined];
export const useEnableCloud = () => {
const t = useI18n();
const authService = useService(AuthService);
const account = useLiveData(authService.session.account$);
const loginStatus = useLiveData(useService(AuthService).session.status$);
const setAuthAtom = useSetAtom(authAtom);
const { openConfirmModal, closeConfirmModal } = useConfirmModal();
@ -39,7 +41,11 @@ export const useEnableCloud = () => {
async (ws: Workspace | null, options?: ConfirmEnableCloudOptions) => {
try {
if (!ws) return;
const { id: newId } = await workspacesService.transformLocalToCloud(ws);
if (!account) return;
const { id: newId } = await workspacesService.transformLocalToCloud(
ws,
account.id
);
openPage(newId, options?.openPageId || WorkspaceSubPath.ALL);
options?.onSuccess?.();
} catch (e) {
@ -49,7 +55,7 @@ export const useEnableCloud = () => {
});
}
},
[openPage, t, workspacesService]
[account, openPage, t, workspacesService]
);
const openSignIn = useCallback(() => {

View File

@ -11,6 +11,7 @@ import {
ApplicationStarted,
type BlobStorage,
catchErrorInto,
type DocStorage,
exhaustMapSwitchUntilChanged,
fromPromise,
type GlobalState,
@ -77,11 +78,10 @@ export class CloudWorkspaceFlavourProviderService
async createWorkspace(
initial: (
docCollection: DocCollection,
blobStorage: BlobStorage
blobStorage: BlobStorage,
docStorage: DocStorage
) => Promise<void>
): Promise<WorkspaceMetadata> {
const tempId = nanoid();
// create workspace on cloud, get workspace id
const {
createWorkspace: { id: workspaceId },
@ -94,7 +94,7 @@ export class CloudWorkspaceFlavourProviderService
const docStorage = this.storageProvider.getDocStorage(workspaceId);
const docCollection = new DocCollection({
id: tempId,
id: workspaceId,
idGenerator: () => nanoid(),
schema: globalBlockSuiteSchema,
blobSources: {
@ -103,7 +103,7 @@ export class CloudWorkspaceFlavourProviderService
});
// apply initial state
await initial(docCollection, blobStorage);
await initial(docCollection, blobStorage, docStorage);
// save workspace to local storage, should be vary fast
await docStorage.doc.set(

View File

@ -3,6 +3,7 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { DocCollection } from '@blocksuite/store';
import type {
BlobStorage,
DocStorage,
WorkspaceEngineProvider,
WorkspaceFlavourProvider,
WorkspaceMetadata,
@ -56,7 +57,8 @@ export class LocalWorkspaceFlavourProvider
async createWorkspace(
initial: (
docCollection: DocCollection,
blobStorage: BlobStorage
blobStorage: BlobStorage,
docStorage: DocStorage
) => Promise<void>
): Promise<WorkspaceMetadata> {
const id = nanoid();
@ -73,7 +75,7 @@ export class LocalWorkspaceFlavourProvider
});
// apply initial state
await initial(docCollection, blobStorage);
await initial(docCollection, blobStorage, docStorage);
// save workspace to local storage, should be vary fast
await docStorage.doc.set(id, encodeStateAsUpdate(docCollection.doc));

View File

@ -5,9 +5,12 @@ import {
enableCloudWorkspace,
loginUser,
} from '@affine-test/kit/utils/cloud';
import { clickPageModeButton } from '@affine-test/kit/utils/editor';
import {
clickNewPageButton,
getBlockSuiteEditorTitle,
waitForEditorLoad,
waitForEmptyEditor,
} from '@affine-test/kit/utils/page-logic';
import {
openSettingModal,
@ -94,3 +97,31 @@ test('should have pagination in member list', async ({ page }) => {
await page.waitForTimeout(500);
expect(await page.locator('[data-testid="member-item"]').count()).toBe(3);
});
test('should transform local favorites data', async ({ page }) => {
await page.reload();
await waitForEditorLoad(page);
await createLocalWorkspace(
{
name: 'test',
},
page
);
await page.getByTestId('explorer-bar-add-favorite-button').first().click();
await clickPageModeButton(page);
await waitForEmptyEditor(page);
await getBlockSuiteEditorTitle(page).fill('this is a new fav page');
await expect(
page
.getByTestId('explorer-favorites')
.locator('[draggable] >> text=this is a new fav page')
).toBeVisible();
await enableCloudWorkspace(page);
await expect(
page
.getByTestId('explorer-favorites')
.locator('[draggable] >> text=this is a new fav page')
).toBeVisible();
});