Windowsでフォルダ内容の監視

タイトルの通り、フォルダ内容の変化を監視するスクリプトです。

検索したら、http://tgolden.sc.sabren.com/python/win32_how_do_i/watch_directory_for_changes.htmlのサイトに全て書かれていました。

以下の内容は、彼のサイトのデッドコピーです。

win32上のPythonでフォルダを監視するには、

  1. os.listdirを使う
  2. FindFirstChangeNotificationを使う
  3. ReadDirectoryChangesを使う

3つの方法があります。

使いやすさと効率は、FindFirstChangeNotificationを使う方法が一番バランスが取れていると思います。

os.listdirを使う

メリット
  1. シンプルで書くのも理解するのも簡単
  2. 標準モジュールしか使わない→クロスプラットフォーム
デメリット
  1. いちいちos.listdirでアクセスするので、ファイル数が増えると遅い。
  2. ファイルの「更新」を検出できない(os.statなどを使う必要がある)。
from __future__ import division, print_function
import os
import time

def watch0(path_to_watch):
    before = set(os.listdir(path_to_watch))
    while 1:
        time.sleep(10)
        after = set(os.listdir(path_to_watch))
        added = after - before
        removed = before - after
        if added:
            print("Added:", ", ".join(added))
        if removed:
            print("Removed:", ", ".join(removed))
        before = after

watch0(".")

FindFirstChangeNotificationを使う

メリット
  1. 実際にフォルダに変化があった時だけアクセスするので、負荷が少ない
  2. 「名前の変更は通知する」「サイズ変更は無視する」など、通知の内容を細かく設定できる
デメリット
  1. 頻度は少ないとはいえ、os.listdirを使う必要がある
from __future__ import division, print_function

import os
import win32file
import win32event
import win32con

def watch1(path_to_watch="."):
    change_handle = win32file.FindFirstChangeNotification(
        path_to_watch,
        0,
        win32con.FILE_NOTIFY_CHANGE_FILE_NAME,
    )
    try:
        old_path_contents = set(os.listdir(path_to_watch))
        while 1:
            result = win32event.WaitForSingleObject(change_handle, 500)
            if result == win32con.WAIT_OBJECT_0:
                new_path_contents = set(os.listdir(path_to_watch))
                added = new_path_contents - old_path_contents
                deleted = old_path_contents - new_path_contents
                if added:
                    print("Added:", ", ".join(added))
                if deleted:
                    print("Deleted:", ", ".join(deleted))
                old_path_contents = new_path_contents
                win32file.FindNextChangeNotification (change_handle)
    finally:
        win32file.FindCloseChangeNotification (change_handle)

watch1()

ReadDirectoryChangesを使う

メリット
  1. 実際にフォルダに変化があった時だけアクセスするので、負荷が少ない
  2. 通知の内容を細かく設定できる
  3. どのファイルがどう変化したかを検出できる
  4. os.listdirを使う必要が無い
デメリット
  1. フォルダに変化が無い時にループを抜けるのは簡単ではありません。
from __future__ import division, print_function

def watch2(path_to_watch):
    FILE_LIST_DIRECTORY = 0x0001
    
    hDir = win32file.CreateFile(
        path_to_watch,
        FILE_LIST_DIRECTORY,
        win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
        None,
        win32con.OPEN_EXISTING,
        win32con.FILE_FLAG_BACKUP_SEMANTICS,
        None,
    )
    
    while 1:
        filt = (win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
                win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
                win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
                win32con.FILE_NOTIFY_CHANGE_SIZE |
                win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
                win32con.FILE_NOTIFY_CHANGE_SECURITY)
        results = win32file.ReadDirectoryChangesW(
            hDir,
            1024,
            True,
            filt,
            None,
            None
        )
        ACTIONS = {
            1: "Created",
            2: "Deleted",
            3: "Updated",
            4: "Renamed from something",
            5: "Renamed to something"
        }
        for action, file in results:
            full_filename = os.path.join(path_to_watch, file)
            print(full_filename, ACTIONS.get(action, "Unknown"))

watch2(".")