Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 📁 File Manager
A simple command-line file manager built with Node.js, allowing users to interact with the file system directly from the terminal — navigate directories, manage files, and perform compression/decompression operations.
176 changes: 176 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import os from "os";
import readline from "readline";
import { stdin as input, stdout as output } from "process";
import * as fsOps from "./operations/fs.js";
import * as navOps from "./operations/navigation.js";
import * as osOps from "./operations/os.js";
import * as hashOps from "./operations/hash.js";
import * as compressOps from "./operations/compress.js";

let currentDir = os.homedir();
let username = "Anonymous";

const args = process.argv.slice(2);
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith("--username=")) {
username = args[i].split("=")[1];
break;
}
}

const rl = readline.createInterface({ input, output, prompt: "" });

const showCurrentDir = () => {
console.log(`You are currently in ${currentDir}`);
};

const showPrompt = () => {
showCurrentDir();
rl.prompt();
};

const parseCommand = (input) => {
const trimmed = input.trim();
const spaceIdx = trimmed.indexOf(" ");

if (spaceIdx === -1) {
return { cmd: trimmed, args: [] };
}

const cmd = trimmed.substring(0, spaceIdx);
const argsStr = trimmed.substring(spaceIdx + 1).trim();
const args = argsStr.split(" ").filter((a) => a.length > 0);

return { cmd, args };
};

const handleCommand = async (line) => {
const { cmd, args } = parseCommand(line);

try {
switch (cmd) {
case "up":
currentDir = navOps.goUp(currentDir);
break;

case "cd":
if (args.length === 0) {
throw new Error("Invalid input");
}
currentDir = await navOps.changeDirectory(currentDir, args[0]);
break;

case "ls":
await fsOps.listDirectory(currentDir);
break;

case "cat":
if (args.length === 0) {
throw new Error("Invalid input");
}
await fsOps.readFile(currentDir, args[0]);
break;

case "add":
if (args.length === 0) {
throw new Error("Invalid input");
}
await fsOps.createFile(currentDir, args[0]);
break;

case "mkdir":
if (args.length === 0) {
throw new Error("Invalid input");
}
await fsOps.createDirectory(currentDir, args[0]);
break;

case "rn":
if (args.length < 2) {
throw new Error("Invalid input");
}
await fsOps.renameFile(currentDir, args[0], args[1]);
break;

case "cp":
if (args.length < 2) {
throw new Error("Invalid input");
}
await fsOps.copyFile(currentDir, args[0], args[1]);
break;

case "mv":
if (args.length < 2) {
throw new Error("Invalid input");
}
await fsOps.moveFile(currentDir, args[0], args[1]);
break;

case "rm":
if (args.length === 0) {
throw new Error("Invalid input");
}
await fsOps.deleteFile(currentDir, args[0]);
break;

case "os":
if (args.length === 0) {
throw new Error("Invalid input");
}
osOps.handleOsCommand(args[0]);
break;

case "hash":
if (args.length === 0) {
throw new Error("Invalid input");
}
await hashOps.calculateHash(currentDir, args[0]);
break;

case "compress":
if (args.length < 2) {
throw new Error("Invalid input");
}
await compressOps.compressFile(currentDir, args[0], args[1]);
break;

case "decompress":
if (args.length < 2) {
throw new Error("Invalid input");
}
await compressOps.decompressFile(currentDir, args[0], args[1]);
break;

case ".exit":
rl.close();
return;

default:
if (cmd.length > 0) {
throw new Error("Invalid input");
}
}
} catch (err) {
if (err.message === "Invalid input") {
console.log("Invalid input");
} else {
console.log("Operation failed");
}
}

showPrompt();
};

console.log(`Welcome to the File Manager, ${username}!`);
showPrompt();

rl.on("line", handleCommand);

rl.on("close", () => {
console.log(`Thank you for using File Manager, ${username}, goodbye!`);
process.exit(0);
});

process.on("SIGINT", () => {
rl.close();
});
26 changes: 26 additions & 0 deletions operations/compress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createReadStream, createWriteStream } from "fs";
import { createBrotliCompress, createBrotliDecompress } from "zlib";
import { pipeline } from "stream/promises";
import path from "path";

export const compressFile = async (currentPath, source, destination) => {
const srcPath = path.resolve(currentPath, source);
const destPath = path.resolve(currentPath, destination);

const readStream = createReadStream(srcPath);
const brotli = createBrotliCompress();
const writeStream = createWriteStream(destPath);

await pipeline(readStream, brotli, writeStream);
};

export const decompressFile = async (currentPath, source, destination) => {
const srcPath = path.resolve(currentPath, source);
const destPath = path.resolve(currentPath, destination);

const readStream = createReadStream(srcPath);
const brotli = createBrotliDecompress();
const writeStream = createWriteStream(destPath);

await pipeline(readStream, brotli, writeStream);
};
91 changes: 91 additions & 0 deletions operations/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import fs from "fs/promises";
import path from "path";
import { createReadStream, createWriteStream } from "fs";
import { pipeline } from "stream/promises";

export const listDirectory = async (currentPath) => {
const items = await fs.readdir(currentPath, { withFileTypes: true });

const dirs = [];
const files = [];

for (const item of items) {
if (item.isDirectory()) {
dirs.push({ Name: item.name, Type: "directory" });
} else {
files.push({ Name: item.name, Type: "file" });
}
}

dirs.sort((a, b) => a.Name.localeCompare(b.Name));
files.sort((a, b) => a.Name.localeCompare(b.Name));

const combined = [...dirs, ...files];

console.table(combined);
};

export const readFile = async (currentPath, filename) => {
const filePath = path.resolve(currentPath, filename);
const stream = createReadStream(filePath, "utf8");

return new Promise((resolve, reject) => {
stream.on("data", (chunk) => {
process.stdout.write(chunk);
});

stream.on("end", () => {
console.log();
resolve();
});

stream.on("error", reject);
});
};

export const createFile = async (currentPath, filename) => {
const filePath = path.resolve(currentPath, filename);
const handle = await fs.open(filePath, "wx");
await handle.close();
};

export const createDirectory = async (currentPath, dirname) => {
const dirPath = path.resolve(currentPath, dirname);
await fs.mkdir(dirPath);
};

export const renameFile = async (currentPath, oldName, newName) => {
const oldPath = path.resolve(currentPath, oldName);
const newPath = path.resolve(currentPath, newName);
await fs.rename(oldPath, newPath);
};

export const copyFile = async (currentPath, source, destination) => {
const srcPath = path.resolve(currentPath, source);
const destPath = path.resolve(currentPath, destination);

const stats = await fs.stat(destPath).catch(() => null);
let finalDest = destPath;

if (stats && stats.isDirectory()) {
const basename = path.basename(srcPath);
finalDest = path.join(destPath, basename);
}

const readStream = createReadStream(srcPath);
const writeStream = createWriteStream(finalDest);

await pipeline(readStream, writeStream);
};

export const moveFile = async (currentPath, source, destination) => {
await copyFile(currentPath, source, destination);

const srcPath = path.resolve(currentPath, source);
await fs.unlink(srcPath);
};

export const deleteFile = async (currentPath, filename) => {
const filePath = path.resolve(currentPath, filename);
await fs.unlink(filePath);
};
23 changes: 23 additions & 0 deletions operations/hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createReadStream } from "fs";
import { createHash } from "crypto";
import path from "path";

export const calculateHash = async (currentPath, filename) => {
const filePath = path.resolve(currentPath, filename);
const hash = createHash("sha256");
const stream = createReadStream(filePath);

return new Promise((resolve, reject) => {
stream.on("data", (chunk) => {
hash.update(chunk);
});

stream.on("end", () => {
const result = hash.digest("hex");
console.log(result);
resolve();
});

stream.on("error", reject);
});
};
24 changes: 24 additions & 0 deletions operations/navigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import fs from "fs/promises";
import path from "path";

export const goUp = (currentPath) => {
const parentDir = path.dirname(currentPath);

if (parentDir === currentPath) {
return currentPath;
}

return parentDir;
};

export const changeDirectory = async (currentPath, targetPath) => {
const newPath = path.resolve(currentPath, targetPath);

const stats = await fs.stat(newPath);

if (!stats.isDirectory()) {
throw new Error("Not a directory");
}

return newPath;
};
33 changes: 33 additions & 0 deletions operations/os.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os from "os";

export const handleOsCommand = (arg) => {
switch (arg) {
case "--EOL":
console.log(JSON.stringify(os.EOL));
break;

case "--cpus":
const cpus = os.cpus();
console.log(`Overall amount of CPUs: ${cpus.length}`);
cpus.forEach((cpu, idx) => {
const ghz = (cpu.speed / 1000).toFixed(2);
console.log(`CPU ${idx + 1}: ${cpu.model}, ${ghz} GHz`);
});
break;

case "--homedir":
console.log(os.homedir());
break;

case "--username":
console.log(os.userInfo().username);
break;

case "--architecture":
console.log(os.arch());
break;

default:
throw new Error("Invalid input");
}
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"name": "file-manager",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"start": "node index.js"
},
"repository": {
"type": "git",
Expand Down