Refactored file watcher code
Some checks failed
Docker Image CI / build (push) Has been cancelled

This commit is contained in:
Rishi Ghan
2026-04-15 10:59:48 -04:00
parent 83f905ebb6
commit c4cf233053

View File

@@ -183,17 +183,31 @@ export default class ApiService extends Service {
return; return;
} }
// Chokidar uses the best native watcher per platform:
// - macOS: FSEvents
// - Linux: inotify
// - Windows: ReadDirectoryChangesW
// Only use polling when explicitly requested (Docker, network mounts, etc.)
const forcePolling = process.env.USE_POLLING === "true";
const platform = process.platform;
const watchMode = forcePolling ? "polling" : `native (${platform})`;
this.fileWatcher = chokidar.watch(watchDir, { this.fileWatcher = chokidar.watch(watchDir, {
persistent: true, persistent: true,
ignoreInitial: true, ignoreInitial: true,
followSymlinks: true, followSymlinks: true,
depth: 10, depth: 10,
usePolling: true, // Use native file watchers by default (FSEvents/inotify/ReadDirectoryChangesW)
interval: 5000, // Fall back to polling only when explicitly requested via USE_POLLING=true
usePolling: forcePolling,
interval: forcePolling ? 1000 : undefined,
binaryInterval: forcePolling ? 1000 : undefined,
atomic: true, atomic: true,
awaitWriteFinish: { stabilityThreshold: 2000, pollInterval: 100 }, awaitWriteFinish: { stabilityThreshold: 2000, pollInterval: 100 },
ignored: (p) => p.endsWith(".dctmp") || p.includes("/.git/"), ignored: (p) => p.endsWith(".dctmp") || p.includes("/.git/"),
}); });
this.logger.info(`[Watcher] Platform: ${platform}, Mode: ${watchMode}`);
/** /**
* Returns a debounced handler for a specific path, creating one if needed. * Returns a debounced handler for a specific path, creating one if needed.
@@ -254,26 +268,38 @@ export default class ApiService extends Service {
filePath: string, filePath: string,
stats?: fs.Stats stats?: fs.Stats
): Promise<void> { ): Promise<void> {
this.logger.info(`File event [${event}]: ${filePath}`); const ext = path.extname(filePath).toLowerCase();
const isComicFile = [".cbz", ".cbr", ".cb7"].includes(ext);
this.logger.info(`[Watcher] File event [${event}]: ${filePath} (ext: ${ext}, isComic: ${isComicFile})`);
// Handle file/directory removal — mark affected comics as missing and notify frontend // Handle file/directory removal — mark affected comics as missing and notify frontend
if (event === "unlink" || event === "unlinkDir") { if (event === "unlink" || event === "unlinkDir") {
try { // For unlink events, process if it's a comic file OR a directory (unlinkDir)
const result: any = await this.broker.call("library.markFileAsMissing", { filePath }); if (event === "unlinkDir" || isComicFile) {
if (result.marked > 0) { this.logger.info(`[Watcher] Processing deletion for: ${filePath}`);
await this.broker.call("socket.broadcast", { try {
namespace: "/", const result: any = await this.broker.call("library.markFileAsMissing", { filePath });
event: "LS_FILES_MISSING", this.logger.info(`[Watcher] markFileAsMissing result: marked=${result.marked}, path=${filePath}`);
args: [{ if (result.marked > 0) {
missingComics: result.missingComics, await this.broker.call("socket.broadcast", {
triggerPath: filePath, namespace: "/",
count: result.marked, event: "LS_FILES_MISSING",
}], args: [{
}); missingComics: result.missingComics,
this.logger.info(`[Watcher] Marked ${result.marked} comic(s) as missing for path: ${filePath}`); triggerPath: filePath,
count: result.marked,
}],
});
this.logger.info(`[Watcher] Marked ${result.marked} comic(s) as missing for path: ${filePath}`);
} else {
this.logger.info(`[Watcher] No matching comics found in DB for deleted path: ${filePath}`);
}
} catch (err) {
this.logger.error(`[Watcher] Failed to mark comics missing for ${filePath}:`, err);
} }
} catch (err) { } else {
this.logger.error(`[Watcher] Failed to mark comics missing for ${filePath}:`, err); this.logger.info(`[Watcher] Ignoring non-comic file deletion: ${filePath}`);
} }
return; return;
} }