"""Корень вики проекта: поле wiki_root, значения для GET /projects, хуки создания и правки проекта.""" import glob import logging import shutil from ...core import db, queue from ...core.modules import ProjectField from . import commands, paths from .roots import parse_root, parse_root_edit # noqa: F401 parse_root — прежнее имя для импортёров log = logging.getLogger(__name__) FIELDS = (ProjectField("wiki_root", parse_root, editable=True, validate_edit=parse_root_edit),) def view(conn, labels: list[str]) -> dict[str, dict]: """Поля проекта для списка меток; без корня все значения None.""" if not labels: return {} marks = ", ".join("?" * len(labels)) rows = {r["label"]: r for r in conn.execute(f"SELECT * FROM wiki_project WHERE label IN ({marks})", labels)} out = {} for label in labels: r = rows.get(label) out[label] = {"wiki_root": r["root_code"] if r else None, "wiki_root_title": r["root_title"] if r else None, "wiki_pages": r["pages_count"] if r else None, "wiki_synced_at": r["synced_at"] if r else None, "wiki_sync_error": r["sync_error"] if r else None} return out def on_created(conn, project, fields, author) -> dict | None: """Проект создан (в его транзакции): с корнем — строка wiki_project и пакет синхронизации.""" root = fields.get("wiki_root") if root is None: return None conn.execute("INSERT INTO wiki_project(label, root_code) VALUES (?, ?)", (project["label"], root)) return commands.enqueue_sync(conn, project["label"], root, author) def _cancel_queued(conn, label: str) -> None: """Отменить пакеты wiki_sync проекта, ни одна команда которых ещё не выдана.""" ids = [r["id"] for r in conn.execute( "SELECT b.id FROM batch b WHERE b.project = ? AND b.cancelled_at IS NULL " "AND EXISTS (SELECT 1 FROM command c WHERE c.batch_id = b.id AND c.type = 'wiki_sync' AND c.status = 'queued') " "AND NOT EXISTS (SELECT 1 FROM command c WHERE c.batch_id = b.id AND c.status NOT IN ('queued'))", (label,))] for batch_id in ids: queue.cancel_batch(conn, batch_id) def _drop_copy(conn, label: str) -> None: """Сброс корня: строки проекта, индекс поиска (если он уже создан) и каталог копии.""" if db.has_table(conn, "wiki_page_fts"): conn.execute("DELETE FROM wiki_page_fts WHERE label = ?", (label,)) conn.execute("DELETE FROM wiki_page WHERE label = ?", (label,)) conn.execute("DELETE FROM wiki_attachment WHERE label = ?", (label,)) conn.execute("DELETE FROM wiki_project WHERE label = ?", (label,)) try: base = paths.project_dir(conn, label) except RuntimeError: # база без файла (тесты в памяти): копии на диске нет return leftovers = sorted(base.parent.glob(glob.escape(f".{base.name}") + ".new*")) if base.parent.is_dir() else [] for d in (base, *leftovers, base.with_name(f".{base.name}.old")): try: shutil.rmtree(d) except FileNotFoundError: pass except OSError as e: log.warning("вики: каталог %s не удалён: %s", d, e) def on_updated(conn, project, fields, author) -> list[dict]: """Корень изменён (в транзакции PATCH): невыданные пакеты проекта отменяются; новый корень — пакет синхронизации, прежняя копия остаётся до успеха; None — копия, страницы, индекс и вложения проекта удаляются.""" if "wiki_root" not in fields: return [] label, root = project["label"], fields["wiki_root"] row = conn.execute("SELECT root_code FROM wiki_project WHERE label = ?", (label,)).fetchone() if (row["root_code"] if row else None) == root: return [] _cancel_queued(conn, label) if root is None: _drop_copy(conn, label) return [] if row is None: conn.execute("INSERT INTO wiki_project(label, root_code) VALUES (?, ?)", (label, root)) else: conn.execute("UPDATE wiki_project SET root_code = ? WHERE label = ?", (root, label)) return [commands.enqueue_sync(conn, label, root, author)]