"""Настройки каталогизатора.""" from __future__ import annotations import os import sys from pathlib import Path def _default_root() -> Path: """Папка, предлагаемая при первом запуске. Переопределяется переменной окружения CATALOG_ROOT или аргументом команды. Жёсткий путь к библиотеке действует только там, где он есть, иначе берётся домашняя папка «Документы» — так программа осмысленно стартует и на macOS, и на Linux. """ given = os.environ.get("CATALOG_ROOT") if given: return Path(given).expanduser() library = Path(r"E:\Yandex.Disk\литература") if library.is_dir(): return library for name in ("Documents", "Документы"): candidate = Path.home() / name if candidate.is_dir(): return candidate return Path.home() DEFAULT_ROOT = _default_root() def _app_dir() -> Path: """Папка, где программа держит базы каталогов и отчёты. Из исходников — корень проекта. В собранной программе — рядом с самим файлом, кроме macOS: там ``.app`` считается неизменяемым (и может запускаться из карантина), поэтому данные уходят в ``~/Library/Application Support``. """ if not getattr(sys, "frozen", False): return Path(__file__).resolve().parent.parent if sys.platform == "darwin": target = Path.home() / "Library" / "Application Support" / "Catalogizer" target.mkdir(parents=True, exist_ok=True) return target return Path(sys.executable).resolve().parent APP_DIR = _app_dir() DB_PATH = APP_DIR / "catalog.db" # база «главной» библиотеки OUT_DIR = APP_DIR / "out" # отчёты по ней CATALOGS_DIR = APP_DIR / "catalogs" # базы остальных обработанных папок CURRENT_FILE = APP_DIR / ".current" # последняя обработанная папка # Расширения, которые считаем «документами» библиотеки. DOC_EXTENSIONS = { ".pdf", ".djvu", ".djv", ".doc", ".docx", ".rtf", ".odt", ".chm", ".epub", ".fb2", ".ppt", ".pptx", ".xls", ".xlsx", ".txt", ".md", } # Каталоги, которые не имеет смысла разбирать (SDK, репозитории, сборки). SKIP_DIR_NAMES = { ".git", ".svn", "node_modules", "__pycache__", ".idea", ".vscode", "Debug", "Release", "obj", "build", "cmake-build-debug", } # Мусорные txt/md из состава SDK и репозиториев — не тащим в каталог. NOISE_FILENAMES = { "readme.txt", "readme.md", "license.txt", "license.md", "licence.txt", "changelog.md", "changelog.txt", "contributing.md", "code_of_conduct.md", "manifest.txt", "makefile.txt", "notice.txt", "authors.md", } # Ограничения разбора. MAX_PDF_PAGES = 20 # сколько страниц читаем из начала документа MAX_TEXT_CHARS = 30_000 # сколько символов текста храним в базе MIN_TEXT_CHARS = 200 # меньше — считаем, что текстового слоя нет (скан) MIN_FILE_SIZE = 2_048 # файлы мельче игнорируем MAX_PDF_BYTES = 400_000_000 # PDF крупнее не разбираем PDF_TIME_BUDGET = 25 # секунд на один PDF: повреждённые чинятся вечно # Сколько тезисов и ключевых слов делать на документ. N_THESES = 5 N_KEYWORDS = 12 def _slug(root: Path) -> str: """Короткое имя каталога, однозначно привязанное к пути папки.""" import hashlib import re name = re.sub(r"[^\w\-]+", "_", root.name, flags=re.U).strip("_") or "catalog" tail = hashlib.sha1(str(root).lower().encode("utf-8")).hexdigest()[:6] return "%s-%s" % (name[:40], tail) def paths_for(root: Path) -> tuple[Path, Path]: """База и папка отчётов для указанной папки с документами.""" root = Path(root).resolve() if root == Path(DEFAULT_ROOT).resolve(): return DB_PATH, OUT_DIR slug = _slug(root) return CATALOGS_DIR / (slug + ".db"), OUT_DIR / slug def remember(root: Path) -> None: """Запоминает последнюю обработанную папку — её и берут search/report.""" CURRENT_FILE.write_text(str(Path(root).resolve()), encoding="utf-8") def current_root() -> Path: """Папка, с которой работали в последний раз.""" try: value = CURRENT_FILE.read_text(encoding="utf-8").strip() except OSError: return Path(DEFAULT_ROOT) return Path(value) if value else Path(DEFAULT_ROOT)