async plugins fixed
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationHandler } from './operation_handler';
|
||||
import { OperationPlugin } from './operation_plugin';
|
||||
|
||||
export abstract class OperationBase implements OperationPlugin {
|
||||
// Shared operation codes
|
||||
public static readonly operationCodes = {
|
||||
OK: 'OK',
|
||||
ERR: 'ERR',
|
||||
END: 'END',
|
||||
};
|
||||
|
||||
// Default handler for OK operation
|
||||
public static handleOk(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
return parsedMessage; // Typically, you would just acknowledge with OK, returning as-is
|
||||
}
|
||||
|
||||
// Default handler for ERR operation
|
||||
public static handleErr(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
return parsedMessage; // Typically, you would log the error and return
|
||||
}
|
||||
|
||||
// Default handler for END operation
|
||||
public static handleEnd(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.END,
|
||||
metaInfo: { message: 'Connection ended.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Register the common OK, ERR, and END handlers
|
||||
public static registerCommonOperations(operationHandler: OperationHandler): void {
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.OK, OperationBase.handleOk);
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.ERR, OperationBase.handleErr);
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.END, OperationBase.handleEnd);
|
||||
}
|
||||
|
||||
// Abstract register method that will be implemented by subclasses
|
||||
public abstract register(operationHandler: OperationHandler): void;
|
||||
}
|
||||
@@ -2,14 +2,14 @@
|
||||
import { ParsedMessage, MessageHandler } from '../message_handler';
|
||||
import { OperationPlugin } from './operation_plugin';
|
||||
|
||||
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => ParsedMessage;
|
||||
// Define handler function type to return Promise<ParsedMessage>
|
||||
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => Promise<ParsedMessage>;
|
||||
|
||||
export class OperationHandler {
|
||||
private static instance: OperationHandler;
|
||||
private handlers: { [operationCode: string]: OperationHandlerFunction } = {};
|
||||
|
||||
private constructor() {
|
||||
// Register only the unknown command handler on initialization
|
||||
this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand);
|
||||
}
|
||||
|
||||
@@ -26,25 +26,24 @@ export class OperationHandler {
|
||||
this.handlers[operationCode] = handler;
|
||||
}
|
||||
|
||||
// Handle operation request
|
||||
public handleOperation(rawMessage: string): ParsedMessage {
|
||||
// Parse and validate the message
|
||||
// Handle operation request asynchronously
|
||||
public async handleOperation(rawMessage: string): Promise<ParsedMessage> {
|
||||
const parsedMessage = MessageHandler.parseMessage(rawMessage);
|
||||
if (!parsedMessage || !MessageHandler.validateMessage(parsedMessage)) {
|
||||
return this.handleUnknownCommand(parsedMessage);
|
||||
}
|
||||
|
||||
// Dispatch the handler for the given operation code
|
||||
// Retrieve the handler for the operation code and invoke it asynchronously
|
||||
const handler = this.handlers[parsedMessage.operationCode];
|
||||
if (handler) {
|
||||
return handler(parsedMessage);
|
||||
return await handler(parsedMessage);
|
||||
} else {
|
||||
return this.handleUnknownCommand(parsedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Default handler for unknown commands
|
||||
private handleUnknownCommand(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
private async handleUnknownCommand(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
return {
|
||||
operationCode: 'UNKNOWN_COMMAND',
|
||||
metaInfo: { message: 'Unknown command received.' },
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
import os from 'node:os';
|
||||
import {OperationPlugin} from "../operations_base/operation_plugin";
|
||||
|
||||
export class GeneralOperations extends OperationBase {
|
||||
export class GeneralOperations implements OperationPlugin {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
|
||||
OK: 'OK',
|
||||
ERR: 'ERR',
|
||||
END: 'END',
|
||||
HEARTBEAT: 'HEARTBEAT',
|
||||
ALIVE: 'ALIVE',
|
||||
SET_PUBLIC_KEY: 'SET_PUBLIC_KEY',
|
||||
SET_AES_KEY: 'SET_AES_KEY',
|
||||
};
|
||||
|
||||
// Handle heartbeat operation
|
||||
public static handleHeartbeat(): ParsedMessage {
|
||||
// Handle heartbeat operation asynchronously
|
||||
public static async handleHeartbeat(): Promise<ParsedMessage> {
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
let ipAddress = 'Unknown';
|
||||
|
||||
for (const iface of Object.values(networkInterfaces)) {
|
||||
// @ts-ignore
|
||||
for (const address of iface) {
|
||||
for (const address of iface!) {
|
||||
if (address.family === 'IPv4' && !address.internal) {
|
||||
ipAddress = address.address;
|
||||
break;
|
||||
@@ -34,8 +35,8 @@ export class GeneralOperations extends OperationBase {
|
||||
};
|
||||
}
|
||||
|
||||
// Handle public key exchange
|
||||
public static handlePublicKey(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Handle public key exchange asynchronously
|
||||
public static async handlePublicKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
const clientPublicKey = parsedMessage.metaInfo?.publicKey;
|
||||
if (clientPublicKey) {
|
||||
return {
|
||||
@@ -50,14 +51,14 @@ export class GeneralOperations extends OperationBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle AES key exchange
|
||||
public static handleAESKey(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Handle AES key exchange asynchronously
|
||||
public static async handleAESKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
const aesKey = parsedMessage.metaInfo?.aesKey;
|
||||
const aesIv = parsedMessage.metaInfo?.aesIv;
|
||||
if (aesKey && aesIv) {
|
||||
return {
|
||||
operationCode: GeneralOperations.operationCodes.SET_AES_KEY,
|
||||
metaInfo: { aesKey: aesKey, aesIv: aesIv },
|
||||
metaInfo: { aesKey, aesIv },
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -67,14 +68,22 @@ export class GeneralOperations extends OperationBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Default async handler for OK operation
|
||||
public static async handleOk(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
return parsedMessage; // Acknowledge with OK, returning as-is
|
||||
}
|
||||
|
||||
// Default async handler for ERR operation
|
||||
public static async handleErr(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
return parsedMessage; // Log the error and return
|
||||
}
|
||||
|
||||
// Register general operations with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
// Register specific handlers for the general operations
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.HEARTBEAT, GeneralOperations.handleHeartbeat);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey); // Register AES key handler
|
||||
|
||||
// Register common operations inherited from the base class
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.OK, GeneralOperations.handleOk);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.ERR, GeneralOperations.handleErr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
import {operationCodes} from "../operation_codes";
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import checkDiskSpace from "check-disk-space";
|
||||
import { JsonManager } from '../../helpers/json_manager';
|
||||
import {OperationPlugin} from "../operations_base/operation_plugin";
|
||||
|
||||
const LOCK_FILE_EXTENSION = '.lock';
|
||||
|
||||
export class UserToUserOperations extends OperationBase {
|
||||
export class UserToUserOperations implements OperationPlugin {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
|
||||
SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT',
|
||||
GET_USER_INFORMATION: 'GET_USER_INFORMATION',
|
||||
RESET_DATABASE: 'RESET_DATABASE',
|
||||
BACKUP_FILE: 'BACKUP_FILE',
|
||||
CLEAR_BACKUP: 'CLEAR_BACKUP',
|
||||
SHARE_FILE: 'SHARE_FILE',
|
||||
@@ -24,429 +22,149 @@ export class UserToUserOperations extends OperationBase {
|
||||
DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE',
|
||||
};
|
||||
|
||||
// Utility function to pause execution (sleep)
|
||||
static sleep(ms: number): void {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < ms) {
|
||||
// Busy wait loop (not optimal but fine for this short duration)
|
||||
}
|
||||
}
|
||||
|
||||
// Read JSON file with a lock mechanism
|
||||
static readJsonSync(filePath: string): any {
|
||||
const lockFilePath = `${filePath}.${LOCK_FILE_EXTENSION}`;
|
||||
const absolutePath = path.resolve(filePath);
|
||||
|
||||
private static async hasEnoughDiskSpace(directory: string, requiredPercentage: number = 25): Promise<boolean> {
|
||||
try {
|
||||
// Loop until the lock file is removed by another process
|
||||
while (fs.existsSync(lockFilePath)) {
|
||||
console.log(`Waiting for lock file to be released: ${lockFilePath}`);
|
||||
UserToUserOperations.sleep(100);
|
||||
}
|
||||
|
||||
// Create lock file to signal this process is working on the file
|
||||
fs.writeFileSync(lockFilePath, ''); // Create the lock file
|
||||
|
||||
// Read and parse the JSON file
|
||||
const fileContents = fs.readFileSync(absolutePath, 'utf-8');
|
||||
const parsedJson = JSON.parse(fileContents);
|
||||
|
||||
// Once processing is done, delete the lock file
|
||||
fs.unlinkSync(lockFilePath); // Remove the lock file
|
||||
|
||||
return parsedJson; // Return the parsed JSON data
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing JSON from ${filePath}:`, error);
|
||||
|
||||
// Ensure the lock file is removed even in case of an error
|
||||
if (fs.existsSync(lockFilePath)) {
|
||||
fs.unlinkSync(lockFilePath);
|
||||
}
|
||||
|
||||
return null; // Return null or throw error based on preference
|
||||
}
|
||||
}
|
||||
|
||||
static writeJsonSync(filePath: string, data: any): boolean {
|
||||
const lockFilePath = `${filePath}.${LOCK_FILE_EXTENSION}`;
|
||||
const absolutePath = path.resolve(filePath);
|
||||
|
||||
try {
|
||||
// Loop until the lock file is removed by another process
|
||||
while (fs.existsSync(lockFilePath)) {
|
||||
console.log(`Waiting for lock file to be released: ${lockFilePath}`);
|
||||
UserToUserOperations.sleep(100); // Use the sleep utility to pause
|
||||
}
|
||||
|
||||
// Create lock file to signal this process is working on the file
|
||||
fs.writeFileSync(lockFilePath, ''); // Create the lock file
|
||||
|
||||
// Write the data to the JSON file
|
||||
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
console.log(`Data written successfully to ${absolutePath}`);
|
||||
|
||||
// Once processing is done, delete the lock file
|
||||
fs.unlinkSync(lockFilePath); // Remove the lock file
|
||||
|
||||
return true; // Indicate successful write
|
||||
} catch (error) {
|
||||
console.error(`Error writing JSON to ${filePath}:`, error);
|
||||
|
||||
// Ensure the lock file is removed even in case of an error
|
||||
if (fs.existsSync(lockFilePath)) {
|
||||
fs.unlinkSync(lockFilePath);
|
||||
}
|
||||
|
||||
return false; // Indicate failure
|
||||
}
|
||||
}
|
||||
|
||||
private static hasEnoughDiskSpace(directory: string, requiredPercentage: number): boolean {
|
||||
try {
|
||||
let availableSpace = 0;
|
||||
let totalSpace = 0;
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Windows
|
||||
const output = execSync(`wmic logicaldisk where "DeviceID='${directory[0]}:'" get FreeSpace,Size`).toString();
|
||||
const lines = output.trim().split('\n');
|
||||
const [freeSpaceStr, totalSpaceStr] = lines[1].trim().split(/\s+/);
|
||||
availableSpace = parseInt(freeSpaceStr, 10); // Available space in bytes
|
||||
totalSpace = parseInt(totalSpaceStr, 10); // Total space in bytes
|
||||
} else {
|
||||
// Unix-based (Linux/macOS)
|
||||
const output = execSync(`df -k "${directory}"`).toString();
|
||||
const lines = output.trim().split('\n');
|
||||
const parts = lines[lines.length - 1].split(/\s+/);
|
||||
const availableSpaceInKb = parseInt(parts[3], 10); // Available space in KB
|
||||
const totalSpaceInKb = parseInt(parts[1], 10); // Total space in KB
|
||||
availableSpace = availableSpaceInKb * 1024;
|
||||
totalSpace = totalSpaceInKb * 1024;
|
||||
}
|
||||
|
||||
// Calculate available space as a percentage of the total space
|
||||
const diskInfo = await checkDiskSpace(directory);
|
||||
const availableSpace = diskInfo.free;
|
||||
const totalSpace = diskInfo.size;
|
||||
const availablePercentage = (availableSpace / totalSpace) * 100;
|
||||
|
||||
// Return true if the available percentage is greater than or equal to the required percentage
|
||||
return availablePercentage >= requiredPercentage;
|
||||
} catch (error) {
|
||||
console.error(`Error checking disk space: ${error}`);
|
||||
return false; // Return false if there's an error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static handleResetDatabase(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Path to the application.json file
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
|
||||
public static async handleSendAnnouncement(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.message) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing announcement message.' }};
|
||||
}
|
||||
|
||||
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
|
||||
try {
|
||||
// Read the existing data from application.json
|
||||
const appData = UserToUserOperations.readJsonSync(pathToApplicationJson) || {};
|
||||
|
||||
// Update the reset_application_preferences field to true
|
||||
appData.reset_application_preferences = true;
|
||||
|
||||
// Write the updated data back to application.json
|
||||
const success = UserToUserOperations.writeJsonSync(pathToApplicationJson, appData);
|
||||
|
||||
if (success) {
|
||||
console.log(`Application preferences reset successfully.`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: 'Application preferences reset successfully.' },
|
||||
};
|
||||
} else {
|
||||
throw new Error("Failed to write to application.json");
|
||||
}
|
||||
await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message);
|
||||
console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error resetting application preferences: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error resetting application preferences: ${error.message}` },
|
||||
};
|
||||
console.error(`Error saving announcement: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static handleSendAnnouncement(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure the message is available in metaInfo
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.message) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing announcement message in meta information.' },
|
||||
};
|
||||
}
|
||||
|
||||
const announcementMessage = parsedMessage.metaInfo.message;
|
||||
|
||||
// Path to the application.json file
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
|
||||
public static async handleGetUserInformation(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'));
|
||||
try {
|
||||
// Read the existing data from application.json
|
||||
const appData = UserToUserOperations.readJsonSync(pathToApplicationJson) || {};
|
||||
|
||||
// Update the announcement field with the new message
|
||||
appData.announcement = announcementMessage;
|
||||
|
||||
// Write the updated data back to application.json
|
||||
fs.writeFileSync(pathToApplicationJson, JSON.stringify(appData, null, 2), 'utf-8');
|
||||
|
||||
console.log(`Announcement message saved successfully: ${announcementMessage}`);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: 'Announcement message saved successfully.' },
|
||||
};
|
||||
const userInfo = await jsonManager.readValue('user_info');
|
||||
return { operationCode: operationCodes.OK, metaInfo: userInfo };
|
||||
} catch (error: any) {
|
||||
console.error(`Error saving announcement message: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error saving announcement message: ${error.message}` },
|
||||
};
|
||||
console.error(`Error fetching user info: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Error fetching user info' }};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle user information retrieval
|
||||
public static handleGetUserInformation(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const pathToUserJson = path.join(__dirname, '..', '..', 'json_files', 'userConfig.json');
|
||||
const userInfo = UserToUserOperations.readJsonSync(pathToUserJson);
|
||||
|
||||
if (userInfo) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: userInfo.user_info,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Error fetching user information.' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle file reception and saving
|
||||
public static handleBackupFile(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure metaInfo and fileContent are available
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing file content or meta information.' },
|
||||
};
|
||||
public static async handleBackupFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing file or user information.' }};
|
||||
}
|
||||
|
||||
const { userName, relativeFilePath } = parsedMessage.metaInfo;
|
||||
|
||||
if (!userName || !relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name or file path information.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Base directory where backups will be stored
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
|
||||
// Full path where the file will be stored (under the user's directory)
|
||||
const fullFilePath = path.join(baseBackupDir, userName, relativeFilePath);
|
||||
const fullFilePath = path.join(__dirname, '..', '..', 'backups', userName, relativeFilePath);
|
||||
|
||||
try {
|
||||
// Check if there is enough disk space
|
||||
const requiredPercentage = 25;
|
||||
if (!UserToUserOperations.hasEnoughDiskSpace(baseBackupDir, requiredPercentage)) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Insufficient disk space for backup.' },
|
||||
};
|
||||
if (!await UserToUserOperations.hasEnoughDiskSpace(path.dirname(fullFilePath), 25)) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo:
|
||||
{ message: 'Insufficient disk space.' }};
|
||||
}
|
||||
|
||||
// Ensure the directory structure exists (create directories if they don't exist)
|
||||
const dirPath = path.dirname(fullFilePath);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
await fs.mkdir(path.dirname(fullFilePath), { recursive: true });
|
||||
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer);
|
||||
console.log(`File saved: ${fullFilePath}`);
|
||||
|
||||
// Write the file content to the correct path
|
||||
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
|
||||
|
||||
console.log(`File saved successfully: ${fullFilePath}`);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `File saved successfully: ${relativeFilePath}` },
|
||||
};
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: `File saved: ${relativeFilePath}` }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error saving file: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error saving file: ${error.message}` },
|
||||
};
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error saving file: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle clearing all backups for a user
|
||||
public static handleClearBackup(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure the userName is available in metaInfo
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name in meta information.' },
|
||||
};
|
||||
public static async handleClearBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }};
|
||||
}
|
||||
|
||||
const { userName } = parsedMessage.metaInfo;
|
||||
|
||||
// Base directory where backups are stored
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
const userBackupDir = path.join(baseBackupDir, userName);
|
||||
|
||||
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.userName);
|
||||
try {
|
||||
// Check if the user's backup directory exists
|
||||
if (fs.existsSync(userBackupDir)) {
|
||||
// Recursively delete the user's backup directory
|
||||
fs.rmSync(userBackupDir, { recursive: true, force: true });
|
||||
console.log(`Backup cleared successfully for user: ${userName}`);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `Backup cleared successfully for user: ${userName}` },
|
||||
};
|
||||
} else {
|
||||
console.warn(`Backup directory not found for user: ${userName}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Backup directory not found for user: ${userName}` },
|
||||
};
|
||||
}
|
||||
await fs.rm(userBackupDir, { recursive: true, force: true });
|
||||
console.log(`Backup cleared: ${userBackupDir}`);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: `Backup cleared for ${parsedMessage.metaInfo.userName}` }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error clearing backup for user: ${userName} - ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error clearing backup for user: ${userName}` },
|
||||
};
|
||||
console.error(`Error clearing backup: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing backup: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle sharing file operation
|
||||
public static handleShareFile(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure metaInfo and fileContent are available
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing file content or meta information.' },
|
||||
};
|
||||
public static async handleShareFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' }};
|
||||
}
|
||||
|
||||
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
|
||||
const { userName, relativeFilePath } = parsedMessage.metaInfo;
|
||||
|
||||
if (!userName || !relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name or file path information.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Path to the application.json to read the shareDirectory field
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
|
||||
|
||||
// Corrected the condition
|
||||
if (!appInfo || !appInfo.shareDirectory) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Error retrieving share directory from application.json.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Get the share directory path
|
||||
const shareDirectory = appInfo.shareDirectory.path;
|
||||
|
||||
// Full path where the file will be stored (under the user's directory in the shared folder)
|
||||
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath);
|
||||
|
||||
try {
|
||||
// Ensure the directory structure exists (create directories if they don't exist)
|
||||
const dirPath = path.dirname(fullFilePath);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
const appInfo = await jsonManager.readValue('shareDirectory');
|
||||
const shareDirectory = appInfo?.path || '';
|
||||
|
||||
if (!shareDirectory) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Share directory missing.' }};
|
||||
}
|
||||
|
||||
// Write the file content to the correct path
|
||||
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
|
||||
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath);
|
||||
await fs.mkdir(path.dirname(fullFilePath), { recursive: true });
|
||||
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer);
|
||||
console.log(`File shared: ${fullFilePath}`);
|
||||
|
||||
console.log(`File shared successfully: ${fullFilePath}`);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
|
||||
};
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: `File shared: ${relativeFilePath}` }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error sharing file: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error sharing file: ${error.message}` },
|
||||
};
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error sharing file: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
public static handleClearDepartment(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure the userName is available in metaInfo
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name in meta information.' },
|
||||
};
|
||||
public static async handleClearDepartment(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }};
|
||||
}
|
||||
|
||||
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
|
||||
const { userName } = parsedMessage.metaInfo;
|
||||
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
|
||||
|
||||
// Corrected the condition
|
||||
if (!appInfo || !appInfo.departmentDirectory) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Error retrieving share directory from application.json.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Get the share directory path
|
||||
const baseDepartmentDir = appInfo.departmentDirectory.path;
|
||||
const userDepartmentDir = path.join(baseDepartmentDir, '..', 'DEPARTMENT_FILES', userName);
|
||||
|
||||
try {
|
||||
// Check if the user's backup directory exists
|
||||
if (fs.existsSync(userDepartmentDir)) {
|
||||
// Recursively delete the user's backup directory
|
||||
fs.rmSync(userDepartmentDir, { recursive: true, force: true });
|
||||
console.log(`Backup cleared successfully for user: ${userName}`);
|
||||
const appInfo = await jsonManager.readValue('departmentDirectory');
|
||||
const departmentDir = appInfo?.path || '';
|
||||
const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `Backup cleared successfully for user: ${userName}`},
|
||||
};
|
||||
} else {
|
||||
console.warn(`Backup directory not found for user: ${userName}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Backup directory not found for user: ${userName}`},
|
||||
};
|
||||
try {
|
||||
await fs.access(userDepartmentDir)
|
||||
}catch(ex: any){
|
||||
return { operationCode: operationCodes.OK};
|
||||
}
|
||||
|
||||
await fs.rm(userDepartmentDir, { recursive: true, force: true });
|
||||
console.log(`Department backup cleared: ${userDepartmentDir}`);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: `Department backup cleared for ${userName}` }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error clearing backup for user: ${userName} - ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error clearing backup for user: ${userName}`},
|
||||
};
|
||||
console.error(`Error clearing department: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing department: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
public static handleDepartmentFile(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
public static async handleDepartmentFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
// Ensure metaInfo and fileContent are available
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing file content or meta information.' },
|
||||
};
|
||||
}
|
||||
@@ -455,130 +173,99 @@ export class UserToUserOperations extends OperationBase {
|
||||
|
||||
if (!userName || !relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name or file path information.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Path to the application.json to read the shareDirectory field
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
|
||||
const jsonManager = new JsonManager(pathToApplicationJson);
|
||||
|
||||
// Corrected the condition
|
||||
if (!appInfo || !appInfo.departmentDirectory) {
|
||||
// Read application configuration asynchronously
|
||||
let appInfo;
|
||||
try {
|
||||
appInfo = await jsonManager.readValue('departmentDirectory');
|
||||
} catch (error: any) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Error retrieving share directory from application.json.' },
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: `Error reading application config: ${error.message}` },
|
||||
};
|
||||
}
|
||||
|
||||
if (!appInfo || !appInfo.path) {
|
||||
return {
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Error retrieving department directory from application.json.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Get the share directory path
|
||||
const departmentDirectory = appInfo.departmentDirectory.path;
|
||||
|
||||
// Full path where the file will be stored (under the user's directory in the shared folder)
|
||||
const departmentDirectory = appInfo.path;
|
||||
const fullFilePath = path.join(departmentDirectory, '..', 'DEPARTMENT_FILES', userName, relativeFilePath);
|
||||
|
||||
try {
|
||||
// Ensure the directory structure exists (create directories if they don't exist)
|
||||
const dirPath = path.dirname(fullFilePath);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
await fs.mkdir(dirPath, { recursive: true });
|
||||
|
||||
// Write the file content to the correct path
|
||||
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
|
||||
|
||||
console.log(`File shared successfully: ${fullFilePath}`);
|
||||
// Write the file content to the specified path
|
||||
await fs.writeFile(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `File shared successfully: ${relativeFilePath}`},
|
||||
};
|
||||
operationCode: operationCodes.OK,
|
||||
metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error(`Error sharing file: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: {message: `Error sharing file: ${error.message}`},
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: `Error sharing file: ${error.message}` },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a backup has been created for a user
|
||||
public static handleIsBackupCreated(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
if(!parsedMessage.metaInfo) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing metaInfo.' },
|
||||
};
|
||||
public static async handleIsBackupCreated(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.name) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }};
|
||||
}
|
||||
|
||||
const {name} = parsedMessage.metaInfo;
|
||||
if (!name) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing name in meta information.' },
|
||||
};
|
||||
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name);
|
||||
|
||||
try {
|
||||
const exists = await fs.access(userBackupDir).then(() => true).catch(() => false);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { backupExists: exists }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error checking backup: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error checking backup: ${error.message}` }};
|
||||
}
|
||||
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
const userBackupDir = path.join(baseBackupDir, name);
|
||||
|
||||
console.log(userBackupDir);
|
||||
|
||||
const exists = fs.existsSync(userBackupDir);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { backupExists: exists },
|
||||
};
|
||||
}
|
||||
|
||||
// Get the structure of the backup directory for a user
|
||||
public static handleGetBackupStructure(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
if(!parsedMessage.metaInfo) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing metaInfo.' },
|
||||
};
|
||||
public static async handleGetBackupStructure(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.name) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }};
|
||||
}
|
||||
|
||||
const {name} = parsedMessage.metaInfo;
|
||||
if (!name) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing name in meta information.' },
|
||||
};
|
||||
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name);
|
||||
|
||||
try {
|
||||
const structure = await UserToUserOperations.buildDirectoryStructure(userBackupDir);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { structure }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error building backup structure: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
|
||||
}
|
||||
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
const userBackupDir = path.join(baseBackupDir, name);
|
||||
|
||||
if (!fs.existsSync(userBackupDir)) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { structure: {} }, // Return empty structure if directory doesn't exist
|
||||
};
|
||||
}
|
||||
|
||||
const fileStructure = UserToUserOperations.buildDirectoryStructure(userBackupDir);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { structure: fileStructure },
|
||||
};
|
||||
}
|
||||
|
||||
// Build the directory structure recursively
|
||||
private static buildDirectoryStructure(directoryPath: string): any {
|
||||
private static async buildDirectoryStructure(directoryPath: string): Promise<any> {
|
||||
const structure: any = {};
|
||||
const files = fs.readdirSync(directoryPath);
|
||||
const files = await fs.readdir(directoryPath);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(directoryPath, file);
|
||||
const stats = fs.statSync(filePath);
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
structure[file] = UserToUserOperations.buildDirectoryStructure(filePath); // Recursive for subdirectories
|
||||
structure[file] = await UserToUserOperations.buildDirectoryStructure(filePath);
|
||||
} else {
|
||||
structure[file] = path.relative(directoryPath, filePath);
|
||||
}
|
||||
@@ -587,67 +274,43 @@ export class UserToUserOperations extends OperationBase {
|
||||
return structure;
|
||||
}
|
||||
|
||||
// Handle file request from backup directory
|
||||
public static handleReqFileFromBackup(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
if(!parsedMessage.metaInfo) {
|
||||
public static async handleReqFileFromBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.name || !parsedMessage.metaInfo?.relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing metaInfo.' },
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name or file path in meta information.' },
|
||||
};
|
||||
}
|
||||
|
||||
const { name, relativeFilePath } = parsedMessage.metaInfo;
|
||||
|
||||
if (!name || !relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing name or file path in meta information.' },
|
||||
};
|
||||
}
|
||||
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
const userBackupDir = path.join(baseBackupDir, name);
|
||||
const fullFilePath = path.join(userBackupDir, relativeFilePath);
|
||||
|
||||
if (!fs.existsSync(fullFilePath)) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'File not found in backup.' },
|
||||
};
|
||||
}
|
||||
const fullFilePath = path.join(__dirname, '..', '..', 'backups', name, relativeFilePath);
|
||||
|
||||
try {
|
||||
const fileContent = fs.readFileSync(fullFilePath, 'base64');
|
||||
const fileContent = await fs.readFile(fullFilePath);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
operationCode: operationCodes.OK,
|
||||
metaInfo: { relativeFilePath },
|
||||
fileContent: Buffer.from(fileContent, 'base64')
|
||||
fileContent,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error(`Error reading file: ${error.message}`);
|
||||
console.error(`Error reading file from backup: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error reading file: ${error.message}`},
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: `Error reading file: ${error.message}` },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Register user-to-user operations with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
// Register specific handlers for user-to-user operations
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.RESET_DATABASE, UserToUserOperations.handleResetDatabase);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, UserToUserOperations.handleSendAnnouncement);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_DEPARTMENT, UserToUserOperations.handleClearDepartment);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.IS_BACKUP_CREATED, UserToUserOperations.handleIsBackupCreated);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup);
|
||||
|
||||
// Register common operations inherited from the base class
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
const completeMessage = messages[i];
|
||||
if (completeMessage) {
|
||||
this.processCompleteMessage(completeMessage);
|
||||
await this.processCompleteMessage(completeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
}
|
||||
|
||||
private processCompleteMessage(completeMessage: string): void {
|
||||
private async processCompleteMessage(completeMessage: string): Promise<void> {
|
||||
const [headerJson, chunkContent] = completeMessage.split('|');
|
||||
const header = JSON.parse(headerJson);
|
||||
|
||||
@@ -142,12 +142,12 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
|
||||
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
|
||||
const fullMessage = this.chunkBuffers[header.messageId].join('');
|
||||
this.handleIncomingMessage(fullMessage);
|
||||
await this.handleIncomingMessage(fullMessage);
|
||||
delete this.chunkBuffers[header.messageId];
|
||||
}
|
||||
}
|
||||
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
async handleIncomingMessage(incomingMessage: string): Promise<void> {
|
||||
let messageToProcess = incomingMessage;
|
||||
if (this.aesKey && this.aesIv) {
|
||||
messageToProcess = this.decryptWithAes(incomingMessage);
|
||||
@@ -155,7 +155,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
messageToProcess = this.decryptWithRsa(incomingMessage);
|
||||
}
|
||||
|
||||
const result = this.operationHandler.handleOperation(messageToProcess);
|
||||
const result = await this.operationHandler.handleOperation(messageToProcess);
|
||||
|
||||
if (result.operationCode === operationCodes.SET_AES_KEY) {
|
||||
this.isAesKeySetFlag = true;
|
||||
@@ -176,6 +176,8 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
|
||||
getHandlerResult(): ParsedMessage | null {
|
||||
return this.handlerResult;
|
||||
const message = this.handlerResult;
|
||||
this.handlerResult = null;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
const completeMessage = messages[i];
|
||||
if (completeMessage) {
|
||||
this.processCompleteMessage(completeMessage);
|
||||
await this.processCompleteMessage(completeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
}
|
||||
|
||||
private processCompleteMessage(completeMessage: string): void {
|
||||
private async processCompleteMessage(completeMessage: string): Promise<void> {
|
||||
const [headerJson, chunkContent] = completeMessage.split('|');
|
||||
const header = JSON.parse(headerJson);
|
||||
|
||||
@@ -126,7 +126,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
|
||||
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
|
||||
const fullMessage = this.chunkBuffers[header.messageId].join('');
|
||||
this.handleIncomingMessage(fullMessage);
|
||||
await this.handleIncomingMessage(fullMessage);
|
||||
delete this.chunkBuffers[header.messageId];
|
||||
}
|
||||
}
|
||||
@@ -169,14 +169,14 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
|
||||
// Handle incoming message (decrypt with AES if available)
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
async handleIncomingMessage(incomingMessage: string): Promise<void> {
|
||||
let messageToProcess = incomingMessage;
|
||||
|
||||
if (this.aesKey && this.aesIv) {
|
||||
messageToProcess = this.decryptWithAes(incomingMessage);
|
||||
}
|
||||
|
||||
this.handlerResult = this.operationHandler.handleOperation(messageToProcess);
|
||||
this.handlerResult = await this.operationHandler.handleOperation(messageToProcess);
|
||||
}
|
||||
|
||||
// Write message to socket
|
||||
|
||||
@@ -12,8 +12,8 @@ export class UdpSocketCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
|
||||
// Handle incoming message (no decryption needed for UDP)
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
this.handlerResult = this.operationHandler.handleOperation(incomingMessage);
|
||||
async handleIncomingMessage(incomingMessage: string): Promise<void> {
|
||||
this.handlerResult = await this.operationHandler.handleOperation(incomingMessage);
|
||||
}
|
||||
|
||||
// Send plain message over UDP (metaInfo as JSON and fileContent as Buffer)
|
||||
|
||||
@@ -14,6 +14,7 @@ export class TcpServer {
|
||||
private readonly operationHandler: OperationHandler;
|
||||
private readonly port: number;
|
||||
private readonly host: string;
|
||||
private clientQueues: Map<string, Promise<void>> = new Map();
|
||||
|
||||
constructor(host: string, port: number) {
|
||||
this.connectionManager = new ConnectionManager();
|
||||
@@ -25,21 +26,14 @@ export class TcpServer {
|
||||
this.operationHandler.loadPlugin(new UserToUserOperations());
|
||||
}
|
||||
|
||||
// Unified logging function
|
||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
||||
const prefix = '[TcpServer]';
|
||||
if (level === 'error') {
|
||||
console.error(`${prefix} ${message}`);
|
||||
} else {
|
||||
console.log(`${prefix} ${message}`);
|
||||
}
|
||||
console[level === 'error' ? 'error' : 'log'](`${prefix} ${message}`);
|
||||
}
|
||||
|
||||
// Start the TCP server
|
||||
public start(): void {
|
||||
const tcpServer = net.createServer();
|
||||
|
||||
// Handle incoming connections
|
||||
tcpServer.on('connection', (socket: Socket) => {
|
||||
const ip = socket.remoteAddress || 'unknown';
|
||||
const port = socket.remotePort || 0;
|
||||
@@ -49,61 +43,66 @@ export class TcpServer {
|
||||
|
||||
const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler);
|
||||
this.connectionManager.addConnection(ip, port, tcpCommunicator);
|
||||
|
||||
// Generate RSA key pair and start key exchange
|
||||
tcpCommunicator.generateKeyPair();
|
||||
|
||||
tcpCommunicator.sendPublicKey()
|
||||
.then(() => tcpCommunicator.sendAesKey())
|
||||
.catch(err => {
|
||||
this.log(`Error during key exchange with client ${clientId}: ${err}`, 'error');
|
||||
socket.end(); // Close the connection in case of any error
|
||||
socket.end();
|
||||
});
|
||||
|
||||
// Handle incoming data in chunks
|
||||
socket.on('data', async (data: Buffer) => {
|
||||
this.log(`Data received from client ${clientId}`);
|
||||
await this.handleData(data, ip, port);
|
||||
this.clientQueues.set(clientId, Promise.resolve());
|
||||
|
||||
socket.on('data', (data: Buffer) => {
|
||||
this.queueClientDataProcessing(data, ip, port);
|
||||
});
|
||||
|
||||
// Handle client disconnect
|
||||
socket.on('end', () => {
|
||||
this.log(`Client disconnected: ${clientId}`);
|
||||
this.connectionManager.removeCommunicator(ip, port);
|
||||
this.clientQueues.delete(clientId);
|
||||
});
|
||||
|
||||
// Handle socket errors
|
||||
socket.on('error', (err: Error) => {
|
||||
this.log(`Error from client ${clientId}: ${err.message}`, 'error');
|
||||
this.connectionManager.removeCommunicator(ip, port);
|
||||
this.clientQueues.delete(clientId);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle server errors
|
||||
tcpServer.on('error', (err: Error) => {
|
||||
this.log(`TCP server error: ${err.message}`, 'error');
|
||||
});
|
||||
|
||||
// Start listening for connections
|
||||
tcpServer.listen(this.port, this.host, () => {
|
||||
this.log(`TCP server listening on ${this.host}:${this.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle incoming data from a client
|
||||
private queueClientDataProcessing(data: Buffer, ip: string, port: number): void {
|
||||
const clientId = `${ip}:${port}`;
|
||||
const clientQueue = this.clientQueues.get(clientId) || Promise.resolve();
|
||||
|
||||
this.clientQueues.set(
|
||||
clientId,
|
||||
clientQueue.then(() => this.handleData(data, ip, port)).catch(error => {
|
||||
this.log(`Error handling data for ${clientId}: ${error}`, 'error');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async handleData(data: Buffer, ip: string, port: number): Promise<void> {
|
||||
const clientId = `${ip}:${port}`;
|
||||
|
||||
// Retrieve the communicator associated with this connection
|
||||
const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator;
|
||||
if (!communicator) {
|
||||
this.log(`No communicator found for ${clientId}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle incoming chunked message via communicator
|
||||
await communicator.handleIncomingChunk(data);
|
||||
|
||||
// Fetch and process result if available
|
||||
// Check if message is complete before fetching result
|
||||
const handlerResult = communicator.getHandlerResult();
|
||||
if (handlerResult) {
|
||||
try {
|
||||
|
||||
@@ -50,7 +50,7 @@ export class UdpServer {
|
||||
const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler);
|
||||
|
||||
// Process the incoming message using the communicator
|
||||
communicator.handleIncomingMessage(msg.toString());
|
||||
await communicator.handleIncomingMessage(msg.toString());
|
||||
|
||||
const communicatorResult = communicator.getHandlerResult();
|
||||
if (communicatorResult) {
|
||||
|
||||
Generated
+1205
-1064
File diff suppressed because it is too large
Load Diff
+8
-2
@@ -15,6 +15,7 @@
|
||||
"author": "Cerbu Andrei - Mihnea",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"check-disk-space": "^3.4.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"ping": "^0.4.4",
|
||||
"uuid": "^10.0.0"
|
||||
@@ -30,6 +31,7 @@
|
||||
"copyfiles": "^2.4.1",
|
||||
"del-cli": "^5.0.0",
|
||||
"electron": "^33.0.2",
|
||||
"node-disk-info": "^1.3.0",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"config": {
|
||||
@@ -54,14 +56,18 @@
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-zip",
|
||||
"platforms": ["darwin"],
|
||||
"platforms": [
|
||||
"darwin"
|
||||
],
|
||||
"config": {
|
||||
"icon": "../app_icons/icon.icns"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-deb",
|
||||
"platforms": ["linux"],
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"config": {
|
||||
"icon": "../app_icons/icon.png"
|
||||
}
|
||||
|
||||
@@ -87,11 +87,10 @@ export class DepartmentSharer {
|
||||
const userIp = user.ip;
|
||||
this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort);
|
||||
|
||||
if (await this.tcpCommunicator.connect()) continue;
|
||||
if (!await this.tcpCommunicator.connect()) continue;
|
||||
|
||||
// First clear the department directory
|
||||
const clearSuccess = await this.clearDepartmentDirectory();
|
||||
if (clearSuccess) {
|
||||
// First clear the department directory;
|
||||
if (await this.clearDepartmentDirectory(userName)) {
|
||||
await this.sendFilesToUser(departmentFiles.structure, userName);
|
||||
}
|
||||
|
||||
@@ -108,12 +107,14 @@ export class DepartmentSharer {
|
||||
}
|
||||
|
||||
// Clear the department directory for a user
|
||||
private async clearDepartmentDirectory(): Promise<boolean> {
|
||||
private async clearDepartmentDirectory(userName: string): Promise<boolean> {
|
||||
if(!this.tcpCommunicator) return false;
|
||||
|
||||
if (!await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT)) return false;
|
||||
if (!await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, {userName: userName})) return false;
|
||||
const response = await this.waitForResponse();
|
||||
|
||||
this.log(`${response?.operationCode}: ${response?.metaInfo?.message}`);
|
||||
|
||||
if (!response || response.operationCode !== operationCodes.OK){
|
||||
this.log('Failed to clear the department directory.', 'error');
|
||||
return false;
|
||||
@@ -158,6 +159,8 @@ export class DepartmentSharer {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log(`File sent successfully: ${fileName} to ${userName}`);
|
||||
|
||||
unsentFiles.splice(unsentFiles.indexOf(fileName), 1);
|
||||
await this.tcpCommunicator.disconnect();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,17 @@ export class WindowManager {
|
||||
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
|
||||
@@ -21,8 +32,9 @@ export class WindowManager {
|
||||
message: message,
|
||||
buttons: ['OK'],
|
||||
});
|
||||
this.log(`Alert displayed with message: "${message}"`);
|
||||
} else {
|
||||
console.error('Main window is not available.');
|
||||
this.log('Main window is not available.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,17 +43,17 @@ export class WindowManager {
|
||||
if (this.mainWindow) {
|
||||
try {
|
||||
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`);
|
||||
console.log(`Navigating to: ${destinationPath}`);
|
||||
this.log(`Navigating to: ${destinationPath}`);
|
||||
|
||||
// Load the destination HTML file into the main window
|
||||
await this.mainWindow.loadFile(destinationPath);
|
||||
console.log(`Navigated to ${destination}`);
|
||||
this.log(`Navigated to ${destination}`);
|
||||
} catch (error) {
|
||||
console.error('Error changing content:', error);
|
||||
throw error; // Pass the error back to the render process
|
||||
this.log(`Error changing content: ${error}`, 'error');
|
||||
throw error; // Pass the error back to the render process
|
||||
}
|
||||
} else {
|
||||
console.error('Main window is not available.');
|
||||
this.log('Main window is not available.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,26 +63,26 @@ export class WindowManager {
|
||||
properties: ['openDirectory'], // Only allow selecting directories
|
||||
});
|
||||
|
||||
// If the user cancels, result.filePaths will be an empty array
|
||||
if (result.filePaths && result.filePaths.length > 0) {
|
||||
this.log(`Directory selected: ${result.filePaths[0]}`);
|
||||
return result.filePaths[0]; // Return the selected directory path
|
||||
} else {
|
||||
console.log('No directory selected.');
|
||||
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 {
|
||||
// Use Electron's shell module to show the file in the explorer
|
||||
shell.showItemInFolder(filePath);
|
||||
console.log(`Opened file explorer for: ${filePath}`);
|
||||
this.log(`Opened file explorer for: ${filePath}`);
|
||||
} catch (error: any) {
|
||||
console.error(`Error showing file in explorer: ${error.message}`);
|
||||
this.log(`Error showing file in explorer: ${error.message}`, 'error');
|
||||
}
|
||||
} else {
|
||||
console.error('File path is undefined or does not exist.');
|
||||
this.log('File path is undefined or does not exist.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +96,10 @@ export class WindowManager {
|
||||
});
|
||||
|
||||
if (result.filePaths && result.filePaths.length > 0) {
|
||||
this.log(`File selected: ${result.filePaths[0]}`);
|
||||
return result.filePaths[0]; // Return the selected file path
|
||||
} else {
|
||||
console.log('No file selected.');
|
||||
this.log('No file selected.');
|
||||
return undefined; // Return undefined if no file was selected
|
||||
}
|
||||
}
|
||||
@@ -94,15 +107,14 @@ export class WindowManager {
|
||||
// Method to display an announcement in a new window
|
||||
async displayAnnouncement(): Promise<void> {
|
||||
if (this.announcementWindow) {
|
||||
// If the window is already open, focus it
|
||||
this.announcementWindow.focus();
|
||||
this.log('Announcement window focused.');
|
||||
return;
|
||||
}
|
||||
|
||||
const mainScreen = require('electron').screen.getPrimaryDisplay();
|
||||
const { width, height } = mainScreen.size;
|
||||
|
||||
// Initialize the announcement window
|
||||
this.announcementWindow = new BrowserWindow({
|
||||
width: width / 3,
|
||||
height: height / 2,
|
||||
@@ -116,20 +128,22 @@ export class WindowManager {
|
||||
});
|
||||
|
||||
this.announcementWindow.removeMenu();
|
||||
|
||||
// Load the announcement page
|
||||
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; // Clean up the reference
|
||||
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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationHandler } from './operation_handler';
|
||||
import { OperationPlugin } from './operation_plugin';
|
||||
|
||||
export abstract class OperationBase implements OperationPlugin {
|
||||
// Shared operation codes
|
||||
public static readonly operationCodes = {
|
||||
OK: 'OK',
|
||||
ERR: 'ERR',
|
||||
END: 'END',
|
||||
};
|
||||
|
||||
// Default handler for OK operation
|
||||
public static handleOk(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
return parsedMessage; // Typically, you would just acknowledge with OK, returning as-is
|
||||
}
|
||||
|
||||
// Default handler for ERR operation
|
||||
public static handleErr(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
return parsedMessage; // Typically, you would log the error and return
|
||||
}
|
||||
|
||||
// Default handler for END operation
|
||||
public static handleEnd(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
return {
|
||||
operationCode: OperationBase.operationCodes.END,
|
||||
metaInfo: { message: 'Connection ended.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Register the common OK, ERR, and END handlers
|
||||
public static registerCommonOperations(operationHandler: OperationHandler): void {
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.OK, OperationBase.handleOk);
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.ERR, OperationBase.handleErr);
|
||||
operationHandler.registerHandler(OperationBase.operationCodes.END, OperationBase.handleEnd);
|
||||
}
|
||||
|
||||
// Abstract register method that will be implemented by subclasses
|
||||
public abstract register(operationHandler: OperationHandler): void;
|
||||
}
|
||||
@@ -2,14 +2,14 @@
|
||||
import { ParsedMessage, MessageHandler } from '../message_handler';
|
||||
import { OperationPlugin } from './operation_plugin';
|
||||
|
||||
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => ParsedMessage;
|
||||
// Define handler function type to return Promise<ParsedMessage>
|
||||
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => Promise<ParsedMessage>;
|
||||
|
||||
export class OperationHandler {
|
||||
private static instance: OperationHandler;
|
||||
private handlers: { [operationCode: string]: OperationHandlerFunction } = {};
|
||||
|
||||
private constructor() {
|
||||
// Register only the unknown command handler on initialization
|
||||
this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand);
|
||||
}
|
||||
|
||||
@@ -26,25 +26,24 @@ export class OperationHandler {
|
||||
this.handlers[operationCode] = handler;
|
||||
}
|
||||
|
||||
// Handle operation request
|
||||
public handleOperation(rawMessage: string): ParsedMessage {
|
||||
// Parse and validate the message
|
||||
// Handle operation request asynchronously
|
||||
public async handleOperation(rawMessage: string): Promise<ParsedMessage> {
|
||||
const parsedMessage = MessageHandler.parseMessage(rawMessage);
|
||||
if (!parsedMessage || !MessageHandler.validateMessage(parsedMessage)) {
|
||||
return this.handleUnknownCommand(parsedMessage);
|
||||
}
|
||||
|
||||
// Dispatch the handler for the given operation code
|
||||
// Retrieve the handler for the operation code and invoke it asynchronously
|
||||
const handler = this.handlers[parsedMessage.operationCode];
|
||||
if (handler) {
|
||||
return handler(parsedMessage);
|
||||
return await handler(parsedMessage);
|
||||
} else {
|
||||
return this.handleUnknownCommand(parsedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Default handler for unknown commands
|
||||
private handleUnknownCommand(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
private async handleUnknownCommand(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
return {
|
||||
operationCode: 'UNKNOWN_COMMAND',
|
||||
metaInfo: { message: 'Unknown command received.' },
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
import os from 'node:os';
|
||||
import {OperationPlugin} from "../operations_base/operation_plugin";
|
||||
|
||||
export class GeneralOperations extends OperationBase {
|
||||
export class GeneralOperations implements OperationPlugin {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
|
||||
OK: 'OK',
|
||||
ERR: 'ERR',
|
||||
END: 'END',
|
||||
HEARTBEAT: 'HEARTBEAT',
|
||||
ALIVE: 'ALIVE',
|
||||
SET_PUBLIC_KEY: 'SET_PUBLIC_KEY',
|
||||
SET_AES_KEY: 'SET_AES_KEY',
|
||||
};
|
||||
|
||||
// Handle heartbeat operation
|
||||
public static handleHeartbeat(): ParsedMessage {
|
||||
// Handle heartbeat operation asynchronously
|
||||
public static async handleHeartbeat(): Promise<ParsedMessage> {
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
let ipAddress = 'Unknown';
|
||||
|
||||
for (const iface of Object.values(networkInterfaces)) {
|
||||
// @ts-ignore
|
||||
for (const address of iface) {
|
||||
for (const address of iface!) {
|
||||
if (address.family === 'IPv4' && !address.internal) {
|
||||
ipAddress = address.address;
|
||||
break;
|
||||
@@ -34,8 +35,8 @@ export class GeneralOperations extends OperationBase {
|
||||
};
|
||||
}
|
||||
|
||||
// Handle public key exchange
|
||||
public static handlePublicKey(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Handle public key exchange asynchronously
|
||||
public static async handlePublicKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
const clientPublicKey = parsedMessage.metaInfo?.publicKey;
|
||||
if (clientPublicKey) {
|
||||
return {
|
||||
@@ -50,14 +51,14 @@ export class GeneralOperations extends OperationBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle AES key exchange
|
||||
public static handleAESKey(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Handle AES key exchange asynchronously
|
||||
public static async handleAESKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
const aesKey = parsedMessage.metaInfo?.aesKey;
|
||||
const aesIv = parsedMessage.metaInfo?.aesIv;
|
||||
if (aesKey && aesIv) {
|
||||
return {
|
||||
operationCode: GeneralOperations.operationCodes.SET_AES_KEY,
|
||||
metaInfo: { aesKey: aesKey, aesIv: aesIv },
|
||||
metaInfo: { aesKey, aesIv },
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -67,14 +68,22 @@ export class GeneralOperations extends OperationBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Default async handler for OK operation
|
||||
public static async handleOk(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
return parsedMessage; // Acknowledge with OK, returning as-is
|
||||
}
|
||||
|
||||
// Default async handler for ERR operation
|
||||
public static async handleErr(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
return parsedMessage; // Log the error and return
|
||||
}
|
||||
|
||||
// Register general operations with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
// Register specific handlers for the general operations
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.HEARTBEAT, GeneralOperations.handleHeartbeat);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey); // Register AES key handler
|
||||
|
||||
// Register common operations inherited from the base class
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.OK, GeneralOperations.handleOk);
|
||||
operationHandler.registerHandler(GeneralOperations.operationCodes.ERR, GeneralOperations.handleErr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { ParsedMessage } from '../message_handler';
|
||||
import { OperationBase } from '../operations_base/operation_base';
|
||||
import { OperationHandler } from '../operations_base/operation_handler';
|
||||
import {operationCodes} from "../operation_codes";
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import checkDiskSpace from "check-disk-space";
|
||||
import { JsonManager } from '../../helpers/json_manager';
|
||||
import {OperationPlugin} from "../operations_base/operation_plugin";
|
||||
|
||||
const LOCK_FILE_EXTENSION = '.lock';
|
||||
|
||||
export class UserToUserOperations extends OperationBase {
|
||||
export class UserToUserOperations implements OperationPlugin {
|
||||
public static readonly operationCodes = {
|
||||
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
|
||||
SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT',
|
||||
GET_USER_INFORMATION: 'GET_USER_INFORMATION',
|
||||
RESET_DATABASE: 'RESET_DATABASE',
|
||||
BACKUP_FILE: 'BACKUP_FILE',
|
||||
CLEAR_BACKUP: 'CLEAR_BACKUP',
|
||||
SHARE_FILE: 'SHARE_FILE',
|
||||
@@ -24,429 +22,149 @@ export class UserToUserOperations extends OperationBase {
|
||||
DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE',
|
||||
};
|
||||
|
||||
// Utility function to pause execution (sleep)
|
||||
static sleep(ms: number): void {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < ms) {
|
||||
// Busy wait loop (not optimal but fine for this short duration)
|
||||
}
|
||||
}
|
||||
|
||||
// Read JSON file with a lock mechanism
|
||||
static readJsonSync(filePath: string): any {
|
||||
const lockFilePath = `${filePath}.${LOCK_FILE_EXTENSION}`;
|
||||
const absolutePath = path.resolve(filePath);
|
||||
|
||||
private static async hasEnoughDiskSpace(directory: string, requiredPercentage: number = 25): Promise<boolean> {
|
||||
try {
|
||||
// Loop until the lock file is removed by another process
|
||||
while (fs.existsSync(lockFilePath)) {
|
||||
console.log(`Waiting for lock file to be released: ${lockFilePath}`);
|
||||
UserToUserOperations.sleep(100);
|
||||
}
|
||||
|
||||
// Create lock file to signal this process is working on the file
|
||||
fs.writeFileSync(lockFilePath, ''); // Create the lock file
|
||||
|
||||
// Read and parse the JSON file
|
||||
const fileContents = fs.readFileSync(absolutePath, 'utf-8');
|
||||
const parsedJson = JSON.parse(fileContents);
|
||||
|
||||
// Once processing is done, delete the lock file
|
||||
fs.unlinkSync(lockFilePath); // Remove the lock file
|
||||
|
||||
return parsedJson; // Return the parsed JSON data
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing JSON from ${filePath}:`, error);
|
||||
|
||||
// Ensure the lock file is removed even in case of an error
|
||||
if (fs.existsSync(lockFilePath)) {
|
||||
fs.unlinkSync(lockFilePath);
|
||||
}
|
||||
|
||||
return null; // Return null or throw error based on preference
|
||||
}
|
||||
}
|
||||
|
||||
static writeJsonSync(filePath: string, data: any): boolean {
|
||||
const lockFilePath = `${filePath}.${LOCK_FILE_EXTENSION}`;
|
||||
const absolutePath = path.resolve(filePath);
|
||||
|
||||
try {
|
||||
// Loop until the lock file is removed by another process
|
||||
while (fs.existsSync(lockFilePath)) {
|
||||
console.log(`Waiting for lock file to be released: ${lockFilePath}`);
|
||||
UserToUserOperations.sleep(100); // Use the sleep utility to pause
|
||||
}
|
||||
|
||||
// Create lock file to signal this process is working on the file
|
||||
fs.writeFileSync(lockFilePath, ''); // Create the lock file
|
||||
|
||||
// Write the data to the JSON file
|
||||
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
console.log(`Data written successfully to ${absolutePath}`);
|
||||
|
||||
// Once processing is done, delete the lock file
|
||||
fs.unlinkSync(lockFilePath); // Remove the lock file
|
||||
|
||||
return true; // Indicate successful write
|
||||
} catch (error) {
|
||||
console.error(`Error writing JSON to ${filePath}:`, error);
|
||||
|
||||
// Ensure the lock file is removed even in case of an error
|
||||
if (fs.existsSync(lockFilePath)) {
|
||||
fs.unlinkSync(lockFilePath);
|
||||
}
|
||||
|
||||
return false; // Indicate failure
|
||||
}
|
||||
}
|
||||
|
||||
private static hasEnoughDiskSpace(directory: string, requiredPercentage: number): boolean {
|
||||
try {
|
||||
let availableSpace = 0;
|
||||
let totalSpace = 0;
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Windows
|
||||
const output = execSync(`wmic logicaldisk where "DeviceID='${directory[0]}:'" get FreeSpace,Size`).toString();
|
||||
const lines = output.trim().split('\n');
|
||||
const [freeSpaceStr, totalSpaceStr] = lines[1].trim().split(/\s+/);
|
||||
availableSpace = parseInt(freeSpaceStr, 10); // Available space in bytes
|
||||
totalSpace = parseInt(totalSpaceStr, 10); // Total space in bytes
|
||||
} else {
|
||||
// Unix-based (Linux/macOS)
|
||||
const output = execSync(`df -k "${directory}"`).toString();
|
||||
const lines = output.trim().split('\n');
|
||||
const parts = lines[lines.length - 1].split(/\s+/);
|
||||
const availableSpaceInKb = parseInt(parts[3], 10); // Available space in KB
|
||||
const totalSpaceInKb = parseInt(parts[1], 10); // Total space in KB
|
||||
availableSpace = availableSpaceInKb * 1024;
|
||||
totalSpace = totalSpaceInKb * 1024;
|
||||
}
|
||||
|
||||
// Calculate available space as a percentage of the total space
|
||||
const diskInfo = await checkDiskSpace(directory);
|
||||
const availableSpace = diskInfo.free;
|
||||
const totalSpace = diskInfo.size;
|
||||
const availablePercentage = (availableSpace / totalSpace) * 100;
|
||||
|
||||
// Return true if the available percentage is greater than or equal to the required percentage
|
||||
return availablePercentage >= requiredPercentage;
|
||||
} catch (error) {
|
||||
console.error(`Error checking disk space: ${error}`);
|
||||
return false; // Return false if there's an error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static handleResetDatabase(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Path to the application.json file
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
|
||||
public static async handleSendAnnouncement(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.message) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing announcement message.' }};
|
||||
}
|
||||
|
||||
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
|
||||
try {
|
||||
// Read the existing data from application.json
|
||||
const appData = UserToUserOperations.readJsonSync(pathToApplicationJson) || {};
|
||||
|
||||
// Update the reset_application_preferences field to true
|
||||
appData.reset_application_preferences = true;
|
||||
|
||||
// Write the updated data back to application.json
|
||||
const success = UserToUserOperations.writeJsonSync(pathToApplicationJson, appData);
|
||||
|
||||
if (success) {
|
||||
console.log(`Application preferences reset successfully.`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: 'Application preferences reset successfully.' },
|
||||
};
|
||||
} else {
|
||||
throw new Error("Failed to write to application.json");
|
||||
}
|
||||
await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message);
|
||||
console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error resetting application preferences: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error resetting application preferences: ${error.message}` },
|
||||
};
|
||||
console.error(`Error saving announcement: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static handleSendAnnouncement(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure the message is available in metaInfo
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.message) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing announcement message in meta information.' },
|
||||
};
|
||||
}
|
||||
|
||||
const announcementMessage = parsedMessage.metaInfo.message;
|
||||
|
||||
// Path to the application.json file
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
|
||||
public static async handleGetUserInformation(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'));
|
||||
try {
|
||||
// Read the existing data from application.json
|
||||
const appData = UserToUserOperations.readJsonSync(pathToApplicationJson) || {};
|
||||
|
||||
// Update the announcement field with the new message
|
||||
appData.announcement = announcementMessage;
|
||||
|
||||
// Write the updated data back to application.json
|
||||
fs.writeFileSync(pathToApplicationJson, JSON.stringify(appData, null, 2), 'utf-8');
|
||||
|
||||
console.log(`Announcement message saved successfully: ${announcementMessage}`);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: 'Announcement message saved successfully.' },
|
||||
};
|
||||
const userInfo = await jsonManager.readValue('user_info');
|
||||
return { operationCode: operationCodes.OK, metaInfo: userInfo };
|
||||
} catch (error: any) {
|
||||
console.error(`Error saving announcement message: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error saving announcement message: ${error.message}` },
|
||||
};
|
||||
console.error(`Error fetching user info: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Error fetching user info' }};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle user information retrieval
|
||||
public static handleGetUserInformation(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
const pathToUserJson = path.join(__dirname, '..', '..', 'json_files', 'userConfig.json');
|
||||
const userInfo = UserToUserOperations.readJsonSync(pathToUserJson);
|
||||
|
||||
if (userInfo) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: userInfo.user_info,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Error fetching user information.' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle file reception and saving
|
||||
public static handleBackupFile(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure metaInfo and fileContent are available
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing file content or meta information.' },
|
||||
};
|
||||
public static async handleBackupFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing file or user information.' }};
|
||||
}
|
||||
|
||||
const { userName, relativeFilePath } = parsedMessage.metaInfo;
|
||||
|
||||
if (!userName || !relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name or file path information.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Base directory where backups will be stored
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
|
||||
// Full path where the file will be stored (under the user's directory)
|
||||
const fullFilePath = path.join(baseBackupDir, userName, relativeFilePath);
|
||||
const fullFilePath = path.join(__dirname, '..', '..', 'backups', userName, relativeFilePath);
|
||||
|
||||
try {
|
||||
// Check if there is enough disk space
|
||||
const requiredPercentage = 25;
|
||||
if (!UserToUserOperations.hasEnoughDiskSpace(baseBackupDir, requiredPercentage)) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Insufficient disk space for backup.' },
|
||||
};
|
||||
if (!await UserToUserOperations.hasEnoughDiskSpace(path.dirname(fullFilePath), 25)) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo:
|
||||
{ message: 'Insufficient disk space.' }};
|
||||
}
|
||||
|
||||
// Ensure the directory structure exists (create directories if they don't exist)
|
||||
const dirPath = path.dirname(fullFilePath);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
await fs.mkdir(path.dirname(fullFilePath), { recursive: true });
|
||||
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer);
|
||||
console.log(`File saved: ${fullFilePath}`);
|
||||
|
||||
// Write the file content to the correct path
|
||||
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
|
||||
|
||||
console.log(`File saved successfully: ${fullFilePath}`);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `File saved successfully: ${relativeFilePath}` },
|
||||
};
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: `File saved: ${relativeFilePath}` }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error saving file: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error saving file: ${error.message}` },
|
||||
};
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error saving file: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle clearing all backups for a user
|
||||
public static handleClearBackup(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure the userName is available in metaInfo
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name in meta information.' },
|
||||
};
|
||||
public static async handleClearBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }};
|
||||
}
|
||||
|
||||
const { userName } = parsedMessage.metaInfo;
|
||||
|
||||
// Base directory where backups are stored
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
const userBackupDir = path.join(baseBackupDir, userName);
|
||||
|
||||
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.userName);
|
||||
try {
|
||||
// Check if the user's backup directory exists
|
||||
if (fs.existsSync(userBackupDir)) {
|
||||
// Recursively delete the user's backup directory
|
||||
fs.rmSync(userBackupDir, { recursive: true, force: true });
|
||||
console.log(`Backup cleared successfully for user: ${userName}`);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `Backup cleared successfully for user: ${userName}` },
|
||||
};
|
||||
} else {
|
||||
console.warn(`Backup directory not found for user: ${userName}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Backup directory not found for user: ${userName}` },
|
||||
};
|
||||
}
|
||||
await fs.rm(userBackupDir, { recursive: true, force: true });
|
||||
console.log(`Backup cleared: ${userBackupDir}`);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: `Backup cleared for ${parsedMessage.metaInfo.userName}` }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error clearing backup for user: ${userName} - ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error clearing backup for user: ${userName}` },
|
||||
};
|
||||
console.error(`Error clearing backup: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing backup: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle sharing file operation
|
||||
public static handleShareFile(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure metaInfo and fileContent are available
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing file content or meta information.' },
|
||||
};
|
||||
public static async handleShareFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' }};
|
||||
}
|
||||
|
||||
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
|
||||
const { userName, relativeFilePath } = parsedMessage.metaInfo;
|
||||
|
||||
if (!userName || !relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name or file path information.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Path to the application.json to read the shareDirectory field
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
|
||||
|
||||
// Corrected the condition
|
||||
if (!appInfo || !appInfo.shareDirectory) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Error retrieving share directory from application.json.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Get the share directory path
|
||||
const shareDirectory = appInfo.shareDirectory.path;
|
||||
|
||||
// Full path where the file will be stored (under the user's directory in the shared folder)
|
||||
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath);
|
||||
|
||||
try {
|
||||
// Ensure the directory structure exists (create directories if they don't exist)
|
||||
const dirPath = path.dirname(fullFilePath);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
const appInfo = await jsonManager.readValue('shareDirectory');
|
||||
const shareDirectory = appInfo?.path || '';
|
||||
|
||||
if (!shareDirectory) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Share directory missing.' }};
|
||||
}
|
||||
|
||||
// Write the file content to the correct path
|
||||
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
|
||||
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath);
|
||||
await fs.mkdir(path.dirname(fullFilePath), { recursive: true });
|
||||
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer);
|
||||
console.log(`File shared: ${fullFilePath}`);
|
||||
|
||||
console.log(`File shared successfully: ${fullFilePath}`);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
|
||||
};
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: `File shared: ${relativeFilePath}` }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error sharing file: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error sharing file: ${error.message}` },
|
||||
};
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error sharing file: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
public static handleClearDepartment(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
// Ensure the userName is available in metaInfo
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name in meta information.' },
|
||||
};
|
||||
public static async handleClearDepartment(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.userName) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }};
|
||||
}
|
||||
|
||||
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
|
||||
const { userName } = parsedMessage.metaInfo;
|
||||
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
|
||||
|
||||
// Corrected the condition
|
||||
if (!appInfo || !appInfo.departmentDirectory) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Error retrieving share directory from application.json.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Get the share directory path
|
||||
const baseDepartmentDir = appInfo.departmentDirectory.path;
|
||||
const userDepartmentDir = path.join(baseDepartmentDir, '..', 'DEPARTMENT_FILES', userName);
|
||||
|
||||
try {
|
||||
// Check if the user's backup directory exists
|
||||
if (fs.existsSync(userDepartmentDir)) {
|
||||
// Recursively delete the user's backup directory
|
||||
fs.rmSync(userDepartmentDir, { recursive: true, force: true });
|
||||
console.log(`Backup cleared successfully for user: ${userName}`);
|
||||
const appInfo = await jsonManager.readValue('departmentDirectory');
|
||||
const departmentDir = appInfo?.path || '';
|
||||
const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `Backup cleared successfully for user: ${userName}`},
|
||||
};
|
||||
} else {
|
||||
console.warn(`Backup directory not found for user: ${userName}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Backup directory not found for user: ${userName}`},
|
||||
};
|
||||
try {
|
||||
await fs.access(userDepartmentDir)
|
||||
}catch(ex: any){
|
||||
return { operationCode: operationCodes.OK};
|
||||
}
|
||||
|
||||
await fs.rm(userDepartmentDir, { recursive: true, force: true });
|
||||
console.log(`Department backup cleared: ${userDepartmentDir}`);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { message: `Department backup cleared for ${userName}` }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error clearing backup for user: ${userName} - ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error clearing backup for user: ${userName}`},
|
||||
};
|
||||
console.error(`Error clearing department: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing department: ${error.message}` }};
|
||||
}
|
||||
}
|
||||
|
||||
public static handleDepartmentFile(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
public static async handleDepartmentFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
// Ensure metaInfo and fileContent are available
|
||||
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing file content or meta information.' },
|
||||
};
|
||||
}
|
||||
@@ -455,130 +173,99 @@ export class UserToUserOperations extends OperationBase {
|
||||
|
||||
if (!userName || !relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name or file path information.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Path to the application.json to read the shareDirectory field
|
||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
|
||||
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
|
||||
const jsonManager = new JsonManager(pathToApplicationJson);
|
||||
|
||||
// Corrected the condition
|
||||
if (!appInfo || !appInfo.departmentDirectory) {
|
||||
// Read application configuration asynchronously
|
||||
let appInfo;
|
||||
try {
|
||||
appInfo = await jsonManager.readValue('departmentDirectory');
|
||||
} catch (error: any) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Error retrieving share directory from application.json.' },
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: `Error reading application config: ${error.message}` },
|
||||
};
|
||||
}
|
||||
|
||||
if (!appInfo || !appInfo.path) {
|
||||
return {
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Error retrieving department directory from application.json.' },
|
||||
};
|
||||
}
|
||||
|
||||
// Get the share directory path
|
||||
const departmentDirectory = appInfo.departmentDirectory.path;
|
||||
|
||||
// Full path where the file will be stored (under the user's directory in the shared folder)
|
||||
const departmentDirectory = appInfo.path;
|
||||
const fullFilePath = path.join(departmentDirectory, '..', 'DEPARTMENT_FILES', userName, relativeFilePath);
|
||||
|
||||
try {
|
||||
// Ensure the directory structure exists (create directories if they don't exist)
|
||||
const dirPath = path.dirname(fullFilePath);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
await fs.mkdir(dirPath, { recursive: true });
|
||||
|
||||
// Write the file content to the correct path
|
||||
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
|
||||
|
||||
console.log(`File shared successfully: ${fullFilePath}`);
|
||||
// Write the file content to the specified path
|
||||
await fs.writeFile(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { message: `File shared successfully: ${relativeFilePath}`},
|
||||
};
|
||||
operationCode: operationCodes.OK,
|
||||
metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error(`Error sharing file: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: {message: `Error sharing file: ${error.message}`},
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: `Error sharing file: ${error.message}` },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a backup has been created for a user
|
||||
public static handleIsBackupCreated(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
if(!parsedMessage.metaInfo) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing metaInfo.' },
|
||||
};
|
||||
public static async handleIsBackupCreated(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.name) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }};
|
||||
}
|
||||
|
||||
const {name} = parsedMessage.metaInfo;
|
||||
if (!name) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing name in meta information.' },
|
||||
};
|
||||
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name);
|
||||
|
||||
try {
|
||||
const exists = await fs.access(userBackupDir).then(() => true).catch(() => false);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { backupExists: exists }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error checking backup: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error checking backup: ${error.message}` }};
|
||||
}
|
||||
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
const userBackupDir = path.join(baseBackupDir, name);
|
||||
|
||||
console.log(userBackupDir);
|
||||
|
||||
const exists = fs.existsSync(userBackupDir);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { backupExists: exists },
|
||||
};
|
||||
}
|
||||
|
||||
// Get the structure of the backup directory for a user
|
||||
public static handleGetBackupStructure(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
if(!parsedMessage.metaInfo) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing metaInfo.' },
|
||||
};
|
||||
public static async handleGetBackupStructure(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.name) {
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }};
|
||||
}
|
||||
|
||||
const {name} = parsedMessage.metaInfo;
|
||||
if (!name) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing name in meta information.' },
|
||||
};
|
||||
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name);
|
||||
|
||||
try {
|
||||
const structure = await UserToUserOperations.buildDirectoryStructure(userBackupDir);
|
||||
return { operationCode: operationCodes.OK, metaInfo: { structure }};
|
||||
} catch (error: any) {
|
||||
console.error(`Error building backup structure: ${error.message}`);
|
||||
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
|
||||
}
|
||||
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
const userBackupDir = path.join(baseBackupDir, name);
|
||||
|
||||
if (!fs.existsSync(userBackupDir)) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { structure: {} }, // Return empty structure if directory doesn't exist
|
||||
};
|
||||
}
|
||||
|
||||
const fileStructure = UserToUserOperations.buildDirectoryStructure(userBackupDir);
|
||||
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
metaInfo: { structure: fileStructure },
|
||||
};
|
||||
}
|
||||
|
||||
// Build the directory structure recursively
|
||||
private static buildDirectoryStructure(directoryPath: string): any {
|
||||
private static async buildDirectoryStructure(directoryPath: string): Promise<any> {
|
||||
const structure: any = {};
|
||||
const files = fs.readdirSync(directoryPath);
|
||||
const files = await fs.readdir(directoryPath);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(directoryPath, file);
|
||||
const stats = fs.statSync(filePath);
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
structure[file] = UserToUserOperations.buildDirectoryStructure(filePath); // Recursive for subdirectories
|
||||
structure[file] = await UserToUserOperations.buildDirectoryStructure(filePath);
|
||||
} else {
|
||||
structure[file] = path.relative(directoryPath, filePath);
|
||||
}
|
||||
@@ -587,67 +274,43 @@ export class UserToUserOperations extends OperationBase {
|
||||
return structure;
|
||||
}
|
||||
|
||||
// Handle file request from backup directory
|
||||
public static handleReqFileFromBackup(parsedMessage: ParsedMessage): ParsedMessage {
|
||||
if(!parsedMessage.metaInfo) {
|
||||
public static async handleReqFileFromBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
|
||||
if (!parsedMessage.metaInfo?.name || !parsedMessage.metaInfo?.relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing metaInfo.' },
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing user name or file path in meta information.' },
|
||||
};
|
||||
}
|
||||
|
||||
const { name, relativeFilePath } = parsedMessage.metaInfo;
|
||||
|
||||
if (!name || !relativeFilePath) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'Missing name or file path in meta information.' },
|
||||
};
|
||||
}
|
||||
|
||||
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
|
||||
const userBackupDir = path.join(baseBackupDir, name);
|
||||
const fullFilePath = path.join(userBackupDir, relativeFilePath);
|
||||
|
||||
if (!fs.existsSync(fullFilePath)) {
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: 'File not found in backup.' },
|
||||
};
|
||||
}
|
||||
const fullFilePath = path.join(__dirname, '..', '..', 'backups', name, relativeFilePath);
|
||||
|
||||
try {
|
||||
const fileContent = fs.readFileSync(fullFilePath, 'base64');
|
||||
const fileContent = await fs.readFile(fullFilePath);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.OK,
|
||||
operationCode: operationCodes.OK,
|
||||
metaInfo: { relativeFilePath },
|
||||
fileContent: Buffer.from(fileContent, 'base64')
|
||||
fileContent,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error(`Error reading file: ${error.message}`);
|
||||
console.error(`Error reading file from backup: ${error.message}`);
|
||||
return {
|
||||
operationCode: UserToUserOperations.operationCodes.ERR,
|
||||
metaInfo: { message: `Error reading file: ${error.message}`},
|
||||
operationCode: operationCodes.ERR,
|
||||
metaInfo: { message: `Error reading file: ${error.message}` },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Register user-to-user operations with the OperationHandler
|
||||
public register(operationHandler: OperationHandler): void {
|
||||
// Register specific handlers for user-to-user operations
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.RESET_DATABASE, UserToUserOperations.handleResetDatabase);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, UserToUserOperations.handleSendAnnouncement);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_DEPARTMENT, UserToUserOperations.handleClearDepartment);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.IS_BACKUP_CREATED, UserToUserOperations.handleIsBackupCreated);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure);
|
||||
operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup);
|
||||
|
||||
// Register common operations inherited from the base class
|
||||
OperationBase.registerCommonOperations(operationHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
const completeMessage = messages[i];
|
||||
if (completeMessage) {
|
||||
this.processCompleteMessage(completeMessage);
|
||||
await this.processCompleteMessage(completeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
}
|
||||
|
||||
private processCompleteMessage(completeMessage: string): void {
|
||||
private async processCompleteMessage(completeMessage: string): Promise<void> {
|
||||
const [headerJson, chunkContent] = completeMessage.split('|');
|
||||
const header = JSON.parse(headerJson);
|
||||
|
||||
@@ -142,12 +142,12 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
|
||||
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
|
||||
const fullMessage = this.chunkBuffers[header.messageId].join('');
|
||||
this.handleIncomingMessage(fullMessage);
|
||||
await this.handleIncomingMessage(fullMessage);
|
||||
delete this.chunkBuffers[header.messageId];
|
||||
}
|
||||
}
|
||||
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
async handleIncomingMessage(incomingMessage: string): Promise<void> {
|
||||
let messageToProcess = incomingMessage;
|
||||
if (this.aesKey && this.aesIv) {
|
||||
messageToProcess = this.decryptWithAes(incomingMessage);
|
||||
@@ -155,7 +155,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
messageToProcess = this.decryptWithRsa(incomingMessage);
|
||||
}
|
||||
|
||||
const result = this.operationHandler.handleOperation(messageToProcess);
|
||||
const result = await this.operationHandler.handleOperation(messageToProcess);
|
||||
|
||||
if (result.operationCode === operationCodes.SET_AES_KEY) {
|
||||
this.isAesKeySetFlag = true;
|
||||
@@ -176,6 +176,8 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
|
||||
getHandlerResult(): ParsedMessage | null {
|
||||
return this.handlerResult;
|
||||
const message = this.handlerResult;
|
||||
this.handlerResult = null;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
const completeMessage = messages[i];
|
||||
if (completeMessage) {
|
||||
this.processCompleteMessage(completeMessage);
|
||||
await this.processCompleteMessage(completeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
}
|
||||
|
||||
private processCompleteMessage(completeMessage: string): void {
|
||||
private async processCompleteMessage(completeMessage: string): Promise<void> {
|
||||
const [headerJson, chunkContent] = completeMessage.split('|');
|
||||
const header = JSON.parse(headerJson);
|
||||
|
||||
@@ -126,7 +126,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
|
||||
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
|
||||
const fullMessage = this.chunkBuffers[header.messageId].join('');
|
||||
this.handleIncomingMessage(fullMessage);
|
||||
await this.handleIncomingMessage(fullMessage);
|
||||
delete this.chunkBuffers[header.messageId];
|
||||
}
|
||||
}
|
||||
@@ -169,14 +169,14 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
|
||||
// Handle incoming message (decrypt with AES if available)
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
async handleIncomingMessage(incomingMessage: string): Promise<void> {
|
||||
let messageToProcess = incomingMessage;
|
||||
|
||||
if (this.aesKey && this.aesIv) {
|
||||
messageToProcess = this.decryptWithAes(incomingMessage);
|
||||
}
|
||||
|
||||
this.handlerResult = this.operationHandler.handleOperation(messageToProcess);
|
||||
this.handlerResult = await this.operationHandler.handleOperation(messageToProcess);
|
||||
}
|
||||
|
||||
// Write message to socket
|
||||
|
||||
@@ -12,8 +12,8 @@ export class UdpSocketCommunicator extends SocketCommunicatorBase {
|
||||
}
|
||||
|
||||
// Handle incoming message (no decryption needed for UDP)
|
||||
handleIncomingMessage(incomingMessage: string): void {
|
||||
this.handlerResult = this.operationHandler.handleOperation(incomingMessage);
|
||||
async handleIncomingMessage(incomingMessage: string): Promise<void> {
|
||||
this.handlerResult = await this.operationHandler.handleOperation(incomingMessage);
|
||||
}
|
||||
|
||||
// Send plain message over UDP (metaInfo as JSON and fileContent as Buffer)
|
||||
|
||||
@@ -14,6 +14,7 @@ export class TcpServer {
|
||||
private readonly operationHandler: OperationHandler;
|
||||
private readonly port: number;
|
||||
private readonly host: string;
|
||||
private clientQueues: Map<string, Promise<void>> = new Map();
|
||||
|
||||
constructor(host: string, port: number) {
|
||||
this.connectionManager = new ConnectionManager();
|
||||
@@ -25,21 +26,14 @@ export class TcpServer {
|
||||
this.operationHandler.loadPlugin(new UserToUserOperations());
|
||||
}
|
||||
|
||||
// Unified logging function
|
||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
||||
const prefix = '[TcpServer]';
|
||||
if (level === 'error') {
|
||||
console.error(`${prefix} ${message}`);
|
||||
} else {
|
||||
console.log(`${prefix} ${message}`);
|
||||
}
|
||||
console[level === 'error' ? 'error' : 'log'](`${prefix} ${message}`);
|
||||
}
|
||||
|
||||
// Start the TCP server
|
||||
public start(): void {
|
||||
const tcpServer = net.createServer();
|
||||
|
||||
// Handle incoming connections
|
||||
tcpServer.on('connection', (socket: Socket) => {
|
||||
const ip = socket.remoteAddress || 'unknown';
|
||||
const port = socket.remotePort || 0;
|
||||
@@ -49,61 +43,66 @@ export class TcpServer {
|
||||
|
||||
const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler);
|
||||
this.connectionManager.addConnection(ip, port, tcpCommunicator);
|
||||
|
||||
// Generate RSA key pair and start key exchange
|
||||
tcpCommunicator.generateKeyPair();
|
||||
|
||||
tcpCommunicator.sendPublicKey()
|
||||
.then(() => tcpCommunicator.sendAesKey())
|
||||
.catch(err => {
|
||||
this.log(`Error during key exchange with client ${clientId}: ${err}`, 'error');
|
||||
socket.end(); // Close the connection in case of any error
|
||||
socket.end();
|
||||
});
|
||||
|
||||
// Handle incoming data in chunks
|
||||
socket.on('data', async (data: Buffer) => {
|
||||
this.log(`Data received from client ${clientId}`);
|
||||
await this.handleData(data, ip, port);
|
||||
this.clientQueues.set(clientId, Promise.resolve());
|
||||
|
||||
socket.on('data', (data: Buffer) => {
|
||||
this.queueClientDataProcessing(data, ip, port);
|
||||
});
|
||||
|
||||
// Handle client disconnect
|
||||
socket.on('end', () => {
|
||||
this.log(`Client disconnected: ${clientId}`);
|
||||
this.connectionManager.removeCommunicator(ip, port);
|
||||
this.clientQueues.delete(clientId);
|
||||
});
|
||||
|
||||
// Handle socket errors
|
||||
socket.on('error', (err: Error) => {
|
||||
this.log(`Error from client ${clientId}: ${err.message}`, 'error');
|
||||
this.connectionManager.removeCommunicator(ip, port);
|
||||
this.clientQueues.delete(clientId);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle server errors
|
||||
tcpServer.on('error', (err: Error) => {
|
||||
this.log(`TCP server error: ${err.message}`, 'error');
|
||||
});
|
||||
|
||||
// Start listening for connections
|
||||
tcpServer.listen(this.port, this.host, () => {
|
||||
this.log(`TCP server listening on ${this.host}:${this.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle incoming data from a client
|
||||
private queueClientDataProcessing(data: Buffer, ip: string, port: number): void {
|
||||
const clientId = `${ip}:${port}`;
|
||||
const clientQueue = this.clientQueues.get(clientId) || Promise.resolve();
|
||||
|
||||
this.clientQueues.set(
|
||||
clientId,
|
||||
clientQueue.then(() => this.handleData(data, ip, port)).catch(error => {
|
||||
this.log(`Error handling data for ${clientId}: ${error}`, 'error');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async handleData(data: Buffer, ip: string, port: number): Promise<void> {
|
||||
const clientId = `${ip}:${port}`;
|
||||
|
||||
// Retrieve the communicator associated with this connection
|
||||
const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator;
|
||||
if (!communicator) {
|
||||
this.log(`No communicator found for ${clientId}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle incoming chunked message via communicator
|
||||
await communicator.handleIncomingChunk(data);
|
||||
|
||||
// Fetch and process result if available
|
||||
// Check if message is complete before fetching result
|
||||
const handlerResult = communicator.getHandlerResult();
|
||||
if (handlerResult) {
|
||||
try {
|
||||
|
||||
@@ -50,7 +50,7 @@ export class UdpServer {
|
||||
const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler);
|
||||
|
||||
// Process the incoming message using the communicator
|
||||
communicator.handleIncomingMessage(msg.toString());
|
||||
await communicator.handleIncomingMessage(msg.toString());
|
||||
|
||||
const communicatorResult = communicator.getHandlerResult();
|
||||
if (communicatorResult) {
|
||||
|
||||
Reference in New Issue
Block a user