Add chokidar watcher settings.

This commit is contained in:
Patrick Fic
2025-03-12 16:06:22 -07:00
parent a01d4bfb44
commit 10368f8f9e
8 changed files with 195 additions and 10 deletions

View File

@@ -1,13 +1,15 @@
import { ipcMain } from "electron"; import { ipcMain } from "electron";
import ipcTypes from "../../util/ipcTypes.json";
import log from "electron-log/main"; import log from "electron-log/main";
import { ipcMainHandleAuthStateChanged } from "./ipcMainHandler.user"; import ipcTypes from "../../util/ipcTypes.json";
import { StartWatcher } from "../watcher/watcher";
import { import {
SettingsWatchedFilePathsGet,
SettingsWatchedFilePathsAdd, SettingsWatchedFilePathsAdd,
SettingsWatchedFilePathsGet,
SettingsWatchedFilePathsRemove,
} from "./ipcMainHandler.settings"; } from "./ipcMainHandler.settings";
// Log all IPC messages and their payloads import { ipcMainHandleAuthStateChanged } from "./ipcMainHandler.user";
// Log all IPC messages and their payloads
const logIpcMessages = () => { const logIpcMessages = () => {
// Get all message types from ipcTypes.toMain // Get all message types from ipcTypes.toMain
Object.keys(ipcTypes.toMain).forEach((key) => { Object.keys(ipcTypes.toMain).forEach((key) => {
@@ -37,8 +39,10 @@ ipcMain.on(ipcTypes.toMain.test, (payload: any) =>
console.log("** Verify that ipcMain is loaded and working.", payload) console.log("** Verify that ipcMain is loaded and working.", payload)
); );
//Auth handler
ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged); ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged);
//Settings Handlers
ipcMain.handle( ipcMain.handle(
ipcTypes.toMain.settings.filepaths.get, ipcTypes.toMain.settings.filepaths.get,
SettingsWatchedFilePathsGet SettingsWatchedFilePathsGet
@@ -47,4 +51,14 @@ ipcMain.handle(
ipcTypes.toMain.settings.filepaths.add, ipcTypes.toMain.settings.filepaths.add,
SettingsWatchedFilePathsAdd SettingsWatchedFilePathsAdd
); );
ipcMain.handle(
ipcTypes.toMain.settings.filepaths.remove,
SettingsWatchedFilePathsRemove
);
//Watcher Handlers
ipcMain.on(ipcTypes.toMain.watcher.start, () => {
StartWatcher();
});
logIpcMessages(); logIpcMessages();

View File

@@ -16,16 +16,31 @@ const SettingsWatchedFilePathsAdd = async (event: IpcMainInvokeEvent) => {
if (!result.canceled) { if (!result.canceled) {
Store.set( Store.set(
"settings.filepaths", "settings.filepaths",
_.union(result.filePaths, Store.get("filepaths")) _.union(result.filePaths, Store.get("settings.filepaths"))
); );
} }
return Store.get("settings.filepaths"); return Store.get("settings.filepaths");
}; };
const SettingsWatchedFilePathsRemove = async (
event: IpcMainInvokeEvent,
path: string
) => {
Store.set(
"settings.filepaths",
_.without(Store.get("settings.filepaths"), path)
);
return Store.get("settings.filepaths");
};
const SettingsWatchedFilePathsGet = async (event: IpcMainInvokeEvent) => { const SettingsWatchedFilePathsGet = async (event: IpcMainInvokeEvent) => {
const filepaths = Store.get("settings.filepaths"); const filepaths = Store.get("settings.filepaths");
return filepaths; return filepaths;
}; };
export { SettingsWatchedFilePathsAdd, SettingsWatchedFilePathsGet }; export {
SettingsWatchedFilePathsAdd,
SettingsWatchedFilePathsGet,
SettingsWatchedFilePathsRemove,
};

110
src/main/watcher/watcher.ts Normal file
View File

@@ -0,0 +1,110 @@
import chokidar, { FSWatcher } from "chokidar";
import { Notification } from "electron";
import log from "electron-log/main";
import path from "path";
import errorTypeCheck from "../../util/errorTypeCheck";
import store from "../store/store";
var watcher: FSWatcher;
async function StartWatcher(): Promise<boolean> {
const filePaths = store.get("settings.filepaths") || [];
log.info("Use polling? ", store.get("settings.polling").enabled);
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);
}
}
watcher = chokidar.watch(filePaths, {
ignored: (filepath, stats) => {
const p = path.parse(filepath);
return !stats?.isFile() && p.ext !== "" && p.ext.toUpperCase() !== ".ENV";
},
usePolling: store.get("settings.polling").enabled || false,
interval: store.get("settings.polling").pollingInterval || 1000,
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));
})
.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 onWatcherReady() {
log.info("Watcher ready!");
// const b = BrowserWindow.getAllWindows()[0];
// b.webContents.send(ipcTypes.default.fileWatcher.toRenderer.startSuccess);
new Notification({
title: "Watcher Started",
body: "Newly exported estimates will be automatically uploaded.",
}).show();
log.info("Confirmed watched paths:", watcher.getWatched());
}
async function StopWatcher(): Promise<boolean> {
if (watcher) {
await watcher.close();
log.info("Watcher stopped.");
new Notification({
title: "RPS Watcher Stopped",
body: "Estimates will not be automatically uploaded.",
}).show();
return true;
}
return false;
}
async function HandleNewFile(path) {
//await ImportJob(path);
log.log("Received a new file", path);
}
export { StartWatcher, StopWatcher, watcher };

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json"; import ipcTypes from "../../../../util/ipcTypes.json";
import { Button } from "antd"; import { Button, Space } from "antd";
import { DeleteFilled } from "@ant-design/icons";
const SettingsWatchedPaths: React.FC = () => { const SettingsWatchedPaths: React.FC = () => {
const [watchedPaths, setWatchedPaths] = useState<string[]>([]); const [watchedPaths, setWatchedPaths] = useState<string[]>([]);
@@ -23,10 +24,30 @@ const SettingsWatchedPaths: React.FC = () => {
}); });
}; };
const handleRemovePath = (path: string) => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.filepaths.remove, path)
.then((paths: string[]) => {
setWatchedPaths(paths);
});
};
return ( return (
<div> <div>
<div>Currently Watched paths</div> <div>Currently Watched paths</div>
<ul>{watchedPaths?.map((path, index) => <li key={index}>{path}</li>)}</ul> <ul>
{watchedPaths?.map((path, index) => (
<li key={index}>
<Space>
{path}
<Button
icon={<DeleteFilled />}
onClick={() => handleRemovePath(path)}
/>
</Space>
</li>
))}
</ul>
<Button onClick={handleAddPath}>{t("settings.actions.addpath")}</Button> <Button onClick={handleAddPath}>{t("settings.actions.addpath")}</Button>
</div> </div>
); );

View File

@@ -0,0 +1,16 @@
import { Button } from "antd";
import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json";
const SettingsWatcher: React.FC = () => {
const { t } = useTranslation();
const handleStart = () => {
window.electron.ipcRenderer.send(ipcTypes.toMain.watcher.start);
};
return (
<Button onClick={handleStart}>{t("settings.actions.startwatcher")}</Button>
);
};
export default SettingsWatcher;

View File

@@ -1,9 +1,11 @@
import SettingsWatchedPaths from "./Settings.WatchedPaths"; import SettingsWatchedPaths from "./Settings.WatchedPaths";
import SettingsWatcher from "./Settings.Watcher";
const Settings: React.FC = () => { const Settings: React.FC = () => {
return ( return (
<div> <div>
<SettingsWatchedPaths /> <SettingsWatchedPaths />
<SettingsWatcher />
</div> </div>
); );
}; };

View File

@@ -2,7 +2,9 @@ function errorTypeCheck(passedError: any): ParsedError {
const errorMessage = const errorMessage =
passedError instanceof Error ? passedError.message : String(passedError); passedError instanceof Error ? passedError.message : String(passedError);
const errorStack = const errorStack =
passedError instanceof Error ? passedError.stack : "unknown"; passedError instanceof Error
? (passedError.stack ?? "")
: String(passedError);
return { return {
message: errorMessage, message: errorMessage,

View File

@@ -2,10 +2,15 @@
"toMain": { "toMain": {
"test": "toMain_test", "test": "toMain_test",
"authStateChanged": "toMain_authStateChanged", "authStateChanged": "toMain_authStateChanged",
"watcher": {
"start": "toMain_watcher_start",
"stop": "toMain_watcher_stop"
},
"settings": { "settings": {
"filepaths": { "filepaths": {
"get": "toMain_settings_filepaths_get", "get": "toMain_settings_filepaths_get",
"add": "toMain_settings_filepaths_add" "add": "toMain_settings_filepaths_add",
"remove": "toMain_settings_filepaths_remove"
} }
} }
}, },