import fs from 'fs' import path from 'path' import { FileEncryptor } from './file_encryptor' import { TcpCommunicator } from './tcp_communicator' import { operationCodes } from '../network/operation_codes' import { ParsedMessage } from '../network/message_handler' import { Database } from '../database/database' import { NetworkUserScheme } from '../database/schemes/network_scheme' export class BackupManager { private fileEncryptor: FileEncryptor | null = null private readonly db: Database private readonly clientPort: number private isBusy: boolean = false private intervalId: NodeJS.Timeout | null = null private stopRequested: boolean = false constructor(pathToDatabaseFile: string, clientPort: number) { this.db = new Database(pathToDatabaseFile) 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 data = await this.db.read() const userInfo = data.app_config.user_info const encryptionKey = data.app_config.encryption_key const usersIp = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip) const backupDirectoryData = data.local_resources.directory_schemes.backup this.fileEncryptor = new FileEncryptor(encryptionKey.key, encryptionKey.iv) await this.sendFilesToUsers( userInfo.name, usersIp, backupDirectoryData.structure, backupDirectoryData.path, ) } catch (error: any) { this.log(`Error in initialize process: ${error.message}`, 'error') } finally { this.log('Backup process completed.') this.isBusy = false } } private encryptFile(filePath: string): string { if (!this.fileEncryptor) { return filePath } if (!fs.existsSync(filePath)) { this.log(`File not found: ${filePath}`, 'error') return '' } return this.fileEncryptor.encryptFileToBase64(filePath) } private async sendFilesToUsers( userName: string, usersIp: string[], fileStructure: { [key: string]: 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}`) } } }