175 lines
5.3 KiB
TypeScript
175 lines
5.3 KiB
TypeScript
import fs from 'fs'
|
|
import path from 'path'
|
|
import { FileEncryptor } from './file_encryptor'
|
|
import { TcpCommunicator } from './tcp_communicator'
|
|
import { operationCodes } from '../network/operation_codes'
|
|
import { ParsedMessage } from '../network/message_handler'
|
|
import { Database } from '../database/database'
|
|
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
|
|
|
export class BackupManager {
|
|
private fileEncryptor: FileEncryptor | null = null
|
|
private readonly db: Database
|
|
private readonly clientPort: number
|
|
private isBusy: boolean = false
|
|
private intervalId: NodeJS.Timeout | null = null
|
|
private stopRequested: boolean = false
|
|
|
|
constructor(pathToDatabaseFile: string, clientPort: number) {
|
|
this.db = new Database(pathToDatabaseFile)
|
|
this.clientPort = clientPort
|
|
}
|
|
|
|
async start(): Promise<void> {
|
|
this.intervalId = setInterval(async () => {
|
|
if (!this.isBusy || !this.stopRequested) {
|
|
this.isBusy = true
|
|
this.log('Start successfully. Backup files to users.')
|
|
await this.initialize()
|
|
}
|
|
|
|
if (global.gc) {
|
|
global.gc()
|
|
}
|
|
}, 10000) // 10-second interval for testing
|
|
}
|
|
|
|
private async initialize(): Promise<void> {
|
|
this.isBusy = true
|
|
try {
|
|
const data = await this.db.read()
|
|
const userInfo = data.app_config.user_info
|
|
const encryptionKey = data.app_config.encryption_key
|
|
const usersIp = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip)
|
|
const backupDirectoryData = data.local_resources.directory_schemes.backup
|
|
|
|
this.fileEncryptor = new FileEncryptor(encryptionKey.key, encryptionKey.iv)
|
|
|
|
await this.sendFilesToUsers(
|
|
userInfo.name,
|
|
usersIp,
|
|
backupDirectoryData.structure,
|
|
backupDirectoryData.path,
|
|
)
|
|
} catch (error: any) {
|
|
this.log(`Error in initialize process: ${error.message}`, 'error')
|
|
} finally {
|
|
this.log('Backup process completed.')
|
|
this.isBusy = false
|
|
}
|
|
}
|
|
|
|
private encryptFile(filePath: string): string {
|
|
if (!this.fileEncryptor) {
|
|
return filePath
|
|
}
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
this.log(`File not found: ${filePath}`, 'error')
|
|
return ''
|
|
}
|
|
|
|
return this.fileEncryptor.encryptFileToBase64(filePath)
|
|
}
|
|
|
|
private async sendFilesToUsers(
|
|
userName: string,
|
|
usersIp: string[],
|
|
fileStructure: { [key: string]: string },
|
|
backupDirectoryPath: string,
|
|
): Promise<void> {
|
|
let unsentFiles = Object.keys(fileStructure)
|
|
|
|
for (const fileName of unsentFiles) {
|
|
const filePath = fileStructure[fileName]
|
|
const encryptedFileContent = this.encryptFile(filePath)
|
|
|
|
if (!encryptedFileContent) {
|
|
this.log(`Failed to encrypt file: ${fileName}`, 'error')
|
|
continue
|
|
}
|
|
|
|
const relativeFilePath = path.relative(backupDirectoryPath, filePath)
|
|
const metaInfo = { userName, relativeFilePath }
|
|
|
|
for (const ip of usersIp) {
|
|
const tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
|
|
|
|
try {
|
|
await tcpCommunicator.connect()
|
|
this.log(`Connected to ${ip}`)
|
|
|
|
const sendSuccess = await tcpCommunicator.sendMessage(
|
|
operationCodes.BACKUP_FILE,
|
|
metaInfo,
|
|
Buffer.from(encryptedFileContent, 'base64'),
|
|
)
|
|
if (!sendSuccess) {
|
|
throw new Error('Failed to send file content.')
|
|
}
|
|
|
|
const responseReceived = await this.waitForResponse(tcpCommunicator)
|
|
if (!responseReceived) {
|
|
throw new Error('Timeout waiting for the message response.')
|
|
}
|
|
|
|
this.log(`Successfully sent file: ${fileName} to ${ip}`)
|
|
unsentFiles = unsentFiles.filter((f) => f !== fileName)
|
|
break
|
|
} catch (error) {
|
|
this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error')
|
|
} finally {
|
|
await tcpCommunicator.disconnect()
|
|
this.log(`Disconnected from ${ip}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (unsentFiles.length > 0) {
|
|
process.send?.({ type: 'log', message: 'Backup could not be completed for all files' })
|
|
} else {
|
|
process.send?.({ type: 'log', message: 'Backup completed successfully' })
|
|
}
|
|
}
|
|
|
|
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
|
|
return new Promise((resolve) => {
|
|
const idResponseCheck = setInterval(() => {
|
|
if (!tcpCommunicator) return null
|
|
if (tcpCommunicator.hasResponseArrived()) {
|
|
clearInterval(idResponseCheck)
|
|
resolve(tcpCommunicator.getLastResult())
|
|
}
|
|
}, 100)
|
|
})
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
this.stopRequested = true // Signal that stop is requested
|
|
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId)
|
|
this.intervalId = null
|
|
}
|
|
|
|
// Wait for any ongoing process to complete if busy
|
|
while (this.isBusy) {
|
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
}
|
|
|
|
console.log('[BackupManager] Stopped successfully.')
|
|
}
|
|
|
|
// Unified logging function
|
|
private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void {
|
|
const prefix = '[BackupManager]'
|
|
if (level === 'error') {
|
|
console.error(`${prefix} ${message}`)
|
|
} else if (level === 'warn') {
|
|
console.warn(`${prefix} ${message}`)
|
|
} else {
|
|
console.log(`${prefix} ${message}`)
|
|
}
|
|
}
|
|
}
|