fast-filesystem-mcp
Version:
Fast Filesystem MCP Server - Advanced file operations with Auto-Chunking, Sequential Reading, complex file operations (copy, move, delete, batch, compress), optimized for Claude Desktop
276 lines • 11.2 kB
JavaScript
// ========================================
// 체크포인트 기반 대용량 파일 작성 시스템
// 중단된 작성을 안전하게 복구하고 계속 진행
// ========================================
import { promises as fs } from 'fs';
export class SafeLargeFileWriter {
targetPath;
checkpointPath;
backupPath;
sections = [];
options;
constructor(targetPath, options = {}) {
this.targetPath = targetPath;
this.checkpointPath = targetPath + '.checkpoint.json';
this.backupPath = targetPath + '.backup';
this.options = {
enableEmojis: false,
checkpointInterval: 1,
autoBackup: true,
verifyWrite: true,
maxRetries: 3,
...options
};
}
// 이모지 제거 함수
removeEmojis(text) {
if (this.options.enableEmojis)
return text;
// 이모지 패턴 정규식
const emojiRegex = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F700}-\u{1F77F}]|[\u{1F780}-\u{1F7FF}]|[\u{1F800}-\u{1F8FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1FA00}-\u{1FA6F}]|[\u{1FA70}-\u{1FAFF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu;
return text.replace(emojiRegex, '');
}
// 로그 출력 (이모지 제거 적용)
log(message) {
console.log(this.removeEmojis(message));
}
// 섹션 추가
addSection(id, name, content) {
const cleanContent = this.removeEmojis(content);
const lineCount = cleanContent.split('\n').length;
const size = Buffer.byteLength(cleanContent, 'utf8');
this.sections.push({
id,
name,
content: cleanContent,
lineCount,
size
});
this.log(`섹션 추가: ${name} (${lineCount}줄, ${(size / 1024).toFixed(1)}KB)`);
}
// 체크포인트 저장
async saveCheckpoint(completedSections, currentSection, fileSize, lineCount, mode) {
const checkpoint = {
targetFile: this.targetPath,
totalSections: this.sections.length,
completedSections,
currentSection,
timestamp: new Date().toISOString(),
fileSize,
lastLineCount: lineCount,
mode
};
await fs.writeFile(this.checkpointPath, JSON.stringify(checkpoint, null, 2));
this.log(`체크포인트 저장: 섹션 ${currentSection}/${this.sections.length} 완료`);
}
// 체크포인트 로드
async loadCheckpoint() {
try {
const data = await fs.readFile(this.checkpointPath, 'utf8');
return JSON.parse(data);
}
catch (error) {
return null;
}
}
// 백업 생성
async createBackup() {
if (!this.options.autoBackup)
return;
try {
await fs.copyFile(this.targetPath, this.backupPath);
this.log(`백업 생성: ${this.backupPath}`);
}
catch (error) {
this.log('새 파일 생성 (백업 없음)');
}
}
// 백업에서 복구
async restoreFromBackup() {
try {
await fs.copyFile(this.backupPath, this.targetPath);
this.log(`백업에서 복구: ${this.targetPath}`);
return true;
}
catch (error) {
this.log('백업 파일 없음');
return false;
}
}
// 파일 상태 검증
async verifyFileState() {
try {
const content = await fs.readFile(this.targetPath, 'utf8');
const lineCount = content.split('\n').length;
const size = Buffer.byteLength(content, 'utf8');
return { size, lineCount };
}
catch (error) {
return null;
}
}
// 진행률 표시
showProgress(current, total, sectionName) {
const percentage = ((current / total) * 100).toFixed(1);
const filled = Math.floor(current / total * 20);
const progressBar = '█'.repeat(filled) + '░'.repeat(20 - filled);
this.log(`진행률: [${progressBar}] ${percentage}% (${current}/${total}) - ${sectionName}`);
}
// 안전한 파일 작성
async writeSafely(mode = 'write') {
this.log('대용량 파일 안전 작성 시작...');
// 백업 생성
await this.createBackup();
// 기존 체크포인트 확인
const existingCheckpoint = await this.loadCheckpoint();
let startSection = 0;
let completedSections = [];
if (existingCheckpoint) {
this.log('기존 체크포인트 발견!');
this.log(`마지막 저장: ${existingCheckpoint.timestamp}`);
this.log(`완료된 섹션: ${existingCheckpoint.completedSections.length}/${existingCheckpoint.totalSections}`);
// 파일 상태 검증
const currentState = await this.verifyFileState();
if (currentState && currentState.size === existingCheckpoint.fileSize) {
startSection = existingCheckpoint.currentSection;
completedSections = existingCheckpoint.completedSections;
mode = existingCheckpoint.mode;
this.log('파일 상태 일치. 이어서 작성합니다.');
}
else {
this.log('파일 상태 불일치. 처음부터 다시 작성합니다.');
startSection = 0;
completedSections = [];
}
}
try {
let currentFileSize = 0;
let currentLineCount = 0;
// 첫 번째 섹션 또는 새로 시작하는 경우
if (startSection === 0) {
const firstSection = this.sections[0];
if (mode === 'write') {
await fs.writeFile(this.targetPath, firstSection.content);
}
else {
await fs.appendFile(this.targetPath, firstSection.content);
}
completedSections.push(firstSection.id);
currentFileSize = firstSection.size;
currentLineCount = firstSection.lineCount;
await this.saveCheckpoint(completedSections, 1, currentFileSize, currentLineCount, mode);
this.showProgress(1, this.sections.length, firstSection.name);
startSection = 1;
}
else {
// 기존 파일 상태 가져오기
const state = await this.verifyFileState();
if (state) {
currentFileSize = state.size;
currentLineCount = state.lineCount;
}
}
// 나머지 섹션들 append
for (let i = startSection; i < this.sections.length; i++) {
const section = this.sections[i];
// 이미 완료된 섹션인지 확인
if (completedSections.includes(section.id)) {
this.log(`섹션 건너뛰기: ${section.name} (이미 완료됨)`);
continue;
}
try {
// 섹션 내용 append
await fs.appendFile(this.targetPath, section.content);
completedSections.push(section.id);
currentFileSize += section.size;
currentLineCount += section.lineCount;
// 체크포인트 저장 (설정된 간격마다)
if (i % (this.options.checkpointInterval || 1) === 0) {
await this.saveCheckpoint(completedSections, i + 1, currentFileSize, currentLineCount, mode);
}
this.showProgress(i + 1, this.sections.length, section.name);
// 잠시 대기 (너무 빠른 작성 방지)
await new Promise(resolve => setTimeout(resolve, 10));
}
catch (error) {
this.log(`섹션 작성 실패: ${section.name}`);
throw error;
}
}
// 작성 완료
this.log('모든 섹션 작성 완료!');
// 최종 검증
if (this.options.verifyWrite) {
const finalState = await this.verifyFileState();
if (finalState) {
this.log(`최종 파일 크기: ${(finalState.size / 1024).toFixed(1)}KB`);
this.log(`최종 라인 수: ${finalState.lineCount.toLocaleString()}줄`);
}
}
// 체크포인트 파일 삭제
await fs.unlink(this.checkpointPath).catch(() => { });
this.log('체크포인트 파일 정리 완료');
return true;
}
catch (error) {
this.log('작성 중 오류 발생: ' + (error instanceof Error ? error.message : String(error)));
// 백업에서 복구 시도
const restored = await this.restoreFromBackup();
if (restored) {
this.log('백업에서 복구 완료');
}
return false;
}
}
// 상태 확인
async getStatus() {
const checkpoint = await this.loadCheckpoint();
const fileState = await this.verifyFileState();
this.log('\n=== 파일 작성 상태 ===');
if (checkpoint) {
this.log(`체크포인트: ${checkpoint.timestamp}`);
this.log(`완료된 섹션: ${checkpoint.completedSections.length}/${checkpoint.totalSections}`);
this.log(`진행률: ${(checkpoint.completedSections.length / checkpoint.totalSections * 100).toFixed(1)}%`);
}
else {
this.log('체크포인트 없음 (새로 시작)');
}
if (fileState) {
this.log(`현재 파일: ${(fileState.size / 1024).toFixed(1)}KB, ${fileState.lineCount.toLocaleString()}줄`);
}
else {
this.log('파일 없음');
}
this.log(`목표 섹션: ${this.sections.length}개`);
this.log('========================\n');
}
// 체크포인트 초기화
async resetCheckpoint() {
try {
await fs.unlink(this.checkpointPath);
this.log('체크포인트 초기화 완료');
}
catch (error) {
this.log('체크포인트 파일 없음');
}
}
// 남은 섹션들만 추가로 작성
async continueFromCheckpoint() {
this.log('체크포인트에서 이어서 작성 시작...');
return await this.writeSafely('append');
}
// 섹션 목록 조회
getSections() {
return this.sections;
}
// 총 크기 계산
getTotalSize() {
return this.sections.reduce((total, section) => total + section.size, 0);
}
// 총 라인 수 계산
getTotalLines() {
return this.sections.reduce((total, section) => total + section.lineCount, 0);
}
}
export default SafeLargeFileWriter;
//# sourceMappingURL=checkpoint-writer.js.map