← 返回 Day 167 正文

Day 167 · 练习与清单

Day 167 — 文件监控(watchdog)· 完成清单与练习

建议顺序:先读完 README.md(尤其第 2、3、5 节),再跑 code/ 下的 4 个文件, 最后做练习。每个练习都要求"能解释为什么",而不只是"跑通"。


✅ 今日完成清单

一、环境与依赖

二、跑通示例代码(每项都要求先预测输出,再运行验证)

三、动手改造("读懂了"的唯一证明)

四、概念自检(不看 README,口头或纸面回答)


📝 基础练习题(1–3)

练习 1:最小可用的"新文件落盘播报器"

写一个脚本,监控给定目录(非递归),要求:

  1. 只在文件创建/修改时输出 [新文件] name (size bytes)
  2. 输出时必须带 time.strftime("%H:%M:%S") 前缀
  3. 忽略隐藏文件(. 开头)和 .tmp 结尾的文件
  4. 收到 Ctrl+C 后打印 共收到 N 个事件 并干净退出(进程真的结束,不是假死)

验收标准:ps aux | grep 你的脚本名 在退出后应该查不到残留进程。

💡 提示(先自己写,卡住了再看) - 事件对象有 `.is_directory`、`.src_path`、`.event_type`。 - 文件大小用 `os.path.getsize(path)`,但要对 `FileNotFoundError` 兜底 (事件到达时文件可能已被删)。 - 干净退出三件套:`observer.stop()` → `observer.join()` → `sys.exit(0)`。

练习 2:文件名非法字符检查器

写一个 handler,对每个新建文件检查文件名是否包含 <>:"/\|?* 或首尾空格,命中就打印 ⚠ 非法文件名: ...。

思考并回答:为什么这个检查放在 on_created 而不是 on_any_event 里更合适? (提示:考虑"写一个文件"过程中 modified 事件的数量)

💡 提示 `on_any_event` 会被 created/modified/moved/closed 同时触发,同一个坏名字会被报 3~5 次。 `on_created` 每个文件生命周期只来一次 —— 这就是"事件选择"本身就是一种去重。

练习 3:用 PollingObserver 复现"丢事件"

要求:

  1. 用 PollingObserver(timeout=2) 监控目录
  2. 在另一个终端快速创建再删除一个文件(存活 < 1 秒)
  3. 观察:控制台没有任何输出
  4. 改用默认 Observer 重跑一次,观察这次有输出

写下结论:轮询丢事件的根因是什么?生产上什么场景可以接受这种丢失?


🚀 进阶挑战题(4–5)

练习 4:带"文件写入完成"信号的生产级搬运

给 04-auto-classify.py 增加一个更可靠的"写完了"判定:文件大小连续 3 次采样不变 (采样间隔 0.5s)才允许搬运,与现有静默期防抖做与运算。

要求:

  1. 新增 --stable-checks 3 参数,默认 3;设为 0 时退化为纯防抖
  2. 用 threading.Lock 保护 Pending 的大小快照,避免多线程读脏数据
  3. 写一个单测式 --self-test 用例,用临时文件模拟"文件在增长"与"文件已静止"两种情况

思考:为什么"大小稳定"单独用不够,必须再叠加"忽略临时扩展名"?

💡 提示 有些写入器(如 `dd`、某些网络下载)会**先分配 0 字节再一次性写**,中途长时间"大小稳定"。 所以"稳定"是必要不充分条件;忽略临时名 + 静默期,才是工程上的三重保险。

练习 5:设计一个"inbox 到归档"的状态机

把 04-auto-classify.py 的分类结果从"直接移动"改成"三阶段状态机":

  NEW --(静默期到)--> VERIFIED --(移动成功)--> ARCHIVED
           |                  |
           +--(超时 30s)--> QUARANTINE(进 quarantine/ 待人工处理)

要求:

  1. 每个文件的一行审计日志里必须能看出它走过的每个状态(含时间戳)
  2. 卡在 VERIFIED 超过 30 秒的文件,自动移入 quarantine/
  3. 进程重启后能恢复:启动时扫描 operations.jsonl,把未 ARCHIVED 的文件重新纳入待办

为什么这题值得做:现实里的文件流水线从来不是"一步到位", 只有状态机才能让"卡住的"变成"可见的",而不是"静默丢掉的"。