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..f597589 --- /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, 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/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..8ef742f --- /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