fix: import workspace may only show default preload page (#2685)

This commit is contained in:
Peng Xiao
2023-06-06 14:42:50 +08:00
committed by GitHub
parent f78760cb83
commit 84f68fc2c0
3 changed files with 139 additions and 75 deletions
@@ -3,14 +3,15 @@ import type { Y as YType } from '@blocksuite/store';
import { uuidv4, Workspace } from '@blocksuite/store'; import { uuidv4, Workspace } from '@blocksuite/store';
import { beforeEach, describe, expect, test, vi } from 'vitest'; import { beforeEach, describe, expect, test, vi } from 'vitest';
import type { SQLiteProvider } from '../../type'; import type { SQLiteDBDownloadProvider, SQLiteProvider } from '../../type';
import { createSQLiteProvider } from '../index'; import { createSQLiteDBDownloadProvider, createSQLiteProvider } from '../index';
const Y = Workspace.Y; const Y = Workspace.Y;
let id: string; let id: string;
let workspace: Workspace; let workspace: Workspace;
let provider: SQLiteProvider; let provider: SQLiteProvider;
let downloadProvider: SQLiteDBDownloadProvider;
let offlineYdoc: YType.Doc; let offlineYdoc: YType.Doc;
@@ -60,12 +61,13 @@ beforeEach(() => {
}); });
workspace.register(AffineSchemas).register(__unstableSchemas); workspace.register(AffineSchemas).register(__unstableSchemas);
provider = createSQLiteProvider(workspace); provider = createSQLiteProvider(workspace);
downloadProvider = createSQLiteDBDownloadProvider(workspace);
offlineYdoc = new Y.Doc(); offlineYdoc = new Y.Doc();
offlineYdoc.getText('text').insert(0, 'sqlite-hello'); offlineYdoc.getText('text').insert(0, 'sqlite-hello');
}); });
describe('SQLite provider', () => { describe('SQLite download provider', () => {
test('connect', async () => { test('sync updates', async () => {
// on connect, the updates from sqlite should be sync'ed to the existing ydoc // on connect, the updates from sqlite should be sync'ed to the existing ydoc
// and ydoc should be sync'ed back to sqlite // and ydoc should be sync'ed back to sqlite
// Workspace.Y.applyUpdate(workspace.doc); // Workspace.Y.applyUpdate(workspace.doc);
@@ -73,7 +75,8 @@ describe('SQLite provider', () => {
expect(offlineYdoc.getText('text').toString()).toBe('sqlite-hello'); expect(offlineYdoc.getText('text').toString()).toBe('sqlite-hello');
await provider.connect(); downloadProvider.sync();
await downloadProvider.whenReady;
// depending on the nature of the sync, the data can be sync'ed in either direction // depending on the nature of the sync, the data can be sync'ed in either direction
const options = ['mem-hellosqlite-hello', 'sqlite-hellomem-hello']; const options = ['mem-hellosqlite-hello', 'sqlite-hellomem-hello'];
@@ -83,10 +86,10 @@ describe('SQLite provider', () => {
expect(synced.length).toBe(1); expect(synced.length).toBe(1);
expect(workspace.doc.getText('text').toString()).toBe(synced[0]); expect(workspace.doc.getText('text').toString()).toBe(synced[0]);
workspace.doc.getText('text').insert(0, 'world'); // workspace.doc.getText('text').insert(0, 'world');
// check if the data are sync'ed // // check if the data are sync'ed
expect(offlineYdoc.getText('text').toString()).toBe('world' + synced[0]); // expect(offlineYdoc.getText('text').toString()).toBe('world' + synced[0]);
}); });
test('blobs will be synced to sqlite on connect', async () => { test('blobs will be synced to sqlite on connect', async () => {
@@ -98,14 +101,15 @@ describe('SQLite provider', () => {
return blob; return blob;
}); });
await provider.connect(); downloadProvider.sync();
await downloadProvider.whenReady;
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise(resolve => setTimeout(resolve, 100));
expect(mockedAddBlob).toBeCalledWith(id, 'blob1', bin); expect(mockedAddBlob).toBeCalledWith(id, 'blob1', bin);
}); });
test('on db update', async () => { test('on db update', async () => {
await provider.connect(); provider.connect();
offlineYdoc.getText('text').insert(0, 'sqlite-world'); offlineYdoc.getText('text').insert(0, 'sqlite-world');
@@ -114,8 +118,8 @@ describe('SQLite provider', () => {
update: Y.encodeStateAsUpdate(offlineYdoc), update: Y.encodeStateAsUpdate(offlineYdoc),
}); });
// not yet updated // not yet updated (because the workspace id is different)
expect(workspace.doc.getText('text').toString()).toBe('sqlite-hello'); expect(workspace.doc.getText('text').toString()).toBe('');
triggerDBUpdate?.({ triggerDBUpdate?.({
workspaceId: id, workspaceId: id,
+117 -61
View File
@@ -17,6 +17,7 @@ import type {
LocalIndexedDBBackgroundProvider, LocalIndexedDBBackgroundProvider,
LocalIndexedDBDownloadProvider, LocalIndexedDBDownloadProvider,
Provider, Provider,
SQLiteDBDownloadProvider,
SQLiteProvider, SQLiteProvider,
} from '../type'; } from '../type';
import { CallbackSet } from '../utils'; import { CallbackSet } from '../utils';
@@ -156,10 +157,11 @@ const createIndexedDBDownloadProvider = (
}; };
}; };
const sqliteOrigin = Symbol('sqlite-provider-origin');
const createSQLiteProvider = ( const createSQLiteProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace blockSuiteWorkspace: BlockSuiteWorkspace
): SQLiteProvider => { ): SQLiteProvider => {
const sqliteOrigin = Symbol('sqlite-provider-origin');
const apis = window.apis!; const apis = window.apis!;
const events = window.events!; const events = window.events!;
// make sure it is being used in Electron with APIs // make sure it is being used in Electron with APIs
@@ -173,9 +175,87 @@ const createSQLiteProvider = (
apis.db.applyDocUpdate(blockSuiteWorkspace.id, update); apis.db.applyDocUpdate(blockSuiteWorkspace.id, update);
} }
let unsubscribe = () => {};
let connected = false;
const callbacks = new CallbackSet();
const connect = () => {
logger.info('connecting sqlite provider', blockSuiteWorkspace.id);
blockSuiteWorkspace.doc.on('update', handleUpdate);
unsubscribe = events.db.onExternalUpdate(({ update, workspaceId }) => {
if (workspaceId === blockSuiteWorkspace.id) {
Y.applyUpdate(blockSuiteWorkspace.doc, update, sqliteOrigin);
}
});
connected = true;
logger.info('connecting sqlite done', blockSuiteWorkspace.id);
};
const cleanup = () => {
logger.info('disconnecting sqlite provider', blockSuiteWorkspace.id);
unsubscribe();
blockSuiteWorkspace.doc.off('update', handleUpdate);
connected = false;
};
return {
flavour: 'sqlite',
background: true,
callbacks,
get connected(): boolean {
return connected;
},
cleanup,
connect,
disconnect: cleanup,
};
};
const createSQLiteDBDownloadProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace
): SQLiteDBDownloadProvider => {
const apis = window.apis!;
let disconnected = false;
let _resolve: () => void;
let _reject: (error: unknown) => void;
const promise = new Promise<void>((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
async function syncUpdates() {
logger.info('syncing updates from sqlite', blockSuiteWorkspace.id);
const updates = await apis.db.getDocAsUpdates(blockSuiteWorkspace.id);
if (disconnected) {
return;
}
if (updates) {
Y.applyUpdate(blockSuiteWorkspace.doc, updates, sqliteOrigin);
}
const diff = Y.encodeStateAsUpdate(blockSuiteWorkspace.doc, updates);
// also apply updates to sqlite
apis.db.applyDocUpdate(blockSuiteWorkspace.id, diff);
const bs = blockSuiteWorkspace.blobs;
if (bs && !disconnected) {
await syncBlobIntoSQLite(bs);
}
}
async function syncBlobIntoSQLite(bs: BlobManager) { async function syncBlobIntoSQLite(bs: BlobManager) {
const persistedKeys = await apis.db.getBlobKeys(blockSuiteWorkspace.id); const persistedKeys = await apis.db.getBlobKeys(blockSuiteWorkspace.id);
if (disconnected) {
return;
}
const allKeys = await bs.list().catch(() => []); const allKeys = await bs.list().catch(() => []);
const keysToPersist = allKeys.filter(k => !persistedKeys.includes(k)); const keysToPersist = allKeys.filter(k => !persistedKeys.includes(k));
@@ -187,6 +267,11 @@ const createSQLiteProvider = (
logger.warn('blob not found for', k); logger.warn('blob not found for', k);
return; return;
} }
if (disconnected) {
return;
}
return window.apis?.db.addBlob( return window.apis?.db.addBlob(
blockSuiteWorkspace.id, blockSuiteWorkspace.id,
k, k,
@@ -196,61 +281,23 @@ const createSQLiteProvider = (
); );
} }
async function syncUpdates() {
logger.info('syncing updates from sqlite', blockSuiteWorkspace.id);
const updates = await apis.db.getDocAsUpdates(blockSuiteWorkspace.id);
if (updates) {
Y.applyUpdate(blockSuiteWorkspace.doc, updates, sqliteOrigin);
}
const mergeUpdates = Y.encodeStateAsUpdate(blockSuiteWorkspace.doc);
// also apply updates to sqlite
apis.db.applyDocUpdate(blockSuiteWorkspace.id, mergeUpdates);
const bs = blockSuiteWorkspace.blobs;
if (bs) {
// this can be non-blocking
syncBlobIntoSQLite(bs);
}
}
let unsubscribe = () => {};
let connected = false;
const callbacks = new CallbackSet();
return { return {
flavour: 'sqlite', flavour: 'sqlite-download',
background: true, necessary: true,
callbacks, get whenReady() {
get connected(): boolean { return promise;
return connected;
}, },
cleanup: () => { cleanup: () => {
throw new Error('Method not implemented.'); disconnected = true;
}, },
connect: async () => { sync: async () => {
logger.info('connecting sqlite provider', blockSuiteWorkspace.id); logger.info('connect indexeddb provider', blockSuiteWorkspace.id);
await syncUpdates(); try {
connected = true; await syncUpdates();
_resolve();
blockSuiteWorkspace.doc.on('update', handleUpdate); } catch (error) {
_reject(error);
unsubscribe = events.db.onExternalUpdate(({ update, workspaceId }) => { }
if (workspaceId === blockSuiteWorkspace.id) {
Y.applyUpdate(blockSuiteWorkspace.doc, update, sqliteOrigin);
}
});
// blockSuiteWorkspace.doc.on('destroy', ...);
logger.info('connecting sqlite done', blockSuiteWorkspace.id);
},
disconnect: () => {
unsubscribe();
blockSuiteWorkspace.doc.off('update', handleUpdate);
connected = false;
}, },
}; };
}; };
@@ -261,21 +308,30 @@ export {
createBroadCastChannelProvider, createBroadCastChannelProvider,
createIndexedDBBackgroundProvider, createIndexedDBBackgroundProvider,
createIndexedDBDownloadProvider, createIndexedDBDownloadProvider,
createSQLiteDBDownloadProvider,
createSQLiteProvider, createSQLiteProvider,
}; };
export const createLocalProviders = ( export const createLocalProviders = (
blockSuiteWorkspace: BlockSuiteWorkspace blockSuiteWorkspace: BlockSuiteWorkspace
): Provider[] => { ): Provider[] => {
return ( const providers = [
[ createIndexedDBBackgroundProvider(blockSuiteWorkspace),
config.enableBroadCastChannelProvider && createIndexedDBDownloadProvider(blockSuiteWorkspace),
createBroadCastChannelProvider(blockSuiteWorkspace), ] as Provider[];
createIndexedDBBackgroundProvider(blockSuiteWorkspace),
createIndexedDBDownloadProvider(blockSuiteWorkspace), if (config.enableBroadCastChannelProvider) {
environment.isDesktop && createSQLiteProvider(blockSuiteWorkspace), providers.push(createBroadCastChannelProvider(blockSuiteWorkspace));
] as any[] }
).filter(v => Boolean(v));
if (environment.isDesktop) {
providers.push(
createSQLiteProvider(blockSuiteWorkspace),
createSQLiteDBDownloadProvider(blockSuiteWorkspace)
);
}
return providers;
}; };
export const createAffineProviders = ( export const createAffineProviders = (
+6 -2
View File
@@ -78,12 +78,16 @@ export interface LocalIndexedDBBackgroundProvider extends BackgroundProvider {
flavour: 'local-indexeddb-background'; flavour: 'local-indexeddb-background';
} }
export interface LocalIndexedDBDownloadProvider extends NecessaryProvider {
flavour: 'local-indexeddb';
}
export interface SQLiteProvider extends BackgroundProvider { export interface SQLiteProvider extends BackgroundProvider {
flavour: 'sqlite'; flavour: 'sqlite';
} }
export interface LocalIndexedDBDownloadProvider extends NecessaryProvider { export interface SQLiteDBDownloadProvider extends NecessaryProvider {
flavour: 'local-indexeddb'; flavour: 'sqlite-download';
} }
export interface AffineWebSocketProvider extends BackgroundProvider { export interface AffineWebSocketProvider extends BackgroundProvider {