added email verification
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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<ParsedMessage> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
dist
|
||||
package-lock.json
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100
|
||||
}
|
||||
Generated
+36
-9
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 <br> tags
|
||||
let formattedText = text.replace(/\n/g, '<br>');
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
+33
-42
@@ -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;
|
||||
}
|
||||
|
||||
+33
-83
@@ -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);
|
||||
}
|
||||
|
||||
+17
-14
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+11
-11
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T, Q> {
|
||||
private readonly filePath: string
|
||||
private memoryStore: MemoryManager<Q>
|
||||
private queueStore: QueueManager
|
||||
|
||||
constructor(filePath: string) {
|
||||
this.filePath = filePath
|
||||
this.memoryStore = new MemoryManager<Q>()
|
||||
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<void> {
|
||||
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<void> {
|
||||
await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 })
|
||||
}
|
||||
|
||||
// Read JSON from file
|
||||
async read(): Promise<T> {
|
||||
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<T>) => Awaited<T> | Promise<Awaited<T>>,
|
||||
): Promise<void> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
export class MemoryManager<T> {
|
||||
private storage: Map<string, T>
|
||||
|
||||
constructor() {
|
||||
this.storage = new Map<string, T>()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
@@ -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 = []
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface NetworkUserScheme {
|
||||
id: string
|
||||
ip: string
|
||||
name: string
|
||||
departmentId: string
|
||||
}
|
||||
|
||||
export interface NetworkScheme {
|
||||
serverIp: string
|
||||
usersInLan: NetworkUserScheme[]
|
||||
announcement: string
|
||||
}
|
||||
+151
-168
@@ -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<DatabaseScheme, any>
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
this.isBusy = true;
|
||||
return this.fileEncryptor.encryptFileToBase64(filePath)
|
||||
}
|
||||
|
||||
private async sendFilesToUsers(
|
||||
userName: string,
|
||||
usersIp: string[],
|
||||
fileStructure: { [key: string]: string },
|
||||
backupDirectoryPath: string,
|
||||
): Promise<void> {
|
||||
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<ParsedMessage | null> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<ParsedMessage | null> {
|
||||
return new Promise((resolve) => {
|
||||
const idResponseCheck = setInterval(() => {
|
||||
if (!tcpCommunicator) return null;
|
||||
if (tcpCommunicator.hasResponseArrived()) {
|
||||
clearInterval(idResponseCheck);
|
||||
resolve(tcpCommunicator.getLastResult());
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DatabaseScheme, any>
|
||||
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<DatabaseScheme, any>(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<void> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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<any> {
|
||||
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<boolean> {
|
||||
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<any> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<ParsedMessage | null> {
|
||||
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<ParsedMessage | null> {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DatabaseScheme, any>
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<ParsedMessage | null> {
|
||||
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<ParsedMessage | null> {
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string, FSWatcher> // Stores watchers with directory ID as the key
|
||||
private readonly db: JsonDatabase<DatabaseScheme, any>
|
||||
private totalSizes: Map<string, number> // 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<DatabaseScheme, any>(pathToDatabaseFile)
|
||||
this.watchers = new Map<string, FSWatcher>()
|
||||
this.totalSizes = new Map<string, number>()
|
||||
}
|
||||
|
||||
// Start the watcher and register all directories
|
||||
async start(): Promise<void> {
|
||||
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<void> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+132
-132
@@ -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<FileSendTask>;
|
||||
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<DatabaseScheme, any>
|
||||
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<FileItemTask>(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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
while (!this.queueManager.isEmpty()) {
|
||||
const task = this.queueManager.peek();
|
||||
this.log('trimiti fisier');
|
||||
// Method to process the queue
|
||||
private async processQueue(): Promise<void> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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<ParsedMessage | null> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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<ParsedMessage | null> {
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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<any | null> {
|
||||
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<boolean> {
|
||||
await this.acquireLock(); // Acquire the lock
|
||||
// Read a value by key from the JSON file with a lock
|
||||
public async readValue(key: string): Promise<any | null> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
await this.acquireLock(); // Acquire the lock
|
||||
// Remove a key-value pair from the JSON file with a lock
|
||||
public async removeValue(key: string): Promise<boolean> {
|
||||
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<boolean> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> {
|
||||
const generate = async (): Promise<string> => {
|
||||
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<string> {
|
||||
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<any | null> {
|
||||
return this.readValue(guid);
|
||||
}
|
||||
|
||||
// Update meta information by merging new data into existing data
|
||||
public async updateMetaInformation(guid: string, newMetaInfo: any): Promise<boolean> {
|
||||
return await this.writeValue(guid, newMetaInfo);
|
||||
}
|
||||
|
||||
// Remove meta information using the GUID
|
||||
public removeMetaInformation(guid: string): Promise<boolean> {
|
||||
return this.removeValue(guid);
|
||||
}
|
||||
}
|
||||
+198
-167
@@ -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<DatabaseScheme, any>
|
||||
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<DatabaseScheme, any>(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<ParsedMessage | null> {
|
||||
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<ParsedMessage | null> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
this.log('All intervals have been stopped.', 'log', 'stopAllIntervals')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export class QueueManager<T> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<boolean> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<boolean> {
|
||||
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true;
|
||||
this.tcpClient.closeSocket();
|
||||
return true;
|
||||
}
|
||||
async disconnect(): Promise<boolean> {
|
||||
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true
|
||||
this.tcpClient.closeSocket()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DatabaseScheme, any>
|
||||
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<void> {
|
||||
this.intervalId = setInterval(async () => {
|
||||
await this.initialize(); // Re-run every minute
|
||||
// Start fetching user info periodically
|
||||
async start(): Promise<void> {
|
||||
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<ParsedMessage | null> {
|
||||
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<ParsedMessage | null> {
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
|
||||
+142
-142
@@ -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<void> {
|
||||
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<void> {
|
||||
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<string | undefined> {
|
||||
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<void> {
|
||||
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<string | undefined> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<string | undefined> {
|
||||
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<void> {
|
||||
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<string | undefined> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
if (this.announcementWindow) {
|
||||
this.announcementWindow.close()
|
||||
this.log('Announcement window closed by user.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+116
-106
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
return this.startForkedWorker('directories_watcher_worker.js', {
|
||||
MEMORY_MANAGER_PATH: memoryManagerPath,
|
||||
APPLICATION_INFO_PATH: applicationInfoPath
|
||||
});
|
||||
}
|
||||
async startDirectoriesWatchersWorker(pathToDatabaseFile: string): Promise<void> {
|
||||
return this.startForkedWorker('directories_watcher_worker.js', {
|
||||
DATABASE_FILE_PATH: pathToDatabaseFile,
|
||||
})
|
||||
}
|
||||
|
||||
async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
interface RegisteredClient {
|
||||
id: string;
|
||||
ip: string;
|
||||
port: number;
|
||||
}
|
||||
id: string
|
||||
ip: string
|
||||
port: number
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<DatabaseScheme, any>
|
||||
|
||||
constructor(pathToDatabaseFile: string) {
|
||||
this.db = new JsonDatabase(pathToDatabaseFile)
|
||||
}
|
||||
|
||||
async getAppType(): Promise<string> {
|
||||
const data = await this.db.read()
|
||||
return data.app_config.app_type
|
||||
}
|
||||
|
||||
async getUserInfo(): Promise<UserInfoScheme> {
|
||||
const data = await this.db.read()
|
||||
return data.app_config.user_info
|
||||
}
|
||||
|
||||
async getActiveUsers(): Promise<any> {
|
||||
const data = await this.db.read()
|
||||
return data.network.usersInLan
|
||||
}
|
||||
|
||||
async getLocalResources(): Promise<DirectorySchemes> {
|
||||
const data = await this.db.read()
|
||||
return data.local_resources.directory_schemes
|
||||
}
|
||||
|
||||
async getDirectoryInfo(id: string): Promise<DirectoryInfo> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
const data = await this.db.read()
|
||||
return data.local_resources.directory_schemes.backup.path !== ''
|
||||
}
|
||||
|
||||
async writeUserInfo(userInfo: UserInfoScheme): Promise<boolean> {
|
||||
await this.db.update((data) => {
|
||||
data.app_config.user_info = userInfo
|
||||
return data
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async writeEncryptionKey(encryptionKey: EncryptionKeyScheme): Promise<boolean> {
|
||||
await this.db.update((data) => {
|
||||
data.app_config.encryption_key = encryptionKey
|
||||
return data
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async setLoginStatus(status: boolean): Promise<boolean> {
|
||||
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<boolean> {
|
||||
await this.db.reset()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export const ipcDatabaseHandler = new IpcDatabaseHandler(pathToDatabaseFile)
|
||||
@@ -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<DatabaseScheme, any>
|
||||
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<boolean> {
|
||||
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)
|
||||
+183
-290
@@ -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<FileItemTask> | 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<DatabaseScheme, any> | 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<void> {
|
||||
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<DatabaseScheme, any>(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
|
||||
);
|
||||
});
|
||||
}
|
||||
// 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)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+54
-43
@@ -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<string> => ipcDatabaseHandler.getAppType(),
|
||||
getUserInfo: (): Promise<UserInfoScheme> => ipcDatabaseHandler.getUserInfo(),
|
||||
getActiveUsers: (): Promise<NetworkUserScheme> => ipcDatabaseHandler.getActiveUsers(),
|
||||
getLocalResources: (): Promise<DirectorySchemes> => ipcDatabaseHandler.getLocalResources(),
|
||||
getDirectoryInfo: (id: string): Promise<DirectoryInfo> => ipcDatabaseHandler.getDirectoryInfo(id),
|
||||
setLoginStatus: (status: boolean): Promise<boolean> => ipcDatabaseHandler.setLoginStatus(status),
|
||||
isBackupSet: (): Promise<boolean> => ipcDatabaseHandler.isBackupSet(),
|
||||
writeUserInfo: (userInfo: UserInfoScheme): Promise<boolean> =>
|
||||
ipcDatabaseHandler.writeUserInfo(userInfo),
|
||||
writeEncryptionKey: (encryptionKey: EncryptionKeyScheme): Promise<boolean> =>
|
||||
ipcDatabaseHandler.writeEncryptionKey(encryptionKey),
|
||||
writeDirectoryPath: (id: string, path: string): Promise<boolean> => ipcDatabaseHandler.writeDirectoryPath(id, path),
|
||||
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> =>
|
||||
ipcDatabaseHandler.addTaskToSendFileQueue(task),
|
||||
resetInternalDatabase: (): Promise<boolean> => ipcDatabaseHandler.resetInternalDatabase(),
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('networkAPI', {
|
||||
openUcSocket: (): Promise<boolean> => ipcUCHandler.openUcSocket(),
|
||||
sendUcMessage: (operationCode: string, metaInfo: any, fileContent: any): Promise<boolean> =>
|
||||
ipcUCHandler.sendUcMessage(operationCode, metaInfo, fileContent),
|
||||
closeUcSocket: (): Promise<boolean> => ipcUCHandler.closeUcSocket(),
|
||||
hasResponseArrived: (): Promise<boolean> => ipcUCHandler.hasResponseArrived(),
|
||||
getLastUcResult: (): Promise<ParsedMessage | null> => ipcUCHandler.getLastUcResult(),
|
||||
getOperationsCodes: (): Promise<{ [key: string]: string }> => ipcUCHandler.getOperationsCodes(),
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('uiAPI', {
|
||||
startWorkers: (): Promise<void> => ipcRenderer.invoke('start-workers'),
|
||||
stopWorkers: (): Promise<void> => ipcRenderer.invoke('stop-workers'),
|
||||
showAlert: (message: string): Promise<void> => ipcRenderer.invoke('show-alert', message),
|
||||
changeContent: (destination: string): Promise<void> =>
|
||||
ipcRenderer.invoke('change-content', destination),
|
||||
selectDirectory: (): Promise<string | undefined> => ipcRenderer.invoke('select-directory'),
|
||||
selectFile: (): Promise<string | undefined> => ipcRenderer.invoke('select-file'),
|
||||
showFileInExplorer: (path: string): Promise<void> =>
|
||||
ipcRenderer.invoke('show-file-in-explorer', path),
|
||||
readAnnouncement: (): Promise<string> => ipcRenderer.invoke('read-announcement'),
|
||||
closeAnnouncementWindow: (): Promise<void> => ipcRenderer.invoke('close-announcement-window'),
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
// UserConfig methods
|
||||
readUserConfig: (key: string): Promise<any> => ipcRenderer.invoke('read-user-json-files', key),
|
||||
writeUserConfig: (key: string, value: any): Promise<boolean> => ipcRenderer.invoke('write-user-json-files', key, value),
|
||||
removeUserConfig: (key: string) : Promise<boolean> => ipcRenderer.invoke('remove-user-json-files', key),
|
||||
resetUserConfig: (): Promise<boolean> => ipcRenderer.invoke('reset-user-json-files'),
|
||||
|
||||
// ApplicationPreferences methods
|
||||
readApplicationInfo: (key: string): Promise<any> => ipcRenderer.invoke('read-application-json-files', key),
|
||||
writeApplicationInfo: (key: string, value: any): Promise<boolean> => ipcRenderer.invoke('write-application-json-files', key, value),
|
||||
removeApplicationInfo: (key: string) : Promise<boolean> => ipcRenderer.invoke('remove-application-preferences', key),
|
||||
resetApplicationInfo: (): Promise<boolean> => ipcRenderer.invoke('reset-application-json-files'),
|
||||
|
||||
// UcCommunication methods
|
||||
openUcSocket: (): Promise<any> => ipcRenderer.invoke('open-socket'),
|
||||
sendUcMessage: (operationCode: string, metaInfo: any, fileContent: any): Promise<any> => ipcRenderer.invoke('send-message', operationCode, metaInfo, fileContent),
|
||||
closeUcSocket: (): Promise<any> => ipcRenderer.invoke('close-socket'),
|
||||
hasResponseArrived: (): Promise<boolean> => ipcRenderer.invoke('has-response-arrived'),
|
||||
getLastUcResult: (): Promise<any> => ipcRenderer.invoke('get-last-result'),
|
||||
getOperationsCodes: (): Promise<{ data: { [key: string]: string } }> => ipcRenderer.invoke('get-operation-codes'),
|
||||
|
||||
// MemoryManager methods
|
||||
createMemoryEntry: (): Promise<string> => ipcRenderer.invoke('memory-create-entry'),
|
||||
readMemoryEntry: (id: string): Promise<any> => ipcRenderer.invoke('memory-read-entry', id),
|
||||
updateMemoryEntry: (id: string, data: any): Promise<boolean> => ipcRenderer.invoke('memory-update-entry', id, data),
|
||||
removeMemoryEntry: (id: string): Promise<boolean> => ipcRenderer.invoke('memory-remove-entry', id),
|
||||
resetMemory: (): Promise<boolean> => ipcRenderer.invoke('memory-reset'),
|
||||
|
||||
// UI methods
|
||||
showAlert: (message: string): Promise<void> => ipcRenderer.invoke('show-alert', message),
|
||||
changeContent: (destination: string): Promise<void> => ipcRenderer.invoke('change-content', destination),
|
||||
selectDirectory: (): Promise<string | undefined> => ipcRenderer.invoke('select-directory'),
|
||||
selectFile: (): Promise<string | undefined> => ipcRenderer.invoke('select-file'),
|
||||
showFileInExplorer: (path: string): Promise<void> => ipcRenderer.invoke('show-file-in-explorer', path),
|
||||
closeAnnouncementWindow: (): Promise<void> => ipcRenderer.invoke('close-announcement-window'),
|
||||
|
||||
// Queue methods
|
||||
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> => ipcRenderer.invoke('add-task-to-send-file-queue', task),
|
||||
|
||||
// Workers
|
||||
startBackupRetrieval: (destinationPath: string): Promise<void> => ipcRenderer.invoke('start-backup-retrieval', destinationPath),
|
||||
});
|
||||
// Workers
|
||||
startBackupRetrieval: (destinationPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke('start-backup-retrieval', destinationPath),
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { UdpClient} from './udp/udp_client';
|
||||
export { TcpClient } from './tcp/tcp_client'
|
||||
export { UdpClient } from './udp/udp_client'
|
||||
export { TcpClient } from './tcp/tcp_client'
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
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',
|
||||
}
|
||||
|
||||
@@ -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<ParsedMessage>
|
||||
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => Promise<ParsedMessage>;
|
||||
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => Promise<ParsedMessage>
|
||||
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
return {
|
||||
operationCode: 'UNKNOWN_COMMAND',
|
||||
metaInfo: { message: 'Unknown command received.' },
|
||||
}
|
||||
}
|
||||
|
||||
// Handle operation request asynchronously
|
||||
public async handleOperation(rawMessage: string): Promise<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<ParsedMessage> {
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
let ipAddress = 'Unknown';
|
||||
// Handle heartbeat operation asynchronously
|
||||
public static async handleAreYouHuman(): Promise<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
return parsedMessage; // Acknowledge with OK, returning as-is
|
||||
// Handle AES key exchange asynchronously
|
||||
public static async handleAESKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
return parsedMessage; // Log the error and return
|
||||
}
|
||||
// Default async handler for OK operation
|
||||
public static async handleOk(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<boolean> {
|
||||
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<boolean> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.message) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing announcement message.' }};
|
||||
}
|
||||
public static async handleGetUserInformation(
|
||||
parsedMessage: ParsedMessage,
|
||||
): Promise<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' } }
|
||||
}
|
||||
|
||||
public static async handleBackupFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' } }
|
||||
}
|
||||
|
||||
public static async handleShareFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
// 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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
// 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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
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<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.name) {
|
||||
return {
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing name in meta information.' },
|
||||
}
|
||||
}
|
||||
|
||||
private static async buildDirectoryStructure(directoryPath: string): Promise<any> {
|
||||
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<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.name) {
|
||||
return {
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing name in meta information.' },
|
||||
}
|
||||
}
|
||||
|
||||
public static async handleReqFileFromBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
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<any> {
|
||||
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<ParsedMessage> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = '<EOP>';
|
||||
protected readonly CHUNK_SIZE = 1024;
|
||||
private incompleteChunkBuffer: string = '';
|
||||
protected readonly EOP = '<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<void> {
|
||||
// Append incoming data to the incomplete buffer
|
||||
this.incompleteChunkBuffer += data.toString()
|
||||
|
||||
// Split the buffer by <EOP> 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 <EOP> 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<void>
|
||||
|
||||
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<void> {
|
||||
// Append incoming data to the incomplete buffer
|
||||
this.incompleteChunkBuffer += data.toString();
|
||||
|
||||
// Split the buffer by <EOP> 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 <EOP> 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<void>;
|
||||
|
||||
abstract sendMessage(message: string): Promise<void>;
|
||||
abstract sendMessage(message: string): Promise<void>
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
this.generateKeyPair()
|
||||
if (!this.publicKey || !this.privateKey) {
|
||||
throw new Error('RSA key pair is not available. Please generate RSA key pair.')
|
||||
}
|
||||
|
||||
async sendPublicKey(): Promise<void> {
|
||||
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<void> {
|
||||
this.generateAesKey()
|
||||
if (!this.aesKey || !this.aesIv) {
|
||||
throw new Error('AES key or IV is not available. Please generate AES key.')
|
||||
}
|
||||
|
||||
async sendAesKey(): Promise<void> {
|
||||
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<void> {
|
||||
const messageToProcess = this.decryptWithAes(incomingMessage)
|
||||
this.handlerResult = await this.operationHandler.handleOperation(messageToProcess)
|
||||
}
|
||||
|
||||
async sendMessage(
|
||||
operationCode: string,
|
||||
metaInfo?: { [key: string]: any },
|
||||
fileContent?: Buffer,
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
this.handlerResult = await this.operationHandler.handleOperation(incomingMessage);
|
||||
}
|
||||
// Handle incoming message (no decryption needed for UDP)
|
||||
async handleIncomingMessage(incomingMessage: string): Promise<void> {
|
||||
this.handlerResult = await this.operationHandler.handleOperation(incomingMessage)
|
||||
}
|
||||
}
|
||||
|
||||
+109
-100
@@ -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<boolean> {
|
||||
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<boolean> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+113
-110
@@ -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<string, Promise<void>> = new Map();
|
||||
private readonly connectionManager: ConnectionManager
|
||||
private readonly operationHandler: OperationHandler
|
||||
private readonly port: number
|
||||
private readonly host: string
|
||||
private clientQueues: Map<string, Promise<void>> = 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<void> {
|
||||
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<void> {
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+145
-145
@@ -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<string[]> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
Reference in New Issue
Block a user