added email verification
This commit is contained in:
+151
-168
@@ -1,192 +1,175 @@
|
||||
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";
|
||||
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 { JsonDatabase } from '../database/database'
|
||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
||||
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
||||
|
||||
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;
|
||||
private fileEncryptor: FileEncryptor | null = null
|
||||
private readonly db: JsonDatabase<DatabaseScheme, any>
|
||||
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;
|
||||
constructor(pathToDatabaseFile: string, clientPort: number) {
|
||||
this.db = new JsonDatabase(pathToDatabaseFile)
|
||||
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 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
|
||||
}
|
||||
|
||||
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
|
||||
if (!fs.existsSync(filePath)) {
|
||||
this.log(`File not found: ${filePath}`, 'error')
|
||||
return ''
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
this.isBusy = true;
|
||||
return this.fileEncryptor.encryptFileToBase64(filePath)
|
||||
}
|
||||
|
||||
private async sendFilesToUsers(
|
||||
userName: string,
|
||||
usersIp: string[],
|
||||
fileStructure: { [key: string]: 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 {
|
||||
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;
|
||||
}
|
||||
await tcpCommunicator.connect()
|
||||
this.log(`Connected to ${ip}`)
|
||||
|
||||
this.fileEncryptor = new FileEncryptor(encryptionKeyData.key, encryptionKeyData.iv);
|
||||
const sendSuccess = await tcpCommunicator.sendMessage(
|
||||
operationCodes.BACKUP_FILE,
|
||||
metaInfo,
|
||||
Buffer.from(encryptedFileContent, 'base64'),
|
||||
)
|
||||
if (!sendSuccess) {
|
||||
throw new Error('Failed to send file content.')
|
||||
}
|
||||
|
||||
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 responseReceived = await this.waitForResponse(tcpCommunicator)
|
||||
if (!responseReceived) {
|
||||
throw new Error('Timeout waiting for the message response.')
|
||||
}
|
||||
|
||||
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');
|
||||
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 {
|
||||
this.log('Backup process completed.');
|
||||
this.isBusy = false;
|
||||
await tcpCommunicator.disconnect()
|
||||
this.log(`Disconnected from ${ip}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private encryptFile(filePath: string): string {
|
||||
if (!this.fileEncryptor) {
|
||||
return filePath;
|
||||
}
|
||||
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' })
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
this.log(`File not found: ${filePath}`, 'error');
|
||||
return '';
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
return this.fileEncryptor.encryptFileToBase64(filePath);
|
||||
async stop(): Promise<void> {
|
||||
this.stopRequested = true // Signal that stop is requested
|
||||
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
this.intervalId = null
|
||||
}
|
||||
|
||||
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' });
|
||||
}
|
||||
// Wait for any ongoing process to complete if busy
|
||||
while (this.isBusy) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
}
|
||||
|
||||
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}`);
|
||||
}
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user