"""Команды модуля wiki: wiki_sync (синхронизация дерева проекта) и wiki_fetch (файл вложения). Обе читают Eva.""" import re from ...core import queue from ...core.errors import CommandError from ...core.modules import CommandHooks from . import attachments, sync TYPES = {"wiki_sync", "wiki_fetch"} SPEC = {"wiki_sync": {"required": ["label", "root"], "optional": []}, "wiki_fetch": {"required": ["attachment_id", "url"], "optional": []}} _ROOT = re.compile(r"^DOC-\d+$") def validate(cmds) -> list[dict]: if not isinstance(cmds, list) or not cmds: raise CommandError("пакет без команд") out = [] for i, cmd in enumerate(cmds, 1): if not isinstance(cmd, dict) or "type" not in cmd: raise CommandError(f"команда {i}: нет type") ctype, params = cmd["type"], cmd.get("params") or {} if ctype not in SPEC: raise CommandError(f"команда {i}: неизвестный тип «{ctype}»") if not isinstance(params, dict): raise CommandError(f"команда {i}: params не объект") spec = SPEC[ctype] missing = [f for f in spec["required"] if params.get(f) in (None, "")] if missing: raise CommandError(f"команда {i} ({ctype}): нет {', '.join(missing)}; ожидаются {', '.join(spec['required'])}") unknown = set(params) - set(spec["required"]) if unknown: raise CommandError(f"команда {i} ({ctype}): лишние параметры {', '.join(sorted(unknown))}; " f"ожидаются {', '.join(spec['required'])}") if ctype == "wiki_sync": if not isinstance(params["label"], str): raise CommandError(f"команда {i}: label — строка") if not isinstance(params["root"], str) or not _ROOT.match(params["root"]): raise CommandError(f"команда {i}: root — код DOC-<цифры>") else: a = params["attachment_id"] if not isinstance(a, int) or isinstance(a, bool): raise CommandError(f"команда {i}: attachment_id — целое") if not isinstance(params["url"], str): raise CommandError(f"команда {i}: url — строка") out.append({"type": ctype, "params": params}) return out def check_batch(conn, cmds, project, files_dir) -> list[dict]: """wiki_sync живёт в пакете своего проекта: project пакета равен label команды.""" for i, c in enumerate(cmds, 1): if c["type"] == "wiki_sync" and c["params"]["label"] != project: raise CommandError(f"команда {i}: проект пакета ({project}) не равен label ({c['params']['label']})") return cmds def enqueue_sync(conn, label: str, root: str, author: str) -> dict: """Пакет «Вики <метка>» из одной команды wiki_sync.""" return queue.create_batch(conn, f"Вики {label}", author, [{"type": "wiki_sync", "params": {"label": label, "root": root}}], project=label) def active_sync(conn, label: str) -> dict | None: """Пакет wiki_sync проекта не в терминальном статусе (есть команда queued или claimed) или None.""" row = conn.execute( "SELECT b.id FROM batch b WHERE b.project = ? AND EXISTS (SELECT 1 FROM command c WHERE c.batch_id = b.id " "AND c.type = 'wiki_sync' AND c.status IN ('queued', 'claimed')) ORDER BY b.id DESC LIMIT 1", (label,)).fetchone() return queue.batch_view(conn, row["id"], with_commands=False) if row else None def on_failed(conn, cmd, now) -> None: (attachments if cmd["type"] == "wiki_fetch" else sync).on_failed(conn, cmd, now) def check_result(conn, cmd, status, result, files_dir) -> tuple[str, str | None]: if cmd["type"] == "wiki_fetch": return attachments.check_result(conn, cmd, status, result, files_dir) return sync.check_result(conn, cmd, status, result, files_dir) def on_result(conn, cmd, status, result, jira_calls, rate_limited, now, files_dir) -> None: if cmd["type"] == "wiki_fetch": attachments.on_result(conn, cmd, status, result, now) else: sync.on_result(conn, cmd, status, result, jira_calls, rate_limited, now, files_dir) def after_result(conn, cmd, status) -> None: if cmd["type"] == "wiki_sync": sync.after_result(conn, cmd, status) # wiki_sync — sync.py (хуки before/abort/after только у него), wiki_fetch — attachments.py HOOKS = CommandHooks( types=frozenset(TYPES), read_types=frozenset(TYPES), validate=validate, check_batch=check_batch, tail=lambda conn, cmds, project: None, pause=lambda conn, ctype: 0, deliver_params=lambda conn, cmd, files_dir: cmd["params"], temp_definer=None, temp_keys_used=lambda cmd: [], substitute=lambda ctype, params, mapping: params, drop_unresolved=lambda cmd, unresolved: unresolved, retry_params=lambda cmd: cmd["params"], check_confirmed=lambda conn, cmd: (False, None), on_claim=sync.on_claim, on_failed=on_failed, check_result=check_result, on_result=on_result, after_result=after_result, on_late_result=lambda *a: False, before_result=sync.before_result, abort_result=sync.abort_result)