SDA-4415: Finalizing the approach on working with files

This commit is contained in:
NguyenTranHoangSym committed 2025-03-28 15:34:38 +07:00
1 parent 4e4223915f
commit 0e75904399
6 files changed
+240 -7

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
import { IFile, IFileOptions } from '../../interfaces/file-helper.interface';
import { Files } from './files';
export type GetLatestModifiedFiles = (
folderPath: string,
options?: IFileOptions,
) => Map<string, IFile>;
export abstract class FileHelperBase {
public abstract getLatestModifiedFiles: GetLatestModifiedFiles;
public createFiles = () => {
return new Files();
};
}
+111
View File
@@ -0,0 +1,111 @@
import * as fs from 'fs';
import path = require('path');
import { isWindowsOS } from '../../../common/env';
import { logger } from '../../../common/logger';
import { IFile, IFiles } from '../../interfaces/file-helper.interface';
import {
LogCategory,
LogUtilities,
Platform,
} from '../../interfaces/reports-handlers.interface';
import { Files } from './files';
import { FileHelperBase, GetLatestModifiedFiles } from './files-base-helper';
export class FileHelper extends FileHelperBase {
private files: IFiles;
constructor() {
super();
this.files = this.createFiles();
}
public get = () => {
return this.files;
};
public set = (files: Files) => {
return (this.files = files);
};
public validateFilename = (filename: string): string => {
return filename?.replace(/[^a-zA-Z0-9/_\.-]/g, '_');
};
public validateFilePath = (path: string) => {
const unixPathRegex = /^\/([^\/]+\/)*([^\/]+)$/;
const windowsPathRegex = /^[a-zA-Z]:\\([^\\]+\\)*([^\\]+)$/;
if (isWindowsOS) {
return windowsPathRegex.test(path);
}
return unixPathRegex.test(path);
};
public unsetFiles = () => {
this.files = this.createFiles();
};
public getLatestModifiedFiles: GetLatestModifiedFiles = (
folderPath: string,
options = {
includeOnly: [''],
type: '',
},
) => {
const logFiles = fs.readdirSync(folderPath);
const now = new Date();
let latestModifiedLogTimestamp = 0;
logFiles?.forEach((file, index) => {
const sanitizedLogname = this.validateFilename(file);
const sanitizedFolderPath = path.normalize(folderPath);
if (!sanitizedLogname || !this.validateFilePath(sanitizedFolderPath)) {
logger.log(
'error',
'file-helper: Log file has a malicious format to be exported',
);
return;
}
const logFilePath = path.join(sanitizedFolderPath, sanitizedLogname);
const logFileStats = fs.statSync(logFilePath);
const currentLogModifiedTimestamp = logFileStats.mtime.getTime();
if (
logFileStats.isFile() &&
options.includeOnly?.some((option) =>
file.toLowerCase().includes(option),
)
) {
const logLastModifiedTimestamp = new Date(logFileStats.mtime);
const timestampDiffInMinutes =
(now.getTime() - logLastModifiedTimestamp.getTime()) /
LogUtilities.ONE_MINUTE;
const modifiedFile: IFile = {
fileName: file,
platform: options.type ?? Platform.IV,
modifiedTimestamp: currentLogModifiedTimestamp,
};
if (
timestampDiffInMinutes <= LogUtilities.LOG_TIMESTAMP_THRESH_HOLD &&
logFileStats.mtime.getTime() > latestModifiedLogTimestamp
) {
this.files.get().set(`${LogCategory.LATEST}_${index}`, modifiedFile);
latestModifiedLogTimestamp = logFileStats.mtime.getTime();
} else if (currentLogModifiedTimestamp > latestModifiedLogTimestamp) {
this.files.get().set(LogCategory.RECENT, modifiedFile);
latestModifiedLogTimestamp = currentLogModifiedTimestamp;
}
}
});
if (this.files.get().has(`${LogCategory.LATEST}_1`)) {
this.files.get().delete(LogCategory.RECENT);
}
return this.files.get();
};
}
export const fileHelper = new FileHelper();
+17
View File
@@ -0,0 +1,17 @@
import { IFile, IFiles } from '../../interfaces/file-helper.interface';
export class Files implements IFiles {
private files = new Map<string, IFile>();
constructor(private _files?: Map<string, IFile>) {
this.set(this._files ?? this.files);
}
public get = () => {
return this.files;
};
public set = (files: Map<string, IFile>) => {
this.files = files;
};
}
@@ -0,0 +1,15 @@
export interface IFile {
fileName: string;
platform: string;
modifiedTimestamp: number;
}
export interface IFileOptions {
includeOnly?: string[];
type?: string;
}
export interface IFiles {
get: () => Map<string, IFile>;
set: (files: Map<string, IFile>) => void;
}
@@ -0,0 +1,18 @@
export enum Platform {
IV = 'IV',
}
export enum LogCategory {
LATEST = 'LATEST',
RECENT = 'RECENT',
C9_TRADER = 'c9trader',
C9_ZUES = 'c9zeus',
}
export enum LogUtilities {
ONE_MINUTE = 60000,
LOG_TIMESTAMP_THRESH_HOLD = 31,
}
export type RetrieveIVLogs = (logsPath: string) => void;
export type RemoveIVLogs = (logsPath: string) => void;
+64 -7
View File
@@ -1,12 +1,19 @@
import * as zip from 'adm-zip';
import { app, BrowserWindow, dialog, shell, WebContents } from 'electron';
import * as fs from 'fs';
import { homedir as getHomedir } from 'os';
import * as path from 'path';
import { ILogs } from '../common/api-interface';
import { isLinux, isMac, isWindowsOS } from '../common/env';
import { i18n } from '../common/i18n';
import { logger } from '../common/logger';
import { fileHelper } from './helpers/file/files-helper';
import {
LogCategory,
RemoveIVLogs,
RetrieveIVLogs,
} from './interfaces/reports-handlers.interface';
/**
* Archives files in the source directory
@@ -65,14 +72,10 @@ let logWebContents: WebContents;
const logTypes: string[] = [];
const receivedLogs: ILogs[] = [];
const validateFilename = (filename: string): string => {
return filename?.replace(/[^a-zA-Z0-9/_\.-]/g, '_');
};
const writeClientLogs = async (retrievedLogs: ILogs[]) => {
const writeLogs = async (retrievedLogs: ILogs[]) => {
for await (const logs of retrievedLogs) {
for (const logFile of logs.logFiles) {
const sanitizedFilename = validateFilename(logFile.filename);
const sanitizedFilename = fileHelper.validateFilename(logFile.filename);
if (!sanitizedFilename) {
continue;
}
@@ -123,7 +126,9 @@ export const packageLogs = async (retrievedLogs: ILogs[]): Promise<void> => {
const timestamp = new Date().getTime();
const destination = app.getPath('downloads') + destPath + timestamp + '.zip';
await writeClientLogs(retrievedLogs);
removeLastIVLogs(logsPath);
extractIVLogs(logsPath);
await writeLogs(retrievedLogs);
generateArchiveForDirectory(
logsPath,
destination,
@@ -278,3 +283,55 @@ const writeDataToFile = async (filePath: string, data: string) => {
writeStream.end();
};
const removeLastIVLogs: RemoveIVLogs = (logsPath: string) => {
const ivLogsList = fileHelper.getLatestModifiedFiles(logsPath, {
includeOnly: [LogCategory.C9_TRADER, LogCategory.C9_ZUES],
});
ivLogsList.forEach((log) => {
const logPath = `${logsPath}/${log.fileName}`;
try {
fs.unlinkSync(logPath);
logger.info(`reports-handler: removed ${log.fileName}`);
} catch (e) {
logger.error(`reports-handler: Error in removing file - ${e}`);
}
});
};
const extractIVLogs: RetrieveIVLogs = (logsPath: string) => {
const ivFolderPath = path.join(
getHomedir(),
'AppData',
'Local',
'Cloud9_Technologies',
'C9Trader',
'log',
);
fileHelper.set(fileHelper.createFiles());
const ivLogsList = fileHelper.getLatestModifiedFiles(ivFolderPath);
if (ivLogsList.size < 1) {
logger.error(`reports-handler: Cannot copy log, logs arent exist`);
} else {
try {
ivLogsList.forEach((log) => {
logger.info(`reports-handler: Start reading logs, ${log.fileName}`);
const ivLog = fs.readFileSync(`${ivFolderPath}/${log.fileName}`);
if (ivLog) {
logger.info(`reports-handler: Log found start copying content`);
fs.writeFileSync(`${logsPath}/${log.fileName}`, ivLog);
} else {
logger.error(`reports-handler: ${log} cannot be found.`);
}
});
fileHelper.unsetFiles();
} catch (e) {
logger.error(`reports-handler: ${e}`);
}
}
};