From dcf505c89be779f58be6f403e80e0c3ade9b1c3f Mon Sep 17 00:00:00 2001 From: andrei-mihnea-cerbu Date: Tue, 4 Feb 2025 19:08:34 +0200 Subject: [PATCH] added email verification --- UC/src/network/operation_codes.ts | 1 + .../operations_custom/auth_operations.ts | 26 + User/.prettierignore | 3 + User/.prettierrc | 7 + User/package-lock.json | 45 +- User/package.json | 4 + User/render/js/announcement.js | 11 +- User/render/js/helpers.js | 6 +- User/render/js/login.js | 75 +- User/render/js/main_menu.js | 116 +-- User/render/js/profile.js | 31 +- User/render/js/reset_database.js | 4 +- User/render/js/reset_password.js | 24 +- User/render/js/share_file.js | 26 +- User/render/js/sign_up.js | 22 +- User/src/database/database.ts | 141 ++++ User/src/database/helpers/memory_manager.ts | 42 ++ User/src/database/helpers/queue_manager.ts | 38 + .../src/database/schemes/app_config_scheme.ts | 20 + User/src/database/schemes/database_scheme.ts | 9 + .../schemes/local_resources_scheme.ts | 16 + User/src/database/schemes/network_scheme.ts | 12 + User/src/helpers/backup_manager.ts | 319 ++++----- User/src/helpers/backup_retrieval.ts | 368 +++++----- User/src/helpers/department_sharer.ts | 333 +++++---- User/src/helpers/directory_watcher.ts | 253 +++---- User/src/helpers/file_encryptor.ts | 90 +-- User/src/helpers/file_sharer.ts | 264 +++---- User/src/helpers/json_manager.ts | 192 ++--- User/src/helpers/memory_manager.ts | 47 -- User/src/helpers/network_scanner.ts | 365 +++++----- User/src/helpers/queue_manager.ts | 137 ---- User/src/helpers/tcp_communicator.ts | 152 ++-- User/src/helpers/users_info_fetcher.ts | 173 ++--- User/src/helpers/window_manager.ts | 284 ++++---- User/src/helpers/worker_manager.ts | 222 +++--- User/src/interfaces/file_item_task.ts | 8 +- User/src/interfaces/pool_request.ts | 18 +- User/src/interfaces/registered_client.ts | 8 +- User/src/interfaces/worker_message.ts | 6 +- User/src/ipc-handlers/database_handler.ts | 137 ++++ User/src/ipc-handlers/uc_handler.ts | 60 ++ User/src/ipc-handlers/ui_handler.ts | 0 User/src/main/main.ts | 473 +++++-------- User/src/main/preload.ts | 97 +-- User/src/network/connection_manager.ts | 62 +- User/src/network/message_handler.ts | 110 +-- User/src/network/network.ts | 4 +- User/src/network/operation_codes.ts | 71 +- .../operations_base/operation_handler.ts | 88 +-- .../operations_base/operation_plugin.ts | 4 +- .../operations_custom/general_operations.ts | 161 +++-- .../user_to_user_operations.ts | 658 ++++++++++-------- .../socket_communicator_base.ts | 277 ++++---- .../tcp_client_communicator.ts | 155 +++-- .../tcp_server_communicator.ts | 122 ++-- .../udp_socket_communicator.ts | 58 +- User/src/network/tcp/tcp_client.ts | 209 +++--- User/src/network/tcp/tcp_server.ts | 223 +++--- User/src/network/udp/udp_client.ts | 290 ++++---- User/src/network/udp/udp_server.ts | 137 ++-- User/src/workers/backup_retrieval_worker.ts | 53 +- .../src/workers/directories_watcher_worker.ts | 46 +- User/src/workers/network_scanner_worker.ts | 50 +- .../workers/resource_coordinator_worker.ts | 55 +- User/src/workers/servers_worker.ts | 40 +- User/tsconfig.json | 2 +- 67 files changed, 3922 insertions(+), 3638 deletions(-) create mode 100644 User/.prettierignore create mode 100644 User/.prettierrc create mode 100644 User/src/database/database.ts create mode 100644 User/src/database/helpers/memory_manager.ts create mode 100644 User/src/database/helpers/queue_manager.ts create mode 100644 User/src/database/schemes/app_config_scheme.ts create mode 100644 User/src/database/schemes/database_scheme.ts create mode 100644 User/src/database/schemes/local_resources_scheme.ts create mode 100644 User/src/database/schemes/network_scheme.ts delete mode 100644 User/src/helpers/memory_manager.ts delete mode 100644 User/src/helpers/queue_manager.ts create mode 100644 User/src/ipc-handlers/database_handler.ts create mode 100644 User/src/ipc-handlers/uc_handler.ts create mode 100644 User/src/ipc-handlers/ui_handler.ts diff --git a/UC/src/network/operation_codes.ts b/UC/src/network/operation_codes.ts index 7f6edb1..3d05a89 100644 --- a/UC/src/network/operation_codes.ts +++ b/UC/src/network/operation_codes.ts @@ -14,6 +14,7 @@ export let operationCodes = { LOGIN: 'LOGIN', SIGN_UP: 'SIGN_UP', RESET_PASSWORD: 'RESET_PASSWORD', + EMAIL_VERIFICATION: 'EMAIL_VERIFICATION', FIND_BY_EMAIL: 'FIND_BY_EMAIL', MODIFY_USER: 'MODIFY_USER', diff --git a/UC/src/network/operations_custom/auth_operations.ts b/UC/src/network/operations_custom/auth_operations.ts index 0a8d7ea..6149447 100644 --- a/UC/src/network/operations_custom/auth_operations.ts +++ b/UC/src/network/operations_custom/auth_operations.ts @@ -9,6 +9,7 @@ export class AuthOperations implements OperationPlugin { LOGIN: 'LOGIN', SIGN_UP: 'SIGN_UP', RESET_PASSWORD: 'RESET_PASSWORD', + EMAIL_VERIFICATION: 'EMAIL_VERIFICATION', }; // Utility function to validate email format @@ -147,10 +148,35 @@ export class AuthOperations implements OperationPlugin { } } + public static async handleEmailVerification(parsedMessage: ParsedMessage): Promise { + const { email } = parsedMessage.metaInfo || {}; + + if (!AuthOperations.isValidEmail(email)) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Invalid email format.' }, + }; + } + + const user = userDatabase.findByEmail(email); + if (!user) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'User not found.' }, + }; + } + + return { + operationCode: operationCodes.OK, + metaInfo: { message: 'Email verified successfully.' }, + }; + } + // Register operations with the OperationHandler public register(operationHandler: OperationHandler): void { operationHandler.registerHandler(AuthOperations.operationCodes.LOGIN, AuthOperations.handleLogin); operationHandler.registerHandler(AuthOperations.operationCodes.SIGN_UP, AuthOperations.handleSignUp); operationHandler.registerHandler(AuthOperations.operationCodes.RESET_PASSWORD, AuthOperations.handleResetPassword); + operationHandler.registerHandler(AuthOperations.operationCodes.EMAIL_VERIFICATION, AuthOperations.handleEmailVerification); } } diff --git a/User/.prettierignore b/User/.prettierignore new file mode 100644 index 0000000..16acd49 --- /dev/null +++ b/User/.prettierignore @@ -0,0 +1,3 @@ +node_modules +dist +package-lock.json diff --git a/User/.prettierrc b/User/.prettierrc new file mode 100644 index 0000000..86c151f --- /dev/null +++ b/User/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 2, + "printWidth": 100 +} diff --git a/User/package-lock.json b/User/package-lock.json index 7aa3edc..f245652 100644 --- a/User/package-lock.json +++ b/User/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "check-disk-space": "^3.4.0", "dotenv": "^16.4.5", + "jsonfile": "^6.1.0", "ping": "^0.4.4", "uuid": "^10.0.0" }, @@ -20,12 +21,14 @@ "@electron-forge/maker-rpm": "^6.0.0", "@electron-forge/maker-squirrel": "^6.0.0", "@electron-forge/maker-zip": "^6.0.0", + "@types/jsonfile": "^6.1.4", "@types/ping": "^0.4.4", "@types/uuid": "^10.0.0", "copyfiles": "^2.4.1", "del-cli": "^5.0.0", "electron": "^33.0.2", "node-disk-info": "^1.3.0", + "prettier": "^3.4.2", "typescript": "^5.6.2" } }, @@ -864,6 +867,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -2685,9 +2698,9 @@ } }, "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "license": "MIT", "dependencies": { @@ -3297,7 +3310,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/hard-rejection": { @@ -3846,7 +3859,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -5056,6 +5068,22 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proc-log": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", @@ -6279,7 +6307,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -6505,9 +6532,9 @@ } }, "node_modules/yarn-or-npm/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/User/package.json b/User/package.json index f4192bb..334868e 100644 --- a/User/package.json +++ b/User/package.json @@ -4,6 +4,7 @@ "version": "1.0.0", "description": "Aplicatie P2P pentru stocarea resurselor digitale", "scripts": { + "format": "prettier --write ./src/**/*.ts", "clean": "del-cli dist && del-cli out", "build-dist": "tsc && copyfiles -u 1 'src/**/*' dist", "start-dev": "tsc && copyfiles -u 1 'src/**/*' dist && electron dist/main/main.js -- --expose-gc", @@ -17,6 +18,7 @@ "dependencies": { "check-disk-space": "^3.4.0", "dotenv": "^16.4.5", + "jsonfile": "^6.1.0", "ping": "^0.4.4", "uuid": "^10.0.0" }, @@ -26,12 +28,14 @@ "@electron-forge/maker-rpm": "^6.0.0", "@electron-forge/maker-squirrel": "^6.0.0", "@electron-forge/maker-zip": "^6.0.0", + "@types/jsonfile": "^6.1.4", "@types/ping": "^0.4.4", "@types/uuid": "^10.0.0", "copyfiles": "^2.4.1", "del-cli": "^5.0.0", "electron": "^33.0.2", "node-disk-info": "^1.3.0", + "prettier": "^3.4.2", "typescript": "^5.6.2" }, "config": { diff --git a/User/render/js/announcement.js b/User/render/js/announcement.js index 7f63357..a087cf3 100644 --- a/User/render/js/announcement.js +++ b/User/render/js/announcement.js @@ -1,11 +1,9 @@ // Load announcement content from the application info when the page loads async function loadAnnouncement() { try { - const announcementText = await window.electronAPI.readApplicationInfo('announcement'); + const announcementText = await window.electronAPI.readAnnouncement(); const announcementContent = document.getElementById('announcement-content'); if (announcementContent && announcementText) { - - console.log('Announcement:', announcementText); announcementContent.innerHTML = formatTextForHtml(announcementText); } } catch (error) { @@ -15,14 +13,8 @@ async function loadAnnouncement() { } function formatTextForHtml(text) { - // Replace newlines with
tags let formattedText = text.replace(/\n/g, '
'); - - // Replace tabs with a few non-breaking spaces for indentation formattedText = formattedText.replace(/\t/g, '    '); - - // Replace other special characters as needed - // Example: Handle double spaces by converting to   formattedText = formattedText.replace(/ /g, '  '); return formattedText; @@ -30,6 +22,5 @@ function formatTextForHtml(text) { // Close the window when the close button is clicked function closeWindow() { - window.electronAPI.writeApplicationInfo('announcement', ''); window.electronAPI.closeAnnouncementWindow(); } diff --git a/User/render/js/helpers.js b/User/render/js/helpers.js index b770ad0..bc69ed1 100644 --- a/User/render/js/helpers.js +++ b/User/render/js/helpers.js @@ -13,7 +13,7 @@ function fadeOut(destination) { container.addEventListener('animationend', async () => { try { - await window.electronAPI.changeContent(destination); + await window.uiAPI.changeContent(destination); console.log('Navigated to', destination); } catch (error) { console.error('Error navigating:', error); @@ -24,9 +24,9 @@ function fadeOut(destination) { async function waitForResponse() { return new Promise((resolve) => { const idResponseCheck = setInterval(async () => { - if (await window.electronAPI.hasResponseArrived()) { + if (await window.networkAPI.hasResponseArrived()) { clearInterval(idResponseCheck); - resolve(await window.electronAPI.getLastUcResult()); // Resolve the response or null if not available + resolve(await window.networkAPI.getLastUcResult()); // Resolve the response or null if not available } }, 100); // Check every 100 milliseconds if the response has arrived }); diff --git a/User/render/js/login.js b/User/render/js/login.js index 2670032..811bc59 100644 --- a/User/render/js/login.js +++ b/User/render/js/login.js @@ -8,10 +8,12 @@ document.addEventListener('DOMContentLoaded', async function () { const resetPasswordButton = document.getElementById('resetPassword'); const signUpButton = document.getElementById('signup'); + await window.databaseAPI.setLoginStatus(false); + // Retrieve the operation codes via IPC - const operationCodes = await window.electronAPI.getOperationsCodes(); + const operationCodes = await window.networkAPI.getOperationsCodes(); if (!operationCodes) { - await window.electronAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.'); + await window.uiAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.'); return; } @@ -22,12 +24,12 @@ document.addEventListener('DOMContentLoaded', async function () { resetPasswordButton.addEventListener('click', function (e) { e.preventDefault(); - window.electronAPI.changeContent('reset_password'); + window.uiAPI.changeContent('reset_password'); }); signUpButton.addEventListener('click', function (e) { e.preventDefault(); - window.electronAPI.changeContent('sign_up'); + window.uiAPI.changeContent('sign_up'); }); // Submit button logic (handle login) @@ -42,50 +44,52 @@ document.addEventListener('DOMContentLoaded', async function () { const password = formData.get('password'); // Open a TCP socket to the stored IP - if (!await window.electronAPI.openUcSocket()) { - await window.electronAPI.showAlert('Internal error of the application. Unable to open socket.'); + if (!await window.networkAPI.openUcSocket()) { + await window.uiAPI.showAlert('Internal error of the application. Unable to open socket.'); return; } // Attempt login if (!await attemptLogin(email, password)) { - await window.electronAPI.closeUcSocket(); + await window.networkAPI.closeUcSocket(); return; } // Fetch and store user info if (!await fetchAndStoreUserInfo(email)) { - await window.electronAPI.closeUcSocket(); + await window.networkAPI.closeUcSocket(); return; } // Fetch user info from local storage - const userInfo = await window.electronAPI.readUserConfig('user_info'); + const userInfo = await window.databaseAPI.getUserInfo('user_info'); if (!userInfo) { - await window.electronAPI.closeUcSocket(); - await window.electronAPI.showAlert('Failed to fetch user info.'); + await window.networkAPI.closeUcSocket(); + await window.uiAPI.showAlert('Failed to fetch user info.'); return; } // Fetch and store encryption key if (!await fetchAndStoreEncryptionKey(userInfo.id)) { - await window.electronAPI.closeUcSocket(); + await window.networkAPI.closeUcSocket(); return; } // Close the socket and navigate to main menu after success - await window.electronAPI.closeUcSocket(); - await window.electronAPI.changeContent('main_menu'); + await window.networkAPI.closeUcSocket(); + await window.uiAPI.startWorkers(); + await window.databaseAPI.setLoginStatus(true); + await window.uiAPI.changeContent('main_menu'); }); }); async function attemptLogin(email, password) { - const app_type = await window.electronAPI.readUserConfig('app_type'); + const app_type = await window.databaseAPI.getAppType(); const messageData = {email, password, app_type}; // Send the login message to the server - if (!await window.electronAPI.sendUcMessage(codeLogin, messageData)) { - await window.electronAPI.showAlert('Failed to send login request.'); + if (!await window.networkAPI.sendUcMessage(codeLogin, messageData)) { + await window.uiAPI.showAlert('Failed to send login request.'); return false; } @@ -93,57 +97,44 @@ async function attemptLogin(email, password) { const response = await waitForResponse(); if (!response) { - await window.electronAPI.showAlert('No response from server.'); + await window.uiAPI.showAlert('No response from server.'); return false; } if (response.operationCode !== codeOk) { - await window.electronAPI.showAlert(response.metaInfo.message); + await window.uiAPI.showAlert(response.metaInfo.message); return false; } - const user_info = await window.electronAPI.readUserConfig('user_info'); - if (!user_info || (user_info && user_info.email !== email)) { - await window.electronAPI.resetApplicationInfo(); - await window.electronAPI.resetMemory(); - await window.electronAPI.resetUserConfig(); - await window.electronAPI.writeUserConfig('app_type', app_type); - await window.electronAPI.writeUserConfig('user_info', {email, password}); - return true; - } - return true; } async function fetchAndStoreUserInfo(userEmail) { - if (!await window.electronAPI.sendUcMessage(codeFindByEmail, {email: userEmail})) { - await window.electronAPI.showAlert('Failed to send request to fetch user info.'); + if (!await window.networkAPI.sendUcMessage(codeFindByEmail, {email: userEmail})) { + await window.uiAPI.showAlert('Failed to send request to fetch user info.'); return false; } // Wait for the response using waitForResponse const response = await waitForResponse(); if (response && response.operationCode === codeOk) { - let userInfo = await window.electronAPI.readUserConfig('user_info'); - if (!userInfo) { - userInfo = {}; - } - + const userInfo = {}; userInfo.id = response.metaInfo.id; + userInfo.email = response.metaInfo.email; userInfo.departmentId = response.metaInfo.departmentId; userInfo.name = response.metaInfo.name || 'User'; - await window.electronAPI.writeUserConfig('user_info', userInfo); + await window.databaseAPI.writeUserInfo(userInfo); return true; } - await window.electronAPI.showAlert('Failed to fetch user info from server.'); + await window.uiAPI.showAlert('Failed to fetch user info from server.'); return false; } async function fetchAndStoreEncryptionKey(userId) { - if (!await window.electronAPI.sendUcMessage(codeFindKeyByUser, {userId})) { - await window.electronAPI.showAlert('Failed to send request to fetch encryption key.'); + if (!await window.networkAPI.sendUcMessage(codeFindKeyByUser, {userId})) { + await window.uiAPI.showAlert('Failed to send request to fetch encryption key.'); return false; } @@ -155,10 +146,10 @@ async function fetchAndStoreEncryptionKey(userId) { iv: response.metaInfo.key.iv, }; - await window.electronAPI.writeUserConfig('encryption_key', encryptionKey); + await window.databaseAPI.writeEncryptionKey(encryptionKey); return true; } - await window.electronAPI.showAlert('Failed to fetch encryption key from server.'); + await window.uiAPI.showAlert('Failed to fetch encryption key from server.'); return false; } diff --git a/User/render/js/main_menu.js b/User/render/js/main_menu.js index 3f709ce..43a0e85 100644 --- a/User/render/js/main_menu.js +++ b/User/render/js/main_menu.js @@ -1,3 +1,7 @@ +let backupDirId = ''; +let shareDirId = ''; +let departmentDirId = ''; + document.addEventListener('DOMContentLoaded', async function () { setInterval(loadReceivedFiles, 3000); // Call loadReceivedFiles every 3000 ms (3 seconds) @@ -47,16 +51,15 @@ async function initialSetup(){ } async function restoreBackup() { - const backupDirectory = await window.electronAPI.readApplicationInfo('backupDirectory'); + const backupDirectory = await window.databaseAPI.isBackupSet(); - if (backupDirectory && backupDirectory.path) { - // If backup directory is already set, display an alert - await window.electronAPI.showAlert('You have already set a backup directory. You cannot restore again.'); + if (backupDirectory) { + await window.uiAPI.showAlert('You have already set a backup directory. You cannot restore again.'); return; } // Prompt the user to choose the destination for the restored backup - const destinationPath = await window.electronAPI.selectDirectory(); + const destinationPath = await window.uiAPI.selectDirectory(); if (!destinationPath) { return; // User canceled the directory selection } @@ -69,26 +72,31 @@ async function restoreBackup() { } async function checkAndSetAllDirectories() { - await attachNotificationButton('backupDirectory', 'Set your backup directory!', 'backup_alert', 'alert'); - await attachNotificationButton('shareDirectory', 'Set your share directory!', 'share_alert', 'alert'); - await attachNotificationButton('departmentDirectory', 'Set your department directory!', 'department_alert', 'alert'); + const directorySchemes = await window.databaseAPI.getLocalResources(); + + backupDirId = directorySchemes.backup.id; + shareDirId = directorySchemes.shared.id; + departmentDirId = directorySchemes.department.id; + + await attachNotificationButton(backupDirId, 'Set your backup directory!', 'backup_alert', 'alert'); + await attachNotificationButton(shareDirId, 'Set your share directory!', 'share_alert', 'alert'); + await attachNotificationButton(departmentDirId, 'Set your department directory!', 'department_alert', 'alert'); } -async function checkPathExistence(pathKey) { - return await window.electronAPI.readApplicationInfo(pathKey); +async function checkPathExistence(id) { + const dirInfo = await window.databaseAPI.getDirectoryInfo(id); + return dirInfo.path !== '' } -async function setPath(pathKey) { - const path = await window.electronAPI.selectDirectory(); - if (path === undefined) return; +async function setPath(id){ + const path = await window.uiAPI.selectDirectory(); + if (path === undefined) return false; - const id = await window.electronAPI.createMemoryEntry() - console.log({id, path}) - await window.electronAPI.writeApplicationInfo(pathKey, {id, path}); + return await window.databaseAPI.writeDirectoryPath(id, path); } -async function attachNotificationButton(pathKey, buttonText, buttonId, buttonName) { - const path = await checkPathExistence(pathKey); +async function attachNotificationButton(entryId, buttonText, buttonId, buttonName) { + const path = await checkPathExistence(entryId); if (!path) { const notificationsDiv = document.getElementById('notifications'); const button = document.createElement('button'); @@ -97,8 +105,7 @@ async function attachNotificationButton(pathKey, buttonText, buttonId, buttonNam button.name = buttonName; button.textContent = buttonText; button.addEventListener('click', async function () { - await setPath(pathKey); - button.remove(); // Remove button after setting the path + if(await setPath(entryId)) button.remove(); }); notificationsDiv.appendChild(button); } @@ -108,7 +115,7 @@ async function fetchUserInfo() { const usernameField = document.getElementById('username-field'); // Read the user credentials from the userConfig - let userInfo = await window.electronAPI.readUserConfig('user_info'); + let userInfo = await window.databaseAPI.getUserInfo(); if (userInfo && userInfo.name) { usernameField.textContent = userInfo.name; return; @@ -116,7 +123,7 @@ async function fetchUserInfo() { // Update the greeting with the fetched user's name if (usernameField) { - usernameField.textContent = userInfo.name; // Update the h1 with the user's name + usernameField.textContent = userInfo.name; } else { console.error("Username field is not available in the DOM."); } @@ -124,29 +131,14 @@ async function fetchUserInfo() { async function loadReceivedFiles() { // Read the shareDirectory from applicationInfo - const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory'); - - if (!shareDirectoryData || !shareDirectoryData.id) { - console.log('No received files or directory ID found.'); - return; - } - - const directoryId = shareDirectoryData.id; - - // Fetch the directory structure (JSON objects with user names and file paths) - const directoryStructure = await window.electronAPI.readMemoryEntry(directoryId); - - if (!directoryStructure || !directoryStructure.structure) { - console.log('No received files found in the directory structure.'); - return; - } + const shareDirData = await window.databaseAPI.getDirectoryInfo(shareDirId); const notificationsDiv = document.getElementById('notifications'); const existingButtons = Array.from(notificationsDiv.children).map(button => button.getAttribute('data-filepath')); // Iterate over each user and their files in the structure - Object.keys(directoryStructure.structure).forEach(userName => { - const userFiles = directoryStructure.structure[userName]; + Object.keys(shareDirData.structure).forEach(userName => { + const userFiles = shareDirData.structure[userName]; // Iterate over each file of the user Object.keys(userFiles).forEach(fileName => { @@ -166,56 +158,14 @@ async function loadReceivedFiles() { }); } -async function removeReceivedFile(filePath) { - // Read the shareDirectory from applicationInfo - const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory'); - - if (!shareDirectoryData || !shareDirectoryData.id) { - console.log('No directory ID found to update.'); - return; - } - - const directoryId = shareDirectoryData.id; - - // Fetch the directory structure - let directoryStructure = await window.electronAPI.readMemoryEntry(directoryId); - - if (directoryStructure && directoryStructure.structure) { - // Iterate over each user and their files - for (let userName in directoryStructure.structure) { - let userFiles = directoryStructure.structure[userName]; - - // Check if the file exists and remove it - if (userFiles[filePath]) { - delete userFiles[filePath]; - - // Remove the user if no more files are left - if (Object.keys(userFiles).length === 0) { - delete directoryStructure.structure[userName]; - } - - // Update the directory structure in memory - await window.electronAPI.updateMemoryEntry(directoryId, directoryStructure); - console.log(`File ${filePath} removed from memory.`); - return; - } - } - } else { - console.log('No directory structure found in memory to update.'); - } -} - async function handleFileReceivedButtonPressed(filePath, button) { console.log('Notification button clicked!'); // Open the file in the file explorer - await window.electronAPI.showFileInExplorer(filePath) + await window.uiAPI.showFileInExplorer(filePath) .then(() => console.log('File explorer opened for: ' + filePath)) .catch(error => console.error('Error opening file explorer:', error)); // Remove the button after opening the file button.remove(); - - // Remove the file from memory - await removeReceivedFile(filePath); } diff --git a/User/render/js/profile.js b/User/render/js/profile.js index 44f715e..2c6de5b 100644 --- a/User/render/js/profile.js +++ b/User/render/js/profile.js @@ -1,22 +1,27 @@ +let id = ''; +let departmentId = ''; + document.addEventListener('DOMContentLoaded', async function () { - const {email, password, name} = await window.electronAPI.readUserConfig('user_info'); + const {id: userId, name, email, departmentId: userDepartmentId} = await window.databaseAPI.getUserInfo(); const emailInput = document.querySelector('input[name="email"]'); const usernameInput = document.querySelector('input[name="username"]'); const passwordInput = document.querySelector('input[name="password"]'); // Pre-fill form fields with existing data - emailInput.value = email; + id = userId + departmentId = userDepartmentId; usernameInput.value = name; - passwordInput.value = password; + emailInput.value = email; + passwordInput.value = ''; const backButton = document.getElementById('back'); const submitButton = document.getElementById('submit'); // Get operation codes from the backend - const operationCodes = await window.electronAPI.getOperationsCodes(); + const operationCodes = await window.networkAPI.getOperationsCodes(); if (!operationCodes) { - await window.electronAPI.showAlert('Internal error of the application.'); + await window.uiAPI.showAlert('Internal error of the application.'); return; } @@ -39,8 +44,7 @@ document.addEventListener('DOMContentLoaded', async function () { const name = usernameInput.value; const password = passwordInput.value; - const { id, departmentId } = await window.electronAPI.readUserConfig('user_info'); // Fetch login data - const app_type = await window.electronAPI.readUserConfig('app_type'); // Fetch app type + const app_type = await window.databaseAPI.getAppType(); // Prepare the data to be sent via the UC socket const messageData = { @@ -49,18 +53,18 @@ document.addEventListener('DOMContentLoaded', async function () { email: email, password: password, departmentId: departmentId, - app_type: app_type // Include app_type in the payload + app_type: app_type }; // Open UC socket - if (!await window.electronAPI.openUcSocket()) { - await window.electronAPI.showAlert('Failed to open socket. Internal error of the application.'); + if (!await window.networkAPI.openUcSocket()) { + await window.uiAPI.showAlert('Failed to open socket. Internal error of the application.'); return; } // Send the message to the server using the UC socket - if (!await window.electronAPI.sendUcMessage(codeModifyUser, messageData)) { - await window.electronAPI.showAlert('Failed to send message.'); + if (!await window.networkAPI.sendUcMessage(codeModifyUser, messageData)) { + await window.uiAPI.showAlert('Failed to send message.'); return; } @@ -70,8 +74,7 @@ document.addEventListener('DOMContentLoaded', async function () { console.log('User update successful.'); // Save updated user info to the userConfig - await window.electronAPI.writeUserConfig('user_credentials', { email: email, password: password }); - await window.electronAPI.writeUserConfig('user_info', {id: id, departmentId: departmentId, name: name}); + await window.databaseAPI.writeUserInfo({ id: id, email: email, name: name, departmentId: departmentId }); // Navigate back to the main menu fadeOut('main_menu'); diff --git a/User/render/js/reset_database.js b/User/render/js/reset_database.js index caa9b05..30fda71 100644 --- a/User/render/js/reset_database.js +++ b/User/render/js/reset_database.js @@ -1,4 +1,6 @@ document.addEventListener('DOMContentLoaded', async function () { await new Promise(resolve => setTimeout(resolve, 7000)); - window.electronAPI.changeContent('login'); + await window.databaseAPI.resetInternalDatabase(); + await window.uiAPI.stopWorkers(); + await window.uiAPI.changeContent('welcome'); }); \ No newline at end of file diff --git a/User/render/js/reset_password.js b/User/render/js/reset_password.js index 667ae4c..80a02ac 100644 --- a/User/render/js/reset_password.js +++ b/User/render/js/reset_password.js @@ -6,9 +6,9 @@ document.addEventListener('DOMContentLoaded', async function () { const resetPasswordButton = document.getElementById('resetPassword'); // Retrieve the operation codes via IPC - const operationCodes = await window.electronAPI.getOperationsCodes(); + const operationCodes = await window.networkAPI.getOperationsCodes(); if (!operationCodes) { - await window.electronAPI.showAlert('Internal error of the application.'); + await window.uiAPI.showAlert('Internal error of the application.'); return; } @@ -18,14 +18,14 @@ document.addEventListener('DOMContentLoaded', async function () { // Back to login backToLoginButton.addEventListener('click', function (e) { e.preventDefault(); - window.electronAPI.changeContent('login'); + window.uiAPI.changeContent('login'); }); // Reset password logic resetPasswordButton.addEventListener('click', async function (e) { e.preventDefault(); - if (!await window.electronAPI.openUcSocket()) { - await window.electronAPI.showAlert('Internal error of the application.'); + if (!await window.networkAPI.openUcSocket()) { + await window.uiAPI.showAlert('Internal error of the application.'); return; } @@ -36,7 +36,7 @@ document.addEventListener('DOMContentLoaded', async function () { // Ensure the email and new password are provided if (!email || !newPassword) { - await window.electronAPI.showAlert('Please provide both email and new password.'); + await window.uiAPI.showAlert('Please provide both email and new password.'); return; } @@ -44,23 +44,23 @@ document.addEventListener('DOMContentLoaded', async function () { return; } - await window.electronAPI.closeUcSocket(); - await window.electronAPI.showAlert('Password reset successfully.'); - window.electronAPI.changeContent('login'); + await window.networkAPI.closeUcSocket(); + await window.uiAPI.showAlert('Password reset successfully.'); + await window.uiAPI.changeContent('login'); }); }); async function attemptResetPassword(email, newPassword) { - const app_type = await window.electronAPI.readUserConfig('app_type'); + const app_type = await window.databaseAPI.getAppType(); const messageData = { email, newPassword, app_type }; - if (!await window.electronAPI.sendUcMessage(codeResetPassword, messageData)) return false; + if (!await window.networkAPI.sendUcMessage(codeResetPassword, messageData)) return false; const response = await waitForResponse(); if (response && response.operationCode === codeOk) { return true; } else { - await window.electronAPI.showAlert(response?.metaInfo?.message || 'Error resetting password.'); + await window.uiAPI.showAlert(response?.metaInfo?.message || 'Error resetting password.'); return false; } } diff --git a/User/render/js/share_file.js b/User/render/js/share_file.js index 5b5eb45..f3b259f 100644 --- a/User/render/js/share_file.js +++ b/User/render/js/share_file.js @@ -9,7 +9,7 @@ document.addEventListener('DOMContentLoaded', async function () { selectFileButton.addEventListener('click', async function (event) { event.preventDefault(); try { - pathToFile = await window.electronAPI.selectFile(); + pathToFile = await window.uiAPI.selectFile(); updateFileName(); } catch (error) { console.error('Error opening file dialog:', error); @@ -20,11 +20,11 @@ document.addEventListener('DOMContentLoaded', async function () { // Check if a file was chosen if (!pathToFile.trim()) { - await window.electronAPI.showAlert('File not chosen!'); + await window.uiAPI.showAlert('File not chosen!'); return; } - const user_info = await window.electronAPI.readUserConfig('user_info'); + const user_info = await window.databaseAPI.getUserInfo(); if (!user_info) { return; } @@ -36,7 +36,7 @@ document.addEventListener('DOMContentLoaded', async function () { .map(checkbox => checkbox.value); // Get IP of the selected users if (!selectedUserIps.length) { - await window.electronAPI.showAlert('No user selected!'); + await window.uiAPI.showAlert('No user selected!'); return; } @@ -48,8 +48,8 @@ document.addEventListener('DOMContentLoaded', async function () { path: pathToFile, // File path userName: user_info.name // Sender's username from userConfig }; - await window.electronAPI.addTaskToSendFileQueue(task); - await window.electronAPI.showAlert('File sent to the queue.'); + await window.databaseAPI.addTaskToSendFileQueue(task); + await window.uiAPI.showAlert('File sent to the queue.'); console.log(`Task added to send file to IP ${selectedUserIp}`); } catch (error) { console.error(`Error processing IP ${selectedUserIp}:`, error); @@ -85,18 +85,12 @@ async function fetchUsersAndCreateCheckboxes() { .map(checkbox => checkbox.value) ); - const usersInfoId = await window.electronAPI.readApplicationInfo('active_users_info'); - if (!usersInfoId) { - await window.electronAPI.showAlert('Internal error.'); - return; - } - - const usersInfo = await window.electronAPI.readMemoryEntry(usersInfoId); + const usersInfo = await window.databaseAPI.getActiveUsers(); if (!usersInfo || usersInfo.length === 0) { - await window.electronAPI.showAlert('No active users found.'); + await window.uiAPI.showAlert('No active users found.'); clearInterval(fetchUsersInterval); fetchUsersInterval = null; - await window.electronAPI.changeContent('main_menu'); + await window.uiAPI.changeContent('main_menu'); return; } @@ -115,7 +109,7 @@ async function fetchUsersAndCreateCheckboxes() { } const label = document.createElement('label'); - label.innerHTML = `${user.user_info.name}`; // Display user's name + label.innerHTML = `${user.user_info.name}`; label.insertBefore(checkbox, label.firstChild); usersDiv.appendChild(label); diff --git a/User/render/js/sign_up.js b/User/render/js/sign_up.js index 124fe49..c466aa5 100644 --- a/User/render/js/sign_up.js +++ b/User/render/js/sign_up.js @@ -18,12 +18,12 @@ document.addEventListener('DOMContentLoaded', async function () { const step1 = document.getElementById('step1'); const step2 = document.getElementById('step2'); - if (!await window.electronAPI.openUcSocket()) { + if (!await window.networkAPI.openUcSocket()) { alert('Internal error of the application.'); return; } - const operationCodes = await window.electronAPI.getOperationsCodes(); + const operationCodes = await window.networkAPI.getOperationsCodes(); if (!operationCodes) { await window.electronAPI.showAlert('Internal error of the application.'); return; @@ -35,7 +35,7 @@ document.addEventListener('DOMContentLoaded', async function () { let departments = await getDepartments(); if (departments === null) { - await window.electronAPI.showAlert('Failed to fetch departments.'); + await window.uiAPI.showAlert('Failed to fetch departments.'); return; } @@ -47,7 +47,7 @@ document.addEventListener('DOMContentLoaded', async function () { userData.email = document.querySelector('input[name="email"]').value; userData.name = document.querySelector('input[name="name"]').value; userData.password = document.querySelector('input[name="password"]').value; - userData.app_type = await window.electronAPI.readUserConfig('app_type'); + userData.app_type = await window.databaseAPI.getAppType(); // Move to Step 2 const departmentList = document.getElementById('departmentList'); @@ -75,7 +75,7 @@ document.addEventListener('DOMContentLoaded', async function () { // Get the selected department userData.departmentId = document.querySelector('input[name="dept"]:checked')?.value; if (!userData.departmentId) { - await window.electronAPI.showAlert('Please select a department.'); + await window.uiAPI.showAlert('Please select a department.'); return; } @@ -84,8 +84,8 @@ document.addEventListener('DOMContentLoaded', async function () { return; } - await window.electronAPI.showAlert('Signup successful!'); - await window.electronAPI.changeContent('login'); + await window.uiAPI.showAlert('Signup successful!'); + await window.uiAPI.changeContent('login'); }); // Back to Step 1 from Step 2 @@ -96,8 +96,8 @@ document.addEventListener('DOMContentLoaded', async function () { // Back to login backToLogin.addEventListener('click', async function (e) { e.preventDefault(); - await window.electronAPI.closeUcSocket(); - await window.electronAPI.changeContent('login'); + await window.networkAPI.closeUcSocket(); + await window.uiAPI.changeContent('login'); }); }); @@ -113,13 +113,13 @@ async function getDepartments() { } async function attemptSignUp() { - if (!await window.electronAPI.sendUcMessage(codeSignUp, userData)) return false; + if (!await window.networkAPI.sendUcMessage(codeSignUp, userData)) return false; const response = await waitForResponse(); if (response && response.operationCode === codeOk) { return true; } - await window.electronAPI.showAlert(`Signup failed: ${response?.metaInfo?.message || 'Unknown error'}`); + await window.uiAPI.showAlert(`Signup failed: ${response?.metaInfo?.message || 'Unknown error'}`); return false; } diff --git a/User/src/database/database.ts b/User/src/database/database.ts new file mode 100644 index 0000000..a77f64e --- /dev/null +++ b/User/src/database/database.ts @@ -0,0 +1,141 @@ +import jsonfile from 'jsonfile' +import { promises as fs } from 'fs' +import { v4 as uuidv4 } from 'uuid' +import { MemoryManager } from './helpers/memory_manager' +import { QueueManager, FileItemTask } from './helpers/queue_manager' + +const defaultData = { + app_config: { + app_type: 'client', + user_info: { + id: '', + email: '', + password: '', + departmentId: '', + name: '', + }, + encryption_key: { + key: '', + iv: '', + }, + reset_application_preferences: false, + logged_in: false, + server_found: false, + }, + network: { + usersInLan: [], + serverIp: '', + }, + local_resources: { + directory_schemes: { + backup: { id: uuidv4(), path: '', structure: {}, totalSize: 0 }, + department: { id: uuidv4(), path: '', structure: {}, totalSize: 0 }, + shared: { id: uuidv4(), path: '', structure: {}, totalSize: 0 }, + }, + }, +} + +export class JsonDatabase { + private readonly filePath: string + private memoryStore: MemoryManager + private queueStore: QueueManager + + constructor(filePath: string) { + this.filePath = filePath + this.memoryStore = new MemoryManager() + this.queueStore = new QueueManager() + + // Ensure the file exists and is not empty + this.ensureFileExists().then(() => { + console.log('[Database] File exists and is properly initialized.') + }) + } + + // Ensure the JSON file exists, or create it with default content + private async ensureFileExists(): Promise { + try { + await fs.access(this.filePath) // Check if file exists + + // Check if file is empty + const fileContent = await fs.readFile(this.filePath, 'utf-8') + if (!fileContent.trim()) { + console.log(`[Database] ${this.filePath} is empty. Writing default data...`) + await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 }) + } + } catch (error) { + console.log(`[Database] ${this.filePath} not found. Creating with default data...`) + + // Create the file with default content + await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 }) + console.log(`[Database] ${this.filePath} created successfully.`) + } + } + + async reset(): Promise { + await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 }) + } + + // Read JSON from file + async read(): Promise { + try { + return await jsonfile.readFile(this.filePath) + } catch (error: any) { + if (error.code === 'ENOENT') { + return defaultData as T // Return defaultData if file does not exist + } + throw error + } + } + + // Update JSON file with atomic write + async update( + updateCallback: (data: Awaited) => Awaited | Promise>, + ): Promise { + let data = await this.read() + data = await updateCallback(data) + await jsonfile.writeFile(this.filePath, data, { spaces: 2 }) + } + + // Generate and return a new UUID + generateUUID(): string { + return uuidv4() + } + + // MemoryStore Operations + setMemory(uuid: string, value: Q): void { + this.memoryStore.set(uuid, value) + } + + getMemory(uuid: string): Q | undefined { + return this.memoryStore.get(uuid) + } + + deleteMemory(uuid: string): boolean { + return this.memoryStore.delete(uuid) + } + + hasMemory(uuid: string): boolean { + return this.memoryStore.has(uuid) + } + + // Queue Operations + pushQueue(task: FileItemTask): void { + this.queueStore.push(task) + } + + popQueue(): FileItemTask | undefined { + return this.queueStore.pop() + } + + seekQueue(): FileItemTask | undefined { + return this.queueStore.seek() + } + + queueSize(): number { + return this.queueStore.size() + } + + clearQueue(): void { + this.queueStore.clear() + } +} diff --git a/User/src/database/helpers/memory_manager.ts b/User/src/database/helpers/memory_manager.ts new file mode 100644 index 0000000..9ffa909 --- /dev/null +++ b/User/src/database/helpers/memory_manager.ts @@ -0,0 +1,42 @@ +export class MemoryManager { + private storage: Map + + constructor() { + this.storage = new Map() + } + + // Set a value in the memory store + set(uuid: string, value: T): void { + this.storage.set(uuid, value) + } + + // Get a value from the memory store + get(uuid: string): T | undefined { + return this.storage.get(uuid) + } + + // Check if a key exists + has(uuid: string): boolean { + return this.storage.has(uuid) + } + + // Delete a key-value pair + delete(uuid: string): boolean { + return this.storage.delete(uuid) + } + + // Get all keys + keys(): string[] { + return Array.from(this.storage.keys()) + } + + // Get all values + values(): T[] { + return Array.from(this.storage.values()) + } + + // Clear all stored values + clear(): void { + this.storage.clear() + } +} diff --git a/User/src/database/helpers/queue_manager.ts b/User/src/database/helpers/queue_manager.ts new file mode 100644 index 0000000..4b5e07d --- /dev/null +++ b/User/src/database/helpers/queue_manager.ts @@ -0,0 +1,38 @@ +export interface FileItemTask { + ip: string + path: string + userName: string +} + +export class QueueManager { + private queue: FileItemTask[] + + constructor() { + this.queue = [] + } + + // Add a new task to the queue + push(task: FileItemTask): void { + this.queue.push(task) + } + + // Remove and return the first task (FIFO) + pop(): FileItemTask | undefined { + return this.queue.shift() + } + + // View the first task without removing it + seek(): FileItemTask | undefined { + return this.queue[0] + } + + // Get queue length + size(): number { + return this.queue.length + } + + // Clear all tasks + clear(): void { + this.queue = [] + } +} diff --git a/User/src/database/schemes/app_config_scheme.ts b/User/src/database/schemes/app_config_scheme.ts new file mode 100644 index 0000000..81fd163 --- /dev/null +++ b/User/src/database/schemes/app_config_scheme.ts @@ -0,0 +1,20 @@ +export interface AppConfigScheme { + app_type: 'client' | 'ceo' + user_info: UserInfoScheme + encryption_key: EncryptionKeyScheme + reset_application_preferences: boolean + logged_in: boolean + server_found: boolean +} + +export interface UserInfoScheme { + id: string + name: string + email: string + departmentId: string +} + +export interface EncryptionKeyScheme { + key: string + iv: string +} diff --git a/User/src/database/schemes/database_scheme.ts b/User/src/database/schemes/database_scheme.ts new file mode 100644 index 0000000..6e0d355 --- /dev/null +++ b/User/src/database/schemes/database_scheme.ts @@ -0,0 +1,9 @@ +import { NetworkScheme } from './network_scheme' +import { AppConfigScheme } from './app_config_scheme' +import { LocalResourcesScheme } from './local_resources_scheme' + +export interface DatabaseScheme { + app_config: AppConfigScheme + network: NetworkScheme + local_resources: LocalResourcesScheme +} diff --git a/User/src/database/schemes/local_resources_scheme.ts b/User/src/database/schemes/local_resources_scheme.ts new file mode 100644 index 0000000..793712d --- /dev/null +++ b/User/src/database/schemes/local_resources_scheme.ts @@ -0,0 +1,16 @@ +export interface LocalResourcesScheme { + directory_schemes: DirectorySchemes +} + +export interface DirectorySchemes { + backup: DirectoryInfo + department: DirectoryInfo + shared: DirectoryInfo +} + +export interface DirectoryInfo { + id: string + path: string + structure: any + totalSize: number +} diff --git a/User/src/database/schemes/network_scheme.ts b/User/src/database/schemes/network_scheme.ts new file mode 100644 index 0000000..65b6a7c --- /dev/null +++ b/User/src/database/schemes/network_scheme.ts @@ -0,0 +1,12 @@ +export interface NetworkUserScheme { + id: string + ip: string + name: string + departmentId: string +} + +export interface NetworkScheme { + serverIp: string + usersInLan: NetworkUserScheme[] + announcement: string +} diff --git a/User/src/helpers/backup_manager.ts b/User/src/helpers/backup_manager.ts index ae87b65..4f32074 100644 --- a/User/src/helpers/backup_manager.ts +++ b/User/src/helpers/backup_manager.ts @@ -1,192 +1,175 @@ -import fs from 'fs'; -import path from 'path'; -import { FileEncryptor } from './file_encryptor'; -import { MemoryManager } from './memory_manager'; -import { JsonManager } from './json_manager'; -import { TcpCommunicator } from './tcp_communicator'; -import { operationCodes } from '../network/operation_codes'; -import { ParsedMessage } from "../network/message_handler"; +import fs from 'fs' +import path from 'path' +import { FileEncryptor } from './file_encryptor' +import { TcpCommunicator } from './tcp_communicator' +import { operationCodes } from '../network/operation_codes' +import { ParsedMessage } from '../network/message_handler' +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' +import { NetworkUserScheme } from '../database/schemes/network_scheme' export class BackupManager { - private fileEncryptor: FileEncryptor | null = null; - private memoryManager: MemoryManager; - private applicationInfo: JsonManager; - private userConfig: JsonManager; - private readonly clientPort: number; - private isBusy: boolean = false; - private intervalId: NodeJS.Timeout | null = null; - private stopRequested: boolean = false; + private fileEncryptor: FileEncryptor | null = null + private readonly db: JsonDatabase + private readonly clientPort: number + private isBusy: boolean = false + private intervalId: NodeJS.Timeout | null = null + private stopRequested: boolean = false - constructor(userConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, clientPort: number) { - this.applicationInfo = new JsonManager(applicationInfoPath); - this.userConfig = new JsonManager(userConfigPath); - this.memoryManager = new MemoryManager(memoryManagerPath); - this.clientPort = clientPort; + constructor(pathToDatabaseFile: string, clientPort: number) { + this.db = new JsonDatabase(pathToDatabaseFile) + this.clientPort = clientPort + } + + async start(): Promise { + this.intervalId = setInterval(async () => { + if (!this.isBusy || !this.stopRequested) { + this.isBusy = true + this.log('Start successfully. Backup files to users.') + await this.initialize() + } + + if (global.gc) { + global.gc() + } + }, 10000) // 10-second interval for testing + } + + private async initialize(): Promise { + this.isBusy = true + try { + const data = await this.db.read() + const userInfo = data.app_config.user_info + const encryptionKey = data.app_config.encryption_key + const usersIp = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip) + const backupDirectoryData = data.local_resources.directory_schemes.backup + + this.fileEncryptor = new FileEncryptor(encryptionKey.key, encryptionKey.iv) + + await this.sendFilesToUsers( + userInfo.name, + usersIp, + backupDirectoryData.structure, + backupDirectoryData.path, + ) + } catch (error: any) { + this.log(`Error in initialize process: ${error.message}`, 'error') + } finally { + this.log('Backup process completed.') + this.isBusy = false + } + } + + private encryptFile(filePath: string): string { + if (!this.fileEncryptor) { + return filePath } - async start(): Promise { - this.intervalId = setInterval(async () => { - if (!this.isBusy || !this.stopRequested) { - this.isBusy = true; - this.log('Start successfully. Backup files to users.'); - await this.initialize(); - } - - if (global.gc) { - global.gc(); - } - }, 10000); // 10-second interval for testing + if (!fs.existsSync(filePath)) { + this.log(`File not found: ${filePath}`, 'error') + return '' } - private async initialize(): Promise { - this.isBusy = true; + return this.fileEncryptor.encryptFileToBase64(filePath) + } + + private async sendFilesToUsers( + userName: string, + usersIp: string[], + fileStructure: { [key: string]: string }, + backupDirectoryPath: string, + ): Promise { + let unsentFiles = Object.keys(fileStructure) + + for (const fileName of unsentFiles) { + const filePath = fileStructure[fileName] + const encryptedFileContent = this.encryptFile(filePath) + + if (!encryptedFileContent) { + this.log(`Failed to encrypt file: ${fileName}`, 'error') + continue + } + + const relativeFilePath = path.relative(backupDirectoryPath, filePath) + const metaInfo = { userName, relativeFilePath } + + for (const ip of usersIp) { + const tcpCommunicator = new TcpCommunicator(ip, this.clientPort) + try { - const encryptionKeyData = await this.userConfig.readValue('encryption_key'); - if (!encryptionKeyData || !encryptionKeyData.key || !encryptionKeyData.iv) { - this.log('Encryption key data is missing in user configuration.', 'error'); - return; - } + await tcpCommunicator.connect() + this.log(`Connected to ${ip}`) - this.fileEncryptor = new FileEncryptor(encryptionKeyData.key, encryptionKeyData.iv); + const sendSuccess = await tcpCommunicator.sendMessage( + operationCodes.BACKUP_FILE, + metaInfo, + Buffer.from(encryptedFileContent, 'base64'), + ) + if (!sendSuccess) { + throw new Error('Failed to send file content.') + } - const userInfo = await this.userConfig.readValue('user_info'); - if (!userInfo || !userInfo.name) { - this.log('User information is missing in user configuration.', 'error'); - return; - } - const userName = userInfo.name; + const responseReceived = await this.waitForResponse(tcpCommunicator) + if (!responseReceived) { + throw new Error('Timeout waiting for the message response.') + } - const backupDirectoryData = await this.applicationInfo.readValue('backupDirectory'); - if (!backupDirectoryData || !backupDirectoryData.id || !backupDirectoryData.path) { - this.log('Backup directory information is missing in application info.', 'error'); - return; - } - - const activeUsersIp = await this.applicationInfo.readValue('users_ip'); - if (!activeUsersIp || !activeUsersIp.length) { - this.log('No active users found.', 'error'); - return; - } - - const backupDirectoryId = backupDirectoryData.id; - const backupDirectoryPath = backupDirectoryData.path; - - const directoryData = await this.memoryManager.retrieveMetaInformation(backupDirectoryId); - if (!directoryData || !directoryData.structure) { - this.log('Backup directory structure is missing in memory.', 'error'); - return; - } - - await this.sendFilesToUsers(directoryData.structure, userName, activeUsersIp, backupDirectoryPath); - } catch (error: any) { - this.log(`Error in initialize process: ${error.message}`, 'error'); + this.log(`Successfully sent file: ${fileName} to ${ip}`) + unsentFiles = unsentFiles.filter((f) => f !== fileName) + break + } catch (error) { + this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error') } finally { - this.log('Backup process completed.'); - this.isBusy = false; + await tcpCommunicator.disconnect() + this.log(`Disconnected from ${ip}`) } + } } - private encryptFile(filePath: string): string { - if (!this.fileEncryptor) { - return filePath; - } + if (unsentFiles.length > 0) { + process.send?.({ type: 'log', message: 'Backup could not be completed for all files' }) + } else { + process.send?.({ type: 'log', message: 'Backup completed successfully' }) + } + } - if (!fs.existsSync(filePath)) { - this.log(`File not found: ${filePath}`, 'error'); - return ''; + private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise { + return new Promise((resolve) => { + const idResponseCheck = setInterval(() => { + if (!tcpCommunicator) return null + if (tcpCommunicator.hasResponseArrived()) { + clearInterval(idResponseCheck) + resolve(tcpCommunicator.getLastResult()) } + }, 100) + }) + } - return this.fileEncryptor.encryptFileToBase64(filePath); + async stop(): Promise { + this.stopRequested = true // Signal that stop is requested + + if (this.intervalId) { + clearInterval(this.intervalId) + this.intervalId = null } - private async sendFilesToUsers(fileStructure: { [key: string]: string }, userName: string, usersIp: string[], backupDirectoryPath: string): Promise { - let unsentFiles = Object.keys(fileStructure); - - for (const fileName of unsentFiles) { - const filePath = fileStructure[fileName]; - const encryptedFileContent = this.encryptFile(filePath); - - if (!encryptedFileContent) { - this.log(`Failed to encrypt file: ${fileName}`, 'error'); - continue; - } - - const relativeFilePath = path.relative(backupDirectoryPath, filePath); - const metaInfo = { userName, relativeFilePath }; - - for (const ip of usersIp) { - const tcpCommunicator = new TcpCommunicator(ip, this.clientPort); - - try { - await tcpCommunicator.connect(); - this.log(`Connected to ${ip}`); - - const sendSuccess = await tcpCommunicator.sendMessage(operationCodes.BACKUP_FILE, metaInfo, Buffer.from(encryptedFileContent, 'base64')); - if (!sendSuccess) { - throw new Error('Failed to send file content.'); - } - - const responseReceived = await this.waitForResponse(tcpCommunicator); - if (!responseReceived) { - throw new Error('Timeout waiting for the message response.'); - } - - this.log(`Successfully sent file: ${fileName} to ${ip}`); - unsentFiles = unsentFiles.filter(f => f !== fileName); - break; - } catch (error) { - this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error'); - } finally { - await tcpCommunicator.disconnect(); - this.log(`Disconnected from ${ip}`); - } - } - } - - if (unsentFiles.length > 0) { - process.send?.({type: 'log', message: 'Backup could not be completed for all files'}); - } else { - process.send?.({type: 'log', message: 'Backup completed successfully' }); - } + // Wait for any ongoing process to complete if busy + while (this.isBusy) { + await new Promise((resolve) => setTimeout(resolve, 100)) } - private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise { - return new Promise((resolve) => { - const idResponseCheck = setInterval(() => { - if (!tcpCommunicator) return null; - if (tcpCommunicator.hasResponseArrived()) { - clearInterval(idResponseCheck); - resolve(tcpCommunicator.getLastResult()); - } - }, 100); - }); - } - - async stop(): Promise { - this.stopRequested = true; // Signal that stop is requested - - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - } - - // Wait for any ongoing process to complete if busy - while (this.isBusy) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - - console.log("[BackupManager] Stopped successfully."); - } - - // Unified logging function - private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void { - const prefix = '[BackupManager]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); - } else if (level === 'warn') { - console.warn(`${prefix} ${message}`); - } else { - console.log(`${prefix} ${message}`); - } + console.log('[BackupManager] Stopped successfully.') + } + + // Unified logging function + private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void { + const prefix = '[BackupManager]' + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else if (level === 'warn') { + console.warn(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) } + } } diff --git a/User/src/helpers/backup_retrieval.ts b/User/src/helpers/backup_retrieval.ts index aec9696..159369d 100644 --- a/User/src/helpers/backup_retrieval.ts +++ b/User/src/helpers/backup_retrieval.ts @@ -1,206 +1,210 @@ -import { JsonManager } from './json_manager'; -import { TcpCommunicator } from "./tcp_communicator"; -import { operationCodes } from '../network/operation_codes'; -import path from 'path'; -import fs from 'fs'; -import crypto from 'crypto'; -import { ParsedMessage } from "../network/message_handler"; +import { TcpCommunicator } from './tcp_communicator' +import { operationCodes } from '../network/operation_codes' +import path from 'path' +import fs from 'fs' +import crypto from 'crypto' +import { ParsedMessage } from '../network/message_handler' +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' export class BackupRetrievalWorker { - private userConfig: JsonManager; - private applicationInfo: JsonManager; - private readonly clientPort: number; - private readonly destinationPath: string; - private encryptionKey: Buffer | null = null; - private iv: Buffer | null = null; - private tcpCommunicator: TcpCommunicator | null = null; - private stopRequested: boolean = false; - private isBusy: boolean = false; + private db: JsonDatabase + private readonly clientPort: number + private readonly destinationPath: string + private encryptionKey: Buffer | null = null + private iv: Buffer | null = null + private tcpCommunicator: TcpCommunicator | null = null + private stopRequested: boolean = false + private isBusy: boolean = false - constructor(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string) { - this.userConfig = new JsonManager(userConfigPath); - this.applicationInfo = new JsonManager(applicationInfoPath); - this.clientPort = clientPort; - this.destinationPath = destinationPath; + constructor(pathToDatabaseFile: string, clientPort: number, destinationPath: string) { + this.db = new JsonDatabase(pathToDatabaseFile) + this.clientPort = clientPort + this.destinationPath = destinationPath + } + + // Logging helper function + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[BackupRetrievalWorker]' + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) + } + } + + async start(): Promise { + if (!this.stopRequested) return + this.isBusy = true + try { + const data = await this.db.read() + const userInfo = data.app_config.user_info + const encryptionKey = data.app_config.encryption_key + + const userName = userInfo.name + this.encryptionKey = Buffer.from(encryptionKey.key, 'base64') + this.iv = Buffer.from(encryptionKey.iv, 'base64') + + const activeUsers = data.network.usersInLan + if (!activeUsers.length) { + throw new Error('No active users found.') + } + + for (const lanUser of activeUsers) { + const success = await this.processBackupForIp(lanUser.ip, userName) + if (!success) { + throw new Error(`Failed to retrieve backup from ${lanUser.ip}`) + } + this.log(`Backup retrieved successfully from ${lanUser.ip}`) + } + + process.send?.({ type: 'showAlert', message: 'Backup retrieval completed successfully.' }) + process.send?.({ type: 'changeContent', page: 'main_menu' }) + } catch (error: any) { + this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error') + process.send?.({ type: 'showAlert', message: `A problem occurred: ${error.message}` }) + process.send?.({ type: 'changeContent', page: 'main_menu' }) + } finally { + this.isBusy = false } - // Logging helper function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[BackupRetrievalWorker]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); - } else { - console.log(`${prefix} ${message}`); - } + if (global.gc) { + global.gc() + } + } + + private async processBackupForIp(ip: string, userName: string): Promise { + this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort) + if (!(await this.tcpCommunicator.connect())) { + this.log(`Failed to connect to ${ip}`, 'error') + return true } - async start(): Promise { - if(!this.stopRequested) return; - this.isBusy = true; - try { - const userInfo = await this.userConfig.readValue('user_info'); - if (!userInfo || !userInfo.name) { - throw new Error('User information or name is missing.'); - } - const userName = userInfo.name; - - const encryptionData = await this.userConfig.readValue('encryption_key'); - if (!encryptionData || !encryptionData.key || !encryptionData.iv) { - throw new Error('Encryption key or IV is missing.'); - } - - this.encryptionKey = Buffer.from(encryptionData.key, 'base64'); - this.iv = Buffer.from(encryptionData.iv, 'base64'); - - const activeUsersIp = await this.applicationInfo.readValue('users_ip'); - if (!activeUsersIp || !activeUsersIp.length) { - throw new Error('No active users found.'); - } - - for (const ip of activeUsersIp) { - const success = await this.processBackupForIp(ip, userName); - if (!success) { - throw new Error(`Failed to retrieve backup from ${ip}`); - } - this.log(`Backup retrieved successfully from ${ip}`); - } - - process.send?.({ type: 'showAlert', message: 'Backup retrieval completed successfully.' }); - process.send?.({ type: 'changeContent', page: 'main_menu' }); - } catch (error: any) { - this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error'); - process.send?.({ type: 'showAlert', message: `A problem occurred: ${error.message}` }); - process.send?.({ type: 'changeContent', page: 'main_menu' }); - } - finally { - this.isBusy = false; - } - - if (global.gc) { - global.gc(); - } + const backupExists = await this.checkIfBackupExists(userName) + if (!backupExists) { + this.log(`No backup found for user ${userName} on IP ${ip}`) + await this.tcpCommunicator.disconnect() + return true } - private async processBackupForIp(ip: string, userName: string): Promise { - this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort); - if (!await this.tcpCommunicator.connect()) { - this.log(`Failed to connect to ${ip}`, 'error'); - return true; - } - - const backupExists = await this.checkIfBackupExists(userName); - if (!backupExists) { - this.log(`No backup found for user ${userName} on IP ${ip}`); - await this.tcpCommunicator.disconnect(); - return true; - } - - const backupStructure = await this.requestBackupStructure(userName); - if (!backupStructure || Object.keys(backupStructure).length === 0) { - this.log(`No files found in backup structure for user ${userName} on IP ${ip}`); - await this.tcpCommunicator.disconnect(); - return true; - } - - for (const relativeFilePath of Object.keys(backupStructure)) { - const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath); - if (!fileRequestSuccess) { - await this.tcpCommunicator.disconnect(); - throw new Error(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`); - } - } - - await this.tcpCommunicator.disconnect(); - return true; + const backupStructure = await this.requestBackupStructure(userName) + if (!backupStructure || Object.keys(backupStructure).length === 0) { + this.log(`No files found in backup structure for user ${userName} on IP ${ip}`) + await this.tcpCommunicator.disconnect() + return true } - private async checkIfBackupExists(userName: string): Promise { - if (!this.tcpCommunicator) return false; - - const metaInfo = { name: userName }; - if (!await this.tcpCommunicator.sendMessage(operationCodes.IS_BACKUP_CREATED, metaInfo)) return false; - - const response = await this.waitForResponse(); - return response?.metaInfo?.backupExists === true; + for (const relativeFilePath of Object.keys(backupStructure)) { + const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath) + if (!fileRequestSuccess) { + await this.tcpCommunicator.disconnect() + throw new Error( + `Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`, + ) + } } - private async requestBackupStructure(userName: string): Promise { - if (!this.tcpCommunicator) return false; + await this.tcpCommunicator.disconnect() + return true + } - const metaInfo = { name: userName }; - if (!await this.tcpCommunicator.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo)) return null; + private async checkIfBackupExists(userName: string): Promise { + if (!this.tcpCommunicator) return false - const response = await this.waitForResponse(); - return response?.metaInfo?.structure || null; + const metaInfo = { name: userName } + if (!(await this.tcpCommunicator.sendMessage(operationCodes.IS_BACKUP_CREATED, metaInfo))) + return false + + const response = await this.waitForResponse() + return response?.metaInfo?.backupExists === true + } + + private async requestBackupStructure(userName: string): Promise { + if (!this.tcpCommunicator) return false + + const metaInfo = { name: userName } + if (!(await this.tcpCommunicator.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo))) + return null + + const response = await this.waitForResponse() + return response?.metaInfo?.structure || null + } + + private async requestBackupFile(userName: string, relativeFilePath: string): Promise { + if (!this.tcpCommunicator) return false + + const metaInfo = { name: userName, relativeFilePath } + if (!(await this.tcpCommunicator.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo))) + return false + + const response = await this.waitForResponse() + if ( + response?.operationCode === operationCodes.OK && + response.metaInfo && + response.fileContent + ) { + return this.saveFile( + response.metaInfo.relativeFilePath, + response.fileContent.toString('base64'), + ) + } + return false + } + + private saveFile(relativeFilePath: string, fileContent: string): boolean { + if (!this.encryptionKey || !this.iv) { + throw new Error('Encryption key or IV is not set.') } - private async requestBackupFile(userName: string, relativeFilePath: string): Promise { - if (!this.tcpCommunicator) return false; - - const metaInfo = { name: userName, relativeFilePath }; - if (!await this.tcpCommunicator.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo)) return false; - - const response = await this.waitForResponse(); - if (response?.operationCode === operationCodes.OK && response.metaInfo && response.fileContent) { - return this.saveFile(response.metaInfo.relativeFilePath, response.fileContent.toString('base64')); - } - return false; + let encryptedBuffer: Buffer + try { + encryptedBuffer = Buffer.from(fileContent, 'base64') + } catch (error: any) { + throw new Error(`Error decoding base64 file content: ${error.message}`) } - private saveFile(relativeFilePath: string, fileContent: string): boolean { - if (!this.encryptionKey || !this.iv) { - throw new Error('Encryption key or IV is not set.'); - } - - let encryptedBuffer: Buffer; - try { - encryptedBuffer = Buffer.from(fileContent, 'base64'); - } catch (error: any) { - throw new Error(`Error decoding base64 file content: ${error.message}`); - } - - let decryptedContent: Buffer; - try { - const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv); - decryptedContent = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]); - } catch (error: any) { - throw new Error(`Error decrypting file: ${error.message}`); - } - - const fullFilePath = path.join(this.destinationPath, relativeFilePath); - try { - const dirPath = path.dirname(fullFilePath); - if (!fs.existsSync(dirPath)) { - fs.mkdirSync(dirPath, { recursive: true }); - } - fs.writeFileSync(fullFilePath, decryptedContent); - this.log(`File saved successfully: ${fullFilePath}`); - return true; - } catch (error: any) { - throw new Error(`Error saving file ${relativeFilePath}: ${error.message}`); - } + let decryptedContent: Buffer + try { + const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv) + decryptedContent = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]) + } catch (error: any) { + throw new Error(`Error decrypting file: ${error.message}`) } - async stop(): Promise { - this.stopRequested = true; // Signal that stop is requested + const fullFilePath = path.join(this.destinationPath, relativeFilePath) + try { + const dirPath = path.dirname(fullFilePath) + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }) + } + fs.writeFileSync(fullFilePath, decryptedContent) + this.log(`File saved successfully: ${fullFilePath}`) + return true + } catch (error: any) { + throw new Error(`Error saving file ${relativeFilePath}: ${error.message}`) + } + } - // Wait for any ongoing process to complete if busy - while (this.isBusy) { - await new Promise((resolve) => setTimeout(resolve, 100)); + async stop(): Promise { + this.stopRequested = true // Signal that stop is requested + + // Wait for any ongoing process to complete if busy + while (this.isBusy) { + await new Promise((resolve) => setTimeout(resolve, 100)) + } + + console.log('[BackupManager] Stopped successfully.') + } + + private async waitForResponse(): Promise { + return new Promise((resolve) => { + const idResponseCheck = setInterval(async () => { + if (!this.tcpCommunicator) return null + if (this.tcpCommunicator.hasResponseArrived()) { + clearInterval(idResponseCheck) + resolve(this.tcpCommunicator.getLastResult()) } - - console.log("[BackupManager] Stopped successfully."); - } - - private async waitForResponse(): Promise { - return new Promise((resolve) => { - const idResponseCheck = setInterval(async () => { - if (!this.tcpCommunicator) return null; - if (this.tcpCommunicator.hasResponseArrived()) { - clearInterval(idResponseCheck); - resolve(this.tcpCommunicator.getLastResult()); - } - }, 100); - }); - } + }, 100) + }) + } } diff --git a/User/src/helpers/department_sharer.ts b/User/src/helpers/department_sharer.ts index ea844b7..96adb4f 100644 --- a/User/src/helpers/department_sharer.ts +++ b/User/src/helpers/department_sharer.ts @@ -1,214 +1,191 @@ -import fs from 'fs'; -import path from 'path'; -import { TcpCommunicator } from './tcp_communicator'; -import { operationCodes } from '../network/operation_codes'; -import { JsonManager } from './json_manager'; -import { MemoryManager } from './memory_manager'; -import { ParsedMessage } from "../network/message_handler"; +import fs from 'fs' +import path from 'path' +import { TcpCommunicator } from './tcp_communicator' +import { operationCodes } from '../network/operation_codes' +import { ParsedMessage } from '../network/message_handler' +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' export class DepartmentSharer { - private userConfig: JsonManager; - private applicationInfo: JsonManager; - private memoryManager: MemoryManager; - private departmentDirectory: string | null; - private readonly clientPort: number; - private isBusy: boolean = false; - private tcpCommunicator: TcpCommunicator | null = null; - private stopRequested: boolean = false; - private intervalId: NodeJS.Timeout | null = null; + private readonly db: JsonDatabase + private departmentDirectory: string | null = null + private readonly clientPort: number + private isBusy: boolean = false + private tcpCommunicator: TcpCommunicator | null = null + private stopRequested: boolean = false + private intervalId: NodeJS.Timeout | null = null - constructor( - userConfigPath: string, - applicationInfoPath: string, - memoryManagerPath: string, - clientPort: number - ) { - this.userConfig = new JsonManager(userConfigPath); - this.applicationInfo = new JsonManager(applicationInfoPath); - this.memoryManager = new MemoryManager(memoryManagerPath); - this.clientPort = clientPort; - this.departmentDirectory = null; - } + constructor(pathToDatabaseFile: string, clientPort: number) { + this.db = new JsonDatabase(pathToDatabaseFile) + this.clientPort = clientPort + } - // Start sharing files with the department every minute - async start(): Promise { - this.intervalId = setInterval(async () => { - if (!this.isBusy || !this.stopRequested) { - this.isBusy = true; - this.log('Start successfully. Sharing files with the department.'); - await this.shareFilesWithDepartment(); - } + // Start sharing files with the department every minute + async start(): Promise { + this.intervalId = setInterval(async () => { + if (!this.isBusy || !this.stopRequested) { + this.isBusy = true + this.log('Start successfully. Sharing files with the department.') + await this.shareFilesWithDepartment() + } - if (global.gc) { - global.gc(); - } - }, 10000); // 10-second interval for testing - } + if (global.gc) { + global.gc() + } + }, 10000) // 10-second interval for testing + } - // Share files with users in the same department - private async shareFilesWithDepartment(): Promise { - try { - // Get the current user's department information - const userInfo = await this.userConfig.readValue('user_info'); - if (!userInfo || !userInfo.departmentId || !userInfo.name) { - throw new Error('User information or department ID is missing in the configuration.'); - } + // Share files with users in the same department + private async shareFilesWithDepartment(): Promise { + try { + const data = await this.db.read() + const userInfo = data.app_config.user_info + const departmentStructure = data.local_resources.directory_schemes.department - const departmentId = userInfo.departmentId; - const userName = userInfo.name; + const departmentId = userInfo.departmentId + const userName = userInfo.name + const activeUsers = data.network.usersInLan + this.departmentDirectory = departmentStructure.path - // Get the list of active users from applicationInfo - const activeUsersId = await this.applicationInfo.readValue('active_users_info'); - if (!activeUsersId) { - throw new Error('No active users found.'); - } + // Filter users who belong to the same department + const departmentUsers = activeUsers.filter( + (user: any) => user.user_info.departmentId === departmentId, + ) + if (departmentUsers.length === 0) { + throw new Error('No users found in the same department.') + } - const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId); - if (!activeUsers || activeUsers.length === 0) { - throw new Error('No active users found.'); - } + // Iterate over all department users and perform the operations + for (const user of departmentUsers) { + const userIp = user.ip + this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort) - // Filter users who belong to the same department - const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId); - if (departmentUsers.length === 0) { - throw new Error('No users found in the same department.'); - } + if (!(await this.tcpCommunicator.connect())) continue - // Get department directory info - const departmentData = await this.applicationInfo.readValue('departmentDirectory'); - if (!departmentData || !departmentData.path || !departmentData.id) { - throw new Error('No department directory found.'); - } - - this.departmentDirectory = departmentData.path; - - // Read files from the MemoryManager related to this department - const departmentFiles = await this.memoryManager.retrieveMetaInformation(departmentData.id); - if (!departmentFiles || !departmentFiles.structure) { - throw new Error('No files found for this department in the memory manager.'); - } - - // Iterate over all department users and perform the operations - for (const user of departmentUsers) { - const userIp = user.ip; - this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort); - - if (!await this.tcpCommunicator.connect()) continue; - - // First clear the department directory; - if (await this.clearDepartmentDirectory(userName)) { - await this.sendFilesToUser(departmentFiles.structure, userName); - } - - await this.tcpCommunicator.disconnect(); - } - } - catch(error: any) { - this.log(error.message, 'error'); - } - finally{ - this.log('Department sharing completed.'); - this.isBusy = false; - } - } - - // Clear the department directory for a user - private async clearDepartmentDirectory(userName: string): Promise { - if(!this.tcpCommunicator) return false; - - if (!await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, {userName: userName})) return false; - const response = await this.waitForResponse(); - - if (!response || response.operationCode !== operationCodes.OK){ - this.log('Failed to clear the department directory.', 'error'); - return false; + // First clear the department directory; + if (await this.clearDepartmentDirectory(userName)) { + await this.sendFilesToUser(departmentStructure.structure, userName) } - return true; + await this.tcpCommunicator.disconnect() + } + } catch (error: any) { + this.log(error.message, 'error') + } finally { + this.log('Department sharing completed.') + this.isBusy = false + } + } + + // Clear the department directory for a user + private async clearDepartmentDirectory(userName: string): Promise { + if (!this.tcpCommunicator) return false + + if ( + !(await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, { + userName: userName, + })) + ) + return false + const response = await this.waitForResponse() + + if (!response || response.operationCode !== operationCodes.OK) { + this.log('Failed to clear the department directory.', 'error') + return false } - // Send the files to a user in the department - private async sendFilesToUser(files: { [key: string]: string }, userName: string): Promise { - if(!this.tcpCommunicator) return; - const unsentFiles = Object.keys(files); + return true + } - console.log(`\n\n${unsentFiles}\n\n`); + // Send the files to a user in the department + private async sendFilesToUser(files: { [key: string]: string }, userName: string): Promise { + if (!this.tcpCommunicator) return + const unsentFiles = Object.keys(files) - for (const fileName of unsentFiles) { - const filePath = files[fileName]; + console.log(`\n\n${unsentFiles}\n\n`) - // Ensure the file exists before attempting to send - if (!fs.existsSync(filePath)) { - this.log(`File not found: ${filePath}`, 'error'); - continue; - } + for (const fileName of unsentFiles) { + const filePath = files[fileName] - // Read the file content - const fileContent = fs.readFileSync(filePath); + // Ensure the file exists before attempting to send + if (!fs.existsSync(filePath)) { + this.log(`File not found: ${filePath}`, 'error') + continue + } - // Get the relative path of the file (used in the meta info) - if (!this.departmentDirectory) return; - const relativeFilePath = path.relative(this.departmentDirectory, filePath); + // Read the file content + const fileContent = fs.readFileSync(filePath) - // Prepare the metaInfo (same structure as FileSharer) - const metaInfo = { - userName, - relativeFilePath - }; + // Get the relative path of the file (used in the meta info) + if (!this.departmentDirectory) return + const relativeFilePath = path.relative(this.departmentDirectory, filePath) - // Send the file - if (!await this.tcpCommunicator.sendMessage(operationCodes.DEPARTMENT_FILE, metaInfo, Buffer.from(fileContent))) return; + // Prepare the metaInfo (same structure as FileSharer) + const metaInfo = { + userName, + relativeFilePath, + } - console.log(`\n\nSending file: ${fileName} to ${userName}\n\n`); + // Send the file + if ( + !(await this.tcpCommunicator.sendMessage( + operationCodes.DEPARTMENT_FILE, + metaInfo, + Buffer.from(fileContent), + )) + ) + return - const response = await this.waitForResponse(); - if (!response || response.operationCode !== operationCodes.OK) { - this.log(`Failed to send file: ${fileName}`, 'error'); - return; - } + console.log(`\n\nSending file: ${fileName} to ${userName}\n\n`) - this.log(`File sent successfully: ${fileName} to ${userName}`); + const response = await this.waitForResponse() + if (!response || response.operationCode !== operationCodes.OK) { + this.log(`Failed to send file: ${fileName}`, 'error') + return + } - unsentFiles.splice(unsentFiles.indexOf(fileName), 1); - await this.tcpCommunicator.disconnect(); - } - } - - async stop(): Promise { - this.stopRequested = true; // Signal that stop is requested + this.log(`File sent successfully: ${fileName} to ${userName}`) - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - } + unsentFiles.splice(unsentFiles.indexOf(fileName), 1) + await this.tcpCommunicator.disconnect() + } + } - // Wait for any ongoing process to complete if busy - while (this.isBusy) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } + async stop(): Promise { + this.stopRequested = true // Signal that stop is requested - console.log("[BackupManager] Stopped successfully."); + if (this.intervalId) { + clearInterval(this.intervalId) + this.intervalId = null } - private async waitForResponse(): Promise { - return new Promise((resolve) => { - const idResponseCheck = setInterval(async () => { - if (!this.tcpCommunicator) return null; - if (this.tcpCommunicator.hasResponseArrived()) { - clearInterval(idResponseCheck); - resolve(this.tcpCommunicator.getLastResult()); - } - }, 100); - }); + // Wait for any ongoing process to complete if busy + while (this.isBusy) { + await new Promise((resolve) => setTimeout(resolve, 100)) } - // Unified logging function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[DepartmentSharer]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); - } else { - console.log(`${prefix} ${message}`); + console.log('[BackupManager] Stopped successfully.') + } + + private async waitForResponse(): Promise { + return new Promise((resolve) => { + const idResponseCheck = setInterval(async () => { + if (!this.tcpCommunicator) return null + if (this.tcpCommunicator.hasResponseArrived()) { + clearInterval(idResponseCheck) + resolve(this.tcpCommunicator.getLastResult()) } + }, 100) + }) + } + + // Unified logging function + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[DepartmentSharer]' + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) } + } } diff --git a/User/src/helpers/directory_watcher.ts b/User/src/helpers/directory_watcher.ts index 7549485..ed905c7 100644 --- a/User/src/helpers/directory_watcher.ts +++ b/User/src/helpers/directory_watcher.ts @@ -1,163 +1,134 @@ -import { promises as fs, watch, FSWatcher } from 'fs'; -import path from 'path'; -import { MemoryManager } from './memory_manager'; -import { JsonManager } from './json_manager'; +import { promises as fs, watch, FSWatcher } from 'fs' +import path from 'path' +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' export class DirectoryWatcher { - private directoryPath: string; - private directoryMemoryId: string; - private directoryScheme: any; - private applicationInfo: JsonManager; - private memoryManager: MemoryManager; - private readonly sourceKey: string; - private directoryWatcher: FSWatcher | null; - private totalSize: number; - private isBusy: boolean; + private readonly watchers: Map // Stores watchers with directory ID as the key + private readonly db: JsonDatabase + private totalSizes: Map // Stores total sizes per directory ID - constructor(memoryManagerPath: string, applicationInfoPath: string, sourceKey: string) { - this.sourceKey = sourceKey; - this.applicationInfo = new JsonManager(applicationInfoPath); - this.memoryManager = new MemoryManager(memoryManagerPath); - this.directoryMemoryId = ''; - this.directoryPath = ''; - this.directoryWatcher = null; - this.totalSize = 0; - this.isBusy = false; + constructor(pathToDatabaseFile: string) { + this.db = new JsonDatabase(pathToDatabaseFile) + this.watchers = new Map() + this.totalSizes = new Map() + } + + // Start the watcher and register all directories + async start(): Promise { + const dbData = await this.db.read() + const directorySchemes = dbData.local_resources?.directory_schemes + + if (!directorySchemes) { + this.log('Error: directory_schemes not found in database.', 'error') + return } - // Start the watcher with a busy flag to prevent overlapping operations - async start(): Promise { - setInterval(async () => { - if (!this.isBusy) { - this.isBusy = true; - const initialized = await this.initialize(); - if (initialized) this.log('Directory watcher started successfully.'); - this.isBusy = false; - } - }, 10000); // 10-second interval for testing + // Initialize watchers for each directory + this.registerWatcher(directorySchemes.backup?.id, directorySchemes.backup?.path) + this.registerWatcher(directorySchemes.department?.id, directorySchemes.department?.path) + this.registerWatcher(directorySchemes.shared?.id, directorySchemes.shared?.path) + + this.log('Directory watcher started successfully.') + } + + // Register a watcher for a directory with a given ID + private registerWatcher(id: string | undefined, directoryPath: string | undefined): void { + if (!id || !directoryPath) { + this.log(`Skipping watcher: ID or path is missing.`, 'error') + return } - // Method to initialize and validate the backup directory - async initialize(): Promise { - const directoryData = await this.applicationInfo.readValue(this.sourceKey); - if (!directoryData) { - this.log('Directory data not found in application info.', 'error'); - return false; - } - - this.directoryPath = directoryData.path; - this.directoryMemoryId = directoryData.id; - - if (!this.directoryMemoryId || !this.directoryPath) { - this.log('Components of entry in \'DirectoryWatcher\' not found.', 'error'); - await this.applicationInfo.removeValue(this.sourceKey); - return false; - } - - if (!this.directoryScheme || Object.keys(this.directoryScheme).length === 0) { - // No structure in memory, scan and save it - const result = await this.buildDirectoryScheme(this.directoryPath); - this.directoryScheme = result.structure; - this.totalSize = result.size; - - await this.memoryManager.updateMetaInformation(this.directoryMemoryId, { - structure: this.directoryScheme, - totalSize: this.totalSize, - }); - } - - // Start watching the directory (after stopping any existing watcher) - this.restartWatcher(); - return true; + if (this.watchers.has(id)) { + this.log(`Watcher for ID ${id} is already running.`, 'error') + return } - // Recursively build the directory structure and calculate the total size - private async buildDirectoryScheme(dirPath: string): Promise<{ structure: any, size: number }> { - const directoryScheme: any = {}; - let totalSize = 0; + // Create and store a new watcher + const watcher = watch(directoryPath, { recursive: true }, async (eventType, filename) => { + if (filename) { + this.log(`File change detected in ${directoryPath}: ${eventType} - ${filename}`) + await this.handleDirectoryChange(id, directoryPath) + } + }) - const items = await fs.readdir(dirPath, { withFileTypes: true }); + this.watchers.set(id, watcher) + this.log(`Watching directory: ${directoryPath} (ID: ${id})`) + } - for (const item of items) { - const fullPath = path.join(dirPath, item.name); - const stats = await fs.stat(fullPath); + // Handle directory change and update the correct entry in the database + private async handleDirectoryChange(id: string, directoryPath: string): Promise { + const result = await this.buildDirectoryScheme(directoryPath) + this.totalSizes.set(id, result.size) - if (item.isDirectory()) { - // If it's a directory, recursively build its structure and accumulate size - const { structure, size } = await this.buildDirectoryScheme(fullPath); - directoryScheme[item.name] = structure; - totalSize += size; - } else if (item.isFile()) { - // If it's a file, store its full path and accumulate size - directoryScheme[item.name] = fullPath; - totalSize += stats.size; - } - } + await this.db.update((data) => { + // @ts-ignore + if (!data.local_resources || !data.local_resources.directory_schemes[id]) { + this.log(`Error: Directory scheme for ID ${id} not found in database.`, 'error') + return data + } - return { structure: directoryScheme, size: totalSize }; + // @ts-ignore + data.local_resources.directory_schemes[id].structure = result.structure + // @ts-ignore + data.local_resources.directory_schemes[id].totalSize = result.size + + return data + }) + + this.log(`Updated directory scheme for ID: ${id}`) + } + + // Recursively build the directory structure and calculate total size + private async buildDirectoryScheme(dirPath: string): Promise<{ structure: any; size: number }> { + const directoryScheme: any = {} + let totalSize = 0 + + const items = await fs.readdir(dirPath, { withFileTypes: true }) + + for (const item of items) { + const fullPath = path.join(dirPath, item.name) + const stats = await fs.stat(fullPath) + + if (item.isDirectory()) { + const { structure, size } = await this.buildDirectoryScheme(fullPath) + directoryScheme[item.name] = structure + totalSize += size + } else if (item.isFile()) { + directoryScheme[item.name] = fullPath + totalSize += stats.size + } } - // Restart the directory watcher, ensuring any previous watcher is closed - private restartWatcher(): void { - if (this.directoryWatcher) { - this.log('Stopping existing watcher...'); - this.directoryWatcher.close(); - } + return { structure: directoryScheme, size: totalSize } + } - this.startDirectoryWatcher(); + // Stop and remove a watcher for a specific directory ID + public stopWatcher(id: string): void { + const watcher = this.watchers.get(id) + if (watcher) { + watcher.close() + this.watchers.delete(id) + this.log(`Stopped watcher for ID: ${id}`) } + } - // Start watching the backup directory for changes - private startDirectoryWatcher(): void { - if (!this.directoryPath) { - throw new Error('Backup directory not set. Cannot start watcher.'); - } - - this.directoryWatcher = watch(this.directoryPath, { recursive: true }, async (eventType, filename) => { - if (filename) { - this.log(`File change detected: ${eventType} - ${filename}`); - // Rebuild the directory scheme and update memory - const result = await this.buildDirectoryScheme(this.directoryPath); - this.directoryScheme = result.structure; - this.totalSize = result.size; - - await this.memoryManager.updateMetaInformation(this.directoryMemoryId, { - structure: this.directoryScheme, - totalSize: this.totalSize, - }); - - this.log('Directory structure and size updated in memory.'); - } - }); - - this.log(`Watching for changes in: ${this.directoryPath}`); + // Stop all watchers + public stopAllWatchers(): void { + for (const [id, watcher] of this.watchers) { + watcher.close() + this.log(`Stopped watcher for ID: ${id}`) } + this.watchers.clear() + } - // Close the directory watcher - public closeWatcher(): void { - if (this.directoryWatcher) { - this.log(`Stopping watcher for ${this.directoryPath}`); - this.directoryWatcher.close(); - this.directoryWatcher = null; - } - - if (global.gc) { - global.gc(); - } - } - - // Unified logging function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const sourcePrefix = `[DirectoryWatcher] {${this.capitalize(this.sourceKey)}}`; - if (level === 'error') { - console.error(`${sourcePrefix} ${message}`); - } else { - console.log(`${sourcePrefix} ${message}`); - } - } - - // Capitalize the first letter of the sourceKey - private capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1); + // Unified logging function + private log(message: string, level: 'log' | 'error' = 'log'): void { + const sourcePrefix = `[DirectoryWatcher]` + if (level === 'error') { + console.error(`${sourcePrefix} ${message}`) + } else { + console.log(`${sourcePrefix} ${message}`) } + } } diff --git a/User/src/helpers/file_encryptor.ts b/User/src/helpers/file_encryptor.ts index 20bc391..49e78da 100644 --- a/User/src/helpers/file_encryptor.ts +++ b/User/src/helpers/file_encryptor.ts @@ -1,55 +1,55 @@ -import fs from 'fs'; -import crypto from 'crypto'; +import fs from 'fs' +import crypto from 'crypto' export class FileEncryptor { - private readonly encryptionKey: Buffer; - private readonly iv: Buffer; + private readonly encryptionKey: Buffer + private readonly iv: Buffer - constructor(base64Key: string, base64Iv: string) { - // Decode the base64-encoded key and IV - this.encryptionKey = Buffer.from(base64Key, 'base64'); - this.iv = Buffer.from(base64Iv, 'base64'); + constructor(base64Key: string, base64Iv: string) { + // Decode the base64-encoded key and IV + this.encryptionKey = Buffer.from(base64Key, 'base64') + this.iv = Buffer.from(base64Iv, 'base64') + } + + // Method to read a file, encrypt it, and return the encrypted content as a base64 string + public encryptFileToBase64(filePath: string): string { + try { + // Read the file contents + const fileBuffer = fs.readFileSync(filePath) + + // Create the cipher using AES-256-CBC (or another algorithm you prefer) + const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, this.iv) + + // Encrypt the file data + let encryptedData = cipher.update(fileBuffer) + encryptedData = Buffer.concat([encryptedData, cipher.final()]) + + // Return the encrypted data as a base64 string + return encryptedData.toString('base64') + } catch (err) { + console.error(`Error encrypting file at path ${filePath}:`, err) + throw err } + } - // Method to read a file, encrypt it, and return the encrypted content as a base64 string - public encryptFileToBase64(filePath: string): string { - try { - // Read the file contents - const fileBuffer = fs.readFileSync(filePath); + // Method to decrypt base64-encoded encrypted content and return the decrypted buffer + public decryptBase64(encryptedBase64: string): Buffer { + try { + // Decode the base64-encoded encrypted data + const encryptedData = Buffer.from(encryptedBase64, 'base64') - // Create the cipher using AES-256-CBC (or another algorithm you prefer) - const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, this.iv); + // Create the decipher using AES-256-CBC + const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv) - // Encrypt the file data - let encryptedData = cipher.update(fileBuffer); - encryptedData = Buffer.concat([encryptedData, cipher.final()]); + // Decrypt the data + let decryptedData = decipher.update(encryptedData) + decryptedData = Buffer.concat([decryptedData, decipher.final()]) - // Return the encrypted data as a base64 string - return encryptedData.toString('base64'); - } catch (err) { - console.error(`Error encrypting file at path ${filePath}:`, err); - throw err; - } - } - - // Method to decrypt base64-encoded encrypted content and return the decrypted buffer - public decryptBase64(encryptedBase64: string): Buffer { - try { - // Decode the base64-encoded encrypted data - const encryptedData = Buffer.from(encryptedBase64, 'base64'); - - // Create the decipher using AES-256-CBC - const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv); - - // Decrypt the data - let decryptedData = decipher.update(encryptedData); - decryptedData = Buffer.concat([decryptedData, decipher.final()]); - - // Return the decrypted buffer - return decryptedData; - } catch (err) { - console.error('Error decrypting data:', err); - throw err; - } + // Return the decrypted buffer + return decryptedData + } catch (err) { + console.error('Error decrypting data:', err) + throw err } + } } diff --git a/User/src/helpers/file_sharer.ts b/User/src/helpers/file_sharer.ts index cada88f..b921ea6 100644 --- a/User/src/helpers/file_sharer.ts +++ b/User/src/helpers/file_sharer.ts @@ -1,146 +1,146 @@ -import fs from "fs"; -import path from "path"; -import { QueueManager } from './queue_manager'; -import { TcpCommunicator } from "./tcp_communicator"; -import { operationCodes } from '../network/operation_codes'; -import { compareFnFileItemTask, FileItemTask } from "../interfaces/file_item_task"; -import { ParsedMessage } from "../network/message_handler"; +import fs from 'fs' +import path from 'path' +import { TcpCommunicator } from './tcp_communicator' +import { operationCodes } from '../network/operation_codes' +import { ParsedMessage } from '../network/message_handler' +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' interface FileSendTask { - ip: string; - path: string; - userName: string; + ip: string + path: string + userName: string } export class FileSharer { - private queueManager: QueueManager; - private readonly clientPort: number; - private isBusy: boolean; - private tcpCommunicator: TcpCommunicator | null = null; - private stopRequested: boolean = false; - private intervalId: NodeJS.Timeout | null = null; + private readonly db: JsonDatabase + private readonly clientPort: number + private isBusy: boolean = false + private tcpCommunicator: TcpCommunicator | null = null + private stopRequested: boolean = false + private intervalId: NodeJS.Timeout | null = null - constructor(queueFilePath: string, clientPort: number) { - this.queueManager = new QueueManager(queueFilePath, compareFnFileItemTask); - this.clientPort = clientPort; - this.isBusy = false; // Initialize the busy flag - } + constructor(pathToDatabaseFile: string, clientPort: number) { + this.db = new JsonDatabase(pathToDatabaseFile) + this.clientPort = clientPort + } - // Start processing the file queue - async start(): Promise { - this.intervalId = setInterval(async () => { - if (!this.isBusy || !this.stopRequested) { // Check if the queue is already being processed - this.isBusy = true; // Set busy flag to true before starting - this.log("Start successfully. Processing the queue."); - await this.processQueue(); - } + // Start processing the file queue + async start(): Promise { + this.intervalId = setInterval(async () => { + if (!this.isBusy || !this.stopRequested) { + // Check if the queue is already being processed + this.isBusy = true // Set busy flag to true before starting + this.log('Start successfully. Processing the queue.') + await this.processQueue() + } - if (global.gc) { - global.gc(); - } - }, 10000); // 10 seconds interval - } + if (global.gc) { + global.gc() + } + }, 10000) + } - // Method to process the queue - private async processQueue(): Promise { - while (!this.queueManager.isEmpty()) { - const task = this.queueManager.peek(); - this.log('trimiti fisier'); + // Method to process the queue + private async processQueue(): Promise { + for (let i = 0; i < this.db.queueSize(); i++) { + const task = this.db.popQueue() + if (task) { + this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`) + const success = await this.sendFile(task) - if (task) { - this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`); - const success = await this.sendFile(task); - - if (!success) { - this.log(`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`, 'error'); - this.queueManager.dequeue(); - this.queueManager.enqueue(task); // Re-add to queue if failed - } else { - this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`); - this.queueManager.dequeue(); - } - } - } - this.isBusy = false; // Reset busy flag after the queue is processed - } - - // Method to send the file to a specific IP using TcpCommunicator - private async sendFile(task: FileSendTask): Promise { - const { ip, path: filePath, userName } = task; - - // Ensure the file exists before attempting to send - if (!fs.existsSync(filePath)) { - this.log(`File not found: ${filePath}`, 'error'); - return false; - } - - // Read the file contents - const fileContent = fs.readFileSync(filePath); - - // Extract the file name from the file path using path.basename - const fileName = path.basename(filePath); - - const metaInfo = { - userName, // Sender's username - relativeFilePath: fileName, // Use the file name instead of the full path - }; - - this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort); - - if (!await this.tcpCommunicator.connect()) { - this.log(`Failed to connect to IP: ${ip}`, 'error'); - return false; - } - - this.log(`Sending file: ${filePath} to IP: ${ip}`); - - if (!await this.tcpCommunicator.sendMessage(operationCodes.SHARE_FILE, metaInfo, fileContent)) return false; - - const response = await this.waitForResponse(); - if (!response || response.operationCode !== operationCodes.OK) { - this.log(`Failed to send file: ${filePath} to IP: ${ip}`, 'error'); - return false; - } - - await this.tcpCommunicator.disconnect(); - return true; - } - - async stop(): Promise { - this.stopRequested = true; // Signal that stop is requested - - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - } - - // Wait for any ongoing process to complete if busy - while (this.isBusy) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - - console.log("[BackupManager] Stopped successfully."); - } - - private async waitForResponse(): Promise { - return new Promise((resolve) => { - const idResponseCheck = setInterval(async () => { - if (!this.tcpCommunicator) return null; - if (this.tcpCommunicator.hasResponseArrived()) { - clearInterval(idResponseCheck); - resolve(this.tcpCommunicator.getLastResult()); // Resolve the response or null if not available - } - }, 100); // Check every 100 milliseconds if the response has arrived - }); - } - - // Unified logging function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[FileSharer]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); + if (!success) { + this.log( + `Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`, + 'error', + ) + this.db.pushQueue(task) } else { - console.log(`${prefix} ${message}`); + this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`) } + } } + this.isBusy = false // Reset busy flag after the queue is processed + } + + // Method to send the file to a specific IP using TcpCommunicator + private async sendFile(task: FileSendTask): Promise { + const { ip, path: filePath, userName } = task + + // Ensure the file exists before attempting to send + if (!fs.existsSync(filePath)) { + this.log(`File not found: ${filePath}`, 'error') + return false + } + + // Read the file contents + const fileContent = fs.readFileSync(filePath) + + // Extract the file name from the file path using path.basename + const fileName = path.basename(filePath) + + const metaInfo = { + userName, // Sender's username + relativeFilePath: fileName, // Use the file name instead of the full path + } + + this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort) + + if (!(await this.tcpCommunicator.connect())) { + this.log(`Failed to connect to IP: ${ip}`, 'error') + return false + } + + this.log(`Sending file: ${filePath} to IP: ${ip}`) + + if (!(await this.tcpCommunicator.sendMessage(operationCodes.SHARE_FILE, metaInfo, fileContent))) + return false + + const response = await this.waitForResponse() + if (!response || response.operationCode !== operationCodes.OK) { + this.log(`Failed to send file: ${filePath} to IP: ${ip}`, 'error') + return false + } + + await this.tcpCommunicator.disconnect() + return true + } + + async stop(): Promise { + this.stopRequested = true // Signal that stop is requested + + if (this.intervalId) { + clearInterval(this.intervalId) + this.intervalId = null + } + + // Wait for any ongoing process to complete if busy + while (this.isBusy) { + await new Promise((resolve) => setTimeout(resolve, 100)) + } + + console.log('[BackupManager] Stopped successfully.') + } + + private async waitForResponse(): Promise { + return new Promise((resolve) => { + const idResponseCheck = setInterval(async () => { + if (!this.tcpCommunicator) return null + if (this.tcpCommunicator.hasResponseArrived()) { + clearInterval(idResponseCheck) + resolve(this.tcpCommunicator.getLastResult()) // Resolve the response or null if not available + } + }, 100) // Check every 100 milliseconds if the response has arrived + }) + } + + // Unified logging function + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[FileSharer]' + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) + } + } } diff --git a/User/src/helpers/json_manager.ts b/User/src/helpers/json_manager.ts index 627acac..35c5ee3 100644 --- a/User/src/helpers/json_manager.ts +++ b/User/src/helpers/json_manager.ts @@ -1,119 +1,119 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'fs' +import path from 'path' export class JsonManager { - private readonly filePath: string; - private readonly lockFilePath: string; + private readonly filePath: string + private readonly lockFilePath: string - constructor(filePath: string) { - const dir = path.dirname(filePath); + constructor(filePath: string) { + const dir = path.dirname(filePath) - // Check if the directory exists, throw error if it doesn't - if (!fs.existsSync(dir)) { - throw new Error(`The directory does not exist: ${dir}`); - } - - this.filePath = filePath; - this.lockFilePath = `${filePath}.lock`; // Define the lock file path - - // If the file doesn't exist, create it - if (!fs.existsSync(filePath)) { - fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8'); - } + // Check if the directory exists, throw error if it doesn't + if (!fs.existsSync(dir)) { + throw new Error(`The directory does not exist: ${dir}`) } - // Method to acquire a lock (create .lock file) - private async acquireLock(): Promise { - while (fs.existsSync(this.lockFilePath)) { - // Wait until the lock file is released - await new Promise(resolve => setTimeout(resolve, 100)); // 100ms delay before retrying - } - // Create the lock file - fs.writeFileSync(this.lockFilePath, ''); + this.filePath = filePath + this.lockFilePath = `${filePath}.lock` // Define the lock file path + + // If the file doesn't exist, create it + if (!fs.existsSync(filePath)) { + fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8') } + } - // Method to release the lock (delete .lock file) - private releaseLock(): void { - if (fs.existsSync(this.lockFilePath)) { - fs.unlinkSync(this.lockFilePath); - } + // Method to acquire a lock (create .lock file) + private async acquireLock(): Promise { + while (fs.existsSync(this.lockFilePath)) { + // Wait until the lock file is released + await new Promise((resolve) => setTimeout(resolve, 100)) // 100ms delay before retrying } + // Create the lock file + fs.writeFileSync(this.lockFilePath, '') + } - // Read a value by key from the JSON file with a lock - public async readValue(key: string): Promise { - await this.acquireLock(); // Acquire the lock - - try { - if (!fs.existsSync(this.filePath)) return null; - - const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')); - return data[key] !== undefined ? data[key] : null; - } catch (err: any) { - console.error(`Error reading from JSON file: ${err.message}`); - return null; - } finally { - this.releaseLock(); // Always release the lock after the operation - } + // Method to release the lock (delete .lock file) + private releaseLock(): void { + if (fs.existsSync(this.lockFilePath)) { + fs.unlinkSync(this.lockFilePath) } + } - // Write a key-value pair to the JSON file with a lock - public async writeValue(key: string, value: any): Promise { - await this.acquireLock(); // Acquire the lock + // Read a value by key from the JSON file with a lock + public async readValue(key: string): Promise { + await this.acquireLock() // Acquire the lock - try { - let data: { [key: string]: any } = {}; + try { + if (!fs.existsSync(this.filePath)) return null - if (fs.existsSync(this.filePath)) { - data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')); - } - - // Update the key with the new value - data[key] = value; - - fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8'); - return true; - } catch (err: any) { - console.error(`Error writing to JSON file: ${err.message}`); - return false; - } finally { - this.releaseLock(); // Always release the lock after the operation - } + const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')) + return data[key] !== undefined ? data[key] : null + } catch (err: any) { + console.error(`Error reading from JSON file: ${err.message}`) + return null + } finally { + this.releaseLock() // Always release the lock after the operation } + } - // Remove a key-value pair from the JSON file with a lock - public async removeValue(key: string): Promise { - await this.acquireLock(); // Acquire the lock + // Write a key-value pair to the JSON file with a lock + public async writeValue(key: string, value: any): Promise { + await this.acquireLock() // Acquire the lock - try { - if (!fs.existsSync(this.filePath)) return false; + try { + let data: { [key: string]: any } = {} - const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')); - if (data[key] !== undefined) { - delete data[key]; - fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8'); - return true; - } - return false; - } catch (err: any) { - console.error(`Error removing key from JSON file: ${err.message}`); - return false; - } finally { - this.releaseLock(); // Always release the lock after the operation - } + if (fs.existsSync(this.filePath)) { + data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')) + } + + // Update the key with the new value + data[key] = value + + fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8') + return true + } catch (err: any) { + console.error(`Error writing to JSON file: ${err.message}`) + return false + } finally { + this.releaseLock() // Always release the lock after the operation } + } - // Reset the JSON file by clearing all data with a lock - public async resetFile(): Promise { - await this.acquireLock(); // Acquire the lock + // Remove a key-value pair from the JSON file with a lock + public async removeValue(key: string): Promise { + await this.acquireLock() // Acquire the lock - try { - fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8'); - return true; - } catch (err: any) { - console.error(`Error resetting JSON file: ${err.message}`); - return false; - } finally { - this.releaseLock(); // Always release the lock after the operation - } + try { + if (!fs.existsSync(this.filePath)) return false + + const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')) + if (data[key] !== undefined) { + delete data[key] + fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8') + return true + } + return false + } catch (err: any) { + console.error(`Error removing key from JSON file: ${err.message}`) + return false + } finally { + this.releaseLock() // Always release the lock after the operation } + } + + // Reset the JSON file by clearing all data with a lock + public async resetFile(): Promise { + await this.acquireLock() // Acquire the lock + + try { + fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8') + return true + } catch (err: any) { + console.error(`Error resetting JSON file: ${err.message}`) + return false + } finally { + this.releaseLock() // Always release the lock after the operation + } + } } diff --git a/User/src/helpers/memory_manager.ts b/User/src/helpers/memory_manager.ts deleted file mode 100644 index 93e93b2..0000000 --- a/User/src/helpers/memory_manager.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { JsonManager } from './json_manager'; - -export class MemoryManager extends JsonManager { - constructor(filePath: string) { - super(filePath); // Call the parent constructor to ensure file initialization - } - - // Generate a new unique GUID and ensure it doesn't already exist in the file - private generateUniqueGuid(): Promise { - const generate = async (): Promise => { - const guid = uuidv4(); - const value = await this.readValue(guid); - if (value === null) { - return guid; - } - return generate(); - }; - return generate(); - } - - // Store meta information with a unique GUID as the key - public async storeMetaInformation(metaInfo: any): Promise { - const guid = await this.generateUniqueGuid(); - const success = await this.writeValue(guid, metaInfo); - if (success) { - return guid; // Return the unique GUID for future reference - } else { - throw new Error('Failed to store meta information.'); - } - } - - // Retrieve meta information using the GUID - public retrieveMetaInformation(guid: string): Promise { - return this.readValue(guid); - } - - // Update meta information by merging new data into existing data - public async updateMetaInformation(guid: string, newMetaInfo: any): Promise { - return await this.writeValue(guid, newMetaInfo); - } - - // Remove meta information using the GUID - public removeMetaInformation(guid: string): Promise { - return this.removeValue(guid); - } -} diff --git a/User/src/helpers/network_scanner.ts b/User/src/helpers/network_scanner.ts index d1ce897..bcc6fa6 100644 --- a/User/src/helpers/network_scanner.ts +++ b/User/src/helpers/network_scanner.ts @@ -1,185 +1,216 @@ -import {JsonManager} from "./json_manager"; -import {UdpClient} from "../network/udp/udp_client"; -import {TcpCommunicator} from "./tcp_communicator"; -import {operationCodes} from "../network/operation_codes"; -import {ParsedMessage} from "../network/message_handler"; +import { UdpClient } from '../network/udp/udp_client' +import { TcpCommunicator } from './tcp_communicator' +import { operationCodes } from '../network/operation_codes' +import { ParsedMessage } from '../network/message_handler' +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' export class NetworkScanner { - private applicationInfo: JsonManager; - private userConfig: JsonManager; - private readonly udpPort: number; - private readonly tcpPort: number; - private readonly okPage: string; - private readonly errorPage: string; - private readonly databaseResetPage: string; - private appStarted = false; - private intervalIds: NodeJS.Timeout[] = []; + private db: JsonDatabase + private readonly udpPort: number + private readonly tcpPort: number + private readonly okPage: string + private readonly errorPage: string + private readonly databaseResetPage: string + private intervalIds: NodeJS.Timeout[] = [] - // Flags to prevent overlapping executions - private ucCheckBusy = false; - private ipLookupBusy = false; - private sendLoginBusy = false; + // Flags to prevent overlapping executions + private ucCheckBusy = false + private ipLookupBusy = false + private sendLoginBusy = false - constructor(applicationInfoPath: string, userConfigPath: string, udpPort: number, tcpPort: number, okPage: string, errorPage: string, databaseResetPage: string) { - this.applicationInfo = new JsonManager(applicationInfoPath); - this.userConfig = new JsonManager(userConfigPath); - this.udpPort = udpPort; - this.tcpPort = tcpPort; - this.okPage = okPage; - this.errorPage = errorPage; - this.databaseResetPage = databaseResetPage; + constructor( + pathToDatabaseFile: string, + udpPort: number, + tcpPort: number, + okPage: string, + errorPage: string, + databaseResetPage: string, + ) { + this.db = new JsonDatabase(pathToDatabaseFile) + this.udpPort = udpPort + this.tcpPort = tcpPort + this.okPage = okPage + this.errorPage = errorPage + this.databaseResetPage = databaseResetPage - // Start tasks - this.startUCCheck(); - this.startUserIPLookup(); - this.sendLoginRequest(); + // Start tasks + this.startUCCheck() + this.startUserIPLookup() + this.sendAccountCheckRequest() + } + + // Log helper function for consistent logging format + private log(message: string, level: 'log' | 'error' = 'log', methodName: string = ''): void { + const prefix = `[NetworkScanner${methodName ? `.${methodName}` : ''}]` + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) } + } - // Log helper function for consistent logging format - private log(message: string, level: 'log' | 'error' = 'log', methodName: string = ''): void { - const prefix = `[NetworkScanner${methodName ? `.${methodName}` : ''}]`; - if (level === 'error') { - console.error(`${prefix} ${message}`); + // UC Check Task + private startUCCheck(interval: number = 5000): void { + const intervalId = setInterval(async () => { + if (this.ucCheckBusy) return + this.ucCheckBusy = true + + try { + this.log('UC Check running...', 'log', 'startUCCheck') + const udpClient = new UdpClient(this.udpPort) + const aliveClients = await udpClient.getTargetClients(operationCodes.ARE_YOU_UC) + const data = await this.db.read() + const foundClient = aliveClients.length > 0 + const serverFound = data.app_config.server_found + + if (foundClient) { + const ipAddress = aliveClients[0] + + if (data.network.serverIp !== ipAddress) { + await this.db.update((data) => { + data.network.serverIp = ipAddress + return data + }) + process.send?.({ type: 'changeContent', page: this.okPage }) + } + + if (!serverFound) { + process.send?.({ type: 'changeContent', page: this.okPage }) + await this.db.update((data) => { + data.app_config.server_found = true + return data + }) + } } else { - console.log(`${prefix} ${message}`); + process.send?.({ type: 'changeContent', page: this.errorPage }) + await this.db.update((data) => { + data.app_config.server_found = false + return data + }) } - } + } catch (err) { + this.log(`Error checking UC: ${err}`, 'error', 'startUCCheck') + process.send?.({ type: 'changeContent', page: this.errorPage }) + } finally { + this.ucCheckBusy = false + } + }, interval) - // UC Check Task - private startUCCheck(interval: number = 5000): void { - const intervalId = setInterval(async () => { - if (this.ucCheckBusy) return; - this.ucCheckBusy = true; + this.intervalIds.push(intervalId) + } - try { - this.log('UC Check running...', 'log', 'startUCCheck'); - const udpClient = new UdpClient(this.udpPort); - const aliveClients = await udpClient.getTargetClients(operationCodes.ARE_YOU_UC); - const storedIp = await this.applicationInfo.readValue('serverIp'); - const foundClient = aliveClients.length > 0; + // IP Lookup Task + private startUserIPLookup(interval: number = 10000): void { + const intervalId = setInterval(async () => { + if (this.ipLookupBusy) return + this.ipLookupBusy = true - if (foundClient) { - const ipAddress = aliveClients[0]; // Use the first alive client + try { + this.log('IP Lookup running...', 'log', 'startUserIPLookup') + const data = await this.db.read() + const serverIp = data.network.serverIp + const udpClient = new UdpClient(this.udpPort) + const activeIPs = await udpClient.getTargetClients(operationCodes.ARE_YOU_HUMAN) + const filteredIPs = activeIPs.filter((ip) => ip !== serverIp) - if (!storedIp || storedIp !== ipAddress) { - await this.applicationInfo.writeValue('serverIp', ipAddress); - if (!this.appStarted) { - process.send?.({ type: 'changeContent', page: this.okPage }); - } - this.appStarted = true; - } else if (!this.appStarted) { - process.send?.({ type: 'changeContent', page: this.okPage }); - this.appStarted = true; - } - } else { - process.send?.({ type: 'changeContent', page: this.errorPage }); - } - } catch (err) { - this.log(`Error checking UC: ${err}`, 'error', 'startUCCheck'); - process.send?.({ type: 'changeContent', page: this.errorPage }); - } finally { - this.ucCheckBusy = false; - } - }, interval); + // Save the filtered IPs to 'users_ip' + await this.db.update((data) => { + data.network.usersInLan = filteredIPs.map((ip) => ({ + id: '', + ip, + name: '', + departmentId: '', + })) + return data + }) + } catch (err) { + this.log(`Error during user IP lookup: ${err}`, 'error', 'startUserIPLookup') + } finally { + this.ipLookupBusy = false + } + }, interval) - this.intervalIds.push(intervalId); - } + this.intervalIds.push(intervalId) + } - // IP Lookup Task - private startUserIPLookup(interval: number = 10000): void { - const intervalId = setInterval(async () => { - if (this.ipLookupBusy) return; - this.ipLookupBusy = true; + // Login Request Task + private sendAccountCheckRequest(interval: number = 5000): void { + const intervalId = setInterval(async () => { + if (this.sendLoginBusy) return + this.sendLoginBusy = true - try { - this.log('IP Lookup running...', 'log', 'startUserIPLookup'); - const serverIp = await this.applicationInfo.readValue('serverIp'); - const udpClient = new UdpClient(this.udpPort); - const activeIPs = await udpClient.getTargetClients(operationCodes.ARE_YOU_HUMAN); - const filteredIPs = activeIPs.filter(ip => ip !== serverIp); - - // Save the filtered IPs to 'users_ip' - await this.applicationInfo.writeValue('users_ip', filteredIPs); - } catch (err) { - this.log(`Error during user IP lookup: ${err}`, 'error', 'startUserIPLookup'); - } finally { - this.ipLookupBusy = false; - } - }, interval); - - this.intervalIds.push(intervalId); - } - - // Login Request Task - private sendLoginRequest(interval: number = 5000): void { - const intervalId = setInterval(async () => { - if (this.sendLoginBusy || this.appStarted) return; - this.sendLoginBusy = true; - - try { - const userInfo = await this.userConfig.readValue('user_info'); - if (!userInfo || !userInfo.email || !userInfo.password) { - this.log("Email or password not found in user config.", 'error', 'sendLoginRequest'); - return; - } - - const app_type = await this.userConfig.readValue('app_type'); - const email = userInfo.email; - const password = userInfo.password; - const serverIp = await this.applicationInfo.readValue('serverIp'); - - if (!serverIp) { - this.log("Server IP not found in application info.", 'error', 'sendLoginRequest'); - return; - } - - const tcpCommunicator = new TcpCommunicator(serverIp, this.tcpPort); - - if (!await tcpCommunicator.connect()) { - this.log("Failed to connect to the server.", 'error', 'sendLoginRequest'); - return; - } - - const metaInfo = { email, password, app_type }; - if (!await tcpCommunicator.sendMessage(operationCodes.LOGIN, metaInfo)) { - this.log("Failed to send login request.", 'error', 'sendLoginRequest'); - await tcpCommunicator.disconnect(); - return; - } - - const response = await this.waitForResponse(tcpCommunicator); - if (response?.operationCode !== operationCodes.OK) { - await this.userConfig.resetFile(); - await this.userConfig.writeValue('app_type', app_type); - process.send?.({ type: 'changeContent', page: this.databaseResetPage }); - } - } catch (err) { - this.log(`Error during login request: ${err}`, 'error', 'sendLoginRequest'); - } finally { - this.sendLoginBusy = false; - } - }, interval); - - this.intervalIds.push(intervalId); - } - - // Helper function to wait for a response from the TCP communicator - private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise { - return new Promise((resolve) => { - const checkInterval = setInterval(() => { - if (tcpCommunicator.hasResponseArrived()) { - clearInterval(checkInterval); - resolve(tcpCommunicator.getLastResult()); - } - }, 100); - }); - } - - // Method to stop all intervals (for cleanup if needed) - public stopAllIntervals(): void { - for (const id of this.intervalIds) { - clearInterval(id); + try { + const data = await this.db.read() + const loggedIn = data.app_config.logged_in + if (!loggedIn) { + this.log('User is not logged in.', 'log', 'sendAccountCheckRequest') + return } - this.log("All intervals have been stopped.", 'log', 'stopAllIntervals'); + + const userInfo = data.app_config.user_info + if (!userInfo || !userInfo.email) { + this.log( + 'Email or password not found in user config.', + 'error', + 'sendAccountCheckRequest', + ) + return + } + + const email = userInfo.email + const serverIp = data.network.serverIp + + if (!serverIp) { + this.log('Server IP not found in application info.', 'error', 'sendAccountCheckRequest') + return + } + + const tcpCommunicator = new TcpCommunicator(serverIp, this.tcpPort) + + if (!(await tcpCommunicator.connect())) { + this.log('Failed to connect to the server.', 'error', 'sendAccountCheckRequest') + return + } + + const metaInfo = { email } + if (!(await tcpCommunicator.sendMessage(operationCodes.EMAIL_VERIFICATION, metaInfo))) { + this.log('Failed to send login request.', 'error', 'sendAccountCheckRequest') + await tcpCommunicator.disconnect() + return + } + + const response = await this.waitForResponse(tcpCommunicator) + if (response?.operationCode !== operationCodes.OK) { + process.send?.({ type: 'changeContent', page: this.databaseResetPage }) + } + } catch (err) { + this.log(`Error during login request: ${err}`, 'error', 'sendAccountCheckRequest') + } finally { + this.sendLoginBusy = false + } + }, interval) + + this.intervalIds.push(intervalId) + } + + // Helper function to wait for a response from the TCP communicator + private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise { + return new Promise((resolve) => { + const checkInterval = setInterval(() => { + if (tcpCommunicator.hasResponseArrived()) { + clearInterval(checkInterval) + resolve(tcpCommunicator.getLastResult()) + } + }, 100) + }) + } + + // Method to stop all intervals (for cleanup if needed) + public stopAllIntervals(): void { + for (const id of this.intervalIds) { + clearInterval(id) } -} \ No newline at end of file + this.log('All intervals have been stopped.', 'log', 'stopAllIntervals') + } +} diff --git a/User/src/helpers/queue_manager.ts b/User/src/helpers/queue_manager.ts deleted file mode 100644 index cc3aeb0..0000000 --- a/User/src/helpers/queue_manager.ts +++ /dev/null @@ -1,137 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -export class QueueManager { - private readonly filePath: string; - private readonly lockFilePath: string; - private queue: T[]; - private readonly compareFn: (a: T, b: T) => boolean; // Comparison function - - constructor(filePath: string, compareFn: (a: T, b: T) => boolean) { - this.filePath = filePath; - this.lockFilePath = `${filePath}.lock`; // Define the lock file path - this.queue = []; - this.compareFn = compareFn; - - const dir = path.dirname(filePath); - - // Check if the directory exists, throw error if it doesn't - if (!fs.existsSync(dir)) { - throw new Error(`The directory does not exist: ${dir}`); - } - - // If the file doesn't exist, create it - if (!fs.existsSync(filePath)) { - fs.writeFileSync(filePath, JSON.stringify([], null, 2), 'utf8'); - } - } - - // Method to acquire a lock (create .lock file) - private acquireLock(): void { - while (fs.existsSync(this.lockFilePath)) { - // Wait until the lock file is released - this.sleepSync(100); // 100ms delay before retrying - } - // Create the lock file - fs.writeFileSync(this.lockFilePath, ''); - } - - // Sleep function to simulate delay for locking mechanism - private sleepSync(ms: number): void { - const start = Date.now(); - while (Date.now() - start < ms) { - // busy wait - } - } - - // Method to release the lock (delete .lock file) - private releaseLock(): void { - if (fs.existsSync(this.lockFilePath)) { - fs.unlinkSync(this.lockFilePath); - } - } - - // Load the queue from the JSON file - loadQueue(): void { - this.acquireLock(); // Acquire the lock - - try { - const fileData = fs.readFileSync(this.filePath, 'utf8'); - this.queue = JSON.parse(fileData) || []; - } catch (err) { - // If the file doesn't exist or is invalid, start with an empty queue - this.queue = []; - } finally { - this.releaseLock(); // Release the lock - } - } - - // Save the queue back to the JSON file - saveQueue(): void { - this.acquireLock(); // Acquire the lock - - try { - fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf8'); - } finally { - this.releaseLock(); // Release the lock - } - } - - // Enqueue: Add an item to the end of the queue if it doesn't already exist - enqueue(item: T): void { - this.loadQueue(); // Ensure we load the latest queue - - // Check if the item already exists in the queue - const exists = this.queue.some(existingItem => this.compareFn(existingItem, item)); - - console.log(this.queue); - - if (!exists) { - this.queue.push(item); - this.saveQueue(); // Save the updated queue - } else { - console.log('Item already exists in the queue. Skipping enqueue.'); - } - } - - // Dequeue: Remove an item from the front of the queue - dequeue(): T | null { - this.loadQueue(); // Ensure we load the latest queue - if (this.queue.length === 0) { - return null; // Queue is empty - } - const item = this.queue.shift() as T; // Remove the first item - this.saveQueue(); // Save the updated queue - return item; - } - - // Peek: Get the item at the front of the queue without removing it - peek(): T | null { - this.loadQueue(); // Ensure we load the latest queue - return this.queue.length > 0 ? this.queue[0] : null; - } - - // Check if the queue is empty - isEmpty(): boolean { - this.loadQueue(); // Ensure we load the latest queue - return this.queue.length === 0; - } - - // Get the length of the queue - length(): number { - this.loadQueue(); // Ensure we load the latest queue - return this.queue.length; - } - - // Clear the entire queue - clearQueue(): void { - this.acquireLock(); // Acquire the lock - - try { - this.queue = []; // Clear the queue - fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf8'); - } finally { - this.releaseLock(); // Release the lock - } - } -} diff --git a/User/src/helpers/tcp_communicator.ts b/User/src/helpers/tcp_communicator.ts index 648b71a..45e555b 100644 --- a/User/src/helpers/tcp_communicator.ts +++ b/User/src/helpers/tcp_communicator.ts @@ -1,86 +1,90 @@ -import { TcpClient } from "../network/tcp/tcp_client"; -import { ParsedMessage } from "../network/message_handler"; +import { TcpClient } from '../network/tcp/tcp_client' +import { ParsedMessage } from '../network/message_handler' export class TcpCommunicator { - private readonly ip: string; - private readonly port: number; - private tcpClient: TcpClient | null = null; - private lastResult: ParsedMessage | null = null; + private readonly ip: string + private readonly port: number + private tcpClient: TcpClient | null = null + private lastResult: ParsedMessage | null = null - constructor(ip: string, port: number) { - this.ip = ip; - this.port = port; + constructor(ip: string, port: number) { + this.ip = ip + this.port = port + } + + async connect(): Promise { + this.tcpClient = new TcpClient(this.port) + this.tcpClient.openSocket(this.ip) + return this.tcpClient.isSocketConnected() + } + + async sendMessage( + operationCode: string, + metaInfo?: { [key: string]: any }, + fileContent?: Buffer, + ): Promise { + if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return false + + // Wait until the AES key is set before sending the message + return new Promise((resolve) => { + const idWaitForAes = setInterval(async () => { + if (this.tcpClient?.isAesKeySet()) { + clearInterval(idWaitForAes) + + // Send the message once AES key is set + const status = await this.tcpClient!.sendMessage(operationCode, metaInfo, fileContent) + + if (status) { + await this.waitForResponse() + } + + resolve(status) + } + }, 100) + }) + } + + getLastResult(): ParsedMessage | null { + const message = this.lastResult + this.lastResult = null + + if (global.gc) { + global.gc() } - async connect(): Promise { - this.tcpClient = new TcpClient(this.port); - this.tcpClient.openSocket(this.ip); - return this.tcpClient.isSocketConnected(); - } + return message + } - async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { - if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return false; + hasResponseArrived(): boolean { + if (!this.tcpClient) return false + return this.lastResult !== null + } - // Wait until the AES key is set before sending the message - return new Promise((resolve) => { - const idWaitForAes = setInterval(async () => { - if (this.tcpClient?.isAesKeySet()) { - clearInterval(idWaitForAes); + private waitForResponse(): Promise { + return new Promise((resolve, reject) => { + if (!this.tcpClient) { + reject() + } - // Send the message once AES key is set - const status = await this.tcpClient!.sendMessage(operationCode, metaInfo, fileContent); - - if (status) { - await this.waitForResponse(); - } - - resolve(status); - } - }, 100); - }); - } - - getLastResult(): ParsedMessage | null { - const message = this.lastResult; - this.lastResult = null; - - if (global.gc) { - global.gc(); + // Start interval for waiting for the response + const responseInterval = setInterval(() => { + if (!this.tcpClient?.isSocketConnected()) { + clearInterval(responseInterval) + resolve() } - return message; - } + if (this.tcpClient?.isMessageReceived()) { + this.lastResult = this.tcpClient.getLastResult() + clearInterval(responseInterval) // Stop checking once we have a response + resolve() + } + }, 100) // Check every 100 milliseconds + }) + } - hasResponseArrived(): boolean { - if(!this.tcpClient) return false; - return this.lastResult !== null; - } - - private waitForResponse(): Promise { - return new Promise((resolve, reject) => { - if (!this.tcpClient) { - reject(); - } - - // Start interval for waiting for the response - const responseInterval = setInterval(() => { - if (!this.tcpClient?.isSocketConnected()) { - clearInterval(responseInterval); - resolve(); - } - - if (this.tcpClient?.isMessageReceived()) { - this.lastResult = this.tcpClient.getLastResult(); - clearInterval(responseInterval); // Stop checking once we have a response - resolve(); - } - }, 100); // Check every 100 milliseconds - }); - } - - async disconnect(): Promise { - if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true; - this.tcpClient.closeSocket(); - return true; - } + async disconnect(): Promise { + if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true + this.tcpClient.closeSocket() + return true + } } diff --git a/User/src/helpers/users_info_fetcher.ts b/User/src/helpers/users_info_fetcher.ts index 2852b04..e0c8973 100644 --- a/User/src/helpers/users_info_fetcher.ts +++ b/User/src/helpers/users_info_fetcher.ts @@ -1,122 +1,93 @@ -import { JsonManager } from "./json_manager"; -import { MemoryManager } from "./memory_manager"; -import { operationCodes } from "../network/operation_codes"; -import { TcpCommunicator } from "./tcp_communicator"; -import { ParsedMessage } from "../network/message_handler"; +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' +import { NetworkUserScheme } from '../database/schemes/network_scheme' +import { TcpCommunicator } from './tcp_communicator' +import { operationCodes } from '../network/operation_codes' +import { ParsedMessage } from '../network/message_handler' export class UsersInfoFetcher { - private applicationInfo: JsonManager; - private memoryManager: MemoryManager; - private tcpCommunicator: TcpCommunicator | null = null; - private readonly clientPort: number; - private memoryId: string; - private readonly activeUsersKey: string; - private intervalId: NodeJS.Timeout | null = null; + private db: JsonDatabase + private tcpCommunicator: TcpCommunicator | null = null + private readonly clientPort: number + private intervalId: NodeJS.Timeout | null = null - constructor(applicationInfoPath: string, memoryManagerPath: string, clientPort: number) { - this.applicationInfo = new JsonManager(applicationInfoPath); - this.memoryManager = new MemoryManager(memoryManagerPath); - this.memoryId = ''; - this.clientPort = clientPort; - this.tcpCommunicator = null; - this.activeUsersKey = 'active_users_info'; - } + constructor(pathToDatabaseFile: string, clientPort: number) { + this.db = new JsonDatabase(pathToDatabaseFile) + this.clientPort = clientPort + this.tcpCommunicator = null + } - // Method to start checking user info periodically (every minute) - async start(): Promise { - this.intervalId = setInterval(async () => { - await this.initialize(); // Re-run every minute + // Start fetching user info periodically + async start(): Promise { + this.intervalId = setInterval(async () => { + const data = await this.db.read() + const usersIps = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip) - if (global.gc) { - global.gc(); - } - }, 5000); // 5-second interval for testing - } - - // Initialize and fetch user IPs and process users info - private async initialize() { - const usersIps = await this.applicationInfo.readValue('users_ip'); - if (!usersIps) { - this.log('No IP addresses found in users_ip', 'error'); - return; + for (const ip of usersIps) { + this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort) + if (!(await this.tcpCommunicator.connect())) { + this.log(`Failed to open connection for IP: ${ip}`, 'error') + continue } - // Ensure active_users_info exists in the memory - this.memoryId = await this.applicationInfo.readValue(this.activeUsersKey); - if (!this.memoryId) { - this.memoryId = await this.memoryManager.storeMetaInformation([]); - await this.applicationInfo.writeValue(this.activeUsersKey, this.memoryId); + if (!(await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION))) { + await this.tcpCommunicator.disconnect() + continue } - // Check user information - await this.checkUsersInfo(usersIps); - } + // Wait for the response + const response = await this.waitForResponse() - // Check user info from the list of IPs - private async checkUsersInfo(usersIps: string[]) { - let usersInfo: Array<{ ip: string, user_info: any }> = []; // Array to store IP and user_info objects + if (response && response.metaInfo) { + await this.db.update((dbData) => { + const userIndex = dbData.network.usersInLan.findIndex((user) => user.ip === ip) - for (const ip of usersIps) { - this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort); - if (!await this.tcpCommunicator.connect()) { - this.log(`Failed to open connection for IP: ${ip}`, 'error'); - continue; + if (userIndex !== -1) { + // @ts-ignore + dbData.network.usersInLan[userIndex].id = response.metaInfo.id + // @ts-ignore + dbData.network.usersInLan[userIndex].name = response.metaInfo.name + // @ts-ignore + dbData.network.usersInLan[userIndex].departmentId = response.metaInfo.departmentId + } else { + this.log(`User with IP ${ip} not found in the database.`, 'error') } - if(!await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION)){ - await this.tcpCommunicator.disconnect(); - continue; - } - - // Wait for the response for 10 seconds - const response = await this.waitForResponse(); - - // If a response is received and is successful, append it to usersInfo - if (response && response.metaInfo) { - usersInfo.push({ - ip: ip, - user_info: response.metaInfo - }); - } - - await this.tcpCommunicator.disconnect(); + return dbData + }) } - await this.updateActiveUsers(usersInfo); // Update active users information in the memory - } + await this.tcpCommunicator.disconnect() + } - private async waitForResponse(): Promise { - return new Promise((resolve) => { - const idResponseCheck = setInterval(async () => { - if (!this.tcpCommunicator) return null; - if (this.tcpCommunicator.hasResponseArrived()) { - clearInterval(idResponseCheck); - resolve(this.tcpCommunicator.getLastResult()); // Resolve the response or null if not available - } - }, 100); // Check every 100 milliseconds if the response has arrived - }); - } + if (global.gc) { + global.gc() + } + }, 5000) // 5-second interval for testing + } - // Update active users information in the memory - private async updateActiveUsers(userInfo: any[]) { - await this.memoryManager.updateMetaInformation(this.memoryId, userInfo); // Update active users in memory - } - - stop(): void { - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - console.log("[UsersInfoFetcher] Stopped successfully."); + private async waitForResponse(): Promise { + return new Promise((resolve) => { + const idResponseCheck = setInterval(async () => { + if (!this.tcpCommunicator) return null + if (this.tcpCommunicator.hasResponseArrived()) { + clearInterval(idResponseCheck) + resolve(this.tcpCommunicator.getLastResult()) } - } + }, 100) // Check every 100ms + }) + } - // Unified logging function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[UsersInfoFetcher]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); - } else { - console.log(`${prefix} ${message}`); - } + stop(): void { + if (this.intervalId) { + clearInterval(this.intervalId) + this.intervalId = null + this.log('Stopped successfully.') } + } + + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[UsersInfoFetcher]' + level === 'error' ? console.error(`${prefix} ${message}`) : console.log(`${prefix} ${message}`) + } } diff --git a/User/src/helpers/window_manager.ts b/User/src/helpers/window_manager.ts index 97abfc3..64a65f3 100644 --- a/User/src/helpers/window_manager.ts +++ b/User/src/helpers/window_manager.ts @@ -1,149 +1,149 @@ -import { BrowserWindow, dialog, shell } from 'electron'; -import fs from 'fs'; -import path from 'path'; +import { BrowserWindow, dialog, shell } from 'electron' +import fs from 'fs' +import path from 'path' export class WindowManager { - private readonly mainWindow: BrowserWindow; - private readonly pathToPagesDir: string; - private announcementWindow: BrowserWindow | null = null; + private readonly mainWindow: BrowserWindow + private readonly pathToPagesDir: string + private announcementWindow: BrowserWindow | null = null - constructor(mainWindow: BrowserWindow, pathToPagesDir: string) { - this.pathToPagesDir = pathToPagesDir; - this.mainWindow = mainWindow; - this.log('WindowManager initialized.'); + constructor(mainWindow: BrowserWindow, pathToPagesDir: string) { + this.pathToPagesDir = pathToPagesDir + this.mainWindow = mainWindow + this.log('WindowManager initialized.') + } + + // Logging helper function + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[WindowManager]' + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) + } + } + + // Show an alert dialog + async showAlert(message: string): Promise { + if (this.mainWindow) { + await dialog.showMessageBox(this.mainWindow, { + type: 'info', + title: 'Alert', + message: message, + buttons: ['OK'], + }) + this.log(`Alert displayed with message: "${message}"`) + } else { + this.log('Main window is not available.', 'error') + } + } + + // Change the content of the current window to load a new HTML file + async changeContent(destination: string): Promise { + if (this.mainWindow) { + try { + const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`) + this.log(`Navigating to: ${destinationPath}`) + + // Load the destination HTML file into the main window + await this.mainWindow.loadFile(destinationPath) + this.log(`Navigated to ${destination}`) + } catch (error) { + this.log(`Error changing content: ${error}`, 'error') + throw error // Pass the error back to the render process + } + } else { + this.log('Main window is not available.', 'error') + } + } + + // New method to select a directory + async selectDirectory(): Promise { + const result = await dialog.showOpenDialog(this.mainWindow, { + properties: ['openDirectory'], // Only allow selecting directories + }) + + if (result.filePaths && result.filePaths.length > 0) { + this.log(`Directory selected: ${result.filePaths[0]}`) + return result.filePaths[0] // Return the selected directory path + } else { + this.log('No directory selected.') + return undefined // Return undefined if no directory was selected + } + } + + // Show a file in the explorer + async showFileInExplorer(filePath: string): Promise { + if (filePath && fs.existsSync(filePath)) { + try { + shell.showItemInFolder(filePath) + this.log(`Opened file explorer for: ${filePath}`) + } catch (error: any) { + this.log(`Error showing file in explorer: ${error.message}`, 'error') + } + } else { + this.log('File path is undefined or does not exist.', 'error') + } + } + + // New method to open the file explorer and choose a file + async selectFile(): Promise { + const result = await dialog.showOpenDialog(this.mainWindow, { + properties: ['openFile'], // Allow selecting a file + filters: [ + { name: 'All Files', extensions: ['*'] }, // Optionally filter for specific file types + ], + }) + + if (result.filePaths && result.filePaths.length > 0) { + this.log(`File selected: ${result.filePaths[0]}`) + return result.filePaths[0] // Return the selected file path + } else { + this.log('No file selected.') + return undefined // Return undefined if no file was selected + } + } + + // Method to display an announcement in a new window + async displayAnnouncement(): Promise { + if (this.announcementWindow) { + this.announcementWindow.focus() + this.log('Announcement window focused.') + return } - // Logging helper function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[WindowManager]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); - } else { - console.log(`${prefix} ${message}`); - } - } - - // Show an alert dialog - async showAlert(message: string): Promise { - if (this.mainWindow) { - await dialog.showMessageBox(this.mainWindow, { - type: 'info', - title: 'Alert', - message: message, - buttons: ['OK'], - }); - this.log(`Alert displayed with message: "${message}"`); - } else { - this.log('Main window is not available.', 'error'); - } - } - - // Change the content of the current window to load a new HTML file - async changeContent(destination: string): Promise { - if (this.mainWindow) { - try { - const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`); - this.log(`Navigating to: ${destinationPath}`); - - // Load the destination HTML file into the main window - await this.mainWindow.loadFile(destinationPath); - this.log(`Navigated to ${destination}`); - } catch (error) { - this.log(`Error changing content: ${error}`, 'error'); - throw error; // Pass the error back to the render process - } - } else { - this.log('Main window is not available.', 'error'); - } - } - - // New method to select a directory - async selectDirectory(): Promise { - const result = await dialog.showOpenDialog(this.mainWindow, { - properties: ['openDirectory'], // Only allow selecting directories - }); - - if (result.filePaths && result.filePaths.length > 0) { - this.log(`Directory selected: ${result.filePaths[0]}`); - return result.filePaths[0]; // Return the selected directory path - } else { - this.log('No directory selected.'); - return undefined; // Return undefined if no directory was selected - } - } - - // Show a file in the explorer - async showFileInExplorer(filePath: string): Promise { - if (filePath && fs.existsSync(filePath)) { - try { - shell.showItemInFolder(filePath); - this.log(`Opened file explorer for: ${filePath}`); - } catch (error: any) { - this.log(`Error showing file in explorer: ${error.message}`, 'error'); - } - } else { - this.log('File path is undefined or does not exist.', 'error'); - } - } - - // New method to open the file explorer and choose a file - async selectFile(): Promise { - const result = await dialog.showOpenDialog(this.mainWindow, { - properties: ['openFile'], // Allow selecting a file - filters: [ - { name: 'All Files', extensions: ['*'] } // Optionally filter for specific file types - ] - }); - - if (result.filePaths && result.filePaths.length > 0) { - this.log(`File selected: ${result.filePaths[0]}`); - return result.filePaths[0]; // Return the selected file path - } else { - this.log('No file selected.'); - return undefined; // Return undefined if no file was selected - } - } - - // Method to display an announcement in a new window - async displayAnnouncement(): Promise { - if (this.announcementWindow) { - this.announcementWindow.focus(); - this.log('Announcement window focused.'); - return; - } - - const mainScreen = require('electron').screen.getPrimaryDisplay(); - const { width, height } = mainScreen.size; - - this.announcementWindow = new BrowserWindow({ - width: width / 3, - height: height / 2, - resizable: false, - title: 'Announcement', - webPreferences: { - preload: path.join(__dirname, '..', 'main', 'preload.js'), - contextIsolation: true, - nodeIntegration: false, - }, - }); - - this.announcementWindow.removeMenu(); - const announcementPath = path.join(this.pathToPagesDir, 'announcement.html'); - await this.announcementWindow.loadFile(announcementPath); - - this.log(`Announcement window opened at: ${announcementPath}`); - - // Handle window close - this.announcementWindow.on('closed', () => { - this.announcementWindow = null; - this.log('Announcement window closed.'); - }); - } - - async closeAnnouncementWindow(): Promise { - if (this.announcementWindow) { - this.announcementWindow.close(); - this.log('Announcement window closed by user.'); - } + const mainScreen = require('electron').screen.getPrimaryDisplay() + const { width, height } = mainScreen.size + + this.announcementWindow = new BrowserWindow({ + width: width / 3, + height: height / 2, + resizable: false, + title: 'Announcement', + webPreferences: { + preload: path.join(__dirname, '..', 'main', 'preload.js'), + contextIsolation: true, + nodeIntegration: false, + }, + }) + + this.announcementWindow.removeMenu() + const announcementPath = path.join(this.pathToPagesDir, 'announcement.html') + await this.announcementWindow.loadFile(announcementPath) + + this.log(`Announcement window opened at: ${announcementPath}`) + + // Handle window close + this.announcementWindow.on('closed', () => { + this.announcementWindow = null + this.log('Announcement window closed.') + }) + } + + async closeAnnouncementWindow(): Promise { + if (this.announcementWindow) { + this.announcementWindow.close() + this.log('Announcement window closed by user.') } + } } diff --git a/User/src/helpers/worker_manager.ts b/User/src/helpers/worker_manager.ts index c4980b6..12977f5 100644 --- a/User/src/helpers/worker_manager.ts +++ b/User/src/helpers/worker_manager.ts @@ -1,121 +1,131 @@ -import { fork, ChildProcess } from 'child_process'; -import path from 'path'; -import { WindowManager } from "./window_manager"; +import { fork, ChildProcess } from 'child_process' +import path from 'path' +import { WindowManager } from './window_manager' export class WorkerManager { - private readonly pathToWorkerDir: string; - private windowManager: WindowManager; - private workers: ChildProcess[]; - private cleanupInProgress: boolean = false; + private readonly pathToWorkerDir: string + private windowManager: WindowManager + private workers: ChildProcess[] + private cleanupInProgress: boolean = false - constructor(pathToWorkerDir: string, windowManager: WindowManager) { - this.pathToWorkerDir = pathToWorkerDir; - this.windowManager = windowManager; - this.workers = []; // Initialize the array to store child processes - } + constructor(pathToWorkerDir: string, windowManager: WindowManager) { + this.pathToWorkerDir = pathToWorkerDir + this.windowManager = windowManager + this.workers = [] // Initialize the array to store child processes + } - async startNetworkScannerWorker(udpPort: number, tcpPort: number, okPage: string, errorPage: string, databaseResetPage: string, userConfigPath: string, applicationInfoPath: string): Promise { - return this.startForkedWorker('network_scanner_worker.js', { - UDP_PORT: udpPort.toString(), - TCP_PORT: tcpPort.toString(), - OK_PAGE: okPage, - ERROR_PAGE: errorPage, - DATABASE_RESET_PAGE: databaseResetPage, - USER_CONFIG_PATH: userConfigPath, - APPLICATION_INFO_PATH: applicationInfoPath - }); - } + async startNetworkScannerWorker( + udpPort: number, + tcpPort: number, + okPage: string, + errorPage: string, + databaseResetPage: string, + pathToDatabaseFile: string, + ): Promise { + return this.startForkedWorker('network_scanner_worker.js', { + UDP_PORT: udpPort.toString(), + TCP_PORT: tcpPort.toString(), + OK_PAGE: okPage, + ERROR_PAGE: errorPage, + DATABASE_RESET_PAGE: databaseResetPage, + DATABASE_FILE_PATH: pathToDatabaseFile, + }) + } - async startDirectoriesWatchersWorker(memoryManagerPath: string, applicationInfoPath: string): Promise { - return this.startForkedWorker('directories_watcher_worker.js', { - MEMORY_MANAGER_PATH: memoryManagerPath, - APPLICATION_INFO_PATH: applicationInfoPath - }); - } + async startDirectoriesWatchersWorker(pathToDatabaseFile: string): Promise { + return this.startForkedWorker('directories_watcher_worker.js', { + DATABASE_FILE_PATH: pathToDatabaseFile, + }) + } - async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise { - return this.startForkedWorker('servers_worker.js', { - HOST: host, - USER_UDP_PORT: udpPort.toString(), - USER_TCP_PORT: tcpPort.toString() - }); - } + async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise { + return this.startForkedWorker('servers_worker.js', { + HOST: host, + USER_UDP_PORT: udpPort.toString(), + USER_TCP_PORT: tcpPort.toString(), + }) + } - async startResourceCoordinatorWorker(usersConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, queueManagerPath: string, tcpPort: number): Promise { - return this.startForkedWorker('resource_coordinator_worker.js', { - USERS_CONFIG_PATH: usersConfigPath, - APPLICATION_INFO_PATH: applicationInfoPath, - MEMORY_MANAGER_PATH: memoryManagerPath, - QUEUE_MANAGER_PATH: queueManagerPath, - TCP_PORT: tcpPort.toString() - }); - } + async startResourceCoordinatorWorker( + pathToDatabaseFiles: string, + tcpPort: number, + ): Promise { + return this.startForkedWorker('resource_coordinator_worker.js', { + DATABASE_FILE_PATH: pathToDatabaseFiles, + TCP_PORT: tcpPort.toString(), + }) + } - async startBackupRetrievalWorker(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string): Promise { - return this.startForkedWorker('backup_retrieval_worker.js', { - USER_CONFIG_PATH: userConfigPath, - APPLICATION_INFO_PATH: applicationInfoPath, - CLIENT_PORT: clientPort.toString(), - DESTINATION_PATH: destinationPath - }); - } + async startBackupRetrievalWorker( + clientPort: number, + destinationPath: string, + pathToDatabaseFile: string, + ): Promise { + return this.startForkedWorker('backup_retrieval_worker.js', { + CLIENT_PORT: clientPort.toString(), + DESTINATION_PATH: destinationPath, + DATABASE_FILE_PATH: pathToDatabaseFile, + }) + } - private async startForkedWorker(scriptName: string, envData: { [key: string]: string }): Promise { - return new Promise((resolve, reject) => { - const worker = fork(path.join(this.pathToWorkerDir, scriptName), { - execArgv: ['--max-old-space-size=4096'], // Set memory limit for the forked process - env: { ...process.env, ...envData } // Merge environment variables - }); + private async startForkedWorker( + scriptName: string, + envData: { [key: string]: string }, + ): Promise { + return new Promise((resolve, reject) => { + const worker = fork(path.join(this.pathToWorkerDir, scriptName), { + execArgv: ['--max-old-space-size=4096'], // Set memory limit for the forked process + env: { ...process.env, ...envData }, // Merge environment variables + }) - this.workers.push(worker); // Store the worker reference + this.workers.push(worker) // Store the worker reference - worker.on('message', (data: unknown) => { - const message = data as { type: string, page?: string, message?: string }; // Type casting for message + worker.on('message', (data: unknown) => { + const message = data as { type: string; page?: string; message?: string } // Type casting for message - if (message.type === 'changeContent' && message.page) { - this.windowManager.changeContent(message.page); - } else if (message.type === 'showAlert' && message.message) { - this.windowManager.showAlert(message.message); - } else { - console.log(`${scriptName} message:`, message); - } - }); - - worker.on('error', (err) => { - console.error(`${scriptName} error:`, err); - worker.kill(); - this.removeWorker(worker); - reject(err); - }); - - worker.on('exit', (code, signal) => { - this.removeWorker(worker); - if (code === 0) { - console.log(`${scriptName} exited successfully`); - resolve(); - } else if (signal) { - console.log(`${scriptName} was killed with signal: ${signal}`); - } else { - console.error(`${scriptName} exited with code: ${code}`);; - } - }); - }); - } - - closeAllWorkers(): void { - if (this.cleanupInProgress) return; // Prevent duplicate cleanup - this.cleanupInProgress = true; - - console.log('Terminating all running workers...'); - this.workers.forEach(worker => worker.kill()); - this.workers = []; - } - - // Helper method to remove a worker from the workers array when it exits - private removeWorker(worker: ChildProcess): void { - const index = this.workers.indexOf(worker); - if (index > -1) { - this.workers.splice(index, 1); + if (message.type === 'changeContent' && message.page) { + this.windowManager.changeContent(message.page) + } else if (message.type === 'showAlert' && message.message) { + this.windowManager.showAlert(message.message) + } else { + console.log(`${scriptName} message:`, message) } + }) + + worker.on('error', (err) => { + console.error(`${scriptName} error:`, err) + worker.kill() + this.removeWorker(worker) + reject(err) + }) + + worker.on('exit', (code, signal) => { + this.removeWorker(worker) + if (code === 0) { + console.log(`${scriptName} exited successfully`) + resolve() + } else if (signal) { + console.log(`${scriptName} was killed with signal: ${signal}`) + } else { + console.error(`${scriptName} exited with code: ${code}`) + } + }) + }) + } + + closeAllWorkers(): void { + if (this.cleanupInProgress) return // Prevent duplicate cleanup + this.cleanupInProgress = true + + console.log('Terminating all running workers...') + this.workers.forEach((worker) => worker.kill()) + this.workers = [] + } + + private removeWorker(worker: ChildProcess): void { + const index = this.workers.indexOf(worker) + if (index > -1) { + this.workers.splice(index, 1) } + } } diff --git a/User/src/interfaces/file_item_task.ts b/User/src/interfaces/file_item_task.ts index 6934379..b05a61f 100644 --- a/User/src/interfaces/file_item_task.ts +++ b/User/src/interfaces/file_item_task.ts @@ -1,8 +1,8 @@ export interface FileItemTask { - ip: string; - path: string; - userName: string; + ip: string + path: string + userName: string } export const compareFnFileItemTask = (task1: FileItemTask, task2: FileItemTask) => - task1.ip === task2.ip && task1.path === task2.path; + task1.ip === task2.ip && task1.path === task2.path diff --git a/User/src/interfaces/pool_request.ts b/User/src/interfaces/pool_request.ts index 73e9302..b7abe1f 100644 --- a/User/src/interfaces/pool_request.ts +++ b/User/src/interfaces/pool_request.ts @@ -1,15 +1,15 @@ interface PoolRequest { - type: PoolOperation; // Renamed to PoolOperation - clientId: string; // Add clientId to the request - data: PoolDataBundle; + type: PoolOperation // Renamed to PoolOperation + clientId: string // Add clientId to the request + data: PoolDataBundle } interface PoolDataBundle { - port?: number; - ip?: string; - operationCode?: string; // Keep operationCode here - metaInfo?: { [key: string]: any }; - fileContent?: Buffer; + port?: number + ip?: string + operationCode?: string // Keep operationCode here + metaInfo?: { [key: string]: any } + fileContent?: Buffer } -type PoolOperation = 'open' | 'send' | 'close'; // Define the allowed PoolOperations +type PoolOperation = 'open' | 'send' | 'close' // Define the allowed PoolOperations diff --git a/User/src/interfaces/registered_client.ts b/User/src/interfaces/registered_client.ts index 71ac44b..c5e8205 100644 --- a/User/src/interfaces/registered_client.ts +++ b/User/src/interfaces/registered_client.ts @@ -1,5 +1,5 @@ interface RegisteredClient { - id: string; - ip: string; - port: number; -} \ No newline at end of file + id: string + ip: string + port: number +} diff --git a/User/src/interfaces/worker_message.ts b/User/src/interfaces/worker_message.ts index b72d132..74ab069 100644 --- a/User/src/interfaces/worker_message.ts +++ b/User/src/interfaces/worker_message.ts @@ -1,6 +1,6 @@ // Define a type for the message structure interface WorkerMessage { - type: 'changeContent' | 'showAlert' | 'log'; - page?: string; - message?: string; + type: 'changeContent' | 'showAlert' | 'log' + page?: string + message?: string } diff --git a/User/src/ipc-handlers/database_handler.ts b/User/src/ipc-handlers/database_handler.ts new file mode 100644 index 0000000..e488cf3 --- /dev/null +++ b/User/src/ipc-handlers/database_handler.ts @@ -0,0 +1,137 @@ +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' +import path from 'path' +import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme' +import { FileItemTask } from '../database/helpers/queue_manager' +import { DirectoryInfo, DirectorySchemes } from '../database/schemes/local_resources_scheme' + +const pathToDatabaseFile = path.join(__dirname, '..', 'database.json') + +class IpcDatabaseHandler { + private readonly db: JsonDatabase + + constructor(pathToDatabaseFile: string) { + this.db = new JsonDatabase(pathToDatabaseFile) + } + + async getAppType(): Promise { + const data = await this.db.read() + return data.app_config.app_type + } + + async getUserInfo(): Promise { + const data = await this.db.read() + return data.app_config.user_info + } + + async getActiveUsers(): Promise { + const data = await this.db.read() + return data.network.usersInLan + } + + async getLocalResources(): Promise { + const data = await this.db.read() + return data.local_resources.directory_schemes + } + + async getDirectoryInfo(id: string): Promise { + const data = await this.db.read() + const directorySchemes = data.local_resources.directory_schemes + + switch (id) { + case directorySchemes.backup.id: { + return directorySchemes.backup + } + + case directorySchemes.department.id: { + return directorySchemes.department + } + + case directorySchemes.shared.id: { + return directorySchemes.shared + } + + default: { + throw new Error(`Directory with id ${id} not found`) + } + } + } + + async writeDirectoryPath(id: string, path: string): Promise { + try { + await this.db.update((data) => { + switch (id) { + case data.local_resources.directory_schemes.backup.id: { + data.local_resources.directory_schemes.backup.path = path + + break + } + + case data.local_resources.directory_schemes.department.id: { + data.local_resources.directory_schemes.department.path = path + break + } + + case data.local_resources.directory_schemes.shared.id: { + data.local_resources.directory_schemes.shared.path = path + break + } + + default: { + throw new Error(`Directory with id ${id} not found`) + } + } + + return data + }) + + return true + } + catch (e) { + return false + } + } + + async isBackupSet(): Promise { + const data = await this.db.read() + return data.local_resources.directory_schemes.backup.path !== '' + } + + async writeUserInfo(userInfo: UserInfoScheme): Promise { + await this.db.update((data) => { + data.app_config.user_info = userInfo + return data + }) + + return true + } + + async writeEncryptionKey(encryptionKey: EncryptionKeyScheme): Promise { + await this.db.update((data) => { + data.app_config.encryption_key = encryptionKey + return data + }) + + return true + } + + async setLoginStatus(status: boolean): Promise { + await this.db.update((data) => { + data.app_config.logged_in = status + return data + }) + + return true + } + + async addTaskToSendFileQueue(task: FileItemTask) { + this.db.pushQueue(task) + } + + async resetInternalDatabase(): Promise { + await this.db.reset() + return true + } +} + +export const ipcDatabaseHandler = new IpcDatabaseHandler(pathToDatabaseFile) diff --git a/User/src/ipc-handlers/uc_handler.ts b/User/src/ipc-handlers/uc_handler.ts new file mode 100644 index 0000000..83f8002 --- /dev/null +++ b/User/src/ipc-handlers/uc_handler.ts @@ -0,0 +1,60 @@ +import { TcpCommunicator } from '../helpers/tcp_communicator' +import dotenv from 'dotenv' +import path from 'path' +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' +import { operationCodes } from '../network/operation_codes' + +dotenv.config({ path: path.join(__dirname, '..', '..', '.env') }) +const pathToDatabaseFile = path.join(__dirname, '..', 'database.json') + +class IpcUCHandler { + private readonly TCP_PORT: number + private readonly db: JsonDatabase + private tcpCommunicator: TcpCommunicator | null = null + + constructor(pathToDatabaseFile: string) { + this.TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234 + this.db = new JsonDatabase(pathToDatabaseFile) + } + + async openUcSocket(): Promise { + if (!this.db) throw new Error('TcpMethods is not initialized.') + + const data = await this.db.read() + const serverIp = data.network.serverIp + + this.tcpCommunicator = new TcpCommunicator(serverIp, this.TCP_PORT) + return await this.tcpCommunicator.connect() + } + + async sendUcMessage( + operationCode: string, + metaInfo?: { [key: string]: any }, + fileContent?: Buffer, + ) { + if (!this.tcpCommunicator) throw new Error('TcpMethods is not initialized.') + return await this.tcpCommunicator.sendMessage(operationCode, metaInfo, fileContent) + } + + async hasResponseArrived() { + if (!this.tcpCommunicator) throw new Error('TcpMethods is not initialized.') + return this.tcpCommunicator.hasResponseArrived() + } + + async getLastUcResult() { + if (!this.tcpCommunicator) throw new Error('TcpMethods is not initialized.') + return this.tcpCommunicator.getLastResult() + } + + async closeUcSocket() { + if (!this.tcpCommunicator) throw new Error('TcpMethods is not initialized.') + return await this.tcpCommunicator.disconnect() + } + + async getOperationsCodes(): Promise<{ [key: string]: string }> { + return operationCodes + } +} + +export const ipcUCHandler = new IpcUCHandler(pathToDatabaseFile) diff --git a/User/src/ipc-handlers/ui_handler.ts b/User/src/ipc-handlers/ui_handler.ts new file mode 100644 index 0000000..e69de29 diff --git a/User/src/main/main.ts b/User/src/main/main.ts index 02ea963..52aebcb 100644 --- a/User/src/main/main.ts +++ b/User/src/main/main.ts @@ -1,351 +1,244 @@ -import {app, BrowserWindow, ipcMain, IpcMainInvokeEvent} from 'electron'; -import path from 'path'; -import { promises as fs } from 'fs'; -import dotenv from 'dotenv'; +import { app, BrowserWindow, ipcMain, IpcMainInvokeEvent } from 'electron' +import path from 'path' +import { promises as fs } from 'fs' +import dotenv from 'dotenv' +import { v4 as uuidv4 } from 'uuid' -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 { WorkerManager } from '../helpers/worker_manager' +import { DirectoryWatcher } from '../helpers/directory_watcher' +import { WindowManager } from '../helpers/window_manager' -import {operationCodes} from "../network/operation_codes"; -import os from "os"; +import os from 'os' +import { JsonDatabase } from '../database/database' +import { DatabaseScheme } from '../database/schemes/database_scheme' // Load environment variables -dotenv.config({ path: path.join(__dirname, '..', '..', '.env') }); +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(); +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; +let mainWindow: BrowserWindow | null = null +let windowManager: WindowManager | null = null +let db: JsonDatabase | null = null +let workerManager: WorkerManager | null = null +let directoryWatcher: DirectoryWatcher | 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'); +const pathToPagesDir = path.join(__dirname, '..', '..', 'render', 'html') +const pathToWorkerDir = path.join(__dirname, '..', 'workers') +const pathToDatabaseFile = path.join(__dirname, '..', 'database.json') +const pathToClientsBackups = path.join(__dirname, '..', 'backups') async function cleanupAndExit() { - // Stop all workers - if (workerManager) { - console.log('Terminating all workers...'); - workerManager.closeAllWorkers(); - } + // Stop all workers + if (workerManager) { + console.log('Terminating all workers...') + workerManager.closeAllWorkers() + } - // Reset memory - if (memoryManager) { - await memoryManager.resetFile(); - } + // Close watchers + if (directoryWatcher) { + console.log('Stopping backup directory watcher...') + directoryWatcher.stopAllWatchers() // Add this method to DirectoryWatcher to close the watcher + } - // Close watchers - if (backupDirectoryManager) { - console.log('Stopping backup directory watcher...'); - backupDirectoryManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher - } + if (workerManager) { + console.log('Terminating all workers...') + workerManager.closeAllWorkers() + } - 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 + 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}`); - } + 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; - } - } + 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 + } + return '' // Fallback if no IP is found } function startAnnouncementWatcher() { - const checkInterval = 5000; // Check every 5 seconds + const checkInterval = 5000 // Check every 5 seconds - announcementWatcher = setInterval(async () => { - if(!windowManager || !applicationInfo) return; - const announcement = await applicationInfo.readValue('announcement'); + announcementWatcher = setInterval(async () => { + if (!windowManager || !db) return + const data = await db.read() - if (announcement) await windowManager.displayAnnouncement(); - }, checkInterval); + if (data.network.announcement) await windowManager.displayAnnouncement() + }, checkInterval) } function startResetApplicationWatcher() { - const checkInterval = 5000; // Check every 5 seconds + const checkInterval = 5000 // Check every 5 seconds - resetApplicationWatcher = setInterval(async () => { - if(!applicationInfo || !windowManager) return; - const resetApplicationPreferences = await applicationInfo.readValue('reset_application_preferences'); + resetApplicationWatcher = setInterval(async () => { + /* + if (!applicationInfo || !windowManager) return + const resetApplicationPreferences = await applicationInfo.readValue( + 'reset_application_preferences', + ) - if (resetApplicationPreferences) await windowManager.changeContent('reset-database'); - }, checkInterval); + 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; + 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 = new BrowserWindow({ + title, + width: width / 1.5, + height: height / 1.5, + resizable: false, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + nodeIntegration: false, + contextIsolation: true, + sandbox: false, + }, + }) - mainWindow.removeMenu(); + //mainWindow.removeMenu() - await ensureDirectoryExists(pathToJsons); - await ensureDirectoryExists(pathToClientsBackups); + 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); + windowManager = new WindowManager(mainWindow, pathToPagesDir) + workerManager = new WorkerManager(pathToWorkerDir, windowManager) + db = new JsonDatabase(pathToDatabaseFile) - workerManager = new WorkerManager(pathToWorkerDir, windowManager); + await db.update((data) => { + data.app_config.server_found = false + data.app_config.logged_in = false + return data + }) - 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() - startAnnouncementWatcher(); - startResetApplicationWatcher(); + workerManager.startNetworkScannerWorker( + UDP_PORT, + TCP_PORT, + 'login', + 'uc_not_found', + 'reset_database', + pathToDatabaseFile, + ) + workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT) - 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); + registerIPCHandlers() - 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'); -}); + 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 -}); + 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 -}); + 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 -}); + console.log('Application is quitting, starting cleanup...') + await cleanupAndExit() // Call cleanup before app quit +}) -// Register IPC handlers +// Register IPC 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('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('start-workers', async (_event: IpcMainInvokeEvent) => { + if (!workerManager) throw new Error('WorkerManager is not initialized.') + workerManager.startDirectoriesWatchersWorker(pathToDatabaseFile) + workerManager.startResourceCoordinatorWorker(pathToDatabaseFile, TCP_PORT) + }) - ipcMain.handle('select-directory', async (_event: IpcMainInvokeEvent) => { - if (!windowManager) throw new Error('WindowManager is not initialized.'); - return await windowManager.selectDirectory(); - }); + ipcMain.handle('stop-workers', async (_event: IpcMainInvokeEvent) => { + if (!workerManager) throw new Error('WorkerManager is not initialized.') + workerManager.closeAllWorkers() - ipcMain.handle('select-file', async (_event: IpcMainInvokeEvent) => { - if (!windowManager) throw new Error('WindowManager is not initialized.'); - return await windowManager.selectFile(); - }); + workerManager.startNetworkScannerWorker( + UDP_PORT, + TCP_PORT, + 'login', + 'uc_not_found', + 'reset_database', + pathToDatabaseFile, + ) + workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT) + }) - 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('change-content', async (_event: IpcMainInvokeEvent, destination: string) => { + if (!windowManager) throw new Error('WindowManager is not initialized.') + await windowManager.changeContent(destination) + }) - ipcMain.handle('close-announcement-window', async (_event: IpcMainInvokeEvent) => { - if (!windowManager) throw new Error('WindowManager is not initialized.'); - return await windowManager.closeAnnouncementWindow(); - }); + ipcMain.handle('select-directory', async (_event: IpcMainInvokeEvent) => { + if (!windowManager) throw new Error('WindowManager is not initialized.') + return await windowManager.selectDirectory() + }) - // TcpMethods IPC Handlers - ipcMain.handle('open-socket', async (_event: IpcMainInvokeEvent) => { - if (!applicationInfo) throw new Error('TcpMethods is not initialized.'); + ipcMain.handle('select-file', async (_event: IpcMainInvokeEvent) => { + if (!windowManager) throw new Error('WindowManager is not initialized.') + return await windowManager.selectFile() + }) - const serverIp = await applicationInfo.readValue('serverIp'); - if (!serverIp) return; + 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) + }) - tcpCommunicator = new TcpCommunicator(serverIp, TCP_PORT); - return await tcpCommunicator.connect() - }); + ipcMain.handle('read-announcement', async (_event: IpcMainInvokeEvent) => { + if (!db) throw new Error('Database is not initialized.') + const data = await db.read() + const announcement = data.network.announcement + data.network.announcement = '' - 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); - }); + return announcement + }) - ipcMain.handle('has-response-arrived', async (_event: IpcMainInvokeEvent) => { - if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.'); - return tcpCommunicator.hasResponseArrived(); - }); + ipcMain.handle('close-announcement-window', async (_event: IpcMainInvokeEvent) => { + if (!windowManager) throw new Error('WindowManager is not initialized.') + return await windowManager.closeAnnouncementWindow() + }) - 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 - ); - }); -} \ No newline at end of file + // 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(TCP_PORT, destinationPath, pathToDatabaseFile) + }, + ) +} diff --git a/User/src/main/preload.ts b/User/src/main/preload.ts index 6437ed7..cb43ac0 100644 --- a/User/src/main/preload.ts +++ b/User/src/main/preload.ts @@ -1,45 +1,56 @@ -import { contextBridge, ipcRenderer } from 'electron'; -import {FileItemTask} from "../interfaces/file_item_task"; +import { contextBridge, ipcRenderer } from 'electron' +import { FileItemTask } from '../interfaces/file_item_task' +import { ipcDatabaseHandler } from '../ipc-handlers/database_handler' +import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme' +import { ipcUCHandler } from '../ipc-handlers/uc_handler' +import { ParsedMessage } from '../network/message_handler' +import { NetworkUserScheme } from '../database/schemes/network_scheme' +import { DirectoryInfo, DirectorySchemes } from "../database/schemes/local_resources_scheme"; + +contextBridge.exposeInMainWorld('databaseAPI', { + getAppType: (): Promise => ipcDatabaseHandler.getAppType(), + getUserInfo: (): Promise => ipcDatabaseHandler.getUserInfo(), + getActiveUsers: (): Promise => ipcDatabaseHandler.getActiveUsers(), + getLocalResources: (): Promise => ipcDatabaseHandler.getLocalResources(), + getDirectoryInfo: (id: string): Promise => ipcDatabaseHandler.getDirectoryInfo(id), + setLoginStatus: (status: boolean): Promise => ipcDatabaseHandler.setLoginStatus(status), + isBackupSet: (): Promise => ipcDatabaseHandler.isBackupSet(), + writeUserInfo: (userInfo: UserInfoScheme): Promise => + ipcDatabaseHandler.writeUserInfo(userInfo), + writeEncryptionKey: (encryptionKey: EncryptionKeyScheme): Promise => + ipcDatabaseHandler.writeEncryptionKey(encryptionKey), + writeDirectoryPath: (id: string, path: string): Promise => ipcDatabaseHandler.writeDirectoryPath(id, path), + addTaskToSendFileQueue: (task: FileItemTask): Promise => + ipcDatabaseHandler.addTaskToSendFileQueue(task), + resetInternalDatabase: (): Promise => ipcDatabaseHandler.resetInternalDatabase(), +}) + +contextBridge.exposeInMainWorld('networkAPI', { + openUcSocket: (): Promise => ipcUCHandler.openUcSocket(), + sendUcMessage: (operationCode: string, metaInfo: any, fileContent: any): Promise => + ipcUCHandler.sendUcMessage(operationCode, metaInfo, fileContent), + closeUcSocket: (): Promise => ipcUCHandler.closeUcSocket(), + hasResponseArrived: (): Promise => ipcUCHandler.hasResponseArrived(), + getLastUcResult: (): Promise => ipcUCHandler.getLastUcResult(), + getOperationsCodes: (): Promise<{ [key: string]: string }> => ipcUCHandler.getOperationsCodes(), +}) + +contextBridge.exposeInMainWorld('uiAPI', { + startWorkers: (): Promise => ipcRenderer.invoke('start-workers'), + stopWorkers: (): Promise => ipcRenderer.invoke('stop-workers'), + showAlert: (message: string): Promise => ipcRenderer.invoke('show-alert', message), + changeContent: (destination: string): Promise => + ipcRenderer.invoke('change-content', destination), + selectDirectory: (): Promise => ipcRenderer.invoke('select-directory'), + selectFile: (): Promise => ipcRenderer.invoke('select-file'), + showFileInExplorer: (path: string): Promise => + ipcRenderer.invoke('show-file-in-explorer', path), + readAnnouncement: (): Promise => ipcRenderer.invoke('read-announcement'), + closeAnnouncementWindow: (): Promise => ipcRenderer.invoke('close-announcement-window'), +}) contextBridge.exposeInMainWorld('electronAPI', { - // UserConfig methods - readUserConfig: (key: string): Promise => ipcRenderer.invoke('read-user-json-files', key), - writeUserConfig: (key: string, value: any): Promise => ipcRenderer.invoke('write-user-json-files', key, value), - removeUserConfig: (key: string) : Promise => ipcRenderer.invoke('remove-user-json-files', key), - resetUserConfig: (): Promise => ipcRenderer.invoke('reset-user-json-files'), - - // ApplicationPreferences methods - readApplicationInfo: (key: string): Promise => ipcRenderer.invoke('read-application-json-files', key), - writeApplicationInfo: (key: string, value: any): Promise => ipcRenderer.invoke('write-application-json-files', key, value), - removeApplicationInfo: (key: string) : Promise => ipcRenderer.invoke('remove-application-preferences', key), - resetApplicationInfo: (): Promise => ipcRenderer.invoke('reset-application-json-files'), - - // UcCommunication methods - openUcSocket: (): Promise => ipcRenderer.invoke('open-socket'), - sendUcMessage: (operationCode: string, metaInfo: any, fileContent: any): Promise => ipcRenderer.invoke('send-message', operationCode, metaInfo, fileContent), - closeUcSocket: (): Promise => ipcRenderer.invoke('close-socket'), - hasResponseArrived: (): Promise => ipcRenderer.invoke('has-response-arrived'), - getLastUcResult: (): Promise => ipcRenderer.invoke('get-last-result'), - getOperationsCodes: (): Promise<{ data: { [key: string]: string } }> => ipcRenderer.invoke('get-operation-codes'), - - // MemoryManager methods - createMemoryEntry: (): Promise => ipcRenderer.invoke('memory-create-entry'), - readMemoryEntry: (id: string): Promise => ipcRenderer.invoke('memory-read-entry', id), - updateMemoryEntry: (id: string, data: any): Promise => ipcRenderer.invoke('memory-update-entry', id, data), - removeMemoryEntry: (id: string): Promise => ipcRenderer.invoke('memory-remove-entry', id), - resetMemory: (): Promise => ipcRenderer.invoke('memory-reset'), - - // UI methods - showAlert: (message: string): Promise => ipcRenderer.invoke('show-alert', message), - changeContent: (destination: string): Promise => ipcRenderer.invoke('change-content', destination), - selectDirectory: (): Promise => ipcRenderer.invoke('select-directory'), - selectFile: (): Promise => ipcRenderer.invoke('select-file'), - showFileInExplorer: (path: string): Promise => ipcRenderer.invoke('show-file-in-explorer', path), - closeAnnouncementWindow: (): Promise => ipcRenderer.invoke('close-announcement-window'), - - // Queue methods - addTaskToSendFileQueue: (task: FileItemTask): Promise => ipcRenderer.invoke('add-task-to-send-file-queue', task), - - // Workers - startBackupRetrieval: (destinationPath: string): Promise => ipcRenderer.invoke('start-backup-retrieval', destinationPath), -}); + // Workers + startBackupRetrieval: (destinationPath: string): Promise => + ipcRenderer.invoke('start-backup-retrieval', destinationPath), +}) diff --git a/User/src/network/connection_manager.ts b/User/src/network/connection_manager.ts index 6dba3eb..3b191ef 100644 --- a/User/src/network/connection_manager.ts +++ b/User/src/network/connection_manager.ts @@ -1,43 +1,43 @@ -import { SocketCommunicatorBase } from "./socket_communicator/socket_communicator_base"; +import { SocketCommunicatorBase } from './socket_communicator/socket_communicator_base' interface Connection { - communicator: SocketCommunicatorBase; + communicator: SocketCommunicatorBase } export class ConnectionManager { - private readonly connections: { [key: string]: Connection }; + private readonly connections: { [key: string]: Connection } - constructor() { - this.connections = {}; + constructor() { + this.connections = {} + } + + // Adds a new communicator, keyed by both IP and port + addConnection(ip: string, port: number, communicator: SocketCommunicatorBase): void { + const key = `${ip}:${port}` + + // Store the communicator along with the client's public and private keys + this.connections[key] = { + communicator, } + } - // Adds a new communicator, keyed by both IP and port - addConnection(ip: string, port: number, communicator: SocketCommunicatorBase): void { - const key = `${ip}:${port}`; - - // Store the communicator along with the client's public and private keys - this.connections[key] = { - communicator - }; + // Removes a communicator based on IP and port + removeCommunicator(ip: string, port: number): void { + const key = `${ip}:${port}` + if (this.connections[key]) { + delete this.connections[key] } + } - // Removes a communicator based on IP and port - removeCommunicator(ip: string, port: number): void { - const key = `${ip}:${port}`; - if (this.connections[key]) { - delete this.connections[key]; - } - } + // Retrieves a communicator based on IP and port + getCommunicator(ip: string, port: number): SocketCommunicatorBase | null { + const key = `${ip}:${port}` + return this.connections[key] ? this.connections[key].communicator : null + } - // Retrieves a communicator based on IP and port - getCommunicator(ip: string, port: number): SocketCommunicatorBase | null { - const key = `${ip}:${port}`; - return this.connections[key] ? this.connections[key].communicator : null; - } - - // Checks if a communicator exists for a given IP and port - communicatorExists(ip: string, port: number): boolean { - const key = `${ip}:${port}`; - return this.connections[key] !== undefined; - } + // Checks if a communicator exists for a given IP and port + communicatorExists(ip: string, port: number): boolean { + const key = `${ip}:${port}` + return this.connections[key] !== undefined + } } diff --git a/User/src/network/message_handler.ts b/User/src/network/message_handler.ts index 6583a1d..2fa41f0 100644 --- a/User/src/network/message_handler.ts +++ b/User/src/network/message_handler.ts @@ -1,65 +1,65 @@ export interface ParsedMessage { - operationCode: string; - metaInfo?: { [key: string]: any }; - fileContent?: Buffer; + operationCode: string + metaInfo?: { [key: string]: any } + fileContent?: Buffer } export class MessageHandler { - // Format the message with operationCode, guid, metaInfo, and fileContent (Base64 for fileContent) - static formatMessage( - operationCode: string, - metaInfo?: { [key: string]: any }, - fileContent?: Buffer - ): string { - let message = `${operationCode}\n`; // First part: operationCode and guid + // Format the message with operationCode, guid, metaInfo, and fileContent (Base64 for fileContent) + static formatMessage( + operationCode: string, + metaInfo?: { [key: string]: any }, + fileContent?: Buffer, + ): string { + let message = `${operationCode}\n` // First part: operationCode and guid - if (metaInfo && Object.keys(metaInfo).length > 0) { - message += `${JSON.stringify(metaInfo)}\n`; // Add metaInfo - } - - if (fileContent && fileContent.length > 0) { - message += fileContent.toString('base64'); // Convert buffer to Base64 for fileContent - } - - return message; + if (metaInfo && Object.keys(metaInfo).length > 0) { + message += `${JSON.stringify(metaInfo)}\n` // Add metaInfo } - // Parse the incoming message (convert Base64 back to Buffer if fileContent is present) - static parseMessage(msg: string): ParsedMessage { - const parts = msg.split('\n'); // Split by \n (operationCode, metaInfo, and fileContent are on separate lines) - - // First part should always be the operation code - const operationCode = parts[0]?.trim(); - if (!operationCode) { - throw new Error('Missing operation code in the message'); - } - - let metaInfo: { [key: string]: any } | undefined = undefined; - let fileContent: Buffer | undefined = undefined; - - // Parse the metaInfo (JSON object) if present - if (parts[1]) { - try { - metaInfo = JSON.parse(parts[1].trim()); - } catch (err) { - console.error('Invalid metaInfo JSON format:', err); - } - } - - // Convert Base64 string back to Buffer for fileContent if present - if (parts[2]) { - fileContent = Buffer.from(parts[2].trim(), 'base64'); - } - - return { - operationCode, - metaInfo, - fileContent, - }; + if (fileContent && fileContent.length > 0) { + message += fileContent.toString('base64') // Convert buffer to Base64 for fileContent } - // Validate if the parsed message contains an operation code - static validateMessage(parsedMessage: ParsedMessage | null): boolean { - return !!parsedMessage?.operationCode; + return message + } + + // Parse the incoming message (convert Base64 back to Buffer if fileContent is present) + static parseMessage(msg: string): ParsedMessage { + const parts = msg.split('\n') // Split by \n (operationCode, metaInfo, and fileContent are on separate lines) + + // First part should always be the operation code + const operationCode = parts[0]?.trim() + if (!operationCode) { + throw new Error('Missing operation code in the message') } -} \ No newline at end of file + + let metaInfo: { [key: string]: any } | undefined = undefined + let fileContent: Buffer | undefined = undefined + + // Parse the metaInfo (JSON object) if present + if (parts[1]) { + try { + metaInfo = JSON.parse(parts[1].trim()) + } catch (err) { + console.error('Invalid metaInfo JSON format:', err) + } + } + + // Convert Base64 string back to Buffer for fileContent if present + if (parts[2]) { + fileContent = Buffer.from(parts[2].trim(), 'base64') + } + + return { + operationCode, + metaInfo, + fileContent, + } + } + + // Validate if the parsed message contains an operation code + static validateMessage(parsedMessage: ParsedMessage | null): boolean { + return !!parsedMessage?.operationCode + } +} diff --git a/User/src/network/network.ts b/User/src/network/network.ts index 8f65956..4351757 100644 --- a/User/src/network/network.ts +++ b/User/src/network/network.ts @@ -1,2 +1,2 @@ -export { UdpClient} from './udp/udp_client'; -export { TcpClient } from './tcp/tcp_client' \ No newline at end of file +export { UdpClient } from './udp/udp_client' +export { TcpClient } from './tcp/tcp_client' diff --git a/User/src/network/operation_codes.ts b/User/src/network/operation_codes.ts index fe7bd78..da9c980 100644 --- a/User/src/network/operation_codes.ts +++ b/User/src/network/operation_codes.ts @@ -1,41 +1,42 @@ export let operationCodes = { - // General Operations - ARE_YOU_HUMAN: 'ARE_YOU_HUMAN', - ARE_YOU_UC: 'ARE_YOU_UC', - ALIVE: 'ALIVE', - SET_PUBLIC_KEY: 'SET_PUBLIC_KEY', - SET_AES_KEY: 'SET_AES_KEY', - RESET_DATABASE: 'RESET_DATABASE', - OK: 'OK', - ERR: 'ERR', - END: 'END', - UNKNOWN_COMMAND: 'UNKNOWN_COMMAND', + // General Operations + ARE_YOU_HUMAN: 'ARE_YOU_HUMAN', + ARE_YOU_UC: 'ARE_YOU_UC', + ALIVE: 'ALIVE', + SET_PUBLIC_KEY: 'SET_PUBLIC_KEY', + SET_AES_KEY: 'SET_AES_KEY', + RESET_DATABASE: 'RESET_DATABASE', + OK: 'OK', + ERR: 'ERR', + END: 'END', + UNKNOWN_COMMAND: 'UNKNOWN_COMMAND', - // Auth Operations - LOGIN: 'LOGIN', - SIGN_UP: 'SIGN_UP', - RESET_PASSWORD: 'RESET_PASSWORD', + // Auth Operations + LOGIN: 'LOGIN', + SIGN_UP: 'SIGN_UP', + RESET_PASSWORD: 'RESET_PASSWORD', + EMAIL_VERIFICATION: 'EMAIL_VERIFICATION', - FIND_BY_EMAIL: 'FIND_BY_EMAIL', - MODIFY_USER: 'MODIFY_USER', + FIND_BY_EMAIL: 'FIND_BY_EMAIL', + MODIFY_USER: 'MODIFY_USER', - GET_DEPARTMENTS: 'GET_DEPARTMENTS', - CREATE_DEPARTMENT: 'CREATE_DEPARTMENT', - MODIFY_DEPARTMENT: 'MODIFY_DEPARTMENT', - DELETE_DEPARTMENT: 'DELETE_DEPARTMENT', + GET_DEPARTMENTS: 'GET_DEPARTMENTS', + CREATE_DEPARTMENT: 'CREATE_DEPARTMENT', + MODIFY_DEPARTMENT: 'MODIFY_DEPARTMENT', + DELETE_DEPARTMENT: 'DELETE_DEPARTMENT', - GET_USERS: 'GET_USERS', - FIND_KEY_BY_USER_ID: 'FIND_KEY_BY_USER_ID', - SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT', + GET_USERS: 'GET_USERS', + FIND_KEY_BY_USER_ID: 'FIND_KEY_BY_USER_ID', + SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT', - GET_USER_INFORMATION: 'GET_USER_INFORMATION', - CLEAR_BACKUP: 'CLEAR_BACKUP', - BACKUP_FILE: 'BACKUP_FILE', - SHARE_FILE: 'SHARE_FILE', - CLEAR_DEPARTMENT: 'CLEAR_DEPARTMENT', - DEPARTMENT_FILE: 'DEPARTMENT_FILE', - IS_BACKUP_CREATED: 'IS_BACKUP_CREATED', - GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE', - REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP', - DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE', -}; \ No newline at end of file + GET_USER_INFORMATION: 'GET_USER_INFORMATION', + CLEAR_BACKUP: 'CLEAR_BACKUP', + BACKUP_FILE: 'BACKUP_FILE', + SHARE_FILE: 'SHARE_FILE', + CLEAR_DEPARTMENT: 'CLEAR_DEPARTMENT', + DEPARTMENT_FILE: 'DEPARTMENT_FILE', + IS_BACKUP_CREATED: 'IS_BACKUP_CREATED', + GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE', + REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP', + DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE', +} diff --git a/User/src/network/operations_base/operation_handler.ts b/User/src/network/operations_base/operation_handler.ts index 4b9a2f2..0228fb5 100644 --- a/User/src/network/operations_base/operation_handler.ts +++ b/User/src/network/operations_base/operation_handler.ts @@ -1,57 +1,57 @@ // operation_handler.ts -import { ParsedMessage, MessageHandler } from '../message_handler'; -import { OperationPlugin } from './operation_plugin'; +import { ParsedMessage, MessageHandler } from '../message_handler' +import { OperationPlugin } from './operation_plugin' // Define handler function type to return Promise -type OperationHandlerFunction = (parsedMessage: ParsedMessage) => Promise; +type OperationHandlerFunction = (parsedMessage: ParsedMessage) => Promise export class OperationHandler { - private static instance: OperationHandler; - private handlers: { [operationCode: string]: OperationHandlerFunction } = {}; + private static instance: OperationHandler + private handlers: { [operationCode: string]: OperationHandlerFunction } = {} - private constructor() { - this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand); + private constructor() { + this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand) + } + + // Singleton instance + public static getInstance(): OperationHandler { + if (!OperationHandler.instance) { + OperationHandler.instance = new OperationHandler() + } + return OperationHandler.instance + } + + // Register a handler for a specific operation code + public registerHandler(operationCode: string, handler: OperationHandlerFunction): void { + this.handlers[operationCode] = handler + } + + // Handle operation request asynchronously + public async handleOperation(rawMessage: string): Promise { + const parsedMessage = MessageHandler.parseMessage(rawMessage) + if (!parsedMessage || !MessageHandler.validateMessage(parsedMessage)) { + return this.handleUnknownCommand(parsedMessage) } - // Singleton instance - public static getInstance(): OperationHandler { - if (!OperationHandler.instance) { - OperationHandler.instance = new OperationHandler(); - } - return OperationHandler.instance; + // Retrieve the handler for the operation code and invoke it asynchronously + const handler = this.handlers[parsedMessage.operationCode] + if (handler) { + return await handler(parsedMessage) + } else { + return this.handleUnknownCommand(parsedMessage) } + } - // Register a handler for a specific operation code - public registerHandler(operationCode: string, handler: OperationHandlerFunction): void { - this.handlers[operationCode] = handler; + // Default handler for unknown commands + private async handleUnknownCommand(parsedMessage: ParsedMessage): Promise { + return { + operationCode: 'UNKNOWN_COMMAND', + metaInfo: { message: 'Unknown command received.' }, } + } - // Handle operation request asynchronously - public async handleOperation(rawMessage: string): Promise { - const parsedMessage = MessageHandler.parseMessage(rawMessage); - if (!parsedMessage || !MessageHandler.validateMessage(parsedMessage)) { - return this.handleUnknownCommand(parsedMessage); - } - - // Retrieve the handler for the operation code and invoke it asynchronously - const handler = this.handlers[parsedMessage.operationCode]; - if (handler) { - return await handler(parsedMessage); - } else { - return this.handleUnknownCommand(parsedMessage); - } - } - - // Default handler for unknown commands - private async handleUnknownCommand(parsedMessage: ParsedMessage): Promise { - return { - operationCode: 'UNKNOWN_COMMAND', - metaInfo: { message: 'Unknown command received.' }, - }; - } - - // Plugin system: Load plugins to register handlers - public loadPlugin(plugin: OperationPlugin): void { - plugin.register(this); - } + // Plugin system: Load plugins to register ipc-handlers + public loadPlugin(plugin: OperationPlugin): void { + plugin.register(this) + } } diff --git a/User/src/network/operations_base/operation_plugin.ts b/User/src/network/operations_base/operation_plugin.ts index 6b11a29..654433c 100644 --- a/User/src/network/operations_base/operation_plugin.ts +++ b/User/src/network/operations_base/operation_plugin.ts @@ -1,6 +1,6 @@ // operation_plugin.ts -import { OperationHandler } from './operation_handler'; +import { OperationHandler } from './operation_handler' export interface OperationPlugin { - register(operationHandler: OperationHandler): void; + register(operationHandler: OperationHandler): void } diff --git a/User/src/network/operations_custom/general_operations.ts b/User/src/network/operations_custom/general_operations.ts index afe4c50..6f9fd53 100644 --- a/User/src/network/operations_custom/general_operations.ts +++ b/User/src/network/operations_custom/general_operations.ts @@ -1,88 +1,103 @@ -import { ParsedMessage } from '../message_handler'; -import { OperationHandler } from '../operations_base/operation_handler'; -import os from 'node:os'; -import {OperationPlugin} from "../operations_base/operation_plugin"; +import { ParsedMessage } from '../message_handler' +import { OperationHandler } from '../operations_base/operation_handler' +import os from 'node:os' +import { OperationPlugin } from '../operations_base/operation_plugin' export class GeneralOperations implements OperationPlugin { - public static readonly operationCodes = { - OK: 'OK', - ERR: 'ERR', - ARE_YOU_HUMAN: 'ARE_YOU_HUMAN', - ALIVE: 'ALIVE', - SET_PUBLIC_KEY: 'SET_PUBLIC_KEY', - SET_AES_KEY: 'SET_AES_KEY', - }; + public static readonly operationCodes = { + OK: 'OK', + ERR: 'ERR', + ARE_YOU_HUMAN: 'ARE_YOU_HUMAN', + ALIVE: 'ALIVE', + SET_PUBLIC_KEY: 'SET_PUBLIC_KEY', + SET_AES_KEY: 'SET_AES_KEY', + } - // Handle heartbeat operation asynchronously - public static async handleAreYouHuman(): Promise { - const networkInterfaces = os.networkInterfaces(); - let ipAddress = 'Unknown'; + // Handle heartbeat operation asynchronously + public static async handleAreYouHuman(): Promise { + const networkInterfaces = os.networkInterfaces() + let ipAddress = 'Unknown' - for (const iface of Object.values(networkInterfaces)) { - for (const address of iface!) { - if (address.family === 'IPv4' && !address.internal) { - ipAddress = address.address; - break; - } - } - if (ipAddress !== 'Unknown') break; + for (const iface of Object.values(networkInterfaces)) { + for (const address of iface!) { + if (address.family === 'IPv4' && !address.internal) { + ipAddress = address.address + break } - - return { - operationCode: GeneralOperations.operationCodes.ALIVE, - metaInfo: { ipAddress }, - }; + } + if (ipAddress !== 'Unknown') break } - // Handle public key exchange asynchronously - public static async handlePublicKey(parsedMessage: ParsedMessage): Promise { - const clientPublicKey = parsedMessage.metaInfo?.publicKey; - if (clientPublicKey) { - return { - operationCode: GeneralOperations.operationCodes.SET_PUBLIC_KEY, - metaInfo: { publicKey: clientPublicKey }, - }; - } else { - return { - operationCode: GeneralOperations.operationCodes.ERR, - metaInfo: { message: 'No public key provided.' }, - }; - } + return { + operationCode: GeneralOperations.operationCodes.ALIVE, + metaInfo: { ipAddress }, } + } - // Handle AES key exchange asynchronously - public static async handleAESKey(parsedMessage: ParsedMessage): Promise { - const aesKey = parsedMessage.metaInfo?.aesKey; - const aesIv = parsedMessage.metaInfo?.aesIv; - if (aesKey && aesIv) { - return { - operationCode: GeneralOperations.operationCodes.SET_AES_KEY, - metaInfo: { aesKey, aesIv }, - }; - } else { - return { - operationCode: GeneralOperations.operationCodes.ERR, - metaInfo: { message: 'No AES key provided.' }, - }; - } + // Handle public key exchange asynchronously + public static async handlePublicKey(parsedMessage: ParsedMessage): Promise { + const clientPublicKey = parsedMessage.metaInfo?.publicKey + if (clientPublicKey) { + return { + operationCode: GeneralOperations.operationCodes.SET_PUBLIC_KEY, + metaInfo: { publicKey: clientPublicKey }, + } + } else { + return { + operationCode: GeneralOperations.operationCodes.ERR, + metaInfo: { message: 'No public key provided.' }, + } } + } - // Default async handler for OK operation - public static async handleOk(parsedMessage: ParsedMessage): Promise { - return parsedMessage; // Acknowledge with OK, returning as-is + // Handle AES key exchange asynchronously + public static async handleAESKey(parsedMessage: ParsedMessage): Promise { + const aesKey = parsedMessage.metaInfo?.aesKey + const aesIv = parsedMessage.metaInfo?.aesIv + if (aesKey && aesIv) { + return { + operationCode: GeneralOperations.operationCodes.SET_AES_KEY, + metaInfo: { aesKey, aesIv }, + } + } else { + return { + operationCode: GeneralOperations.operationCodes.ERR, + metaInfo: { message: 'No AES key provided.' }, + } } + } - // Default async handler for ERR operation - public static async handleErr(parsedMessage: ParsedMessage): Promise { - return parsedMessage; // Log the error and return - } + // Default async handler for OK operation + public static async handleOk(parsedMessage: ParsedMessage): Promise { + return parsedMessage // Acknowledge with OK, returning as-is + } - // Register general operations with the OperationHandler - public register(operationHandler: OperationHandler): void { - operationHandler.registerHandler(GeneralOperations.operationCodes.ARE_YOU_HUMAN, GeneralOperations.handleAreYouHuman); - operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey); - operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey); - operationHandler.registerHandler(GeneralOperations.operationCodes.OK, GeneralOperations.handleOk); - operationHandler.registerHandler(GeneralOperations.operationCodes.ERR, GeneralOperations.handleErr); - } + // Default async handler for ERR operation + public static async handleErr(parsedMessage: ParsedMessage): Promise { + return parsedMessage // Log the error and return + } + + // Register general operations with the OperationHandler + public register(operationHandler: OperationHandler): void { + operationHandler.registerHandler( + GeneralOperations.operationCodes.ARE_YOU_HUMAN, + GeneralOperations.handleAreYouHuman, + ) + operationHandler.registerHandler( + GeneralOperations.operationCodes.SET_PUBLIC_KEY, + GeneralOperations.handlePublicKey, + ) + operationHandler.registerHandler( + GeneralOperations.operationCodes.SET_AES_KEY, + GeneralOperations.handleAESKey, + ) + operationHandler.registerHandler( + GeneralOperations.operationCodes.OK, + GeneralOperations.handleOk, + ) + operationHandler.registerHandler( + GeneralOperations.operationCodes.ERR, + GeneralOperations.handleErr, + ) + } } diff --git a/User/src/network/operations_custom/user_to_user_operations.ts b/User/src/network/operations_custom/user_to_user_operations.ts index 9cc8dec..a13621c 100644 --- a/User/src/network/operations_custom/user_to_user_operations.ts +++ b/User/src/network/operations_custom/user_to_user_operations.ts @@ -1,318 +1,434 @@ -import { ParsedMessage } from '../message_handler'; -import { OperationHandler } from '../operations_base/operation_handler'; -import {operationCodes} from "../operation_codes"; -import path from 'path'; -import fs from 'fs/promises'; -import checkDiskSpace from "check-disk-space"; -import { JsonManager } from '../../helpers/json_manager'; -import {OperationPlugin} from "../operations_base/operation_plugin"; +import { ParsedMessage } from '../message_handler' +import { OperationHandler } from '../operations_base/operation_handler' +import { operationCodes } from '../operation_codes' +import path from 'path' +import fs from 'fs/promises' +import checkDiskSpace from 'check-disk-space' +import { JsonManager } from '../../helpers/json_manager' +import { OperationPlugin } from '../operations_base/operation_plugin' export class UserToUserOperations implements OperationPlugin { - public static readonly operationCodes = { - SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT', - GET_USER_INFORMATION: 'GET_USER_INFORMATION', - BACKUP_FILE: 'BACKUP_FILE', - CLEAR_BACKUP: 'CLEAR_BACKUP', - SHARE_FILE: 'SHARE_FILE', - CLEAR_DEPARTMENT: 'CLEAR_DEPARTMENT', - DEPARTMENT_FILE: 'DEPARTMENT_FILE', - IS_BACKUP_CREATED: 'IS_BACKUP_CREATED', - GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE', - REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP', - DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE', - }; + public static readonly operationCodes = { + SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT', + GET_USER_INFORMATION: 'GET_USER_INFORMATION', + BACKUP_FILE: 'BACKUP_FILE', + CLEAR_BACKUP: 'CLEAR_BACKUP', + SHARE_FILE: 'SHARE_FILE', + CLEAR_DEPARTMENT: 'CLEAR_DEPARTMENT', + DEPARTMENT_FILE: 'DEPARTMENT_FILE', + IS_BACKUP_CREATED: 'IS_BACKUP_CREATED', + GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE', + REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP', + DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE', + } - private static async hasEnoughDiskSpace(directory: string, requiredPercentage: number = 25): Promise { - try { - const diskInfo = await checkDiskSpace(directory); - const availableSpace = diskInfo.free; - const totalSpace = diskInfo.size; - const availablePercentage = (availableSpace / totalSpace) * 100; - return availablePercentage >= requiredPercentage; - } catch (error) { - console.error(`Error checking disk space: ${error}`); - return false; - } + private static async hasEnoughDiskSpace( + directory: string, + requiredPercentage: number = 25, + ): Promise { + try { + const diskInfo = await checkDiskSpace(directory) + const availableSpace = diskInfo.free + const totalSpace = diskInfo.size + const availablePercentage = (availableSpace / totalSpace) * 100 + return availablePercentage >= requiredPercentage + } catch (error) { + console.error(`Error checking disk space: ${error}`) + return false + } + } + + public static async handleSendAnnouncement(parsedMessage: ParsedMessage): Promise { + if (!parsedMessage.metaInfo?.message) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Missing announcement message.' }, + } } + const jsonManager = new JsonManager( + path.join(__dirname, '..', '..', 'json_files', 'application.json'), + ) + try { + await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message) + console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`) + return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' } } + } catch (error: any) { + console.error(`Error saving announcement: ${error.message}`) + return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` } } + } + } - public static async handleSendAnnouncement(parsedMessage: ParsedMessage): Promise { - if (!parsedMessage.metaInfo?.message) { - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing announcement message.' }}; - } + public static async handleGetUserInformation( + parsedMessage: ParsedMessage, + ): Promise { + const jsonManager = new JsonManager( + path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'), + ) + try { + const userInfo = await jsonManager.readValue('user_info') + return { operationCode: operationCodes.OK, metaInfo: userInfo } + } catch (error: any) { + console.error(`Error fetching user info: ${error.message}`) + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Error fetching user info' }, + } + } + } - const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json')); - try { - await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message); - console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`); - return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' }}; - } catch (error: any) { - console.error(`Error saving announcement: ${error.message}`); - return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }}; - } + public static async handleBackupFile(parsedMessage: ParsedMessage): Promise { + if ( + !parsedMessage.metaInfo?.userName || + !parsedMessage.metaInfo?.relativeFilePath || + !parsedMessage.fileContent + ) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Missing file or user information.' }, + } } - public static async handleGetUserInformation(parsedMessage: ParsedMessage): Promise { - const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'userConfig.json')); - try { - const userInfo = await jsonManager.readValue('user_info'); - return { operationCode: operationCodes.OK, metaInfo: userInfo }; - } catch (error: any) { - console.error(`Error fetching user info: ${error.message}`); - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Error fetching user info' }}; + const { userName, relativeFilePath } = parsedMessage.metaInfo + const fullFilePath = path.join(__dirname, '..', '..', 'backups', userName, relativeFilePath) + + try { + if (!(await UserToUserOperations.hasEnoughDiskSpace(path.dirname(fullFilePath), 25))) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Insufficient disk space.' }, } + } + + await fs.mkdir(path.dirname(fullFilePath), { recursive: true }) + await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer) + console.log(`File saved: ${fullFilePath}`) + + return { + operationCode: operationCodes.OK, + metaInfo: { message: `File saved: ${relativeFilePath}` }, + } + } catch (error: any) { + console.error(`Error saving file: ${error.message}`) + return { + operationCode: operationCodes.ERR, + metaInfo: { message: `Error saving file: ${error.message}` }, + } + } + } + + public static async handleClearBackup(parsedMessage: ParsedMessage): Promise { + if (!parsedMessage.metaInfo?.userName) { + return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' } } } - public static async handleBackupFile(parsedMessage: ParsedMessage): Promise { - if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) { - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing file or user information.' }}; - } + const userBackupDir = path.join( + __dirname, + '..', + '..', + 'backups', + parsedMessage.metaInfo.userName, + ) + try { + await fs.rm(userBackupDir, { recursive: true, force: true }) + console.log(`Backup cleared: ${userBackupDir}`) + return { + operationCode: operationCodes.OK, + metaInfo: { message: `Backup cleared for ${parsedMessage.metaInfo.userName}` }, + } + } catch (error: any) { + console.error(`Error clearing backup: ${error.message}`) + return { + operationCode: operationCodes.ERR, + metaInfo: { message: `Error clearing backup: ${error.message}` }, + } + } + } - const { userName, relativeFilePath } = parsedMessage.metaInfo; - const fullFilePath = path.join(__dirname, '..', '..', 'backups', userName, relativeFilePath); - - try { - if (!await UserToUserOperations.hasEnoughDiskSpace(path.dirname(fullFilePath), 25)) { - return { operationCode: operationCodes.ERR, metaInfo: - { message: 'Insufficient disk space.' }}; - } - - await fs.mkdir(path.dirname(fullFilePath), { recursive: true }); - await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer); - console.log(`File saved: ${fullFilePath}`); - - return { operationCode: operationCodes.OK, metaInfo: { message: `File saved: ${relativeFilePath}` }}; - } catch (error: any) { - console.error(`Error saving file: ${error.message}`); - return { operationCode: operationCodes.ERR, metaInfo: { message: `Error saving file: ${error.message}` }}; - } + public static async handleShareFile(parsedMessage: ParsedMessage): Promise { + if ( + !parsedMessage.metaInfo?.userName || + !parsedMessage.metaInfo?.relativeFilePath || + !parsedMessage.fileContent + ) { + return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' } } } - public static async handleClearBackup(parsedMessage: ParsedMessage): Promise { - if (!parsedMessage.metaInfo?.userName) { - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }}; - } + const jsonManager = new JsonManager( + path.join(__dirname, '..', '..', 'json_files', 'application.json'), + ) + const { userName, relativeFilePath } = parsedMessage.metaInfo - const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.userName); - try { - await fs.rm(userBackupDir, { recursive: true, force: true }); - console.log(`Backup cleared: ${userBackupDir}`); - return { operationCode: operationCodes.OK, metaInfo: { message: `Backup cleared for ${parsedMessage.metaInfo.userName}` }}; - } catch (error: any) { - console.error(`Error clearing backup: ${error.message}`); - return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing backup: ${error.message}` }}; + try { + const appInfo = await jsonManager.readValue('shareDirectory') + const shareDirectory = appInfo?.path || '' + + console.log(`\n\nShare directory: ${shareDirectory}\n\n`) + + if (!shareDirectory) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Share directory missing.' }, } + } + + const fullFilePath = path.join(shareDirectory, userName, relativeFilePath) + await fs.mkdir(path.dirname(fullFilePath), { recursive: true }) + await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer) + console.log(`File shared: ${fullFilePath}`) + + return { + operationCode: operationCodes.OK, + metaInfo: { message: `File shared: ${relativeFilePath}` }, + } + } catch (error: any) { + console.error(`Error sharing file: ${error.message}`) + return { + operationCode: operationCodes.ERR, + metaInfo: { message: `Error sharing file: ${error.message}` }, + } + } + } + + public static async handleClearDepartment(parsedMessage: ParsedMessage): Promise { + if (!parsedMessage.metaInfo?.userName) { + return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' } } } - public static async handleShareFile(parsedMessage: ParsedMessage): Promise { - if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) { - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' }}; - } + const jsonManager = new JsonManager( + path.join(__dirname, '..', '..', 'json_files', 'application.json'), + ) + const { userName } = parsedMessage.metaInfo - const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json')); - const { userName, relativeFilePath } = parsedMessage.metaInfo; + try { + const appInfo = await jsonManager.readValue('departmentDirectory') + const departmentDir = appInfo?.path || '' + const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName) - try { - const appInfo = await jsonManager.readValue('shareDirectory'); - const shareDirectory = appInfo?.path || ''; + try { + await fs.access(userDepartmentDir) + } catch (ex: any) { + return { operationCode: operationCodes.OK } + } - console.log(`\n\nShare directory: ${shareDirectory}\n\n`); + await fs.rm(userDepartmentDir, { recursive: true, force: true }) + console.log(`Department backup cleared: ${userDepartmentDir}`) + return { + operationCode: operationCodes.OK, + metaInfo: { message: `Department backup cleared for ${userName}` }, + } + } catch (error: any) { + console.error(`Error clearing department: ${error.message}`) + return { + operationCode: operationCodes.ERR, + metaInfo: { message: `Error clearing department: ${error.message}` }, + } + } + } - if (!shareDirectory) { - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Share directory missing.' }}; - } - - const fullFilePath = path.join(shareDirectory, userName, relativeFilePath); - await fs.mkdir(path.dirname(fullFilePath), { recursive: true }); - await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer); - console.log(`File shared: ${fullFilePath}`); - - return { operationCode: operationCodes.OK, metaInfo: { message: `File shared: ${relativeFilePath}` }}; - } catch (error: any) { - console.error(`Error sharing file: ${error.message}`); - return { operationCode: operationCodes.ERR, metaInfo: { message: `Error sharing file: ${error.message}` }}; - } + public static async handleDepartmentFile(parsedMessage: ParsedMessage): Promise { + // Ensure metaInfo and fileContent are available + if (!parsedMessage.metaInfo || !parsedMessage.fileContent) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Missing file content or meta information.' }, + } } - public static async handleClearDepartment(parsedMessage: ParsedMessage): Promise { - if (!parsedMessage.metaInfo?.userName) { - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }}; - } + const { userName, relativeFilePath } = parsedMessage.metaInfo - const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json')); - const { userName } = parsedMessage.metaInfo; - - try { - const appInfo = await jsonManager.readValue('departmentDirectory'); - const departmentDir = appInfo?.path || ''; - const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName); - - try { - await fs.access(userDepartmentDir) - }catch(ex: any){ - return { operationCode: operationCodes.OK}; - } - - await fs.rm(userDepartmentDir, { recursive: true, force: true }); - console.log(`Department backup cleared: ${userDepartmentDir}`); - return { operationCode: operationCodes.OK, metaInfo: { message: `Department backup cleared for ${userName}` }}; - } catch (error: any) { - console.error(`Error clearing department: ${error.message}`); - return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing department: ${error.message}` }}; - } + if (!userName || !relativeFilePath) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Missing user name or file path information.' }, + } } - public static async handleDepartmentFile(parsedMessage: ParsedMessage): Promise { - // Ensure metaInfo and fileContent are available - if (!parsedMessage.metaInfo || !parsedMessage.fileContent) { - return { - operationCode: operationCodes.ERR, - metaInfo: { message: 'Missing file content or meta information.' }, - }; - } + // Path to the application.json to read the shareDirectory field + const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json') + const jsonManager = new JsonManager(pathToApplicationJson) - const { userName, relativeFilePath } = parsedMessage.metaInfo; - - if (!userName || !relativeFilePath) { - return { - operationCode: operationCodes.ERR, - metaInfo: { message: 'Missing user name or file path information.' }, - }; - } - - // Path to the application.json to read the shareDirectory field - const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json'); - const jsonManager = new JsonManager(pathToApplicationJson); - - // Read application configuration asynchronously - let appInfo; - try { - appInfo = await jsonManager.readValue('departmentDirectory'); - } catch (error: any) { - return { - operationCode: operationCodes.ERR, - metaInfo: { message: `Error reading application config: ${error.message}` }, - }; - } - - if (!appInfo || !appInfo.path) { - return { - operationCode: operationCodes.ERR, - metaInfo: { message: 'Error retrieving department directory from application.json.' }, - }; - } - - // Get the share directory path - const departmentDirectory = appInfo.path; - const fullFilePath = path.join(departmentDirectory, '..', 'DEPARTMENT_FILES', userName, relativeFilePath); - - try { - // Ensure the directory structure exists (create directories if they don't exist) - const dirPath = path.dirname(fullFilePath); - await fs.mkdir(dirPath, { recursive: true }); - - // Write the file content to the specified path - await fs.writeFile(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64'); - - return { - operationCode: operationCodes.OK, - metaInfo: { message: `File shared successfully: ${relativeFilePath}` }, - }; - } catch (error: any) { - return { - operationCode: operationCodes.ERR, - metaInfo: { message: `Error sharing file: ${error.message}` }, - }; - } + // Read application configuration asynchronously + let appInfo + try { + appInfo = await jsonManager.readValue('departmentDirectory') + } catch (error: any) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: `Error reading application config: ${error.message}` }, + } } - public static async handleIsBackupCreated(parsedMessage: ParsedMessage): Promise { - if (!parsedMessage.metaInfo?.name) { - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }}; - } - - const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name); - - try { - const exists = await fs.access(userBackupDir).then(() => true).catch(() => false); - return { operationCode: operationCodes.OK, metaInfo: { backupExists: exists }}; - } catch (error: any) { - console.error(`Error checking backup: ${error.message}`); - return { operationCode: operationCodes.ERR, metaInfo: { message: `Error checking backup: ${error.message}` }}; - } + if (!appInfo || !appInfo.path) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Error retrieving department directory from application.json.' }, + } } - public static async handleGetBackupStructure(parsedMessage: ParsedMessage): Promise { - if (!parsedMessage.metaInfo?.name) { - return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }}; - } + // Get the share directory path + const departmentDirectory = appInfo.path + const fullFilePath = path.join( + departmentDirectory, + '..', + 'DEPARTMENT_FILES', + userName, + relativeFilePath, + ) - const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name); + try { + // Ensure the directory structure exists (create directories if they don't exist) + const dirPath = path.dirname(fullFilePath) + await fs.mkdir(dirPath, { recursive: true }) - try { - const structure = await UserToUserOperations.buildDirectoryStructure(userBackupDir); - return { operationCode: operationCodes.OK, metaInfo: { structure }}; - } catch (error: any) { - console.error(`Error building backup structure: ${error.message}`); - return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }}; - } + // Write the file content to the specified path + await fs.writeFile(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64') + + return { + operationCode: operationCodes.OK, + metaInfo: { message: `File shared successfully: ${relativeFilePath}` }, + } + } catch (error: any) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: `Error sharing file: ${error.message}` }, + } + } + } + + public static async handleIsBackupCreated(parsedMessage: ParsedMessage): Promise { + if (!parsedMessage.metaInfo?.name) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Missing name in meta information.' }, + } } - private static async buildDirectoryStructure(directoryPath: string): Promise { - const structure: any = {}; - const files = await fs.readdir(directoryPath); + const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name) - for (const file of files) { - const filePath = path.join(directoryPath, file); - const stats = await fs.stat(filePath); + try { + const exists = await fs + .access(userBackupDir) + .then(() => true) + .catch(() => false) + return { operationCode: operationCodes.OK, metaInfo: { backupExists: exists } } + } catch (error: any) { + console.error(`Error checking backup: ${error.message}`) + return { + operationCode: operationCodes.ERR, + metaInfo: { message: `Error checking backup: ${error.message}` }, + } + } + } - if (stats.isDirectory()) { - structure[file] = await UserToUserOperations.buildDirectoryStructure(filePath); - } else { - structure[file] = path.relative(directoryPath, filePath); - } - } - - return structure; + public static async handleGetBackupStructure( + parsedMessage: ParsedMessage, + ): Promise { + if (!parsedMessage.metaInfo?.name) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Missing name in meta information.' }, + } } - public static async handleReqFileFromBackup(parsedMessage: ParsedMessage): Promise { - if (!parsedMessage.metaInfo?.name || !parsedMessage.metaInfo?.relativeFilePath) { - return { - operationCode: operationCodes.ERR, - metaInfo: { message: 'Missing user name or file path in meta information.' }, - }; - } + const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name) - const { name, relativeFilePath } = parsedMessage.metaInfo; - const fullFilePath = path.join(__dirname, '..', '..', 'backups', name, relativeFilePath); + try { + const structure = await UserToUserOperations.buildDirectoryStructure(userBackupDir) + return { operationCode: operationCodes.OK, metaInfo: { structure } } + } catch (error: any) { + console.error(`Error building backup structure: ${error.message}`) + return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` } } + } + } - try { - const fileContent = await fs.readFile(fullFilePath); - return { - operationCode: operationCodes.OK, - metaInfo: { relativeFilePath }, - fileContent, - }; - } catch (error: any) { - console.error(`Error reading file from backup: ${error.message}`); - return { - operationCode: operationCodes.ERR, - metaInfo: { message: `Error reading file: ${error.message}` }, - }; - } + private static async buildDirectoryStructure(directoryPath: string): Promise { + const structure: any = {} + const files = await fs.readdir(directoryPath) + + for (const file of files) { + const filePath = path.join(directoryPath, file) + const stats = await fs.stat(filePath) + + if (stats.isDirectory()) { + structure[file] = await UserToUserOperations.buildDirectoryStructure(filePath) + } else { + structure[file] = path.relative(directoryPath, filePath) + } } - public register(operationHandler: OperationHandler): void { - operationHandler.registerHandler(UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, UserToUserOperations.handleSendAnnouncement); - operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation); - operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile); - operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup); - operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile); - operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_DEPARTMENT, UserToUserOperations.handleClearDepartment); - operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile); - operationHandler.registerHandler(UserToUserOperations.operationCodes.IS_BACKUP_CREATED, UserToUserOperations.handleIsBackupCreated); - operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure); - operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup); + return structure + } + + public static async handleReqFileFromBackup( + parsedMessage: ParsedMessage, + ): Promise { + if (!parsedMessage.metaInfo?.name || !parsedMessage.metaInfo?.relativeFilePath) { + return { + operationCode: operationCodes.ERR, + metaInfo: { message: 'Missing user name or file path in meta information.' }, + } } + + const { name, relativeFilePath } = parsedMessage.metaInfo + const fullFilePath = path.join(__dirname, '..', '..', 'backups', name, relativeFilePath) + + try { + const fileContent = await fs.readFile(fullFilePath) + return { + operationCode: operationCodes.OK, + metaInfo: { relativeFilePath }, + fileContent, + } + } catch (error: any) { + console.error(`Error reading file from backup: ${error.message}`) + return { + operationCode: operationCodes.ERR, + metaInfo: { message: `Error reading file: ${error.message}` }, + } + } + } + + public register(operationHandler: OperationHandler): void { + operationHandler.registerHandler( + UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, + UserToUserOperations.handleSendAnnouncement, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.GET_USER_INFORMATION, + UserToUserOperations.handleGetUserInformation, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.BACKUP_FILE, + UserToUserOperations.handleBackupFile, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.CLEAR_BACKUP, + UserToUserOperations.handleClearBackup, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.SHARE_FILE, + UserToUserOperations.handleShareFile, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.CLEAR_DEPARTMENT, + UserToUserOperations.handleClearDepartment, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.DEPARTMENT_FILE, + UserToUserOperations.handleDepartmentFile, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.IS_BACKUP_CREATED, + UserToUserOperations.handleIsBackupCreated, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, + UserToUserOperations.handleGetBackupStructure, + ) + operationHandler.registerHandler( + UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, + UserToUserOperations.handleReqFileFromBackup, + ) + } } diff --git a/User/src/network/socket_communicator/socket_communicator_base.ts b/User/src/network/socket_communicator/socket_communicator_base.ts index f749193..0f00258 100644 --- a/User/src/network/socket_communicator/socket_communicator_base.ts +++ b/User/src/network/socket_communicator/socket_communicator_base.ts @@ -1,159 +1,162 @@ -import { ParsedMessage } from '../message_handler'; -import { OperationHandler } from '../operations_base/operation_handler'; +import { ParsedMessage } from '../message_handler' +import { OperationHandler } from '../operations_base/operation_handler' import { - constants, - createCipheriv, - createDecipheriv, - generateKeyPairSync, - privateEncrypt, - publicDecrypt, - randomBytes -} from "crypto"; + constants, + createCipheriv, + createDecipheriv, + generateKeyPairSync, + privateEncrypt, + publicDecrypt, + randomBytes, +} from 'crypto' export abstract class SocketCommunicatorBase { - protected readonly ip: string; - protected readonly port: number; - protected readonly operationHandler: OperationHandler; - protected handlerResult: ParsedMessage | null; + protected readonly ip: string + protected readonly port: number + protected readonly operationHandler: OperationHandler + protected handlerResult: ParsedMessage | null - protected chunkBuffers: { [messageId: string]: string[] }; + protected chunkBuffers: { [messageId: string]: string[] } - protected privateKey: string | null; - protected publicKey: string | null; - protected aesKey: Buffer | null; - protected aesIv: Buffer | null; + protected privateKey: string | null + protected publicKey: string | null + protected aesKey: Buffer | null + protected aesIv: Buffer | null - protected readonly EOP = ''; - protected readonly CHUNK_SIZE = 1024; - private incompleteChunkBuffer: string = ''; + protected readonly EOP = '' + protected readonly CHUNK_SIZE = 1024 + private incompleteChunkBuffer: string = '' - protected constructor(ip: string, port: number, operationHandler: OperationHandler) { - this.ip = ip; - this.port = port; - this.operationHandler = operationHandler - this.handlerResult = null; - this.chunkBuffers = {}; + protected constructor(ip: string, port: number, operationHandler: OperationHandler) { + this.ip = ip + this.port = port + this.operationHandler = operationHandler + this.handlerResult = null + this.chunkBuffers = {} - this.privateKey = null; - this.publicKey = null; - this.aesKey = null; - this.aesIv = null; + this.privateKey = null + this.publicKey = null + this.aesKey = null + this.aesIv = null + } + + // Getter for the handler result + getHandlerResult(): ParsedMessage | null { + const result = this.handlerResult + this.handlerResult = null + return result + } + + protected generateKeyPair(): void { + const { privateKey, publicKey } = generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + }) + this.privateKey = privateKey + this.publicKey = publicKey + } + + protected generateAesKey(): void { + this.aesKey = randomBytes(32) + this.aesIv = randomBytes(16) + } + + protected encryptWithAes(message: string): string { + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not set.') } + const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv) + let encrypted = cipher.update(message, 'utf-8') + encrypted = Buffer.concat([encrypted, cipher.final()]) + return encrypted.toString('base64') + } - // Getter for the handler result - getHandlerResult(): ParsedMessage | null { - const result = this.handlerResult; - this.handlerResult = null; - return result; + protected decryptWithAes(encryptedMessage: string): string { + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not set.') } + const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv) + let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64')) + decrypted = Buffer.concat([decrypted, decipher.final()]) + return decrypted.toString('utf-8') + } - protected generateKeyPair(): void { - const { privateKey, publicKey } = generateKeyPairSync('rsa', { - modulusLength: 2048, - publicKeyEncoding: { type: 'spki', format: 'pem' }, - privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, - }); - this.privateKey = privateKey; - this.publicKey = publicKey; + protected decryptWithRsa(message: string): string { + if (!this.publicKey) { + throw new Error('Server public key not set.') } - - protected generateAesKey(): void { - this.aesKey = randomBytes(32); - this.aesIv = randomBytes(16); + try { + const encryptedMessage = Buffer.from(message, 'base64') + const decrypted = publicDecrypt( + { + key: this.publicKey, + padding: constants.RSA_PKCS1_PADDING, + }, + encryptedMessage, + ) + return decrypted.toString('utf-8') + } catch (error) { + throw new Error('Failed to decrypt RSA message.') } + } - protected encryptWithAes(message: string): string { - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key or IV is not set.'); + protected encryptWithRsa(message: string): string { + if (!this.privateKey) throw new Error('Server private key not set.') + return privateEncrypt( + { + key: this.privateKey, + padding: constants.RSA_PKCS1_PADDING, + }, + Buffer.from(message), + ).toString('base64') + } + + async handleIncomingChunk(data: Buffer): Promise { + // Append incoming data to the incomplete buffer + this.incompleteChunkBuffer += data.toString() + + // Split the buffer by to separate complete and incomplete messages + const messages = this.incompleteChunkBuffer.split(this.EOP) + + // Save the last item back to the buffer if it's incomplete (no at the end) + this.incompleteChunkBuffer = messages.pop() || '' + + // Process each complete message in the split results + for (const incomingMessage of messages) { + try { + const [headerJson, chunkContent] = incomingMessage.split('|') + const header = JSON.parse(headerJson) + + // Initialize an array for chunks if it's the first chunk for this messageId + if (!this.chunkBuffers[header.messageId]) { + this.chunkBuffers[header.messageId] = [] } - const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv); - let encrypted = cipher.update(message, 'utf-8'); - encrypted = Buffer.concat([encrypted, cipher.final()]); - return encrypted.toString('base64'); - } - protected decryptWithAes(encryptedMessage: string): string { - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key or IV is not set.'); + // Store the chunk in the correct position based on sequenceNumber (1-based indexing) + this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent + + // Check if all chunks have been received + if ( + this.chunkBuffers[header.messageId].filter((chunk) => chunk !== undefined).length === + header.totalChunks + ) { + // Join all chunks to form the full message + const fullMessage = this.chunkBuffers[header.messageId].join('') + + // Process the complete message + await this.handleIncomingMessage(fullMessage) + + // Clear the chunk buffer for this messageId + delete this.chunkBuffers[header.messageId] } - const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv); - let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64')); - decrypted = Buffer.concat([decrypted, decipher.final()]); - return decrypted.toString('utf-8'); + } catch (error: any) { + console.error(`Error handling chunk: ${error.message}`) + } } + } - protected decryptWithRsa(message: string): string { - if (!this.publicKey) { - throw new Error('Server public key not set.'); - } - try { - const encryptedMessage = Buffer.from(message, 'base64'); - const decrypted = publicDecrypt( - { - key: this.publicKey, - padding: constants.RSA_PKCS1_PADDING, - }, - encryptedMessage - ); - return decrypted.toString('utf-8'); - } catch (error) { - throw new Error('Failed to decrypt RSA message.'); - } - } + abstract handleIncomingMessage(incomingMessage: string): Promise - protected encryptWithRsa(message: string): string { - if (!this.privateKey) throw new Error('Server private key not set.'); - return privateEncrypt( - { - key: this.privateKey, - padding: constants.RSA_PKCS1_PADDING, - }, - Buffer.from(message) - ).toString('base64'); - } - - async handleIncomingChunk(data: Buffer): Promise { - // Append incoming data to the incomplete buffer - this.incompleteChunkBuffer += data.toString(); - - // Split the buffer by to separate complete and incomplete messages - const messages = this.incompleteChunkBuffer.split(this.EOP); - - // Save the last item back to the buffer if it's incomplete (no at the end) - this.incompleteChunkBuffer = messages.pop() || ""; - - // Process each complete message in the split results - for (const incomingMessage of messages) { - try { - const [headerJson, chunkContent] = incomingMessage.split('|'); - const header = JSON.parse(headerJson); - - // Initialize an array for chunks if it's the first chunk for this messageId - if (!this.chunkBuffers[header.messageId]) { - this.chunkBuffers[header.messageId] = []; - } - - // Store the chunk in the correct position based on sequenceNumber (1-based indexing) - this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent; - - // Check if all chunks have been received - if (this.chunkBuffers[header.messageId].filter(chunk => chunk !== undefined).length === header.totalChunks) { - // Join all chunks to form the full message - const fullMessage = this.chunkBuffers[header.messageId].join(''); - - // Process the complete message - await this.handleIncomingMessage(fullMessage); - - // Clear the chunk buffer for this messageId - delete this.chunkBuffers[header.messageId]; - } - } catch (error: any) { - console.error(`Error handling chunk: ${error.message}`); - } - } - } - - abstract handleIncomingMessage(incomingMessage: string): Promise; - - abstract sendMessage(message: string): Promise; + abstract sendMessage(message: string): Promise } diff --git a/User/src/network/socket_communicator/tcp_client_communicator.ts b/User/src/network/socket_communicator/tcp_client_communicator.ts index 11824b8..fa97bca 100644 --- a/User/src/network/socket_communicator/tcp_client_communicator.ts +++ b/User/src/network/socket_communicator/tcp_client_communicator.ts @@ -1,87 +1,90 @@ -import { Socket } from 'net'; -import { SocketCommunicatorBase } from './socket_communicator_base'; -import { OperationHandler } from '../operations_base/operation_handler'; -import { operationCodes } from '../operation_codes'; -import {MessageHandler} from "../message_handler"; +import { Socket } from 'net' +import { SocketCommunicatorBase } from './socket_communicator_base' +import { OperationHandler } from '../operations_base/operation_handler' +import { operationCodes } from '../operation_codes' +import { MessageHandler } from '../message_handler' export class TcpClientCommunicator extends SocketCommunicatorBase { - private readonly socket: Socket; - private isAesKeySetFlag: boolean; + private readonly socket: Socket + private isAesKeySetFlag: boolean - constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { - super(ip, port, operationHandler); - this.socket = socket; - this.aesKey = null; - this.aesIv = null; - this.isAesKeySetFlag = false; - this.chunkBuffers = {}; + constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { + super(ip, port, operationHandler) + this.socket = socket + this.aesKey = null + this.aesIv = null + this.isAesKeySetFlag = false + this.chunkBuffers = {} + } + + isAesKeySet(): boolean { + return this.isAesKeySetFlag + } + + setServerPublicKey(publicKey: string): void { + this.publicKey = publicKey + } + + setAesKey(aesKey: string, aesIv: string): void { + this.aesKey = Buffer.from(aesKey, 'base64') + this.aesIv = Buffer.from(aesIv, 'base64') + } + + async handleIncomingMessage(incomingMessage: string): Promise { + let messageToProcess + + if (this.aesKey && this.aesIv) { + messageToProcess = this.decryptWithAes(incomingMessage) + } else if (this.publicKey) { + messageToProcess = this.decryptWithRsa(incomingMessage) + } else { + messageToProcess = Buffer.from(incomingMessage, 'base64').toString('utf-8') } - isAesKeySet(): boolean { - return this.isAesKeySetFlag; + const result = await this.operationHandler.handleOperation(messageToProcess) + + if (result.operationCode === operationCodes.SET_AES_KEY) { + this.isAesKeySetFlag = true + this.setAesKey(result.metaInfo?.aesKey, result.metaInfo?.aesIv) + return } - setServerPublicKey(publicKey: string): void { - this.publicKey = publicKey; + if (result.operationCode === operationCodes.SET_PUBLIC_KEY) { + this.setServerPublicKey(result.metaInfo?.publicKey) + return } - setAesKey(aesKey: string, aesIv: string): void { - this.aesKey = Buffer.from(aesKey, 'base64'); - this.aesIv = Buffer.from(aesIv, 'base64'); + this.handlerResult = result + } + + async sendMessage( + operationCode: string, + metaInfo?: { [key: string]: any }, + fileContent?: Buffer, + ): Promise { + const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent) + + const outgoingMessage = this.encryptWithAes(message) + + // Calculate optimal chunk size based on network latency + const totalChunks = Math.ceil(outgoingMessage.length / this.CHUNK_SIZE) + + const messageId = Date.now().toString() + + // Send each chunk with a delay between them + for (let i = 0; i < totalChunks; i++) { + const chunk = outgoingMessage.slice(i * this.CHUNK_SIZE, (i + 1) * this.CHUNK_SIZE) + const chunkHeader = JSON.stringify({ + messageId, + sequenceNumber: i + 1, + totalChunks, + }) + const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}` + + if (!this.socket.write(chunkWithHeader)) { + // Wait for the 'drain' event before writing the next chunk + await new Promise((resolve) => this.socket.once('drain', resolve)) + } } - - async handleIncomingMessage(incomingMessage: string): Promise { - let messageToProcess; - - if (this.aesKey && this.aesIv) { - messageToProcess = this.decryptWithAes(incomingMessage); - } else if (this.publicKey) { - messageToProcess = this.decryptWithRsa(incomingMessage); - } else { - messageToProcess = Buffer.from(incomingMessage, 'base64').toString('utf-8'); - } - - const result = await this.operationHandler.handleOperation(messageToProcess); - - if (result.operationCode === operationCodes.SET_AES_KEY) { - this.isAesKeySetFlag = true; - this.setAesKey(result.metaInfo?.aesKey, result.metaInfo?.aesIv); - return; - } - - if (result.operationCode === operationCodes.SET_PUBLIC_KEY) { - this.setServerPublicKey(result.metaInfo?.publicKey); - return; - } - - this.handlerResult = result; - } - - async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { - const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); - - const outgoingMessage = this.encryptWithAes(message); - - // Calculate optimal chunk size based on network latency - const totalChunks = Math.ceil(outgoingMessage.length / this.CHUNK_SIZE); - - const messageId = Date.now().toString(); - - // Send each chunk with a delay between them - for (let i = 0; i < totalChunks; i++) { - const chunk = outgoingMessage.slice(i * this.CHUNK_SIZE, (i + 1) * this.CHUNK_SIZE); - const chunkHeader = JSON.stringify({ - messageId, - sequenceNumber: i + 1, - totalChunks, - }); - const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`; - - if (!this.socket.write(chunkWithHeader)) { - // Wait for the 'drain' event before writing the next chunk - await new Promise((resolve) => this.socket.once('drain', resolve)); - } - } - } - + } } diff --git a/User/src/network/socket_communicator/tcp_server_communicator.ts b/User/src/network/socket_communicator/tcp_server_communicator.ts index aa1779b..d2531ab 100644 --- a/User/src/network/socket_communicator/tcp_server_communicator.ts +++ b/User/src/network/socket_communicator/tcp_server_communicator.ts @@ -1,74 +1,78 @@ -import { Socket } from 'net'; -import { MessageHandler } from '../message_handler'; -import { SocketCommunicatorBase } from './socket_communicator_base'; -import { OperationHandler } from '../operations_base/operation_handler'; -import {operationCodes} from "../operation_codes"; +import { Socket } from 'net' +import { MessageHandler } from '../message_handler' +import { SocketCommunicatorBase } from './socket_communicator_base' +import { OperationHandler } from '../operations_base/operation_handler' +import { operationCodes } from '../operation_codes' export class TcpServerCommunicator extends SocketCommunicatorBase { - private readonly socket: Socket; + private readonly socket: Socket - constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { - super(ip, port, operationHandler); - this.socket = socket; + constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { + super(ip, port, operationHandler) + this.socket = socket + } + + async sendPublicKey(): Promise { + this.generateKeyPair() + if (!this.publicKey || !this.privateKey) { + throw new Error('RSA key pair is not available. Please generate RSA key pair.') } - async sendPublicKey(): Promise { - this.generateKeyPair(); - if (!this.publicKey || !this.privateKey) { - throw new Error('RSA key pair is not available. Please generate RSA key pair.'); - } + await this.sendMessage(operationCodes.SET_PUBLIC_KEY, { publicKey: this.publicKey }) + } - await this.sendMessage(operationCodes.SET_PUBLIC_KEY, { publicKey: this.publicKey }); + async sendAesKey(): Promise { + this.generateAesKey() + if (!this.aesKey || !this.aesIv) { + throw new Error('AES key or IV is not available. Please generate AES key.') } - async sendAesKey(): Promise { - this.generateAesKey(); - if (!this.aesKey || !this.aesIv) { - throw new Error('AES key or IV is not available. Please generate AES key.'); - } + const aesKeyBase64 = this.aesKey.toString('base64') + const aesIvBase64 = this.aesIv.toString('base64') + await this.sendMessage(operationCodes.SET_AES_KEY, { aesKey: aesKeyBase64, aesIv: aesIvBase64 }) + } - const aesKeyBase64 = this.aesKey.toString('base64'); - const aesIvBase64 = this.aesIv.toString('base64'); - await this.sendMessage(operationCodes.SET_AES_KEY, { aesKey: aesKeyBase64, aesIv: aesIvBase64 }); + async handleIncomingMessage(incomingMessage: string): Promise { + const messageToProcess = this.decryptWithAes(incomingMessage) + this.handlerResult = await this.operationHandler.handleOperation(messageToProcess) + } + + async sendMessage( + operationCode: string, + metaInfo?: { [key: string]: any }, + fileContent?: Buffer, + ): Promise { + const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent) + + let outgoingMessage: string + switch (operationCode) { + case operationCodes.SET_PUBLIC_KEY: + outgoingMessage = Buffer.from(message, 'utf-8').toString('base64') + break + case operationCodes.SET_AES_KEY: + outgoingMessage = this.encryptWithRsa(message) + break + default: + outgoingMessage = this.encryptWithAes(message) } - async handleIncomingMessage(incomingMessage: string): Promise { - const messageToProcess = this.decryptWithAes(incomingMessage); - this.handlerResult = await this.operationHandler.handleOperation(messageToProcess); - } + const totalChunks = Math.ceil(outgoingMessage.length / this.CHUNK_SIZE) + const messageId = Date.now().toString() - async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { - const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); + // Send each chunk with a delay between them + for (let i = 0; i < totalChunks; i++) { + const chunk = outgoingMessage.slice(i * this.CHUNK_SIZE, (i + 1) * this.CHUNK_SIZE) + const chunkHeader = JSON.stringify({ + messageId, + sequenceNumber: i + 1, + totalChunks, + }) + const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}` - let outgoingMessage: string; - switch (operationCode) { - case operationCodes.SET_PUBLIC_KEY: - outgoingMessage = Buffer.from(message, 'utf-8').toString('base64'); - break; - case operationCodes.SET_AES_KEY: - outgoingMessage = this.encryptWithRsa(message); - break; - default: - outgoingMessage = this.encryptWithAes(message); - } - - const totalChunks = Math.ceil(outgoingMessage.length / this.CHUNK_SIZE); - const messageId = Date.now().toString(); - - // Send each chunk with a delay between them - for (let i = 0; i < totalChunks; i++) { - const chunk = outgoingMessage.slice(i * this.CHUNK_SIZE, (i + 1) * this.CHUNK_SIZE); - const chunkHeader = JSON.stringify({ - messageId, - sequenceNumber: i + 1, - totalChunks, - }); - const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`; - - if (!this.socket.write(chunkWithHeader)) { - // Wait for the 'drain' event before writing the next chunk - await new Promise((resolve) => this.socket.once('drain', resolve)); - } - } + if (!this.socket.write(chunkWithHeader)) { + // Wait for the 'drain' event before writing the next chunk + await new Promise((resolve) => this.socket.once('drain', resolve)) + } } + } } diff --git a/User/src/network/socket_communicator/udp_socket_communicator.ts b/User/src/network/socket_communicator/udp_socket_communicator.ts index f9f4e48..c37a993 100644 --- a/User/src/network/socket_communicator/udp_socket_communicator.ts +++ b/User/src/network/socket_communicator/udp_socket_communicator.ts @@ -1,33 +1,37 @@ -import { Socket as UdpSocket } from 'dgram'; -import { MessageHandler } from '../message_handler'; -import { SocketCommunicatorBase } from './socket_communicator_base'; -import {OperationHandler} from "../operations_base/operation_handler"; +import { Socket as UdpSocket } from 'dgram' +import { MessageHandler } from '../message_handler' +import { SocketCommunicatorBase } from './socket_communicator_base' +import { OperationHandler } from '../operations_base/operation_handler' export class UdpSocketCommunicator extends SocketCommunicatorBase { - private readonly socket: UdpSocket; + private readonly socket: UdpSocket - constructor(socket: UdpSocket, ip: string, port: number, operationHandler: OperationHandler) { - super(ip, port, operationHandler); - this.socket = socket; - } + constructor(socket: UdpSocket, ip: string, port: number, operationHandler: OperationHandler) { + super(ip, port, operationHandler) + this.socket = socket + } - // Send plain message over UDP (metaInfo as JSON and fileContent as Buffer) - async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { - const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); - return new Promise((resolve, reject) => { - this.socket.send(message, this.port, this.ip, (err: any) => { - if (err) { - console.error('Error sending UDP message:', err); - return reject(err); - } - console.log(`Plain message sent to ${this.ip}:${this.port} (UDP)`); - resolve(); - }); - }); - } + // Send plain message over UDP (metaInfo as JSON and fileContent as Buffer) + async sendMessage( + operationCode: string, + metaInfo?: { [key: string]: any }, + fileContent?: Buffer, + ): Promise { + const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent) + return new Promise((resolve, reject) => { + this.socket.send(message, this.port, this.ip, (err: any) => { + if (err) { + console.error('Error sending UDP message:', err) + return reject(err) + } + console.log(`Plain message sent to ${this.ip}:${this.port} (UDP)`) + resolve() + }) + }) + } - // Handle incoming message (no decryption needed for UDP) - async handleIncomingMessage(incomingMessage: string): Promise { - this.handlerResult = await this.operationHandler.handleOperation(incomingMessage); - } + // Handle incoming message (no decryption needed for UDP) + async handleIncomingMessage(incomingMessage: string): Promise { + this.handlerResult = await this.operationHandler.handleOperation(incomingMessage) + } } diff --git a/User/src/network/tcp/tcp_client.ts b/User/src/network/tcp/tcp_client.ts index e87efae..6a98a0e 100644 --- a/User/src/network/tcp/tcp_client.ts +++ b/User/src/network/tcp/tcp_client.ts @@ -1,111 +1,120 @@ -import net, { Socket } from 'net'; -import { TcpClientCommunicator } from '../socket_communicator/tcp_client_communicator'; -import { OperationHandler } from '../operations_base/operation_handler'; -import { operationCodes } from "../operation_codes"; -import { GeneralOperations } from "../operations_custom/general_operations"; -import { UserToUserOperations } from "../operations_custom/user_to_user_operations"; -import { ParsedMessage } from "../message_handler"; +import net, { Socket } from 'net' +import { TcpClientCommunicator } from '../socket_communicator/tcp_client_communicator' +import { OperationHandler } from '../operations_base/operation_handler' +import { operationCodes } from '../operation_codes' +import { GeneralOperations } from '../operations_custom/general_operations' +import { UserToUserOperations } from '../operations_custom/user_to_user_operations' +import { ParsedMessage } from '../message_handler' export class TcpClient { - private readonly tcp_port: number; - private socket: Socket | null; - private communicator: TcpClientCommunicator | null; - private readonly operationHandler: OperationHandler; - private lastResult: ParsedMessage | null; + private readonly tcp_port: number + private socket: Socket | null + private communicator: TcpClientCommunicator | null + private readonly operationHandler: OperationHandler + private lastResult: ParsedMessage | null - constructor(tcp_port: number) { - this.tcp_port = tcp_port; - this.socket = null; - this.communicator = null; - this.operationHandler = OperationHandler.getInstance(); - this.lastResult = null; + constructor(tcp_port: number) { + this.tcp_port = tcp_port + this.socket = null + this.communicator = null + this.operationHandler = OperationHandler.getInstance() + this.lastResult = null - this.operationHandler.loadPlugin(new GeneralOperations()); - this.operationHandler.loadPlugin(new UserToUserOperations()); + this.operationHandler.loadPlugin(new GeneralOperations()) + this.operationHandler.loadPlugin(new UserToUserOperations()) + } + + // Unified logging function + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[TcpClient]' + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) + } + } + + // Open a TCP socket connection + openSocket(ip: string): void { + this.socket = new net.Socket() + + this.socket.connect(this.tcp_port, ip, () => { + this.log(`Connected to server at ${ip}:${this.tcp_port}`) + this.communicator = new TcpClientCommunicator( + this.socket as Socket, + ip, + this.tcp_port, + this.operationHandler, + ) + }) + + this.socket.on('error', (err) => { + this.log(`Connection error to server at ${ip}:${this.tcp_port}: ${err.message}`, 'error') + }) + + this.socket.on('data', async (data: Buffer) => { + if (this.communicator) { + await this.communicator.handleIncomingChunk(data) + this.lastResult = this.communicator.getHandlerResult() + this.log(`Data received from ${ip}:${this.tcp_port}`) + } + }) + + this.socket.on('close', () => { + this.log(`Connection closed: ${ip}:${this.tcp_port}`) + this.lastResult = null + }) + } + + // Close the socket connection + closeSocket(): void { + if (this.socket) { + this.socket.end() + this.socket = null + this.communicator = null + this.lastResult = null + this.log('Client socket connection closed.') + } + } + + // Send a message with operationCode, metaInfo, and fileContent in chunks + async sendMessage( + operationCode: string, + metaInfo?: { [key: string]: any }, + fileContent?: Buffer, + ): Promise { + if (!this.communicator || !this.isAesKeySet()) { + this.log('Communicator not initialized or AES key not set.', 'error') + return false } - // Unified logging function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[TcpClient]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); - } else { - console.log(`${prefix} ${message}`); - } - } + this.log(`Sending message with operationCode: ${operationCode}`) + await this.communicator.sendMessage(operationCode, metaInfo, fileContent) + return true + } - // Open a TCP socket connection - openSocket(ip: string): void { - this.socket = new net.Socket(); + // Check if AES key is set + isAesKeySet(): boolean { + if (!this.communicator) return false + return this.communicator?.isAesKeySet() + } - this.socket.connect(this.tcp_port, ip, () => { - this.log(`Connected to server at ${ip}:${this.tcp_port}`); - this.communicator = new TcpClientCommunicator(this.socket as Socket, ip, this.tcp_port, this.operationHandler); - }); + // Check if the message is received (based on if lastResult is available) + isMessageReceived(): boolean { + return this.lastResult !== null + } - this.socket.on('error', (err) => { - this.log(`Connection error to server at ${ip}:${this.tcp_port}: ${err.message}`, 'error'); - }); + // Get the last result (and clear it after returning) + getLastResult(): ParsedMessage | null { + const result = this.lastResult + this.lastResult = null + return result + } - this.socket.on('data', async (data: Buffer) => { - if (this.communicator) { - await this.communicator.handleIncomingChunk(data); - this.lastResult = this.communicator.getHandlerResult(); - this.log(`Data received from ${ip}:${this.tcp_port}`); - } - }); - - this.socket.on('close', () => { - this.log(`Connection closed: ${ip}:${this.tcp_port}`); - this.lastResult = null; - }); - } - - // Close the socket connection - closeSocket(): void { - if (this.socket) { - this.socket.end(); - this.socket = null; - this.communicator = null; - this.lastResult = null; - this.log('Client socket connection closed.'); - } - } - - // Send a message with operationCode, metaInfo, and fileContent in chunks - async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise { - if (!this.communicator || !this.isAesKeySet()) { - this.log('Communicator not initialized or AES key not set.', 'error'); - return false; - } - - this.log(`Sending message with operationCode: ${operationCode}`); - await this.communicator.sendMessage(operationCode, metaInfo, fileContent); - return true; - } - - // Check if AES key is set - isAesKeySet(): boolean { - if (!this.communicator) return false; - return this.communicator?.isAesKeySet(); - } - - // Check if the message is received (based on if lastResult is available) - isMessageReceived(): boolean { - return this.lastResult !== null; - } - - // Get the last result (and clear it after returning) - getLastResult(): ParsedMessage | null { - const result = this.lastResult; - this.lastResult = null; - return result; - } - - // Check if the socket is still connected - isSocketConnected(): boolean { - const connected = this.socket !== null && !this.socket.destroyed; - this.log(`Socket connected: ${connected}`); - return connected; - } + // Check if the socket is still connected + isSocketConnected(): boolean { + const connected = this.socket !== null && !this.socket.destroyed + this.log(`Socket connected: ${connected}`) + return connected + } } diff --git a/User/src/network/tcp/tcp_server.ts b/User/src/network/tcp/tcp_server.ts index 6e3429f..fc9f016 100644 --- a/User/src/network/tcp/tcp_server.ts +++ b/User/src/network/tcp/tcp_server.ts @@ -1,119 +1,122 @@ -import net, { Socket } from 'net'; -import path from 'path'; -import dotenv from 'dotenv'; -import { ConnectionManager } from "../connection_manager"; -import { TcpServerCommunicator } from "../socket_communicator/tcp_server_communicator"; -import { GeneralOperations } from "../operations_custom/general_operations"; -import { OperationHandler } from "../operations_base/operation_handler"; -import { UserToUserOperations } from "../operations_custom/user_to_user_operations"; +import net, { Socket } from 'net' +import path from 'path' +import dotenv from 'dotenv' +import { ConnectionManager } from '../connection_manager' +import { TcpServerCommunicator } from '../socket_communicator/tcp_server_communicator' +import { GeneralOperations } from '../operations_custom/general_operations' +import { OperationHandler } from '../operations_base/operation_handler' +import { UserToUserOperations } from '../operations_custom/user_to_user_operations' -dotenv.config({ path: path.resolve(__dirname, './config/.env') }); +dotenv.config({ path: path.resolve(__dirname, './config/.env') }) export class TcpServer { - private readonly connectionManager: ConnectionManager; - private readonly operationHandler: OperationHandler; - private readonly port: number; - private readonly host: string; - private clientQueues: Map> = new Map(); + private readonly connectionManager: ConnectionManager + private readonly operationHandler: OperationHandler + private readonly port: number + private readonly host: string + private clientQueues: Map> = new Map() - constructor(host: string, port: number) { - this.connectionManager = new ConnectionManager(); - this.operationHandler = OperationHandler.getInstance(); - this.host = host; - this.port = port; + constructor(host: string, port: number) { + this.connectionManager = new ConnectionManager() + this.operationHandler = OperationHandler.getInstance() + this.host = host + this.port = port - this.operationHandler.loadPlugin(new GeneralOperations()); - this.operationHandler.loadPlugin(new UserToUserOperations()); + this.operationHandler.loadPlugin(new GeneralOperations()) + this.operationHandler.loadPlugin(new UserToUserOperations()) + } + + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[TcpServer]' + console[level === 'error' ? 'error' : 'log'](`${prefix} ${message}`) + } + + public start(): void { + const tcpServer = net.createServer() + + tcpServer.on('connection', (socket: Socket) => { + const ip = socket.remoteAddress || 'unknown' + const port = socket.remotePort || 0 + const clientId = `${ip}:${port}` + + this.log(`Client connected: ${clientId}`) + + const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler) + this.connectionManager.addConnection(ip, port, tcpCommunicator) + + tcpCommunicator + .sendPublicKey() + .then(() => tcpCommunicator.sendAesKey()) + .catch((err) => { + this.log(`Error during key exchange with client ${clientId}: ${err}`, 'error') + socket.end() + }) + + this.clientQueues.set(clientId, Promise.resolve()) + + socket.on('data', (data: Buffer) => { + this.queueClientDataProcessing(data, ip, port) + }) + + socket.on('end', () => { + this.log(`Client disconnected: ${clientId}`) + this.connectionManager.removeCommunicator(ip, port) + this.clientQueues.delete(clientId) + }) + + socket.on('error', (err: Error) => { + this.log(`Error from client ${clientId}: ${err.message}`, 'error') + this.connectionManager.removeCommunicator(ip, port) + this.clientQueues.delete(clientId) + }) + }) + + tcpServer.on('error', (err: Error) => { + this.log(`TCP server error: ${err.message}`, 'error') + }) + + tcpServer.listen(this.port, this.host, () => { + this.log(`TCP server listening on ${this.host}:${this.port}`) + }) + } + + private queueClientDataProcessing(data: Buffer, ip: string, port: number): void { + const clientId = `${ip}:${port}` + const clientQueue = this.clientQueues.get(clientId) || Promise.resolve() + + this.clientQueues.set( + clientId, + clientQueue + .then(() => this.handleData(data, ip, port)) + .catch((error) => { + this.log(`Error handling data for ${clientId}: ${error}`, 'error') + }), + ) + } + + private async handleData(data: Buffer, ip: string, port: number): Promise { + const clientId = `${ip}:${port}` + const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator + if (!communicator) { + this.log(`No communicator found for ${clientId}`, 'error') + return } - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[TcpServer]'; - console[level === 'error' ? 'error' : 'log'](`${prefix} ${message}`); - } - - public start(): void { - const tcpServer = net.createServer(); - - tcpServer.on('connection', (socket: Socket) => { - const ip = socket.remoteAddress || 'unknown'; - const port = socket.remotePort || 0; - const clientId = `${ip}:${port}`; - - this.log(`Client connected: ${clientId}`); - - const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler); - this.connectionManager.addConnection(ip, port, tcpCommunicator); - - tcpCommunicator.sendPublicKey() - .then(() => tcpCommunicator.sendAesKey()) - .catch(err => { - this.log(`Error during key exchange with client ${clientId}: ${err}`, 'error'); - socket.end(); - }); - - this.clientQueues.set(clientId, Promise.resolve()); - - socket.on('data', (data: Buffer) => { - this.queueClientDataProcessing(data, ip, port); - }); - - socket.on('end', () => { - this.log(`Client disconnected: ${clientId}`); - this.connectionManager.removeCommunicator(ip, port); - this.clientQueues.delete(clientId); - }); - - socket.on('error', (err: Error) => { - this.log(`Error from client ${clientId}: ${err.message}`, 'error'); - this.connectionManager.removeCommunicator(ip, port); - this.clientQueues.delete(clientId); - }); - }); - - tcpServer.on('error', (err: Error) => { - this.log(`TCP server error: ${err.message}`, 'error'); - }); - - tcpServer.listen(this.port, this.host, () => { - this.log(`TCP server listening on ${this.host}:${this.port}`); - }); - } - - private queueClientDataProcessing(data: Buffer, ip: string, port: number): void { - const clientId = `${ip}:${port}`; - const clientQueue = this.clientQueues.get(clientId) || Promise.resolve(); - - this.clientQueues.set( - clientId, - clientQueue.then(() => this.handleData(data, ip, port)).catch(error => { - this.log(`Error handling data for ${clientId}: ${error}`, 'error'); - }) - ); - } - - private async handleData(data: Buffer, ip: string, port: number): Promise { - const clientId = `${ip}:${port}`; - const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator; - if (!communicator) { - this.log(`No communicator found for ${clientId}`, 'error'); - return; - } - - await communicator.handleIncomingChunk(data); - - // Check if message is complete before fetching result - const handlerResult = communicator.getHandlerResult(); - if (handlerResult) { - try { - await communicator.sendMessage( - handlerResult.operationCode, - handlerResult.metaInfo, - handlerResult.fileContent - ); - this.log(`Response sent to ${clientId}`); - } catch (err) { - this.log(`Failed to send response to ${clientId}: ${err}`, 'error'); - } - } + await communicator.handleIncomingChunk(data) + + // Check if message is complete before fetching result + const handlerResult = communicator.getHandlerResult() + if (handlerResult) { + try { + await communicator.sendMessage( + handlerResult.operationCode, + handlerResult.metaInfo, + handlerResult.fileContent, + ) + this.log(`Response sent to ${clientId}`) + } catch (err) { + this.log(`Failed to send response to ${clientId}: ${err}`, 'error') + } } + } } diff --git a/User/src/network/udp/udp_client.ts b/User/src/network/udp/udp_client.ts index a16c2e1..11002b4 100644 --- a/User/src/network/udp/udp_client.ts +++ b/User/src/network/udp/udp_client.ts @@ -1,161 +1,161 @@ -import dgram from 'dgram'; -import ping from 'ping'; -import { OperationHandler } from '../operations_base/operation_handler'; -import { MessageHandler } from '../message_handler'; -import { GeneralOperations } from "../operations_custom/general_operations"; -import { operationCodes } from "../operation_codes"; -import os from 'os'; +import dgram from 'dgram' +import ping from 'ping' +import { OperationHandler } from '../operations_base/operation_handler' +import { MessageHandler } from '../message_handler' +import { GeneralOperations } from '../operations_custom/general_operations' +import { operationCodes } from '../operation_codes' +import os from 'os' export class UdpClient { - private udpSocket: dgram.Socket; - private readonly port: number; - private operationHandler: OperationHandler; + private udpSocket: dgram.Socket + private readonly port: number + private operationHandler: OperationHandler - constructor(port: number) { - this.port = port; - this.udpSocket = dgram.createSocket('udp4'); - this.operationHandler = OperationHandler.getInstance(); - this.operationHandler.loadPlugin(new GeneralOperations()); + constructor(port: number) { + this.port = port + this.udpSocket = dgram.createSocket('udp4') + this.operationHandler = OperationHandler.getInstance() + this.operationHandler.loadPlugin(new GeneralOperations()) + } + + // Unified logging function + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[UdpClient]' + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) + } + } + + // Send heartbeat and get clients that respond with ALIVE, excluding the host machine's IP addresses + async getTargetClients(heartbeatCode: string): Promise { + const subnet = this.getSubnet() + const ipRange = this.getIPRange(subnet) + + // Get local machine's IP addresses to exclude + const localIPs = this.getLocalIPs() + this.log(`Local IPs to exclude from scan: ${localIPs.join(', ')}`) + + // First, filter active IPs that respond to ping + const activeIps = await this.filterActiveIps(ipRange) + this.log(`Active IPs in subnet ${subnet}: ${activeIps.join(', ')}`) + + // Send heartbeat to each active IP and keep only those that respond with ALIVE + const aliveClients: string[] = [] + for (const ip of activeIps) { + if (!localIPs.includes(ip)) { + const result = await this.sendHeartbeat(ip, heartbeatCode) + if (result.found) { + aliveClients.push(ip) + } + } } - // Unified logging function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[UdpClient]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); + this.log(`Alive clients (excluding local machine): ${aliveClients.join(', ')}`) + return aliveClients + } + + // Get local IP addresses of the host machine (excluding loopback) + private getLocalIPs(): string[] { + const interfaces = os.networkInterfaces() + const localIPs: string[] = [] + + Object.values(interfaces).forEach((iface) => { + iface?.forEach((address) => { + if (address.family === 'IPv4' && !address.internal) { + localIPs.push(address.address) + } + }) + }) + + return localIPs + } + + // Send heartbeat to an IP + private async sendHeartbeat(ip: string, heartbeatCode: string): Promise<{ found: boolean }> { + return new Promise((resolve) => { + const heartbeatMessage = MessageHandler.formatMessage(heartbeatCode) + + this.log(`Sending heartbeat to ${ip}`) + this.udpSocket.send(heartbeatMessage, this.port, ip, (err) => { + if (err) { + this.log(`Failed to send heartbeat to ${ip}: ${err.message}`, 'error') + resolve({ found: false }) } else { - console.log(`${prefix} ${message}`); - } - } + const timeout = setTimeout(() => { + this.dropConnection(ip) + resolve({ found: false }) + }, 1500) - // Send heartbeat and get clients that respond with ALIVE, excluding the host machine's IP addresses - async getTargetClients(heartbeatCode: string): Promise { - const subnet = this.getSubnet(); - const ipRange = this.getIPRange(subnet); - - // Get local machine's IP addresses to exclude - const localIPs = this.getLocalIPs(); - this.log(`Local IPs to exclude from scan: ${localIPs.join(', ')}`); - - // First, filter active IPs that respond to ping - const activeIps = await this.filterActiveIps(ipRange); - this.log(`Active IPs in subnet ${subnet}: ${activeIps.join(', ')}`); - - // Send heartbeat to each active IP and keep only those that respond with ALIVE - const aliveClients: string[] = []; - for (const ip of activeIps) { - if (!localIPs.includes(ip)) { - const result = await this.sendHeartbeat(ip, heartbeatCode); - if (result.found) { - aliveClients.push(ip); - } + this.udpSocket.once('message', (msg, rinfo) => { + if (rinfo.address === ip) { + clearTimeout(timeout) + const parsedMessage = MessageHandler.parseMessage(msg.toString()) + if (parsedMessage?.operationCode === operationCodes.ALIVE) { + this.log(`Received ALIVE response from ${ip}`) + resolve({ found: true }) + } else { + this.log(`Unexpected response from ${ip}`) + resolve({ found: false }) + } } + }) } + }) + }) + } - this.log(`Alive clients (excluding local machine): ${aliveClients.join(', ')}`); - return aliveClients; + // Drop connection for a specific IP + private dropConnection(ip: string): void { + try { + this.udpSocket.removeAllListeners('message') + this.log(`Dropped connection listeners for ${ip}`) + } catch (err: any) { + this.log(`Error dropping connection to ${ip}: ${err.message}`, 'error') } + } - // Get local IP addresses of the host machine (excluding loopback) - private getLocalIPs(): string[] { - const interfaces = os.networkInterfaces(); - const localIPs: string[] = []; - - Object.values(interfaces).forEach((iface) => { - iface?.forEach((address) => { - if (address.family === 'IPv4' && !address.internal) { - localIPs.push(address.address); - } - }); - }); - - return localIPs; - } - - // Send heartbeat to an IP - private async sendHeartbeat(ip: string, heartbeatCode: string): Promise<{ found: boolean }> { - return new Promise((resolve) => { - const heartbeatMessage = MessageHandler.formatMessage(heartbeatCode); - - this.log(`Sending heartbeat to ${ip}`); - this.udpSocket.send(heartbeatMessage, this.port, ip, (err) => { - if (err) { - this.log(`Failed to send heartbeat to ${ip}: ${err.message}`, 'error'); - resolve({ found: false }); - } else { - const timeout = setTimeout(() => { - this.dropConnection(ip); - resolve({ found: false }); - }, 1500); - - this.udpSocket.once('message', (msg, rinfo) => { - if (rinfo.address === ip) { - clearTimeout(timeout); - const parsedMessage = MessageHandler.parseMessage(msg.toString()); - if (parsedMessage?.operationCode === operationCodes.ALIVE) { - this.log(`Received ALIVE response from ${ip}`); - resolve({ found: true }); - } else { - this.log(`Unexpected response from ${ip}`); - resolve({ found: false }); - } - } - }); - } - }); - }); - } - - // Drop connection for a specific IP - private dropConnection(ip: string): void { - try { - this.udpSocket.removeAllListeners('message'); - this.log(`Dropped connection listeners for ${ip}`); - } catch (err: any) { - this.log(`Error dropping connection to ${ip}: ${err.message}`, 'error'); + // Get the subnet (e.g., 192.168.1) + private getSubnet(): string { + const interfaces = os.networkInterfaces() + for (const iface of Object.values(interfaces)) { + for (const address of iface || []) { + if (address.family === 'IPv4' && !address.internal) { + const subnet = address.address.split('.').slice(0, 3).join('.') + this.log(`Detected subnet: ${subnet}`) + return subnet } + } + } + return '' + } + + // Get IP range (assuming /24 subnet) + private getIPRange(subnet: string): string[] { + const ipRange = [] + for (let i = 1; i < 255; i++) { + ipRange.push(`${subnet}.${i}`) + } + this.log(`Generated IP range for subnet ${subnet}`) + return ipRange + } + + // Filter only active IPs by pinging each IP in the range + private async filterActiveIps(ipRange: string[]): Promise { + const activeIps: string[] = [] + + const pingPromises = ipRange.map((ip) => ping.promise.probe(ip, { timeout: 1 })) + + const pingResults = await Promise.all(pingPromises) + + for (const result of pingResults) { + if (result.alive) { + activeIps.push(result.host) + } } - // Get the subnet (e.g., 192.168.1) - private getSubnet(): string { - const interfaces = os.networkInterfaces(); - for (const iface of Object.values(interfaces)) { - for (const address of iface || []) { - if (address.family === 'IPv4' && !address.internal) { - const subnet = address.address.split('.').slice(0, 3).join('.'); - this.log(`Detected subnet: ${subnet}`); - return subnet; - } - } - } - return ''; - } - - // Get IP range (assuming /24 subnet) - private getIPRange(subnet: string): string[] { - const ipRange = []; - for (let i = 1; i < 255; i++) { - ipRange.push(`${subnet}.${i}`); - } - this.log(`Generated IP range for subnet ${subnet}`); - return ipRange; - } - - // Filter only active IPs by pinging each IP in the range - private async filterActiveIps(ipRange: string[]): Promise { - const activeIps: string[] = []; - - const pingPromises = ipRange.map(ip => ping.promise.probe(ip, { timeout: 1 })); - - const pingResults = await Promise.all(pingPromises); - - for (const result of pingResults) { - if (result.alive) { - activeIps.push(result.host); - } - } - - this.log(`Active IPs after pinging: ${activeIps.join(', ')}`); - return activeIps; - } + this.log(`Active IPs after pinging: ${activeIps.join(', ')}`) + return activeIps + } } diff --git a/User/src/network/udp/udp_server.ts b/User/src/network/udp/udp_server.ts index c18aaa5..0cb7f4f 100644 --- a/User/src/network/udp/udp_server.ts +++ b/User/src/network/udp/udp_server.ts @@ -1,77 +1,80 @@ -import dgram, { RemoteInfo } from 'dgram'; -import { UdpSocketCommunicator } from "../socket_communicator/udp_socket_communicator"; -import { OperationHandler } from "../operations_base/operation_handler"; -import { GeneralOperations } from "../operations_custom/general_operations"; +import dgram, { RemoteInfo } from 'dgram' +import { UdpSocketCommunicator } from '../socket_communicator/udp_socket_communicator' +import { OperationHandler } from '../operations_base/operation_handler' +import { GeneralOperations } from '../operations_custom/general_operations' export class UdpServer { - private readonly udpServer: dgram.Socket; - private readonly operationHandler: OperationHandler; - private readonly port: number; - private readonly host: string; + private readonly udpServer: dgram.Socket + private readonly operationHandler: OperationHandler + private readonly port: number + private readonly host: string - constructor(host: string, port: number) { - this.udpServer = dgram.createSocket('udp4'); - this.operationHandler = OperationHandler.getInstance(); - this.host = host; - this.port = port; + constructor(host: string, port: number) { + this.udpServer = dgram.createSocket('udp4') + this.operationHandler = OperationHandler.getInstance() + this.host = host + this.port = port - // Load only the GeneralOperations into the operation handler - this.operationHandler.loadPlugin(new GeneralOperations()); + // Load only the GeneralOperations into the operation handler + this.operationHandler.loadPlugin(new GeneralOperations()) + } + + // Unified logging function + private log(message: string, level: 'log' | 'error' = 'log'): void { + const prefix = '[UdpServer]' + if (level === 'error') { + console.error(`${prefix} ${message}`) + } else { + console.log(`${prefix} ${message}`) } + } - // Unified logging function - private log(message: string, level: 'log' | 'error' = 'log'): void { - const prefix = '[UdpServer]'; - if (level === 'error') { - console.error(`${prefix} ${message}`); - } else { - console.log(`${prefix} ${message}`); - } + // Start the UDP server + public start(): void { + this.udpServer.on('message', this.handleUdpMessages.bind(this)) + this.udpServer.on('error', this.handleError.bind(this)) + this.udpServer.on('listening', this.handleListening.bind(this)) + + // Bind the server to the UDP port and host + this.udpServer.bind(this.port, this.host) + } + + // Handle incoming UDP messages + private async handleUdpMessages(msg: Buffer, rinfo: RemoteInfo): Promise { + const ip = rinfo.address + const port = rinfo.port + + this.log(`Received message from ${ip}:${port}`) + + // Create a temporary communicator for the incoming message + const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler) + + // Process the incoming message using the communicator + await communicator.handleIncomingMessage(msg.toString()) + + const communicatorResult = communicator.getHandlerResult() + if (communicatorResult) { + // Send response back to the client using the temporary communicator + try { + await communicator.sendMessage( + communicatorResult.operationCode, + communicatorResult.metaInfo, + ) + this.log(`Sent response to ${ip}:${port}`) + } catch (error: any) { + this.log(`Error sending response to ${ip}:${port}: ${error.message}`, 'error') + } } + } - // Start the UDP server - public start(): void { - this.udpServer.on('message', this.handleUdpMessages.bind(this)); - this.udpServer.on('error', this.handleError.bind(this)); - this.udpServer.on('listening', this.handleListening.bind(this)); + // Handle UDP server errors + private handleError(err: Error): void { + this.log(`UDP server error:\n${err.stack}`, 'error') + } - // Bind the server to the UDP port and host - this.udpServer.bind(this.port, this.host); - } - - // Handle incoming UDP messages - private async handleUdpMessages(msg: Buffer, rinfo: RemoteInfo): Promise { - const ip = rinfo.address; - const port = rinfo.port; - - this.log(`Received message from ${ip}:${port}`); - - // Create a temporary communicator for the incoming message - const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler); - - // Process the incoming message using the communicator - await communicator.handleIncomingMessage(msg.toString()); - - const communicatorResult = communicator.getHandlerResult(); - if (communicatorResult) { - // Send response back to the client using the temporary communicator - try { - await communicator.sendMessage(communicatorResult.operationCode, communicatorResult.metaInfo); - this.log(`Sent response to ${ip}:${port}`); - } catch (error: any) { - this.log(`Error sending response to ${ip}:${port}: ${error.message}`, 'error'); - } - } - } - - // Handle UDP server errors - private handleError(err: Error): void { - this.log(`UDP server error:\n${err.stack}`, 'error'); - } - - // Handle when the UDP server starts listening - private handleListening(): void { - const address = this.udpServer.address(); - this.log(`UDP server listening on ${address.address}:${address.port}`); - } + // Handle when the UDP server starts listening + private handleListening(): void { + const address = this.udpServer.address() + this.log(`UDP server listening on ${address.address}:${address.port}`) + } } diff --git a/User/src/workers/backup_retrieval_worker.ts b/User/src/workers/backup_retrieval_worker.ts index ef1a3dd..eb87516 100644 --- a/User/src/workers/backup_retrieval_worker.ts +++ b/User/src/workers/backup_retrieval_worker.ts @@ -1,50 +1,43 @@ -import { BackupRetrievalWorker } from '../helpers/backup_retrieval'; -import dotenv from 'dotenv'; +import { BackupRetrievalWorker } from '../helpers/backup_retrieval' +import dotenv from 'dotenv' // Load environment variables from .env file if it exists -dotenv.config(); +dotenv.config() // Retrieve configuration from environment variables -const userConfigPath = process.env.USER_CONFIG_PATH as string; -const applicationInfoPath = process.env.APPLICATION_INFO_PATH as string; -const clientPort = Number(process.env.CLIENT_PORT); -const destinationPath = process.env.DESTINATION_PATH as string; +const clientPort = Number(process.env.CLIENT_PORT) +const destinationPath = process.env.DESTINATION_PATH as string +const pathToDatabaseFile = process.env.DATABASE_FILE_PATH as string // Validate that all required environment variables are present -if (!userConfigPath || !applicationInfoPath || !clientPort || !destinationPath) { - console.error('Error: Missing required environment variables.'); - process.exit(1); +if (!pathToDatabaseFile || !clientPort || !destinationPath) { + console.error('Error: Missing required environment variables.') + process.exit(1) } // Initialize the BackupRetrievalWorker const backupRetrievalWorker = new BackupRetrievalWorker( - userConfigPath, - applicationInfoPath, - clientPort, - destinationPath -); + pathToDatabaseFile, + clientPort, + destinationPath, +) // Start the backup retrieval process backupRetrievalWorker.start().then(() => { - console.log('Backup retrieval process completed successfully.'); -}); + console.log('Backup retrieval process completed successfully.') +}) process.on('SIGTERM', async () => { - console.log('Received SIGTERM. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGTERM. Cleaning up...') + await cleanupAndExit() +}) process.on('SIGINT', async () => { - console.log('Received SIGINT. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGINT. Cleaning up...') + await cleanupAndExit() +}) async function cleanupAndExit() { - // Perform any cleanup, such as closing connections, saving data, etc. - // Example: if you have a server instance running, you may want to close it: - // await server.close(); - - console.log('Cleanup complete. Exiting.'); - process.exit(0); // Exit with code 0 to indicate a clean exit + console.log('Cleanup complete. Exiting.') + process.exit(0) } - diff --git a/User/src/workers/directories_watcher_worker.ts b/User/src/workers/directories_watcher_worker.ts index b864f40..bc304df 100644 --- a/User/src/workers/directories_watcher_worker.ts +++ b/User/src/workers/directories_watcher_worker.ts @@ -1,44 +1,34 @@ -import { DirectoryWatcher } from "../helpers/directory_watcher"; -import dotenv from 'dotenv'; +import { DirectoryWatcher } from '../helpers/directory_watcher' +import dotenv from 'dotenv' // Load environment variables from .env file if it exists -dotenv.config(); +dotenv.config() -// Retrieve configuration from environment variables -const memoryManagerPath = process.env.MEMORY_MANAGER_PATH as string; -const applicationInfoPath = process.env.APPLICATION_INFO_PATH as string; +const pathToDatabaseFile = process.env.DATABASE_FILE_PATH as string // Validate that all required environment variables are present -if (!memoryManagerPath || !applicationInfoPath) { - console.error('Error: Missing required environment variables.'); - process.exit(1); +if (!pathToDatabaseFile) { + console.error('Error: Missing required environment variables.') + process.exit(1) } // Initialize and start DirectoryWatcher instances -const backupDirectoryManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'backupDirectory'); -backupDirectoryManager.start(); - -const departmentShareManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'departmentDirectory'); -departmentShareManager.start(); - -const shareFileManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'shareDirectory'); -shareFileManager.start(); +const watcher = new DirectoryWatcher(pathToDatabaseFile) +watcher.start() process.on('SIGTERM', async () => { - console.log('Received SIGTERM. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGTERM. Cleaning up...') + await cleanupAndExit() +}) process.on('SIGINT', async () => { - console.log('Received SIGINT. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGINT. Cleaning up...') + await cleanupAndExit() +}) async function cleanupAndExit() { - backupDirectoryManager.closeWatcher(); - departmentShareManager.closeWatcher(); - shareFileManager.closeWatcher(); + watcher.stopAllWatchers() - console.log('Cleanup complete. Exiting.'); - process.exit(0); // Exit with code 0 to indicate a clean exit + console.log('Cleanup complete. Exiting.') + process.exit(0) // Exit with code 0 to indicate a clean exit } diff --git a/User/src/workers/network_scanner_worker.ts b/User/src/workers/network_scanner_worker.ts index f60bf03..6d2714b 100644 --- a/User/src/workers/network_scanner_worker.ts +++ b/User/src/workers/network_scanner_worker.ts @@ -1,39 +1,37 @@ -import { parentPort } from 'worker_threads'; -import { NetworkScanner } from '../helpers/network_scanner'; +import { parentPort } from 'worker_threads' +import { NetworkScanner } from '../helpers/network_scanner' // Extract data from environment variables -const udpPort = parseInt(process.env.UDP_PORT || '0', 10); -const tcpPort = parseInt(process.env.TCP_PORT || '0', 10); -const okPage = process.env.OK_PAGE || ''; -const errorPage = process.env.ERROR_PAGE || ''; -const databaseResetPage = process.env.DATABASE_RESET_PAGE || ''; -const userConfigPath = process.env.USER_CONFIG_PATH || ''; -const applicationInfoPath = process.env.APPLICATION_INFO_PATH || ''; +const udpPort = parseInt(process.env.UDP_PORT || '0', 10) +const tcpPort = parseInt(process.env.TCP_PORT || '0', 10) +const okPage = process.env.OK_PAGE || '' +const errorPage = process.env.ERROR_PAGE || '' +const databaseResetPage = process.env.DATABASE_RESET_PAGE || '' +const pathToDatabaseFile = process.env.DATABASE_FILE_PATH || '' // Start the NetworkScanner instance const networkScanner = new NetworkScanner( - applicationInfoPath, - userConfigPath, - udpPort, - tcpPort, - okPage, - errorPage, - databaseResetPage -); + pathToDatabaseFile, + udpPort, + tcpPort, + okPage, + errorPage, + databaseResetPage, +) process.on('SIGTERM', async () => { - console.log('Received SIGTERM. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGTERM. Cleaning up...') + await cleanupAndExit() +}) process.on('SIGINT', async () => { - console.log('Received SIGINT. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGINT. Cleaning up...') + await cleanupAndExit() +}) async function cleanupAndExit() { - networkScanner.stopAllIntervals(); + networkScanner.stopAllIntervals() - console.log('Cleanup complete. Exiting.'); - process.exit(0); // Exit with code 0 to indicate a clean exit + console.log('Cleanup complete. Exiting.') + process.exit(0) // Exit with code 0 to indicate a clean exit } diff --git a/User/src/workers/resource_coordinator_worker.ts b/User/src/workers/resource_coordinator_worker.ts index c10513c..348bddc 100644 --- a/User/src/workers/resource_coordinator_worker.ts +++ b/User/src/workers/resource_coordinator_worker.ts @@ -1,43 +1,40 @@ -import { UsersInfoFetcher } from '../helpers/users_info_fetcher'; -import { BackupManager } from '../helpers/backup_manager'; -import { FileSharer } from '../helpers/file_sharer'; -import { DepartmentSharer } from '../helpers/department_sharer'; +import { UsersInfoFetcher } from '../helpers/users_info_fetcher' +import { BackupManager } from '../helpers/backup_manager' +import { FileSharer } from '../helpers/file_sharer' +import { DepartmentSharer } from '../helpers/department_sharer' // Retrieve data from environment variables -const usersConfigPath = process.env.USERS_CONFIG_PATH || ''; -const applicationInfoPath = process.env.APPLICATION_INFO_PATH || ''; -const memoryManagerPath = process.env.MEMORY_MANAGER_PATH || ''; -const queueManagerPath = process.env.QUEUE_MANAGER_PATH || ''; -const tcpPort = parseInt(process.env.TCP_PORT || '0', 10); +const pathToDatabaseFile = process.env.DATABASE_FILE_PATH || '' +const tcpPort = parseInt(process.env.TCP_PORT || '0', 10) -const usersInfoFetcher = new UsersInfoFetcher(applicationInfoPath, memoryManagerPath, tcpPort); -usersInfoFetcher.start(); +const usersInfoFetcher = new UsersInfoFetcher(pathToDatabaseFile, tcpPort) +usersInfoFetcher.start() -const backupManager = new BackupManager(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort); -backupManager.start(); +const backupManager = new BackupManager(pathToDatabaseFile, tcpPort) +backupManager.start() -const fileSharer = new FileSharer(queueManagerPath, tcpPort); -fileSharer.start(); +const departmentSharer = new DepartmentSharer(pathToDatabaseFile, tcpPort) +departmentSharer.start() -const departmentSharer = new DepartmentSharer(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort); -departmentSharer.start(); +const fileSharer = new FileSharer(pathToDatabaseFile, tcpPort) +fileSharer.start() process.on('SIGTERM', async () => { - console.log('Received SIGTERM. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGTERM. Cleaning up...') + await cleanupAndExit() +}) process.on('SIGINT', async () => { - console.log('Received SIGINT. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGINT. Cleaning up...') + await cleanupAndExit() +}) async function cleanupAndExit() { - usersInfoFetcher.stop(); - await backupManager.stop(); - await fileSharer.stop(); - await departmentSharer.stop(); + usersInfoFetcher.stop() + await backupManager.stop() + await fileSharer.stop() + await departmentSharer.stop() - console.log('Cleanup complete. Exiting.'); - process.exit(0); // Exit with code 0 to indicate a clean exit + console.log('Cleanup complete. Exiting.') + process.exit(0) // Exit with code 0 to indicate a clean exit } diff --git a/User/src/workers/servers_worker.ts b/User/src/workers/servers_worker.ts index 1555807..10d7488 100644 --- a/User/src/workers/servers_worker.ts +++ b/User/src/workers/servers_worker.ts @@ -1,36 +1,36 @@ -import { UdpServer } from "../network/udp/udp_server"; -import { TcpServer } from "../network/tcp/tcp_server"; +import { UdpServer } from '../network/udp/udp_server' +import { TcpServer } from '../network/tcp/tcp_server' let udpServer: UdpServer | null let tcpServer: TcpServer | null // Retrieve data from environment variables -const USER_UDP_PORT = parseInt(process.env.USER_UDP_PORT || '0', 10); -const USER_TCP_PORT = parseInt(process.env.USER_TCP_PORT || '0', 10); -const HOST = process.env.HOST || ''; +const USER_UDP_PORT = parseInt(process.env.USER_UDP_PORT || '0', 10) +const USER_TCP_PORT = parseInt(process.env.USER_TCP_PORT || '0', 10) +const HOST = process.env.HOST || '' // Initialize and start the servers -udpServer = new UdpServer(HOST, USER_UDP_PORT); -udpServer.start(); +udpServer = new UdpServer(HOST, USER_UDP_PORT) +udpServer.start() -tcpServer = new TcpServer(HOST, USER_TCP_PORT); -tcpServer.start(); +tcpServer = new TcpServer(HOST, USER_TCP_PORT) +tcpServer.start() process.on('SIGTERM', async () => { - console.log('Received SIGTERM. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGTERM. Cleaning up...') + await cleanupAndExit() +}) process.on('SIGINT', async () => { - console.log('Received SIGINT. Cleaning up...'); - await cleanupAndExit(); -}); + console.log('Received SIGINT. Cleaning up...') + await cleanupAndExit() +}) async function cleanupAndExit() { - // Perform any cleanup, such as closing connections, saving data, etc. - // Example: if you have a server instance running, you may want to close it: - // await server.close(); + // Perform any cleanup, such as closing connections, saving data, etc. + // Example: if you have a server instance running, you may want to close it: + // await server.close(); - console.log('Cleanup complete. Exiting.'); - process.exit(0); // Exit with code 0 to indicate a clean exit + console.log('Cleanup complete. Exiting.') + process.exit(0) // Exit with code 0 to indicate a clean exit } diff --git a/User/tsconfig.json b/User/tsconfig.json index a62789c..41891ac 100644 --- a/User/tsconfig.json +++ b/User/tsconfig.json @@ -9,7 +9,7 @@ "moduleResolution": "node" }, "include": [ - "src/**/*" + "src/**/*.ts" ], "exclude": [ "node_modules"