diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..755c365b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "node-nodejs-fundamentals", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-nodejs-fundamentals", + "version": "1.0.0", + "license": "ISC", + "engines": { + "node": ">=24.10.0", + "npm": ">=10.9.2" + } + } + } +} diff --git a/src/cli/interactive.js b/src/cli/interactive.js index d0e3e0d9..f3a6af83 100644 --- a/src/cli/interactive.js +++ b/src/cli/interactive.js @@ -1,8 +1,42 @@ +import { createInterface } from 'node:readline/promises'; +import { stdin, stdout } from 'node:process'; + const interactive = () => { - // Write your code here - // Use readline module for interactive CLI - // Support commands: uptime, cwd, date, exit - // Handle Ctrl+C and unknown commands + const rl = createInterface({ + input: stdin, + output: stdout, + }); + + rl.setPrompt('> '); + rl.prompt(); + + rl.on('line', (cmd) => { + switch(cmd) { + case 'uptime': + const processUptime = process.uptime(); + console.log(`Uptime: ${processUptime}`); + break; + case 'cwd': + const currDir = process.cwd(); + console.log(currDir.split('/')[currDir.split('/').length-1]); + break; + case 'date': + const currentDate = new Date(); + const ISOstring = currentDate.toISOString(); + console.log(ISOstring); + break; + case 'exit': + console.log('Goodbye!'); + process.exit(0); + default: + console.log('Unknown command'); + } + }) + + rl.on('SIGINT', () => { + console.log('Goodbye!'); + rl.close(); + }); }; interactive(); diff --git a/src/cli/progress.js b/src/cli/progress.js index 3e060763..61efb4ce 100644 --- a/src/cli/progress.js +++ b/src/cli/progress.js @@ -1,8 +1,54 @@ +import { parseArgs } from 'node:util'; +import { hexToAsnii, validateValueType } from '../utils/util.js'; + const progress = () => { - // Write your code here - // Simulate progress bar from 0% to 100% over ~5 seconds - // Update in place using \r every 100ms - // Format: [████████████████████ ] 67% + const moderatedArgv = process.argv.slice(2); + const { values } = parseArgs({ + moderatedArgv, + options: { + duration: { + type: 'string', + default: '5000', + }, + interval: { + type: 'string', + default: '100', + }, + length: { + type: 'string', + default: '30', + }, + }, + strict: false, + allowPositionals: true + }) + if (values.color) { + values.color = moderatedArgv[moderatedArgv.indexOf('--color') + 1]; + } + + validateValueType(values); + + const fullWidth = parseInt(values.length); + const interv = parseInt(values.interval); + const steps = parseInt(values.duration) / parseInt(values.interval); + let currentStep = 0; + + const progressFill = setInterval(() => { + currentStep++; + const progress = currentStep / steps; + const percent = Math.round(progress * 100); + const filledCnt = Math.round(fullWidth * progress); + + const asni = hexToAsnii(values.color); + const filled = asni + '█'.repeat(filledCnt) + '\x1b[0m'; + const empty = " ".repeat(fullWidth - filledCnt); + process.stdout.write(`\r[${filled}${empty}] ${percent}%`); + + if (currentStep >= steps) { + clearInterval(progressFill); + process.stdout.write('\nDone!\n'); + } + }, interv); }; progress(); diff --git a/src/cp/execCommand.js b/src/cp/execCommand.js index 34a89c8d..e89b0b54 100644 --- a/src/cp/execCommand.js +++ b/src/cp/execCommand.js @@ -1,10 +1,41 @@ +import { spawn } from 'child_process'; + const execCommand = () => { - // Write your code here - // Take command from CLI argument - // Spawn child process - // Pipe child stdout/stderr to parent stdout/stderr - // Pass environment variables - // Exit with same code as child + const arg = process.argv.slice(2); + + const cmdAndArgs = arg[0].split(" "); + console.log(cmdAndArgs, 'sdsd') + const [childCMD, ...childCMDComment] = cmdAndArgs; + let cmd; + + console.log(childCMDComment); + if (childCMDComment.length) { + cmd = spawn(childCMD, [...childCMDComment], { + stdio: 'inherit', + env: process.env, + }); + } + + cmd = spawn(childCMD); + + cmd.stdout.on('data', (data) => { + process.stdout.write(`${data} from child`); + }); + + cmd.stderr.on('data', (data) => { + console.log(data, 'p') + process.stderr.write(`Error look: ${data}`); + }); + + cmd.on('exit', (code, signal) => { + if (code !== null) { + process.exit(code); + } else { + cmd.close('close', (code) => { + process.exit(code); + }) + } + }); }; execCommand(); diff --git a/src/fs/findByExt.js b/src/fs/findByExt.js index 24f06cb8..63c9a24b 100644 --- a/src/fs/findByExt.js +++ b/src/fs/findByExt.js @@ -1,7 +1,77 @@ -const findByExt = async () => { - // Write your code here - // Recursively find all files with specific extension - // Parse --ext CLI argument (default: .txt) +import { parseArgs } from 'node:util'; +import path from 'path'; +import fs from 'fs'; +const extFromCLI = process.argv.slice(2); +const { values } = parseArgs({ + extFromCLI, + options: { + ext: { + type: 'string', + default: 'text' + } + } +}); +let deepestPath = ''; + + +const findByExt = async (paths, fileArr) => { + const dir = paths ? paths : './workspace'; + isWorksapceExisted(dir); + const fileList = fileArr ? fileArr : []; + + try { + const files = fs.readdirSync(dir, { withFileTypes: true }); + + if (!files || !files.length) { + throw new Error('File not found!') + } + + for await (const file of files) { + const fullPath = file.parentPath + '/' + file.name; + + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + await findByExt(fullPath, fileList); + } else if (stat.isFile() + && path.basename(fullPath).split('.')[path.basename(fullPath).split('.').length - 1] === values.ext) { + fileList.push(fullPath); + deepestPath = deepestPath.split('/').length > fullPath.split('/') ? deepestPath : fullPath; + } + } + } catch (error) { + console.error(Error(error)); + } + + return fileList; }; -await findByExt(); +const listArr = await findByExt(); + +for (const path of listArr) { + const paths = relativePaths(path); + paths.forEach((el) => { + console.log(`\n${el}`) + }) +} + +async function isWorksapceExisted(path) { + const errorMsg = 'FS operation failed'; + try { + fs.accessSync(path); + } catch (error) { + if (error.code === 'ENOENT') { + console.error(Error(errorMsg)); + } + console.error(Error(error)); + } +} + +function relativePaths(path) { + const deepestPathSet = new Set(); + let pathStepByStep = ''; + for (let i = 0; i < path.split('/').length - 1; i++) { + pathStepByStep = pathStepByStep + deepestPath.split('/')[i] + '/'; + deepestPathSet.add(pathStepByStep) + } + return deepestPathSet; +} \ No newline at end of file diff --git a/src/fs/merge.js b/src/fs/merge.js index cb8e0d8f..95f5d5b0 100644 --- a/src/fs/merge.js +++ b/src/fs/merge.js @@ -1,8 +1,53 @@ +import path from 'path'; +import fs from 'fs'; + +// need to redo + const merge = async () => { - // Write your code here - // Default: read all .txt files from workspace/parts in alphabetical order - // Optional: support --files filename1,filename2,... to merge specific files in provided order - // Concatenate content and write to workspace/merged.txt + const dir = './src/cp'; + const argvs = process.argv.slice(2); + let listFiles = []; + + try { + if (argvs[0] === '--files' && argvs[1]) { + listFiles = process.argv[1].split(','); + + for (const file of listFiles) { + console.log(dir, file.name) + fs.accessSync(path.join(dir, file.name)); + } + } else { + const files = fs.readdirSync(dir, { withFileTypes: true }); + + listFiles = files + .filter((file) => { + const fullPath = file.parentPath + '/' + file.name; + const stat = fs.statSync(fullPath); + console.log(fullPath) + if (stat.isFile() + && path.basename(fullPath).split('.')[path.basename(fullPath).split('.').length - 1] === 'js') { + return file; + } + }).sort(); + + if (listFiles.length === 0) { + console.error(Error('No text file found!')); + } + } + + for (const file of listFiles) { + const content = fs.readFileSync(path.join(dir, file.name), 'utf-8'); + process.stdout.write(content + '\n'); + + } + } catch (error) { + console.error(Error(error)); + } }; await merge(); + +processFiles().catch(err => { + console.error(err.message); + process.exit(1); +}); \ No newline at end of file diff --git a/src/fs/restore.js b/src/fs/restore.js index 96ae1ffb..f20e50f5 100644 --- a/src/fs/restore.js +++ b/src/fs/restore.js @@ -1,8 +1,61 @@ +import fs, { constants } from 'node:fs/promises'; + const restore = async () => { - // Write your code here - // Read snapshot.json - // Treat snapshot.rootPath as metadata only - // Recreate directory/file structure in workspace_restored + const dir = process.cwd(); + const jsonDir = dir + '/snapshot.json'; + isJSONEXist(jsonDir); + + try { + await fs.mkdir(dir + '/workspace_restored'); + } catch (error) { + if (error === 'ENOENT') { + console.error(Error('FS operation failed')); + } + } + + try { + const data = await fs.readFile('./snapshot.json', 'utf8'); + const content = JSON.parse(data); + + for (const entry of content.entries) { + let pathFromJSON = content.rootPath; + + pathFromJSON = pathFromJSON.split('/workspace')[0] +'/workspace_restored/' + entry.path; + + if (entry.type === 'directory') { + await fs.mkdir(pathFromJSON); + } else if (entry.type === 'file') { + const base64String = entry.content; + const buffer = Buffer.from(base64String, 'base64'); + await fs.writeFile(pathFromJSON, buffer); + + } + } + + } catch (error) { + if (error === 'ENOENT') { + console.error(Error('FS operation failed')); + } else if (error === 'EACCES') { + console.error(Error("Access Denied!")); + } else { + console.error(Error(error)) + } + } }; await restore(); + + +async function isJSONEXist(path) { + const errorMsg = 'FS operation failed'; + try { + await fs.access(path, constants.R_OK); + } catch (error) { + if (error.code === 'ENOENT') { + console.error(Error(errorMsg)); + } else if (error.code === 'EACCES') { + console.error(Error('Access Denied!')); + } + console.error(Error(error)); + } +} \ No newline at end of file diff --git a/src/fs/snapshot.js b/src/fs/snapshot.js index 050103d3..cd607460 100644 --- a/src/fs/snapshot.js +++ b/src/fs/snapshot.js @@ -1,9 +1,70 @@ -const snapshot = async () => { - // Write your code here - // Recursively scan workspace directory - // Write snapshot.json with: - // - rootPath: absolute path to workspace - // - entries: flat array of relative paths and metadata +import fs, { constants } from 'node:fs/promises'; +const rootPath = process.cwd() + '/workspace'; + const readableFileContent = { + rootPath: rootPath, + entries: [] + }; + +const snapshot = async (paths) => { + const dir = paths ? paths : rootPath; + isWorksapceExisted(dir); + + try { + const files = await fs.readdir(dir, { withFileTypes: true }); + + for await (const file of files) { + const fullPath = dir + '/' + file.name; + + if (file.isDirectory()) { + await snapshot(fullPath); + readableFileContent.entries.push({ + path: dir.split(process.cwd() + '/workspace')[1], + type: 'directory', + }); + } else { + await fs.access(fullPath, constants.R_OK); + const content = (await fs.readFile(fullPath)).toString('base64'); + const size = (await fs.stat(fullPath)).size; + readableFileContent.entries.push({ + path: dir.split(process.cwd() + '/workspace')[1] + file.name, + type: 'file', + size: size, + content: content + }); + } + + fs.writeFile('./snapshot.json', JSON.stringify(readableFileContent, null, 2), 'utf8', (err) => { + if (err) { + console.error('Error: ', err); + } else { + console.log('Successfully completed!'); + } + }) + } + } catch (error) { + if (error === 'ENOENT') { + console.error(Error("FS operation failed")); + } else if (error === 'EACCES') { + console.error(Error("Access Denied!")); + } else { + console.error(Error(error)); + } + } }; await snapshot(); + + +async function isWorksapceExisted(path) { + const errorMsg = 'FS operation failed'; + try { + await fs.access(path, constants.R_OK); + } catch (error) { + if (error.code === 'ENOENT') { + console.error(Error(errorMsg)); + } else if (error.code === 'EACCES') { + console.error(Error('Access Denied!')); + } + console.error(Error(error)); + } +} \ No newline at end of file diff --git a/src/hash/verify.js b/src/hash/verify.js index 7f1e8961..2927cdb2 100644 --- a/src/hash/verify.js +++ b/src/hash/verify.js @@ -1,8 +1,43 @@ +import fs from 'node:fs/promises'; +import { pipeline } from 'node:stream/promises'; +import { createReadStream } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { isFileOrDirectoryExist } from '../utils/util.js'; + const verify = async () => { - // Write your code here - // Read checksums.json - // Calculate SHA256 hash using Streams API - // Print result: filename — OK/FAIL + const dir = process.cwd(); + const pathToFile = `${dir}/checksums.json`; + isFileOrDirectoryExist(pathToFile); + let content; + + const data = await fs.readFile('./checksums.json', 'utf8'); + content = JSON.parse(data); + + for (const [name, expectedHash] of Object.entries(content)) { + try { + await fs.access(`${dir}/${name}`); + } catch (error) { + console.error(error) + } + + try { + const createdHash = createHash('sha256'); + + const readStream = createReadStream(`${dir}/${name}`); + + await pipeline(readStream, createdHash); + const hashed = createdHash.digest('hex'); + + const isOK = hashed === expectedHash; + console.log(`${name} - ${isOK}`); + } catch (error) { + console.error(error) + } + } }; -await verify(); +try { + await verify(); +} catch (error) { + console.error(error) +} diff --git a/src/modules/dynamic.js b/src/modules/dynamic.js index 008ca387..5d5c1a53 100644 --- a/src/modules/dynamic.js +++ b/src/modules/dynamic.js @@ -1,9 +1,44 @@ +import fs from 'node:fs/promises'; + +const plugins = ['uppercase', 'reverse', 'repeat']; + const dynamic = async () => { - // Write your code here - // Accept plugin name as CLI argument - // Dynamically import plugin from plugins/ directory - // Call run() function and print result - // Handle missing plugin case + const dir = process.cwd() + '/src/modules'; + const argv = process.argv.slice(2); + if (!plugins.includes(argv[0])) { + console.error('Plugin not found!'); + process.exit(1); + } + let module; + let result; + + try { + await fs.access(`${dir}/plugins`); + const arrModulePromises = plugins.map(async (el) => { + await fs.access(`${dir}/plugins/${el}.js`); + return `./plugins/${el}.js`; + }); + + const arrModules = await Promise.all(arrModulePromises); + + for (const mod of arrModules) { + if (mod === `./plugins/${argv[0]}.js`) { + module = await import(mod); + break; + } + } + result = module.run(); + } catch (error) { + if (error === 'ENOENT') { + console.error(Error("FS operation failed")); + } else if (error === 'EACCES') { + console.error(Error("Access Denied!")); + } else { + console.error('Error: ', error) + } + } finally { + process.stdout.write(result+'\n'); + } }; await dynamic(); diff --git a/src/streams/customized/filterTranformStream.js b/src/streams/customized/filterTranformStream.js new file mode 100644 index 00000000..61f6b962 --- /dev/null +++ b/src/streams/customized/filterTranformStream.js @@ -0,0 +1,41 @@ +import { Transform } from 'node:stream'; +import { parseArgs } from 'node:util'; +const patternArgv = process.argv.slice(2); +const { values } = parseArgs({ + patternArgv, + options: { + pattern: { + type: 'string' + } + } +}); + +export class FilterByArgumentTranform extends Transform { + constructor(options) { + super(options); + this.buffer = ''; + } + + _transform(chunk, encoding, callback) { + this.buffer += chunk; + const lines = this.buffer.split('\n'); + + this.buffer = lines.pop(); + + for (const line of lines) { + if (line.includes(values.pattern)) { + this.push(`${line}\n`); + } + } + + callback(); + } + + _flush(callback) { + if (this.buffer.length > 0 && this.buffer.includes(values.pattern)) { + this.push(`${this.buffer}\n`); + } + + callback(); + } +} \ No newline at end of file diff --git a/src/streams/customized/numberTransformStream.js b/src/streams/customized/numberTransformStream.js new file mode 100644 index 00000000..8989670b --- /dev/null +++ b/src/streams/customized/numberTransformStream.js @@ -0,0 +1,31 @@ +import { Transform } from 'node:stream'; + +export class StartLineWithNumberTranform extends Transform { + constructor(options) { + super(options); + this.lineNumber = 1; + this.lineBuffer = ''; + } + + _transform(chunk, encoding, callback) { + this.lineBuffer += chunk.toString(); + const lines = this.lineBuffer.split('\n'); + + this.lineBuffer = lines.pop(); + + for (const line of lines) { + this.push(`${this.lineNumber} | ${line}\n`); + this.lineNumber++; + } + + callback(); + } + + _flush(callback) { + if (this.lineBuffer.length > 0) { + this.push(`${this.lineNumber++} | ${this.lineBuffer}\n`); + } + + callback(); + } +} \ No newline at end of file diff --git a/src/streams/filter.js b/src/streams/filter.js index 3868ab46..f1a5addb 100644 --- a/src/streams/filter.js +++ b/src/streams/filter.js @@ -1,9 +1,10 @@ +import { FilterByArgumentTranform } from './customized/filterTranformStream.js'; + const filter = () => { - // Write your code here - // Read from process.stdin - // Filter lines by --pattern CLI argument - // Use Transform Stream - // Write to process.stdout + const filterTransform = new FilterByArgumentTranform(); + process.stdin + .pipe(filterTransform) + .pipe(process.stdout); }; -filter(); +filter(); \ No newline at end of file diff --git a/src/streams/lineNumberer.js b/src/streams/lineNumberer.js index 579d662e..73162ce8 100644 --- a/src/streams/lineNumberer.js +++ b/src/streams/lineNumberer.js @@ -1,8 +1,11 @@ +import { StartLineWithNumberTranform } from './customized/numberTransformStream.js'; + const lineNumberer = () => { - // Write your code here - // Read from process.stdin - // Use Transform Stream to prepend line numbers - // Write to process.stdout + const customLine = new StartLineWithNumberTranform(); + + process.stdin + .pipe(customLine) + .pipe(process.stdout); }; lineNumberer(); diff --git a/src/streams/split.js b/src/streams/split.js index f8f814fa..38ff42b9 100644 --- a/src/streams/split.js +++ b/src/streams/split.js @@ -1,8 +1,48 @@ +import { parseArgs } from 'node:util'; +import readline from 'readline'; +import fs from 'node:fs'; +const splitArgv = process.argv.slice(2); +const { values } = parseArgs({ + splitArgv, + options: { + lines: { + type: 'string', + default: '10', + } + } +}); + const split = async () => { - // Write your code here - // Read source.txt using Readable Stream - // Split into chunk_1.txt, chunk_2.txt, etc. - // Each chunk max N lines (--lines CLI argument, default: 10) + const dir = process.cwd() + '/source.txt'; + try { + // Readable Stream, I didnot get that is it okay fs.createReadStream or I need to use from Web Stream + const readableStream = fs.createReadStream(dir, { encoding: 'utf8' }); + + const rl = readline.createInterface({ + input: readableStream, + crlfDelay: Infinity + }); + + let fileCnt = 1; + let lineCnt = 0; + let writebleStream = null; + + for await (const line of rl) { + if (lineCnt % parseInt(values.lines) === 0) { + if (writebleStream) { + writebleStream.end(); + } + writebleStream = fs.createWriteStream(`chunk_${fileCnt++}.txt`); + } + + writebleStream.write(`${line}\n`); + lineCnt++; + } + + if (writebleStream) writebleStream.end(); + } catch (error) { + console.error(error); + } }; await split(); diff --git a/src/utils/util.js b/src/utils/util.js new file mode 100644 index 00000000..69e8fcbb --- /dev/null +++ b/src/utils/util.js @@ -0,0 +1,83 @@ +import fs, { constants } from 'node:fs/promises'; +import { join } from 'path'; + +export const isFileOrDirectoryExist = async (path) => { + const errorMsg = 'FS operation failed'; + + try { + await fs.access(path, constants.R_OK); + } catch (error) { + if (error.code === 'ENOENT') { + console.error(Error(errorMsg)); + } else if (error.code === 'EACCES') { + console.error(Error('Access Denied!')); + } + console.error(Error(error)); + } +} + +export const splitArrayByNumberOfCores = (arr, numberOfCores) => { + let result = []; + + const chunkSize = Math.ceil(arr.length / numberOfCores); + + for (let i = 0; i < arr.length; i += chunkSize) { + result.push(arr.slice(i, i + chunkSize)); + } + + return result; +} + +export const keyMerge = (sortedArr) => { + let result = []; + + result = sortedArr.reduce((merged, current) => { + let i = 0, j = 0; + const combined = []; + while (i < merged.length && j < current.length) { + if (merged[i] < current[j]) combined.push(merged[i++]); + else combined.push(current[j++]); + } + return [...combined, ...merged.slice(i), ...current.slice(j)]; + }, []); + + return result; +} + +export const hexToAsnii = (hexString) => { + const red = parseInt(hexString.slice(1, 3), 16); + const green = parseInt(hexString.slice(3, 5), 16); + const blue = parseInt(hexString.slice(5), 16); + return `\x1b[38;2;${red};${green};${blue}m`; +} + +export const validateValueType = (argsKeyValueForm) => { + if (isNaN(argsKeyValueForm.duration)) { + process.stderr.write(new Error('No valid duration value!')) + } + + if (isNaN(argsKeyValueForm.interval)) { + process.stderr.write(new Error('No valid interval value!')); + } + + if (isNaN(argsKeyValueForm.length)) { + process.stderr.write(new Error('No valid length value!')); + } + + const regHex = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i + + if (!regHex.test(argsKeyValueForm.color)) { + argsKeyValueForm.color = '#FFFFFF'; + } +} + +export async function getFilesRecursively(fullPath) { + const files = await fs.readdir(fullPath, { withFileTypes: true }); + const promisedFiles = files.map(async (el) => { + const fullPth = join(fullPath, el.name); + return el.isDirectory() ? (await getFilesRecursively(fullPth)) : fullPth; + }); + + const mappedFiles = await Promise.all(promisedFiles); + return mappedFiles.flat(); +} \ No newline at end of file diff --git a/src/wt/main.js b/src/wt/main.js index d7d21f0c..6f701781 100644 --- a/src/wt/main.js +++ b/src/wt/main.js @@ -1,11 +1,48 @@ +import fs from 'node:fs/promises'; +import os from 'node:os'; +import { Worker } from 'node:worker_threads'; +import { splitArrayByNumberOfCores, keyMerge } from '../utils/util.js'; + const main = async () => { - // Write your code here - // Read data.json containing array of numbers - // Split into N chunks (N = CPU cores) - // Create N workers, send one chunk to each - // Collect sorted chunks - // Merge using k-way merge algorithm - // Log final sorted array + const jsonFilepath = './data.json'; + const workerPath = process.cwd() + '/src/wt/worker.js'; + + try { + await fs.access(jsonFilepath); + + const content = await fs.readFile(jsonFilepath, { encoding: 'utf8' }); + const data = JSON.parse(content); + const numberOfCores = os.cpus().length; + const chunks = splitArrayByNumberOfCores(data, numberOfCores); + + const workerPromises = chunks.map((chunk) => { + return new Promise((resolve, reject) => { + const worker = new Worker(workerPath); + + worker.postMessage({ data: chunk }); + + worker.on('message', (data) => { + resolve(data); + worker.terminate(); + }); + + worker.on('error', reject); + worker.on('exit', (code) => { + if (code !== 0) { + reject(new Error('Worker Stopped!')); + } + }); + }); + }); + + const sortedArr = await Promise.all(workerPromises); + + const result = keyMerge(sortedArr); + console.log(result); + } catch (error) { + console.error(error); + } finally { + } }; -await main(); +await main(); \ No newline at end of file diff --git a/src/wt/worker.js b/src/wt/worker.js index 15f42fc8..61109c0b 100644 --- a/src/wt/worker.js +++ b/src/wt/worker.js @@ -1,9 +1,10 @@ -import { parentPort } from 'worker_threads'; +import { parentPort } from 'node:worker_threads'; -// Receive array from main thread -// Sort in ascending order -// Send back to main thread +parentPort.on('message', (content) => { + if (!Array.isArray(content.data)) { + return; + } -parentPort.on('message', (data) => { - // Write your code here + const sortedArr = content.data.sort((a, b) => a - b); + parentPort.postMessage(sortedArr); }); diff --git a/src/zip/compressDir.js b/src/zip/compressDir.js index 3a3c5089..09077f2f 100644 --- a/src/zip/compressDir.js +++ b/src/zip/compressDir.js @@ -1,9 +1,52 @@ -const compressDir = async () => { - // Write your code here - // Read all files from workspace/toCompress/ - // Compress entire directory structure into archive.br - // Save to workspace/compressed/ - // Use Streams API -}; - -await compressDir(); +import { createReadStream, createWriteStream } from 'fs'; +import { join, relative } from 'path'; +import fs, { constants } from 'node:fs/promises'; +import { pipeline } from 'stream/promises'; +import { createBrotliCompress } from 'node:zlib'; +import { isFileOrDirectoryExist, getFilesRecursively } from '../utils/util.js'; +import { Readable } from 'node:stream'; + +const compress = async () => { + const sourcePath = './workspace/toCompress'; + const finalToPath = './workspace/compressed'; + const toFile = join(finalToPath, 'archive.br'); + isFileOrDirectoryExist(sourcePath); + + try { + await fs.access(finalToPath, constants.F_OK); + } catch (error) { + if (error.code === 'ENOENT') { + await fs.mkdir(finalToPath, { recursive: true }); + } + } + + + try { + const files = await getFilesRecursively(sourcePath); + + async function* iterativeWriteinArchive() { + for await (const file of files) { + const relativePath = relative(sourcePath, file); + const fileStat = await fs.stat(file); + + const header = Buffer.from(`${relativePath.length}:${relativePath}:${fileStat.size}:`); + yield header; + + const readStream = createReadStream(file); + for await (const chunk of readStream) { + yield chunk; + } + } + } + + await pipeline( + Readable.from(iterativeWriteinArchive()), + createBrotliCompress(), + createWriteStream(toFile) + ) + } catch (error) { + console.error(Error(error)); + } +} + +await compress(); diff --git a/src/zip/decompressDir.js b/src/zip/decompressDir.js index d6e770f6..8a883457 100644 --- a/src/zip/decompressDir.js +++ b/src/zip/decompressDir.js @@ -1,8 +1,33 @@ +import { createReadStream, createWriteStream } from 'node:fs'; +import fs, { constants } from 'node:fs/promises'; +import path, { join } from 'node:path'; +import { pipeline } from 'node:stream'; +import { createBrotliDecompress } from 'node:zlib'; +import { isFileOrDirectoryExist } from '../utils/util.js'; + const decompressDir = async () => { - // Write your code here - // Read archive.br from workspace/compressed/ - // Decompress and extract to workspace/decompressed/ - // Use Streams API + const sourcePath = './workspace/compressed'; + const fromFile = join(sourcePath, 'archive.br'); + const toFolder = './workspace/decompressed'; + isFileOrDirectoryExist(sourcePath); + isFileOrDirectoryExist(fromFile); + + try { + await fs.access(toFolder, constants.F_OK); + } catch (error) { + if (error.code === 'ENOENT') { + await fs.mkdir(toFolder, { recursive: true }); + } + } + + const readStream = createReadStream(fromFile); + const transformStream = createBrotliDecompress(); + + try { + await pipeline(source, brotli, parseArchive); + } catch (error) { + console.error(Error(error)); + } }; await decompressDir();