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 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; 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; } async start(): Promise { 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) { parentPort?.postMessage({ success: false, message: 'No active users found.' }); return; } let backupSuccessful = true; for (const ip of activeUsersIp) { const success = await this.processBackupForIp(ip, userName); if (!success) { backupSuccessful = false; parentPort?.postMessage({ success: false, message: 'Backup could not be completed due to an internal error.' }); break; } } if (backupSuccessful) { parentPort?.postMessage({ success: true, message: 'Backup successful.' }); } } catch (error: any) { console.error('Error in BackupRetrievalWorker:', error); parentPort?.postMessage({ success: false, message: `Backup could not be completed due to an internal error: ${error.message}` }); } } private async processBackupForIp(ip: string, userName: string): Promise { this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort); if (!await this.tcpCommunicator.connect()) { console.error(`Failed to connect to ${ip}`); return false; } const backupExists = await this.checkIfBackupExists(userName); if (!backupExists) { console.log(`No backup found for user ${userName} on IP ${ip}`); await this.tcpCommunicator.disconnect(); return true; // Skip user if no backup found, do not mark as error } const backupStructure = await this.requestBackupStructure(userName); if (!backupStructure || Object.keys(backupStructure).length === 0) { console.log(`No files found in backup structure for user ${userName} on IP ${ip}`); await this.tcpCommunicator.disconnect(); return true; // Skip user if no files found, do not mark as error } for (const relativeFilePath of Object.keys(backupStructure)) { const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath); if (!fileRequestSuccess) { console.error(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`); await this.tcpCommunicator.disconnect(); return false; // Stop if any file fails to be retrieved } } 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) { console.error('Encryption key or IV is not set.'); return false; } let encryptedBuffer: Buffer; try { encryptedBuffer = Buffer.from(fileContent, 'base64'); } catch (error) { console.error('Error decoding base64 file content:', error); return false; } 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) { console.error('Error decrypting file:', error); return false; } 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); console.log(`File saved successfully: ${fullFilePath}`); return true; } catch (error: any) { console.error(`Error saving file ${relativeFilePath}: ${error.message}`); return false; } } 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); }); } }