Files
catalog_doc/catalogizer/ui/worker.py
Andrey Kruchinkin 49f91247c7 Каталогизатор документов: разбор папки, тезисы, поиск, GUI
Программа принимает папку с документами, извлекает из них текст и
метаданные и строит каталог: краткое описание, тезисы и ключевые слова
для каждого файла плюс поиск по всему собранному.

Ядро:
- extract: PDF, DJVU, DOC(X), RTF, ODT, CHM, EPUB, FB2, PPT(X), XLS(X), TXT;
  PDF передаётся MuPDF потоком в память (работают длинные пути) и ограничен
  по времени — повреждённый файл иначе чинится минутами;
- summarize: экстрактивное реферирование по TF-IDF, посчитанному на самой
  библиотеке, с лёгким стеммером для русского и английского;
- db: SQLite с полнотекстовым индексом FTS5, морфологический поиск с BM25;
- scanner: инкрементальный многопоточный обход, счётчик попыток защищает
  от файла, обрывающего разбор;
- report: автономный HTML с поиском, Markdown, JSON, CSV.

Интерфейс:
- окно PySide6 в тёмной теме: вкладки разбора и поиска, фоновый поток,
  карточка документа, правка своих ключевых слов;
- командная строка: build, scan, analyze, report, search, show, tag, stats;
- у каждой папки свой каталог, последняя обработанная запоминается.

Сборка: scripts/build_exe.py даёт Catalogizer.exe в Windows и
Catalogizer.app в macOS, иконки .ico и .icns рисуются кодом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 19:20:29 +03:00

79 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""@file worker.py
@brief Фоновый поток построения каталога.
Разбор библиотеки идёт минутами и часами, поэтому выполняется в отдельном
потоке QThread: окно остаётся отзывчивым, а ход работы приходит сигналами.
"""
from __future__ import annotations
import traceback
from pathlib import Path
from PySide6.QtCore import QThread, Signal
from .. import analyze as analyze_mod
from .. import config, db, report, scanner
class BuildWorker(QThread):
"""@brief Полный цикл: обход папки, анализ, выгрузка отчётов."""
#: сделано, всего, поясняющая строка
progress = Signal(int, int, str)
#: строка в журнал
message = Signal(str)
#: итоговая строка состояния
finished_ok = Signal(str)
#: текст ошибки с трассировкой
failed = Signal(str)
def __init__(self, root: Path, jobs: int, max_pages: int,
force: bool = False, parent=None) -> None:
super().__init__(parent)
self._root = Path(root)
self._jobs = jobs
self._max_pages = max_pages
self._force = force
self._stop = False
def request_stop(self) -> None:
"""@brief Просит остановиться после текущей порции файлов.
Разобранное уже записано в базу, поэтому следующий запуск продолжит
с того же места.
"""
self._stop = True
def run(self) -> None: # noqa: D102 — контракт QThread
try:
config.remember(self._root)
db_path, out_dir = config.paths_for(self._root)
con = db.connect(db_path)
self.message.emit("Папка: %s" % self._root)
stats = scanner.scan(
con, self._root, max_pages=self._max_pages, jobs=self._jobs,
force=self._force, quiet=True,
progress=lambda d, t, m: self.progress.emit(d, t, m),
stop=lambda: self._stop)
self.message.emit(
"Разобрано %(processed)d, с ошибками %(errors)d, "
"пропущено %(skipped)d, за %(seconds)s с" % stats)
self.progress.emit(0, 0, "Анализ текстов…")
analyze_mod.analyze(
con, quiet=True,
progress=lambda d, t, m: self.progress.emit(d, t, m))
self.progress.emit(0, 0, "Выгрузка отчётов…")
for kind, path in report.build_all(con, out_dir).items():
self.message.emit("%-5s %s" % (kind, path))
total = con.execute("SELECT COUNT(*) c FROM docs").fetchone()["c"]
con.close()
self.finished_ok.emit(
"Каталог готов: %d документов%s"
% (total, " (остановлено)" if self._stop else ""))
except Exception: # noqa: BLE001 — окно должно показать причину
self.failed.emit(traceback.format_exc())