import {app, BrowserWindow, ipcMain, IpcMainInvokeEvent} from 'electron'; import path from 'path'; import { promises as fs } from 'fs'; import dotenv from 'dotenv'; import {WorkerManager} from "../helpers/worker_manager"; import {DirectoryWatcher} from "../helpers/directory_watcher"; import {QueueManager} from "../helpers/queue_manager"; import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task"; import {WindowManager} from "../helpers/window_manager"; import {JsonManager} from "../helpers/json_manager"; import {MemoryManager} from "../helpers/memory_manager"; import {TcpCommunicator} from "../helpers/tcp_communicator"; import {operationCodes} from "../network/operation_codes"; import os from "os"; // Load environment variables dotenv.config({ path: path.join(__dirname, '..', '..', '.env') }); const UDP_PORT = process.env.UDP_PORT ? parseInt(process.env.UDP_PORT) : 41233; const TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234; const HOST = getLocalIp(); let mainWindow: BrowserWindow | null = null; let windowManager: WindowManager | null = null; let tcpCommunicator: TcpCommunicator | null = null; let userConfig: JsonManager | null = null; let applicationInfo: JsonManager | null = null; let memoryManager: MemoryManager | null = null; let workerManager: WorkerManager | null = null; let backupDirectoryManager: DirectoryWatcher | null = null; let departmentShareManager: DirectoryWatcher | null = null; let sendFileQueue: QueueManager | null = null; let announcementWatcher: NodeJS.Timeout | null = null; let resetApplicationWatcher: NodeJS.Timeout | null = null; const pathToPagesDir = path.join(__dirname, '..', '..', 'render', 'html'); const pathToWorkerDir = path.join(__dirname, '..', 'workers'); const pathToJsons = path.join(__dirname, '..', 'json_files'); const pathToClientsBackups = path.join(__dirname, '..', 'backups'); async function cleanupAndExit() { // Stop all workers if (workerManager) { console.log('Terminating all workers...'); workerManager.closeAllWorkers(); } // Reset memory if (memoryManager) { await memoryManager.resetFile(); } // Close watchers if (backupDirectoryManager) { console.log('Stopping backup directory watcher...'); backupDirectoryManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher } if (departmentShareManager) { console.log('Stopping department directory watcher...'); departmentShareManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher } if(workerManager){ console.log('Terminating all workers...'); workerManager.closeAllWorkers() } console.log('Cleanup complete, exiting application.'); app.quit(); // This will properly close the application } async function ensureDirectoryExists(dirPath: string): Promise { try { await fs.access(dirPath); } catch (err) { // If the directory doesn't exist, create it await fs.mkdir(dirPath, { recursive: true }); console.log(`Directory created: ${dirPath}`); } } function getLocalIp() { const interfaces = os.networkInterfaces(); for (let interfaceName in interfaces) { const addresses = interfaces[interfaceName]; if(!addresses) continue; for (let address of addresses) { // Filter for IPv4 and ignore internal (127.0.0.1) addresses if (address.family === 'IPv4' && !address.internal) { return address.address; } } } return ''; // Fallback if no IP is found } function startAnnouncementWatcher() { const checkInterval = 5000; // Check every 5 seconds announcementWatcher = setInterval(async () => { if(!windowManager || !applicationInfo) return; const announcement = await applicationInfo.readValue('announcement'); if (announcement) await windowManager.displayAnnouncement(); }, checkInterval); } function startResetApplicationWatcher() { const checkInterval = 5000; // Check every 5 seconds resetApplicationWatcher = setInterval(async () => { if(!applicationInfo || !windowManager) return; const resetApplicationPreferences = await applicationInfo.readValue('reset_application_preferences'); if (resetApplicationPreferences) await windowManager.changeContent('reset-database'); }, checkInterval); } app.whenReady().then(async () => { const title = 'Application'; const mainScreen = require('electron').screen.getPrimaryDisplay(); const { width, height } = mainScreen.size; mainWindow = new BrowserWindow({ title, width: width / 1.5, height: height / 1.5, resizable: false, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true, }, }); mainWindow.removeMenu(); await ensureDirectoryExists(pathToJsons); await ensureDirectoryExists(pathToClientsBackups); windowManager = new WindowManager(mainWindow, pathToPagesDir); userConfig = new JsonManager(path.join(pathToJsons, 'userConfig.json')); applicationInfo = new JsonManager(path.join(pathToJsons, 'application.json')); memoryManager = new MemoryManager(path.join(pathToJsons, 'memory.json')); sendFileQueue = new QueueManager(path.join(pathToJsons, 'sendFileTasks.json'), compareFnFileItemTask); workerManager = new WorkerManager(pathToWorkerDir, windowManager); await userConfig.writeValue('app_type', 'client'); await applicationInfo.writeValue('users_ip', []); await applicationInfo.writeValue('serverIp', ''); await applicationInfo.writeValue('announcement', ''); await applicationInfo.writeValue('reset_application_preferences', false); await memoryManager.resetFile(); startAnnouncementWatcher(); startResetApplicationWatcher(); workerManager.startNetworkScannerWorker(UDP_PORT, TCP_PORT, 'login', 'uc_not_found', 'reset_database', path.join(pathToJsons, 'userConfig.json'), path.join(pathToJsons, 'application.json')); workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json')); workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT); workerManager.startResourceCoordinatorWorker( path.join(pathToJsons, 'userConfig.json'), path.join(pathToJsons, 'application.json'), path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'sendFileTasks.json'), TCP_PORT ); registerIPCHandlers(); await windowManager.changeContent('welcome'); }); app.on('window-all-closed', async () => { console.log('All windows closed, starting cleanup...'); await cleanupAndExit(); // Call cleanup when all windows are closed }); // Catch CTRL+C (SIGINT) and clean up resources process.on('SIGINT', async () => { console.log('CTRL+C pressed, starting cleanup...'); await cleanupAndExit(); // Call cleanup on SIGINT }); app.on('before-quit', async () => { console.log('Application is quitting, starting cleanup...'); await cleanupAndExit(); // Call cleanup before app quit }); // Register IPC handlers function registerIPCHandlers() { // Window Manager IPC Handlers ipcMain.handle('show-alert', async (event: IpcMainInvokeEvent, message: string) => { if (!windowManager) throw new Error('WindowManager is not initialized.'); await windowManager.showAlert(message); }); ipcMain.handle('change-content', async (_event: IpcMainInvokeEvent, destination: string) => { if (!windowManager) throw new Error('WindowManager is not initialized.'); await windowManager.changeContent(destination); }); ipcMain.handle('select-directory', async (_event: IpcMainInvokeEvent) => { if (!windowManager) throw new Error('WindowManager is not initialized.'); return await windowManager.selectDirectory(); }); ipcMain.handle('select-file', async (_event: IpcMainInvokeEvent) => { if (!windowManager) throw new Error('WindowManager is not initialized.'); return await windowManager.selectFile(); }); ipcMain.handle('show-file-in-explorer', async (_event: IpcMainInvokeEvent, path: string) => { if (!windowManager) throw new Error('WindowManager is not initialized.'); return await windowManager.showFileInExplorer(path); }); ipcMain.handle('close-announcement-window', async (_event: IpcMainInvokeEvent) => { if (!windowManager) throw new Error('WindowManager is not initialized.'); return await windowManager.closeAnnouncementWindow(); }); // TcpMethods IPC Handlers ipcMain.handle('open-socket', async (_event: IpcMainInvokeEvent) => { if (!applicationInfo) throw new Error('TcpMethods is not initialized.'); const serverIp = await applicationInfo.readValue('serverIp'); if (!serverIp) return; tcpCommunicator = new TcpCommunicator(serverIp, TCP_PORT); return await tcpCommunicator.connect() }); ipcMain.handle('send-message', async (_event: IpcMainInvokeEvent, operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer) => { if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.'); return await tcpCommunicator.sendMessage(operationCode, metaInfo, fileContent); }); ipcMain.handle('has-response-arrived', async (_event: IpcMainInvokeEvent) => { if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.'); return tcpCommunicator.hasResponseArrived(); }); ipcMain.handle('close-socket', async (_event: IpcMainInvokeEvent) => { if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.'); return await tcpCommunicator.disconnect(); }); ipcMain.handle('get-last-result', async (_event: IpcMainInvokeEvent) => { if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.'); return tcpCommunicator.getLastResult(); }); ipcMain.handle('get-operation-codes', () => { return operationCodes; }); // UserConfig IPC Handlers ipcMain.handle('read-user-json-files', async (_event: IpcMainInvokeEvent, key: string) => { if (!userConfig) throw new Error('UserConfig is not initialized.'); return await userConfig.readValue(key); }); ipcMain.handle('write-user-json-files', async (_event: IpcMainInvokeEvent, key: string, value: any) => { if (!userConfig) throw new Error('UserConfig is not initialized.'); return userConfig.writeValue(key, value); }); ipcMain.handle('reset-user-json-files', async () => { if (!userConfig) throw new Error('UserConfig is not initialized.'); return userConfig.resetFile(); }); ipcMain.handle('remove-user-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => { if (!userConfig) throw new Error('UserConfig is not initialized.'); return userConfig.removeValue(key); }); // ApplicationPreferences IPC Handlers ipcMain.handle('read-application-json-files', async (_event: IpcMainInvokeEvent, key: string) => { if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.'); return await applicationInfo.readValue(key); }); ipcMain.handle('write-application-json-files', async (_event: IpcMainInvokeEvent, key: string, value: any) => { if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.'); return applicationInfo.writeValue(key, value); }); ipcMain.handle('reset-application-json-files', async () => { if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.'); const serverIp = await applicationInfo.readValue('serverIp'); await applicationInfo.resetFile(); if(serverIp) { await applicationInfo.writeValue('serverIp', serverIp); } }); ipcMain.handle('remove-application-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => { if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.'); return applicationInfo.removeValue(key); }); // Memory IPC Handlers ipcMain.handle('memory-create-entry', async () => { if (!memoryManager) throw new Error('MemoryManager is not initialized.'); return memoryManager.storeMetaInformation({}); }); ipcMain.handle('memory-read-entry', async (_event: IpcMainInvokeEvent, id: string) => { if (!memoryManager) throw new Error('MemoryManager is not initialized.'); return memoryManager.retrieveMetaInformation(id); }); ipcMain.handle('memory-update-entry', async (_event: IpcMainInvokeEvent, id: string, data: any) => { if (!memoryManager) throw new Error('MemoryManager is not initialized.'); return memoryManager.updateMetaInformation(id, data); }); ipcMain.handle('memory-remove-entry', async (_event: IpcMainInvokeEvent, id: string) => { if (!memoryManager) throw new Error('MemoryManager is not initialized.'); return memoryManager.removeMetaInformation(id); }); ipcMain.handle('memory-reset', async () => { if (!memoryManager) throw new Error('MemoryManager is not initialized.'); return memoryManager.resetFile(); }); // Queue IPC Handlers ipcMain.handle('add-task-to-send-file-queue', async (event: IpcMainInvokeEvent, task: FileItemTask) => { if (!sendFileQueue) throw new Error('SendFileQueue is not initialized.'); sendFileQueue.enqueue(task); }); // Workers IPC Handlers ipcMain.handle('start-backup-retrieval', async (_event: IpcMainInvokeEvent, destinationPath: string) => { if (!workerManager) throw new Error('WorkerManager is not initialized.'); return workerManager.startBackupRetrievalWorker( path.join(pathToJsons, 'userConfig.json'), path.join(pathToJsons, 'application.json'), TCP_PORT, destinationPath ); }); }