From 27e40c1398f7c4c8947a57ec9e8df9736e8b7fcf Mon Sep 17 00:00:00 2001 From: Natallia Date: Tue, 6 Feb 2024 00:14:43 +0100 Subject: [PATCH 1/4] feat: Implement File Manager --- src/commandService/commandService.js | 74 +++++++++++++++ src/constants/errors.js | 4 + src/fileService/fileService.js | 90 +++++++++++++++++++ src/hashService/hashService.js | 26 ++++++ src/helpers/alpabetical-sort.js | 0 src/helpers/get-username.js | 10 +++ src/main.js | 19 ++++ src/navigationService/navigationService.js | 64 +++++++++++++ .../operatingSystemService.js | 50 +++++++++++ src/promptService/prompt-service.js | 24 +++++ src/zipService/zipService.js | 67 ++++++++++++++ 11 files changed, 428 insertions(+) create mode 100644 src/commandService/commandService.js create mode 100644 src/constants/errors.js create mode 100644 src/fileService/fileService.js create mode 100644 src/hashService/hashService.js create mode 100644 src/helpers/alpabetical-sort.js create mode 100644 src/helpers/get-username.js create mode 100644 src/main.js create mode 100644 src/navigationService/navigationService.js create mode 100644 src/operatingSystemService/operatingSystemService.js create mode 100644 src/promptService/prompt-service.js create mode 100644 src/zipService/zipService.js diff --git a/src/commandService/commandService.js b/src/commandService/commandService.js new file mode 100644 index 0000000..44b0bde --- /dev/null +++ b/src/commandService/commandService.js @@ -0,0 +1,74 @@ +import {navigationService} from "../navigationService/navigationService.js"; +import {OperatingSystemService} from "../operatingSystemService/operatingSystemService.js"; +import {ERRORS} from "../constants/errors.js"; +import {HashService} from "../hashService/hashService.js"; +import {FileService} from "../fileService/fileService.js"; +import {ZipService} from "../zipService/zipService.js"; + +export class CommandService { + static parseCommand(commandString) { + const items = commandString.trim().split(" "); + return {command: items[0], args: items.slice(1)} + } + + static async executeCommand(commandString) { + const {command, args} = commandString; + let filePath, copyFilePath, compressDest; + switch (command) { + case 'up': + await navigationService.setDir('..'); + break; + case 'ls': + await navigationService.getList(); + break; + case 'cd': + await navigationService.setDir(args[0]); + break; + case 'os': + OperatingSystemService.getInfo(args[0]); + break; + case 'hash': + filePath = navigationService.getFilepath(args[0]); + await HashService.getHash(filePath); + break; + case 'cat': + filePath = navigationService.getFilepath(args[0]); + await FileService.readFile(filePath); + break; + case 'add': + filePath = navigationService.getFilepath(args[0]); + await FileService.addNewFile(filePath); + break; + case 'rn': + filePath = navigationService.getFilepath(args[0]); + await FileService.renameFile(filePath, args[1]); + break; + case 'rm': + filePath = navigationService.getFilepath(args[0]); + await FileService.removeFile(filePath); + break; + case 'cp': + filePath = navigationService.getFilepath(args[0]); + copyFilePath = navigationService.getFilepath(args[1]); + await FileService.copyFile(filePath, copyFilePath); + break; + case 'mv': + filePath = navigationService.getFilepath(args[0]); + copyFilePath = navigationService.getFilepath(args[1]); + await FileService.moveFile(filePath, copyFilePath); + break; + case 'compress': + filePath = navigationService.getFilepath(args[0]); + compressDest = navigationService.getFilepath(args[1]); + await ZipService.compressFile(filePath, compressDest); + break; + case 'decompress': + filePath = navigationService.getFilepath(args[0]); + compressDest = navigationService.getFilepath(args[1]); + await ZipService.decompressFile(filePath, compressDest); + break; + default: + throw new Error(ERRORS.INVALID_INPUT); + } + } +} \ No newline at end of file diff --git a/src/constants/errors.js b/src/constants/errors.js new file mode 100644 index 0000000..19b83a0 --- /dev/null +++ b/src/constants/errors.js @@ -0,0 +1,4 @@ +export const ERRORS = { + INVALID_INPUT: 'Invalid input', + OPERATION_FAILED: 'Operation failed', +} \ No newline at end of file diff --git a/src/fileService/fileService.js b/src/fileService/fileService.js new file mode 100644 index 0000000..47bcf32 --- /dev/null +++ b/src/fileService/fileService.js @@ -0,0 +1,90 @@ +import fs from "fs"; +import {writeFile, rename, access, rm} from "fs/promises" +import {ERRORS} from "../constants/errors.js"; +import path from 'path'; + +export class FileService { + static async readFile(filePath) { + try { + await new Promise((resolve, reject) => { + const reader = fs.createReadStream(filePath); + reader.on('data', function (chunk) { + console.log(chunk.toString()); + resolve(); + }); + reader.on('error', () => { + reject(); + }) + }) + } catch { + throw new Error(ERRORS.OPERATION_FAILED); + } + } + + static async addNewFile(filePath) { + try { + await writeFile(filePath, '', {flag: 'wx'}); + console.log('File Created!') + } catch (e) { + throw new Error(ERRORS.OPERATION_FAILED); + } + } + + static async renameFile(oldFilePath, newFileName) { + try { + const dir = path.dirname(oldFilePath); + const newFilePath = path.join(dir, newFileName); + const fileExists = await access(newFilePath, fs.constants.F_OK).then(() => true, () => false); + if (fileExists) throw new Error(); + + await rename(oldFilePath, path.join(dir, newFilePath)); + console.log('File renamed!'); + } catch (e) { + throw new Error(ERRORS.OPERATION_FAILED); + } + } + + static async removeFile(filePath) { + try { + await rm(filePath); + console.log('File removed!') + } catch { + throw new Error(ERRORS.OPERATION_FAILED); + } + } + + static async moveFile(sourceFilePath, newFilePath) { + try { + await this.copyFile(sourceFilePath, newFilePath); + await this.removeFile(sourceFilePath); + } catch { + throw new Error(ERRORS.OPERATION_FAILED); + } + } + + static async copyFile(sourceFilePath, newFilePath) { + try { + const fileName = path.basename(sourceFilePath); + const newFile = path.join(newFilePath, fileName); + await new Promise((resolve, reject) => { + const reader = fs.createReadStream(sourceFilePath); + const writer = fs.createWriteStream(newFile); + + reader.on('error', (err) => { + reject(); + }); + writer.on('error', (err) => { + reject(); + }); + writer.on('close', () => { + console.log('File copied!'); + resolve(); + }) + + reader.pipe(writer); + }) + } catch { + throw new Error(ERRORS.OPERATION_FAILED); + } + } +} \ No newline at end of file diff --git a/src/hashService/hashService.js b/src/hashService/hashService.js new file mode 100644 index 0000000..044e5f1 --- /dev/null +++ b/src/hashService/hashService.js @@ -0,0 +1,26 @@ +import fs from "fs"; +import crypto from "crypto"; +import {ERRORS} from "../constants/errors.js"; + +export class HashService { + static async getHash(filePath) { + try { + await new Promise((resolve, reject) => { + const fh = fs.createReadStream(filePath); + const hash = crypto.createHash('sha256'); + hash.setEncoding('hex'); + + + fh.on('end', () => { + hash.end(); + console.log(`Hash of the file - ${hash.read()}`); + resolve(); + }); + fh.on('error', reject); + fh.pipe(hash); + }); + } catch (e) { + throw new Error(ERRORS.OPERATION_FAILED); + } + } +} \ No newline at end of file diff --git a/src/helpers/alpabetical-sort.js b/src/helpers/alpabetical-sort.js new file mode 100644 index 0000000..e69de29 diff --git a/src/helpers/get-username.js b/src/helpers/get-username.js new file mode 100644 index 0000000..873bf3e --- /dev/null +++ b/src/helpers/get-username.js @@ -0,0 +1,10 @@ +export function getUsername() { + const usernameArg = process.argv.slice(2).find((arg, i, arr) => { + if (arg.startsWith("--username")) { + return true; + } + return false; + }); + + return usernameArg ? usernameArg.split("=")[1] : 'Unknown User'; +} \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..fe8c364 --- /dev/null +++ b/src/main.js @@ -0,0 +1,19 @@ +import {initPromptService} from "./promptService/prompt-service.js"; +import {getUsername} from "./helpers/get-username.js"; +import {navigationService} from "./navigationService/navigationService.js"; + + +function initApp() { + const username = getUsername(); + process.stdout.write(`Welcome to the File Manager, ${username}!\n`); + process.stdout.write(`You're currently in ${navigationService.dir}\n`); + + function onClose() { + process.stdout.write(`Thank you for using File Manager, ${username}, goodbye!`); + } + + initPromptService(onClose); +} + +initApp(); + diff --git a/src/navigationService/navigationService.js b/src/navigationService/navigationService.js new file mode 100644 index 0000000..e2efdcb --- /dev/null +++ b/src/navigationService/navigationService.js @@ -0,0 +1,64 @@ +import os from 'os'; +import path from "path"; +import fs from "fs/promises"; +import {ERRORS} from "../constants/errors.js"; + +export class NavigationService { + constructor() { + this.currentDir = os.homedir(); + } + + get dir() { + return this.currentDir; + } + + async setDir(pathCommand) { + try { + const newPath = path.resolve(this.currentDir, pathCommand); + const status = await fs.stat(newPath); + if (!status.isDirectory()) { + throw new Error(); + } + this.currentDir = newPath; + } catch (e) { + throw new Error(ERRORS.INVALID_INPUT); + } + } + + async getList() { + try { + let files = []; + let folders = []; + const items = await fs.readdir(this.currentDir, {withFileTypes: true}); + + items.forEach((item) => { + if (item.isDirectory()) { + folders.push(item.name); + } else { + files.push(item.name); + } + }); + + files.sort(); + folders.sort(); + + files = files.map((item) => ({Name: item, Type: 'file'})) + folders = folders.map((item) => ({Name: item, Type: 'directory'})); + + console.table([...folders, ...files]); + + } catch (err) { + throw new Error(ERRORS.OPERATION_FAILED); + } + } + + getFilepath(filePath) { + try { + return path.resolve(this.currentDir, filePath); + } catch { + throw new Error(ERRORS.INVALID_INPUT); + } + } +} + +export const navigationService = new NavigationService(); diff --git a/src/operatingSystemService/operatingSystemService.js b/src/operatingSystemService/operatingSystemService.js new file mode 100644 index 0000000..b130887 --- /dev/null +++ b/src/operatingSystemService/operatingSystemService.js @@ -0,0 +1,50 @@ +import os from 'os'; +import {ERRORS} from "../constants/errors.js"; + +export class OperatingSystemService { + static getInfo(command) { + switch (command) { + case '--eol': + this.getEOL(); + break; + case '--cpus': + this.getCpus(); + break; + case '--homedir': + this.getHomedir(); + break; + case '--username': + this.getUsername(); + break; + case '--architecture': + this.getArchitecture(); + break; + default: + throw new Error(ERRORS.INVALID_INPUT) + } + } + + static getCpus() { + const cpuInfo = os.cpus(); + console.log(`Overall amount of CPUS - ${cpuInfo.length}`); + for (let cpu of cpuInfo) { + console.log(`${cpu.model} - Speed ${cpu.speed/1000} GHz`); + } + } + + static getEOL() { + console.log(`End of line marker - ${JSON.stringify(os.EOL)}`); + } + + static getHomedir() { + console.log(`Your node directory - ${os.homedir()}`); + } + + static getUsername() { + console.log(`Your system username - ${os.userInfo().username}`); + } + + static getArchitecture() { + console.log(`CPU architecture ${process.arch}`); + } +} \ No newline at end of file diff --git a/src/promptService/prompt-service.js b/src/promptService/prompt-service.js new file mode 100644 index 0000000..a5c4f91 --- /dev/null +++ b/src/promptService/prompt-service.js @@ -0,0 +1,24 @@ +import {createInterface} from 'readline/promises'; +import {navigationService} from "../navigationService/navigationService.js"; +import {CommandService} from "../commandService/commandService.js"; + +let rl; +export const initPromptService = (onClose) => { + rl = createInterface({ + input: process.stdin, + output: process.stdout, + }); + rl.on('line', async (line) => { + try { + const command = CommandService.parseCommand(line); + if (command.command === '.exit') { + rl.close(); return; + } + await CommandService.executeCommand(command); + } catch (e) { + console.log(e.message); + } + process.stdout.write(`You're currently in ${navigationService.dir}\n`); + }); + rl.on('close', onClose); +} \ No newline at end of file diff --git a/src/zipService/zipService.js b/src/zipService/zipService.js new file mode 100644 index 0000000..d3a4cd0 --- /dev/null +++ b/src/zipService/zipService.js @@ -0,0 +1,67 @@ +import {ERRORS} from "../constants/errors.js"; +import fs from "fs"; +import path from "path"; +import zlib from 'zlib'; + +export class ZipService { + static async compressFile(sourceFilePath, destinationDir) { + try { + const fileName = path.basename(sourceFilePath); + const compressedFilePath = path.join(destinationDir, `${fileName}.br`); + await new Promise((resolve, reject) => { + const reader = fs.createReadStream(sourceFilePath); + const writer = fs.createWriteStream(compressedFilePath); + + const brotli = zlib.createBrotliCompress(); + + reader.on('error', (err) => { + reject(); + }); + writer.on('error', (err) => { + reject(); + }); + const stream = reader.pipe(brotli).pipe(writer); + + stream.on('finish', () => { + console.log('File compressed!'); + resolve(); + }); + }) + } catch { + throw new Error(ERRORS.OPERATION_FAILED); + } + } + + static async decompressFile(sourceFilePath, destinationPath) { + try { + const fileName = path.basename(sourceFilePath); + const ext = path.parse(sourceFilePath).ext; + if (ext !== '.br') throw new Error(); + + const decompressedFileName = path.parse(sourceFilePath).name; + const decompressedFilePath = path.join(destinationPath, decompressedFileName); + await new Promise((resolve, reject) => { + const reader = fs.createReadStream(sourceFilePath); + const writer = fs.createWriteStream(decompressedFilePath); + + const brotli = zlib.createBrotliDecompress(); + + reader.on('error', (err) => { + reject(); + }); + writer.on('error', (err) => { + reject(); + }); + const stream = reader.pipe(brotli).pipe(writer); + + stream.on('finish', () => { + console.log('File decompressed!'); + resolve(); + }); + }) + } catch { + throw new Error(ERRORS.OPERATION_FAILED); + } + + } +} \ No newline at end of file From 8cfb8d331f9b39e5708330791ad220243e1caef2 Mon Sep 17 00:00:00 2001 From: Natallia Date: Tue, 6 Feb 2024 00:23:27 +0100 Subject: [PATCH 2/4] refactor: remove useless file --- src/helpers/alpabetical-sort.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/helpers/alpabetical-sort.js diff --git a/src/helpers/alpabetical-sort.js b/src/helpers/alpabetical-sort.js deleted file mode 100644 index e69de29..0000000 From 24c57cc42f6e7e2932fa3100524ac702402ac9f0 Mon Sep 17 00:00:00 2001 From: Natallia Date: Tue, 6 Feb 2024 00:40:43 +0100 Subject: [PATCH 3/4] fix: fix file rename --- src/fileService/fileService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fileService/fileService.js b/src/fileService/fileService.js index 47bcf32..f597589 100644 --- a/src/fileService/fileService.js +++ b/src/fileService/fileService.js @@ -37,7 +37,7 @@ export class FileService { const fileExists = await access(newFilePath, fs.constants.F_OK).then(() => true, () => false); if (fileExists) throw new Error(); - await rename(oldFilePath, path.join(dir, newFilePath)); + await rename(oldFilePath, newFilePath); console.log('File renamed!'); } catch (e) { throw new Error(ERRORS.OPERATION_FAILED); From 56072455a6b7e969555240a04d146c05fbde1616 Mon Sep 17 00:00:00 2001 From: Natallia Date: Tue, 6 Feb 2024 00:48:55 +0100 Subject: [PATCH 4/4] fix: fix EOL --- src/operatingSystemService/operatingSystemService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operatingSystemService/operatingSystemService.js b/src/operatingSystemService/operatingSystemService.js index b130887..8ef742f 100644 --- a/src/operatingSystemService/operatingSystemService.js +++ b/src/operatingSystemService/operatingSystemService.js @@ -4,7 +4,7 @@ import {ERRORS} from "../constants/errors.js"; export class OperatingSystemService { static getInfo(command) { switch (command) { - case '--eol': + case '--EOL': this.getEOL(); break; case '--cpus':