193 lines
7.4 KiB
TypeScript
193 lines
7.4 KiB
TypeScript
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}`);
|
|
}
|
|
}
|
|
}
|