restored backup
This commit is contained in:
Generated
+1192
-964
File diff suppressed because it is too large
Load Diff
+8
-2
@@ -17,6 +17,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
"@electron-forge/maker-squirrel": "^6.0.0",
|
"@electron-forge/maker-squirrel": "^6.0.0",
|
||||||
"@electron-forge/maker-zip": "^6.0.0",
|
"@electron-forge/maker-zip": "^6.0.0",
|
||||||
"@types/ping": "^0.4.4",
|
"@types/ping": "^0.4.4",
|
||||||
|
"@types/proper-lockfile": "^4.1.4",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"del-cli": "^5.0.0",
|
"del-cli": "^5.0.0",
|
||||||
@@ -54,14 +56,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@electron-forge/maker-zip",
|
"name": "@electron-forge/maker-zip",
|
||||||
"platforms": ["darwin"],
|
"platforms": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
"config": {
|
"config": {
|
||||||
"icon": "../app_icons/icon.icns"
|
"icon": "../app_icons/icon.icns"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@electron-forge/maker-deb",
|
"name": "@electron-forge/maker-deb",
|
||||||
"platforms": ["linux"],
|
"platforms": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
"config": {
|
"config": {
|
||||||
"icon": "../app_icons/icon.png"
|
"icon": "../app_icons/icon.png"
|
||||||
}
|
}
|
||||||
|
|||||||
+124
-164
@@ -1,209 +1,169 @@
|
|||||||
import { JsonManager } from './json_manager';
|
|
||||||
import { TcpCommunicator } from "./tcp_communicator";
|
|
||||||
import { operationCodes } from '../network/operation_codes';
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import crypto from 'crypto';
|
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 { parentPort } from 'worker_threads';
|
||||||
import { ParsedMessage } from "../network/message_handler";
|
import { ParsedMessage } from "../network/message_handler";
|
||||||
|
|
||||||
export class BackupRetrievalWorker {
|
export class BackupManager {
|
||||||
private userConfig: JsonManager;
|
private fileEncryptor: FileEncryptor | null = null;
|
||||||
|
private memoryManager: MemoryManager;
|
||||||
private applicationInfo: JsonManager;
|
private applicationInfo: JsonManager;
|
||||||
|
private userConfig: JsonManager;
|
||||||
private readonly clientPort: number;
|
private readonly clientPort: number;
|
||||||
private readonly destinationPath: string;
|
private isBusy: boolean = false;
|
||||||
private encryptionKey: Buffer | null = null;
|
|
||||||
private iv: Buffer | null = null;
|
|
||||||
private tcpCommunicator: TcpCommunicator | null = null;
|
|
||||||
private isBusy: boolean;
|
|
||||||
private lastProcessedUserIndex: number;
|
|
||||||
|
|
||||||
constructor(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string) {
|
constructor(userConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, clientPort: number) {
|
||||||
this.userConfig = new JsonManager(userConfigPath);
|
|
||||||
this.applicationInfo = new JsonManager(applicationInfoPath);
|
this.applicationInfo = new JsonManager(applicationInfoPath);
|
||||||
|
this.userConfig = new JsonManager(userConfigPath);
|
||||||
|
this.memoryManager = new MemoryManager(memoryManagerPath);
|
||||||
this.clientPort = clientPort;
|
this.clientPort = clientPort;
|
||||||
this.destinationPath = destinationPath;
|
|
||||||
this.isBusy = false;
|
|
||||||
this.lastProcessedUserIndex = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (!this.isBusy) {
|
if (!this.isBusy) {
|
||||||
this.log('Start successfully. Processing backup tasks.');
|
|
||||||
await this.processBackupTasks();
|
|
||||||
}
|
|
||||||
}, 10000); // Retry every 10 seconds if there's an error
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processBackupTasks(): Promise<void> {
|
|
||||||
this.isBusy = true;
|
this.isBusy = true;
|
||||||
|
this.log('Start successfully. Backup files to users.');
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
}, 10000); // 10-second interval for testing
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initialize(): Promise<void> {
|
||||||
|
this.isBusy = true;
|
||||||
try {
|
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');
|
const userInfo = await this.userConfig.readValue('user_info');
|
||||||
if (!userInfo || !userInfo.name) {
|
if (!userInfo || !userInfo.name) {
|
||||||
throw new Error('User information or name is missing.');
|
this.log('User information is missing in user configuration.', 'error');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const userName = userInfo.name;
|
const userName = userInfo.name;
|
||||||
|
|
||||||
const encryptionData = await this.userConfig.readValue('encryption_key');
|
const backupDirectoryData = await this.applicationInfo.readValue('backupDirectory');
|
||||||
if (!encryptionData || !encryptionData.key || !encryptionData.iv) {
|
if (!backupDirectoryData || !backupDirectoryData.id || !backupDirectoryData.path) {
|
||||||
throw new Error('Encryption key or IV is missing.');
|
this.log('Backup directory information is missing in application info.', 'error');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.encryptionKey = Buffer.from(encryptionData.key, 'base64');
|
|
||||||
this.iv = Buffer.from(encryptionData.iv, 'base64');
|
|
||||||
|
|
||||||
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
||||||
if (!activeUsersIp || !activeUsersIp.length) {
|
if (!activeUsersIp || !activeUsersIp.length) {
|
||||||
this.isBusy = false;
|
this.log('No active users found.', 'error');
|
||||||
throw new Error('No active users found.');
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each user, starting from the last processed index
|
const backupDirectoryId = backupDirectoryData.id;
|
||||||
let backupSuccessful = true;
|
const backupDirectoryPath = backupDirectoryData.path;
|
||||||
for (let i = this.lastProcessedUserIndex; i < activeUsersIp.length; i++) {
|
|
||||||
const ip = activeUsersIp[i];
|
const directoryData = await this.memoryManager.retrieveMetaInformation(backupDirectoryId);
|
||||||
const success = await this.processBackupForIp(ip, userName);
|
if (!directoryData || !directoryData.structure) {
|
||||||
if (!success) {
|
this.log('Backup directory structure is missing in memory.', 'error');
|
||||||
backupSuccessful = false;
|
return;
|
||||||
this.lastProcessedUserIndex = i; // Remember where it stopped
|
}
|
||||||
|
|
||||||
|
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;
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backupSuccessful) {
|
|
||||||
this.log('Backup retrieval completed successfully for all users.');
|
|
||||||
this.lastProcessedUserIndex = 0; // Reset index to start from the beginning next cycle
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
this.log(error.message, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isBusy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
|
|
||||||
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
|
|
||||||
if (!await this.tcpCommunicator.connect()) {
|
|
||||||
this.log(`Failed to connect to ${ip}`, 'error');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const backupExists = await this.checkIfBackupExists(userName);
|
|
||||||
if (!backupExists) {
|
|
||||||
this.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) {
|
|
||||||
this.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) {
|
|
||||||
this.log(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`, 'error');
|
|
||||||
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) {
|
|
||||||
this.log('Encryption key or IV is not set.', 'error');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let encryptedBuffer: Buffer;
|
|
||||||
try {
|
|
||||||
encryptedBuffer = Buffer.from(fileContent, 'base64');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log(`Error decoding base64 file content: ${error}`, 'error');
|
this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error');
|
||||||
return false;
|
} finally {
|
||||||
|
await tcpCommunicator.disconnect();
|
||||||
|
this.log(`Disconnected from ${ip}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
this.log(`Error decrypting file: ${error}`, '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);
|
|
||||||
this.log(`File saved successfully: ${fullFilePath}`);
|
|
||||||
return true;
|
|
||||||
} catch (error: any) {
|
|
||||||
this.log(`Error saving file ${relativeFilePath}: ${error.message}`, 'error');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async waitForResponse(): Promise<ParsedMessage | null> {
|
if (unsentFiles.length > 0) {
|
||||||
|
parentPort?.postMessage({ success: false, message: 'Backup could not be completed for all files', unsentFiles });
|
||||||
|
} else {
|
||||||
|
parentPort?.postMessage({ success: true, message: 'Backup completed successfully' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const idResponseCheck = setInterval(async () => {
|
const idResponseCheck = setInterval(() => {
|
||||||
if (!this.tcpCommunicator) return null;
|
if (!tcpCommunicator) return null;
|
||||||
if (this.tcpCommunicator.hasResponseArrived()) {
|
if (tcpCommunicator.hasResponseArrived()) {
|
||||||
clearInterval(idResponseCheck);
|
clearInterval(idResponseCheck);
|
||||||
resolve(this.tcpCommunicator.getLastResult());
|
resolve(tcpCommunicator.getLastResult());
|
||||||
}
|
}
|
||||||
}, 100); // Check every 100 milliseconds
|
}, 100);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unified logging function
|
// Unified logging function
|
||||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void {
|
||||||
const prefix = '[BackupRetrievalWorker]';
|
const prefix = '[BackupManager]';
|
||||||
if (level === 'error') {
|
if (level === 'error') {
|
||||||
console.error(`${prefix} ${message}`);
|
console.error(`${prefix} ${message}`);
|
||||||
|
} else if (level === 'warn') {
|
||||||
|
console.warn(`${prefix} ${message}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`${prefix} ${message}`);
|
console.log(`${prefix} ${message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ export class BackupRetrievalWorker {
|
|||||||
this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error');
|
this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error');
|
||||||
parentPort?.postMessage({ success: false, message: `A problem occurred: ${error.message}` });
|
parentPort?.postMessage({ success: false, message: `A problem occurred: ${error.message}` });
|
||||||
}
|
}
|
||||||
|
finally{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
|
private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export class DepartmentSharer {
|
|||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (!this.isBusy) {
|
if (!this.isBusy) {
|
||||||
|
this.isBusy = true;
|
||||||
this.log('Start successfully. Sharing files with the department.');
|
this.log('Start successfully. Sharing files with the department.');
|
||||||
await this.shareFilesWithDepartment();
|
await this.shareFilesWithDepartment();
|
||||||
}
|
}
|
||||||
@@ -40,13 +41,11 @@ export class DepartmentSharer {
|
|||||||
|
|
||||||
// Share files with users in the same department
|
// Share files with users in the same department
|
||||||
private async shareFilesWithDepartment(): Promise<void> {
|
private async shareFilesWithDepartment(): Promise<void> {
|
||||||
this.isBusy = true;
|
try {
|
||||||
|
|
||||||
// Get the current user's department information
|
// Get the current user's department information
|
||||||
const userInfo = await this.userConfig.readValue('user_info');
|
const userInfo = await this.userConfig.readValue('user_info');
|
||||||
if (!userInfo || !userInfo.departmentId || !userInfo.name) {
|
if (!userInfo || !userInfo.departmentId || !userInfo.name) {
|
||||||
this.log('User information or department ID is missing in the configuration.', 'error');
|
throw new Error('User information or department ID is missing in the configuration.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const departmentId = userInfo.departmentId;
|
const departmentId = userInfo.departmentId;
|
||||||
@@ -55,28 +54,24 @@ export class DepartmentSharer {
|
|||||||
// Get the list of active users from applicationInfo
|
// Get the list of active users from applicationInfo
|
||||||
const activeUsersId = await this.applicationInfo.readValue('active_users_info');
|
const activeUsersId = await this.applicationInfo.readValue('active_users_info');
|
||||||
if (!activeUsersId) {
|
if (!activeUsersId) {
|
||||||
this.log('No active users found.', 'error');
|
throw new Error('No active users found.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId);
|
const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId);
|
||||||
if (!activeUsers || activeUsers.length === 0) {
|
if (!activeUsers || activeUsers.length === 0) {
|
||||||
this.log('No active users found.', 'error');
|
throw new Error('No active users found.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter users who belong to the same department
|
// Filter users who belong to the same department
|
||||||
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId);
|
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId);
|
||||||
if (departmentUsers.length === 0) {
|
if (departmentUsers.length === 0) {
|
||||||
this.log('No users found in the same department.', 'log');
|
throw new Error('No users found in the same department.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get department directory info
|
// Get department directory info
|
||||||
const departmentData = await this.applicationInfo.readValue('departmentDirectory');
|
const departmentData = await this.applicationInfo.readValue('departmentDirectory');
|
||||||
if (!departmentData || !departmentData.path || !departmentData.id) {
|
if (!departmentData || !departmentData.path || !departmentData.id) {
|
||||||
this.log('No department directory found.', 'error');
|
throw new Error('No department directory found.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.departmentDirectory = departmentData.path;
|
this.departmentDirectory = departmentData.path;
|
||||||
@@ -84,8 +79,7 @@ export class DepartmentSharer {
|
|||||||
// Read files from the MemoryManager related to this department
|
// Read files from the MemoryManager related to this department
|
||||||
const departmentFiles = await this.memoryManager.retrieveMetaInformation(departmentData.id);
|
const departmentFiles = await this.memoryManager.retrieveMetaInformation(departmentData.id);
|
||||||
if (!departmentFiles || !departmentFiles.structure) {
|
if (!departmentFiles || !departmentFiles.structure) {
|
||||||
this.log('No files found for this department in the memory manager.', 'error');
|
throw new Error('No files found for this department in the memory manager.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over all department users and perform the operations
|
// Iterate over all department users and perform the operations
|
||||||
@@ -104,6 +98,14 @@ export class DepartmentSharer {
|
|||||||
await this.tcpCommunicator.disconnect();
|
await this.tcpCommunicator.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(error: any) {
|
||||||
|
this.log(error.message, 'error');
|
||||||
|
}
|
||||||
|
finally{
|
||||||
|
this.log('Department sharing completed.');
|
||||||
|
this.isBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the department directory for a user
|
// Clear the department directory for a user
|
||||||
private async clearDepartmentDirectory(): Promise<boolean> {
|
private async clearDepartmentDirectory(): Promise<boolean> {
|
||||||
|
|||||||
@@ -1,47 +1,46 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import * as lockfile from 'proper-lockfile';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export class JsonManager {
|
export class JsonManager {
|
||||||
private readonly filePath: string;
|
private readonly filePath: string;
|
||||||
private readonly lockFilePath: string;
|
|
||||||
|
|
||||||
constructor(filePath: string) {
|
constructor(filePath: string) {
|
||||||
const dir = path.dirname(filePath);
|
const dir = path.dirname(filePath);
|
||||||
|
|
||||||
// Check if the directory exists, throw error if it doesn't
|
// Check if the directory exists, throw an error if it doesn't
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
throw new Error(`The directory does not exist: ${dir}`);
|
throw new Error(`The directory does not exist: ${dir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filePath = filePath;
|
this.filePath = filePath;
|
||||||
this.lockFilePath = `${filePath}.lock`; // Define the lock file path
|
|
||||||
|
|
||||||
// If the file doesn't exist, create it
|
// If the file doesn't exist, create it with an empty JSON object
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8');
|
fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to acquire a lock (create .lock file)
|
// Method to acquire a lock with retries
|
||||||
private async acquireLock(): Promise<void> {
|
private async acquireLock(): Promise<() => Promise<void>> {
|
||||||
while (fs.existsSync(this.lockFilePath)) {
|
return lockfile.lock(this.filePath, {
|
||||||
// Wait until the lock file is released
|
retries: {
|
||||||
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms delay before retrying
|
retries: 20, // Retry up to 10 times
|
||||||
|
factor: 1, // Retry factor
|
||||||
|
minTimeout: 100, // Minimum delay between retries in ms
|
||||||
|
maxTimeout: 200 // Maximum delay between retries in ms
|
||||||
}
|
}
|
||||||
// Create the lock file
|
});
|
||||||
fs.writeFileSync(this.lockFilePath, '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to release the lock (delete .lock file)
|
// Method to release the lock
|
||||||
private releaseLock(): void {
|
private async releaseLock(release: () => Promise<void>): Promise<void> {
|
||||||
if (fs.existsSync(this.lockFilePath)) {
|
await release();
|
||||||
fs.unlinkSync(this.lockFilePath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read a value by key from the JSON file with a lock
|
// Read a value by key from the JSON file with a lock
|
||||||
public async readValue(key: string): Promise<any | null> {
|
public async readValue(key: string): Promise<any | null> {
|
||||||
await this.acquireLock(); // Acquire the lock
|
const release = await this.acquireLock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(this.filePath)) return null;
|
if (!fs.existsSync(this.filePath)) return null;
|
||||||
@@ -52,13 +51,13 @@ export class JsonManager {
|
|||||||
console.error(`Error reading from JSON file: ${err.message}`);
|
console.error(`Error reading from JSON file: ${err.message}`);
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
this.releaseLock(); // Always release the lock after the operation
|
await this.releaseLock(release); // Always release the lock after the operation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a key-value pair to the JSON file with a lock
|
// Write a key-value pair to the JSON file with a lock
|
||||||
public async writeValue(key: string, value: any): Promise<boolean> {
|
public async writeValue(key: string, value: any): Promise<boolean> {
|
||||||
await this.acquireLock(); // Acquire the lock
|
const release = await this.acquireLock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data: { [key: string]: any } = {};
|
let data: { [key: string]: any } = {};
|
||||||
@@ -76,13 +75,13 @@ export class JsonManager {
|
|||||||
console.error(`Error writing to JSON file: ${err.message}`);
|
console.error(`Error writing to JSON file: ${err.message}`);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
this.releaseLock(); // Always release the lock after the operation
|
await this.releaseLock(release); // Always release the lock after the operation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a key-value pair from the JSON file with a lock
|
// Remove a key-value pair from the JSON file with a lock
|
||||||
public async removeValue(key: string): Promise<boolean> {
|
public async removeValue(key: string): Promise<boolean> {
|
||||||
await this.acquireLock(); // Acquire the lock
|
const release = await this.acquireLock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(this.filePath)) return false;
|
if (!fs.existsSync(this.filePath)) return false;
|
||||||
@@ -98,13 +97,13 @@ export class JsonManager {
|
|||||||
console.error(`Error removing key from JSON file: ${err.message}`);
|
console.error(`Error removing key from JSON file: ${err.message}`);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
this.releaseLock(); // Always release the lock after the operation
|
await this.releaseLock(release); // Always release the lock after the operation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the JSON file by clearing all data with a lock
|
// Reset the JSON file by clearing all data with a lock
|
||||||
public async resetFile(): Promise<boolean> {
|
public async resetFile(): Promise<boolean> {
|
||||||
await this.acquireLock(); // Acquire the lock
|
const release = await this.acquireLock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8');
|
fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8');
|
||||||
@@ -113,7 +112,7 @@ export class JsonManager {
|
|||||||
console.error(`Error resetting JSON file: ${err.message}`);
|
console.error(`Error resetting JSON file: ${err.message}`);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
this.releaseLock(); // Always release the lock after the operation
|
await this.releaseLock(release); // Always release the lock after the operation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ export class WindowManager {
|
|||||||
constructor(mainWindow: BrowserWindow, pathToPagesDir: string) {
|
constructor(mainWindow: BrowserWindow, pathToPagesDir: string) {
|
||||||
this.pathToPagesDir = pathToPagesDir;
|
this.pathToPagesDir = pathToPagesDir;
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
|
this.log('WindowManager initialized.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging helper function
|
||||||
|
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
||||||
|
const prefix = '[WindowManager]';
|
||||||
|
if (level === 'error') {
|
||||||
|
console.error(`${prefix} ${message}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${prefix} ${message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show an alert dialog
|
// Show an alert dialog
|
||||||
@@ -21,8 +32,9 @@ export class WindowManager {
|
|||||||
message: message,
|
message: message,
|
||||||
buttons: ['OK'],
|
buttons: ['OK'],
|
||||||
});
|
});
|
||||||
|
this.log(`Alert displayed with message: "${message}"`);
|
||||||
} else {
|
} else {
|
||||||
console.error('Main window is not available.');
|
this.log('Main window is not available.', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,17 +43,17 @@ export class WindowManager {
|
|||||||
if (this.mainWindow) {
|
if (this.mainWindow) {
|
||||||
try {
|
try {
|
||||||
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`);
|
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`);
|
||||||
console.log(`Navigating to: ${destinationPath}`);
|
this.log(`Navigating to: ${destinationPath}`);
|
||||||
|
|
||||||
// Load the destination HTML file into the main window
|
// Load the destination HTML file into the main window
|
||||||
await this.mainWindow.loadFile(destinationPath);
|
await this.mainWindow.loadFile(destinationPath);
|
||||||
console.log(`Navigated to ${destination}`);
|
this.log(`Navigated to ${destination}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error changing content:', error);
|
this.log(`Error changing content: ${error}`, 'error');
|
||||||
throw error; // Pass the error back to the render process
|
throw error; // Pass the error back to the render process
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('Main window is not available.');
|
this.log('Main window is not available.', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,26 +63,26 @@ export class WindowManager {
|
|||||||
properties: ['openDirectory'], // Only allow selecting directories
|
properties: ['openDirectory'], // Only allow selecting directories
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the user cancels, result.filePaths will be an empty array
|
|
||||||
if (result.filePaths && result.filePaths.length > 0) {
|
if (result.filePaths && result.filePaths.length > 0) {
|
||||||
|
this.log(`Directory selected: ${result.filePaths[0]}`);
|
||||||
return result.filePaths[0]; // Return the selected directory path
|
return result.filePaths[0]; // Return the selected directory path
|
||||||
} else {
|
} else {
|
||||||
console.log('No directory selected.');
|
this.log('No directory selected.');
|
||||||
return undefined; // Return undefined if no directory was selected
|
return undefined; // Return undefined if no directory was selected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show a file in the explorer
|
||||||
async showFileInExplorer(filePath: string): Promise<void> {
|
async showFileInExplorer(filePath: string): Promise<void> {
|
||||||
if (filePath && fs.existsSync(filePath)) {
|
if (filePath && fs.existsSync(filePath)) {
|
||||||
try {
|
try {
|
||||||
// Use Electron's shell module to show the file in the explorer
|
|
||||||
shell.showItemInFolder(filePath);
|
shell.showItemInFolder(filePath);
|
||||||
console.log(`Opened file explorer for: ${filePath}`);
|
this.log(`Opened file explorer for: ${filePath}`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error showing file in explorer: ${error.message}`);
|
this.log(`Error showing file in explorer: ${error.message}`, 'error');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('File path is undefined or does not exist.');
|
this.log('File path is undefined or does not exist.', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +96,10 @@ export class WindowManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result.filePaths && result.filePaths.length > 0) {
|
if (result.filePaths && result.filePaths.length > 0) {
|
||||||
|
this.log(`File selected: ${result.filePaths[0]}`);
|
||||||
return result.filePaths[0]; // Return the selected file path
|
return result.filePaths[0]; // Return the selected file path
|
||||||
} else {
|
} else {
|
||||||
console.log('No file selected.');
|
this.log('No file selected.');
|
||||||
return undefined; // Return undefined if no file was selected
|
return undefined; // Return undefined if no file was selected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,15 +107,14 @@ export class WindowManager {
|
|||||||
// Method to display an announcement in a new window
|
// Method to display an announcement in a new window
|
||||||
async displayAnnouncement(): Promise<void> {
|
async displayAnnouncement(): Promise<void> {
|
||||||
if (this.announcementWindow) {
|
if (this.announcementWindow) {
|
||||||
// If the window is already open, focus it
|
|
||||||
this.announcementWindow.focus();
|
this.announcementWindow.focus();
|
||||||
|
this.log('Announcement window focused.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainScreen = require('electron').screen.getPrimaryDisplay();
|
const mainScreen = require('electron').screen.getPrimaryDisplay();
|
||||||
const { width, height } = mainScreen.size;
|
const { width, height } = mainScreen.size;
|
||||||
|
|
||||||
// Initialize the announcement window
|
|
||||||
this.announcementWindow = new BrowserWindow({
|
this.announcementWindow = new BrowserWindow({
|
||||||
width: width / 3,
|
width: width / 3,
|
||||||
height: height / 2,
|
height: height / 2,
|
||||||
@@ -116,20 +128,22 @@ export class WindowManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.announcementWindow.removeMenu();
|
this.announcementWindow.removeMenu();
|
||||||
|
|
||||||
// Load the announcement page
|
|
||||||
const announcementPath = path.join(this.pathToPagesDir, 'announcement.html');
|
const announcementPath = path.join(this.pathToPagesDir, 'announcement.html');
|
||||||
await this.announcementWindow.loadFile(announcementPath);
|
await this.announcementWindow.loadFile(announcementPath);
|
||||||
|
|
||||||
|
this.log(`Announcement window opened at: ${announcementPath}`);
|
||||||
|
|
||||||
// Handle window close
|
// Handle window close
|
||||||
this.announcementWindow.on('closed', () => {
|
this.announcementWindow.on('closed', () => {
|
||||||
this.announcementWindow = null; // Clean up the reference
|
this.announcementWindow = null;
|
||||||
|
this.log('Announcement window closed.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeAnnouncementWindow(): Promise<void> {
|
async closeAnnouncementWindow(): Promise<void> {
|
||||||
if (this.announcementWindow) {
|
if (this.announcementWindow) {
|
||||||
this.announcementWindow.close();
|
this.announcementWindow.close();
|
||||||
|
this.log('Announcement window closed by user.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ app.whenReady().then(async () => {
|
|||||||
await applicationInfo.writeValue('announcement', '');
|
await applicationInfo.writeValue('announcement', '');
|
||||||
await memoryManager.resetFile();
|
await memoryManager.resetFile();
|
||||||
|
|
||||||
workerManager.startNetworkScannerWorker(UDP_PORT, 'login', 'uc_not_found', path.join(pathToJsons, 'application.json'));
|
workerManager.startNetworkScannerWorker(UDP_PORT, TCP_PORT, 'login', 'uc_not_found', 'reset_database', path.join(pathToJsons, 'userConfig.json'), path.join(pathToJsons, 'application.json'));
|
||||||
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
|
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
|
||||||
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
|
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
|
||||||
workerManager.startResourceCoordinatorWorker(
|
workerManager.startResourceCoordinatorWorker(
|
||||||
@@ -189,7 +189,7 @@ function registerIPCHandlers() {
|
|||||||
if(workerManager) {
|
if(workerManager) {
|
||||||
workerManager.closeAllWorkers();
|
workerManager.closeAllWorkers();
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
workerManager.startNetworkScannerWorker(UDP_PORT, 'login', 'uc_not_found', path.join(pathToJsons, 'application.json'));
|
workerManager.startNetworkScannerWorker(UDP_PORT, TCP_PORT, 'login', 'uc_not_found', 'reset_database', path.join(pathToJsons, 'userConfig.json'), path.join(pathToJsons, 'application.json'));
|
||||||
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
|
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
|
||||||
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
|
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
|
||||||
workerManager.startResourceCoordinatorWorker(
|
workerManager.startResourceCoordinatorWorker(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { workerData } from 'worker_threads';
|
import { workerData } from 'worker_threads';
|
||||||
import { UsersInfoFetcher } from '../helpers/users_info_fetcher';
|
import { UsersInfoFetcher } from '../helpers/users_info_fetcher';
|
||||||
import {BackupRetrievalWorker} from "../helpers/backup_manager";
|
import {BackupManager} from "../helpers/backup_manager";
|
||||||
import {FileSharer} from "../helpers/file_sharer";
|
import {FileSharer} from "../helpers/file_sharer";
|
||||||
import {DepartmentSharer} from "../helpers/department_sharer";
|
import {DepartmentSharer} from "../helpers/department_sharer";
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ const { usersConfigPath, applicationInfoPath, memoryManagerPath, queueManagerPat
|
|||||||
const usersInfoFetcher = new UsersInfoFetcher(applicationInfoPath, memoryManagerPath, tcpPort);
|
const usersInfoFetcher = new UsersInfoFetcher(applicationInfoPath, memoryManagerPath, tcpPort);
|
||||||
usersInfoFetcher.start();
|
usersInfoFetcher.start();
|
||||||
|
|
||||||
const backupManager = new BackupRetrievalWorker(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort);
|
const backupManager = new BackupManager(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort);
|
||||||
backupManager.start();
|
backupManager.start();
|
||||||
|
|
||||||
const fileSharer = new FileSharer(queueManagerPath, tcpPort);
|
const fileSharer = new FileSharer(queueManagerPath, tcpPort);
|
||||||
|
|||||||
+124
-164
@@ -1,209 +1,169 @@
|
|||||||
import { JsonManager } from './json_manager';
|
|
||||||
import { TcpCommunicator } from "./tcp_communicator";
|
|
||||||
import { operationCodes } from '../network/operation_codes';
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import crypto from 'crypto';
|
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 { parentPort } from 'worker_threads';
|
||||||
import { ParsedMessage } from "../network/message_handler";
|
import { ParsedMessage } from "../network/message_handler";
|
||||||
|
|
||||||
export class BackupRetrievalWorker {
|
export class BackupManager {
|
||||||
private userConfig: JsonManager;
|
private fileEncryptor: FileEncryptor | null = null;
|
||||||
|
private memoryManager: MemoryManager;
|
||||||
private applicationInfo: JsonManager;
|
private applicationInfo: JsonManager;
|
||||||
|
private userConfig: JsonManager;
|
||||||
private readonly clientPort: number;
|
private readonly clientPort: number;
|
||||||
private readonly destinationPath: string;
|
private isBusy: boolean = false;
|
||||||
private encryptionKey: Buffer | null = null;
|
|
||||||
private iv: Buffer | null = null;
|
|
||||||
private tcpCommunicator: TcpCommunicator | null = null;
|
|
||||||
private isBusy: boolean;
|
|
||||||
private lastProcessedUserIndex: number;
|
|
||||||
|
|
||||||
constructor(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string) {
|
constructor(userConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, clientPort: number) {
|
||||||
this.userConfig = new JsonManager(userConfigPath);
|
|
||||||
this.applicationInfo = new JsonManager(applicationInfoPath);
|
this.applicationInfo = new JsonManager(applicationInfoPath);
|
||||||
|
this.userConfig = new JsonManager(userConfigPath);
|
||||||
|
this.memoryManager = new MemoryManager(memoryManagerPath);
|
||||||
this.clientPort = clientPort;
|
this.clientPort = clientPort;
|
||||||
this.destinationPath = destinationPath;
|
|
||||||
this.isBusy = false;
|
|
||||||
this.lastProcessedUserIndex = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (!this.isBusy) {
|
if (!this.isBusy) {
|
||||||
this.log('Start successfully. Processing backup tasks.');
|
|
||||||
await this.processBackupTasks();
|
|
||||||
}
|
|
||||||
}, 10000); // Retry every 10 seconds if there's an error
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processBackupTasks(): Promise<void> {
|
|
||||||
this.isBusy = true;
|
this.isBusy = true;
|
||||||
|
this.log('Start successfully. Backup files to users.');
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
}, 10000); // 10-second interval for testing
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initialize(): Promise<void> {
|
||||||
|
this.isBusy = true;
|
||||||
try {
|
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');
|
const userInfo = await this.userConfig.readValue('user_info');
|
||||||
if (!userInfo || !userInfo.name) {
|
if (!userInfo || !userInfo.name) {
|
||||||
throw new Error('User information or name is missing.');
|
this.log('User information is missing in user configuration.', 'error');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const userName = userInfo.name;
|
const userName = userInfo.name;
|
||||||
|
|
||||||
const encryptionData = await this.userConfig.readValue('encryption_key');
|
const backupDirectoryData = await this.applicationInfo.readValue('backupDirectory');
|
||||||
if (!encryptionData || !encryptionData.key || !encryptionData.iv) {
|
if (!backupDirectoryData || !backupDirectoryData.id || !backupDirectoryData.path) {
|
||||||
throw new Error('Encryption key or IV is missing.');
|
this.log('Backup directory information is missing in application info.', 'error');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.encryptionKey = Buffer.from(encryptionData.key, 'base64');
|
|
||||||
this.iv = Buffer.from(encryptionData.iv, 'base64');
|
|
||||||
|
|
||||||
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
||||||
if (!activeUsersIp || !activeUsersIp.length) {
|
if (!activeUsersIp || !activeUsersIp.length) {
|
||||||
this.isBusy = false;
|
this.log('No active users found.', 'error');
|
||||||
throw new Error('No active users found.');
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each user, starting from the last processed index
|
const backupDirectoryId = backupDirectoryData.id;
|
||||||
let backupSuccessful = true;
|
const backupDirectoryPath = backupDirectoryData.path;
|
||||||
for (let i = this.lastProcessedUserIndex; i < activeUsersIp.length; i++) {
|
|
||||||
const ip = activeUsersIp[i];
|
const directoryData = await this.memoryManager.retrieveMetaInformation(backupDirectoryId);
|
||||||
const success = await this.processBackupForIp(ip, userName);
|
if (!directoryData || !directoryData.structure) {
|
||||||
if (!success) {
|
this.log('Backup directory structure is missing in memory.', 'error');
|
||||||
backupSuccessful = false;
|
return;
|
||||||
this.lastProcessedUserIndex = i; // Remember where it stopped
|
}
|
||||||
|
|
||||||
|
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;
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backupSuccessful) {
|
|
||||||
this.log('Backup retrieval completed successfully for all users.');
|
|
||||||
this.lastProcessedUserIndex = 0; // Reset index to start from the beginning next cycle
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
this.log(error.message, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isBusy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
|
|
||||||
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
|
|
||||||
if (!await this.tcpCommunicator.connect()) {
|
|
||||||
this.log(`Failed to connect to ${ip}`, 'error');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const backupExists = await this.checkIfBackupExists(userName);
|
|
||||||
if (!backupExists) {
|
|
||||||
this.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) {
|
|
||||||
this.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) {
|
|
||||||
this.log(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`, 'error');
|
|
||||||
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) {
|
|
||||||
this.log('Encryption key or IV is not set.', 'error');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let encryptedBuffer: Buffer;
|
|
||||||
try {
|
|
||||||
encryptedBuffer = Buffer.from(fileContent, 'base64');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log(`Error decoding base64 file content: ${error}`, 'error');
|
this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error');
|
||||||
return false;
|
} finally {
|
||||||
|
await tcpCommunicator.disconnect();
|
||||||
|
this.log(`Disconnected from ${ip}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
this.log(`Error decrypting file: ${error}`, '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);
|
|
||||||
this.log(`File saved successfully: ${fullFilePath}`);
|
|
||||||
return true;
|
|
||||||
} catch (error: any) {
|
|
||||||
this.log(`Error saving file ${relativeFilePath}: ${error.message}`, 'error');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async waitForResponse(): Promise<ParsedMessage | null> {
|
if (unsentFiles.length > 0) {
|
||||||
|
parentPort?.postMessage({ success: false, message: 'Backup could not be completed for all files', unsentFiles });
|
||||||
|
} else {
|
||||||
|
parentPort?.postMessage({ success: true, message: 'Backup completed successfully' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const idResponseCheck = setInterval(async () => {
|
const idResponseCheck = setInterval(() => {
|
||||||
if (!this.tcpCommunicator) return null;
|
if (!tcpCommunicator) return null;
|
||||||
if (this.tcpCommunicator.hasResponseArrived()) {
|
if (tcpCommunicator.hasResponseArrived()) {
|
||||||
clearInterval(idResponseCheck);
|
clearInterval(idResponseCheck);
|
||||||
resolve(this.tcpCommunicator.getLastResult());
|
resolve(tcpCommunicator.getLastResult());
|
||||||
}
|
}
|
||||||
}, 100); // Check every 100 milliseconds
|
}, 100);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unified logging function
|
// Unified logging function
|
||||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void {
|
||||||
const prefix = '[BackupRetrievalWorker]';
|
const prefix = '[BackupManager]';
|
||||||
if (level === 'error') {
|
if (level === 'error') {
|
||||||
console.error(`${prefix} ${message}`);
|
console.error(`${prefix} ${message}`);
|
||||||
|
} else if (level === 'warn') {
|
||||||
|
console.warn(`${prefix} ${message}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`${prefix} ${message}`);
|
console.log(`${prefix} ${message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export class DepartmentSharer {
|
|||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (!this.isBusy) {
|
if (!this.isBusy) {
|
||||||
|
this.isBusy = true;
|
||||||
this.log('Start successfully. Sharing files with the department.');
|
this.log('Start successfully. Sharing files with the department.');
|
||||||
await this.shareFilesWithDepartment();
|
await this.shareFilesWithDepartment();
|
||||||
}
|
}
|
||||||
@@ -40,13 +41,11 @@ export class DepartmentSharer {
|
|||||||
|
|
||||||
// Share files with users in the same department
|
// Share files with users in the same department
|
||||||
private async shareFilesWithDepartment(): Promise<void> {
|
private async shareFilesWithDepartment(): Promise<void> {
|
||||||
this.isBusy = true;
|
try {
|
||||||
|
|
||||||
// Get the current user's department information
|
// Get the current user's department information
|
||||||
const userInfo = await this.userConfig.readValue('user_info');
|
const userInfo = await this.userConfig.readValue('user_info');
|
||||||
if (!userInfo || !userInfo.departmentId || !userInfo.name) {
|
if (!userInfo || !userInfo.departmentId || !userInfo.name) {
|
||||||
this.log('User information or department ID is missing in the configuration.', 'error');
|
throw new Error('User information or department ID is missing in the configuration.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const departmentId = userInfo.departmentId;
|
const departmentId = userInfo.departmentId;
|
||||||
@@ -55,28 +54,24 @@ export class DepartmentSharer {
|
|||||||
// Get the list of active users from applicationInfo
|
// Get the list of active users from applicationInfo
|
||||||
const activeUsersId = await this.applicationInfo.readValue('active_users_info');
|
const activeUsersId = await this.applicationInfo.readValue('active_users_info');
|
||||||
if (!activeUsersId) {
|
if (!activeUsersId) {
|
||||||
this.log('No active users found.', 'error');
|
throw new Error('No active users found.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId);
|
const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId);
|
||||||
if (!activeUsers || activeUsers.length === 0) {
|
if (!activeUsers || activeUsers.length === 0) {
|
||||||
this.log('No active users found.', 'error');
|
throw new Error('No active users found.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter users who belong to the same department
|
// Filter users who belong to the same department
|
||||||
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId);
|
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId);
|
||||||
if (departmentUsers.length === 0) {
|
if (departmentUsers.length === 0) {
|
||||||
this.log('No users found in the same department.', 'log');
|
throw new Error('No users found in the same department.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get department directory info
|
// Get department directory info
|
||||||
const departmentData = await this.applicationInfo.readValue('departmentDirectory');
|
const departmentData = await this.applicationInfo.readValue('departmentDirectory');
|
||||||
if (!departmentData || !departmentData.path || !departmentData.id) {
|
if (!departmentData || !departmentData.path || !departmentData.id) {
|
||||||
this.log('No department directory found.', 'error');
|
throw new Error('No department directory found.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.departmentDirectory = departmentData.path;
|
this.departmentDirectory = departmentData.path;
|
||||||
@@ -84,8 +79,7 @@ export class DepartmentSharer {
|
|||||||
// Read files from the MemoryManager related to this department
|
// Read files from the MemoryManager related to this department
|
||||||
const departmentFiles = await this.memoryManager.retrieveMetaInformation(departmentData.id);
|
const departmentFiles = await this.memoryManager.retrieveMetaInformation(departmentData.id);
|
||||||
if (!departmentFiles || !departmentFiles.structure) {
|
if (!departmentFiles || !departmentFiles.structure) {
|
||||||
this.log('No files found for this department in the memory manager.', 'error');
|
throw new Error('No files found for this department in the memory manager.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over all department users and perform the operations
|
// Iterate over all department users and perform the operations
|
||||||
@@ -104,6 +98,14 @@ export class DepartmentSharer {
|
|||||||
await this.tcpCommunicator.disconnect();
|
await this.tcpCommunicator.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(error: any) {
|
||||||
|
this.log(error.message, 'error');
|
||||||
|
}
|
||||||
|
finally{
|
||||||
|
this.log('Department sharing completed.');
|
||||||
|
this.isBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the department directory for a user
|
// Clear the department directory for a user
|
||||||
private async clearDepartmentDirectory(): Promise<boolean> {
|
private async clearDepartmentDirectory(): Promise<boolean> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { workerData } from 'worker_threads';
|
import { workerData } from 'worker_threads';
|
||||||
import { UsersInfoFetcher } from '../helpers/users_info_fetcher';
|
import { UsersInfoFetcher } from '../helpers/users_info_fetcher';
|
||||||
import {BackupRetrievalWorker} from "../helpers/backup_manager";
|
import {BackupManager} from "../helpers/backup_manager";
|
||||||
import {FileSharer} from "../helpers/file_sharer";
|
import {FileSharer} from "../helpers/file_sharer";
|
||||||
import {DepartmentSharer} from "../helpers/department_sharer";
|
import {DepartmentSharer} from "../helpers/department_sharer";
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ const { usersConfigPath, applicationInfoPath, memoryManagerPath, queueManagerPat
|
|||||||
const usersInfoFetcher = new UsersInfoFetcher(applicationInfoPath, memoryManagerPath, tcpPort);
|
const usersInfoFetcher = new UsersInfoFetcher(applicationInfoPath, memoryManagerPath, tcpPort);
|
||||||
usersInfoFetcher.start();
|
usersInfoFetcher.start();
|
||||||
|
|
||||||
const backupManager = new BackupRetrievalWorker(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort);
|
const backupManager = new BackupManager(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort);
|
||||||
backupManager.start();
|
backupManager.start();
|
||||||
|
|
||||||
const fileSharer = new FileSharer(queueManagerPath, tcpPort);
|
const fileSharer = new FileSharer(queueManagerPath, tcpPort);
|
||||||
|
|||||||
Reference in New Issue
Block a user