autool-dispatcher/scripts/reset_catalog_update_state.py
2026-06-17 19:50:39 +08:00

190 lines
6.5 KiB
Python

#!/usr/bin/env python3
import argparse
import json
import os
import sqlite3
import sys
import time
from typing import Any, Dict
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from config import MONITORING_DB_PATH
def _safe_loads(value: Any) -> Dict[str, Any]:
if not value:
return {}
try:
payload = json.loads(value)
except (TypeError, ValueError):
return {}
return payload if isinstance(payload, dict) else {}
def _clear_payload_last_updated(raw_payload: Any) -> str:
payload = _safe_loads(raw_payload)
if not payload:
return str(raw_payload or "")
payload["last_updated"] = ""
original_row = payload.get("original_row")
if isinstance(original_row, dict):
original_row = dict(original_row)
original_row["last_updated"] = ""
original_row["最后更新"] = ""
original_row["更新时间"] = ""
payload["original_row"] = original_row
return json.dumps(payload, ensure_ascii=False)
def reset_catalog_update_state(args) -> int:
db_path = str(args.db_path or "").strip()
apply_changes = bool(args.apply)
clear_payload = bool(args.clear_payload)
now_iso = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
with sqlite3.connect(db_path) as connection:
connection.row_factory = sqlite3.Row
table_row = connection.execute(
"""
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name = 'app_catalog'
"""
).fetchone()
if not table_row:
raise SystemExit(f"app_catalog not found in {db_path}")
counts = connection.execute(
"""
SELECT
COUNT(*) AS total,
COALESCE(SUM(CASE WHEN COALESCE(last_updated, '') != '' THEN 1 ELSE 0 END), 0) AS last_updated_rows,
COALESCE(SUM(CASE WHEN COALESCE(last_update_interval_days, 0) != 0 THEN 1 ELSE 0 END), 0) AS interval_rows
FROM app_catalog
"""
).fetchone()
task_counts = connection.execute(
"""
SELECT
COALESCE(SUM(CASE WHEN COALESCE(is_new_app, 1) = 0 THEN 1 ELSE 0 END), 0) AS app_update_rows,
COALESCE(
SUM(
CASE
WHEN task_status = 'pending'
AND COALESCE(is_new_app, 1) = 0
AND COALESCE(error_reason, '') LIKE 'catalog_version_update:%'
THEN 1
ELSE 0
END
),
0
) AS version_pending_rows
FROM collection_task
"""
).fetchone()
if not apply_changes:
print(
"dry_run=1 db={db} total={total} clear_last_updated={last_updated_rows} "
"reset_interval_rows={interval_rows} app_update_tasks={app_update_rows} "
"delete_version_pending={version_pending_rows}".format(
db=db_path,
total=int(counts["total"] or 0),
last_updated_rows=int(counts["last_updated_rows"] or 0),
interval_rows=int(counts["interval_rows"] or 0),
app_update_rows=int(task_counts["app_update_rows"] or 0),
version_pending_rows=int(task_counts["version_pending_rows"] or 0),
)
)
return 0
cursor = connection.execute(
"""
DELETE FROM collection_task
WHERE task_status = 'pending'
AND COALESCE(is_new_app, 1) = 0
AND COALESCE(error_reason, '') LIKE 'catalog_version_update:%'
"""
)
deleted_version_pending = int(cursor.rowcount or 0)
cursor = connection.execute(
"""
UPDATE app_catalog
SET last_update_interval_days = 0,
updated_at = ?
WHERE COALESCE(last_update_interval_days, 0) != 0
""",
(now_iso,),
)
reset_interval_rows = int(cursor.rowcount or 0)
if clear_payload:
payload_rows = connection.execute(
"""
SELECT package_name, task_payload_json
FROM app_catalog
WHERE COALESCE(task_payload_json, '') != ''
"""
).fetchall()
for row in payload_rows:
connection.execute(
"""
UPDATE app_catalog
SET task_payload_json = ?,
updated_at = ?
WHERE package_name = ?
""",
(_clear_payload_last_updated(row["task_payload_json"]), now_iso, row["package_name"]),
)
cursor = connection.execute(
"""
UPDATE app_catalog
SET last_updated = '',
updated_at = ?
WHERE COALESCE(last_updated, '') != ''
""",
(now_iso,),
)
cleared_last_updated = int(cursor.rowcount or 0)
print(
"dry_run=0 db={db} cleared_last_updated={cleared_last_updated} "
"reset_interval_rows={reset_interval_rows} deleted_version_pending={deleted_version_pending} "
"clear_payload={clear_payload}".format(
db=db_path,
cleared_last_updated=cleared_last_updated,
reset_interval_rows=reset_interval_rows,
deleted_version_pending=deleted_version_pending,
clear_payload=int(clear_payload),
)
)
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Clear app_catalog last_updated and delete pending catalog_version_update collection_task rows."
)
parser.add_argument("--db-path", default=MONITORING_DB_PATH, help="Target monitoring sqlite path.")
parser.add_argument("--apply", action="store_true", help="Apply changes. Without this, only prints counts.")
parser.add_argument(
"--clear-payload",
action="store_true",
help="Also clear last_updated inside task_payload_json/original_row.",
)
return parser
def main() -> int:
return reset_catalog_update_state(build_parser().parse_args())
if __name__ == "__main__":
raise SystemExit(main())