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 { 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 { 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 { 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 { if (!this.tcpCommunicator) return false; const metaInfo = { name: userName }; if (!await this.tcpCommunicator.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo)) return null; const response = await this.waitForResponse(); return response?.metaInfo?.structure || null; } private async requestBackupFile(userName: string, relativeFilePath: string): Promise { if (!this.tcpCommunicator) return false; const metaInfo = { name: userName, relativeFilePath }; if (!await this.tcpCommunicator.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo)) return false; const response = await this.waitForResponse(); if (response?.operationCode === operationCodes.OK && response.metaInfo && response.fileContent) { return this.saveFile(response.metaInfo.relativeFilePath, response.fileContent.toString('base64')); } return false; } private saveFile(relativeFilePath: string, fileContent: string): boolean { if (!this.encryptionKey || !this.iv) { throw new Error('Encryption key or IV is not set.'); } 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 { this.stopRequested = true; // Signal that stop is requested // Wait for any ongoing process to complete if busy while (this.isBusy) { await new Promise((resolve) => setTimeout(resolve, 100)); } console.log("[BackupManager] Stopped successfully."); } private async waitForResponse(): Promise { return new Promise((resolve) => { const idResponseCheck = setInterval(async () => { if (!this.tcpCommunicator) return null; if (this.tcpCommunicator.hasResponseArrived()) { clearInterval(idResponseCheck); resolve(this.tcpCommunicator.getLastResult()); } }, 100); }); } }