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 { this.intervalId = setInterval(async () => { if (!this.isBusy || !this.stopRequested) { this.isBusy = true; this.log('Start successfully. Backup files to users.'); await this.initialize(); } if (global.gc) { global.gc(); } }, 10000); // 10-second interval for testing } private async initialize(): Promise { this.isBusy = true; try { const 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 { 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 { return new Promise((resolve) => { const idResponseCheck = setInterval(() => { if (!tcpCommunicator) return null; if (tcpCommunicator.hasResponseArrived()) { clearInterval(idResponseCheck); resolve(tcpCommunicator.getLastResult()); } }, 100); }); } async stop(): Promise { this.stopRequested = true; // Signal that stop is requested if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } // Wait for any ongoing process to complete if busy while (this.isBusy) { await new Promise((resolve) => setTimeout(resolve, 100)); } console.log("[BackupManager] Stopped successfully."); } // Unified logging function private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void { const prefix = '[BackupManager]'; if (level === 'error') { console.error(`${prefix} ${message}`); } else if (level === 'warn') { console.warn(`${prefix} ${message}`); } else { console.log(`${prefix} ${message}`); } } }