概述
今天在微信公众号上看到一篇有关通过watchdog监测目录和文件的文章,感觉还不错,在此记录下并分享。
watchdog用来监控指定目录/文件的变化,如添加删除文件或目录、修改文件内容、重命名文件或目录等,每种变化都会产生一个事件,且有一个特定的事件类与之对应,然后再通过事件处理类来处理对应的事件,怎么样处理事件完全可以自定义,只需继承事件处理类的基类并重写对应实例方法。
官方demo
import sys
import time
import logging
from watchdog.observers import Observer
from watchdog.events import LoggingEventHandler
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
path = sys.argv[1] if len(sys.argv) > 1 else '.'
event_handler = LoggingEventHandler()
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
finally:
observer.stop()
observer.join()
运行结果查看:
/tmp: 待监测目录,当在tmp目录下新增1.txt文件时,可看到相应格式的打印信息。
使用说明
-
Create an instance of the
watchdog.observers.Observerthread class. -
Implement a subclass of
watchdog.events.FileSystemEventHandler(or as in our case, we will use the built-inwatchdog.events.LoggingEventHandler, which already does). -
Schedule monitoring a few paths with the
observerinstance attaching theevent handler. -
Start the
observerthread and wait for it generate events without blocking our main thread.
实践demo
通过监测目录,实现文件移动。
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import ntpath
import shutil
import os
import time
def get_filename(filepath):
"""
根据文件夹目录,获取文件名称(带后缀)
:param filepath:
return:
"""
return ntpath.basename(filepath)
class FileMoveHandler(FileSystemEventHandler):
def __init__(self) -> None:
super().__init__()
# 文件新建
def on_created(self, event):
# 文件夹操作忽略
if event.is_directory:
pass
# 新建文件
else:
filename = get_filename(event.src_path)
# 文件判定,如果符合条件则开始文件操作
if filename in watch_tags:
self.start(filename)
# 文件修改
def on_modifiled(self, event):
if event.is_directory:
pass
else:
filename = get_filename(event.src_path)
if filename in watch_tags:
self.start(filename)
def start(self, filename):
"""
文件处理逻辑
:param filename:
:return:
"""
try:
# 文件名不带后缀
filename_without_suffix = filename.split(".")[0]
# 源文件路径(压缩包文件)
source_file_path = watch_folder + filename
# 目标文件路径(压缩包文件)
target_file_path = target_folder + filename
# 目标项目文件夹(目标项目)
target_project_path = target_folder + filename_without_suffix
# 删除目标文件夹下的目压缩文件
if os.path.exists(target_file_path):
os.remove(target_file_path)
# 移动文件到目标文件夹中
shutil.move(source_file_path, target_folder)
# 2. 清空目标文件夹中的所有文件夹(如果存在)
if os.path.exists(target_project_path):
shutil.rmtree(target_project_path, ignore_errors=True)
print(f'项目{filename_without_suffix} 移动成功!')
except Exception as e:
print('部署失败,错误原因: ', str(e.args))
if __name__ == "__main__":
watch_folder = "/xxxx/watchdog_src/"
target_folder = "/xxxx/watchdog_dst/"
# 监听文件夹名称,即:项目压缩包名称
watch_tags = ['1.txt', '2.txt']
# 创建一个监听器,用来监听文件夹目录
observer = Observer()
# 创建事件处理对象
move_handler = FileMoveHandler()
# 启动监听
# 参数分别是:观察者,监听目录,是否监听子目录
observer.schedule(move_handler, watch_folder, True)
observer.start()
try:
while True:
time.sleep(2)
except KeyboardInterrupt:
observer.stop()
observer.join()
运行结果查看:
events
通过上述代码,我们可观察到,event变量的重要性,事件的触发和具体操作都是由event来发起。 简单看下event的源码逻辑。
FileSystemEventHandler 类
通过如下源码可发现,类中很多方法并未实现,需要我们按照自己的需求去继承并实现类中的方法。
self.on_any_event(event)
函数实现的比较巧妙,通过接受一个事件,来决定通过何种方式调用哪个方法来去实现。
class FileSystemEventHandler:
"""
Base file system event handler that you can override methods from.
"""
def dispatch(self, event):
"""Dispatches events to the appropriate methods.
:param event:
The event object representing the file system event.
:type event:
:class:`FileSystemEvent`
"""
self.on_any_event(event)
{
EVENT_TYPE_CREATED: self.on_created,
EVENT_TYPE_DELETED: self.on_deleted,
EVENT_TYPE_MODIFIED: self.on_modified,
EVENT_TYPE_MOVED: self.on_moved,
EVENT_TYPE_CLOSED: self.on_closed,
}[event.event_type](event)
def on_any_event(self, event):
"""Catch-all event handler.
:param event:
The event object representing the file system event.
:type event:
:class:`FileSystemEvent`
"""
def on_moved(self, event):
"""Called when a file or a directory is moved or renamed.
:param event:
Event representing file/directory movement.
:type event:
:class:`DirMovedEvent` or :class:`FileMovedEvent`
"""
def on_created(self, event):
"""Called when a file or directory is created.
:param event:
Event representing file/directory creation.
:type event:
:class:`DirCreatedEvent` or :class:`FileCreatedEvent`
"""
def on_deleted(self, event):
"""Called when a file or directory is deleted.
:param event:
Event representing file/directory deletion.
:type event:
:class:`DirDeletedEvent` or :class:`FileDeletedEvent`
"""
def on_modified(self, event):
"""Called when a file or directory is modified.
:param event:
Event representing file/directory modification.
:type event:
:class:`DirModifiedEvent` or :class:`FileModifiedEvent`
"""
def on_closed(self, event):
"""Called when a file opened for writing is closed.
:param event:
Event representing file closing.
:type event:
:class:`FileClosedEvent`
"""
LoggingEventHandler
logger功能,可实现自定义打印log信息。
class LoggingEventHandler(FileSystemEventHandler):
"""Logs all the events captured."""
def __init__(self, logger=None):
super().__init__()
self.logger = logger or logging.root
def on_moved(self, event):
super().on_moved(event)
what = 'directory' if event.is_directory else 'file'
self.logger.info("Moved %s: from %s to %s", what, event.src_path,
event.dest_path)
def on_created(self, event):
super().on_created(event)
what = 'directory' if event.is_directory else 'file'
self.logger.info("Created %s: %s", what, event.src_path)
def on_deleted(self, event):
super().on_deleted(event)
what = 'directory' if event.is_directory else 'file'
self.logger.info("Deleted %s: %s", what, event.src_path)
def on_modified(self, event):
super().on_modified(event)
what = 'directory' if event.is_directory else 'file'
self.logger.info("Modified %s: %s", what, event.src_path)