1. 程式人生 > 實用技巧 >watchdog 監控檔案變化使用總結

watchdog 監控檔案變化使用總結

概述

首先宣告,本文討論的 watchdog,不是微控制器裡的 watchdog,也不是 linux 中的 watchdog,而是 python 世界裡用來監視檔案系統變化的一個第三方模組。在 python 中檔案監視主要有兩個庫,一個是 pyinotify,一個是 watchdog。pyinotify 依賴於 linux 平臺的 inotify 機制,只能應用在 linux 平臺上。watchdog 則對不同平臺的的事件都進行了封裝,不僅可以監視 windows 檔案系統,也可以監視 linux 的檔案系統。

檔案系統事件類

檔案系統事件基類定義如下:

   watchdog.events.FileSystemEvent(event_type, src_path, is_directory=False)

  # event.event_type - 事件型別,為 moved / deleted / created / modified 其中之一
  # event.src_path - 觸發該事件的檔案或目錄路徑
  # event.is_directory - 該事件是否由一個目錄觸發

由 watchdog.events.FileSystemEvent 基類派生的子類如下:

watchdog.events.FileDeletedEvent()

  # 檔案被刪除時觸發該事件

  watchdog.events.DirDeletedEvent()
  # 目錄被刪除時觸發該事件

  watchdog.events.FileCreatedEvent()


  # 檔案被建立時觸發該事件

  watchdog.events.DirCreatedEvent()
  # 目錄被建立時觸發該事件

  watchdog.events.FileModifiedEvent()
  # 檔案被修改時觸發該事件(修改檔案內容、修改檔案inode資訊如許可權和訪問時間,都會觸發該事件)

  watchdog.events.DirModifiedEvent()
  # 目錄被修改時觸發該事件

  watchdog.events.FileMovedEvent()
  # 檔案被移動或重新命名時觸發該事件,因為涉及檔案移動,所以除了event.src_path表示原路徑,還有event.dest_path表示目的路徑

  watchdog.events.DirMovedEvent()
  # 目錄被移動或重新命名時觸發該事件,因為涉及檔案移動,所以除了event.src_path表示原路徑,還有event.dest_path表示目的路徑

檔案系統事件處理類

watchdog.events.FileSystemEventHandler 是事件處理器的基類,用於處理事件,使用者需繼承該類,並在子類中重寫對應方法。需要使用者重寫的方法有:

self.on_any_event(event)
  # 任何事件發生都會首先執行該方法,該方法預設為空,dispatch()方法會先執行該方法,然後再把 event 分派給其他方法處理

  self.on_moved(event)
  # 處理 DirMovedEvent 和 FileMovedEvent 事件,預設為空

  self.on_created(event)
  # 處理 DirCreatedEvent 和 FileCreatedEvent 事件,預設為空

  self.on_deleted(event)
  # 處理 DirDeletedEvent 和 FileDeletedEvent 事件,預設為空

  self.on_modified(event)
  # 處理 DirModifiedEvent 和 FileModifiedEvent 事件,預設為空

以上方法中,event 有幾個屬性可用:

  • event.is_directory - 觸發事件的是否為資料夾
  • event.src_path - 源路徑
  • event.dest_path - 目標路徑
最簡單的應用示例

下面的例子展示瞭如何監視 D:\XufiveGit\PEC\client 資料夾內所有檔案的 moved / deleted / created / modified。請注意,重新命名被視為 moved (移動)。

#-*- coding: utf-8 -*-

from watchdog.observers import Observer
from watchdog.events import *

class FileEventHandler(FileSystemEventHandler):
  def on_any_event(self, event):
    pass

  def on_moved(self, event):
    if event.is_directory:
      print("directory moved from {0} to {1}".format(event.src_path,event.dest_path))
    else:
      print("file moved from {0} to {1}".format(event.src_path,event.dest_path))

  def on_created(self, event):
    if event.is_directory:
      print("directory created:{0}".format(event.src_path))
    else:
      print("file created:{0}".format(event.src_path))

  def on_deleted(self, event):
    if event.is_directory:
      print("directory deleted:{0}".format(event.src_path))
    else:
      print("file deleted:{0}".format(event.src_path))

  def on_modified(self, event):
    if event.is_directory:
      print("directory modified:{0}".format(event.src_path))
    else:
      print("file modified:{0}".format(event.src_path))

if __name__ == "__main__":
  import time
  observer = Observer()
  event_handler = FileEventHandler()
  observer.schedule(event_handler, r"D:\XufiveGit\PEC\client", True)
  observer.start()
  try:
    while True:
      time.sleep(1)

  except KeyboardInterrupt:
    observer.stop()
存在的問題
真正測試過之後,你會發現,上面的例子幾乎沒有實用價值,因為,檔案操作引發的事件比我們想象的多了不少,而且難以在事件函式中做出針對性處理。
比如,新增一個檔案,勢必引發 created 事件,同時也會導致所在資料夾的 modified 事件,如果該檔案目錄比較深,還會引發多層父級資料夾的 modified 事件。 如果,你覺得這不算什麼問題,那麼,在 windows 平臺上每一次的檔案修改引發兩次 modified 事件,算不算一個令人頭疼的問題呢?
在 linux 平臺上表現如何,我沒有測試過,但在 windows 平臺上,由於 watchdog 封裝的是 windows 系統的 FileSystemWatcher Events,
處理檔案的過程中執行了多次檔案系統操作,無法避免地觸發了多次事件。

改進方案

如果對監視檔案的實時性要求不高,又懶得處理一大堆事件,那麼,比較事件前後的資料夾快照就是一個值得嘗試的改進方案。實現這個思路,有三個前提條件:

快速獲取資料夾快照。幸運的是,watchdog 模組為我們提供了 DirectorySnapshot 功能
可以接受200毫秒的延時。檔案操作引發的一大堆事件會集中在一個較短的時間內,一般情況下,在檔案操作之後200毫秒獲取資料夾快照,是一個不錯的間隔
快速比較資料夾快照。這也不是問題,watchdog 模組有 DirectorySnapshotDiff 子模組

改進思路是這樣的:設定一個定時器, 200毫秒後抓取快照,並與上一張快照比較。每當有事件發生時,檢查定時器是否已經啟動。如果未啟動,則直接啟動定時器;否則,說明該事件距離上次事件不足200毫秒,可視為是同一組事件,此時終止定時器,再次重啟。具體程式碼如下:

#-*- coding: utf-8 -*-

import os, threading
from watchdog.observers import Observer
from watchdog.events import *
from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff

class FileEventHandler(FileSystemEventHandler):
  def __init__(self, aim_path):
    FileSystemEventHandler.__init__(self)
    self.aim_path = aim_path
    self.timer = None
    self.snapshot = DirectorySnapshot(self.core.proj_path)

  def on_any_event(self, event):
    if self.timer:
      self.timer.cancel()
    self.timer = threading.Timer(0.2, self.checkSnapshot)
    self.timer.start()

  def checkSnapshot(self):
    snapshot = DirectorySnapshot(self.aim_path)
    diff = DirectorySnapshotDiff(self.snapshot, snapshot)
    self.snapshot = snapshot
    self.timer = None
    #下面是應處理的各種事項
    print("files_created:", diff.files_created)
    print("files_deleted:", diff.files_deleted)
    print("files_modified:", diff.files_modified)
    print("files_moved:", diff.files_moved)
    print("dirs_modified:", diff.dirs_modified)
    print("dirs_moved:", diff.dirs_moved)
    print("dirs_deleted:", diff.dirs_deleted)
    print("dirs_created:", diff.dirs_created)
    # 接下來就是你想幹的啥就乾點啥,或者該乾點啥就乾點啥
    pass

class DirMonitor(object):
  """資料夾監視類"""

  def __init__(self, aim_path):
    """建構函式"""
    self.aim_path= aim_path
    self.observer = Observer()

  def start(self):
    """啟動"""
    event_handler = FileEventHandler(self.aim_path)
    self.observer.schedule(event_handler, self.aim_path, True)
    self.observer.start()

  def stop(self):
    """停止"""
    self.observer.stop()

if __name__ == "__main__":
monitor = DirMonitor(r"D:\XufiveGit\PEC\client")
monitor.start()