feat: init data from cloud

This commit is contained in:
DarkSky
2023-01-04 22:14:48 +08:00
parent 151a2a4311
commit 00dd7e9621
4 changed files with 90 additions and 38 deletions
+1 -8
View File
@@ -37,7 +37,6 @@ export class DataCenter {
readonly signals = { readonly signals = {
listAdd: new Signal<WorkspaceLoadEvent>(), listAdd: new Signal<WorkspaceLoadEvent>(),
listRemove: new Signal<string>(), listRemove: new Signal<string>(),
workspaceLoaded: new Signal<WorkspaceLoadEvent>(),
}; };
static async init(debug: boolean): Promise<DataCenter> { static async init(debug: boolean): Promise<DataCenter> {
@@ -63,12 +62,6 @@ export class DataCenter {
this.signals.listRemove.on(workspace => { this.signals.listRemove.on(workspace => {
this._config.delete(`list:${workspace}`); this._config.delete(`list:${workspace}`);
}); });
this.signals.workspaceLoaded.on(e => {
this._config.set(`list:${e.workspace}`, {
provider: e.provider,
locally: e.locally,
});
});
} }
get apis(): Readonly<Apis> { get apis(): Readonly<Apis> {
@@ -141,7 +134,7 @@ export class DataCenter {
const logger = this._logger.extend(`auth:${providerId}`); const logger = this._logger.extend(`auth:${providerId}`);
logger.enabled = this._logger.enabled; logger.enabled = this._logger.enabled;
await Provider.auth(config, logger); await Provider.auth(config, logger, this.signals);
} }
} }
@@ -1,11 +1,17 @@
import assert from 'assert'; import assert from 'assert';
import { applyUpdate } from 'yjs'; import { applyUpdate, Doc } from 'yjs';
import type { ConfigStore, InitialParams, Logger } from '../index.js'; import type {
ConfigStore,
DataCenterSignals,
InitialParams,
Logger,
} from '../index.js';
import { token, Callback, getApis } from '../../apis/index.js'; import { token, Callback, getApis } from '../../apis/index.js';
import { LocalProvider } from '../local/index.js'; import { LocalProvider } from '../local/index.js';
import { WebsocketProvider } from './sync.js'; import { WebsocketProvider } from './sync.js';
import { IndexedDBProvider } from '../local/indexeddb.js';
export class AffineProvider extends LocalProvider { export class AffineProvider extends LocalProvider {
static id = 'affine'; static id = 'affine';
@@ -55,7 +61,14 @@ export class AffineProvider extends LocalProvider {
} }
async initData() { async initData() {
await super.initData(); const databases = await indexedDB.databases();
await super.initData(
// set locally to true if exists a same name db
databases
.map(db => db.name)
.filter(v => v)
.includes(this._workspace.room)
);
const workspace = this._workspace; const workspace = this._workspace;
const doc = workspace.doc; const doc = workspace.doc;
@@ -64,23 +77,29 @@ export class AffineProvider extends LocalProvider {
if (workspace.room && token.isLogin) { if (workspace.room && token.isLogin) {
try { try {
const updates = await this._apis.downloadWorkspace(workspace.room); // init data from cloud
if (updates) { await AffineProvider._initCloudDoc(
await new Promise(resolve => { workspace.room,
doc.once('update', resolve); doc,
applyUpdate(doc, new Uint8Array(updates)); this._logger,
}); this._signals
// Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later );
this._ws = new WebsocketProvider('/', workspace.room, doc);
await new Promise<void>((resolve, reject) => { // Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later
// TODO: synced will also be triggered on reconnection after losing sync this._ws = new WebsocketProvider('/', workspace.room, doc);
// There needs to be an event mechanism to emit the synchronization state to the upper layer await new Promise<void>((resolve, reject) => {
assert(this._ws); // TODO: synced will also be triggered on reconnection after losing sync
this._ws.once('synced', () => resolve()); // There needs to be an event mechanism to emit the synchronization state to the upper layer
this._ws.once('lost-connection', () => resolve()); assert(this._ws);
this._ws.once('connection-error', () => reject()); this._ws.once('synced', () => resolve());
}); this._ws.once('lost-connection', () => resolve());
} this._ws.once('connection-error', () => reject());
});
this._signals.listAdd.emit({
workspace: workspace.room,
provider: this.id,
locally: true,
});
} catch (e) { } catch (e) {
this._logger('Failed to init cloud workspace', e); this._logger('Failed to init cloud workspace', e);
} }
@@ -92,7 +111,38 @@ export class AffineProvider extends LocalProvider {
doc.getMap('space:meta'); doc.getMap('space:meta');
} }
static async auth(config: Readonly<ConfigStore<string>>, logger: Logger) { private static async _initCloudDoc(
workspace: string,
doc: Doc,
logger: Logger,
signals: DataCenterSignals
) {
const apis = getApis();
logger(`Loading ${workspace}...`);
const updates = await apis.downloadWorkspace(workspace);
if (updates) {
await new Promise(resolve => {
doc.once('update', resolve);
applyUpdate(doc, new Uint8Array(updates));
});
logger(`Loaded: ${workspace}`);
// only add to list as online workspace
signals.listAdd.emit({
workspace,
provider: this.id,
// at this time we always download full workspace
// but after we support sub doc, we can only download metadata
locally: false,
});
}
}
static async auth(
config: Readonly<ConfigStore<string>>,
logger: Logger,
signals: DataCenterSignals
) {
const refreshToken = await config.get('token'); const refreshToken = await config.get('token');
if (refreshToken) { if (refreshToken) {
await token.refreshToken(refreshToken); await token.refreshToken(refreshToken);
@@ -112,5 +162,14 @@ export class AffineProvider extends LocalProvider {
logger(`login success: ${user.displayName}`); logger(`login success: ${user.displayName}`);
// TODO: refresh local workspace data // TODO: refresh local workspace data
const workspaces = await apis.getWorkspaces();
await Promise.all(
workspaces.map(async ({ id }) => {
const doc = new Doc();
const idb = new IndexedDBProvider(id, doc);
await idb.whenSynced;
await this._initCloudDoc(id, doc, logger, signals);
})
);
} }
} }
+6 -2
View File
@@ -61,8 +61,12 @@ export class BaseProvider {
return this._workspace; return this._workspace;
} }
static async auth(_config: Readonly<ConfigStore>, _logger: Logger) { static async auth(
throw Error('Not implemented: auth'); _config: Readonly<ConfigStore>,
logger: Logger,
_signals: DataCenterSignals
) {
logger("This provider doesn't require authentication");
} }
// get workspace listreturn a map of workspace id and boolean // get workspace listreturn a map of workspace id and boolean
@@ -1,7 +1,7 @@
import type { BlobStorage } from '@blocksuite/store'; import type { BlobStorage } from '@blocksuite/store';
import assert from 'assert'; import assert from 'assert';
import type { ConfigStore, InitialParams, Logger } from '../index.js'; import type { ConfigStore, InitialParams } from '../index.js';
import { BaseProvider } from '../base.js'; import { BaseProvider } from '../base.js';
import { IndexedDBProvider } from './indexeddb.js'; import { IndexedDBProvider } from './indexeddb.js';
@@ -21,7 +21,7 @@ export class LocalProvider extends BaseProvider {
this._blobs = blobs; this._blobs = blobs;
} }
async initData() { async initData(locally = true) {
assert(this._workspace.room); assert(this._workspace.room);
this._logger('Loading local data'); this._logger('Loading local data');
this._idb = new IndexedDBProvider( this._idb = new IndexedDBProvider(
@@ -35,7 +35,7 @@ export class LocalProvider extends BaseProvider {
this._signals.listAdd.emit({ this._signals.listAdd.emit({
workspace: this._workspace.room, workspace: this._workspace.room,
provider: this.id, provider: this.id,
locally: true, locally,
}); });
} }
@@ -60,10 +60,6 @@ export class LocalProvider extends BaseProvider {
return this._blobs.set(blob); return this._blobs.set(blob);
} }
static async auth(_config: Readonly<ConfigStore>, logger: Logger) {
logger("Local provider doesn't require authentication");
}
static async list( static async list(
config: Readonly<ConfigStore<boolean>> config: Readonly<ConfigStore<boolean>>
): Promise<Map<string, boolean> | undefined> { ): Promise<Map<string, boolean> | undefined> {