Обзор Архитектуры¶
HistorySync использует понятную архитектуру MVVM (Model-View-ViewModel). UI и бизнес-логика разделены, поэтому одни и те же ключевые компоненты работают и в настольном приложении, и в headless 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;
- пагинацию и запросы на больших объёмах данных;
- upsert/дедупликацию по (browser_type, url, visit_time);
- закладки, аннотации, скрытые записи и данные устройств.
ExtractorManager и extractors¶
src/services/extractor_manager.py координирует обнаружение браузеров и поток извлечения данных. Реальные реализации находятся в src/services/extractors/.
Важно: - extractors для Chromium и Firefox используют WAL snapshots для чтения открытых браузерных БД; - extractor Safari обрабатывает формат истории macOS; - поддержка нового браузера того же семейства часто сводится к расширению существующего extractor.
Слой ViewModel¶
Текущий слой ViewModel небольшой и напрямую соответствует коду:
- MainViewModel координирует sync, backup, scheduler, tray и overlay;
- HistoryViewModel управляет состоянием поиска, пагинацией и виртуализированной выдачей;
- SettingsViewModel отдаёт UI сохранение настроек и действия обслуживания;
- ImportViewModel отвечает за импорт внешних БД.
Разделение i18n¶
Локализация разделена на два уровня:
- src/utils/i18n_core.py для CLI, services и models;
- src/utils/i18n.py для Qt-моста на стороне UI.
Это позволяет headless-режиму не зависеть от Qt, а GUI — корректно реагировать на переключение языка.
Модель Потоков¶
| Поток | Ответственность |
|---|---|
| Qt main thread | рендеринг UI и обработка сигналов |
| Scheduler thread | периодические триггеры sync / backup |
| Worker threads | I/O extractors, загрузки WebDAV, обслуживание |
| pynput thread | слежение за глобальными hotkeys |
| Favicon thread | загрузка и кэширование favicon |
Основной механизм межпоточного взаимодействия — Qt signals / slots.
Принципы Проектирования¶
- services не зависят от Qt, чтобы одна и та же логика работала и в GUI, и в CLI.
- файловые операции атомарны, чтобы избежать частично записанной конфигурации или резервных копий.
- конфиденциальность включена по умолчанию, внутренние URL браузеров и заблокированные домены фильтруются автоматически.
- мягкая деградация, приложение по возможности продолжает работать даже при повреждённой конфигурации или сбое удалённого backup. ```
Модель Потоков¶
| Поток | Ответственность |
|---|---|
| Основной поток Qt | Рендеринг UI, диспетчеризация сигналов |
| Поток планировщика | Периодические триггеры синхронизации/резервного копирования |
| Рабочие потоки | Файловый ввод/вывод экстракторов, загрузки WebDAV (Qt QThreadPool) |
| Поток pynput | Слушатель глобальных горячих клавиш |
| Поток иконок | Асинхронная загрузка и кэширование иконок |
Всё межпоточное взаимодействие использует сигналы/слоты Qt — фоновые потоки испускают сигналы; основной поток их обрабатывает.
Принципы Проектирования¶
- Сервисы не имеют зависимостей от Qt — они могут использоваться из CLI без интерфейса без
QApplication. - Атомарные операции с файлами — сохранения конфигурации и загрузки WebDAV используют паттерн temp-file-then-rename для предотвращения частичных записей.
- Приватность по умолчанию — URL вида
chrome://,about:,file://и расширения браузеров фильтруются автоматически. - Мягкая деградация — повреждённая конфигурация резервируется и заменяется значениями по умолчанию; приложение продолжает работу.