products_state.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. r"""产物开关 —— 把 outputs\<场>\ 下的产物**直接清掉** / 看当前状态 (人工检查页面空状态用)。
  4. ## 2026-09-16 用户令: 清除产物**不留备份**
  5. 原设计是"移动到 `_products_off/<场>/` + 清单留痕, 一条命令还原"。用户令改为**不备份**:
  6. 清掉就是清掉。于是本脚本:
  7. · `--off --yes` 真删除 (rmtree), 打印删了什么; **不再产生 `_products_off*` 目录**;
  8. · `--status` 看产物在位/已清 (清掉后就是"已清", 没有备份清单可列);
  9. · `--on` 已**移除**: 没有备份就没有"挪回"。需要恢复缺失的**随包件**时, 用
  10. `python scripts/products_restore_missing.py --stash <交付包.zip>`
  11. (从交付包 zip 里按需补齐 —— 那是"从交付件恢复", 不是"留一份备份")。
  12. ## 保留不动的
  13. - `data\raw\` 现场数据 (检查的是"产物没了会怎样", 不是"数据没了会怎样")
  14. - `release\` 门户与三维/交付件 (那是外壳, 不是数据产物)
  15. - `logs\` `run\` 运行期文件
  16. - ★ `outputs\<场>\windscada\_pre_rebuild_20260911\` **不再特殊保留**: 它曾是"随包基线备份"而在清除时豁免,
  17. 现在按"不留备份"一并删掉 —— 需要等价验收时, 用 `products_restore_missing.py --stash <交付包.zip>`
  18. 把这个目录从交付包里补回来即可。
  19. 用法:
  20. python scripts/products_state.py --status # 看当前产物状态
  21. python scripts/products_state.py --off --yes # 清掉全部产物 (真删除, 不可恢复; --yes 是防手滑)
  22. """
  23. from __future__ import annotations
  24. import argparse
  25. import pathlib
  26. import shutil
  27. import sys
  28. ROOT = pathlib.Path(__file__).resolve().parents[1]
  29. sys.path.insert(0, str(ROOT))
  30. from src import paths as P # noqa: E402
  31. LEGACY_OFF = ROOT / '_products_off' # 旧版暂存区 (只用于提示, 不再写入)
  32. def farms() -> list[pathlib.Path]:
  33. out = ROOT / 'outputs'
  34. return sorted(p for p in out.iterdir() if p.is_dir()) if out.is_dir() else []
  35. def plan() -> list[pathlib.Path]:
  36. """→ 待清除的产物目录列表 (产物仓本身)。"""
  37. return [d for fam in farms() for d in sorted(fam.iterdir())]
  38. def count(p: pathlib.Path) -> tuple[int, float]:
  39. if p.is_file():
  40. return 1, p.stat().st_size / 1024 / 1024
  41. fs = [f for f in p.rglob('*') if f.is_file()]
  42. return len(fs), sum(f.stat().st_size for f in fs) / 1024 / 1024
  43. def do_off(assume_yes=False) -> int:
  44. items = [p for p in plan() if p.exists()]
  45. if not items:
  46. print('产物已经是空的 (用 --status 看)')
  47. return 0
  48. total_n = total_mb = 0
  49. rows = []
  50. for src in items:
  51. n, mb = count(src)
  52. rows.append((P.rel(src), n, mb))
  53. total_n += n
  54. total_mb += mb
  55. print('即将**删除**以下产物 (按用户令: 不留备份, 删除后不可恢复):')
  56. for rel, n, mb in rows:
  57. print(f' {rel:34s} {n:5d} 件 {mb:8.1f} MB')
  58. print(f' 合计 {len(rows)} 项 / {total_n} 件 / {total_mb:.1f} MB')
  59. print(' 恢复办法: python scripts/products_restore_missing.py --stash <交付包.zip> (从交付包补缺失件)')
  60. if not assume_yes:
  61. print('\n[X] 未执行: 这是不可恢复操作, 请显式加 --yes (运维控制台里点按钮时已由前端确认过一次)')
  62. return 2
  63. for src in items:
  64. shutil.rmtree(src, ignore_errors=True) if src.is_dir() else src.unlink(missing_ok=True)
  65. if LEGACY_OFF.exists():
  66. print(f' [!] 检测到旧版暂存区 {P.rel(LEGACY_OFF)} —— 按"不留备份"的口径, 本次**未删除**它; '
  67. f'确认不需要后请手工删掉 (它是旧设计留下的产物备份)')
  68. print(f'\n已删除 {len(rows)} 项 / {total_n} 件 / {total_mb:.1f} MB (无备份)')
  69. return 0
  70. def do_status() -> int:
  71. items = [p for p in plan() if p.exists()]
  72. if not items:
  73. print('当前: 产物**已清空** (页面应呈空状态)')
  74. print(' 恢复: python scripts/products_restore_missing.py --stash <交付包.zip>')
  75. return 0
  76. print(f'当前: 产物**在位** ({len(items)} 项)')
  77. for src in items:
  78. n, mb = count(src)
  79. print(f' {P.rel(src):34s} {n:5d} 件 {mb:8.1f} MB')
  80. print(' 说明: 按 2026-09-16 用户令, 清除产物不留备份 (没有 _products_off/ 可还原)')
  81. return 0
  82. if __name__ == '__main__':
  83. ap = argparse.ArgumentParser()
  84. g = ap.add_mutually_exclusive_group(required=True)
  85. g.add_argument('--off', action='store_true', help='清掉全部产物 (**直接删除, 不留备份**)')
  86. g.add_argument('--status', action='store_true', help='看当前状态')
  87. g.add_argument('--on', action='store_true', help='[已移除] 旧版的"挪回" —— 见文件头说明')
  88. ap.add_argument('--yes', action='store_true', help='确认执行不可恢复的清除 (--off 必带)')
  89. a = ap.parse_args()
  90. if a.on:
  91. print('[X] --on 已移除: 按用户令"清除产物不留备份", 没有备份可挪回。\n'
  92. ' 要从交付包补回缺失的随包件: python scripts/products_restore_missing.py --stash <交付包.zip>\n'
  93. ' 要重新算出数据面产物: python scripts/rebuild_all.py (SCADA 侧加 --scada)')
  94. sys.exit(3)
  95. sys.exit(do_off(assume_yes=a.yes) if a.off else do_status())