mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
basic filewatcher using websocket
This commit is contained in:
120
server/watcher.js
Normal file
120
server/watcher.js
Normal 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 };
|
||||
38
server/ws.js
38
server/ws.js
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user