mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-01 01:29:31 +08:00
fix: import workspace may only show default preload page (#2685)
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user