Files
bodyshop-desktop/src/main/watcher/watcher.ts
2025-05-22 10:28:03 -07:00

170 lines
4.8 KiB
TypeScript

import chokidar, { FSWatcher } from "chokidar";
import { BrowserWindow, Notification } from "electron";
import log from "electron-log/main";
import fs from "fs";
import path from "path";
import errorTypeCheck from "../../util/errorTypeCheck";
import ipcTypes from "../../util/ipcTypes.json";
import ImportJob from "../decoder/decoder";
import store from "../store/store";
let watcher: FSWatcher | null;
async function StartWatcher(): Promise<boolean> {
const filePaths: string[] = store.get("settings.filepaths") || [];
if (filePaths.length === 0) {
new Notification({
//TODO: Add Translations
title: "Watcher cannot start",
body: "Please set the appropriate file paths and try again.",
}).show();
log.warn("Cannot start watcher. No file paths set.");
return false;
}
if (watcher) {
try {
log.info("Trying to close watcher - it already existed.");
await watcher.close();
log.info("Watcher closed successfully!");
} catch (error) {
log.error("Error trying to close Watcher.", error);
}
}
const pollingSettings =
(store.get("settings.polling") as {
enabled?: boolean;
interval?: number;
}) || {};
watcher = chokidar.watch(filePaths, {
ignored: (filepath, stats) => {
const p = path.parse(filepath);
return (
(!stats?.isFile() && p.ext !== "" && p.ext.toUpperCase() !== ".ENV") ||
p.name?.toUpperCase() === ".DS_STORE"
); //Only watch for .ENV files.
},
usePolling: pollingSettings.enabled || false,
interval: pollingSettings.interval || 30000,
persistent: true,
ignoreInitial: true,
awaitWriteFinish: {
pollInterval: 500,
stabilityThreshold: 2000,
},
});
watcher
.on("add", async function (path) {
console.log("File", path, "has been added");
HandleNewFile(path);
})
// .on("addDir", function (path) {
// console.log("Directory", path, "has been added");
// })
.on("change", async function (path) {
console.log("File", path, "has been changed");
HandleNewFile(path);
})
// .on("unlink", function (path) {
// console.log("File", path, "has been removed");
// })
// .on("unlinkDir", function (path) {
// console.log("Directory", path, "has been removed");
// })
.on("error", function (error) {
log.error("Error in Watcher", errorTypeCheck(error));
// mainWindow.webContents.send(
// ipcTypes.toRenderer.watcher.error,
// errorTypeCheck(error)
// );
})
.on("ready", onWatcherReady);
// .on("raw", function (event, path, details) {
// // This event should be triggered everytime something happens.
// // console.log("Raw event info:", event, path, details);
// });
return true;
}
function removeWatcherPath(path: string): void {
if (watcher) {
watcher.unwatch(path);
log.debug(`Stopped watching path: ${path}`);
}
}
function addWatcherPath(path: string | string[]): void {
if (watcher) {
watcher.add(path);
log.debug(`Started watching path: ${path}`);
}
}
function onWatcherReady(): void {
if (watcher) {
const mainWindow = BrowserWindow.getAllWindows()[0]; //TODO: Filter to only main window once a proper key has been set.
new Notification({
title: "Watcher Started",
body: "Newly exported estimates will be automatically uploaded.",
}).show();
log.info("Confirmed watched paths:", watcher.getWatched());
mainWindow.webContents.send(ipcTypes.toRenderer.watcher.started);
}
}
async function StopWatcher(): Promise<boolean> {
const mainWindow = BrowserWindow.getAllWindows()[0]; //TODO: Filter to only main window once a proper key has been set.
if (watcher) {
await watcher.close();
log.info("Watcher stopped.");
mainWindow.webContents.send(ipcTypes.toRenderer.watcher.stopped);
new Notification({
title: "Watcher Stopped",
body: "Estimates will not be automatically uploaded.",
}).show();
return true;
}
return false;
}
async function HandleNewFile(path): Promise<void> {
log.log("Received a new file", path);
await ImportJob(path);
}
function GetAllEnvFiles(): string[] {
const directories = store.get("settings.filepaths") as string[];
const files: string[] = [];
directories.forEach((directory) => {
try {
const envFiles = fs
.readdirSync(directory)
.filter((file: string) => file.toLowerCase().endsWith(".env"));
envFiles.forEach((file) => {
const fullPath = path.join(directory, file);
files.push(fullPath);
});
} catch (error) {
log.error(`Failed to read directory ${directory}:`, error);
throw error;
}
});
return files;
}
export {
addWatcherPath,
GetAllEnvFiles,
removeWatcherPath,
StartWatcher,
StopWatcher,
watcher,
};