mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(core): new worker workspace engine (#9257)
This commit is contained in:
@@ -18,8 +18,11 @@ export class AwarenessSyncImpl implements AwarenessSync {
|
||||
|
||||
async update(record: AwarenessRecord, origin?: string) {
|
||||
await Promise.all(
|
||||
[this.storages.local, ...Object.values(this.storages.remotes)].map(peer =>
|
||||
peer.update(record, origin)
|
||||
[this.storages.local, ...Object.values(this.storages.remotes)].map(
|
||||
peer =>
|
||||
peer.connection.status === 'connected'
|
||||
? peer.update(record, origin)
|
||||
: Promise.resolve()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,10 +73,14 @@ export class BlobSyncImpl implements BlobSync {
|
||||
async fullSync(signal?: AbortSignal) {
|
||||
throwIfAborted(signal);
|
||||
|
||||
await this.storages.local.connection.waitForConnected(signal);
|
||||
|
||||
for (const [remotePeer, remote] of Object.entries(this.storages.remotes)) {
|
||||
let localList: string[] = [];
|
||||
let remoteList: string[] = [];
|
||||
|
||||
await remote.connection.waitForConnected(signal);
|
||||
|
||||
try {
|
||||
localList = (await this.storages.local.list(signal)).map(b => b.key);
|
||||
throwIfAborted(signal);
|
||||
@@ -150,7 +154,7 @@ export class BlobSyncImpl implements BlobSync {
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.abort?.abort();
|
||||
this.abort?.abort(MANUALLY_STOP);
|
||||
this.abort = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Observable } from 'rxjs';
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
import { combineLatest, map, of, ReplaySubject, share } from 'rxjs';
|
||||
|
||||
import type { DocStorage, SyncStorage } from '../../storage';
|
||||
import { DummyDocStorage } from '../../storage/dummy/doc';
|
||||
@@ -38,18 +38,32 @@ export class DocSyncImpl implements DocSync {
|
||||
);
|
||||
private abort: AbortController | null = null;
|
||||
|
||||
get state$() {
|
||||
return combineLatest(this.peers.map(peer => peer.peerState$)).pipe(
|
||||
map(allPeers => ({
|
||||
total: allPeers.reduce((acc, peer) => Math.max(acc, peer.total), 0),
|
||||
syncing: allPeers.reduce((acc, peer) => Math.max(acc, peer.syncing), 0),
|
||||
synced: allPeers.every(peer => peer.synced),
|
||||
retrying: allPeers.some(peer => peer.retrying),
|
||||
errorMessage:
|
||||
allPeers.find(peer => peer.errorMessage)?.errorMessage ?? null,
|
||||
}))
|
||||
) as Observable<DocSyncState>;
|
||||
}
|
||||
state$ = combineLatest(this.peers.map(peer => peer.peerState$)).pipe(
|
||||
map(allPeers =>
|
||||
allPeers.length === 0
|
||||
? {
|
||||
total: 0,
|
||||
syncing: 0,
|
||||
synced: true,
|
||||
retrying: false,
|
||||
errorMessage: null,
|
||||
}
|
||||
: {
|
||||
total: allPeers.reduce((acc, peer) => Math.max(acc, peer.total), 0),
|
||||
syncing: allPeers.reduce(
|
||||
(acc, peer) => Math.max(acc, peer.syncing),
|
||||
0
|
||||
),
|
||||
synced: allPeers.every(peer => peer.synced),
|
||||
retrying: allPeers.some(peer => peer.retrying),
|
||||
errorMessage:
|
||||
allPeers.find(peer => peer.errorMessage)?.errorMessage ?? null,
|
||||
}
|
||||
),
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
})
|
||||
) as Observable<DocSyncState>;
|
||||
|
||||
constructor(
|
||||
readonly storages: PeerStorageOptions<DocStorage>,
|
||||
@@ -105,7 +119,7 @@ export class DocSyncImpl implements DocSync {
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.abort?.abort();
|
||||
this.abort?.abort(MANUALLY_STOP);
|
||||
this.abort = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { remove } from 'lodash-es';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { Observable, ReplaySubject, share, Subject } from 'rxjs';
|
||||
import { diffUpdate, encodeStateVectorFromUpdate, mergeUpdates } from 'yjs';
|
||||
|
||||
import type { DocStorage, SyncStorage } from '../../storage';
|
||||
@@ -119,54 +119,65 @@ export class DocSyncPeer {
|
||||
};
|
||||
private readonly statusUpdatedSubject$ = new Subject<string | true>();
|
||||
|
||||
get peerState$() {
|
||||
return new Observable<PeerState>(subscribe => {
|
||||
const next = () => {
|
||||
if (this.status.skipped) {
|
||||
subscribe.next({
|
||||
total: 0,
|
||||
syncing: 0,
|
||||
synced: true,
|
||||
retrying: false,
|
||||
errorMessage: null,
|
||||
});
|
||||
} else if (!this.status.syncing) {
|
||||
// if syncing = false, jobMap is empty
|
||||
subscribe.next({
|
||||
total: this.status.docs.size,
|
||||
syncing: this.status.docs.size,
|
||||
synced: false,
|
||||
retrying: this.status.retrying,
|
||||
errorMessage: this.status.errorMessage,
|
||||
});
|
||||
} else {
|
||||
const syncing = this.status.jobMap.size;
|
||||
subscribe.next({
|
||||
total: this.status.docs.size,
|
||||
syncing: syncing,
|
||||
retrying: this.status.retrying,
|
||||
errorMessage: this.status.errorMessage,
|
||||
synced: syncing === 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
peerState$ = new Observable<PeerState>(subscribe => {
|
||||
const next = () => {
|
||||
if (this.status.skipped) {
|
||||
subscribe.next({
|
||||
total: 0,
|
||||
syncing: 0,
|
||||
synced: true,
|
||||
retrying: false,
|
||||
errorMessage: null,
|
||||
});
|
||||
} else if (!this.status.syncing) {
|
||||
// if syncing = false, jobMap is empty
|
||||
subscribe.next({
|
||||
total: this.status.docs.size,
|
||||
syncing: this.status.docs.size,
|
||||
synced: false,
|
||||
retrying: this.status.retrying,
|
||||
errorMessage: this.status.errorMessage,
|
||||
});
|
||||
} else {
|
||||
const syncing = this.status.jobMap.size;
|
||||
subscribe.next({
|
||||
total: this.status.docs.size,
|
||||
syncing: syncing,
|
||||
retrying: this.status.retrying,
|
||||
errorMessage: this.status.errorMessage,
|
||||
synced: syncing === 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
next();
|
||||
const dispose = this.statusUpdatedSubject$.subscribe(() => {
|
||||
next();
|
||||
return this.statusUpdatedSubject$.subscribe(() => {
|
||||
next();
|
||||
});
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
dispose.unsubscribe();
|
||||
};
|
||||
}).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
})
|
||||
);
|
||||
|
||||
docState$(docId: string) {
|
||||
return new Observable<PeerDocState>(subscribe => {
|
||||
const next = () => {
|
||||
const syncing =
|
||||
!this.status.connectedDocs.has(docId) ||
|
||||
this.status.jobMap.has(docId);
|
||||
|
||||
if (this.status.skipped) {
|
||||
subscribe.next({
|
||||
syncing: false,
|
||||
synced: true,
|
||||
retrying: false,
|
||||
errorMessage: null,
|
||||
});
|
||||
}
|
||||
subscribe.next({
|
||||
syncing: syncing,
|
||||
synced: !syncing,
|
||||
syncing:
|
||||
!this.status.connectedDocs.has(docId) ||
|
||||
this.status.jobMap.has(docId),
|
||||
synced: !this.status.jobMap.has(docId),
|
||||
retrying: this.status.retrying,
|
||||
errorMessage: this.status.errorMessage,
|
||||
});
|
||||
@@ -524,10 +535,6 @@ export class DocSyncPeer {
|
||||
const disposes: (() => void)[] = [];
|
||||
|
||||
try {
|
||||
console.info('Remote sync started');
|
||||
this.status.syncing = true;
|
||||
this.statusUpdatedSubject$.next(true);
|
||||
|
||||
// wait for all storages to connect, timeout after 30s
|
||||
await Promise.race([
|
||||
Promise.all([
|
||||
@@ -547,6 +554,10 @@ export class DocSyncPeer {
|
||||
}),
|
||||
]);
|
||||
|
||||
console.info('Remote sync started');
|
||||
this.status.syncing = true;
|
||||
this.statusUpdatedSubject$.next(true);
|
||||
|
||||
// throw error if failed to connect
|
||||
for (const storage of [this.remote, this.local, this.syncMetadata]) {
|
||||
// abort if disconnected
|
||||
|
||||
Reference in New Issue
Block a user