184 lines
7.7 KiB
TypeScript
184 lines
7.7 KiB
TypeScript
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<void> {
|
|
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<boolean> {
|
|
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<boolean> {
|
|
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<any> {
|
|
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<boolean> {
|
|
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<ParsedMessage | null> {
|
|
return new Promise((resolve) => {
|
|
const idResponseCheck = setInterval(async () => {
|
|
if (!this.tcpCommunicator) return null;
|
|
if (this.tcpCommunicator.hasResponseArrived()) {
|
|
clearInterval(idResponseCheck);
|
|
resolve(this.tcpCommunicator.getLastResult());
|
|
}
|
|
}, 100);
|
|
});
|
|
}
|
|
}
|