2023-01-13 14:41:29 +00:00
|
|
|
import { store } from "$sb/plugos-syscall/mod.ts";
|
2023-01-16 17:34:15 +00:00
|
|
|
import {
|
|
|
|
editor,
|
|
|
|
index,
|
|
|
|
space,
|
|
|
|
sync,
|
|
|
|
system,
|
|
|
|
} from "$sb/silverbullet-syscall/mod.ts";
|
2023-01-13 14:41:29 +00:00
|
|
|
import type { SyncEndpoint } from "$sb/silverbullet-syscall/sync.ts";
|
|
|
|
|
|
|
|
export async function configureCommand() {
|
|
|
|
const url = await editor.prompt(
|
|
|
|
"Enter the URL of the remote space to sync with",
|
|
|
|
"https://",
|
|
|
|
);
|
|
|
|
if (!url) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const user = await editor.prompt("Username (if any):");
|
|
|
|
let password = undefined;
|
|
|
|
if (user) {
|
|
|
|
password = await editor.prompt("Password:");
|
|
|
|
}
|
|
|
|
|
|
|
|
const syncConfig: SyncEndpoint = {
|
|
|
|
url,
|
|
|
|
user,
|
|
|
|
password,
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
await system.invokeFunction("server", "check", syncConfig);
|
|
|
|
} catch (e: any) {
|
|
|
|
await editor.flashNotification(
|
|
|
|
`Sync configuration failed: ${e.message}`,
|
|
|
|
"error",
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await store.batchSet([
|
|
|
|
{ key: "sync.config", value: syncConfig },
|
|
|
|
// Empty initial snapshot
|
|
|
|
{ key: "sync.snapshot", value: {} },
|
|
|
|
]);
|
|
|
|
|
|
|
|
await editor.flashNotification("Sync configuration saved.");
|
|
|
|
|
|
|
|
return syncConfig;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function syncCommand() {
|
|
|
|
let config: SyncEndpoint | undefined = await store.get("sync.config");
|
|
|
|
if (!config) {
|
|
|
|
config = await configureCommand();
|
|
|
|
if (!config) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await editor.flashNotification("Starting sync...");
|
|
|
|
try {
|
2023-01-14 17:51:00 +00:00
|
|
|
await system.invokeFunction("server", "check", config);
|
|
|
|
|
2023-01-13 14:41:29 +00:00
|
|
|
const operations = await system.invokeFunction("server", "performSync");
|
|
|
|
await editor.flashNotification(
|
|
|
|
`Sync complete. Performed ${operations} operations.`,
|
|
|
|
);
|
|
|
|
} catch (e: any) {
|
|
|
|
await editor.flashNotification(
|
|
|
|
`Sync failed: ${e.message}`,
|
|
|
|
"error",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-16 17:34:15 +00:00
|
|
|
export async function localWipeAndSyncCommand() {
|
|
|
|
let config: SyncEndpoint | undefined = await store.get("sync.config");
|
|
|
|
if (!config) {
|
|
|
|
config = await configureCommand();
|
|
|
|
if (!config) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!(await editor.confirm(
|
|
|
|
"Are you sure you want to wipe your local space and sync with the remote?",
|
|
|
|
))
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!(await editor.confirm(
|
|
|
|
"To be clear: this means all local content will be deleted with no way to recover it. Are you sure?",
|
|
|
|
))
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("Wiping local pages");
|
|
|
|
await editor.flashNotification("Now wiping all pages");
|
|
|
|
for (const page of await space.listPages()) {
|
|
|
|
console.log("Deleting page", page.name);
|
|
|
|
await space.deletePage(page.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("Wiping local attachments");
|
|
|
|
await editor.flashNotification("Now wiping all attachments");
|
|
|
|
for (const attachment of await space.listAttachments()) {
|
|
|
|
console.log("Deleting attachment", attachment.name);
|
|
|
|
await space.deleteAttachment(attachment.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("Wiping local sync state");
|
|
|
|
await store.set("sync.snapshot", {});
|
|
|
|
|
|
|
|
// Starting actual sync
|
|
|
|
await syncCommand();
|
|
|
|
}
|
|
|
|
|
2023-01-13 14:41:29 +00:00
|
|
|
// Run on server
|
|
|
|
export function check(config: SyncEndpoint) {
|
|
|
|
return sync.check(config);
|
|
|
|
}
|
|
|
|
|
2023-01-20 15:08:01 +00:00
|
|
|
// const syncTimeout = 1000 * 60 * 30; // 30 minutes
|
|
|
|
const syncTimeout = 1000 * 20; // 20s
|
|
|
|
|
2023-01-13 14:41:29 +00:00
|
|
|
// Run on server
|
|
|
|
export async function performSync() {
|
|
|
|
const config: SyncEndpoint = await store.get("sync.config");
|
2023-01-20 15:08:01 +00:00
|
|
|
if (!config) {
|
|
|
|
// Sync not configured
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
await sync.check(config);
|
|
|
|
} catch (e: any) {
|
|
|
|
console.error("Sync check failed", e.message);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Check if sync not already in progress
|
|
|
|
const ongoingSync: number | undefined = await store.get("sync.startTime");
|
|
|
|
if (ongoingSync) {
|
|
|
|
if (Date.now() - ongoingSync > syncTimeout) {
|
|
|
|
console.log("Sync timed out, continuing");
|
|
|
|
} else {
|
|
|
|
console.log("Sync already in progress");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Keep track of sync start time
|
|
|
|
await store.set("sync.startTime", Date.now());
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Perform actual sync
|
|
|
|
const snapshot = await store.get("sync.snapshot");
|
|
|
|
const { snapshot: newSnapshot, operations, error } = await sync.syncAll(
|
|
|
|
config,
|
|
|
|
snapshot,
|
|
|
|
);
|
|
|
|
// Store snapshot
|
|
|
|
await store.set("sync.snapshot", newSnapshot);
|
|
|
|
// Clear sync start time
|
|
|
|
await store.del("sync.startTime");
|
|
|
|
if (error) {
|
|
|
|
console.error("Sync error", error);
|
|
|
|
throw new Error(error);
|
|
|
|
}
|
|
|
|
return operations;
|
|
|
|
} catch (e: any) {
|
|
|
|
// Clear sync start time
|
|
|
|
await store.del("sync.startTime");
|
|
|
|
console.error("Sync error", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function syncPage(page: string) {
|
|
|
|
const config: SyncEndpoint = await store.get("sync.config");
|
|
|
|
if (!config) {
|
|
|
|
// Sync not configured
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Check if sync not already in progress
|
|
|
|
const ongoingSync: number | undefined = await store.get("sync.startTime");
|
|
|
|
if (ongoingSync) {
|
|
|
|
if (Date.now() - ongoingSync > syncTimeout) {
|
|
|
|
console.log("Sync timed out, continuing");
|
|
|
|
} else {
|
|
|
|
console.log("Sync already in progress");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Keep track of sync start time
|
|
|
|
await store.set("sync.startTime", Date.now());
|
2023-01-13 14:41:29 +00:00
|
|
|
const snapshot = await store.get("sync.snapshot");
|
2023-01-20 15:08:01 +00:00
|
|
|
console.log("Syncing page", page);
|
|
|
|
try {
|
|
|
|
const { snapshot: newSnapshot, error } = await sync.syncFile(
|
|
|
|
config,
|
|
|
|
snapshot,
|
|
|
|
`${page}.md`,
|
|
|
|
);
|
|
|
|
// Store snapshot
|
|
|
|
await store.set("sync.snapshot", newSnapshot);
|
|
|
|
// Clear sync start time
|
|
|
|
await store.del("sync.startTime");
|
|
|
|
if (error) {
|
|
|
|
console.error("Sync error", error);
|
|
|
|
throw new Error(error);
|
|
|
|
}
|
|
|
|
} catch (e: any) {
|
|
|
|
// Clear sync start time
|
|
|
|
await store.del("sync.startTime");
|
|
|
|
console.error("Sync error", e);
|
2023-01-13 14:41:29 +00:00
|
|
|
}
|
|
|
|
}
|