feat: support force sync by click (#4089)

Co-authored-by: JimmFly <yangjinfei001@gmail.com>
This commit is contained in:
Alex Yang
2023-09-01 01:15:07 -05:00
committed by GitHub
parent 83e7e9db8d
commit 92f0b31196
13 changed files with 341 additions and 117 deletions

View File

@@ -1,4 +1,4 @@
import type { StatusAdapter } from '@affine/y-provider';
import type { DataSourceAdapter } from '@affine/y-provider';
import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store';
import type {
@@ -32,7 +32,7 @@ export interface BroadCastChannelProvider extends PassiveDocProvider {
* Long polling provider with local IndexedDB
*/
export interface LocalIndexedDBBackgroundProvider
extends StatusAdapter,
extends DataSourceAdapter,
PassiveDocProvider {
flavour: 'local-indexeddb-background';
}
@@ -41,7 +41,7 @@ export interface LocalIndexedDBDownloadProvider extends ActiveDocProvider {
flavour: 'local-indexeddb';
}
export interface SQLiteProvider extends PassiveDocProvider, StatusAdapter {
export interface SQLiteProvider extends PassiveDocProvider, DataSourceAdapter {
flavour: 'sqlite';
}
@@ -49,7 +49,9 @@ export interface SQLiteDBDownloadProvider extends ActiveDocProvider {
flavour: 'sqlite-download';
}
export interface AffineSocketIOProvider extends PassiveDocProvider {
export interface AffineSocketIOProvider
extends PassiveDocProvider,
DataSourceAdapter {
flavour: 'affine-socket-io';
}

View File

@@ -1,4 +1,4 @@
import type { Status, StatusAdapter } from '@affine/y-provider';
import type { DataSourceAdapter, Status } from '@affine/y-provider';
import { useCallback, useSyncExternalStore } from 'react';
type UIStatus =
@@ -7,9 +7,9 @@ type UIStatus =
type: 'unknown';
};
export function useDataSourceStatus(datasource: StatusAdapter): UIStatus {
export function useDataSourceStatus(provider: DataSourceAdapter): UIStatus {
return useSyncExternalStore(
datasource.subscribeStatusChange,
useCallback(() => datasource.status, [datasource])
provider.subscribeStatusChange,
useCallback(() => provider.status, [provider])
);
}

View File

@@ -35,9 +35,15 @@ const createAffineSocketIOProvider: DocProviderCreator = (
{ awareness }
): AffineSocketIOProvider => {
const dataSource = createAffineDataSource(id, doc, awareness);
const lazyProvider = createLazyProvider(doc, dataSource, {
origin: 'affine-socket-io',
});
return {
flavour: 'affine-socket-io',
...createLazyProvider(doc, dataSource),
...lazyProvider,
get status() {
return lazyProvider.status;
},
};
};
@@ -50,6 +56,7 @@ const createIndexedDBBackgroundProvider: DocProviderCreator = (
let connected = false;
return {
flavour: 'local-indexeddb-background',
datasource: indexeddbProvider.datasource,
passive: true,
get status() {
return indexeddbProvider.status;

View File

@@ -54,11 +54,12 @@ export const createSQLiteProvider: DocProviderCreator = (
id,
rootDoc
): SQLiteProvider => {
let datasource: ReturnType<typeof createDatasource> | null = null;
const datasource = createDatasource(id);
let provider: ReturnType<typeof createLazyProvider> | null = null;
let connected = false;
return {
flavour: 'sqlite',
datasource,
passive: true,
get status() {
assertExists(provider);
@@ -69,14 +70,12 @@ export const createSQLiteProvider: DocProviderCreator = (
return provider.subscribeStatusChange(onStatusChange);
},
connect: () => {
datasource = createDatasource(id);
provider = createLazyProvider(rootDoc, datasource, { origin: 'sqlite' });
provider.connect();
connected = true;
},
disconnect: () => {
provider?.disconnect();
datasource = null;
provider = null;
connected = false;
},

View File

@@ -4,6 +4,7 @@ import {
writeOperation,
} from '@affine/y-provider';
import { assertExists } from '@blocksuite/global/utils';
import type { IDBPDatabase } from 'idb';
import { openDB } from 'idb';
import type { Doc } from 'yjs';
import { diffUpdate, encodeStateVectorFromUpdate } from 'yjs';
@@ -31,13 +32,20 @@ export const createIndexedDBDatasource = ({
dbName: string;
mergeCount?: number;
}) => {
const dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
upgrade: upgradeDB,
});
let dbPromise: Promise<IDBPDatabase<BlockSuiteBinaryDB>> | null = null;
const getDb = async () => {
if (dbPromise === null) {
dbPromise = openDB<BlockSuiteBinaryDB>(dbName, dbVersion, {
upgrade: upgradeDB,
});
}
return dbPromise;
};
const adapter = {
queryDocState: async (guid, options) => {
try {
const db = await dbPromise;
const db = await getDb();
const store = db
.transaction('workspace', 'readonly')
.objectStore('workspace');
@@ -64,7 +72,7 @@ export const createIndexedDBDatasource = ({
},
sendDocUpdate: async (guid, update) => {
try {
const db = await dbPromise;
const db = await getDb();
const store = db
.transaction('workspace', 'readwrite')
.objectStore('workspace');
@@ -96,10 +104,15 @@ export const createIndexedDBDatasource = ({
return {
...adapter,
disconnect: () => {
dbPromise.then(db => db.close()).catch(console.error);
getDb()
.then(db => db.close())
.then(() => {
dbPromise = null;
})
.catch(console.error);
},
cleanup: async () => {
const db = await dbPromise;
const db = await getDb();
await db.clear('workspace');
},
};
@@ -112,7 +125,7 @@ export const createIndexedDBProvider = (
doc: Doc,
dbName: string = DEFAULT_DB_NAME
): IndexedDBProvider => {
let datasource: ReturnType<typeof createIndexedDBDatasource> | null = null;
const datasource = createIndexedDBDatasource({ dbName, mergeCount });
let provider: ReturnType<typeof createLazyProvider> | null = null;
const apis = {
@@ -128,14 +141,12 @@ export const createIndexedDBProvider = (
if (apis.connected) {
apis.disconnect();
}
datasource = createIndexedDBDatasource({ dbName, mergeCount });
provider = createLazyProvider(doc, datasource, { origin: 'idb' });
provider.connect();
},
disconnect: () => {
datasource?.disconnect();
provider?.disconnect();
datasource = null;
provider = null;
},
cleanup: async () => {
@@ -144,6 +155,7 @@ export const createIndexedDBProvider = (
get connected() {
return provider?.connected || false;
},
datasource,
} satisfies IndexedDBProvider;
return apis;

View File

@@ -1,4 +1,4 @@
import type { StatusAdapter } from '@affine/y-provider';
import type { DataSourceAdapter } from '@affine/y-provider';
import type { DBSchema, IDBPDatabase } from 'idb';
export const dbVersion = 1;
@@ -9,7 +9,7 @@ export function upgradeDB(db: IDBPDatabase<BlockSuiteBinaryDB>) {
db.createObjectStore('milestone', { keyPath: 'id' });
}
export interface IndexedDBProvider extends StatusAdapter {
export interface IndexedDBProvider extends DataSourceAdapter {
connect: () => void;
disconnect: () => void;
cleanup: () => Promise<void>;

View File

@@ -1,6 +1,6 @@
import type { DocState, StatusAdapter } from './types';
import type { DocState } from './types';
export interface DatasourceDocAdapter extends Partial<StatusAdapter> {
export interface DatasourceDocAdapter {
/**
* request diff update from other clients
*/

View File

@@ -7,7 +7,7 @@ import {
} from 'yjs';
import type { DatasourceDocAdapter } from './data-source';
import type { StatusAdapter } from './types';
import type { DataSourceAdapter } from './types';
import type { Status } from './types';
function getDoc(doc: Doc, guid: string): Doc | undefined {
@@ -45,7 +45,7 @@ export const createLazyProvider = (
rootDoc: Doc,
datasource: DatasourceDocAdapter,
options: LazyProviderOptions = {}
): DocProvider & StatusAdapter => {
): DocProvider & DataSourceAdapter => {
let connected = false;
const pendingMap = new Map<string, Uint8Array[]>(); // guid -> pending-updates
const disposableMap = new Map<string, Set<() => void>>();
@@ -62,21 +62,17 @@ export const createLazyProvider = (
const callbackSet = new Set<() => void>();
const changeStatus = (newStatus: Status) => {
// simulate a stack, each syncing and synced should be paired
if (newStatus.type === 'idle') {
if (connected && syncingStack !== 0) {
console.error('syncingStatus !== 0, this should not happen');
}
syncingStack = 0;
}
if (newStatus.type === 'syncing') {
syncingStack++;
}
if (newStatus.type === 'synced' || newStatus.type === 'error') {
} else if (newStatus.type === 'synced' || newStatus.type === 'error') {
syncingStack--;
}
if (syncingStack < 0) {
console.error('syncingStatus < 0, this should not happen');
console.error(
'syncingStatus < 0, this should not happen',
options.origin
);
}
if (syncingStack === 0) {
@@ -85,6 +81,17 @@ export const createLazyProvider = (
if (newStatus.type !== 'synced') {
currentStatus = newStatus;
}
if (syncingStack === 0) {
if (!connected) {
currentStatus = {
type: 'idle',
};
} else {
currentStatus = {
type: 'synced',
};
}
}
callbackSet.forEach(cb => cb());
};
@@ -102,12 +109,6 @@ export const createLazyProvider = (
stateVector: encodeStateVector(doc),
})
.then(remoteUpdate => {
if (!connected) {
changeStatus({
type: 'idle',
});
return;
}
changeStatus({
type: 'synced',
});
@@ -201,9 +202,6 @@ export const createLazyProvider = (
function setupDatasourceListeners() {
assertExists(abortController, 'abortController should be defined');
const unsubscribe = datasource.onDocUpdate?.((guid, update) => {
if (!connected) {
return;
}
changeStatus({
type: 'syncing',
});
@@ -283,12 +281,6 @@ export const createLazyProvider = (
// but we want to populate the cache for later update events
connectDoc(rootDoc)
.then(() => {
if (!connected) {
changeStatus({
type: 'idle',
});
return;
}
changeStatus({
type: 'synced',
});
@@ -305,9 +297,6 @@ export const createLazyProvider = (
async function disconnect() {
connected = false;
changeStatus({
type: 'idle',
});
disposeAll();
assertExists(abortController, 'abortController should be defined');
abortController.abort();
@@ -349,5 +338,7 @@ export const createLazyProvider = (
passive: true,
connect,
disconnect,
datasource,
};
};

View File

@@ -1,3 +1,5 @@
import type { DatasourceDocAdapter } from './data-source';
export type Status =
| {
type: 'idle';
@@ -10,11 +12,13 @@ export type Status =
}
| {
type: 'error';
error: Error;
error: unknown;
};
export interface StatusAdapter {
export interface DataSourceAdapter {
datasource: DatasourceDocAdapter;
readonly status: Status;
subscribeStatusChange(onStatusChange: () => void): () => void;
}