diff --git a/.gitignore b/.gitignore index 3699a3e..b214ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# Created by https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,git -# Edit at https://www.toptal.com/developers/gitignore?templates=node,visualstudiocode,git +# Created by https://www.toptal.com/developers/gitignore/api/macos,git,visualstudiocode,node +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,git,visualstudiocode,node ### Git ### # Created by git for backups. To disable backups in Git: @@ -16,6 +16,39 @@ *_LOCAL_*.txt *_REMOTE_*.txt +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + ### Node ### # Logs logs @@ -175,4 +208,4 @@ dist .history .ionide -# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,git \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/macos,git,visualstudiocode,node \ No newline at end of file diff --git a/convert/.gitkeep b/convert/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/normalize.js b/normalize.js index 33ff2a8..ba3e72e 100644 --- a/normalize.js +++ b/normalize.js @@ -1,82 +1,108 @@ const fs = require("fs").promises; const path = require("path"); +const chokidar = require("chokidar"); -// 특정 파일/디렉토리를 무시하는 기능 추가 +// 무시할 파일/디렉토리를 결정하는 함수 function shouldIgnore(itemName) { const ignoredItems = [".git", "node_modules", ".env"]; return ignoredItems.includes(itemName); } +// 파일 이름을 정규화하는 함수 async function normalizeFileName(filePath) { const dir = path.dirname(filePath); const oldName = path.basename(filePath); - const newName = oldName.normalize("NFC"); + // console.log(`정규화 시도: "${oldName}" -> "${newName}"`); + if (oldName !== newName && !shouldIgnore(oldName)) { const newPath = path.join(dir, newName); try { - // 경로에 공백이 있을 경우를 대비해 이스케이프 처리 - const escapedOldPath = filePath.replace(/ /g, "\\ "); - const escapedNewPath = newPath.replace(/ /g, "\\ "); - await fs.rename(escapedOldPath, escapedNewPath); - console.log(`이름 변경: "${oldName}" -> "${newName}"`); + await fs.rename(filePath, newPath); + // console.log(`이름 변경 성공: "${oldName}" -> "${newName}"`); return newPath; } catch (error) { console.error(`이름 변경 실패 ("${oldName}"):`, error); return filePath; } + } else { + // console.log(`정규화 필요 없음: "${oldName}"`); } return filePath; } +// 디렉토리를 재귀적으로 처리하는 함수 async function processDirectory(dirPath) { + // console.log(`디렉토리 처리 시작: "${dirPath}"`); try { - // 경로에 공백이 있을 경우를 대비해 이스케이프 처리 - const escapedDirPath = dirPath.replace(/ /g, "\\ "); - const entries = await fs.readdir(escapedDirPath, { withFileTypes: true }); + const entries = await fs.readdir(dirPath, { withFileTypes: true }); - const items = entries.map((entry) => ({ - name: entry.name, - fullPath: path.join(dirPath, entry.name), - isDirectory: entry.isDirectory(), - })); - - for (const item of items) { - if (item.isDirectory) { - await processDirectory(item.fullPath); - await normalizeFileName(item.fullPath); + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name); + // console.log(`파일 처리 시작: "${fullPath}"`); + if (entry.isDirectory()) { + if (!shouldIgnore(entry.name)) { + await processDirectory(fullPath); + await normalizeFileName(fullPath); + } } else { - await normalizeFileName(item.fullPath); + await normalizeFileName(fullPath); } + // console.log(`파일 처리 완료: "${fullPath}"`); } + await normalizeFileName(dirPath); + // console.log(`디렉토리 처리 완료: "${dirPath}"`); } catch (error) { console.error(`디렉토리 처리 중 오류 발생 ("${dirPath}"):`, error); } } -async function processRoot(rootPath) { - try { - // 경로에 공백이 있을 경우를 대비해 이스케이프 처리 - const escapedRootPath = path.resolve(rootPath); - const stats = await fs.stat(escapedRootPath); +// 디렉토리를 감시하는 함수 +function watchDirectory(directory) { + const watcher = chokidar.watch(directory, { + ignored: (pathStr) => { + const baseName = path.basename(pathStr); + return shouldIgnore(baseName); + }, + persistent: true, + ignoreInitial: false, // 초기 파일 추가 이벤트를 감지 + awaitWriteFinish: { + stabilityThreshold: 200, // 파일이 완전히 작성될 때까지 대기 (ms) + pollInterval: 100, + }, + depth: Infinity, // 하위 디렉토리까지 감시 + }); - if (stats.isDirectory()) { - const normalizedRootPath = await normalizeFileName(rootPath); - await processDirectory(normalizedRootPath); - } else { - await normalizeFileName(rootPath); - } - } catch (error) { - console.error(`처리 중 오류 발생 ("${rootPath}"):`, error); - } + watcher + .on("add", async (filePath) => { + // console.log(`파일 추가됨: "${filePath}"`); + await normalizeFileName(filePath); + }) + .on("change", async (filePath) => { + // console.log(`파일 변경됨: "${filePath}"`); + await normalizeFileName(filePath); + }) + .on("unlink", (filePath) => { + // console.log(`파일 삭제됨: "${filePath}"`); + }) + .on("addDir", async (dirPath) => { + // console.log(`디렉토리 추가됨: "${dirPath}"`); + await processDirectory(dirPath); // 새 디렉토리를 추가되자마자 처리 + }) + .on("unlinkDir", (dirPath) => { + // console.log(`디렉토리 삭제됨: "${dirPath}"`); + }) + .on("error", (error) => console.error(`Watcher error: ${error}`)) + .on("ready", () => { + console.log("초기 스캔 완료. 변경 감시 중..."); + }); + + console.log(`감시 시작: "${directory}"`); } // 명령줄 인자로 경로를 받거나 기본값 사용 -// 경로에 공백이 있을 경우를 대비해 따옴표로 감싸진 경로도 처리 const targetPath = process.argv[2] || "./convert"; -// 프로그램 실행 -processRoot(targetPath) - .then(() => console.log("모든 처리가 완료되었습니다.")) - .catch((error) => console.error("프로그램 실행 중 오류 발생:", error)); +// 디렉토리 감시 시작 +watchDirectory(targetPath); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..64fba18 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,44 @@ +{ + "name": "nfd-to-nfc-converter", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nfd-to-nfc-converter", + "version": "1.0.0", + "hasInstallScript": true, + "dependencies": { + "chokidar": "^4.0.1" + } + }, + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..35dd61a --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "nfd-to-nfc-converter", + "version": "1.0.0", + "main": "main.js", + "scripts": { + "start": "electron .", + "build": "electron-builder", + "postinstall": "electron-builder install-app-deps" + }, + "build": { + "appId": "com.pieroot.nfdtonfc", + "mac": { + "category": "public.app-category.utilities", + "target": [ + "dmg", + "pkg" + ], + "hardenedRuntime": true, + "gatekeeperAssess": false, + "entitlements": "build/entitlements.mac.plist", + "entitlementsInherit": "build/entitlements.mac.plist" + }, + "files": [ + "**/*", + "!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}", + "!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}", + "!**/node_modules/*.d.ts", + "!**/node_modules/.bin", + "!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}", + "!.editorconfig", + "!**/._*", + "!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}", + "!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}", + "!**/{appveyor.yml,.travis.yml,circle.yml}", + "!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}" + ], + "extraResources": [ + { + "from": "scripts", + "to": "scripts", + "filter": [ + "**/*" + ] + } + ] + }, + "dependencies": { + "chokidar": "^4.0.1" + } +}