#!/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())