아키텍처 개요¶
HistorySync는 MVVM (Model-View-ViewModel) 기반 구조를 사용합니다. UI와 비즈니스 로직을 분리하여, 같은 핵심 기능을 데스크톱 앱과 헤드리스 CLI에서 함께 재사용할 수 있습니다.
고수준 다이어그램¶
graph TD
subgraph Entry Points
GUI["python -m src.main (GUI)"]
CLI["hsync / python -m src.cli (Headless)"]
end
subgraph Views ["views/ - PySide6 UI"]
MW[MainWindow]
HP[HistoryPage]
DP[DashboardPage]
SP[SettingsPage]
TV[TrayIcon]
OV[OverlayWindow]
end
subgraph ViewModels ["viewmodels/ - Qt signals and state"]
MVM[MainViewModel]
HVM[HistoryViewModel]
SVM[SettingsViewModel]
IVM[ImportViewModel]
end
subgraph Services ["services/ - Business Logic"]
EM[ExtractorManager]
LDB[LocalDatabase]
WD[WebDavSyncService]
EX[Exporter]
SC[Scheduler]
FK[FaviconCache]
end
subgraph Models ["models/ - Pure Data"]
HR[HistoryRecord]
AC[AppConfig]
BR[BookmarkRecord]
AR[AnnotationRecord]
end
subgraph Utils ["utils/ - Cross-cutting"]
I18N[i18n / i18n_core]
LOG[Logger]
SEC[security_utils]
THM[ThemeManager]
PATH[path_helper]
end
GUI --> Views
GUI --> ViewModels
CLI --> Services
Views --> ViewModels
ViewModels --> Services
Services --> Models
Services --> Utils
디렉토리 구조¶
src/
├── main.py
├── cli.py
├── models/
│ ├── app_config.py
│ └── history_record.py
├── services/
│ ├── local_db.py
│ ├── extractor_manager.py
│ ├── webdav_sync.py
│ ├── exporter.py
│ ├── scheduler.py
│ ├── favicon_cache.py
│ ├── favicon_manager.py
│ ├── browser_monitor.py
│ ├── browser_scanner.py
│ ├── db_importer.py
│ ├── migration_service.py
│ ├── mock_data_generator.py
│ └── extractors/
│ ├── base_extractor.py
│ ├── chromium_extractor.py
│ ├── firefox_extractor.py
│ ├── safari_extractor.py
│ └── favicon_extractor.py
├── viewmodels/
│ ├── main_viewmodel.py
│ ├── history_viewmodel.py
│ ├── settings_viewmodel.py
│ └── import_viewmodel.py
├── views/
│ ├── main_window.py
│ ├── history_page.py
│ ├── dashboard_page.py
│ ├── stats_page.py
│ ├── bookmarks_page.py
│ ├── settings_page.py
│ ├── overlay_window.py
│ ├── tray_icon.py
│ ├── export_dialog.py
│ └── import_dialog.py
└── utils/
├── constants.py
├── i18n.py
├── i18n_core.py
├── logger.py
├── path_helper.py
├── security_utils.py
├── search_parser.py
├── search_highlighter.py
├── single_instance.py
├── theme_manager.py
└── font_manager.py
이 목록은 실제 구조를 반영하는 핵심 모듈만 다룹니다. 자주 바뀌는 보조 파일까지 문서에 고정하지 않기 위한 선택입니다.
핵심 컴포넌트¶
AppConfig¶
src/models/app_config.py는 전체 사용자 설정을 담습니다. 중첩 설정은 JSON으로 저장되며, 손상된 설정 파일을 읽을 때는 자동 백업과 복구가 수행됩니다.
핵심 포인트:
- _fresh, _load_error 같은 런타임 필드는 저장되지 않습니다.
- WebDAV 비밀번호는 저장 시 암호화되고 로드 시 복호화됩니다.
- --fresh 모드는 임시 디렉토리만 사용하며 실제 사용자 데이터를 건드리지 않습니다.
LocalDatabase¶
src/services/local_db.py는 SQLite 래퍼로서 다음을 담당합니다.
- FTS5 전체 텍스트 검색
- 대용량 데이터셋에 대한 페이지네이션과 조회
- (browser_type, url, visit_time) 기준 upsert / 중복 제거
- 북마크, 주석, 숨김 기록, 장치 데이터 등 부가 저장소
ExtractorManager 와 추출기¶
src/services/extractor_manager.py가 브라우저 감지와 추출 흐름 전체를 조율합니다. 실제 추출 로직은 src/services/extractors/ 아래 구현들에 있습니다.
핵심 포인트: - Chromium/Firefox 추출기는 WAL 스냅샷으로 실행 중인 브라우저 DB를 안전하게 읽습니다. - Safari 추출기는 macOS 전용 기록 형식을 처리합니다. - 같은 계열 브라우저를 추가할 때는 기존 추출기 확장으로 끝나는 경우가 많습니다.
ViewModel 레이어¶
현재 ViewModel 레이어는 작고 실제 구현에 직접 대응합니다.
- MainViewModel은 동기화, 백업, 스케줄러, 트레이, 오버레이를 조정합니다.
- HistoryViewModel은 검색 상태, 페이지네이션, 가상화 리스트를 관리합니다.
- SettingsViewModel은 설정 저장과 유지보수 동작을 UI에 노출합니다.
- ImportViewModel은 외부 DB 가져오기를 담당합니다.
i18n 분리¶
로컬라이제이션은 두 층으로 나뉩니다.
- src/utils/i18n_core.py는 CLI, services, models용입니다.
- src/utils/i18n.py는 Qt 브리지를 추가한 UI용입니다.
이 분리 덕분에 헤드리스 모드는 Qt에 의존하지 않고, GUI는 언어 변경에 반응할 수 있습니다.
스레드 모델¶
| 스레드 | 책임 |
|---|---|
| Qt 메인 스레드 | UI 렌더링 및 시그널 처리 |
| Scheduler 스레드 | 주기적 sync / backup 트리거 |
| Worker 스레드 | 추출기 I/O, WebDAV 업로드, 유지보수 작업 |
| pynput 스레드 | 전역 단축키 감시 |
| Favicon 스레드 | 파비콘 다운로드 및 캐시 |
스레드 간 통신은 주로 Qt signals / slots로 처리됩니다.
설계 원칙¶
- services는 Qt에 의존하지 않는다. GUI와 CLI가 같은 핵심 로직을 공유할 수 있습니다.
- 파일 작업은 원자적으로 수행한다. 설정과 백업의 반쯤 기록된 상태를 피합니다.
- 프라이버시를 기본으로 보호한다. 내부 브라우저 URL과 블랙리스트 도메인은 자동으로 제외됩니다.
- 우아하게 성능 저하를 허용한다. 설정 손상이나 원격 백업 실패가 있어도 가능한 한 동작을 계속합니다. ```
스레딩 모델¶
| 스레드 | 책임 |
|---|---|
| Qt 메인 스레드 | UI 렌더링, 시그널 디스패치 |
| 스케줄러 스레드 | 주기적 동기화/백업 트리거 |
| 워커 스레드 | 추출기 I/O, WebDAV 업로드 (Qt QThreadPool) |
| pynput 스레드 | 전역 단축키 리스너 |
| 파비콘 스레드 | 비동기 파비콘 다운로드 및 캐싱 |
모든 크로스 스레드 통신은 Qt 시그널/슬롯을 사용합니다 — 백그라운드 스레드가 시그널을 발생시키면 메인 스레드가 처리합니다.
설계 원칙¶
- 서비스에는 Qt 의존성 없음 —
QApplication없이도 헤드리스 CLI에서 사용할 수 있습니다. - 원자적 파일 작업 — 설정 저장 및 WebDAV 업로드는 부분 쓰기를 방지하기 위해 임시 파일 후 이름 변경 방식을 사용합니다.
- 기본적으로 프라이버시 보호 —
chrome://,about:,file://, 브라우저 확장 URL은 자동으로 필터링됩니다. - 우아한 성능 저하 — 손상된 설정은 백업되고 기본값으로 교체되며, 앱은 계속 실행됩니다.