89 lines
4.5 KiB
Python
89 lines
4.5 KiB
Python
"""Корень вики проекта: поле 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)]
|