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()