Skip to content

Repository files navigation

JamoFix (자모픽스)

JamoFix는 macOS에서 한글 파일·폴더 이름을 canonical NFC로 제자리 정규화하는 네이티브 메뉴 막대 앱입니다. Windows, 전자문서 시스템, 일부 웹 서비스에서 한글 이름이 자모 단위로 분리되어 보이는 문제를 반복적인 업로드·다운로드 없이 로컬에서 해결합니다.

현재 소스 버전은 1.0.0입니다. 로컬 dogfood는 통과했으며, TestFlight와 Mac App Store 배포는 별도 Apple 계정 게이트 뒤에 진행합니다.

설치

일반 사용자는 GitHub의 최신 릴리스에서 JamoFix-버전.dmg를 내려받아 연 뒤, JamoFix.appApplications로 드래그합니다. 소스 ZIP은 개발자용이며 설치 파일이 아닙니다.

직접 배포 DMG는 Developer ID로 서명하고 Apple 공증 티켓을 첨부한 산출물만 게시합니다. JamoFix 자체에는 업데이트 확인을 위한 네트워크 코드가 없으므로 새 버전은 GitHub Releases에서 사용자가 직접 내려받습니다.

주요 기능

  • 사용자가 시스템 폴더 선택기로 허용한 루트를 최초 재귀 검사
  • 파일과 하위 폴더 이름을 NFC로 제자리 변경
  • FSEvents와 선택 트리 내부 vnode 이벤트 기반 생성·이동·이름 변경 감시
  • 이벤트 등록 실패를 숨기지 않는 fail-closed 상태와 debounce/안정화 대기
  • 알려진 임시 저장 이름(~$ Office lock, .partial, .tmp, .crdownload, swap 파일 등) 보류
  • 메뉴 막대에서 상태 확인, 일시정지/재개, 즉시 검사, 설정 열기, 종료
  • 아이콘만 표시되는 메뉴 막대 표면과 VoiceOver용 자모픽스 접근성 이름
  • app-scoped security-scoped bookmark로 선택 폴더 권한 복원
  • SMAppService.mainApp 기반의 명시적 로그인 실행 opt-in
  • 최근 상태와 오류 이력을 앱 컨테이너에만 저장

JamoFix는 별도 결과 폴더를 만들거나 문서를 복사하지 않습니다. 파일 내용은 읽어 변환하거나 수정하지 않으며, 이름 변경은 같은 부모 디렉터리 안에서만 일어납니다.

요구 사항

  • macOS 13 Ventura 이상
  • Xcode 15 이상(프로젝트는 Swift 5.9 언어 모드)
  • 프로젝트를 다시 생성하려면 선택적으로 XcodeGen 사용

저장소에는 생성된 JamoFix.xcodeproj가 포함되므로 XcodeGen 없이 바로 열 수 있습니다.

빌드와 테스트

Xcode에서 JamoFix.xcodeproj를 열고 JamoFix scheme을 실행합니다. 명령줄에서는 다음처럼 빌드할 수 있습니다.

xcodebuild \
  -project JamoFix.xcodeproj \
  -scheme JamoFix \
  -destination 'platform=macOS' \
  CODE_SIGNING_ALLOWED=NO \
  build

테스트는 실제 임시 디렉터리에 raw NFD 이름을 만들고 APFS rename 동작과 SHA-256 digest 불변을 검증합니다.

xcodebuild \
  -project JamoFix.xcodeproj \
  -scheme JamoFix \
  -destination 'platform=macOS' \
  CODE_SIGNING_ALLOWED=NO \
  test

project.yml을 수정했다면 생성 프로젝트를 갱신합니다.

xcodegen generate

직접 배포용 DMG 생성 절차는 docs/DIRECT_DISTRIBUTION.md에 정리되어 있습니다. DMG나 빌드 산출물은 Git에 커밋하지 않고, 공증이 끝난 파일만 GitHub Release asset으로 첨부합니다.

로컬 dogfood 사용법

  1. Xcode에서 앱을 실행합니다.
  2. 처음 열린 설정에서 테스트용 폴더를 추가합니다.
  3. 추가 직후 자동으로 수행되는 최초 재귀 검사 결과와 기록 탭의 오류가 없는지 확인합니다.
  4. 자동 감시를 켠 상태에서 분해형 이름의 테스트 파일을 저장하거나 이동합니다.
  5. 메뉴 막대에서 일시정지·재개와 종료를 확인합니다.
  6. 충분히 검증한 뒤에만 실제 작업 폴더를 사용자가 직접 추가합니다.

앱 종료 시 FSEvent stream, 디렉터리 감시 descriptor, 모든 security-scoped access를 닫습니다. 별도 daemon, root helper, 숨은 상주 프로세스는 설치하지 않습니다.

안전한 이름 변경 설계

APFS의 normalization-insensitive lookup에서는 NFD와 NFC가 같은 경로처럼 비교될 수 있습니다. JamoFix는 단순히 old → NFC를 호출하지 않습니다.

  1. lstat의 device/inode로 source identity를 고정합니다.
  2. POSIX readdir로 실제 UTF-8 directory entry 철자를 읽습니다.
  3. canonical target collision과 선택 루트·symlink 경계를 확인합니다.
  4. identity가 포함된 충돌 없는 임시 sibling 이름으로 이동합니다.
  5. renameatx_np(..., RENAME_EXCL)로 임시 항목을 NFC target에 원자적으로 이동합니다.
  6. 최종 identity와 raw NFC 철자를 다시 검증합니다.

임시 이동 뒤 실패하면 같은 identity가 증명될 때만 원래 철자로 rollback합니다. rollback조차 안전하게 할 수 없으면 다른 occupant를 덮지 않고 중단하며, app-container journal을 다음 실행에서 identity-bound recovery에 사용합니다.

아키텍처

SwiftUI / MenuBarExtra
        ├── SettingsWindowController (AppKit-owned, reopenable)
        │
     AppModel ── BookmarkStore / HistoryStore / LoginItemService
        │
   FolderMonitor (FSEvents + recursive vnode events)
        │          (debounce + two-snapshot stability)
        │
 DirectoryScanner (recursive, deepest-first, no symlink following)
        │
 SafeRenameEngine ── RenameJournal
        │
 POSIX lstat / readdir / renameatx_np(RENAME_EXCL)
  • JamoFix/Core: UI와 분리된 정규화, 스캔, identity-bound rename, 복구 저널
  • JamoFix/Services: 보안 범위 북마크, 감시, 로그인 항목, 앱 상태와 이력
  • JamoFix/UI: 온보딩, 설정, 폴더 목록, 기록, 개인정보 화면, 메뉴 막대
  • JamoFixTests: unit/integration 및 실제 임시 파일시스템 검증

설정 창은 macOS 13에서 SwiftUI Settings scene을 legacy selector로 여는 방식에 의존하지 않습니다. 앱이 NSWindowController를 직접 보유하고 같은 SwiftUI SettingsView를 호스팅하므로 첫 실행과 메뉴 막대의 “설정 열기”가 동일한 재열기 가능한 창 경로를 사용합니다.

앱은 감시 중에는 메뉴 막대 유틸리티로 동작하고, 설정 창을 열 때만 Dock에 정식 앱 아이콘을 표시합니다. 창을 닫으면 다시 메뉴 막대 전용 상태로 돌아갑니다. 메뉴 막대에는 상태 아이콘만 보이지만 VoiceOver 접근성 이름은 자모픽스로 유지됩니다. Dock 아이콘의 편집 가능한 원본은 design/JamoFixAppIcon.svg이며, 빌드에는 전체 macOS 크기의 AppIcon asset catalog가 포함됩니다.

자동 감시는 FSEventStreamCreateFSEventStreamStart 결과를 검사하고, 선택된 루트와 그 하위 디렉터리에만 vnode event source를 함께 등록합니다. FSEvents가 시작 성공 뒤 콜백을 내지 않는 환경에서도 선택 트리 안의 실제 변경은 vnode 신호가 같은 안정화 스캔 경로로 전달합니다. 새 폴더의 최초 검사들이 이름 변경을 마칠 때까지 새 전체 감시 등록을 보류하며, 중첩 폴더 rename이 연속 callback을 만들 때는 기존 monitor queue에서 watch-list 재구성을 짧게 합칩니다. 등록 전에 전체 디렉터리 수를 계산하고 앱 프로세스의 열린 파일 soft limit만 필요한 수와 256개 여유만큼, 최대 8,192까지 올립니다. 등록 직전에 이미 사라진 하위 경로는 부모 이벤트로 최종 트리를 다시 발견하고, 루트·권한·자원·bounded capacity 오류는 계속 fail-closed로 중단합니다. 최종 안정 상태의 준비 또는 재등록이 실패하면 앱은 “감시 중”으로 표시하지 않고 오류 이력을 남깁니다. 시스템 설정 변경이나 polling은 사용하지 않습니다.

각 감시 루트는 bookmark를 해석할 때의 URL과 watched-folder UUID를 하나의 등록 객체로 묶습니다. 이벤트 처리 뒤 저장된 표시 경로 문자열을 다시 비교하지 않으므로, security-scoped bookmark가 동일 디렉터리를 다른 경로 표기 형태로 반환하더라도 안정화된 항목이 올바른 폴더 검사로 전달됩니다. 통합 로그에는 경로나 파일명을 기록하지 않고 event-source 상태와 처리 건수만 남깁니다.

개인정보와 네트워크

  • 네트워크 entitlement 없음
  • 네트워크 API, 계정, 클라우드, 분석 SDK, 광고 SDK, telemetry 없음
  • 파일 이름·경로·내용을 외부로 전송하지 않음
  • 설정, 북마크, rename recovery journal, 최근 200개 이력은 앱 컨테이너에만 저장
  • App Sandbox entitlement는 user-selected read/write와 app-scoped bookmark만 사용
  • Full Disk Access, 관리자 권한, root helper를 요청하지 않음

파일 내용 digest는 제품 동작에 필요하지 않아 앱이 계산하지 않습니다. 테스트에서만 이름 변경 전후 SHA-256이 같음을 검증합니다.

V1 제한

  • 파일시스템 이벤트는 저장 완료를 보장하지 않으므로, JamoFix는 debounce 뒤 두 snapshot이 같은 항목만 처리합니다. 매우 오래 쓰이는 파일은 다음 안정화 이벤트까지 보류될 수 있습니다.
  • vnode 보조 감시는 선택 트리의 현재 디렉터리마다 descriptor 하나를 사용합니다. 앱은 자기 프로세스 soft limit만 전체 감시 수+256으로 올리며 상한은 8,192입니다. rename 도중 이미 사라진 하위 경로는 부모 이벤트에서 최종 트리를 다시 등록하지만, 상한을 넘는 트리나 한도 상향 실패, 루트·권한 오류는 부분 감시로 숨기지 않고 오류 상태로 중단합니다.
  • renameatx_np(RENAME_EXCL)를 지원하지 않는 파일시스템에서는 파괴적인 fallback 없이 오류로 중단합니다.
  • stale bookmark는 자동으로 넓은 권한을 요청하지 않습니다. 사용자가 설정에서 폴더를 다시 선택해야 합니다.
  • 로그인 실행의 명시적 opt-in, 앱 재실행 간 등록 유지, opt-out과 등록 해제는 서명된 disposable 앱에서 확인했습니다. 실제 로그아웃/로그인 뒤 자동 기동은 release-stage 수동 dogfood가 필요하며, 등록 실패나 시스템 승인 필요 상태는 기록과 UI에 표시됩니다.
  • 메뉴 막대·설정의 실제 클릭 흐름, 로그인 재부팅 흐름, 장시간 FSEvents 부하는 자동화된 UI-independent 테스트 밖의 수동 dogfood 항목입니다.
  • 제품의 지원 표기는 macOS 13+로 유지하지만, 현재 확보된 실기기가 macOS 26.5.2뿐이어서 macOS 13 런타임 호환성은 검증 완료가 아닌 환경상 미검증 항목입니다.

구현·검증 상세는 docs/IMPLEMENTATION_REPORT.md를 참고하세요. 제품 계약은 docs/PRODUCT_SPEC.md가 우선합니다.

배포 준비 자료는 docs/DISTRIBUTION_CHECKLIST.mddocs/APP_STORE_METADATA.md에 정리되어 있습니다. 개인정보 처리방침은 PRIVACY.md, 변경 내역은 CHANGELOG.md를 참고하세요.

라이선스

MIT License

About

한글 파일·폴더 이름을 로컬에서 NFC로 정규화하는 macOS 메뉴 막대 앱

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages