basic filewatcher using websocket

This commit is contained in:
Nystik
2026-03-22 14:56:05 +01:00
parent be0792dab7
commit c5f5bec324
8 changed files with 342 additions and 11 deletions

120
server/watcher.js Normal file
View File

@@ -0,0 +1,120 @@
const chokidar = require("chokidar");
const path = require("path");
const fs = require("fs");
// Per-vault chokidar watchers
// Map<vaultId, { watcher, listeners: Set<fn>, vaultPath }>
const vaultWatchers = new Map();
function startWatching(vaultId, vaultPath) {
if (vaultWatchers.has(vaultId)) {
return vaultWatchers.get(vaultId);
}
const watcher = chokidar.watch(vaultPath, {
persistent: true,
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 300,
pollInterval: 100,
},
ignored: [
/(^|[\/\\])\.git([\/\\]|$)/, // .git directories
],
});
const entry = { watcher, listeners: new Set(), vaultPath };
function emit(type, fullPath, stat) {
const rel = path.relative(vaultPath, fullPath).replace(/\\/g, "/");
const event = { type, path: rel };
if (stat) {
event.stat = {
size: stat.size,
mtime: stat.mtimeMs,
ctime: stat.ctimeMs,
};
}
for (const fn of entry.listeners) {
try {
fn(event);
} catch (e) {
console.error("[watcher] Listener error:", e);
}
}
}
watcher
.on("add", (fullPath) => {
try {
const stat = fs.statSync(fullPath);
emit("created", fullPath, stat);
} catch {
emit("created", fullPath, null);
}
})
.on("change", (fullPath) => {
try {
const stat = fs.statSync(fullPath);
emit("modified", fullPath, stat);
} catch {
emit("modified", fullPath, null);
}
})
.on("unlink", (fullPath) => {
emit("deleted", fullPath, null);
})
.on("addDir", (fullPath) => {
// Skip vault root itself
if (path.resolve(fullPath) === path.resolve(vaultPath)) return;
emit("folder-created", fullPath, null);
})
.on("unlinkDir", (fullPath) => {
emit("deleted", fullPath, null);
})
.on("error", (err) => {
console.error(`[watcher] Error on vault "${vaultId}":`, err.message);
});
vaultWatchers.set(vaultId, entry);
console.log(`[watcher] Started watching vault: ${vaultId}`);
return entry;
}
function stopWatching(vaultId) {
const entry = vaultWatchers.get(vaultId);
if (entry) {
entry.watcher.close();
entry.listeners.clear();
vaultWatchers.delete(vaultId);
console.log(`[watcher] Stopped watching vault: ${vaultId}`);
}
}
function addListener(vaultId, fn) {
const entry = vaultWatchers.get(vaultId);
if (entry) {
entry.listeners.add(fn);
}
}
function removeListener(vaultId, fn) {
const entry = vaultWatchers.get(vaultId);
if (entry) {
entry.listeners.delete(fn);
// Stop watching if no listeners remain
if (entry.listeners.size === 0) {
stopWatching(vaultId);
}
}
}
module.exports = { startWatching, stopWatching, addListener, removeListener };

View File

@@ -1,25 +1,41 @@
const { WebSocketServer } = require("ws");
const url = require("url");
const config = require("./config");
const watcher = require("./watcher");
//currently unused
function setupWebSocket(server) {
const wss = new WebSocketServer({ server, path: "/ws" });
wss.on("connection", (ws) => {
console.log("[ws] Client connected");
wss.on("connection", (ws, req) => {
const params = new url.URL(req.url, "http://localhost").searchParams;
const vaultId = params.get("vault");
ws.on("message", (data) => {
// TODO: handle watch/unwatch subscriptions from client
const msg = JSON.parse(data);
console.log("[ws] Received:", msg);
});
if (!vaultId || !config.getVaultPath(vaultId)) {
ws.close(4001, "Invalid or missing vault ID");
return;
}
const vaultPath = config.getVaultPath(vaultId);
console.log(`[ws] Client connected to vault: ${vaultId}`);
// Start watching this vault (no-op if already watching)
watcher.startWatching(vaultId, vaultPath);
// Per-client listener that forwards events over WebSocket
const listener = (event) => {
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify(event));
}
};
watcher.addListener(vaultId, listener);
ws.on("close", () => {
console.log("[ws] Client disconnected");
console.log(`[ws] Client disconnected from vault: ${vaultId}`);
watcher.removeListener(vaultId, listener);
});
});
// TODO: maybe integrate chokidar file watching and broadcast changes
return wss;
}