Sync current open page every 5s
This commit is contained in:
parent
4af7afa4aa
commit
7498cc1ecb
@ -3,6 +3,7 @@ import buildMarkdown from "../markdown_parser/parser.ts";
|
||||
import { parse } from "../markdown_parser/parse_tree.ts";
|
||||
import type { FileMeta } from "../types.ts";
|
||||
import { SpacePrimitives } from "./space_primitives.ts";
|
||||
import { EventEmitter } from "../../plugos/event.ts";
|
||||
|
||||
type SyncHash = number;
|
||||
|
||||
@ -28,13 +29,19 @@ export type SyncOptions = {
|
||||
onSyncProgress?: (syncStatus: SyncStatus) => void;
|
||||
};
|
||||
|
||||
type SyncDirection = "primary->secondary" | "secondary->primary";
|
||||
export type SyncEvents = {
|
||||
fileSynced: (meta: FileMeta, direction: SyncDirection) => void;
|
||||
};
|
||||
|
||||
// Implementation of this algorithm https://unterwaditzer.net/2016/sync-algorithm.html
|
||||
export class SpaceSync {
|
||||
export class SpaceSync extends EventEmitter<SyncEvents> {
|
||||
constructor(
|
||||
private primary: SpacePrimitives,
|
||||
private secondary: SpacePrimitives,
|
||||
readonly options: SyncOptions,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async syncFiles(
|
||||
@ -143,6 +150,7 @@ export class SpaceSync {
|
||||
writtenMeta.lastModified,
|
||||
]);
|
||||
operations++;
|
||||
await this.emit("fileSynced", writtenMeta, "primary->secondary");
|
||||
} else if (
|
||||
secondaryHash !== undefined && primaryHash === undefined &&
|
||||
!snapshot.has(name)
|
||||
@ -165,6 +173,7 @@ export class SpaceSync {
|
||||
secondaryHash,
|
||||
]);
|
||||
operations++;
|
||||
await this.emit("fileSynced", writtenMeta, "secondary->primary");
|
||||
} else if (
|
||||
primaryHash !== undefined && snapshot.has(name) &&
|
||||
secondaryHash === undefined
|
||||
@ -227,6 +236,7 @@ export class SpaceSync {
|
||||
writtenMeta.lastModified,
|
||||
]);
|
||||
operations++;
|
||||
await this.emit("fileSynced", writtenMeta, "primary->secondary");
|
||||
} else if (
|
||||
primaryHash !== undefined && secondaryHash !== undefined &&
|
||||
snapshot.get(name) &&
|
||||
@ -251,6 +261,7 @@ export class SpaceSync {
|
||||
secondaryHash,
|
||||
]);
|
||||
operations++;
|
||||
await this.emit("fileSynced", writtenMeta, "secondary->primary");
|
||||
} else if (
|
||||
( // File changed on both ends, but we don't have any info in the snapshot (resync scenario?): have to run through conflict handling
|
||||
primaryHash !== undefined && secondaryHash !== undefined &&
|
||||
|
@ -21,7 +21,7 @@ import { PlugSpacePrimitives } from "../common/spaces/plug_space_primitives.ts";
|
||||
import { IndexedDBSpacePrimitives } from "../common/spaces/indexeddb_space_primitives.ts";
|
||||
import { FileMetaSpacePrimitives } from "../common/spaces/file_meta_space_primitives.ts";
|
||||
import { EventedSpacePrimitives } from "../common/spaces/evented_space_primitives.ts";
|
||||
import { SyncService } from "./sync_service.ts";
|
||||
import { pageSyncInterval, SyncService } from "./sync_service.ts";
|
||||
import { simpleHash } from "../common/crypto.ts";
|
||||
import { DexieKVStore } from "../plugos/lib/kv_store.dexie.ts";
|
||||
import { SyncStatus } from "../common/spaces/sync.ts";
|
||||
@ -166,6 +166,10 @@ export class Client {
|
||||
this.loadCustomStyles().catch(console.error);
|
||||
|
||||
await this.dispatchAppEvent("editor:init");
|
||||
|
||||
setInterval(() => {
|
||||
this.syncService.syncFile(`${this.currentPage!}.md`);
|
||||
}, pageSyncInterval);
|
||||
}
|
||||
|
||||
private initSync() {
|
||||
@ -215,6 +219,14 @@ export class Client {
|
||||
Math.round(status.filesProcessed / status.totalFiles * 100),
|
||||
);
|
||||
});
|
||||
this.syncService.spaceSync.on({
|
||||
fileSynced: (meta, direction) => {
|
||||
if (meta.name.endsWith(".md") && direction === "secondary->primary") {
|
||||
// We likely polled the currently open page which trigggered a local update, let's update the editor accordingly
|
||||
this.space.getPageMeta(meta.name.slice(0, -3));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private initNavigator() {
|
||||
|
@ -67,7 +67,7 @@ export function MiniEditor(
|
||||
|
||||
useEffect(() => {
|
||||
if (editorDiv.current) {
|
||||
console.log("Creating editor view");
|
||||
// console.log("Creating editor view");
|
||||
const editorView = new EditorView({
|
||||
state: buildEditorState(),
|
||||
parent: editorDiv.current!,
|
||||
|
@ -14,6 +14,9 @@ const syncSnapshotKey = "syncSnapshot";
|
||||
// Keeps the start time of an ongoing sync, is reset once the sync is done
|
||||
const syncStartTimeKey = "syncStartTime";
|
||||
|
||||
// Keeps the start time of the last full sync cycle
|
||||
const syncLastFullCycleKey = "syncLastFullCycle";
|
||||
|
||||
// Keeps the last time an activity was registered, used to detect if a sync is still alive and whether a new one should be started already
|
||||
const syncLastActivityKey = "syncLastActivity";
|
||||
|
||||
@ -23,7 +26,10 @@ const syncInitialFullSyncCompletedKey = "syncInitialFullSyncCompleted";
|
||||
const syncMaxIdleTimeout = 1000 * 20; // 20s
|
||||
|
||||
// How often to sync the whole space
|
||||
const syncInterval = 10 * 1000; // Every 10s
|
||||
const spaceSyncInterval = 10 * 1000; // Every 10s, but because of the check this may happen mid-cycle so after ~15s
|
||||
|
||||
// Used from Client
|
||||
export const pageSyncInterval = 5000;
|
||||
|
||||
/**
|
||||
* The SyncService primarily wraps the SpaceSync engine but also coordinates sync between
|
||||
@ -88,15 +94,24 @@ export class SyncService {
|
||||
return this.kvStore.has(syncInitialFullSyncCompletedKey);
|
||||
}
|
||||
|
||||
async registerSyncStart(): Promise<void> {
|
||||
async registerSyncStart(fullSync: boolean): Promise<void> {
|
||||
// Assumption: this is called after an isSyncing() check
|
||||
await this.kvStore.batchSet([{
|
||||
key: syncStartTimeKey,
|
||||
value: Date.now(),
|
||||
}, {
|
||||
key: syncLastActivityKey,
|
||||
value: Date.now(),
|
||||
}]);
|
||||
await this.kvStore.batchSet([
|
||||
{
|
||||
key: syncStartTimeKey,
|
||||
value: Date.now(),
|
||||
},
|
||||
{
|
||||
key: syncLastActivityKey,
|
||||
value: Date.now(),
|
||||
},
|
||||
...fullSync // If this is a full sync cycle
|
||||
? [{
|
||||
key: syncLastFullCycleKey,
|
||||
value: Date.now(),
|
||||
}]
|
||||
: [],
|
||||
]);
|
||||
}
|
||||
|
||||
async registerSyncProgress(status?: SyncStatus): Promise<void> {
|
||||
@ -150,16 +165,18 @@ export class SyncService {
|
||||
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const lastActivity = (await this.kvStore.get(syncLastActivityKey)) || 0;
|
||||
if (lastActivity && Date.now() - lastActivity > syncInterval) {
|
||||
// It's been a while since the last activity, let's sync the whole space
|
||||
// The reason to do this check is that there may be multiple tabs open each with their sync cycle
|
||||
await this.syncSpace();
|
||||
if (!await this.isSyncing()) {
|
||||
const lastFullCycle =
|
||||
(await this.kvStore.get(syncLastFullCycleKey)) || 0;
|
||||
if (lastFullCycle && Date.now() - lastFullCycle > spaceSyncInterval) {
|
||||
// It's been a while since the last full cycle, let's sync the whole space
|
||||
await this.syncSpace();
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
}
|
||||
}, syncInterval / 2); // check every half the sync cycle because actually running the sync takes some time therefore we don't want to wait for the full cycle
|
||||
}, spaceSyncInterval / 2); // check every half the sync cycle because actually running the sync takes some time therefore we don't want to wait for the full cycle
|
||||
}
|
||||
|
||||
async syncSpace(): Promise<number> {
|
||||
@ -167,7 +184,7 @@ export class SyncService {
|
||||
console.log("Already syncing");
|
||||
return 0;
|
||||
}
|
||||
await this.registerSyncStart();
|
||||
await this.registerSyncStart(true);
|
||||
let operations = 0;
|
||||
const snapshot = await this.getSnapshot();
|
||||
// console.log("Excluded from sync", excludedFromSync);
|
||||
@ -197,7 +214,7 @@ export class SyncService {
|
||||
console.log("Already syncing, aborting individual file sync for", name);
|
||||
return;
|
||||
}
|
||||
await this.registerSyncStart();
|
||||
await this.registerSyncStart(false);
|
||||
console.log("Syncing file", name);
|
||||
const snapshot = await this.getSnapshot();
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user