silverbullet/plugs/federation/library.ts

116 lines
3.6 KiB
TypeScript

import { editor, space, system } from "@silverbulletmd/silverbullet/syscalls";
import { listFilesCached, readFile } from "./federation.ts";
import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
import { federatedPathToLocalPath, wildcardPathToRegex } from "./util.ts";
import type { LibraryDef } from "@silverbulletmd/silverbullet/type/config";
export async function updateLibrariesCommand() {
if (
await editor.confirm(
"Are you sure you want to update all libraries?",
)
) {
await editor.flashNotification("Updating all libraries...");
const updateStats: UpdateStats = await system.invokeFunction(
"federation.updateLibraries",
);
await editor.reloadConfigAndCommands();
await editor.flashNotification(
`Updated ${updateStats.libraries} libraries containing a total of ${updateStats.items} items.`,
);
}
}
type UpdateStats = {
libraries: number;
items: number;
};
// Run on the server for efficiency and CORS avoidance
export async function updateLibraries(): Promise<UpdateStats> {
const updateStats: UpdateStats = { libraries: 0, items: 0 };
const libraries =
((await system.reloadConfig())?.libraries || []) as LibraryDef[];
console.log("Libraries", await system.getSpaceConfig());
for (const lib of libraries) {
// Handle deprecated 'source' field
if (lib.source) {
lib.import = lib.source;
}
if (!lib.import) {
console.warn("Library source not set, skipping", lib);
continue;
}
const pageUri = parsePageRef(lib.import).page;
if (!pageUri.startsWith("!")) {
console.warn(
"Library source must be a federated page, skipping",
pageUri,
);
continue;
}
console.log("Now updating library", pageUri);
const localLibraryPath = federatedPathToLocalPath(pageUri);
// Sanity check the `source` pattern to avoid disaster (like wiping out the whole space)
if (!/^Library\/.+/.test(localLibraryPath)) {
console.warn(
"Skipping library",
pageUri,
"as it does not start with Library/",
);
continue;
}
// Fetch new list of pages
let newPages = await listFilesCached(pageUri, true);
console.log("All pages", newPages.length);
if (lib.exclude) {
for (const exclude of lib.exclude) {
const excludeUri = parsePageRef(exclude).page;
const excludeRegex = wildcardPathToRegex(excludeUri + ".md");
newPages = newPages.filter((p) => {
if (excludeRegex.test(p.name)) {
console.info("Excluding", p.name);
return false;
}
return true;
});
}
}
// Compile existing page list in local space (to be removed)
const localPages = await space.listPages();
const localSourceRegex = wildcardPathToRegex(localLibraryPath);
// Remove pages that match the source pattern, but in their "local" form
const pagesToRemove = localPages.filter((p) =>
localSourceRegex.test(p.name)
);
console.log("Pages to remove", pagesToRemove.length);
for (const page of pagesToRemove) {
console.info("Deleting", page.name);
await space.deletePage(page.name);
}
// Import the new pages
for (const page of newPages) {
console.info("Importing", page.name);
// Fetch the file
const buf = (await readFile(page.name)).data;
// Write to local space
await space.writeFile(federatedPathToLocalPath(page.name), buf);
updateStats.items++;
}
updateStats.libraries++;
console.log("Done with library", pageUri);
}
return updateStats;
}