diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df01e2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode +.log.txt +./src/fs/files_copy +./src/fs/files \ No newline at end of file diff --git a/Readme.md b/Readme.md index c8cffb1..98f5d27 100644 --- a/Readme.md +++ b/Readme.md @@ -1,40 +1,68 @@ # Scoring: File Manager + +[Assignment](https://github.com/AlreadyBored/nodejs-assignments/blob/main/assignments/file-manager/assignment.md) + +Runs with: + +`npm run start -- --username=your_username` + +Or, if [arguments are not detected](https://github.com/npm/cli/issues/7375), it might work with: + +`npm run start -- -- --username=your_username` + + +## Commands + +`ls, list, dir / mkdir / up, back / cd / add, touch, write, w / cat, read / rm , del, remove, delete / rn, rename / cp, copy / mv, move / eol / cpuinfo / home, homedir / userinfo / arch, architecture / hash / compress / decompress / help, ? / .exit, exit, quit, q! ` + +## Note + +- Would be cool if your terminal supported utf-8, if no there might be question marks in output. +- I had to use `currentDirectory();` before each break in switch statement, because if i put it in function after all statments, it shows old folder (async so async). If there are more elegant solution for my case, please let me know. +- `compress` command deletes original file. + + +## Extra features +- `add` can write sting in new file if string in quotes is provided as second argument. +- Removing empty directories just with `rm` by distinguishing between files and directories with `isfile.js`. +- Logging failed commands in `logOutput.js`, well originally I had bigger plans for this but don't want to break anything at this point. + ## Basic Scope - General - - **+6** Application accepts username and prints proper message - - **+10** Application exits if user pressed `ctrl+c` or sent `.exit` command and proper message is printed +- ✅ **+6** Application accepts username and prints proper message +- ✅ **+10** Application exits if user pressed `ctrl+c` or sent `.exit` command and proper message is printed - Operations fail - - **+20** Attempts to perform an operation on a non-existent file or work on a non-existent path result in the operation fail - - **+10** Operation fail doesn't crash application +- ✅ **+20** Attempts to perform an operation on a non-existent file or work on a non-existent path result in the operation fail +- ✅ **+10** Operation fail doesn't crash application - Navigation & working directory operations implemented properly - - **+10** Go upper from current directory - - **+10** Go to dedicated folder from current directory - - **+20** List all files and folders in current directory +- ✅ **+10** Go upper from current directory + - **+10** Go to dedicated folder from current directory +- ✅ **+20** List all files and folders in current directory - Basic operations with files implemented properly - - **+10** Read file and print it's content in console - - **+10** Create empty file - - **+10** Rename file - - **+10** Copy file - - **+10** Move file - - **+10** Delete file + - ✅ **+10** Read file and print it's content in console + - ✅ **+10** Create empty file + - ✅**+10** Rename file + - ✅**+10** Copy file + - ✅**+10** Move file + - ✅**+10** Delete file - Operating system info (prints following information in console) implemented properly - - **+6** Get EOL (default system End-Of-Line) - - **+10** Get host machine CPUs info (overall amount of CPUS plus model and clock rate (in GHz) for each of them) - - **+6** Get home directory - - **+6** Get current *system user name* (Do not confuse with the username that is set when the application starts) - - **+6** Get CPU architecture for which Node.js binary has compiled + - ✅**+6** Get EOL (default system End-Of-Line) + - ✅**+10** Get host machine CPUs info (overall amount of CPUS plus model and clock rate (in GHz) for each of them) + - ✅**+6** Get home directory + - ✅**+6** Get current *system user name* (Do not confuse with the username that is set when the application starts) + - ✅**+6** Get CPU architecture for which Node.js binary has compiled - Hash calculation implemented properly - - **+20** Calculate hash for file + - ✅**+20** Calculate hash for file - Compress and decompress operations - - **+20** Compress file (using Brotli algorithm) - - **+20** Decompress file (using Brotli algorithm) + - ✅**+20** Compress file (using Brotli algorithm) + - ✅**+20** Decompress file (using Brotli algorithm) ## Advanced Scope -- **+30** All operations marked as to be implemented using certain streams should be performed using Streams API -- **+20** No synchronous Node.js API with asynchronous analogues is used (e.g. not used `readFileSync` instead of `readFile`) -- **+20** Codebase is written in ESM modules instead of CommonJS -- **+20** Codebase is separated (at least 7 modules) +- ✅ **+30** All operations marked as to be implemented using certain streams should be performed using Streams API +- ✅ **+20** No synchronous Node.js API with asynchronous analogues is used (e.g. not used `readFileSync` instead of `readFile`) +- ✅ **+20** Codebase is written in ESM modules instead of CommonJS +- ✅ **+20** Codebase is separated (at least 7 modules) ## Forfeits diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..89086b8 --- /dev/null +++ b/log.txt @@ -0,0 +1,918 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +rm asdfdsf +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +cd "whatever it was" +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +cat love +cat love +cat love +cat love +cat love +cat love +cat love +cat love +cat love +cat love +cat love +cat love +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +cat test.cpp +touch file +rm file +touch file +touch "file file" +cat file file +cat "file file" +rn "file file" "file space" +rm "file space" +rm "file file" + + + +ls +rm file +touch "file" +ls +rm "file" +mkdir folder +rm folder +ды +ls +ls +cd c:\ +cd c:\ +ls +ls +cd "G:\Games" +cd "G:\Games" +ls +rm file.txt +add file.txt aaaa +add "file text.txt" +ls +cd .. + + + + + + + + + + +ls +ls +ls +ls +ls +cd .. +cd .. +cd .. +cd .. +cd .. + + + + + + + + + + + + + + + + + + + + + +ls +ls +ls +ls +ls +ls +ls +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd . +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\ +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +cd c:\sdfds +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +mkdir bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +cd bruh +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +mkdir eh +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +ls +touch file +rm file +touch file +cp file file2 +rm file2 +rim file +rm file +rm file + + + +rm a +rm a +rm a +rm b +rm b +rm b +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd bruh +cd bruh +cd bruh +touch ababa +touch ababa +touch ababa +ls +ls +ls +cp ababa bebebe +cp ababa bebebe +cp ababa bebebe +cd .. +ls +cd bruh +ls +cp ababa bebebe +ls +cd ababa c:\bebe +cd .. +ls +cat bebe +cd .. +cat bebe +cd bruh +ls +cp fsadf +cp gfdg fdg +cp "" "" +ls + + + + + + + + + + + + + + + +cpu +g:\ +cd G:\ +cd G:\ +cd G: +cd G: +cd +cd +cd "G:\" +cd "G:\" +ls +ls +cd c: +cd c: +cd C: +cd C: +cd C:\ +cd C:\ +cd C:\ +cd C:\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +ды +ls +ls +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +ls +ls +cd g: +cd g: +cd g:\ +cd g:\ + + + + + + + + + + + + + + +ls +ls +ls +ls +ls +ls +exit +exit +exit +exit +exit +exit + +ls +ls +cd c:\ +cd c:\ + + + + + + + + + + + + + + +ls +ls +ls +ls +ls +ls +cd .. +cd .. +cd .. +cd .. +cd .. +cd .. +cd h:\ +cd h:\ +cd h:\ +cd h:\ +cd h:\ +cd h:\ + + + + + + + + + + + + + +ls +ls +ls +ls +ls +ls +ls +ls +cd d: +cd d: +cd d: +cd d: +cd d: +cd d: +cd d: +cd d: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +cd c: +ls +help +ls +cd bruh +ls +cp aa dd +ls +cpuinfo +okay +help +help +ls +cd .. +cd g:\ +ls +hash fun.jpg +ls +cls +cat diff --git a/package.json b/package.json index 388d0e8..784b9d3 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ }, "type": "module", "scripts": { + "start" : "node src/filemanager/filemanager.js", "cli:args": "node src/cli/args.js --some-arg value1 --other 1337 --arg2 42", "cli:env": "npx cross-env SOME=any RSS_foo=bar RSS_bar=baz node src/cli/env.js", "cp": "node src/cp/cp.js", diff --git a/src/cli/args.js b/src/cli/args.js deleted file mode 100644 index 6db317e..0000000 --- a/src/cli/args.js +++ /dev/null @@ -1,24 +0,0 @@ -const parseArgs = () => { - const parsedArgs = process.argv.slice(2); - const output = {}; - - for (let i = 0; i < parsedArgs.length; i += 2) { - const key = parsedArgs[i].replace("--", ""); - const value = parsedArgs[i + 1]; - output[key] = value; - - } - - // for (const [key, value] of Object.entries(output)) { - // console.log(`${key} is ${value}`); - // } - - - const result = Object.entries(output) - .map(([key, value]) => `${key} is ${value}`); - console.log(result.join(', ')); - - -}; - -parseArgs(); \ No newline at end of file diff --git a/src/cli/env.js b/src/cli/env.js deleted file mode 100644 index e8f5206..0000000 --- a/src/cli/env.js +++ /dev/null @@ -1,10 +0,0 @@ - -const parseEnv = () => { - const rssVars = Object.entries(process.env) - .filter(([key]) => key.startsWith('RSS_')) - .map(([key, value]) => `${key}=${value}`); - console.log(rssVars.join('; ')); - -}; - -parseEnv(); \ No newline at end of file diff --git a/src/constants/global.js b/src/constants/global.js new file mode 100644 index 0000000..88f8166 --- /dev/null +++ b/src/constants/global.js @@ -0,0 +1,12 @@ +export const FILES_FOLDER = 'files'; +export const FS_ERROR = '(╯°□°)╯︵ ┻━┻ FS Operation failed.'; +export const GENERIC_ERROR = '(╯°□°)╯︵ ┻━┻ Operation failed.'; +export const STREAM_ERRROR = '(╯°□°)╯︵ ┻━┻ Stream operation failed.'; +export const INIT_MESSAGE = 'ヾ(・ω・*) Type "help" to get available commands >>> '; +export const INVALID_INPUT = '(╬ Ò﹏Ó) Invalid input.'; +export const COMPRESS_ERROR = '(╯°□°)╯︵ ┻━┻ Compression operation failed.'; +export const PROMPT_MESSAGE = 'Welcome to the File manager,'; +export const EXIT_MESSAGE1 = 'Thank you for using File Manager, '; +export const EXIT_MESSAGE2 = ', goodbye! ᓚ₍ ^. .^₎'; + +export const LOG_PATH = 'log.txt'; diff --git a/src/filemanager/cp.js b/src/filemanager/cp.js new file mode 100644 index 0000000..8adef9b --- /dev/null +++ b/src/filemanager/cp.js @@ -0,0 +1,24 @@ +import path from 'path' +import { copy } from "../fs/copy.js"; + + +export const filemanagerCp = async (currDir, arg1, arg2) => { +try { + var src, dest = '' + + if (path.isAbsolute(arg1)) { + src = path.resolve(arg1); + } else { + src = path.join(currDir, arg1); + } + + if (path.isAbsolute(arg2)) { + dest = path.resolve(arg2); + } else { + dest = path.join(currDir, arg2); + } + + await copy(src, dest) + + } catch (err) {console.error(err);} +} \ No newline at end of file diff --git a/src/filemanager/filemanager.js b/src/filemanager/filemanager.js new file mode 100644 index 0000000..85beffb --- /dev/null +++ b/src/filemanager/filemanager.js @@ -0,0 +1,303 @@ +import { + INIT_MESSAGE, + PROMPT_MESSAGE, + EXIT_MESSAGE1, + EXIT_MESSAGE2, + INVALID_INPUT, + COMPRESS_ERROR, + GENERIC_ERROR, + FS_ERROR, +} from "../constants/global.js"; + +import os from "node:os"; +import { access } from "fs"; +import path from "path"; + +import { create } from "./../fs/create.js"; +import { list } from "./../fs/list.js"; +import { rename } from "./../fs/rename.js"; +import { makeDirectory } from "./../fs/mkdir.js"; + +import { logOutput } from "./logOutput.js"; +import { filemanagerCp } from "./cp.js"; +import { filemanagerRm } from "./rm.js"; +import { filemanagerMv } from "./mv.js"; + +import { readByStream } from "./../streams/read.js"; +import { calculateHash } from "./../hash/calcHash.js"; +import { compressBrotli, decompressBrotli } from "../zip/compress.js"; + +const { stdin, stdout } = process; + +const cpus = os.cpus(); + +const cpuinfo = () => { + cpus.forEach((cpu) => { + console.log( + `Model: ${cpu.model} speed: ${(cpu.speed / 1000).toFixed(2)} GHz` + ); + }); +}; + +const homedir = os.homedir(); +var __currentdir = homedir; + +const getUsername = async () => { + for (const i = 2; i < process.argv.length; i++) { + const arg = process.argv[i]; + const keyValue = arg.split("="); + const key = keyValue[0]; + const value = keyValue[1]; + if (key === "--username") { + return value; + } else return "user"; + } +}; + +const getQuotedValue = async (input) => { + const qr = /"(.*?)"/g; + const matches = Array.from(input.matchAll(qr)); + + if (matches.length > 0) { + return matches.map((match) => match[1]); // extract values from obj to arr + } else return []; +}; + +const prompt = async (input) => { + const commandArgs = input.trim().split(" "); + const command = commandArgs[0]; + const quotedValues = await getQuotedValue(input); + + if (quotedValues.length > 0) { + if (commandArgs[1].includes('"')) commandArgs[1] = quotedValues[0]; + else if (commandArgs[2].includes('"')) commandArgs[2] = quotedValues[0]; + } + if (quotedValues.length > 1 && commandArgs[2].includes('"')) { + commandArgs[2] = quotedValues[1]; + } + + switch (command) { + case "ls": + case "list": + case "dir": + stdout.write("\n"); + await list(__currentdir); + currentDirectory(); + break; + + case "mkdir": + try { + var newDir = " "; + if (path.isAbsolute(commandArgs[1])) { + newDir = path.resolve(commandArgs[1]); + } else { + newDir = path.join(__currentdir, commandArgs[1]); + } + await makeDirectory(newDir); + currentDirectory(); + } catch (err) {console.error(err);} + break; + + case "up": + case "back": + __currentdir = path.join(__currentdir, ".."); + currentDirectory(); + + break; + + case "cd": + try { + var cd = ""; + + if (path.isAbsolute(commandArgs[1])) { + cd = path.resolve(commandArgs[1]); + } else cd = path.join(__currentdir, commandArgs[1]); + + access(cd, (err) => { + if (err) { + console.log(`${commandArgs[1]} is non-existent`); + } else __currentdir = cd; + currentDirectory(); + }); + } catch (err) { + console.error(FS_ERROR, err); + } + break; + + case "add": + case "touch": + case "write": + case "w": + try { + const addPath = path.join(__currentdir, commandArgs[1]); + var content = ""; + if (commandArgs.length > 2) content = commandArgs[2]; + + await create(addPath, content); + } catch (err) { + console.error(err); + } + currentDirectory(); + break; + + case "cat": + case "read": + try { + const catPath = path.join(__currentdir, commandArgs[1]); + stdout.write("\n"); + await readByStream(catPath); + currentDirectory(); + } catch (err) { + console.error(err); + } + break; + + case "remove": + case "delete": + case "del": + case "rm": + try { + await filemanagerRm(__currentdir, commandArgs[1]); + currentDirectory(); + } catch (err) { + console.error(err); + } + break; + + case "rename": + case "rn": + try { + const oldPath = path.join(__currentdir, commandArgs[1]); + const newPath = path.join(__currentdir, commandArgs[2]); + await rename(oldPath, newPath); + } catch (err) { + console.error(err); + } + currentDirectory(); + break; + + case "cp": + case "copy": + try { + await filemanagerCp(__currentdir, commandArgs[1], commandArgs[2]); + currentDirectory(); + } catch (err) { + console.error(err); + } + break; + + case "mv": + case "move": + try { + await filemanagerMv(__currentdir, commandArgs[1], commandArgs[2]); + currentDirectory(); + } catch (err) { + console.error(err); + } + break; + + case "eol": + console.log(`Default system EOL is ${JSON.stringify(os.EOL)}`); + currentDirectory(); + break; + + case "cpuinfo": + console.log(`CPU Info: `); + cpuinfo(); + currentDirectory(); + break; + + case "homedir": + case "home": + console.log(`User home directory: ${homedir}`); + currentDirectory(); + break; + + case "userinfo": + const userinfo = os.userInfo(); + console.log(`System user name: ${userinfo.username}`); + currentDirectory(); + break; + + case "arch": + case "architecture": + console.log(`OS Architecture: ${os.arch}`); + currentDirectory(); + break; + + case "hash": + calculateHash(__currentdir, commandArgs[1]); + currentDirectory(); + break; + + case "compress": + try { + await compressBrotli(__currentdir, commandArgs[1]).then( + await filemanagerRm(__currentdir, commandArgs[1]) + ); + } catch (err) { + console.error(COMPRESS_ERROR, err); + } + currentDirectory(); + + break; + + case "decompress": + try { + await decompressBrotli(__currentdir, commandArgs[1]); + currentDirectory(); + } catch (err) { + console.error(COMPRESS_ERROR, err); + } + break; + + case "help": + case "?": + { + console.log( + "Commands: ls, list, dir / mkdir / up, back / cd / add, touch, write, w / cat, read / rm , del, remove, delete / rn, rename / cp, copy / mv, move / eol / cpuinfo / home, homedir / userinfo / arch, architecture / hash / compress / decompress / help, ? / .exit, exit, quit, q! " + ); + } + currentDirectory(); + + break; + + default: + logOutput(INVALID_INPUT, command); + break; + + case ".exit": + case "exit": + case "quit": + case "q!": + detect_exit(); + break; + } +}; + +const currentDirectory = async () => { + console.log("You are currently in", __currentdir); + stdout.write(">>> "); +}; + +const waitForInput = async () => { + stdin.on("data", (data) => { + stdout.write("(づ ᴗ _ᴗ)づ>>> "); + prompt(data.toString()); + }); + process.on("SIGINT", () => { + detect_exit(); + }); +}; + +const detect_exit = async () => { + console.log(EXIT_MESSAGE1 + username + EXIT_MESSAGE2); + process.exit(0); +}; + +const username = await getUsername(); +console.log(PROMPT_MESSAGE, username + "!"); +currentDirectory(); + +stdout.write(INIT_MESSAGE); +waitForInput(); diff --git a/src/filemanager/logOutput.js b/src/filemanager/logOutput.js new file mode 100644 index 0000000..9f7a3f2 --- /dev/null +++ b/src/filemanager/logOutput.js @@ -0,0 +1,8 @@ +import { writeBySteam } from "./../streams/write.js"; +import { LOG_PATH } from "../constants/global.js"; +const { stdin, stdout } = process; + +export const logOutput = async (message) => { + stdout.write(message + "\n"); // вывод в консоль + writeBySteam(LOG_PATH, { flags: "a" }); // логгирование +}; diff --git a/src/filemanager/mv.js b/src/filemanager/mv.js new file mode 100644 index 0000000..82baef8 --- /dev/null +++ b/src/filemanager/mv.js @@ -0,0 +1,21 @@ +import { join } from 'node:path'; +import fs from 'node:fs/promises'; + +import { FS_ERROR } from '../constants/global.js' + +export const filemanagerMv = async (currentDir, src, dest) => { + try { + const srcPath = join(currentDir, src); + const destPath = join(currentDir, dest); + + // Move the file or directory + await fs.rename(srcPath, destPath); + + console.log(`Moved "${src}" to "${dest}"`); + } catch (error) { + console.error(FS_ERROR, error); + } +} + + + \ No newline at end of file diff --git a/src/filemanager/rm.js b/src/filemanager/rm.js new file mode 100644 index 0000000..ce308d4 --- /dev/null +++ b/src/filemanager/rm.js @@ -0,0 +1,15 @@ +import { isFile } from "./../fs/isfile.js"; + +import { remove, removeDir } from "./../fs/delete.js"; +import path from 'path' + +export const filemanagerRm = async (dir, arg) => { +try { + const remPath = path.join(dir, arg); + if (await isFile(remPath)) { + await remove(remPath); + } else await removeDir(remPath); + } catch (err) { + console.error(err); + } +} \ No newline at end of file diff --git a/src/fs/access.js b/src/fs/access.js new file mode 100644 index 0000000..349caf5 --- /dev/null +++ b/src/fs/access.js @@ -0,0 +1,10 @@ +import {access, constants } from 'fs/promises'; + +export const checkAccess = async (path) => { + try { + await access(path, constants.F_OK); + return true; + } catch (error) { + return false; + } +} \ No newline at end of file diff --git a/src/fs/copy.js b/src/fs/copy.js index 3ebdde2..98f89f3 100644 --- a/src/fs/copy.js +++ b/src/fs/copy.js @@ -1,22 +1,13 @@ import fs from 'fs' -const source = './src/fs/files' -const destination = './src/fs/files_copy' - - +import { FS_ERROR } from '../constants/global.js' + // fs.cp(src, dest[, options], callback) = Asynchronously copies the entire directory structure from src to dest, including subdirectories and files. // throws error if source don't exist by default // error on exist throws error if destination exists -const copy = async (src, dest) => { +export const copy = async (src, dest) => { fs.promises.cp(src, dest, {force:false, errorOnExist:true, recursive:true }) - .then( () => { - console.log("Copied") - }) - .catch( (err) => { - console.error("FS operation failed", err) - }) + .then( () => { console.log("Copied")}) + .catch( (err) => {console.error(FS_ERROR, err)}) } - - - -await copy(source, destination); + \ No newline at end of file diff --git a/src/fs/create.js b/src/fs/create.js index fabcd94..d858f5c 100644 --- a/src/fs/create.js +++ b/src/fs/create.js @@ -1,24 +1,12 @@ import fs from 'fs' +import { FS_ERROR } from '../constants/global.js'; -const folder = "./src/fs/files/"; -const filename = "fresh.txt"; -const content = "I am fresh and young"; -const create = async () => { - fs.open(folder + filename, "wx", (err, descriptor) => { // wx flag creates and opens, throws an error if exist - if (err) { - console.log("FS operation failed", err); - } else { - //console.log(descriptor); - fs.write(descriptor, content, (err, bytes) => { - if (err) { - console.error("FS operation failed", err); - } else { - console.log("File created"); - } - }) - } - }) -}; +export const create = async (filePath, content) => { + + fs.promises.writeFile(filePath, content, {flag: 'wx'}) + .then( () => console.log('File has been created.')) + .catch( (error) => console.error(FS_ERROR, error)) -await create(); \ No newline at end of file + +}; \ No newline at end of file diff --git a/src/fs/delete.js b/src/fs/delete.js index 9bd1531..8b514e3 100644 --- a/src/fs/delete.js +++ b/src/fs/delete.js @@ -1,17 +1,16 @@ import fs from 'fs' -const path = './src/fs/files/' -const file = 'fileToRemove.txt' +import { FS_ERROR } from '../constants/global.js'; +export const remove = async (delFilePath) => { + fs.promises.unlink(delFilePath) + .then(() => console.log(`File has been removed.`)) + .catch((err) => console.error(FS_ERROR, err)) +}; -const remove = async () => { - fs.unlink(path + file, (err) => { - if (err) { - console.error("FS operation failed", err) - } else - console.log("Removed") - }) +export const removeDir = async (delFilePath) => { + fs.promises.rmdir(delFilePath) + .then(() => console.log(`Directory has been removed.`)) + .catch((err) => console.error(FS_ERROR, err)) }; - -await remove(); \ No newline at end of file diff --git a/src/fs/files/dontLookAtMe.txt b/src/fs/files/dontLookAtMe.txt deleted file mode 100644 index 8979bab..0000000 --- a/src/fs/files/dontLookAtMe.txt +++ /dev/null @@ -1 +0,0 @@ -What are you looking at?! \ No newline at end of file diff --git a/src/fs/files/fileToRead.txt b/src/fs/files/fileToRead.txt deleted file mode 100644 index 5d66c33..0000000 --- a/src/fs/files/fileToRead.txt +++ /dev/null @@ -1,7 +0,0 @@ -My content -should -be -printed -into -console -! \ No newline at end of file diff --git a/src/fs/files/fileToRemove.txt b/src/fs/files/fileToRemove.txt deleted file mode 100644 index 5d66c33..0000000 --- a/src/fs/files/fileToRemove.txt +++ /dev/null @@ -1,7 +0,0 @@ -My content -should -be -printed -into -console -! \ No newline at end of file diff --git a/src/fs/files/hello.txt b/src/fs/files/hello.txt deleted file mode 100644 index 4e65f77..0000000 --- a/src/fs/files/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Hello Node.js \ No newline at end of file diff --git a/src/fs/files/wrongFilename.txt b/src/fs/files/wrongFilename.txt deleted file mode 100644 index 38cca5d..0000000 --- a/src/fs/files/wrongFilename.txt +++ /dev/null @@ -1,3 +0,0 @@ -# This is a file with a wrong filename - -Hello from **markdown**! \ No newline at end of file diff --git a/src/fs/isfile.js b/src/fs/isfile.js new file mode 100644 index 0000000..80a01b2 --- /dev/null +++ b/src/fs/isfile.js @@ -0,0 +1,26 @@ +import fs from 'fs' +import path from 'path' +import util from 'util' + + + + +export const isFile = async (itemPath) => { + try { + // reading and deciding if directory + const statPr = util.promisify(fs.stat); + try{ + const stats = await statPr(itemPath); + if (stats.isFile()) + return true ; + else + return false; + } catch (err) { + if (err.code === 'EBUSY') + console.log('File is currently locked, skipping...'); + } + } + catch (err) { + console.error(err); + } + }; \ No newline at end of file diff --git a/src/fs/list.js b/src/fs/list.js index dacf9f3..8e39a11 100644 --- a/src/fs/list.js +++ b/src/fs/list.js @@ -1,18 +1,57 @@ -import fs from 'fs' - const path = './src/fs/files/' +import {readdir} from 'fs/promises' +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { FS_ERROR } from '../constants/global.js'; +import fs from 'fs'; +import util from 'util'; -const list = async () => { - fs.readdir(path, (err,files) =>{ - if (err) { - console.error('FS operation failed', err) - } else { - files.forEach(function(file) { - console.log(file) - }) + +const __fileName = fileURLToPath(import.meta.url); +const __dirname = dirname(__fileName); + +//const dir = join(__dirname, FILES_FOLDER); + + +export const list = async (dir) => { + try { + // reading and deciding if directory + const items = await readdir(dir); + const itemsWithStats = await Promise.all(items.map(async (item) => { + const itemPath = join(dir, item); + const statPr = util.promisify(fs.stat); + try{ + const stats = await statPr(itemPath); + return { + name: item, + type: stats.isDirectory() ? 'directory' : 'file' + } + } catch (err) { + if (err.code === 'EBUSY') + console.log('File is currently locked, skipping...'); } + })); + + + itemsWithStats.sort((a, b) => { + if (a.type === 'directory' && b.type === 'directory') { + return a.name.localeCompare(b.name); + } else if (a.type === 'directory') { + return -1; + } else if (b.type === 'directory') { + return 1; + } else { + return a.name.localeCompare(b.name); } - ) -}; + }); + -await list(); \ No newline at end of file + + const display = () => { + console.table(itemsWithStats) + } + display(); + } catch (error) { + console.error(FS_ERROR, error); + } +}; diff --git a/src/fs/mkdir.js b/src/fs/mkdir.js new file mode 100644 index 0000000..b7ab525 --- /dev/null +++ b/src/fs/mkdir.js @@ -0,0 +1,9 @@ +import {mkdir} from 'fs/promises' +import {join} from 'path' +import {FS_ERROR} from '../constants/global.js' + +export const makeDirectory = async(newDir) => { + mkdir(newDir) + .then(() => console.log(`Folder created sucessfully`)) + .catch((err) => console.error(FS_ERROR, err)) +} \ No newline at end of file diff --git a/src/fs/read.js b/src/fs/read.js index d6c9d69..1689c87 100644 --- a/src/fs/read.js +++ b/src/fs/read.js @@ -1,14 +1,9 @@ import fs from 'fs' -const path = './src/fs/files/' -const file = 'fileToRead.txt' -const read = async (source) => { +export const read = async (source) => { const data = await fs.promises.readFile(source, 'utf8') .then((data) => console.log(data)) .catch((error) => console.error("FS Operation failed", error)) } - - -await read(path + file); \ No newline at end of file diff --git a/src/fs/rename.js b/src/fs/rename.js index 0f8a2db..b8c977a 100644 --- a/src/fs/rename.js +++ b/src/fs/rename.js @@ -1,17 +1,20 @@ import fs from 'fs' -const path = './src/fs/files/' -const oldname = 'wrongFilename.txt' -const newname = 'properFilename.md' +import { FS_ERROR } from '../constants/global.js'; +import { checkAccess } from '../fs/access.js' -const rename = async () => { - fs.rename( path + oldname, path + newname, (err) =>{ - if (err) { - console.error('FS operation failed', err) - } else { - console.log('Renamed') - } - }) -}; +export const rename = async (oldFilePath, newFilePath) => { + const fileExist = await checkAccess(newFilePath); + + if (fileExist) { + throw new Error("File exists"); + } -await rename(); \ No newline at end of file + try { + await fs.promises.rename(oldFilePath, newFilePath) + console.log(`${oldFilePath} has been renamed to ${newFilePath}`); + } catch (err) { + console.error(FS_ERROR) + } +} + \ No newline at end of file diff --git a/src/hash/calcHash.js b/src/hash/calcHash.js index 1d20d71..2486098 100644 --- a/src/hash/calcHash.js +++ b/src/hash/calcHash.js @@ -1,11 +1,21 @@ -import fs from 'fs'; -import crypto from 'crypto'; -const file = "src/hash/files/fileToCalculateHashFor.txt" +import fs from 'node:fs'; +import crypto from 'node:crypto'; +import path from 'node:path'; + + + +export const calculateHash = async (currDir, file) => { + var src = ''; + if (path.isAbsolute(file)) { + src = path.resolve(file); + } else { + src = path.join(currDir, file); + } -const calculateHash = async () => { const hash = crypto.createHash('sha256'); - const readStream = fs.createReadStream(file); + const readStream = fs.createReadStream(src); + readStream.on('error', (err) => { console.error('Crypto operation failed ', err); }) @@ -20,4 +30,3 @@ const calculateHash = async () => { }) }; -await calculateHash(); \ No newline at end of file diff --git a/src/hash/files/fileToCalculateHashFor.txt b/src/hash/files/fileToCalculateHashFor.txt deleted file mode 100644 index 08f5656..0000000 --- a/src/hash/files/fileToCalculateHashFor.txt +++ /dev/null @@ -1 +0,0 @@ -Calculate hash for me! \ No newline at end of file diff --git a/src/modules/esm.mjs b/src/modules/esm.mjs deleted file mode 100644 index 4e0d978..0000000 --- a/src/modules/esm.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import path from 'path'; -import { release, version } from 'os'; -import { createServer as createServerHttp } from 'http'; -import './files/c.js'; -import { readFile } from 'fs/promises'; -import { fileURLToPath } from 'url'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const random = Math.random(); - -export let unknownObject; - -if (random > 0.5) { - unknownObject = JSON.parse( await readFile(new URL('./files/a.json', import.meta.url))); -} else { - unknownObject = JSON.parse( await readFile(new URL('./files/b.json', import.meta.url))); -} - -console.log(`Release ${release()}`); -console.log(`Version ${version()}`); -console.log(`Path segment separator is "${path.sep}"`); - -console.log(`Path to current file is ${__filename}`); -console.log(`Path to current directory is ${__dirname}`); - -export const myServer = createServerHttp((_, res) => { - res.end('Request accepted'); -}); - -const PORT = 3000; - -console.log(unknownObject); - -myServer.listen(PORT, () => { - console.log(`Server is listening on port ${PORT}`); - console.log('To terminate it, use Ctrl+C combination'); -}); - \ No newline at end of file diff --git a/src/modules/files/a.json b/src/modules/files/a.json deleted file mode 100644 index 7878a20..0000000 --- a/src/modules/files/a.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "a": 1, - "b": 2, - "c": 3 -} \ No newline at end of file diff --git a/src/modules/files/b.json b/src/modules/files/b.json deleted file mode 100644 index 5214b8c..0000000 --- a/src/modules/files/b.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "a": 11, - "b": 22, - "c": 33 -} \ No newline at end of file diff --git a/src/modules/files/c.js b/src/modules/files/c.js deleted file mode 100644 index ac09fab..0000000 --- a/src/modules/files/c.js +++ /dev/null @@ -1 +0,0 @@ -console.log('Hello from c.js!'); \ No newline at end of file diff --git a/src/streams/files/fileToWrite.txt b/src/streams/files/fileToWrite.txt index d8e82b3..13bccbf 100644 --- a/src/streams/files/fileToWrite.txt +++ b/src/streams/files/fileToWrite.txt @@ -1 +1,2 @@ -will you write +yes +ls diff --git a/src/streams/read.js b/src/streams/read.js index 447fb94..4dcc53a 100644 --- a/src/streams/read.js +++ b/src/streams/read.js @@ -1,8 +1,9 @@ import fs, { ReadStream } from 'fs' +import { STREAM_ERRROR } from '../constants/global.js'; -const file = 'src/streams/files/fileToRead.txt' +//const file = 'src/streams/files/fileToRead.txt' -const read = async () => { +export const readByStream = async (file) => { const readStream = fs.createReadStream(file); @@ -16,9 +17,9 @@ const read = async () => { }) readStream.on('error', (err) =>{ - console.error("Stream operation failed", err); + console.error(STREAM_ERRROR, err); }); }; -await read(); \ No newline at end of file +//await read(file); \ No newline at end of file diff --git a/src/streams/write.js b/src/streams/write.js index 9d78758..009edf6 100644 --- a/src/streams/write.js +++ b/src/streams/write.js @@ -1,28 +1,26 @@ import fs from 'fs' -const file = 'src/streams/files/fileToWrite.txt' +import {GENERIC_ERROR, STREAM_ERRROR} from '../constants/global.js' -const write = async () => { - - const writeStream = fs.createWriteStream(file); +export const writeBySteam = async (destination, args) => { + const writableStream = fs.createWriteStream(destination, args); let data = ''; - process.stdin.on('data', (chunk) => { - writeStream.write(chunk); + writableStream.write(chunk); }); process.stdin.on('end', () => { - writeStream.end(); + writableStream.end(); + }); + + process.stdin.on('error', () => { + throw new Error(GENERIC_ERROR) }); - writeStream.on('error', (err) => { - console.error("Stream operation failed", err); + writableStream.on('error', (err) => { + throw new Error(STREAM_ERRROR); } ) - }; - - -await write(); \ No newline at end of file diff --git a/src/zip/compress.js b/src/zip/compress.js index 2eaa22e..8581ed5 100644 --- a/src/zip/compress.js +++ b/src/zip/compress.js @@ -1,9 +1,51 @@ -import fs from 'fs'; -import zlib from 'zlib'; -const source = 'src/zip/files/fileToCompress.txt' -const destination = 'src/zip/files/archive.gz' +import fs from 'node:fs/promises'; +import { brotliCompress as _bC, brotliDecompress as _bDC } from "node:zlib"; +import path from 'node:path'; +import { promisify } from 'node:util'; +import { COMPRESS_ERROR } from '../constants/global.js'; +const brotliCompress = promisify(_bC); +const brotliDecompress = promisify(_bDC); + + + + +export const compressBrotli = async (currentDir, fileName) => { + try { + const filePath = path.join(currentDir, fileName); + const fileContents = await fs.readFile(filePath); + const compressedData = await brotliCompress(fileContents); + const compressedFilePath = `${filePath}.br`; + await fs.writeFile(compressedFilePath, compressedData); + console.log(`${fileName} has been compressed.`); + + } catch (error) { + console.error(COMPRESS_ERROR, error); + } +} + +export const decompressBrotli = async (currentDir, fileName) => { + try { + const compressedFilePath = path.join(currentDir, fileName); + const compressedData = await fs.readFile(compressedFilePath); + + + const decompressedData = await brotliDecompress(compressedData); + const filePath = compressedFilePath.slice(0,-3); // remove '.br' + await fs.writeFile(filePath, decompressedData); + console.log(`${fileName} has been decompressed.`); + + } catch (error) { + console.error(COMPRESS_ERROR, error); + } +} + + + + + +// someday this will be done too const compress = async () => { const readableStream = fs.createReadStream(source); const writableStream = fs.createWriteStream(destination); @@ -21,4 +63,22 @@ const compress = async () => { }); }; -await compress(); \ No newline at end of file + +const decompress = async () => { + const readableStream = fs.createReadStream(source); + const writableStream = fs.createWriteStream(destination); + const gunzip = zlib.createGunzip(); + + readableStream + .pipe(gunzip) // decompress data + .pipe(writableStream) + .on('finish', () => { + console.log('File has been decompressed'); + }); + + readableStream.on('error', (err) => { + console.error('Zip error', err); + }); +}; + + diff --git a/src/zip/decompress.js b/src/zip/decompress.js deleted file mode 100644 index ce5a6c2..0000000 --- a/src/zip/decompress.js +++ /dev/null @@ -1,24 +0,0 @@ -import fs from 'fs'; -import zlib from 'zlib'; -const destination = 'src/zip/files/fileToCompress.txt' -const source = 'src/zip/files/archive.gz' - - -const decompress = async () => { - const readableStream = fs.createReadStream(source); - const writableStream = fs.createWriteStream(destination); - const gunzip = zlib.createGunzip(); - - readableStream - .pipe(gunzip) // decompress data - .pipe(writableStream) - .on('finish', () => { - console.log('File has been decompressed'); - }); - - readableStream.on('error', (err) => { - console.error('Zip error', err); - }); -}; - -await decompress(); \ No newline at end of file