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 { parse } from "../markdown_parser/parse_tree.ts";
|
||||||
import type { FileMeta } from "../types.ts";
|
import type { FileMeta } from "../types.ts";
|
||||||
import { SpacePrimitives } from "./space_primitives.ts";
|
import { SpacePrimitives } from "./space_primitives.ts";
|
||||||
|
import { EventEmitter } from "../../plugos/event.ts";
|
||||||
|
|
||||||
type SyncHash = number;
|
type SyncHash = number;
|
||||||
|
|
||||||
@ -28,13 +29,19 @@ export type SyncOptions = {
|
|||||||
onSyncProgress?: (syncStatus: SyncStatus) => void;
|
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
|
// Implementation of this algorithm https://unterwaditzer.net/2016/sync-algorithm.html
|
||||||
export class SpaceSync {
|
export class SpaceSync extends EventEmitter<SyncEvents> {
|
||||||
constructor(
|
constructor(
|
||||||
private primary: SpacePrimitives,
|
private primary: SpacePrimitives,
|
||||||
private secondary: SpacePrimitives,
|
private secondary: SpacePrimitives,
|
||||||
readonly options: SyncOptions,
|
readonly options: SyncOptions,
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncFiles(
|
async syncFiles(
|
||||||
@ -143,6 +150,7 @@ export class SpaceSync {
|
|||||||
writtenMeta.lastModified,
|
writtenMeta.lastModified,
|
||||||
]);
|
]);
|
||||||
operations++;
|
operations++;
|
||||||
|
await this.emit("fileSynced", writtenMeta, "primary->secondary");
|
||||||
} else if (
|
} else if (
|
||||||
secondaryHash !== undefined && primaryHash === undefined &&
|
secondaryHash !== undefined && primaryHash === undefined &&
|
||||||
!snapshot.has(name)
|
!snapshot.has(name)
|
||||||
@ -165,6 +173,7 @@ export class SpaceSync {
|
|||||||
secondaryHash,
|
secondaryHash,
|
||||||
]);
|
]);
|
||||||
operations++;
|
operations++;
|
||||||
|
await this.emit("fileSynced", writtenMeta, "secondary->primary");
|
||||||
} else if (
|
} else if (
|
||||||
primaryHash !== undefined && snapshot.has(name) &&
|
primaryHash !== undefined && snapshot.has(name) &&
|
||||||
secondaryHash === undefined
|
secondaryHash === undefined
|
||||||
@ -227,6 +236,7 @@ export class SpaceSync {
|
|||||||
writtenMeta.lastModified,
|
writtenMeta.lastModified,
|
||||||
]);
|
]);
|
||||||
operations++;
|
operations++;
|
||||||
|
await this.emit("fileSynced", writtenMeta, "primary->secondary");
|
||||||
} else if (
|
} else if (
|
||||||
primaryHash !== undefined && secondaryHash !== undefined &&
|
primaryHash !== undefined && secondaryHash !== undefined &&
|
||||||
snapshot.get(name) &&
|
snapshot.get(name) &&
|
||||||
@ -251,6 +261,7 @@ export class SpaceSync {
|
|||||||
secondaryHash,
|
secondaryHash,
|
||||||
]);
|
]);
|
||||||
operations++;
|
operations++;
|
||||||
|
await this.emit("fileSynced", writtenMeta, "secondary->primary");
|
||||||
} else if (
|
} 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
|
( // 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 &&
|
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 { IndexedDBSpacePrimitives } from "../common/spaces/indexeddb_space_primitives.ts";
|
||||||
import { FileMetaSpacePrimitives } from "../common/spaces/file_meta_space_primitives.ts";
|
import { FileMetaSpacePrimitives } from "../common/spaces/file_meta_space_primitives.ts";
|
||||||
import { EventedSpacePrimitives } from "../common/spaces/evented_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 { simpleHash } from "../common/crypto.ts";
|
||||||
import { DexieKVStore } from "../plugos/lib/kv_store.dexie.ts";
|
import { DexieKVStore } from "../plugos/lib/kv_store.dexie.ts";
|
||||||
import { SyncStatus } from "../common/spaces/sync.ts";
|
import { SyncStatus } from "../common/spaces/sync.ts";
|
||||||
@ -166,6 +166,10 @@ export class Client {
|
|||||||
this.loadCustomStyles().catch(console.error);
|
this.loadCustomStyles().catch(console.error);
|
||||||
|
|
||||||
await this.dispatchAppEvent("editor:init");
|
await this.dispatchAppEvent("editor:init");
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
this.syncService.syncFile(`${this.currentPage!}.md`);
|
||||||
|
}, pageSyncInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initSync() {
|
private initSync() {
|
||||||
@ -215,6 +219,14 @@ export class Client {
|
|||||||
Math.round(status.filesProcessed / status.totalFiles * 100),
|
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() {
|
private initNavigator() {
|
||||||
|
@ -67,7 +67,7 @@ export function MiniEditor(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorDiv.current) {
|
if (editorDiv.current) {
|
||||||
console.log("Creating editor view");
|
// console.log("Creating editor view");
|
||||||
const editorView = new EditorView({
|
const editorView = new EditorView({
|
||||||
state: buildEditorState(),
|
state: buildEditorState(),
|
||||||
parent: editorDiv.current!,
|
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
|
// Keeps the start time of an ongoing sync, is reset once the sync is done
|
||||||
const syncStartTimeKey = "syncStartTime";
|
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
|
// 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";
|
const syncLastActivityKey = "syncLastActivity";
|
||||||
|
|
||||||
@ -23,7 +26,10 @@ const syncInitialFullSyncCompletedKey = "syncInitialFullSyncCompleted";
|
|||||||
const syncMaxIdleTimeout = 1000 * 20; // 20s
|
const syncMaxIdleTimeout = 1000 * 20; // 20s
|
||||||
|
|
||||||
// How often to sync the whole space
|
// 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
|
* The SyncService primarily wraps the SpaceSync engine but also coordinates sync between
|
||||||
@ -88,15 +94,24 @@ export class SyncService {
|
|||||||
return this.kvStore.has(syncInitialFullSyncCompletedKey);
|
return this.kvStore.has(syncInitialFullSyncCompletedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerSyncStart(): Promise<void> {
|
async registerSyncStart(fullSync: boolean): Promise<void> {
|
||||||
// Assumption: this is called after an isSyncing() check
|
// Assumption: this is called after an isSyncing() check
|
||||||
await this.kvStore.batchSet([{
|
await this.kvStore.batchSet([
|
||||||
|
{
|
||||||
key: syncStartTimeKey,
|
key: syncStartTimeKey,
|
||||||
value: Date.now(),
|
value: Date.now(),
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
key: syncLastActivityKey,
|
key: syncLastActivityKey,
|
||||||
value: Date.now(),
|
value: Date.now(),
|
||||||
}]);
|
},
|
||||||
|
...fullSync // If this is a full sync cycle
|
||||||
|
? [{
|
||||||
|
key: syncLastFullCycleKey,
|
||||||
|
value: Date.now(),
|
||||||
|
}]
|
||||||
|
: [],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerSyncProgress(status?: SyncStatus): Promise<void> {
|
async registerSyncProgress(status?: SyncStatus): Promise<void> {
|
||||||
@ -150,16 +165,18 @@ export class SyncService {
|
|||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const lastActivity = (await this.kvStore.get(syncLastActivityKey)) || 0;
|
if (!await this.isSyncing()) {
|
||||||
if (lastActivity && Date.now() - lastActivity > syncInterval) {
|
const lastFullCycle =
|
||||||
// It's been a while since the last activity, let's sync the whole space
|
(await this.kvStore.get(syncLastFullCycleKey)) || 0;
|
||||||
// The reason to do this check is that there may be multiple tabs open each with their sync cycle
|
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();
|
await this.syncSpace();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
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> {
|
async syncSpace(): Promise<number> {
|
||||||
@ -167,7 +184,7 @@ export class SyncService {
|
|||||||
console.log("Already syncing");
|
console.log("Already syncing");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
await this.registerSyncStart();
|
await this.registerSyncStart(true);
|
||||||
let operations = 0;
|
let operations = 0;
|
||||||
const snapshot = await this.getSnapshot();
|
const snapshot = await this.getSnapshot();
|
||||||
// console.log("Excluded from sync", excludedFromSync);
|
// console.log("Excluded from sync", excludedFromSync);
|
||||||
@ -197,7 +214,7 @@ export class SyncService {
|
|||||||
console.log("Already syncing, aborting individual file sync for", name);
|
console.log("Already syncing, aborting individual file sync for", name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.registerSyncStart();
|
await this.registerSyncStart(false);
|
||||||
console.log("Syncing file", name);
|
console.log("Syncing file", name);
|
||||||
const snapshot = await this.getSnapshot();
|
const snapshot = await this.getSnapshot();
|
||||||
try {
|
try {
|
||||||
|
Loading…
Reference in New Issue
Block a user