Merged CEO and Client App

This commit is contained in:
andrei-mihnea-cerbu
2025-02-06 10:25:13 +02:00
parent eda2e2d2a0
commit 23802acd98
186 changed files with 365 additions and 11234 deletions
-5
View File
@@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
-1
View File
@@ -1 +0,0 @@
reset_password.js
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/proiect-licenta.iml" filepath="$PROJECT_DIR$/.idea/proiect-licenta.iml" />
</modules>
</component>
</project>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
-6621
View File
File diff suppressed because it is too large Load Diff
-79
View File
@@ -1,79 +0,0 @@
{
"name": "ceoapp",
"productName": "CeoApp",
"version": "1.0.0",
"description": "Aplicatie P2P pentru stocarea resurselor digitale",
"scripts": {
"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",
"start": "npm run clean && npm run build-dist && electron-forge start",
"package": "npm run clean && npm run build-dist && electron-forge package",
"make": "npm run clean && npm run build-dist && electron-forge make"
},
"main": "dist/main/main.js",
"author": "Cerbu Andrei - Mihnea",
"license": "ISC",
"dependencies": {
"dotenv": "^16.4.5",
"ping": "^0.4.4",
"proper-lockfile": "^4.1.2",
"uuid": "^10.0.0"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0",
"@electron-forge/maker-deb": "^6.0.0",
"@electron-forge/maker-rpm": "^6.0.0",
"@electron-forge/maker-squirrel": "^6.0.0",
"@electron-forge/maker-zip": "^6.0.0",
"@types/ping": "^0.4.4",
"@types/proper-lockfile": "^4.1.4",
"@types/uuid": "^10.0.0",
"check-disk-space": "^3.4.0",
"copyfiles": "^2.4.1",
"del-cli": "^5.0.0",
"electron": "^33.0.2",
"typescript": "^5.6.2"
},
"config": {
"forge": {
"packagerConfig": {
"executableName": "ceoapp",
"name": "CeoApp",
"icon": "../app_icons/icon.ico",
"ignore": [
"src",
".idea",
"tsconfig.json"
]
},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "CEOApp",
"setupIcon": "../app_icons/icon.ico"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
],
"config": {
"icon": "../app_icons/icon.icns"
}
},
{
"name": "@electron-forge/maker-deb",
"platforms": [
"linux"
],
"config": {
"icon": "../app_icons/icon.png"
}
}
]
}
}
}
-31
View File
@@ -1,31 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
const operationCodes = await window.electronAPI.getOperationsCodes();
if (!operationCodes) {
await window.electronAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
return;
}
const codeResetDatabase = operationCodes.RESET_DATABASE;
const codeOk = operationCodes.OK;
// 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.');
return;
}
if (!await window.electronAPI.sendUcMessage(codeResetDatabase)) {
await window.electronAPI.showAlert('Failed to send login request.');
return;
}
const response = await waitForResponse();
if (response && response.operationCode !== codeOk) {
await window.electronAPI.showAlert('Database reset failed.');
return;
}
await window.electronAPI.closeUcSocket();
await new Promise(resolve => setTimeout(resolve, 2000));
fadeOut('login');
});
-106
View File
@@ -1,106 +0,0 @@
import { JsonManager } from './json_manager'; // Assuming this manages JSON configurations
import { TcpCommunicator } from "./tcp_communicator";
import { operationCodes } from '../network/operation_codes'; // Assuming this holds operation codes
import { parentPort } from 'worker_threads';
import { ParsedMessage } from "../network/message_handler";
export class AnnouncementSender {
private applicationInfo: JsonManager;
private readonly clientPort: number;
private message: string = '';
private tcpCommunicator: TcpCommunicator | null = null;
private stopRequested: boolean = false;
constructor(applicationInfoPath: string, clientPort: number) {
this.applicationInfo = new JsonManager(applicationInfoPath);
this.clientPort = clientPort;
}
async start(message: string): Promise<void> {
console.log('AnnouncementWorker started.');
this.message = message;
this.stopRequested = false;
try {
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
if (!activeUsersIp || !activeUsersIp.length) {
throw new Error('No active users found.');
}
for (const ip of activeUsersIp) {
if (this.stopRequested) {
console.log('AnnouncementWorker stopped.');
break;
}
const success = await this.sendAnnouncementToIp(ip);
if (!success) {
throw new Error(`Failed to send announcement to all users.`);
}
console.log(`Announcement sent and confirmed successfully from ${ip}`);
}
process.send?.({ type: 'showAlert', message: 'Announcement sent to all active users successfully.' });
} catch (error: any) {
console.error('Error in AnnouncementWorker:', error);
process.send?.({ type: 'shotAlert', message: `A problem occurred: ${error.message}` });
}
console.log('AnnouncementWorker finished.');
}
async stop(): Promise<void> {
console.log('Stopping AnnouncementWorker...');
this.stopRequested = true;
if (this.tcpCommunicator) {
await this.tcpCommunicator.disconnect();
}
console.log('AnnouncementWorker stopped.');
}
private async sendAnnouncementToIp(ip: string): Promise<boolean> {
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
if (!await this.tcpCommunicator.connect()) {
console.log(`Skipping user at IP ${ip} - unable to connect.`);
return true;
}
// Prepare the message metadata
const metaInfo = { message: this.message };
// Send the announcement message
const messageSent = await this.tcpCommunicator.sendMessage(operationCodes.SEND_ANNOUNCEMENT, metaInfo);
if (!messageSent) {
await this.tcpCommunicator.disconnect();
return false;
}
// Await confirmation from the user
const response = await this.waitForResponse();
if (response?.operationCode === operationCodes.OK) {
await this.tcpCommunicator.disconnect();
return true;
}
// If confirmation is not OK, disconnect and halt
await this.tcpCommunicator.disconnect();
return false;
}
private async waitForResponse(): Promise<ParsedMessage | null> {
return new Promise((resolve) => {
const idResponseCheck = setInterval(async () => {
if (this.stopRequested || !this.tcpCommunicator) {
clearInterval(idResponseCheck);
resolve(null);
return;
}
if (this.tcpCommunicator.hasResponseArrived()) {
clearInterval(idResponseCheck);
resolve(this.tcpCommunicator.getLastResult());
}
}, 100);
});
}
}
-192
View File
@@ -1,192 +0,0 @@
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";
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;
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;
}
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 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;
}
this.fileEncryptor = new FileEncryptor(encryptionKeyData.key, encryptionKeyData.iv);
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 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');
} finally {
this.log('Backup process completed.');
this.isBusy = false;
}
}
private encryptFile(filePath: string): string {
if (!this.fileEncryptor) {
return filePath;
}
if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error');
return '';
}
return this.fileEncryptor.encryptFileToBase64(filePath);
}
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' });
}
}
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}`);
}
}
}
-206
View File
@@ -1,206 +0,0 @@
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";
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;
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;
}
// 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 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();
}
}
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;
}
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;
}
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.');
}
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}`);
}
}
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());
}
}, 100);
});
}
}
-214
View File
@@ -1,214 +0,0 @@
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";
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;
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;
}
// 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
}
// 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.');
}
const departmentId = userInfo.departmentId;
const userName = userInfo.name;
// 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.');
}
const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId);
if (!activeUsers || activeUsers.length === 0) {
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.');
}
// 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;
}
return true;
}
// 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);
console.log(`\n\n${unsentFiles}\n\n`);
for (const fileName of unsentFiles) {
const filePath = files[fileName];
// Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error');
continue;
}
// Read the file content
const fileContent = fs.readFileSync(filePath);
// Get the relative path of the file (used in the meta info)
if (!this.departmentDirectory) return;
const relativeFilePath = path.relative(this.departmentDirectory, filePath);
// Prepare the metaInfo (same structure as FileSharer)
const metaInfo = {
userName,
relativeFilePath
};
// Send the file
if (!await this.tcpCommunicator.sendMessage(operationCodes.DEPARTMENT_FILE, metaInfo, Buffer.from(fileContent))) return;
console.log(`\n\nSending file: ${fileName} to ${userName}\n\n`);
const response = await this.waitForResponse();
if (!response || response.operationCode !== operationCodes.OK) {
this.log(`Failed to send file: ${fileName}`, 'error');
return;
}
this.log(`File sent successfully: ${fileName} to ${userName}`);
unsentFiles.splice(unsentFiles.indexOf(fileName), 1);
await this.tcpCommunicator.disconnect();
}
}
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());
}
}, 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}`);
}
}
}
-163
View File
@@ -1,163 +0,0 @@
import { promises as fs, watch, FSWatcher } from 'fs';
import path from 'path';
import { MemoryManager } from './memory_manager';
import { JsonManager } from './json_manager';
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;
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;
}
// 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
}
// 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;
}
// 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;
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()) {
// 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;
}
}
return { structure: directoryScheme, size: totalSize };
}
// Restart the directory watcher, ensuring any previous watcher is closed
private restartWatcher(): void {
if (this.directoryWatcher) {
this.log('Stopping existing watcher...');
this.directoryWatcher.close();
}
this.startDirectoryWatcher();
}
// 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}`);
}
// 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);
}
}
-55
View File
@@ -1,55 +0,0 @@
import fs from 'fs';
import crypto from 'crypto';
export class FileEncryptor {
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');
}
// 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 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;
}
}
}
-146
View File
@@ -1,146 +0,0 @@
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";
interface FileSendTask {
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;
constructor(queueFilePath: string, clientPort: number) {
this.queueManager = new QueueManager<FileItemTask>(queueFilePath, compareFnFileItemTask);
this.clientPort = clientPort;
this.isBusy = false; // Initialize the busy flag
}
// 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
}
// Method to process the queue
private async processQueue(): Promise<void> {
while (!this.queueManager.isEmpty()) {
const task = this.queueManager.peek();
this.log('trimiti fisier');
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}`);
} else {
console.log(`${prefix} ${message}`);
}
}
}
-119
View File
@@ -1,119 +0,0 @@
import fs from 'fs';
import path from 'path';
export class JsonManager {
private readonly filePath: string;
private readonly lockFilePath: string;
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');
}
}
// 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, '');
}
// Method to release the lock (delete .lock file)
private releaseLock(): void {
if (fs.existsSync(this.lockFilePath)) {
fs.unlinkSync(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
}
}
// 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 {
let data: { [key: string]: any } = {};
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
}
}
// 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 {
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
}
}
}
-47
View File
@@ -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);
}
}
-185
View File
@@ -1,185 +0,0 @@
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";
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[] = [];
// 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;
// Start tasks
this.startUCCheck();
this.startUserIPLookup();
this.sendLoginRequest();
}
// 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}`);
}
}
// 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 storedIp = await this.applicationInfo.readValue('serverIp');
const foundClient = aliveClients.length > 0;
if (foundClient) {
const ipAddress = aliveClients[0]; // Use the first alive client
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);
this.intervalIds.push(intervalId);
}
// IP Lookup Task
private startUserIPLookup(interval: number = 10000): void {
const intervalId = setInterval(async () => {
if (this.ipLookupBusy) return;
this.ipLookupBusy = 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);
}
this.log("All intervals have been stopped.", 'log', 'stopAllIntervals');
}
}
-137
View File
@@ -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
}
}
}
-86
View File
@@ -1,86 +0,0 @@
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;
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();
}
return message;
}
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;
}
}
-122
View File
@@ -1,122 +0,0 @@
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";
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;
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';
}
// Method to start checking user info periodically (every minute)
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
await this.initialize(); // Re-run every minute
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;
}
// 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);
}
// Check user information
await this.checkUsersInfo(usersIps);
}
// 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
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(!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();
}
await this.updateActiveUsers(usersInfo); // Update active users information in the memory
}
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
});
}
// 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.");
}
}
// 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}`);
}
}
}
-149
View File
@@ -1,149 +0,0 @@
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;
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;
}
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.');
}
}
}
-128
View File
@@ -1,128 +0,0 @@
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;
constructor(pathToWorkerDir: string, windowManager: WindowManager) {
this.pathToWorkerDir = pathToWorkerDir;
this.windowManager = windowManager;
this.workers = [];
}
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 startDirectoriesWatchersWorker(memoryManagerPath: string, applicationInfoPath: string): Promise<void> {
return this.startForkedWorker('directories_watcher_worker.js', {
MEMORY_MANAGER_PATH: memoryManagerPath,
APPLICATION_INFO_PATH: applicationInfoPath
});
}
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 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 startAnnouncementWorker(applicationInfoPath: string, clientPort: number, message: string): Promise<void> {
return this.startForkedWorker('send_announcement_worker.js', {
APPLICATION_INFO_PATH: applicationInfoPath,
CLIENT_PORT: clientPort.toString(),
MESSAGE: message
});
}
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'],
env: { ...process.env, ...envData }
});
this.workers.push(worker);
worker.on('message', (data: unknown) => {
const message = data as { type: string, page?: string, message?: string };
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;
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);
}
}
}
-8
View File
@@ -1,8 +0,0 @@
export interface FileItemTask {
ip: string;
path: string;
userName: string;
}
export const compareFnFileItemTask = (task1: FileItemTask, task2: FileItemTask) =>
task1.ip === task2.ip && task1.path === task2.path;
-15
View File
@@ -1,15 +0,0 @@
interface PoolRequest {
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;
}
type PoolOperation = 'open' | 'send' | 'close'; // Define the allowed PoolOperations
-5
View File
@@ -1,5 +0,0 @@
interface RegisteredClient {
id: string;
ip: string;
port: number;
}
-363
View File
@@ -1,363 +0,0 @@
import {app, BrowserWindow, ipcMain, IpcMainInvokeEvent} from 'electron';
import path from 'path';
import { promises as fs } from 'fs';
import dotenv from 'dotenv';
import {WorkerManager} from "../helpers/worker_manager";
import {DirectoryWatcher} from "../helpers/directory_watcher";
import {QueueManager} from "../helpers/queue_manager";
import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task";
import {WindowManager} from "../helpers/window_manager";
import {JsonManager} from "../helpers/json_manager";
import {MemoryManager} from "../helpers/memory_manager";
import {TcpCommunicator} from "../helpers/tcp_communicator";
import {operationCodes} from "../network/operation_codes";
import os from "os";
// Load environment variables
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') });
const UDP_PORT = process.env.UDP_PORT ? parseInt(process.env.UDP_PORT) : 41233;
const TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234;
const HOST = getLocalIp();
let mainWindow: BrowserWindow | null = null;
let windowManager: WindowManager | null = null;
let tcpCommunicator: TcpCommunicator | null = null;
let userConfig: JsonManager | null = null;
let applicationInfo: JsonManager | null = null;
let memoryManager: MemoryManager | null = null;
let workerManager: WorkerManager | null = null;
let backupDirectoryManager: DirectoryWatcher | null = null;
let departmentShareManager: DirectoryWatcher | null = null;
let sendFileQueue: QueueManager<FileItemTask> | null = null;
const pathToPagesDir = path.join(__dirname, '..', '..', 'render', 'html');
const pathToWorkerDir = path.join(__dirname, '..', 'workers');
const pathToJsons = path.join(__dirname, '..', 'json_files');
const pathToClientsBackups = path.join(__dirname, '..', 'backups');
async function cleanupAndExit() {
// Stop all workers
if (workerManager) {
console.log('Terminating all workers...');
workerManager.closeAllWorkers();
}
// Reset memory
if (memoryManager) {
await memoryManager.resetFile();
}
// Close watchers
if (backupDirectoryManager) {
console.log('Stopping backup directory watcher...');
backupDirectoryManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher
}
if (departmentShareManager) {
console.log('Stopping department directory watcher...');
departmentShareManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher
}
if(workerManager){
console.log('Terminating all workers...');
workerManager.closeAllWorkers()
}
console.log('Cleanup complete, exiting application.');
app.quit(); // This will properly close the application
}
async function ensureDirectoryExists(dirPath: string): Promise<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}`);
}
}
function getLocalIp() {
const interfaces = os.networkInterfaces();
for (let interfaceName in interfaces) {
const addresses = interfaces[interfaceName];
if(!addresses) continue;
for (let address of addresses) {
// Filter for IPv4 and ignore internal (127.0.0.1) addresses
if (address.family === 'IPv4' && !address.internal) {
return address.address;
}
}
}
return ''; // Fallback if no IP is found
}
app.whenReady().then(async () => {
const title = 'Application';
const mainScreen = require('electron').screen.getPrimaryDisplay();
const { width, height } = mainScreen.size;
mainWindow = new BrowserWindow({
title,
width: width / 1.5,
height: height / 1.5,
resizable: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
},
});
mainWindow.removeMenu();
await ensureDirectoryExists(pathToJsons);
await ensureDirectoryExists(pathToClientsBackups);
windowManager = new WindowManager(mainWindow, pathToPagesDir);
userConfig = new JsonManager(path.join(pathToJsons, 'userConfig.json'));
applicationInfo = new JsonManager(path.join(pathToJsons, 'application.json'));
memoryManager = new MemoryManager(path.join(pathToJsons, 'memory.json'));
sendFileQueue = new QueueManager(path.join(pathToJsons, 'sendFileTasks.json'), compareFnFileItemTask);
workerManager = new WorkerManager(pathToWorkerDir, windowManager);
await userConfig.writeValue('app_type', 'ceo');
await applicationInfo.writeValue('users_ip', []);
await applicationInfo.writeValue('serverIp', '');
await applicationInfo.writeValue('announcement', '');
await memoryManager.resetFile();
workerManager.startNetworkScannerWorker(UDP_PORT, TCP_PORT, 'login', 'uc_not_found', 'reset_database', path.join(pathToJsons, 'userConfig.json'), path.join(pathToJsons, 'application.json'));
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
workerManager.startResourceCoordinatorWorker(
path.join(pathToJsons, 'userConfig.json'),
path.join(pathToJsons, 'application.json'),
path.join(pathToJsons, 'memory.json'),
path.join(pathToJsons, 'sendFileTasks.json'),
TCP_PORT
);
registerIPCHandlers();
await windowManager.changeContent('welcome');
});
app.on('window-all-closed', async () => {
console.log('All windows closed, starting cleanup...');
await cleanupAndExit(); // Call cleanup when all windows are closed
});
// Catch CTRL+C (SIGINT) and clean up resources
process.on('SIGINT', async () => {
console.log('CTRL+C pressed, starting cleanup...');
await cleanupAndExit(); // Call cleanup on SIGINT
});
app.on('before-quit', async () => {
console.log('Application is quitting, starting cleanup...');
await cleanupAndExit(); // Call cleanup before app quit
});
// Register IPC handlers
function registerIPCHandlers() {
// ResetApplicationPreferences IPC Handlers
ipcMain.handle('reset-application-preferences', async () => {
if(applicationInfo) {
const serverIp = await applicationInfo.readValue('serverIp');
await applicationInfo.resetFile();
await applicationInfo.writeValue('serverIp', serverIp);
}
if(userConfig) {
await userConfig.resetFile();
await userConfig.writeValue('app_type', 'ceo');
}
if(memoryManager) {
await memoryManager.resetFile();
}
if(sendFileQueue) {
sendFileQueue.clearQueue();
}
if(workerManager) {
workerManager.closeAllWorkers();
await new Promise(resolve => setTimeout(resolve, 3000));
workerManager.startNetworkScannerWorker(UDP_PORT, TCP_PORT, 'login', 'uc_not_found', 'reset_database', path.join(pathToJsons, 'userConfig.json'), path.join(pathToJsons, 'application.json'));
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
workerManager.startResourceCoordinatorWorker(
path.join(pathToJsons, 'userConfig.json'),
path.join(pathToJsons, 'application.json'),
path.join(pathToJsons, 'memory.json'),
path.join(pathToJsons, 'sendFileTasks.json'),
TCP_PORT
);
}
});
// Window Manager IPC Handlers
ipcMain.handle('show-alert', async (event: IpcMainInvokeEvent, message: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
await windowManager.showAlert(message);
});
ipcMain.handle('change-content', async (_event: IpcMainInvokeEvent, destination: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
await windowManager.changeContent(destination);
});
ipcMain.handle('select-directory', async (_event: IpcMainInvokeEvent) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
return await windowManager.selectDirectory();
});
ipcMain.handle('select-file', async (_event: IpcMainInvokeEvent) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
return await windowManager.selectFile();
});
ipcMain.handle('show-file-in-explorer', async (_event: IpcMainInvokeEvent, path: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
return await windowManager.showFileInExplorer(path);
});
// TcpMethods IPC Handlers
ipcMain.handle('open-socket', async (_event: IpcMainInvokeEvent) => {
if (!applicationInfo) throw new Error('TcpMethods is not initialized.');
const serverIp = await applicationInfo.readValue('serverIp');
if (!serverIp) return;
tcpCommunicator = new TcpCommunicator(serverIp, TCP_PORT);
return await tcpCommunicator.connect()
});
ipcMain.handle('send-message', async (_event: IpcMainInvokeEvent, operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return await tcpCommunicator.sendMessage(operationCode, metaInfo, fileContent);
});
ipcMain.handle('has-response-arrived', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return tcpCommunicator.hasResponseArrived();
});
ipcMain.handle('close-socket', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return await tcpCommunicator.disconnect();
});
ipcMain.handle('get-last-result', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return tcpCommunicator.getLastResult();
});
ipcMain.handle('get-operation-codes', () => {
return operationCodes;
});
// UserConfig IPC Handlers
ipcMain.handle('read-user-json-files', async (_event: IpcMainInvokeEvent, key: string) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return await userConfig.readValue(key);
});
ipcMain.handle('write-user-json-files', async (_event: IpcMainInvokeEvent, key: string, value: any) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.writeValue(key, value);
});
ipcMain.handle('reset-user-json-files', async () => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.resetFile();
});
ipcMain.handle('remove-user-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.removeValue(key);
});
// ApplicationPreferences IPC Handlers
ipcMain.handle('read-application-json-files', async (_event: IpcMainInvokeEvent, key: string) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return await applicationInfo.readValue(key);
});
ipcMain.handle('write-application-json-files', async (_event: IpcMainInvokeEvent, key: string, value: any) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return applicationInfo.writeValue(key, value);
});
ipcMain.handle('reset-application-json-files', async () => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
const serverIp = await applicationInfo.readValue('serverIp');
await applicationInfo.resetFile();
if(serverIp) {
await applicationInfo.writeValue('serverIp', serverIp);
}
});
ipcMain.handle('remove-application-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return applicationInfo.removeValue(key);
});
// Memory IPC Handlers
ipcMain.handle('memory-create-entry', async () => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.storeMetaInformation({});
});
ipcMain.handle('memory-read-entry', async (_event: IpcMainInvokeEvent, id: string) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.retrieveMetaInformation(id);
});
ipcMain.handle('memory-update-entry', async (_event: IpcMainInvokeEvent, id: string, data: any) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.updateMetaInformation(id, data);
});
ipcMain.handle('memory-remove-entry', async (_event: IpcMainInvokeEvent, id: string) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.removeMetaInformation(id);
});
ipcMain.handle('memory-reset', async () => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.resetFile();
});
// Queue IPC Handlers
ipcMain.handle('add-task-to-send-file-queue', async (event: IpcMainInvokeEvent, task: FileItemTask) => {
if (!sendFileQueue) throw new Error('SendFileQueue is not initialized.');
sendFileQueue.enqueue(task);
});
// Workers IPC Handlers
ipcMain.handle('start-backup-retrieval', async (_event: IpcMainInvokeEvent, destinationPath: string) => {
if (!workerManager) throw new Error('WorkerManager is not initialized.');
return workerManager.startBackupRetrievalWorker(
path.join(pathToJsons, 'userConfig.json'),
path.join(pathToJsons, 'application.json'),
TCP_PORT,
destinationPath
);
});
ipcMain.handle('start-announcement-worker', async (_event: IpcMainInvokeEvent, message: string) => {
if (!workerManager) throw new Error('WorkerManager is not initialized.');
return workerManager.startAnnouncementWorker(
path.join(pathToJsons, 'application.json'),
TCP_PORT,
message
);
});
}
-45
View File
@@ -1,45 +0,0 @@
import { contextBridge, ipcRenderer } from 'electron';
import {FileItemTask} from "../interfaces/file_item_task";
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),
// 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),
startAnnouncementWorker: (message: string): Promise<void> => ipcRenderer.invoke('start-announcement-worker', message),
});
-43
View File
@@ -1,43 +0,0 @@
import { SocketCommunicatorBase } from "./socket_communicator/socket_communicator_base";
interface Connection {
communicator: SocketCommunicatorBase;
}
export class ConnectionManager {
private readonly connections: { [key: string]: Connection };
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
};
}
// 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;
}
// 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;
}
}
-65
View File
@@ -1,65 +0,0 @@
export interface ParsedMessage {
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
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;
}
// 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;
}
}
-2
View File
@@ -1,2 +0,0 @@
export { UdpClient} from './udp/udp_client';
export { TcpClient } from './tcp/tcp_client'
-41
View File
@@ -1,41 +0,0 @@
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',
// Auth Operations
LOGIN: 'LOGIN',
SIGN_UP: 'SIGN_UP',
RESET_PASSWORD: 'RESET_PASSWORD',
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_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',
};
@@ -1,57 +0,0 @@
// operation_handler.ts
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>;
export class OperationHandler {
private static instance: OperationHandler;
private handlers: { [operationCode: string]: OperationHandlerFunction } = {};
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);
}
// 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);
}
}
@@ -1,6 +0,0 @@
// operation_plugin.ts
import { OperationHandler } from './operation_handler';
export interface OperationPlugin {
register(operationHandler: OperationHandler): void;
}
@@ -1,88 +0,0 @@
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',
};
// 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;
}
return {
operationCode: GeneralOperations.operationCodes.ALIVE,
metaInfo: { ipAddress },
};
}
// 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.' },
};
}
}
// 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 OK operation
public static async handleOk(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
return parsedMessage; // Acknowledge with OK, returning as-is
}
// 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 +0,0 @@
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',
};
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 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' }};
}
}
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 { 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.' }};
}
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}` }};
}
}
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, relativeFilePath } = parsedMessage.metaInfo;
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.' }};
}
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}` }};
}
}
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.' },
};
}
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}` },
};
}
}
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}` }};
}
}
public static async handleGetBackupStructure(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 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}` }};
}
}
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);
}
}
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 +0,0 @@
import { ParsedMessage } from '../message_handler';
import { OperationHandler } from '../operations_base/operation_handler';
import {
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 chunkBuffers: { [messageId: string]: string[] };
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 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;
}
// 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');
}
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 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.');
}
}
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>;
}
@@ -1,87 +0,0 @@
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;
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');
}
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 +0,0 @@
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;
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.');
}
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.');
}
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);
}
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,33 +0,0 @@
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;
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();
});
});
}
// Handle incoming message (no decryption needed for UDP)
async handleIncomingMessage(incomingMessage: string): Promise<void> {
this.handlerResult = await this.operationHandler.handleOperation(incomingMessage);
}
}
-111
View File
@@ -1,111 +0,0 @@
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;
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());
}
// 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;
}
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;
}
}
-119
View File
@@ -1,119 +0,0 @@
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') });
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();
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());
}
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');
}
}
}
}
-161
View File
@@ -1,161 +0,0 @@
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;
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);
}
}
}
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 {
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);
}
}
this.log(`Active IPs after pinging: ${activeIps.join(', ')}`);
return activeIps;
}
}
-77
View File
@@ -1,77 +0,0 @@
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;
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());
}
// 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');
}
}
}
// 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}`);
}
}
@@ -1,50 +0,0 @@
import { BackupRetrievalWorker } from '../helpers/backup_retrieval';
import dotenv from 'dotenv';
// Load environment variables from .env file if it exists
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;
// Validate that all required environment variables are present
if (!userConfigPath || !applicationInfoPath || !clientPort || !destinationPath) {
console.error('Error: Missing required environment variables.');
process.exit(1);
}
// Initialize the BackupRetrievalWorker
const backupRetrievalWorker = new BackupRetrievalWorker(
userConfigPath,
applicationInfoPath,
clientPort,
destinationPath
);
// Start the backup retrieval process
backupRetrievalWorker.start().then(() => {
console.log('Backup retrieval process completed successfully.');
});
process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...');
await cleanupAndExit();
});
process.on('SIGINT', async () => {
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
}
@@ -1,44 +0,0 @@
import { DirectoryWatcher } from "../helpers/directory_watcher";
import dotenv from 'dotenv';
// Load environment variables from .env file if it exists
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;
// Validate that all required environment variables are present
if (!memoryManagerPath || !applicationInfoPath) {
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();
process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...');
await cleanupAndExit();
});
process.on('SIGINT', async () => {
console.log('Received SIGINT. Cleaning up...');
await cleanupAndExit();
});
async function cleanupAndExit() {
backupDirectoryManager.closeWatcher();
departmentShareManager.closeWatcher();
shareFileManager.closeWatcher();
console.log('Cleanup complete. Exiting.');
process.exit(0); // Exit with code 0 to indicate a clean exit
}
-39
View File
@@ -1,39 +0,0 @@
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 || '';
// Start the NetworkScanner instance
const networkScanner = new NetworkScanner(
applicationInfoPath,
userConfigPath,
udpPort,
tcpPort,
okPage,
errorPage,
databaseResetPage
);
process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...');
await cleanupAndExit();
});
process.on('SIGINT', async () => {
console.log('Received SIGINT. Cleaning up...');
await cleanupAndExit();
});
async function cleanupAndExit() {
networkScanner.stopAllIntervals();
console.log('Cleanup complete. Exiting.');
process.exit(0); // Exit with code 0 to indicate a clean exit
}
@@ -1,43 +0,0 @@
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 usersInfoFetcher = new UsersInfoFetcher(applicationInfoPath, memoryManagerPath, tcpPort);
usersInfoFetcher.start();
const backupManager = new BackupManager(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort);
backupManager.start();
const fileSharer = new FileSharer(queueManagerPath, tcpPort);
fileSharer.start();
const departmentSharer = new DepartmentSharer(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort);
departmentSharer.start();
process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...');
await cleanupAndExit();
});
process.on('SIGINT', async () => {
console.log('Received SIGINT. Cleaning up...');
await cleanupAndExit();
});
async function cleanupAndExit() {
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
}
@@ -1,27 +0,0 @@
import { AnnouncementSender } from "../helpers/announcement_sender";
// Read environment variables passed by WorkerManager
const applicationInfoPath = process.env.APPLICATION_INFO_PATH as string;
const clientPort = parseInt(process.env.CLIENT_PORT as string, 10);
const message = process.env.MESSAGE as string;
if (!applicationInfoPath || !clientPort || !message) {
console.error("Missing necessary environment variables for AnnouncementWorker.");
process.exit(1);
}
// Initialize the AnnouncementSender instance
const announcementSender = new AnnouncementSender(applicationInfoPath, clientPort);
// Start the announcement process
announcementSender.start(message);
// Handle graceful termination on receiving kill signals
const handleExit = async () => {
console.log("Announcement worker is shutting down gracefully...");
await announcementSender.stop(); // Assuming stop() is implemented to clean up resources
process.exit(0);
};
process.on('SIGTERM', handleExit);
process.on('SIGINT', handleExit);
-36
View File
@@ -1,36 +0,0 @@
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 || '';
// Initialize and start the servers
udpServer = new UdpServer(HOST, USER_UDP_PORT);
udpServer.start();
tcpServer = new TcpServer(HOST, USER_TCP_PORT);
tcpServer.start();
process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...');
await cleanupAndExit();
});
process.on('SIGINT', async () => {
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
}
-17
View File
@@ -1,17 +0,0 @@
{
"compilerOptions": {
"outDir": "./dist",
"module": "commonjs",
"target": "es6",
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}
+1 -2
View File
@@ -1,4 +1,3 @@
UDP_PORT=41234
TCP_PORT=41233
HOST=0.0.0.0
IS_CLIENT=true

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

@@ -11,12 +11,12 @@ document.addEventListener('DOMContentLoaded', function () {
const message = announcementTextarea.value.trim();
if (message === '') {
await window.electronAPI.showAlert('Please enter a message before sending.');
await window.uiAPI.showAlert('Please enter a message before sending.');
return;
}
// Send the announcement message via the electronAPI
window.electronAPI.startAnnouncementWorker(message);
window.workersAPI.startAnnouncementWorker(message);
fadeOut('send_announcement');
});
});
@@ -8,9 +8,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. Failed to retrieve operation codes.');
await window.uiAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
return;
}
@@ -21,7 +21,7 @@ document.addEventListener('DOMContentLoaded', async function () {
resetPasswordButton.addEventListener('click', function (e) {
e.preventDefault();
window.electronAPI.changeContent('reset_password');
window.uiAPI.changeContent('reset_password');
});
// Submit button logic (handle login)
@@ -36,50 +36,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.workersAPI.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;
}
@@ -87,57 +89,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;
}
@@ -149,10 +138,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;
}
@@ -29,7 +29,7 @@ document.addEventListener('DOMContentLoaded', async function () {
if (userConfirmed) {
await resetDatabase();
} else {
window.electronAPI.showAlert("Database reset canceled.");
window.uiAPI.showAlert("Database reset canceled.");
}
});
@@ -70,48 +70,52 @@ 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
}
// Call the IPC method to initiate the backup retrieval process
window.electronAPI.startBackupRetrieval(destinationPath);
window.workersAPI.startBackupRetrieval(destinationPath);
// Switch the content to the 'backup_retrieve' page
fadeOut('backup_retrieve');
}
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');
@@ -120,8 +124,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);
}
@@ -131,7 +134,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;
@@ -139,7 +142,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.");
}
@@ -147,29 +150,16 @@ async function fetchUserInfo() {
async function loadReceivedFiles() {
// Read the shareDirectory from applicationInfo
const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory');
const shareDirData = await window.databaseAPI.getDirectoryInfo(shareDirId);
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;
}
console.log('Loading received files:', shareDirData);
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 => {
@@ -189,58 +179,16 @@ 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);
}
async function showConfirmationDialog(message) {
@@ -10,13 +10,13 @@ document.addEventListener('DOMContentLoaded', async () => {
const closeCreateModalButton = document.getElementById('closeCreateModal');
const confirmCreateButton = document.getElementById('confirmCreateButton');
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;
}
backButton.addEventListener('click', async () => {
await window.electronAPI.closeUcSocket();
await window.networkAPI.closeUcSocket();
fadeOut('main_menu');
});
@@ -34,7 +34,7 @@ document.addEventListener('DOMContentLoaded', async () => {
confirmCreateButton.addEventListener('click', async () => {
const departmentName = document.getElementById('newDepartmentName').value.trim();
if (!departmentName) {
window.electronAPI.showAlert("Please enter a department name.");
window.uiAPI.showAlert("Please enter a department name.");
return;
}
@@ -49,9 +49,9 @@ async function fetchDepartments() {
departmentsContainer.innerHTML = ''; // Clear any existing data
// 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;
}
@@ -62,8 +62,8 @@ async function fetchDepartments() {
codeDeleteDepartment = operationCodes.DELETE_DEPARTMENT;
// Send the login message to the server
if (!await window.electronAPI.sendUcMessage(codeGetDepartments)) {
await window.electronAPI.showAlert('Failed to send login request.');
if (!await window.networkAPI.sendUcMessage(codeGetDepartments)) {
await window.uiAPI.showAlert('Failed to send login request.');
return false;
}
@@ -71,7 +71,7 @@ async function fetchDepartments() {
const response = await waitForResponse();
if(!response || response.operationCode !== codeOk){
await window.electronAPI.showAlert('Could not fetch departments.');
await window.uiAPI.showAlert('Could not fetch departments.');
return false;
}
@@ -129,8 +129,8 @@ async function fetchDepartments() {
async function createDepartment(departmentName) {
if (!await window.electronAPI.sendUcMessage(codeCreateDepartment, { departmentName: departmentName })) {
await window.electronAPI.showAlert('Failed to send login request.');
if (!await window.networkAPI.sendUcMessage(codeCreateDepartment, { departmentName: departmentName })) {
await window.uiAPI.showAlert('Failed to send login request.');
return false;
}
@@ -138,7 +138,7 @@ async function createDepartment(departmentName) {
const response = await waitForResponse();
if(!response || response.operationCode !== codeOk){
await window.electronAPI.showAlert('Could not create new department.');
await window.uiAPI.showAlert('Could not create new department.');
return false;
}
@@ -146,8 +146,8 @@ async function createDepartment(departmentName) {
}
async function modifyDepartment(departmentId, newDepartmentName) {
if (!await window.electronAPI.sendUcMessage(codeModifyDepartment, { departmentId: departmentId, newDepartmentName: newDepartmentName })) {
await window.electronAPI.showAlert('Failed to send modify request.');
if (!await window.networkAPI.sendUcMessage(codeModifyDepartment, { departmentId: departmentId, newDepartmentName: newDepartmentName })) {
await window.uiAPI.showAlert('Failed to send modify request.');
return false;
}
@@ -155,7 +155,7 @@ async function modifyDepartment(departmentId, newDepartmentName) {
const response = await waitForResponse();
if(!response || response.operationCode !== codeOk){
await window.electronAPI.showAlert('Could not modify department.');
await window.uiAPI.showAlert('Could not modify department.');
console.log(response.metaInfo.message);
return false;
}
@@ -167,15 +167,15 @@ async function deleteDepartment(departmentId) {
const confirmDelete = confirm("Are you sure you want to delete this department?");
if (!confirmDelete) return;
if (!await window.electronAPI.sendUcMessage(codeDeleteDepartment, { departmentId: departmentId })) {
await window.electronAPI.showAlert('Failed to send delete request.');
if (!await window.networkAPI.sendUcMessage(codeDeleteDepartment, { departmentId: departmentId })) {
await window.uiAPI.showAlert('Failed to send delete request.');
return false;
}
const response = await waitForResponse();
if(!response || response.operationCode !== codeOk){
await window.electronAPI.showAlert('Could not delete department.');
await window.uiAPI.showAlert('Could not delete department.');
return false;
}
@@ -199,7 +199,7 @@ function openModifyModal(departmentId, departmentName) {
console.log(newDepartmentName, departmentId);
if (!newDepartmentName || !departmentId) {
window.electronAPI.showAlert("Please enter a new department name.");
window.uiAPI.showAlert("Please enter a new department name.");
return;
}
await modifyDepartment(departmentId, newDepartmentName);
@@ -6,15 +6,15 @@ let codeOk = '';
document.addEventListener('DOMContentLoaded', async () => {
const backButton = document.getElementById('backButton');
backButton.addEventListener('click', async () => {
await window.electronAPI.closeUcSocket();
await window.networkAPI.closeUcSocket();
fadeOut('main_menu');
});
});
async function fetchData() {
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;
}
@@ -24,7 +24,7 @@ async function fetchData() {
codeOk = operationCodes.OK;
// Open the TCP socket right after fetching the operation codes
if (!await window.electronAPI.openUcSocket()) {
if (!await window.networkAPI.openUcSocket()) {
alert('Failed to open socket. Internal error of the application.');
return;
}
@@ -114,7 +114,7 @@ async function fetchData() {
async function fetchDepartments() {
// Send the request to get departments
if (!await window.electronAPI.sendUcMessage(codeGetDepartments)) {
if (!await window.networkAPI.sendUcMessage(codeGetDepartments)) {
return null;
}
return await waitForResponse();
@@ -122,7 +122,7 @@ async function fetchDepartments() {
async function fetchUsers() {
// Send the request to get users
if (!await window.electronAPI.sendUcMessage(codeGetUsers)) {
if (!await window.networkAPI.sendUcMessage(codeGetUsers)) {
return null;
}
return await waitForResponse();
@@ -130,7 +130,7 @@ async function fetchUsers() {
async function deleteUser(userId) {
// Send the request to delete the user
if (!await window.electronAPI.sendUcMessage(codeDeleteUser, {id: userId})) {
if (!await window.networkAPI.sendUcMessage(codeDeleteUser, {id: userId})) {
alert('Failed to send request to delete user.');
return;
}
@@ -145,6 +145,6 @@ async function deleteUser(userId) {
}
async function redirectToMainMenu() {
await window.electronAPI.closeUcSocket();
await window.networkAPI.closeUcSocket();
fadeOut('main_menu');
}
@@ -0,0 +1,31 @@
document.addEventListener('DOMContentLoaded', async function () {
const operationCodes = await window.networkAPI.getOperationsCodes();
if (!operationCodes) {
await window.uiAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
return;
}
const codeResetDatabase = operationCodes.RESET_DATABASE;
const codeOk = operationCodes.OK;
// Open a TCP socket to the stored IP
if (!await window.networkAPI.openUcSocket()) {
await window.uiAPI.showAlert('Internal error of the application. Unable to open socket.');
return;
}
if (!await window.networkAPI.sendUcMessage(codeResetDatabase)) {
await window.uiAPI.showAlert('Failed to send login request.');
return;
}
const response = await waitForResponse();
if (response && response.operationCode !== codeOk) {
await window.uiAPI.showAlert('Database reset failed.');
return;
}
await window.networkAPI.closeUcSocket();
await new Promise(resolve => setTimeout(resolve, 2000));
fadeOut('login');
});

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Some files were not shown because too many files have changed in this diff Show More