node.js section8 file system note taking
아직 한국어 번역이 없습니다. 영문 원문을 표시합니다.
좀 뭔가 헷갈리는 게 많음
file?
- 의미있는 0과 1의 시퀀스
- 디코딩 방식에 따라 완전히 달라짐
- 메타 데이터: 데이터를 위한 설명서
- unix executable 은 os에서 직접 맞물려 돌아가는 native program
- node.js는 직접 하드웨어를 제어하지 않는다.
- node.js는 운영체제 위에서 동작하는 하나의 프로세스일 뿐
system call
- nodejs와 운영체제가 소통하는 표준 창구를 system call이라 부른다.
- nodejs는 system call을 운영체제에 전달하기 위해 내부적으로 c언어 기반의 비동기 i/o 라이브러리인 libuv를 사용한다.
- js code > libuv > os system call
- libuv내에는 파일 관련 작업을 처리하는 thread pool이 존재한다.
promise api
- 동기 api의 치명적인 한계 main thread blocking
- (error) => {} error first callback 에러 우선 콜백
file command center example
- 상태기반 중복제거 state-based deduplication
- 상태 state를 기준으로 중복 이벤트를 걸러내는 알고리즘
- timer based debounce
- greedy vs lazy in regular expression
import {
access,
writeFile,
watch,
open,
stat,
unlink,
rename,
appendFile,
} from "node:fs/promises";
// promise에서 import할 것
import { Buffer } from "node:buffer";
const FILE_COMMANDS = "./file-commands.txt";
// 파일이 존재하고, 접근 가능한지 검증
const fileExist = async (path) => {
try {
await access(path);
return true;
} catch {
return false;
}
};
// 실제 커맨드들을 실행하는 함수
const executeCommand = async (commandString) => {
const command = commandString.trim();
if (!command) return;
if (command.startsWith("create a file ")) {
const filePath = command.replace("create a file ", "").trim();
await createFile(filePath);
}
else if (command.startsWith("delete a file ")) {
const filePath = command.replace("delete a file ", "").trim();
await deleteFile(filePath);
}
else if (command.startsWith("rename the file ")) {
const match = command.match(/^rename the file (.+) to (.+)$/);
if (match) {
await renameFile(match[1].trim(), match[2].trim());
} else {
console.log(`⚠️ there is no matched pattern.`);
}
}
else if (command.startsWith("add to the file ")) {
const match = command.match(/^add to the file (.+) this content: (.*)$/s);
if (match) {
await addToFile(match[1].trim(), match[2].trim());
} else {
console.log(`⚠️ there is no matched pattern.`);
}
}
};
const createFile = async (path) => {
if (await fileExist(path)) {
return console.log(`${path} file does exist.`);
}
try {
await writeFile(path, "");
console.log(`✅ ${path} is created.`);
} catch (error) {
console.log(`❌ 에러 발생 : ${error.message}`);
}
};
const deleteFile = async (path) => {
try {
await unlink(path);
console.log(`🗑️ ${path} is deleted successfully`);
} catch (error) {
if (error.code === "ENOENT") {
console.log(`⚠️ ${path} does not exist`);
}
console.error(`❌ 에러 발생: ${error.message}`);
}
};
const renameFile = async (oldPath, newPath) => {
try {
await rename(oldPath, newPath);
console.log(`✅ ${oldPath} is now ${newPath} successfully`);
} catch (error) {
if (error.code === "ENOENT") {
console.log(`⚠️ ${oldPath} does not exist`);
}
console.error(`❌ 에러 발생: ${error.message}`);
}
};
let previousContent = ""; // 중복 실행(이벤트 바운스) 방지용 상태 변수
const addToFile = async (path, content) => {
// 1차 방어: 동일한 내용이 연속으로 오면 무시 (이벤트 디바운싱 효과)
if (previousContent === content) return;
// 2차 방어: 의도치 않은 새 파일 생성을 원천 차단 (Early Return)
if (!(await fileExist(path))) {
console.log(
`⚠️ 경고: '${path}' 파일이 존재하지 않아 내용을 추가할 수 없습니다.`,
);
return;
}
try {
await appendFile(path, content);
previousContent = content;
console.log(`📝 내용이 '${path}'에 성공적으로 추가되었습니다.`);
} catch (e) {
// fileExist로 거르지 못한 OS 레벨의 치명적 예외 상황 처리
console.error(`❌ 파일에 내용 추가 중 치명적 오류 발생: ${e.message}`);
}
};
if (!(await fileExist(FILE_COMMANDS))) {
await writeFile(FILE_COMMANDS, "");
}
console.log(`👀 ${FILE_COMMANDS} 파일 변경 사항을 감시하는 중`);
process.on('SIGINT', async () => {
console.log('\n👋 종료 신호 감지, 정리 중...');
// watcher 닫기, 파일 핸들 닫기 등 cleanup
process.exit(0);
});
try {
const watcher = watch(FILE_COMMANDS); // async iterator object 반환
for await (const event of watcher) {
// for await of 는 main thread를 blocking 하지 않음
if (event.eventType === "change") {
console.log(
`[알림] ${event.filename} 파일에서 ${event.eventType} 이벤트 발생! ${new Date().toISOString()}`,
);
// Event Emitter 에서부터 발생하는 개념
const fileHandler = await open(FILE_COMMANDS, "r"); // 파일 디스크립터(FD)
try {
const stat = await fileHandler.stat();
const size = stat.size;
if (size === 0) continue;
const buff = Buffer.alloc(size);
const offset = 0;
const length = buff.byteLength;
const position = 0;
await fileHandler.read(buff, offset, length, position);
const commandString = buff.toString("utf8");
await executeCommand(commandString);
} finally {
await fileHandler.close(); // 메모리 누수 방지 fd 파일 디스크립터 반납 EMFILE에러 방지
}
}
}
} catch (error) {
console.error(`❌ 에러 발생 : ${error.message}`);
}