From 9a2a7907ab9ba3239481985735847da7e9fd834e Mon Sep 17 00:00:00 2001 From: Valery Klapatsiuk Date: Sun, 12 Jun 2022 22:40:51 +0200 Subject: [PATCH 1/2] init: node.js file manager --- .gitignore | 1 + README.md | 4 ++ commands/file/copy.mjs | 28 +++++++++++++ commands/file/create.mjs | 21 ++++++++++ commands/file/delete.mjs | 18 +++++++++ commands/file/fileCommandsHandler.mjs | 33 +++++++++++++++ commands/file/list.mjs | 12 ++++++ commands/file/move.mjs | 32 +++++++++++++++ commands/file/read.mjs | 23 +++++++++++ commands/file/rename.mjs | 15 +++++++ commands/misc/hash/calcHash.mjs | 19 +++++++++ commands/misc/miscCommandsHandler.mjs | 25 ++++++++++++ commands/misc/os/osCommands.mjs | 16 ++++++++ commands/misc/os/osCommandsHandler.mjs | 25 ++++++++++++ commands/misc/zip/compress.mjs | 23 +++++++++++ commands/misc/zip/decompress.mjs | 24 +++++++++++ commands/nav/navCommandsHandler.mjs | 21 ++++++++++ index.js | 56 ++++++++++++++++++++++++++ package.json | 23 +++++++++++ utils/constants.mjs | 30 ++++++++++++++ utils/fsUtils.mjs | 17 ++++++++ utils/handleInput.mjs | 10 +++++ utils/parseArgs.mjs | 5 +++ utils/validateInput.mjs | 24 +++++++++++ 24 files changed, 505 insertions(+) create mode 100644 commands/file/copy.mjs create mode 100644 commands/file/create.mjs create mode 100644 commands/file/delete.mjs create mode 100644 commands/file/fileCommandsHandler.mjs create mode 100644 commands/file/list.mjs create mode 100644 commands/file/move.mjs create mode 100644 commands/file/read.mjs create mode 100644 commands/file/rename.mjs create mode 100644 commands/misc/hash/calcHash.mjs create mode 100644 commands/misc/miscCommandsHandler.mjs create mode 100644 commands/misc/os/osCommands.mjs create mode 100644 commands/misc/os/osCommandsHandler.mjs create mode 100644 commands/misc/zip/compress.mjs create mode 100644 commands/misc/zip/decompress.mjs create mode 100644 commands/nav/navCommandsHandler.mjs create mode 100644 index.js create mode 100644 package.json create mode 100644 utils/constants.mjs create mode 100644 utils/fsUtils.mjs create mode 100644 utils/handleInput.mjs create mode 100644 utils/parseArgs.mjs create mode 100644 utils/validateInput.mjs diff --git a/.gitignore b/.gitignore index 6704566..a6b984d 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,4 @@ dist # TernJS port file .tern-port +.DS_Store diff --git a/README.md b/README.md index 5f4f3cf..3493d13 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # node-js-file-manager File Manager using Node.js APIs + +1. Assignment: https://github.com/AlreadyBored/nodejs-assignments/blob/main/assignments/file-manager/assignment.md +2. Done: 11.06.2022 / Deadline: 11.06.2022 23.59 +3. Self Score: Ваша самопроверка с предварительной оценкой (должны быть пункты из требований кросс-чека (реализованный функционал, а также штрафы) с указанием того, какие из пунктов были выполнены в работе)). diff --git a/commands/file/copy.mjs b/commands/file/copy.mjs new file mode 100644 index 0000000..98df9b2 --- /dev/null +++ b/commands/file/copy.mjs @@ -0,0 +1,28 @@ +import { createReadStream, createWriteStream } from 'node:fs'; +import { exists } from '../../utils/fsUtils.mjs' +import { errMsg } from '../../utils/constants.mjs'; + +export const copy = async (filePath, fileCopyPath) => { + if (!exists(fileCopyPath)) {; + const readable = createReadStream(filePath); + const writable = createWriteStream(fileCopyPath); + + readable.on('error', (err) => { + errMsg(); + return + }); + + writable.on('error', (err) => { + errMsg(); + readable.destroy(); + writable.end(); + return; + }); + + writable.on('finish', () => { + console.log(`${filePath} was copied successfully`) + }); + + readable.pipe(writable); + } + }; diff --git a/commands/file/create.mjs b/commands/file/create.mjs new file mode 100644 index 0000000..00dcced --- /dev/null +++ b/commands/file/create.mjs @@ -0,0 +1,21 @@ +import { createWriteStream } from 'node:fs'; +import path from 'node:path'; +import { cwd } from 'node:process'; +import { exists } from '../../utils/fsUtils.mjs' +import { errMsg } from '../../utils/constants.mjs'; + +export const create = async (fileName) => { + const filePath = path.join(cwd(), fileName); + + if (exists(fileName)) { + console.log(errMsg); + return; + } else { + const writable = createWriteStream(filePath); + writable.write(''); + writable.end(); + writable.on('finish', () => { + console.log(`${fileName} was created`); + }) + } +}; diff --git a/commands/file/delete.mjs b/commands/file/delete.mjs new file mode 100644 index 0000000..59268df --- /dev/null +++ b/commands/file/delete.mjs @@ -0,0 +1,18 @@ +import { rm } from 'node:fs'; +import { errMsg } from '../../utils/constants.mjs'; +import { exists } from '../../utils/fsUtils.mjs'; + +export const remove = async (fileToRemove) => { + if (!exists(fileToRemove)) { + errMsg(); + return; + } + + rm(fileToRemove, (err) => { + if (err) { + errMsg(); + } else { + console.log(`${fileToRemove} was deleted`); + } + }) +}; diff --git a/commands/file/fileCommandsHandler.mjs b/commands/file/fileCommandsHandler.mjs new file mode 100644 index 0000000..0272ce5 --- /dev/null +++ b/commands/file/fileCommandsHandler.mjs @@ -0,0 +1,33 @@ +import { read } from './read.mjs'; +import { create } from './create.mjs'; +import { rename } from './rename.mjs'; +import { copy } from './copy.mjs' +import { move } from './move.mjs' +import { remove } from './delete.mjs' +import { invalidMsg } from '../../utils/constants.mjs' + +export const handleFileCommand = (command, args) => { + switch (command) { + case 'cat': + read(args); + break; + case 'add': + create(args); + break; + case 'rn': + rename(args[0], args[1]); + break; + case 'cp': + copy(args[0], args[1]); + break; + case 'mv': + move(args[0], args[1]); + break; + case 'rm': + remove(args); + break; + default: + console.log(invalidMsg); + return; + } +} diff --git a/commands/file/list.mjs b/commands/file/list.mjs new file mode 100644 index 0000000..38a1ed1 --- /dev/null +++ b/commands/file/list.mjs @@ -0,0 +1,12 @@ +import { readdirSync } from 'node:fs'; +import { errMsg } from '../../utils/constants.mjs'; + +export const list = async () => { + const fileList = readdirSync('.'); + + try { + console.log(fileList) ; + } catch (err) { + console.log(errMsg()); + } +}; diff --git a/commands/file/move.mjs b/commands/file/move.mjs new file mode 100644 index 0000000..05d6339 --- /dev/null +++ b/commands/file/move.mjs @@ -0,0 +1,32 @@ +import { createReadStream, createWriteStream, rm } from 'node:fs'; +import { exists } from '../../utils/fsUtils.mjs' +import { errMsg } from '../../utils/constants.mjs'; + +export const move = async (filePath, fileCopyPath) => { + if (!exists(fileCopyPath)) errMsg(); + + const readable = createReadStream(filePath, {encoding: 'utf8'}); + const writable = createWriteStream(fileCopyPath); + + readable.on('error', () => { + errMsg(); + }); + + writable.on('error', () => { + errMsg(); + readable.destroy(); + writable.end(); + }); + + writable.on('finish', (err) => { + if (err) errMsg(); + + rm(filePath, (err) => { + if (err) errMsg(); + }); + + console.log(`${filePath} was moved successfully`) + }); + + readable.pipe(writable); +}; diff --git a/commands/file/read.mjs b/commands/file/read.mjs new file mode 100644 index 0000000..d94f9ed --- /dev/null +++ b/commands/file/read.mjs @@ -0,0 +1,23 @@ +import { createReadStream } from 'node:fs'; +import { exists } from '../../utils/fsUtils.mjs'; +import { errMsg } from '../../utils/constants.mjs'; + +export const read = async (filePath) => { + if (!exists(filePath)) { + console.log(errMsg()); + return; + } else { + const readable = createReadStream(filePath, 'utf8'); + + readable + .on('data', (chunk) => { + console.log(chunk); + }) + .on('end', () => { + console.log('end-of-file'); + }) + .on('error', () => { + console.log(errMsg()); + }) + } +}; diff --git a/commands/file/rename.mjs b/commands/file/rename.mjs new file mode 100644 index 0000000..0ee13dd --- /dev/null +++ b/commands/file/rename.mjs @@ -0,0 +1,15 @@ +import fs from 'node:fs'; +import { exists } from '../../utils/fsUtils.mjs' +import { errMsg } from '../../utils/constants.mjs'; + +export const rename = async (srcFileName, destFileName) => { + if (exists(destFileName)) { + console.log(errMsg()); + return; + } else { + fs.rename(srcFileName, destFileName, (err) => { + if (err) { console.log(errMsg()) } + else { console.log('File was renamed') }; + }); + } +}; diff --git a/commands/misc/hash/calcHash.mjs b/commands/misc/hash/calcHash.mjs new file mode 100644 index 0000000..835e2fb --- /dev/null +++ b/commands/misc/hash/calcHash.mjs @@ -0,0 +1,19 @@ +import { createHash } from 'node:crypto'; +import { createReadStream } from 'node:fs'; + +export const calcHash = async (filePath) => { + const readable = createReadStream(filePath, 'utf8'); + const hash = createHash('sha256'); + hash.setEncoding('hex'); + + readable + .on('end', () => { + hash.end(); + console.log(hash.read()); + }) + .on('error', () => { + console.log(errMsg()); + }) + + readable.pipe(hash); +} diff --git a/commands/misc/miscCommandsHandler.mjs b/commands/misc/miscCommandsHandler.mjs new file mode 100644 index 0000000..3886b1f --- /dev/null +++ b/commands/misc/miscCommandsHandler.mjs @@ -0,0 +1,25 @@ +import { handleOsCommand } from './os/osCommandsHandler.mjs'; +import { compress} from './zip/compress.mjs' +import { decompress} from './zip/decompress.mjs' +import { invalidMsg } from '../../utils/constants.mjs' +import { calcHash } from './hash/calcHash.mjs'; + +export const handleMiscCommand = (command, args) => { + switch (command) { + case 'os': + handleOsCommand(args); + break; + case 'hash': + calcHash(args); + break; + case 'compress': + compress(args[0], args[1]); + break; + case 'decompress': + decompress(args[0], args[1]); + break; + default: + invalidMsg(); + return; + } +} diff --git a/commands/misc/os/osCommands.mjs b/commands/misc/os/osCommands.mjs new file mode 100644 index 0000000..8a3b574 --- /dev/null +++ b/commands/misc/os/osCommands.mjs @@ -0,0 +1,16 @@ +import { EOL, cpus, userInfo } from "os"; +import { arch } from 'node:process'; + +const eolInfo = () => console.log(JSON.stringify(EOL)); + +const cpusInfo = () => { + console.log(`Number of CPUs: ${cpus().length}`) + console.log(cpus().map(cpu => `${cpu.model}, clock rate: ${cpu.speed}`)); +} +const homedirInfo = () => console.log(userInfo().homedir); + +const usernameInfo = () => console.log(userInfo().username); + +const archInfo = () => console.log(arch); + +export { eolInfo, cpusInfo, homedirInfo, usernameInfo, archInfo } diff --git a/commands/misc/os/osCommandsHandler.mjs b/commands/misc/os/osCommandsHandler.mjs new file mode 100644 index 0000000..05437d0 --- /dev/null +++ b/commands/misc/os/osCommandsHandler.mjs @@ -0,0 +1,25 @@ +import { eolInfo, cpusInfo, homedirInfo, usernameInfo, archInfo } from './osCommands.mjs' +import { invalidMsg } from "../../../utils/constants.mjs"; + +export const handleOsCommand = (input) => { + switch (input.slice(2)) { + case 'EOL': + eolInfo(); + break; + case 'cpus': + cpusInfo(); + break; + case 'homedir': + homedirInfo(); + break; + case 'username': + usernameInfo(); + break; + case 'architecture': + archInfo(); + break; + default: + invalidMsg(); + break; + }; +} diff --git a/commands/misc/zip/compress.mjs b/commands/misc/zip/compress.mjs new file mode 100644 index 0000000..1231609 --- /dev/null +++ b/commands/misc/zip/compress.mjs @@ -0,0 +1,23 @@ +import { createBrotliCompress } from 'node:zlib'; +import { createWriteStream, createReadStream } from 'node:fs'; +import { errMsg } from '../../../utils/constants.mjs'; +import { exists } from '../../../utils/fsUtils.mjs' + +export const compress = async (fileName, archName) => { + if (!exists(fileName)) return errMsg(); + + const readable = createReadStream(fileName); + const writable = createWriteStream(archName); + + const brotli = createBrotliCompress(); + const stream = readable.pipe(brotli).pipe(writable); + + stream + .on('finish', () => { + console.log(`${archName} was successfully created`) + }) + .on('error', () => { + errMsg(); + }); + +}; diff --git a/commands/misc/zip/decompress.mjs b/commands/misc/zip/decompress.mjs new file mode 100644 index 0000000..bf13c8d --- /dev/null +++ b/commands/misc/zip/decompress.mjs @@ -0,0 +1,24 @@ +import { createBrotliDecompress } from 'node:zlib'; +import { createWriteStream, createReadStream } from 'node:fs'; +import { exists } from '../../../utils/fsUtils.mjs'; +import { create } from '../../file/create.mjs' +import { errMsg } from '../../../utils/constants.mjs'; + +export const decompress = async (archName, fileName) => { + if (!exists(fileName)) create(fileName); + else if (!exists(archName)) return errMsg(); + + const readable = createReadStream(archName); + const writable = createWriteStream(fileName); + + const brotli = createBrotliDecompress(); + const stream = readable.pipe(brotli).pipe(writable); + + stream + .on('finish', () => { + console.log(`${archName} was successfully decompressed`) + }) + .on('error', () => { + errMsg(); + }); +}; diff --git a/commands/nav/navCommandsHandler.mjs b/commands/nav/navCommandsHandler.mjs new file mode 100644 index 0000000..6e8a7a2 --- /dev/null +++ b/commands/nav/navCommandsHandler.mjs @@ -0,0 +1,21 @@ +import { chdir, cwd } from 'node:process'; +import { list } from '../file/list.mjs'; +import { sep } from 'node:path'; +import { errMsg, invalidMsg } from '../../utils/constants.mjs' + +export const handleNavCommand = (command, arg) => { + switch (command) { + case 'up': + chdir('..' + sep); + break; + case 'ls': + list(cwd()); + break; + case 'cd': + try { chdir(cwd() + sep + arg); } catch { errMsg() }; + break; + default: + invalidMsg(); + return; + } +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..18b6c18 --- /dev/null +++ b/index.js @@ -0,0 +1,56 @@ +import { stdin as input, stdout as output } from 'node:process'; +import { chdir, cwd } from 'node:process'; +import * as readline from 'node:readline'; +import { handleInput } from './utils/handleInput.mjs'; +import { valid } from './utils/validateInput.mjs'; +import { handleNavCommand } from './commands/nav/navCommandsHandler.mjs'; +import { handleFileCommand } from './commands/file/fileCommandsHandler.mjs'; +import { handleMiscCommand } from './commands/misc/miscCommandsHandler.mjs'; +import { greetMsg, leaveMsg, invalidMsg, currentDirMsg, userHomeDir } from './utils/constants.mjs'; +import { navCommands, fileCommands, miscCommands, exitCommands } from './utils/constants.mjs' + +const cli = readline.createInterface({ input, output, prompt: '> ' }); +cli.write(chdir(userHomeDir)); + + +export const start = async () => { + cli.write(greetMsg); + console.log(currentDirMsg + cwd() + '\n'); + cli.prompt(); + cli.on('line', (line) => { + if (!valid(line)) { + invalidMsg(); + console.log(currentDirMsg + cwd()); + cli.prompt(); + return; + } + + const command = line.trim().split(' ').shift(); + const args = handleInput(line); + + if (navCommands.includes(command)) { + handleNavCommand(command, args); + } + else if (fileCommands.includes(command)) { + handleFileCommand(command, args); + } + else if (miscCommands.includes(command)) { + handleMiscCommand(command, args); + } + else if (exitCommands.includes(command)) { + cli.close(); + } + else { + console.log(invalidMsg); + }; + + console.log(currentDirMsg + cwd()); + cli.prompt(); + }) + .on('close', () => { + console.log(leaveMsg); + process.exit(0); + }); +} + +start(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..b8d7239 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "js-file-manager", + "version": "0.0.1", + "description": "File Manager using Node.js APIs", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/va259/node-js-file-manager.git" + }, + "keywords": [ + "node" + ], + "author": "va259", + "license": "UNLICENSED", + "bugs": { + "url": "https://github.com/va259/node-js-file-manager/issues" + }, + "homepage": "https://github.com/va259/node-js-file-manager#readme" +} diff --git a/utils/constants.mjs b/utils/constants.mjs new file mode 100644 index 0000000..02ce588 --- /dev/null +++ b/utils/constants.mjs @@ -0,0 +1,30 @@ +import { cwd } from 'node:process'; +import { homedir } from 'node:os'; +import path from 'node:path' +import { parseArgs } from './parseArgs.mjs'; + +const allCommands = ['.exit', 'SIGNINT', 'up', 'cd', 'ls', + 'cat', 'add', 'rn', 'cp', 'mv', 'rm', + 'os', 'hash', 'compress', 'decompress']; +const navCommands = ['up', 'cd', 'ls']; +const fileCommands = ['cat', 'add', 'rn', 'cp', 'mv', 'rm']; +const miscCommands = ['os', 'hash', 'compress', 'decompress']; +const exitCommands = ['.exit', 'SIGNINT']; +const noArgsCommands = ['.exit', 'SIGNINT', 'up', 'ls']; +const oneArgCommands = ['cd', 'cat', 'add', 'rm', 'os', 'hash']; +const twoArgsCommands = ['rn', 'cp', 'mv', 'compress', 'decompress']; + +const username = parseArgs(); +const greetMsg = `Welcome to the File Manager, ${username}` + '\n'; +const leaveMsg = `Thank you for using File Manager, ${username}` + '\n'; +const currentDirMsg = 'You are currently in: '; +const invalidMsg = () => console.log('Invalid input'); +const errMsg = () => console.log('Operation failed'); +const promptMsg = 'You are currently in: '; +const baseDirName = path.basename(cwd()); +const userHomeDir = homedir(); + +export { greetMsg, leaveMsg, currentDirMsg, invalidMsg, errMsg, promptMsg } +export { username, baseDirName, userHomeDir }; +export { navCommands, fileCommands, miscCommands, exitCommands } +export { allCommands, noArgsCommands, oneArgCommands, twoArgsCommands } diff --git a/utils/fsUtils.mjs b/utils/fsUtils.mjs new file mode 100644 index 0000000..e691ea6 --- /dev/null +++ b/utils/fsUtils.mjs @@ -0,0 +1,17 @@ +import { existsSync } from 'node:fs'; +import path from 'node:path' +import { fileURLToPath } from 'url'; + +const exists = (filePath) => { + return existsSync(filePath); +}; + +const getPath = (url, filename) => { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const filePath = path.join(__dirname, filename); + return filePath +} + +export { exists, getPath }; + diff --git a/utils/handleInput.mjs b/utils/handleInput.mjs new file mode 100644 index 0000000..aeee2f9 --- /dev/null +++ b/utils/handleInput.mjs @@ -0,0 +1,10 @@ +export const handleInput = (input) => { + const args = input.trim().split(' '); + + if (args.length === 2) { // 1 argument + return args.slice(1).toString(); + } + else if (args.length === 3) { // 2 arguments + return args.slice(1); + } +}; diff --git a/utils/parseArgs.mjs b/utils/parseArgs.mjs new file mode 100644 index 0000000..6e02802 --- /dev/null +++ b/utils/parseArgs.mjs @@ -0,0 +1,5 @@ +export const parseArgs = () => { + const args = process.argv.slice(2).toString().trim(); + if (args.startsWith('--username')) return args.split('=').pop() + else if (args.startsWith('--')) return args.slice(1) +}; diff --git a/utils/validateInput.mjs b/utils/validateInput.mjs new file mode 100644 index 0000000..b9b9695 --- /dev/null +++ b/utils/validateInput.mjs @@ -0,0 +1,24 @@ +import { allCommands, noArgsCommands, oneArgCommands, twoArgsCommands } from './constants.mjs' + +export const valid = (line) => { + if (line) { + const command = line.trim().split(' ')[0]; + const args = line.trim().split(' ').slice(1); + + if (!allCommands.includes(command)) { + return false; + } + else if (noArgsCommands.includes(command) && (args.length !== 0)) { + return false; + } + else if (oneArgCommands.includes(command) && (args.length !== 1)) { + return false; + } + else if (twoArgsCommands.includes(command) && (args.length !== 2)) { + return false; + } + else { + return true; + } + } +} From a4e4353a55bf2e5e68812dea25046b74f56498f6 Mon Sep 17 00:00:00 2001 From: Valery Klapatsiuk Date: Sun, 12 Jun 2022 22:58:13 +0200 Subject: [PATCH 2/2] docs: README.md edit --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 3493d13..5f4f3cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,2 @@ # node-js-file-manager File Manager using Node.js APIs - -1. Assignment: https://github.com/AlreadyBored/nodejs-assignments/blob/main/assignments/file-manager/assignment.md -2. Done: 11.06.2022 / Deadline: 11.06.2022 23.59 -3. Self Score: Ваша самопроверка с предварительной оценкой (должны быть пункты из требований кросс-чека (реализованный функционал, а также штрафы) с указанием того, какие из пунктов были выполнены в работе)).