added email verification

This commit is contained in:
andrei-mihnea-cerbu
2025-02-04 19:08:34 +02:00
parent 9baec7a7cb
commit dcf505c89b
67 changed files with 3922 additions and 3638 deletions
+151 -168
View File
@@ -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}`)
}
}
}
+186 -182
View File
@@ -1,206 +1,210 @@
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 crypto from 'crypto';
import { ParsedMessage } from "../network/message_handler";
import { TcpCommunicator } from './tcp_communicator'
import { operationCodes } from '../network/operation_codes'
import path from 'path'
import fs from 'fs'
import crypto from 'crypto'
import { ParsedMessage } from '../network/message_handler'
import { JsonDatabase } from '../database/database'
import { DatabaseScheme } from '../database/schemes/database_scheme'
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;
private stopRequested: boolean = false;
private isBusy: boolean = false;
private db: JsonDatabase<DatabaseScheme, any>
private readonly clientPort: number
private readonly destinationPath: string
private encryptionKey: Buffer | null = null
private iv: Buffer | null = null
private tcpCommunicator: TcpCommunicator | null = null
private stopRequested: boolean = false
private isBusy: boolean = false
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;
constructor(pathToDatabaseFile: string, clientPort: number, destinationPath: string) {
this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
this.clientPort = clientPort
this.destinationPath = destinationPath
}
// Logging helper function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[BackupRetrievalWorker]'
if (level === 'error') {
console.error(`${prefix} ${message}`)
} else {
console.log(`${prefix} ${message}`)
}
}
async start(): Promise<void> {
if (!this.stopRequested) return
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 userName = userInfo.name
this.encryptionKey = Buffer.from(encryptionKey.key, 'base64')
this.iv = Buffer.from(encryptionKey.iv, 'base64')
const activeUsers = data.network.usersInLan
if (!activeUsers.length) {
throw new Error('No active users found.')
}
for (const lanUser of activeUsers) {
const success = await this.processBackupForIp(lanUser.ip, userName)
if (!success) {
throw new Error(`Failed to retrieve backup from ${lanUser.ip}`)
}
this.log(`Backup retrieved successfully from ${lanUser.ip}`)
}
process.send?.({ type: 'showAlert', message: 'Backup retrieval completed successfully.' })
process.send?.({ type: 'changeContent', page: 'main_menu' })
} catch (error: any) {
this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error')
process.send?.({ type: 'showAlert', message: `A problem occurred: ${error.message}` })
process.send?.({ type: 'changeContent', page: 'main_menu' })
} finally {
this.isBusy = false
}
// Logging helper function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[BackupRetrievalWorker]';
if (level === 'error') {
console.error(`${prefix} ${message}`);
} else {
console.log(`${prefix} ${message}`);
}
if (global.gc) {
global.gc()
}
}
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 true
}
async start(): Promise<void> {
if(!this.stopRequested) return;
this.isBusy = true;
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) {
throw new Error('No active users found.');
}
for (const ip of activeUsersIp) {
const success = await this.processBackupForIp(ip, userName);
if (!success) {
throw new Error(`Failed to retrieve backup from ${ip}`);
}
this.log(`Backup retrieved successfully from ${ip}`);
}
process.send?.({ type: 'showAlert', message: 'Backup retrieval completed successfully.' });
process.send?.({ type: 'changeContent', page: 'main_menu' });
} catch (error: any) {
this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error');
process.send?.({ type: 'showAlert', message: `A problem occurred: ${error.message}` });
process.send?.({ type: 'changeContent', page: 'main_menu' });
}
finally {
this.isBusy = false;
}
if (global.gc) {
global.gc();
}
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
}
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 true;
}
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;
}
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;
}
for (const relativeFilePath of Object.keys(backupStructure)) {
const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath);
if (!fileRequestSuccess) {
await this.tcpCommunicator.disconnect();
throw new Error(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`);
}
}
await this.tcpCommunicator.disconnect();
return true;
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
}
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;
for (const relativeFilePath of Object.keys(backupStructure)) {
const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath)
if (!fileRequestSuccess) {
await this.tcpCommunicator.disconnect()
throw new Error(
`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`,
)
}
}
private async requestBackupStructure(userName: string): Promise<any> {
if (!this.tcpCommunicator) return false;
await this.tcpCommunicator.disconnect()
return true
}
const metaInfo = { name: userName };
if (!await this.tcpCommunicator.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo)) return null;
private async checkIfBackupExists(userName: string): Promise<boolean> {
if (!this.tcpCommunicator) return false
const response = await this.waitForResponse();
return response?.metaInfo?.structure || null;
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) {
throw new Error('Encryption key or IV is not set.')
}
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;
let encryptedBuffer: Buffer
try {
encryptedBuffer = Buffer.from(fileContent, 'base64')
} catch (error: any) {
throw new Error(`Error decoding base64 file content: ${error.message}`)
}
private saveFile(relativeFilePath: string, fileContent: string): boolean {
if (!this.encryptionKey || !this.iv) {
throw new Error('Encryption key or IV is not set.');
}
let encryptedBuffer: Buffer;
try {
encryptedBuffer = Buffer.from(fileContent, 'base64');
} catch (error: any) {
throw new Error(`Error decoding base64 file content: ${error.message}`);
}
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: any) {
throw new Error(`Error decrypting file: ${error.message}`);
}
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) {
throw new Error(`Error saving file ${relativeFilePath}: ${error.message}`);
}
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: any) {
throw new Error(`Error decrypting file: ${error.message}`)
}
async stop(): Promise<void> {
this.stopRequested = true; // Signal that stop is requested
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) {
throw new Error(`Error saving file ${relativeFilePath}: ${error.message}`)
}
}
// Wait for any ongoing process to complete if busy
while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100));
async stop(): Promise<void> {
this.stopRequested = true // Signal that stop is requested
// Wait for any ongoing process to complete if busy
while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100))
}
console.log('[BackupManager] Stopped successfully.')
}
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())
}
console.log("[BackupManager] Stopped successfully.");
}
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);
});
}
}, 100)
})
}
}
+155 -178
View File
@@ -1,214 +1,191 @@
import fs from 'fs';
import path from 'path';
import { TcpCommunicator } from './tcp_communicator';
import { operationCodes } from '../network/operation_codes';
import { JsonManager } from './json_manager';
import { MemoryManager } from './memory_manager';
import { ParsedMessage } from "../network/message_handler";
import fs from 'fs'
import path from 'path'
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'
export class DepartmentSharer {
private userConfig: JsonManager;
private applicationInfo: JsonManager;
private memoryManager: MemoryManager;
private departmentDirectory: string | null;
private readonly clientPort: number;
private isBusy: boolean = false;
private tcpCommunicator: TcpCommunicator | null = null;
private stopRequested: boolean = false;
private intervalId: NodeJS.Timeout | null = null;
private readonly db: JsonDatabase<DatabaseScheme, any>
private departmentDirectory: string | null = null
private readonly clientPort: number
private isBusy: boolean = false
private tcpCommunicator: TcpCommunicator | null = null
private stopRequested: boolean = false
private intervalId: NodeJS.Timeout | null = null
constructor(
userConfigPath: string,
applicationInfoPath: string,
memoryManagerPath: string,
clientPort: number
) {
this.userConfig = new JsonManager(userConfigPath);
this.applicationInfo = new JsonManager(applicationInfoPath);
this.memoryManager = new MemoryManager(memoryManagerPath);
this.clientPort = clientPort;
this.departmentDirectory = null;
}
constructor(pathToDatabaseFile: string, clientPort: number) {
this.db = new JsonDatabase(pathToDatabaseFile)
this.clientPort = clientPort
}
// Start sharing files with the department every minute
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) {
this.isBusy = true;
this.log('Start successfully. Sharing files with the department.');
await this.shareFilesWithDepartment();
}
// Start sharing files with the department every minute
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) {
this.isBusy = true
this.log('Start successfully. Sharing files with the department.')
await this.shareFilesWithDepartment()
}
if (global.gc) {
global.gc();
}
}, 10000); // 10-second interval for testing
}
if (global.gc) {
global.gc()
}
}, 10000) // 10-second interval for testing
}
// Share files with users in the same department
private async shareFilesWithDepartment(): Promise<void> {
try {
// Get the current user's department information
const userInfo = await this.userConfig.readValue('user_info');
if (!userInfo || !userInfo.departmentId || !userInfo.name) {
throw new Error('User information or department ID is missing in the configuration.');
}
// Share files with users in the same department
private async shareFilesWithDepartment(): Promise<void> {
try {
const data = await this.db.read()
const userInfo = data.app_config.user_info
const departmentStructure = data.local_resources.directory_schemes.department
const departmentId = userInfo.departmentId;
const userName = userInfo.name;
const departmentId = userInfo.departmentId
const userName = userInfo.name
const activeUsers = data.network.usersInLan
this.departmentDirectory = departmentStructure.path
// Get the list of active users from applicationInfo
const activeUsersId = await this.applicationInfo.readValue('active_users_info');
if (!activeUsersId) {
throw new Error('No active users found.');
}
// Filter users who belong to the same department
const departmentUsers = activeUsers.filter(
(user: any) => user.user_info.departmentId === departmentId,
)
if (departmentUsers.length === 0) {
throw new Error('No users found in the same department.')
}
const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId);
if (!activeUsers || activeUsers.length === 0) {
throw new Error('No active users found.');
}
// Iterate over all department users and perform the operations
for (const user of departmentUsers) {
const userIp = user.ip
this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort)
// Filter users who belong to the same department
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId);
if (departmentUsers.length === 0) {
throw new Error('No users found in the same department.');
}
if (!(await this.tcpCommunicator.connect())) continue
// Get department directory info
const departmentData = await this.applicationInfo.readValue('departmentDirectory');
if (!departmentData || !departmentData.path || !departmentData.id) {
throw new Error('No department directory found.');
}
this.departmentDirectory = departmentData.path;
// Read files from the MemoryManager related to this department
const departmentFiles = await this.memoryManager.retrieveMetaInformation(departmentData.id);
if (!departmentFiles || !departmentFiles.structure) {
throw new Error('No files found for this department in the memory manager.');
}
// Iterate over all department users and perform the operations
for (const user of departmentUsers) {
const userIp = user.ip;
this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort);
if (!await this.tcpCommunicator.connect()) continue;
// First clear the department directory;
if (await this.clearDepartmentDirectory(userName)) {
await this.sendFilesToUser(departmentFiles.structure, userName);
}
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
private async clearDepartmentDirectory(userName: string): Promise<boolean> {
if(!this.tcpCommunicator) return false;
if (!await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, {userName: userName})) return false;
const response = await this.waitForResponse();
if (!response || response.operationCode !== operationCodes.OK){
this.log('Failed to clear the department directory.', 'error');
return false;
// First clear the department directory;
if (await this.clearDepartmentDirectory(userName)) {
await this.sendFilesToUser(departmentStructure.structure, userName)
}
return true;
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
private async clearDepartmentDirectory(userName: string): Promise<boolean> {
if (!this.tcpCommunicator) return false
if (
!(await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, {
userName: userName,
}))
)
return false
const response = await this.waitForResponse()
if (!response || response.operationCode !== operationCodes.OK) {
this.log('Failed to clear the department directory.', 'error')
return false
}
// Send the files to a user in the department
private async sendFilesToUser(files: { [key: string]: string }, userName: string): Promise<void> {
if(!this.tcpCommunicator) return;
const unsentFiles = Object.keys(files);
return true
}
console.log(`\n\n${unsentFiles}\n\n`);
// Send the files to a user in the department
private async sendFilesToUser(files: { [key: string]: string }, userName: string): Promise<void> {
if (!this.tcpCommunicator) return
const unsentFiles = Object.keys(files)
for (const fileName of unsentFiles) {
const filePath = files[fileName];
console.log(`\n\n${unsentFiles}\n\n`)
// Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error');
continue;
}
for (const fileName of unsentFiles) {
const filePath = files[fileName]
// Read the file content
const fileContent = fs.readFileSync(filePath);
// Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error')
continue
}
// Get the relative path of the file (used in the meta info)
if (!this.departmentDirectory) return;
const relativeFilePath = path.relative(this.departmentDirectory, filePath);
// Read the file content
const fileContent = fs.readFileSync(filePath)
// Prepare the metaInfo (same structure as FileSharer)
const metaInfo = {
userName,
relativeFilePath
};
// Get the relative path of the file (used in the meta info)
if (!this.departmentDirectory) return
const relativeFilePath = path.relative(this.departmentDirectory, filePath)
// Send the file
if (!await this.tcpCommunicator.sendMessage(operationCodes.DEPARTMENT_FILE, metaInfo, Buffer.from(fileContent))) return;
// Prepare the metaInfo (same structure as FileSharer)
const metaInfo = {
userName,
relativeFilePath,
}
console.log(`\n\nSending file: ${fileName} to ${userName}\n\n`);
// Send the file
if (
!(await this.tcpCommunicator.sendMessage(
operationCodes.DEPARTMENT_FILE,
metaInfo,
Buffer.from(fileContent),
))
)
return
const response = await this.waitForResponse();
if (!response || response.operationCode !== operationCodes.OK) {
this.log(`Failed to send file: ${fileName}`, 'error');
return;
}
console.log(`\n\nSending file: ${fileName} to ${userName}\n\n`)
this.log(`File sent successfully: ${fileName} to ${userName}`);
const response = await this.waitForResponse()
if (!response || response.operationCode !== operationCodes.OK) {
this.log(`Failed to send file: ${fileName}`, 'error')
return
}
unsentFiles.splice(unsentFiles.indexOf(fileName), 1);
await this.tcpCommunicator.disconnect();
}
}
async stop(): Promise<void> {
this.stopRequested = true; // Signal that stop is requested
this.log(`File sent successfully: ${fileName} to ${userName}`)
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
unsentFiles.splice(unsentFiles.indexOf(fileName), 1)
await this.tcpCommunicator.disconnect()
}
}
// Wait for any ongoing process to complete if busy
while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
async stop(): Promise<void> {
this.stopRequested = true // Signal that stop is requested
console.log("[BackupManager] Stopped successfully.");
if (this.intervalId) {
clearInterval(this.intervalId)
this.intervalId = null
}
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);
});
// Wait for any ongoing process to complete if busy
while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100))
}
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[DepartmentSharer]';
if (level === 'error') {
console.error(`${prefix} ${message}`);
} else {
console.log(`${prefix} ${message}`);
console.log('[BackupManager] Stopped successfully.')
}
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)
})
}
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[DepartmentSharer]'
if (level === 'error') {
console.error(`${prefix} ${message}`)
} else {
console.log(`${prefix} ${message}`)
}
}
}
+112 -141
View File
@@ -1,163 +1,134 @@
import { promises as fs, watch, FSWatcher } from 'fs';
import path from 'path';
import { MemoryManager } from './memory_manager';
import { JsonManager } from './json_manager';
import { promises as fs, watch, FSWatcher } from 'fs'
import path from 'path'
import { JsonDatabase } from '../database/database'
import { DatabaseScheme } from '../database/schemes/database_scheme'
export class DirectoryWatcher {
private directoryPath: string;
private directoryMemoryId: string;
private directoryScheme: any;
private applicationInfo: JsonManager;
private memoryManager: MemoryManager;
private readonly sourceKey: string;
private directoryWatcher: FSWatcher | null;
private totalSize: number;
private isBusy: boolean;
private readonly watchers: Map<string, FSWatcher> // Stores watchers with directory ID as the key
private readonly db: JsonDatabase<DatabaseScheme, any>
private totalSizes: Map<string, number> // Stores total sizes per directory ID
constructor(memoryManagerPath: string, applicationInfoPath: string, sourceKey: string) {
this.sourceKey = sourceKey;
this.applicationInfo = new JsonManager(applicationInfoPath);
this.memoryManager = new MemoryManager(memoryManagerPath);
this.directoryMemoryId = '';
this.directoryPath = '';
this.directoryWatcher = null;
this.totalSize = 0;
this.isBusy = false;
constructor(pathToDatabaseFile: string) {
this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
this.watchers = new Map<string, FSWatcher>()
this.totalSizes = new Map<string, number>()
}
// Start the watcher and register all directories
async start(): Promise<void> {
const dbData = await this.db.read()
const directorySchemes = dbData.local_resources?.directory_schemes
if (!directorySchemes) {
this.log('Error: directory_schemes not found in database.', 'error')
return
}
// Start the watcher with a busy flag to prevent overlapping operations
async start(): Promise<void> {
setInterval(async () => {
if (!this.isBusy) {
this.isBusy = true;
const initialized = await this.initialize();
if (initialized) this.log('Directory watcher started successfully.');
this.isBusy = false;
}
}, 10000); // 10-second interval for testing
// Initialize watchers for each directory
this.registerWatcher(directorySchemes.backup?.id, directorySchemes.backup?.path)
this.registerWatcher(directorySchemes.department?.id, directorySchemes.department?.path)
this.registerWatcher(directorySchemes.shared?.id, directorySchemes.shared?.path)
this.log('Directory watcher started successfully.')
}
// Register a watcher for a directory with a given ID
private registerWatcher(id: string | undefined, directoryPath: string | undefined): void {
if (!id || !directoryPath) {
this.log(`Skipping watcher: ID or path is missing.`, 'error')
return
}
// Method to initialize and validate the backup directory
async initialize(): Promise<boolean> {
const directoryData = await this.applicationInfo.readValue(this.sourceKey);
if (!directoryData) {
this.log('Directory data not found in application info.', 'error');
return false;
}
this.directoryPath = directoryData.path;
this.directoryMemoryId = directoryData.id;
if (!this.directoryMemoryId || !this.directoryPath) {
this.log('Components of entry in \'DirectoryWatcher\' not found.', 'error');
await this.applicationInfo.removeValue(this.sourceKey);
return false;
}
if (!this.directoryScheme || Object.keys(this.directoryScheme).length === 0) {
// No structure in memory, scan and save it
const result = await this.buildDirectoryScheme(this.directoryPath);
this.directoryScheme = result.structure;
this.totalSize = result.size;
await this.memoryManager.updateMetaInformation(this.directoryMemoryId, {
structure: this.directoryScheme,
totalSize: this.totalSize,
});
}
// Start watching the directory (after stopping any existing watcher)
this.restartWatcher();
return true;
if (this.watchers.has(id)) {
this.log(`Watcher for ID ${id} is already running.`, 'error')
return
}
// Recursively build the directory structure and calculate the total size
private async buildDirectoryScheme(dirPath: string): Promise<{ structure: any, size: number }> {
const directoryScheme: any = {};
let totalSize = 0;
// Create and store a new watcher
const watcher = watch(directoryPath, { recursive: true }, async (eventType, filename) => {
if (filename) {
this.log(`File change detected in ${directoryPath}: ${eventType} - ${filename}`)
await this.handleDirectoryChange(id, directoryPath)
}
})
const items = await fs.readdir(dirPath, { withFileTypes: true });
this.watchers.set(id, watcher)
this.log(`Watching directory: ${directoryPath} (ID: ${id})`)
}
for (const item of items) {
const fullPath = path.join(dirPath, item.name);
const stats = await fs.stat(fullPath);
// Handle directory change and update the correct entry in the database
private async handleDirectoryChange(id: string, directoryPath: string): Promise<void> {
const result = await this.buildDirectoryScheme(directoryPath)
this.totalSizes.set(id, result.size)
if (item.isDirectory()) {
// If it's a directory, recursively build its structure and accumulate size
const { structure, size } = await this.buildDirectoryScheme(fullPath);
directoryScheme[item.name] = structure;
totalSize += size;
} else if (item.isFile()) {
// If it's a file, store its full path and accumulate size
directoryScheme[item.name] = fullPath;
totalSize += stats.size;
}
}
await this.db.update((data) => {
// @ts-ignore
if (!data.local_resources || !data.local_resources.directory_schemes[id]) {
this.log(`Error: Directory scheme for ID ${id} not found in database.`, 'error')
return data
}
return { structure: directoryScheme, size: totalSize };
// @ts-ignore
data.local_resources.directory_schemes[id].structure = result.structure
// @ts-ignore
data.local_resources.directory_schemes[id].totalSize = result.size
return data
})
this.log(`Updated directory scheme for ID: ${id}`)
}
// Recursively build the directory structure and calculate total size
private async buildDirectoryScheme(dirPath: string): Promise<{ structure: any; size: number }> {
const directoryScheme: any = {}
let totalSize = 0
const items = await fs.readdir(dirPath, { withFileTypes: true })
for (const item of items) {
const fullPath = path.join(dirPath, item.name)
const stats = await fs.stat(fullPath)
if (item.isDirectory()) {
const { structure, size } = await this.buildDirectoryScheme(fullPath)
directoryScheme[item.name] = structure
totalSize += size
} else if (item.isFile()) {
directoryScheme[item.name] = fullPath
totalSize += stats.size
}
}
// Restart the directory watcher, ensuring any previous watcher is closed
private restartWatcher(): void {
if (this.directoryWatcher) {
this.log('Stopping existing watcher...');
this.directoryWatcher.close();
}
return { structure: directoryScheme, size: totalSize }
}
this.startDirectoryWatcher();
// Stop and remove a watcher for a specific directory ID
public stopWatcher(id: string): void {
const watcher = this.watchers.get(id)
if (watcher) {
watcher.close()
this.watchers.delete(id)
this.log(`Stopped watcher for ID: ${id}`)
}
}
// Start watching the backup directory for changes
private startDirectoryWatcher(): void {
if (!this.directoryPath) {
throw new Error('Backup directory not set. Cannot start watcher.');
}
this.directoryWatcher = watch(this.directoryPath, { recursive: true }, async (eventType, filename) => {
if (filename) {
this.log(`File change detected: ${eventType} - ${filename}`);
// Rebuild the directory scheme and update memory
const result = await this.buildDirectoryScheme(this.directoryPath);
this.directoryScheme = result.structure;
this.totalSize = result.size;
await this.memoryManager.updateMetaInformation(this.directoryMemoryId, {
structure: this.directoryScheme,
totalSize: this.totalSize,
});
this.log('Directory structure and size updated in memory.');
}
});
this.log(`Watching for changes in: ${this.directoryPath}`);
// Stop all watchers
public stopAllWatchers(): void {
for (const [id, watcher] of this.watchers) {
watcher.close()
this.log(`Stopped watcher for ID: ${id}`)
}
this.watchers.clear()
}
// Close the directory watcher
public closeWatcher(): void {
if (this.directoryWatcher) {
this.log(`Stopping watcher for ${this.directoryPath}`);
this.directoryWatcher.close();
this.directoryWatcher = null;
}
if (global.gc) {
global.gc();
}
}
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const sourcePrefix = `[DirectoryWatcher] {${this.capitalize(this.sourceKey)}}`;
if (level === 'error') {
console.error(`${sourcePrefix} ${message}`);
} else {
console.log(`${sourcePrefix} ${message}`);
}
}
// Capitalize the first letter of the sourceKey
private capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const sourcePrefix = `[DirectoryWatcher]`
if (level === 'error') {
console.error(`${sourcePrefix} ${message}`)
} else {
console.log(`${sourcePrefix} ${message}`)
}
}
}
+45 -45
View File
@@ -1,55 +1,55 @@
import fs from 'fs';
import crypto from 'crypto';
import fs from 'fs'
import crypto from 'crypto'
export class FileEncryptor {
private readonly encryptionKey: Buffer;
private readonly iv: Buffer;
private readonly encryptionKey: Buffer
private readonly iv: Buffer
constructor(base64Key: string, base64Iv: string) {
// Decode the base64-encoded key and IV
this.encryptionKey = Buffer.from(base64Key, 'base64');
this.iv = Buffer.from(base64Iv, 'base64');
constructor(base64Key: string, base64Iv: string) {
// Decode the base64-encoded key and IV
this.encryptionKey = Buffer.from(base64Key, 'base64')
this.iv = Buffer.from(base64Iv, 'base64')
}
// Method to read a file, encrypt it, and return the encrypted content as a base64 string
public encryptFileToBase64(filePath: string): string {
try {
// Read the file contents
const fileBuffer = fs.readFileSync(filePath)
// Create the cipher using AES-256-CBC (or another algorithm you prefer)
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, this.iv)
// Encrypt the file data
let encryptedData = cipher.update(fileBuffer)
encryptedData = Buffer.concat([encryptedData, cipher.final()])
// Return the encrypted data as a base64 string
return encryptedData.toString('base64')
} catch (err) {
console.error(`Error encrypting file at path ${filePath}:`, err)
throw err
}
}
// Method to read a file, encrypt it, and return the encrypted content as a base64 string
public encryptFileToBase64(filePath: string): string {
try {
// Read the file contents
const fileBuffer = fs.readFileSync(filePath);
// Method to decrypt base64-encoded encrypted content and return the decrypted buffer
public decryptBase64(encryptedBase64: string): Buffer {
try {
// Decode the base64-encoded encrypted data
const encryptedData = Buffer.from(encryptedBase64, 'base64')
// Create the cipher using AES-256-CBC (or another algorithm you prefer)
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, this.iv);
// Create the decipher using AES-256-CBC
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv)
// Encrypt the file data
let encryptedData = cipher.update(fileBuffer);
encryptedData = Buffer.concat([encryptedData, cipher.final()]);
// Decrypt the data
let decryptedData = decipher.update(encryptedData)
decryptedData = Buffer.concat([decryptedData, decipher.final()])
// Return the encrypted data as a base64 string
return encryptedData.toString('base64');
} catch (err) {
console.error(`Error encrypting file at path ${filePath}:`, err);
throw err;
}
}
// Method to decrypt base64-encoded encrypted content and return the decrypted buffer
public decryptBase64(encryptedBase64: string): Buffer {
try {
// Decode the base64-encoded encrypted data
const encryptedData = Buffer.from(encryptedBase64, 'base64');
// Create the decipher using AES-256-CBC
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv);
// Decrypt the data
let decryptedData = decipher.update(encryptedData);
decryptedData = Buffer.concat([decryptedData, decipher.final()]);
// Return the decrypted buffer
return decryptedData;
} catch (err) {
console.error('Error decrypting data:', err);
throw err;
}
// Return the decrypted buffer
return decryptedData
} catch (err) {
console.error('Error decrypting data:', err)
throw err
}
}
}
+132 -132
View File
@@ -1,146 +1,146 @@
import fs from "fs";
import path from "path";
import { QueueManager } from './queue_manager';
import { TcpCommunicator } from "./tcp_communicator";
import { operationCodes } from '../network/operation_codes';
import { compareFnFileItemTask, FileItemTask } from "../interfaces/file_item_task";
import { ParsedMessage } from "../network/message_handler";
import fs from 'fs'
import path from 'path'
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'
interface FileSendTask {
ip: string;
path: string;
userName: string;
ip: string
path: string
userName: string
}
export class FileSharer {
private queueManager: QueueManager<FileSendTask>;
private readonly clientPort: number;
private isBusy: boolean;
private tcpCommunicator: TcpCommunicator | null = null;
private stopRequested: boolean = false;
private intervalId: NodeJS.Timeout | null = null;
private readonly db: JsonDatabase<DatabaseScheme, any>
private readonly clientPort: number
private isBusy: boolean = false
private tcpCommunicator: TcpCommunicator | null = null
private stopRequested: boolean = false
private intervalId: NodeJS.Timeout | null = null
constructor(queueFilePath: string, clientPort: number) {
this.queueManager = new QueueManager<FileItemTask>(queueFilePath, compareFnFileItemTask);
this.clientPort = clientPort;
this.isBusy = false; // Initialize the busy flag
}
constructor(pathToDatabaseFile: string, clientPort: number) {
this.db = new JsonDatabase(pathToDatabaseFile)
this.clientPort = clientPort
}
// Start processing the file queue
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) { // Check if the queue is already being processed
this.isBusy = true; // Set busy flag to true before starting
this.log("Start successfully. Processing the queue.");
await this.processQueue();
}
// Start processing the file queue
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) {
// Check if the queue is already being processed
this.isBusy = true // Set busy flag to true before starting
this.log('Start successfully. Processing the queue.')
await this.processQueue()
}
if (global.gc) {
global.gc();
}
}, 10000); // 10 seconds interval
}
if (global.gc) {
global.gc()
}
}, 10000)
}
// Method to process the queue
private async processQueue(): Promise<void> {
while (!this.queueManager.isEmpty()) {
const task = this.queueManager.peek();
this.log('trimiti fisier');
// Method to process the queue
private async processQueue(): Promise<void> {
for (let i = 0; i < this.db.queueSize(); i++) {
const task = this.db.popQueue()
if (task) {
this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`)
const success = await this.sendFile(task)
if (task) {
this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`);
const success = await this.sendFile(task);
if (!success) {
this.log(`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`, 'error');
this.queueManager.dequeue();
this.queueManager.enqueue(task); // Re-add to queue if failed
} else {
this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`);
this.queueManager.dequeue();
}
}
}
this.isBusy = false; // Reset busy flag after the queue is processed
}
// Method to send the file to a specific IP using TcpCommunicator
private async sendFile(task: FileSendTask): Promise<boolean> {
const { ip, path: filePath, userName } = task;
// Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error');
return false;
}
// Read the file contents
const fileContent = fs.readFileSync(filePath);
// Extract the file name from the file path using path.basename
const fileName = path.basename(filePath);
const metaInfo = {
userName, // Sender's username
relativeFilePath: fileName, // Use the file name instead of the full path
};
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
if (!await this.tcpCommunicator.connect()) {
this.log(`Failed to connect to IP: ${ip}`, 'error');
return false;
}
this.log(`Sending file: ${filePath} to IP: ${ip}`);
if (!await this.tcpCommunicator.sendMessage(operationCodes.SHARE_FILE, metaInfo, fileContent)) return false;
const response = await this.waitForResponse();
if (!response || response.operationCode !== operationCodes.OK) {
this.log(`Failed to send file: ${filePath} to IP: ${ip}`, 'error');
return false;
}
await this.tcpCommunicator.disconnect();
return true;
}
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.");
}
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()); // Resolve the response or null if not available
}
}, 100); // Check every 100 milliseconds if the response has arrived
});
}
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[FileSharer]';
if (level === 'error') {
console.error(`${prefix} ${message}`);
if (!success) {
this.log(
`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`,
'error',
)
this.db.pushQueue(task)
} else {
console.log(`${prefix} ${message}`);
this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`)
}
}
}
this.isBusy = false // Reset busy flag after the queue is processed
}
// Method to send the file to a specific IP using TcpCommunicator
private async sendFile(task: FileSendTask): Promise<boolean> {
const { ip, path: filePath, userName } = task
// Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error')
return false
}
// Read the file contents
const fileContent = fs.readFileSync(filePath)
// Extract the file name from the file path using path.basename
const fileName = path.basename(filePath)
const metaInfo = {
userName, // Sender's username
relativeFilePath: fileName, // Use the file name instead of the full path
}
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
if (!(await this.tcpCommunicator.connect())) {
this.log(`Failed to connect to IP: ${ip}`, 'error')
return false
}
this.log(`Sending file: ${filePath} to IP: ${ip}`)
if (!(await this.tcpCommunicator.sendMessage(operationCodes.SHARE_FILE, metaInfo, fileContent)))
return false
const response = await this.waitForResponse()
if (!response || response.operationCode !== operationCodes.OK) {
this.log(`Failed to send file: ${filePath} to IP: ${ip}`, 'error')
return false
}
await this.tcpCommunicator.disconnect()
return true
}
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.')
}
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()) // Resolve the response or null if not available
}
}, 100) // Check every 100 milliseconds if the response has arrived
})
}
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[FileSharer]'
if (level === 'error') {
console.error(`${prefix} ${message}`)
} else {
console.log(`${prefix} ${message}`)
}
}
}
+96 -96
View File
@@ -1,119 +1,119 @@
import fs from 'fs';
import path from 'path';
import fs from 'fs'
import path from 'path'
export class JsonManager {
private readonly filePath: string;
private readonly lockFilePath: string;
private readonly filePath: string
private readonly lockFilePath: string
constructor(filePath: string) {
const dir = path.dirname(filePath);
constructor(filePath: string) {
const dir = path.dirname(filePath)
// Check if the directory exists, throw error if it doesn't
if (!fs.existsSync(dir)) {
throw new Error(`The directory does not exist: ${dir}`);
}
this.filePath = filePath;
this.lockFilePath = `${filePath}.lock`; // Define the lock file path
// If the file doesn't exist, create it
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8');
}
// Check if the directory exists, throw error if it doesn't
if (!fs.existsSync(dir)) {
throw new Error(`The directory does not exist: ${dir}`)
}
// Method to acquire a lock (create .lock file)
private async acquireLock(): Promise<void> {
while (fs.existsSync(this.lockFilePath)) {
// Wait until the lock file is released
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms delay before retrying
}
// Create the lock file
fs.writeFileSync(this.lockFilePath, '');
this.filePath = filePath
this.lockFilePath = `${filePath}.lock` // Define the lock file path
// If the file doesn't exist, create it
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8')
}
}
// Method to release the lock (delete .lock file)
private releaseLock(): void {
if (fs.existsSync(this.lockFilePath)) {
fs.unlinkSync(this.lockFilePath);
}
// Method to acquire a lock (create .lock file)
private async acquireLock(): Promise<void> {
while (fs.existsSync(this.lockFilePath)) {
// Wait until the lock file is released
await new Promise((resolve) => setTimeout(resolve, 100)) // 100ms delay before retrying
}
// Create the lock file
fs.writeFileSync(this.lockFilePath, '')
}
// Read a value by key from the JSON file with a lock
public async readValue(key: string): Promise<any | null> {
await this.acquireLock(); // Acquire the lock
try {
if (!fs.existsSync(this.filePath)) return null;
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
return data[key] !== undefined ? data[key] : null;
} catch (err: any) {
console.error(`Error reading from JSON file: ${err.message}`);
return null;
} finally {
this.releaseLock(); // Always release the lock after the operation
}
// Method to release the lock (delete .lock file)
private releaseLock(): void {
if (fs.existsSync(this.lockFilePath)) {
fs.unlinkSync(this.lockFilePath)
}
}
// Write a key-value pair to the JSON file with a lock
public async writeValue(key: string, value: any): Promise<boolean> {
await this.acquireLock(); // Acquire the lock
// Read a value by key from the JSON file with a lock
public async readValue(key: string): Promise<any | null> {
await this.acquireLock() // Acquire the lock
try {
let data: { [key: string]: any } = {};
try {
if (!fs.existsSync(this.filePath)) return null
if (fs.existsSync(this.filePath)) {
data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
}
// Update the key with the new value
data[key] = value;
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8');
return true;
} catch (err: any) {
console.error(`Error writing to JSON file: ${err.message}`);
return false;
} finally {
this.releaseLock(); // Always release the lock after the operation
}
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'))
return data[key] !== undefined ? data[key] : null
} catch (err: any) {
console.error(`Error reading from JSON file: ${err.message}`)
return null
} finally {
this.releaseLock() // Always release the lock after the operation
}
}
// Remove a key-value pair from the JSON file with a lock
public async removeValue(key: string): Promise<boolean> {
await this.acquireLock(); // Acquire the lock
// Write a key-value pair to the JSON file with a lock
public async writeValue(key: string, value: any): Promise<boolean> {
await this.acquireLock() // Acquire the lock
try {
if (!fs.existsSync(this.filePath)) return false;
try {
let data: { [key: string]: any } = {}
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
if (data[key] !== undefined) {
delete data[key];
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8');
return true;
}
return false;
} catch (err: any) {
console.error(`Error removing key from JSON file: ${err.message}`);
return false;
} finally {
this.releaseLock(); // Always release the lock after the operation
}
if (fs.existsSync(this.filePath)) {
data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'))
}
// Update the key with the new value
data[key] = value
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8')
return true
} catch (err: any) {
console.error(`Error writing to JSON file: ${err.message}`)
return false
} finally {
this.releaseLock() // Always release the lock after the operation
}
}
// Reset the JSON file by clearing all data with a lock
public async resetFile(): Promise<boolean> {
await this.acquireLock(); // Acquire the lock
// Remove a key-value pair from the JSON file with a lock
public async removeValue(key: string): Promise<boolean> {
await this.acquireLock() // Acquire the lock
try {
fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8');
return true;
} catch (err: any) {
console.error(`Error resetting JSON file: ${err.message}`);
return false;
} finally {
this.releaseLock(); // Always release the lock after the operation
}
try {
if (!fs.existsSync(this.filePath)) return false
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'))
if (data[key] !== undefined) {
delete data[key]
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8')
return true
}
return false
} catch (err: any) {
console.error(`Error removing key from JSON file: ${err.message}`)
return false
} finally {
this.releaseLock() // Always release the lock after the operation
}
}
// Reset the JSON file by clearing all data with a lock
public async resetFile(): Promise<boolean> {
await this.acquireLock() // Acquire the lock
try {
fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8')
return true
} catch (err: any) {
console.error(`Error resetting JSON file: ${err.message}`)
return false
} finally {
this.releaseLock() // Always release the lock after the operation
}
}
}
-47
View File
@@ -1,47 +0,0 @@
import { v4 as uuidv4 } from 'uuid';
import { JsonManager } from './json_manager';
export class MemoryManager extends JsonManager {
constructor(filePath: string) {
super(filePath); // Call the parent constructor to ensure file initialization
}
// Generate a new unique GUID and ensure it doesn't already exist in the file
private generateUniqueGuid(): Promise<string> {
const generate = async (): Promise<string> => {
const guid = uuidv4();
const value = await this.readValue(guid);
if (value === null) {
return guid;
}
return generate();
};
return generate();
}
// Store meta information with a unique GUID as the key
public async storeMetaInformation(metaInfo: any): Promise<string> {
const guid = await this.generateUniqueGuid();
const success = await this.writeValue(guid, metaInfo);
if (success) {
return guid; // Return the unique GUID for future reference
} else {
throw new Error('Failed to store meta information.');
}
}
// Retrieve meta information using the GUID
public retrieveMetaInformation(guid: string): Promise<any | null> {
return this.readValue(guid);
}
// Update meta information by merging new data into existing data
public async updateMetaInformation(guid: string, newMetaInfo: any): Promise<boolean> {
return await this.writeValue(guid, newMetaInfo);
}
// Remove meta information using the GUID
public removeMetaInformation(guid: string): Promise<boolean> {
return this.removeValue(guid);
}
}
+198 -167
View File
@@ -1,185 +1,216 @@
import {JsonManager} from "./json_manager";
import {UdpClient} from "../network/udp/udp_client";
import {TcpCommunicator} from "./tcp_communicator";
import {operationCodes} from "../network/operation_codes";
import {ParsedMessage} from "../network/message_handler";
import { UdpClient } from '../network/udp/udp_client'
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'
export class NetworkScanner {
private applicationInfo: JsonManager;
private userConfig: JsonManager;
private readonly udpPort: number;
private readonly tcpPort: number;
private readonly okPage: string;
private readonly errorPage: string;
private readonly databaseResetPage: string;
private appStarted = false;
private intervalIds: NodeJS.Timeout[] = [];
private db: JsonDatabase<DatabaseScheme, any>
private readonly udpPort: number
private readonly tcpPort: number
private readonly okPage: string
private readonly errorPage: string
private readonly databaseResetPage: string
private intervalIds: NodeJS.Timeout[] = []
// Flags to prevent overlapping executions
private ucCheckBusy = false;
private ipLookupBusy = false;
private sendLoginBusy = false;
// Flags to prevent overlapping executions
private ucCheckBusy = false
private ipLookupBusy = false
private sendLoginBusy = false
constructor(applicationInfoPath: string, userConfigPath: string, udpPort: number, tcpPort: number, okPage: string, errorPage: string, databaseResetPage: string) {
this.applicationInfo = new JsonManager(applicationInfoPath);
this.userConfig = new JsonManager(userConfigPath);
this.udpPort = udpPort;
this.tcpPort = tcpPort;
this.okPage = okPage;
this.errorPage = errorPage;
this.databaseResetPage = databaseResetPage;
constructor(
pathToDatabaseFile: string,
udpPort: number,
tcpPort: number,
okPage: string,
errorPage: string,
databaseResetPage: string,
) {
this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
this.udpPort = udpPort
this.tcpPort = tcpPort
this.okPage = okPage
this.errorPage = errorPage
this.databaseResetPage = databaseResetPage
// Start tasks
this.startUCCheck();
this.startUserIPLookup();
this.sendLoginRequest();
// Start tasks
this.startUCCheck()
this.startUserIPLookup()
this.sendAccountCheckRequest()
}
// Log helper function for consistent logging format
private log(message: string, level: 'log' | 'error' = 'log', methodName: string = ''): void {
const prefix = `[NetworkScanner${methodName ? `.${methodName}` : ''}]`
if (level === 'error') {
console.error(`${prefix} ${message}`)
} else {
console.log(`${prefix} ${message}`)
}
}
// Log helper function for consistent logging format
private log(message: string, level: 'log' | 'error' = 'log', methodName: string = ''): void {
const prefix = `[NetworkScanner${methodName ? `.${methodName}` : ''}]`;
if (level === 'error') {
console.error(`${prefix} ${message}`);
// UC Check Task
private startUCCheck(interval: number = 5000): void {
const intervalId = setInterval(async () => {
if (this.ucCheckBusy) return
this.ucCheckBusy = true
try {
this.log('UC Check running...', 'log', 'startUCCheck')
const udpClient = new UdpClient(this.udpPort)
const aliveClients = await udpClient.getTargetClients(operationCodes.ARE_YOU_UC)
const data = await this.db.read()
const foundClient = aliveClients.length > 0
const serverFound = data.app_config.server_found
if (foundClient) {
const ipAddress = aliveClients[0]
if (data.network.serverIp !== ipAddress) {
await this.db.update((data) => {
data.network.serverIp = ipAddress
return data
})
process.send?.({ type: 'changeContent', page: this.okPage })
}
if (!serverFound) {
process.send?.({ type: 'changeContent', page: this.okPage })
await this.db.update((data) => {
data.app_config.server_found = true
return data
})
}
} else {
console.log(`${prefix} ${message}`);
process.send?.({ type: 'changeContent', page: this.errorPage })
await this.db.update((data) => {
data.app_config.server_found = false
return data
})
}
}
} catch (err) {
this.log(`Error checking UC: ${err}`, 'error', 'startUCCheck')
process.send?.({ type: 'changeContent', page: this.errorPage })
} finally {
this.ucCheckBusy = false
}
}, interval)
// UC Check Task
private startUCCheck(interval: number = 5000): void {
const intervalId = setInterval(async () => {
if (this.ucCheckBusy) return;
this.ucCheckBusy = true;
this.intervalIds.push(intervalId)
}
try {
this.log('UC Check running...', 'log', 'startUCCheck');
const udpClient = new UdpClient(this.udpPort);
const aliveClients = await udpClient.getTargetClients(operationCodes.ARE_YOU_UC);
const storedIp = await this.applicationInfo.readValue('serverIp');
const foundClient = aliveClients.length > 0;
// IP Lookup Task
private startUserIPLookup(interval: number = 10000): void {
const intervalId = setInterval(async () => {
if (this.ipLookupBusy) return
this.ipLookupBusy = true
if (foundClient) {
const ipAddress = aliveClients[0]; // Use the first alive client
try {
this.log('IP Lookup running...', 'log', 'startUserIPLookup')
const data = await this.db.read()
const serverIp = data.network.serverIp
const udpClient = new UdpClient(this.udpPort)
const activeIPs = await udpClient.getTargetClients(operationCodes.ARE_YOU_HUMAN)
const filteredIPs = activeIPs.filter((ip) => ip !== serverIp)
if (!storedIp || storedIp !== ipAddress) {
await this.applicationInfo.writeValue('serverIp', ipAddress);
if (!this.appStarted) {
process.send?.({ type: 'changeContent', page: this.okPage });
}
this.appStarted = true;
} else if (!this.appStarted) {
process.send?.({ type: 'changeContent', page: this.okPage });
this.appStarted = true;
}
} else {
process.send?.({ type: 'changeContent', page: this.errorPage });
}
} catch (err) {
this.log(`Error checking UC: ${err}`, 'error', 'startUCCheck');
process.send?.({ type: 'changeContent', page: this.errorPage });
} finally {
this.ucCheckBusy = false;
}
}, interval);
// Save the filtered IPs to 'users_ip'
await this.db.update((data) => {
data.network.usersInLan = filteredIPs.map((ip) => ({
id: '',
ip,
name: '',
departmentId: '',
}))
return data
})
} catch (err) {
this.log(`Error during user IP lookup: ${err}`, 'error', 'startUserIPLookup')
} finally {
this.ipLookupBusy = false
}
}, interval)
this.intervalIds.push(intervalId);
}
this.intervalIds.push(intervalId)
}
// IP Lookup Task
private startUserIPLookup(interval: number = 10000): void {
const intervalId = setInterval(async () => {
if (this.ipLookupBusy) return;
this.ipLookupBusy = true;
// Login Request Task
private sendAccountCheckRequest(interval: number = 5000): void {
const intervalId = setInterval(async () => {
if (this.sendLoginBusy) return
this.sendLoginBusy = true
try {
this.log('IP Lookup running...', 'log', 'startUserIPLookup');
const serverIp = await this.applicationInfo.readValue('serverIp');
const udpClient = new UdpClient(this.udpPort);
const activeIPs = await udpClient.getTargetClients(operationCodes.ARE_YOU_HUMAN);
const filteredIPs = activeIPs.filter(ip => ip !== serverIp);
// Save the filtered IPs to 'users_ip'
await this.applicationInfo.writeValue('users_ip', filteredIPs);
} catch (err) {
this.log(`Error during user IP lookup: ${err}`, 'error', 'startUserIPLookup');
} finally {
this.ipLookupBusy = false;
}
}, interval);
this.intervalIds.push(intervalId);
}
// Login Request Task
private sendLoginRequest(interval: number = 5000): void {
const intervalId = setInterval(async () => {
if (this.sendLoginBusy || this.appStarted) return;
this.sendLoginBusy = true;
try {
const userInfo = await this.userConfig.readValue('user_info');
if (!userInfo || !userInfo.email || !userInfo.password) {
this.log("Email or password not found in user config.", 'error', 'sendLoginRequest');
return;
}
const app_type = await this.userConfig.readValue('app_type');
const email = userInfo.email;
const password = userInfo.password;
const serverIp = await this.applicationInfo.readValue('serverIp');
if (!serverIp) {
this.log("Server IP not found in application info.", 'error', 'sendLoginRequest');
return;
}
const tcpCommunicator = new TcpCommunicator(serverIp, this.tcpPort);
if (!await tcpCommunicator.connect()) {
this.log("Failed to connect to the server.", 'error', 'sendLoginRequest');
return;
}
const metaInfo = { email, password, app_type };
if (!await tcpCommunicator.sendMessage(operationCodes.LOGIN, metaInfo)) {
this.log("Failed to send login request.", 'error', 'sendLoginRequest');
await tcpCommunicator.disconnect();
return;
}
const response = await this.waitForResponse(tcpCommunicator);
if (response?.operationCode !== operationCodes.OK) {
await this.userConfig.resetFile();
await this.userConfig.writeValue('app_type', app_type);
process.send?.({ type: 'changeContent', page: this.databaseResetPage });
}
} catch (err) {
this.log(`Error during login request: ${err}`, 'error', 'sendLoginRequest');
} finally {
this.sendLoginBusy = false;
}
}, interval);
this.intervalIds.push(intervalId);
}
// Helper function to wait for a response from the TCP communicator
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (tcpCommunicator.hasResponseArrived()) {
clearInterval(checkInterval);
resolve(tcpCommunicator.getLastResult());
}
}, 100);
});
}
// Method to stop all intervals (for cleanup if needed)
public stopAllIntervals(): void {
for (const id of this.intervalIds) {
clearInterval(id);
try {
const data = await this.db.read()
const loggedIn = data.app_config.logged_in
if (!loggedIn) {
this.log('User is not logged in.', 'log', 'sendAccountCheckRequest')
return
}
this.log("All intervals have been stopped.", 'log', 'stopAllIntervals');
const userInfo = data.app_config.user_info
if (!userInfo || !userInfo.email) {
this.log(
'Email or password not found in user config.',
'error',
'sendAccountCheckRequest',
)
return
}
const email = userInfo.email
const serverIp = data.network.serverIp
if (!serverIp) {
this.log('Server IP not found in application info.', 'error', 'sendAccountCheckRequest')
return
}
const tcpCommunicator = new TcpCommunicator(serverIp, this.tcpPort)
if (!(await tcpCommunicator.connect())) {
this.log('Failed to connect to the server.', 'error', 'sendAccountCheckRequest')
return
}
const metaInfo = { email }
if (!(await tcpCommunicator.sendMessage(operationCodes.EMAIL_VERIFICATION, metaInfo))) {
this.log('Failed to send login request.', 'error', 'sendAccountCheckRequest')
await tcpCommunicator.disconnect()
return
}
const response = await this.waitForResponse(tcpCommunicator)
if (response?.operationCode !== operationCodes.OK) {
process.send?.({ type: 'changeContent', page: this.databaseResetPage })
}
} catch (err) {
this.log(`Error during login request: ${err}`, 'error', 'sendAccountCheckRequest')
} finally {
this.sendLoginBusy = false
}
}, interval)
this.intervalIds.push(intervalId)
}
// Helper function to wait for a response from the TCP communicator
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (tcpCommunicator.hasResponseArrived()) {
clearInterval(checkInterval)
resolve(tcpCommunicator.getLastResult())
}
}, 100)
})
}
// Method to stop all intervals (for cleanup if needed)
public stopAllIntervals(): void {
for (const id of this.intervalIds) {
clearInterval(id)
}
}
this.log('All intervals have been stopped.', 'log', 'stopAllIntervals')
}
}
-137
View File
@@ -1,137 +0,0 @@
import fs from 'fs';
import path from 'path';
export class QueueManager<T> {
private readonly filePath: string;
private readonly lockFilePath: string;
private queue: T[];
private readonly compareFn: (a: T, b: T) => boolean; // Comparison function
constructor(filePath: string, compareFn: (a: T, b: T) => boolean) {
this.filePath = filePath;
this.lockFilePath = `${filePath}.lock`; // Define the lock file path
this.queue = [];
this.compareFn = compareFn;
const dir = path.dirname(filePath);
// Check if the directory exists, throw error if it doesn't
if (!fs.existsSync(dir)) {
throw new Error(`The directory does not exist: ${dir}`);
}
// If the file doesn't exist, create it
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, JSON.stringify([], null, 2), 'utf8');
}
}
// Method to acquire a lock (create .lock file)
private acquireLock(): void {
while (fs.existsSync(this.lockFilePath)) {
// Wait until the lock file is released
this.sleepSync(100); // 100ms delay before retrying
}
// Create the lock file
fs.writeFileSync(this.lockFilePath, '');
}
// Sleep function to simulate delay for locking mechanism
private sleepSync(ms: number): void {
const start = Date.now();
while (Date.now() - start < ms) {
// busy wait
}
}
// Method to release the lock (delete .lock file)
private releaseLock(): void {
if (fs.existsSync(this.lockFilePath)) {
fs.unlinkSync(this.lockFilePath);
}
}
// Load the queue from the JSON file
loadQueue(): void {
this.acquireLock(); // Acquire the lock
try {
const fileData = fs.readFileSync(this.filePath, 'utf8');
this.queue = JSON.parse(fileData) || [];
} catch (err) {
// If the file doesn't exist or is invalid, start with an empty queue
this.queue = [];
} finally {
this.releaseLock(); // Release the lock
}
}
// Save the queue back to the JSON file
saveQueue(): void {
this.acquireLock(); // Acquire the lock
try {
fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf8');
} finally {
this.releaseLock(); // Release the lock
}
}
// Enqueue: Add an item to the end of the queue if it doesn't already exist
enqueue(item: T): void {
this.loadQueue(); // Ensure we load the latest queue
// Check if the item already exists in the queue
const exists = this.queue.some(existingItem => this.compareFn(existingItem, item));
console.log(this.queue);
if (!exists) {
this.queue.push(item);
this.saveQueue(); // Save the updated queue
} else {
console.log('Item already exists in the queue. Skipping enqueue.');
}
}
// Dequeue: Remove an item from the front of the queue
dequeue(): T | null {
this.loadQueue(); // Ensure we load the latest queue
if (this.queue.length === 0) {
return null; // Queue is empty
}
const item = this.queue.shift() as T; // Remove the first item
this.saveQueue(); // Save the updated queue
return item;
}
// Peek: Get the item at the front of the queue without removing it
peek(): T | null {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length > 0 ? this.queue[0] : null;
}
// Check if the queue is empty
isEmpty(): boolean {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length === 0;
}
// Get the length of the queue
length(): number {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length;
}
// Clear the entire queue
clearQueue(): void {
this.acquireLock(); // Acquire the lock
try {
this.queue = []; // Clear the queue
fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf8');
} finally {
this.releaseLock(); // Release the lock
}
}
}
+78 -74
View File
@@ -1,86 +1,90 @@
import { TcpClient } from "../network/tcp/tcp_client";
import { ParsedMessage } from "../network/message_handler";
import { TcpClient } from '../network/tcp/tcp_client'
import { ParsedMessage } from '../network/message_handler'
export class TcpCommunicator {
private readonly ip: string;
private readonly port: number;
private tcpClient: TcpClient | null = null;
private lastResult: ParsedMessage | null = null;
private readonly ip: string
private readonly port: number
private tcpClient: TcpClient | null = null
private lastResult: ParsedMessage | null = null
constructor(ip: string, port: number) {
this.ip = ip;
this.port = port;
constructor(ip: string, port: number) {
this.ip = ip
this.port = port
}
async connect(): Promise<boolean> {
this.tcpClient = new TcpClient(this.port)
this.tcpClient.openSocket(this.ip)
return this.tcpClient.isSocketConnected()
}
async sendMessage(
operationCode: string,
metaInfo?: { [key: string]: any },
fileContent?: Buffer,
): Promise<boolean> {
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return false
// Wait until the AES key is set before sending the message
return new Promise((resolve) => {
const idWaitForAes = setInterval(async () => {
if (this.tcpClient?.isAesKeySet()) {
clearInterval(idWaitForAes)
// Send the message once AES key is set
const status = await this.tcpClient!.sendMessage(operationCode, metaInfo, fileContent)
if (status) {
await this.waitForResponse()
}
resolve(status)
}
}, 100)
})
}
getLastResult(): ParsedMessage | null {
const message = this.lastResult
this.lastResult = null
if (global.gc) {
global.gc()
}
async connect(): Promise<boolean> {
this.tcpClient = new TcpClient(this.port);
this.tcpClient.openSocket(this.ip);
return this.tcpClient.isSocketConnected();
}
return message
}
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<boolean> {
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return false;
hasResponseArrived(): boolean {
if (!this.tcpClient) return false
return this.lastResult !== null
}
// Wait until the AES key is set before sending the message
return new Promise((resolve) => {
const idWaitForAes = setInterval(async () => {
if (this.tcpClient?.isAesKeySet()) {
clearInterval(idWaitForAes);
private waitForResponse(): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.tcpClient) {
reject()
}
// Send the message once AES key is set
const status = await this.tcpClient!.sendMessage(operationCode, metaInfo, fileContent);
if (status) {
await this.waitForResponse();
}
resolve(status);
}
}, 100);
});
}
getLastResult(): ParsedMessage | null {
const message = this.lastResult;
this.lastResult = null;
if (global.gc) {
global.gc();
// Start interval for waiting for the response
const responseInterval = setInterval(() => {
if (!this.tcpClient?.isSocketConnected()) {
clearInterval(responseInterval)
resolve()
}
return message;
}
if (this.tcpClient?.isMessageReceived()) {
this.lastResult = this.tcpClient.getLastResult()
clearInterval(responseInterval) // Stop checking once we have a response
resolve()
}
}, 100) // Check every 100 milliseconds
})
}
hasResponseArrived(): boolean {
if(!this.tcpClient) return false;
return this.lastResult !== null;
}
private waitForResponse(): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.tcpClient) {
reject();
}
// Start interval for waiting for the response
const responseInterval = setInterval(() => {
if (!this.tcpClient?.isSocketConnected()) {
clearInterval(responseInterval);
resolve();
}
if (this.tcpClient?.isMessageReceived()) {
this.lastResult = this.tcpClient.getLastResult();
clearInterval(responseInterval); // Stop checking once we have a response
resolve();
}
}, 100); // Check every 100 milliseconds
});
}
async disconnect(): Promise<boolean> {
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true;
this.tcpClient.closeSocket();
return true;
}
async disconnect(): Promise<boolean> {
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true
this.tcpClient.closeSocket()
return true
}
}
+72 -101
View File
@@ -1,122 +1,93 @@
import { JsonManager } from "./json_manager";
import { MemoryManager } from "./memory_manager";
import { operationCodes } from "../network/operation_codes";
import { TcpCommunicator } from "./tcp_communicator";
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'
import { TcpCommunicator } from './tcp_communicator'
import { operationCodes } from '../network/operation_codes'
import { ParsedMessage } from '../network/message_handler'
export class UsersInfoFetcher {
private applicationInfo: JsonManager;
private memoryManager: MemoryManager;
private tcpCommunicator: TcpCommunicator | null = null;
private readonly clientPort: number;
private memoryId: string;
private readonly activeUsersKey: string;
private intervalId: NodeJS.Timeout | null = null;
private db: JsonDatabase<DatabaseScheme, any>
private tcpCommunicator: TcpCommunicator | null = null
private readonly clientPort: number
private intervalId: NodeJS.Timeout | null = null
constructor(applicationInfoPath: string, memoryManagerPath: string, clientPort: number) {
this.applicationInfo = new JsonManager(applicationInfoPath);
this.memoryManager = new MemoryManager(memoryManagerPath);
this.memoryId = '';
this.clientPort = clientPort;
this.tcpCommunicator = null;
this.activeUsersKey = 'active_users_info';
}
constructor(pathToDatabaseFile: string, clientPort: number) {
this.db = new JsonDatabase(pathToDatabaseFile)
this.clientPort = clientPort
this.tcpCommunicator = null
}
// Method to start checking user info periodically (every minute)
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
await this.initialize(); // Re-run every minute
// Start fetching user info periodically
async start(): Promise<void> {
this.intervalId = setInterval(async () => {
const data = await this.db.read()
const usersIps = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip)
if (global.gc) {
global.gc();
}
}, 5000); // 5-second interval for testing
}
// Initialize and fetch user IPs and process users info
private async initialize() {
const usersIps = await this.applicationInfo.readValue('users_ip');
if (!usersIps) {
this.log('No IP addresses found in users_ip', 'error');
return;
for (const ip of usersIps) {
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
if (!(await this.tcpCommunicator.connect())) {
this.log(`Failed to open connection for IP: ${ip}`, 'error')
continue
}
// Ensure active_users_info exists in the memory
this.memoryId = await this.applicationInfo.readValue(this.activeUsersKey);
if (!this.memoryId) {
this.memoryId = await this.memoryManager.storeMetaInformation([]);
await this.applicationInfo.writeValue(this.activeUsersKey, this.memoryId);
if (!(await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION))) {
await this.tcpCommunicator.disconnect()
continue
}
// Check user information
await this.checkUsersInfo(usersIps);
}
// Wait for the response
const response = await this.waitForResponse()
// Check user info from the list of IPs
private async checkUsersInfo(usersIps: string[]) {
let usersInfo: Array<{ ip: string, user_info: any }> = []; // Array to store IP and user_info objects
if (response && response.metaInfo) {
await this.db.update((dbData) => {
const userIndex = dbData.network.usersInLan.findIndex((user) => user.ip === ip)
for (const ip of usersIps) {
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
if (!await this.tcpCommunicator.connect()) {
this.log(`Failed to open connection for IP: ${ip}`, 'error');
continue;
if (userIndex !== -1) {
// @ts-ignore
dbData.network.usersInLan[userIndex].id = response.metaInfo.id
// @ts-ignore
dbData.network.usersInLan[userIndex].name = response.metaInfo.name
// @ts-ignore
dbData.network.usersInLan[userIndex].departmentId = response.metaInfo.departmentId
} else {
this.log(`User with IP ${ip} not found in the database.`, 'error')
}
if(!await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION)){
await this.tcpCommunicator.disconnect();
continue;
}
// Wait for the response for 10 seconds
const response = await this.waitForResponse();
// If a response is received and is successful, append it to usersInfo
if (response && response.metaInfo) {
usersInfo.push({
ip: ip,
user_info: response.metaInfo
});
}
await this.tcpCommunicator.disconnect();
return dbData
})
}
await this.updateActiveUsers(usersInfo); // Update active users information in the memory
}
await this.tcpCommunicator.disconnect()
}
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()); // Resolve the response or null if not available
}
}, 100); // Check every 100 milliseconds if the response has arrived
});
}
if (global.gc) {
global.gc()
}
}, 5000) // 5-second interval for testing
}
// Update active users information in the memory
private async updateActiveUsers(userInfo: any[]) {
await this.memoryManager.updateMetaInformation(this.memoryId, userInfo); // Update active users in memory
}
stop(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
console.log("[UsersInfoFetcher] Stopped successfully.");
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) // Check every 100ms
})
}
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[UsersInfoFetcher]';
if (level === 'error') {
console.error(`${prefix} ${message}`);
} else {
console.log(`${prefix} ${message}`);
}
stop(): void {
if (this.intervalId) {
clearInterval(this.intervalId)
this.intervalId = null
this.log('Stopped successfully.')
}
}
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[UsersInfoFetcher]'
level === 'error' ? console.error(`${prefix} ${message}`) : console.log(`${prefix} ${message}`)
}
}
+142 -142
View File
@@ -1,149 +1,149 @@
import { BrowserWindow, dialog, shell } from 'electron';
import fs from 'fs';
import path from 'path';
import { BrowserWindow, dialog, shell } from 'electron'
import fs from 'fs'
import path from 'path'
export class WindowManager {
private readonly mainWindow: BrowserWindow;
private readonly pathToPagesDir: string;
private announcementWindow: BrowserWindow | null = null;
private readonly mainWindow: BrowserWindow
private readonly pathToPagesDir: string
private announcementWindow: BrowserWindow | null = null
constructor(mainWindow: BrowserWindow, pathToPagesDir: string) {
this.pathToPagesDir = pathToPagesDir;
this.mainWindow = mainWindow;
this.log('WindowManager initialized.');
constructor(mainWindow: BrowserWindow, pathToPagesDir: string) {
this.pathToPagesDir = pathToPagesDir
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
async showAlert(message: string): Promise<void> {
if (this.mainWindow) {
await dialog.showMessageBox(this.mainWindow, {
type: 'info',
title: 'Alert',
message: message,
buttons: ['OK'],
})
this.log(`Alert displayed with message: "${message}"`)
} else {
this.log('Main window is not available.', 'error')
}
}
// Change the content of the current window to load a new HTML file
async changeContent(destination: string): Promise<void> {
if (this.mainWindow) {
try {
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`)
this.log(`Navigating to: ${destinationPath}`)
// Load the destination HTML file into the main window
await this.mainWindow.loadFile(destinationPath)
this.log(`Navigated to ${destination}`)
} catch (error) {
this.log(`Error changing content: ${error}`, 'error')
throw error // Pass the error back to the render process
}
} else {
this.log('Main window is not available.', 'error')
}
}
// New method to select a directory
async selectDirectory(): Promise<string | undefined> {
const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openDirectory'], // Only allow selecting directories
})
if (result.filePaths && result.filePaths.length > 0) {
this.log(`Directory selected: ${result.filePaths[0]}`)
return result.filePaths[0] // Return the selected directory path
} else {
this.log('No directory selected.')
return undefined // Return undefined if no directory was selected
}
}
// Show a file in the explorer
async showFileInExplorer(filePath: string): Promise<void> {
if (filePath && fs.existsSync(filePath)) {
try {
shell.showItemInFolder(filePath)
this.log(`Opened file explorer for: ${filePath}`)
} catch (error: any) {
this.log(`Error showing file in explorer: ${error.message}`, 'error')
}
} else {
this.log('File path is undefined or does not exist.', 'error')
}
}
// New method to open the file explorer and choose a file
async selectFile(): Promise<string | undefined> {
const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openFile'], // Allow selecting a file
filters: [
{ name: 'All Files', extensions: ['*'] }, // Optionally filter for specific file types
],
})
if (result.filePaths && result.filePaths.length > 0) {
this.log(`File selected: ${result.filePaths[0]}`)
return result.filePaths[0] // Return the selected file path
} else {
this.log('No file selected.')
return undefined // Return undefined if no file was selected
}
}
// Method to display an announcement in a new window
async displayAnnouncement(): Promise<void> {
if (this.announcementWindow) {
this.announcementWindow.focus()
this.log('Announcement window focused.')
return
}
// 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
async showAlert(message: string): Promise<void> {
if (this.mainWindow) {
await dialog.showMessageBox(this.mainWindow, {
type: 'info',
title: 'Alert',
message: message,
buttons: ['OK'],
});
this.log(`Alert displayed with message: "${message}"`);
} else {
this.log('Main window is not available.', 'error');
}
}
// Change the content of the current window to load a new HTML file
async changeContent(destination: string): Promise<void> {
if (this.mainWindow) {
try {
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`);
this.log(`Navigating to: ${destinationPath}`);
// Load the destination HTML file into the main window
await this.mainWindow.loadFile(destinationPath);
this.log(`Navigated to ${destination}`);
} catch (error) {
this.log(`Error changing content: ${error}`, 'error');
throw error; // Pass the error back to the render process
}
} else {
this.log('Main window is not available.', 'error');
}
}
// New method to select a directory
async selectDirectory(): Promise<string | undefined> {
const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openDirectory'], // Only allow selecting directories
});
if (result.filePaths && result.filePaths.length > 0) {
this.log(`Directory selected: ${result.filePaths[0]}`);
return result.filePaths[0]; // Return the selected directory path
} else {
this.log('No directory selected.');
return undefined; // Return undefined if no directory was selected
}
}
// Show a file in the explorer
async showFileInExplorer(filePath: string): Promise<void> {
if (filePath && fs.existsSync(filePath)) {
try {
shell.showItemInFolder(filePath);
this.log(`Opened file explorer for: ${filePath}`);
} catch (error: any) {
this.log(`Error showing file in explorer: ${error.message}`, 'error');
}
} else {
this.log('File path is undefined or does not exist.', 'error');
}
}
// New method to open the file explorer and choose a file
async selectFile(): Promise<string | undefined> {
const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openFile'], // Allow selecting a file
filters: [
{ name: 'All Files', extensions: ['*'] } // Optionally filter for specific file types
]
});
if (result.filePaths && result.filePaths.length > 0) {
this.log(`File selected: ${result.filePaths[0]}`);
return result.filePaths[0]; // Return the selected file path
} else {
this.log('No file selected.');
return undefined; // Return undefined if no file was selected
}
}
// Method to display an announcement in a new window
async displayAnnouncement(): Promise<void> {
if (this.announcementWindow) {
this.announcementWindow.focus();
this.log('Announcement window focused.');
return;
}
const mainScreen = require('electron').screen.getPrimaryDisplay();
const { width, height } = mainScreen.size;
this.announcementWindow = new BrowserWindow({
width: width / 3,
height: height / 2,
resizable: false,
title: 'Announcement',
webPreferences: {
preload: path.join(__dirname, '..', 'main', 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
this.announcementWindow.removeMenu();
const announcementPath = path.join(this.pathToPagesDir, 'announcement.html');
await this.announcementWindow.loadFile(announcementPath);
this.log(`Announcement window opened at: ${announcementPath}`);
// Handle window close
this.announcementWindow.on('closed', () => {
this.announcementWindow = null;
this.log('Announcement window closed.');
});
}
async closeAnnouncementWindow(): Promise<void> {
if (this.announcementWindow) {
this.announcementWindow.close();
this.log('Announcement window closed by user.');
}
const mainScreen = require('electron').screen.getPrimaryDisplay()
const { width, height } = mainScreen.size
this.announcementWindow = new BrowserWindow({
width: width / 3,
height: height / 2,
resizable: false,
title: 'Announcement',
webPreferences: {
preload: path.join(__dirname, '..', 'main', 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
})
this.announcementWindow.removeMenu()
const announcementPath = path.join(this.pathToPagesDir, 'announcement.html')
await this.announcementWindow.loadFile(announcementPath)
this.log(`Announcement window opened at: ${announcementPath}`)
// Handle window close
this.announcementWindow.on('closed', () => {
this.announcementWindow = null
this.log('Announcement window closed.')
})
}
async closeAnnouncementWindow(): Promise<void> {
if (this.announcementWindow) {
this.announcementWindow.close()
this.log('Announcement window closed by user.')
}
}
}
+116 -106
View File
@@ -1,121 +1,131 @@
import { fork, ChildProcess } from 'child_process';
import path from 'path';
import { WindowManager } from "./window_manager";
import { fork, ChildProcess } from 'child_process'
import path from 'path'
import { WindowManager } from './window_manager'
export class WorkerManager {
private readonly pathToWorkerDir: string;
private windowManager: WindowManager;
private workers: ChildProcess[];
private cleanupInProgress: boolean = false;
private readonly pathToWorkerDir: string
private windowManager: WindowManager
private workers: ChildProcess[]
private cleanupInProgress: boolean = false
constructor(pathToWorkerDir: string, windowManager: WindowManager) {
this.pathToWorkerDir = pathToWorkerDir;
this.windowManager = windowManager;
this.workers = []; // Initialize the array to store child processes
}
constructor(pathToWorkerDir: string, windowManager: WindowManager) {
this.pathToWorkerDir = pathToWorkerDir
this.windowManager = windowManager
this.workers = [] // Initialize the array to store child processes
}
async startNetworkScannerWorker(udpPort: number, tcpPort: number, okPage: string, errorPage: string, databaseResetPage: string, userConfigPath: string, applicationInfoPath: string): Promise<void> {
return this.startForkedWorker('network_scanner_worker.js', {
UDP_PORT: udpPort.toString(),
TCP_PORT: tcpPort.toString(),
OK_PAGE: okPage,
ERROR_PAGE: errorPage,
DATABASE_RESET_PAGE: databaseResetPage,
USER_CONFIG_PATH: userConfigPath,
APPLICATION_INFO_PATH: applicationInfoPath
});
}
async startNetworkScannerWorker(
udpPort: number,
tcpPort: number,
okPage: string,
errorPage: string,
databaseResetPage: string,
pathToDatabaseFile: string,
): Promise<void> {
return this.startForkedWorker('network_scanner_worker.js', {
UDP_PORT: udpPort.toString(),
TCP_PORT: tcpPort.toString(),
OK_PAGE: okPage,
ERROR_PAGE: errorPage,
DATABASE_RESET_PAGE: databaseResetPage,
DATABASE_FILE_PATH: pathToDatabaseFile,
})
}
async startDirectoriesWatchersWorker(memoryManagerPath: string, applicationInfoPath: string): Promise<void> {
return this.startForkedWorker('directories_watcher_worker.js', {
MEMORY_MANAGER_PATH: memoryManagerPath,
APPLICATION_INFO_PATH: applicationInfoPath
});
}
async startDirectoriesWatchersWorker(pathToDatabaseFile: string): Promise<void> {
return this.startForkedWorker('directories_watcher_worker.js', {
DATABASE_FILE_PATH: pathToDatabaseFile,
})
}
async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise<void> {
return this.startForkedWorker('servers_worker.js', {
HOST: host,
USER_UDP_PORT: udpPort.toString(),
USER_TCP_PORT: tcpPort.toString()
});
}
async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise<void> {
return this.startForkedWorker('servers_worker.js', {
HOST: host,
USER_UDP_PORT: udpPort.toString(),
USER_TCP_PORT: tcpPort.toString(),
})
}
async startResourceCoordinatorWorker(usersConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, queueManagerPath: string, tcpPort: number): Promise<void> {
return this.startForkedWorker('resource_coordinator_worker.js', {
USERS_CONFIG_PATH: usersConfigPath,
APPLICATION_INFO_PATH: applicationInfoPath,
MEMORY_MANAGER_PATH: memoryManagerPath,
QUEUE_MANAGER_PATH: queueManagerPath,
TCP_PORT: tcpPort.toString()
});
}
async startResourceCoordinatorWorker(
pathToDatabaseFiles: string,
tcpPort: number,
): Promise<void> {
return this.startForkedWorker('resource_coordinator_worker.js', {
DATABASE_FILE_PATH: pathToDatabaseFiles,
TCP_PORT: tcpPort.toString(),
})
}
async startBackupRetrievalWorker(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string): Promise<void> {
return this.startForkedWorker('backup_retrieval_worker.js', {
USER_CONFIG_PATH: userConfigPath,
APPLICATION_INFO_PATH: applicationInfoPath,
CLIENT_PORT: clientPort.toString(),
DESTINATION_PATH: destinationPath
});
}
async startBackupRetrievalWorker(
clientPort: number,
destinationPath: string,
pathToDatabaseFile: string,
): Promise<void> {
return this.startForkedWorker('backup_retrieval_worker.js', {
CLIENT_PORT: clientPort.toString(),
DESTINATION_PATH: destinationPath,
DATABASE_FILE_PATH: pathToDatabaseFile,
})
}
private async startForkedWorker(scriptName: string, envData: { [key: string]: string }): Promise<void> {
return new Promise((resolve, reject) => {
const worker = fork(path.join(this.pathToWorkerDir, scriptName), {
execArgv: ['--max-old-space-size=4096'], // Set memory limit for the forked process
env: { ...process.env, ...envData } // Merge environment variables
});
private async startForkedWorker(
scriptName: string,
envData: { [key: string]: string },
): Promise<void> {
return new Promise((resolve, reject) => {
const worker = fork(path.join(this.pathToWorkerDir, scriptName), {
execArgv: ['--max-old-space-size=4096'], // Set memory limit for the forked process
env: { ...process.env, ...envData }, // Merge environment variables
})
this.workers.push(worker); // Store the worker reference
this.workers.push(worker) // Store the worker reference
worker.on('message', (data: unknown) => {
const message = data as { type: string, page?: string, message?: string }; // Type casting for message
worker.on('message', (data: unknown) => {
const message = data as { type: string; page?: string; message?: string } // Type casting for message
if (message.type === 'changeContent' && message.page) {
this.windowManager.changeContent(message.page);
} else if (message.type === 'showAlert' && message.message) {
this.windowManager.showAlert(message.message);
} else {
console.log(`${scriptName} message:`, message);
}
});
worker.on('error', (err) => {
console.error(`${scriptName} error:`, err);
worker.kill();
this.removeWorker(worker);
reject(err);
});
worker.on('exit', (code, signal) => {
this.removeWorker(worker);
if (code === 0) {
console.log(`${scriptName} exited successfully`);
resolve();
} else if (signal) {
console.log(`${scriptName} was killed with signal: ${signal}`);
} else {
console.error(`${scriptName} exited with code: ${code}`);;
}
});
});
}
closeAllWorkers(): void {
if (this.cleanupInProgress) return; // Prevent duplicate cleanup
this.cleanupInProgress = true;
console.log('Terminating all running workers...');
this.workers.forEach(worker => worker.kill());
this.workers = [];
}
// Helper method to remove a worker from the workers array when it exits
private removeWorker(worker: ChildProcess): void {
const index = this.workers.indexOf(worker);
if (index > -1) {
this.workers.splice(index, 1);
if (message.type === 'changeContent' && message.page) {
this.windowManager.changeContent(message.page)
} else if (message.type === 'showAlert' && message.message) {
this.windowManager.showAlert(message.message)
} else {
console.log(`${scriptName} message:`, message)
}
})
worker.on('error', (err) => {
console.error(`${scriptName} error:`, err)
worker.kill()
this.removeWorker(worker)
reject(err)
})
worker.on('exit', (code, signal) => {
this.removeWorker(worker)
if (code === 0) {
console.log(`${scriptName} exited successfully`)
resolve()
} else if (signal) {
console.log(`${scriptName} was killed with signal: ${signal}`)
} else {
console.error(`${scriptName} exited with code: ${code}`)
}
})
})
}
closeAllWorkers(): void {
if (this.cleanupInProgress) return // Prevent duplicate cleanup
this.cleanupInProgress = true
console.log('Terminating all running workers...')
this.workers.forEach((worker) => worker.kill())
this.workers = []
}
private removeWorker(worker: ChildProcess): void {
const index = this.workers.indexOf(worker)
if (index > -1) {
this.workers.splice(index, 1)
}
}
}