ファイル操作 ファイルやディレクトリの監視 FileSystemWatcher クラス.NET Framework のクラスライブラリには ファイルやディレクトリの作成 変更 削除を監視する為の FileSystemWatcher クラスが System.IO 名前空間に用意されて居る ( 但し Windows 98/Me では利用出来ない ) 此れを利用すると 特定のディレクトリにファイルが作成された 特定のファイルが修正されたと謂うタイミングで 其等のファイルに対して何等かの処理を行う様なアプリケーションを容易に作成する事が出来る FileSystemWatcher クラスの利用方法は簡単で 先ず其のインスタンスを作成し 下記の表に示す様な各プロパティを設定する プロパティ 説明 デフォルト値 Path 監視するディレクトリのパス なし IncludeSubdirectories サブディレクトリを監視するか何うかを false 示すフラグ Filter 監視するファイル 総てのファイル (*.*) NotifyFilter 監視する変更の種類 (NotifyFilters 列挙体の値 ) NotifyFilters.LastWrite NotifyFilters.DirectoryName NotifyFilters.FileName ( 上記 3 個の値の論理和 ) EnableRaisingEvents 監視を有効にするか何うかを示すフラグ false InternalBufferSize 受け取った通知を格納する内部バッファのサイズ 8192Bytes(8KBytes) Filter プロパティでは 監視対象と成る特定のファイルを指定する事が出来 *.txt や *.jpg と謂う特定の種類のファイルも指定可能で有る EnableRaisingEvents プロパティを True に設定すると 監視処理が開始される 監視が不要な場合には 一時的に此のプロパティを False に設定すれば良い NotifyFilter プロパティでは 監視するディレクトリやファイルの変更の種類を System.IO 名前空間の NotifyFilters 列挙体の値の組み合わせ ( 論理和 :C# では 演算子 Visual Basic では Or 演算子を使用 ) に依り指定する 下記に NotifyFilters 列挙体で定義されて居る値の一覧を示す Attributes CreationTime DirectoryName FileName LastAccess LastWrite Security Size 値 説明ファイル又はフォルダの属性ファイル又はフォルダが作成された時刻ディレクトリの名前ファイルの名前ファイル又はフォルダへの最終アクセス日時ファイル又はフォルダへの最終書き込み日時ファイル又はフォルダのセキュリティ設定ファイル又はフォルダのサイズ -1-
NotifyFilter プロパティで指定した項目が変化する時には 下記のイベントが発生する 此の為 通知を受け度いイベントに関しては インスタンス作成時に 各イベントに対してイベントハンドラを登録して置く必要が有る イベント OnCreated OnDeleted OnRenamed OnChanged OnError イベントの発生するタイミングファイル又はディレクトリが作成された時ファイル又はディレクトリが削除された時ファイル又はディレクトリの名前が変更された時サイズ 属性 最終書き込み日時 最終アクセス日時 セキュリティ設定の孰れかが変更された時内部バッファがオーバーフローした時 下記に FileSystemWatcher クラスを利用した簡単なコード例を示す 下記のコード例では C ドライブ配下の総てのディレクトリに対してテキストファイル ( 拡張子 TXT) の作成と削除を監視し 其等が発生した場合には 其のイベントの種類とファイルのフルパスを表示する Imports System.IO Visual Basic Public Class FileWatcher Private Fw As FileSystemWatcher ' 他スレッド (FileSystemWatcher) よりフォームにアクセスする為のデリゲート Delegate Sub WatcherDelegate(ByVal S As String) ' フォームが読み込まれた時の処理 Private Sub FileWatcher_Load(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Load ' FileSystemWatcher のインスタンス生成 Fw = New System.IO.FileSystemWatcher() ' フォームが閉じられ様と仕た時の処理 Private Sub FileWatcher_FormClosing(ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing ' FileSystemWatcher のインスタンス破棄 If Fw IsNot Nothing Then Fw.Dispose( ) End If ' ボタン ( 監視開始 ) がクリックされた時の処理 Private Sub btnstart_click(byval sender As System.Object, ByVal e As System.EventArgs) _ Handles btnstart.click -2-
' FileSystemWatcher の監視条件設定 Fw.Path = "C: " Fw.Filter = "*.txt" Fw.IncludeSubdirectories = True Fw.NotifyFilter = NotifyFilters.FileName ' FileSystemWatcher のイベントハンドラ追加 AddHandler Fw.Created, AddressOf FileWatching AddHandler Fw.Deleted, AddressOf FileWatching ' FileSystemWatcher の監視開始 Fw.EnableRaisingEvents = True ' ボタン ( 監視停止 ) がクリックされた時の処理 Private Sub btnstop_click(byval sender As System.Object, ByVal e As System.EventArgs) _ Handles btnstop.click Fw.EnableRaisingEvents = False ' FileSystemWatcher のイベント処理を行うジェネラルプロシージャ Private Sub FileWatching(ByVal source As Object, ByVal e As FileSystemEventArgs) ' イベントの種類とフルパスの取得 Dim S As String = e.changetype.tostring() & ":" S &= e.fullpath ' リストボックスへの追加 Invoke(New WatcherDelegate(AddressOf ListboxAddItem), S) ' リストボックスに項目を追加するジェネラルプロシージャ Private Sub ListboxAddItem(ByVal S As String) ' リストボックスへ項目の追加 lstdisp.items.add(s) End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; -3-
namespace FileWatcher public partial class FileWatcher : Form private FileSystemWatcher fw; public FileWatcher( ) InitializeComponent( ); // 他スレッド (FileSystemWatcher) よりフォームにアクセスする為のデリゲート delegate void WatcherDelegate(string s); // フォームが読み込まれた時の処理 private void FileWatcher_Load(object sender, EventArgs e) // FileSystemWatcher のインスタンス生成 fw = new FileSystemWatcher(); // フォームが閉じられ様と仕た時の処理 private void FileWatcher_FormClosing(object sender, FormClosingEventArgs e) if (fw!= null) fw.dispose(); // ボタン ( 監視開始 ) がクリックされた時の処理 private void btnstart_click(object sender, EventArgs e) // FileSystemWatcher の監視条件設定 fw.path = @"C: "; fw.filter = "*.txt"; fw.includesubdirectories = true; fw.notifyfilter = NotifyFilters.FileName; // FileSystemWatcher のイベントハンドラ追加 this.fw.created += new System.IO.FileSystemEventHandler(this.FileWatching); this.fw.deleted += new System.IO.FileSystemEventHandler(this.FileWatching); // FileSystemWatcher の監視開始 fw.enableraisingevents = true; // ボタン ( 監視停止 ) がクリックされた時の処理 private void btnstop_click(object sender, EventArgs e) -4-
fw.enableraisingevents = false; // FileSystemWatcher のイベント処理を行うジェネラルプロシージャ private void FileWatching(Object sender, FileSystemEventArgs e) // イベントの種類とフルパスの取得 string s = e.changetype.tostring() + ":"; s += e.fullpath; // リストボックスへの追加 Invoke(new WatcherDelegate(ListboxAddItem), s); // リストボックスに項目を追加するジェネラルプロシージャ private void ListboxAddItem(string s) // リストボックスへ項目の追加 lstdisp.items.add(s); イベントを処理する FileWatching ジェネラルプロシージャ ( メソッド ) では 第 2 引数の FileSystemEventArgs クラス (System.IO 名前空間 ) のオブジェクトから 下記の情報が取得可能で有る 名前 ChangeType FullPath Name 説明発生したイベントの種類を取得する 影響を受けるファイルやディレクトリの絶対パスを取得する 影響を受けるファイルやディレクトリの名前を取得する 此のプログラムを実行して 例えば エクスプローラ上で C: SRC ディレクトリに有る test.txt と謂うファイルを C: DEST ディレクトリに移動した場合には 下記の様な出力が得られる Deleted:c: SRC test.txt Created:c: DEST test.txt 更に 移動したファイルを削除した場合には 出力は下記の様に成る 此れは ファイルが塵埃箱に移動された事を示して居る Deleted:c: DEST test.txt Created:c: recycler s-1-5-21-911560763-1030517763-475923621-2261 dc4.txt FileSystemWatcher オブジェクトは フォームのスレッドとは別のスレッドで動作する為 当該オブジェクトからフォームのコントロールに対して直接操作する事は出来ない 従って 当該オブジェクトのインスタンスをコードで生成した場合 一般に 其の結果をフォームの何等かのコントロールに表示する Windows アプリケーションでは デリゲートを使用する必要が有る 此の煩雑さを解消する為に 次項で説明する FileSystemWatcher コンポーネントが用意されて居る -5-
FileSystemWatcher クラスの注意点 リファレンスマニュアルにも有る様に ファイル作成等の単純と思える操作に関しても 複数のイベントが発生する場合が有る 此れは 監視対象と成るファイルを作成 変更する外部のアプリケーションの内部動作に依る処も大きい アプリケーションに依っては 例えば ファイルの作成時に何度か其のファイルにアクセスしたり ( 最終アクセス日時が何度も変化する ) 一時ファイルをリネームする事に依り最終的な出力ファイルを作成したりする場合も有る為で有る 実アプリケーションで FileSystemWatcher クラスを利用する場合には 発生するイベントを先ず実際に確認し 必要な NotifyFilter プロパティとイベントの種類を良く吟味する必要が有る FileSystemWatcher コンポーネントの利用 上記では ファイルシステムを監視する為の System.IO 名前空間 FileSystemWatcher クラスの基本的な利用方法に付いて解説した 本項では Visual Studio.NET 以降 ( 以下 VS.NET 以降 ) を使用して Windows アプリケーションを作成する場合に 此のクラスを利用する手順に付いて解説する VS.NET 以降では FileSystemWatcher クラスの機能は ツールボックスのコンポーネントのタブに有る FileSystemWatcher コンポーネントに纏められて居る 此の為 FileSystemWatcher クラスを使うには 此のコンポーネントをフォーム上にドラッグ & ドロップして配置する丈で有る 更に FileSystemWatcher クラスの殆どのプロパティ設定は プロパティウィンドウで行える 亦 配置したコンポーネントをダブルクリックすれば OnChanged イベントに対するイベントハンドラが自動的に作成される OnChanged イベント以外のイベントハンドラに付いては プロパティウィンドウをイベント一覧に切り替えて選択する事が可能で有る 猶 FileSystemWatcher クラスの各プロパティやイベントに付いては 前項を参照され度い FileSystemWatcher クラスの SynchronizingObject プロパティ Windows アプリケーションで FileSystemWatcher クラスを利用する場合には 注意しなければ成らないのは 作成や変更が通知されて呼び出されるイベントハンドラのメソッド ( 以下 FileSystemWatcher イベントハンドラと記述 ) が.NET Framework に依り管理されて居るスレッドプール内のスレッドに依り実行されると謂う事で有る 此のスレッドは Windows フォーム上のボタンのクリック等で呼び出される通常のイベントハンドラが実行されるスレッド ( メインスレッド ) とは別のスレッドで有る 此の為 原則的には FileSystemWatcher イベントハンドラ内でフォーム上のコントロールを操作すると不具合が発生する可能性が有る ( コントロールに対する操作は保証されない ) 但し FileSystemWatcher コンポーネントでは FileSystemWatcher クラスの SynchronizingObject プロパティに依り 其のイベント処理をメインスレッド側で実行する為の仕組みを提供して居る FileSystemWatcher コンポーネントを配置した場合には 此の SynchronizingObject プロパティにフォーム名 ( 既定では Form1) が指定されて居る 此の場合には FileSystemWatcher イベントハンドラはフォームから呼び出される事に成る ( 正確には Form クラスの BeginInvoke メソッドが利用される 此の BeginInvoke メソッドは何のスレッドからも安全に呼出が可能 ) 此の結果 FileSystemWatcher イベントハンドラはメインスレッド上で実行される事に成る 猶 FileSystemWatcher イベントハンドラがメインスレッド上で実行されると謂う事は 其のイベントハンドラで時間の懸かる処理を行うと ユーザーインターフェイスの反応を悪くして了うと謂う事に繋がる Windows アプリケーションで FileSystemWatcher コンポーネントを利用する場合には 此の点にも注意が必要で有る -6-
Public Class FileWatcher Visual Basic ' ボタン ( 監視開始 ) がクリックされた時の処理 Private Sub btnstart_click(byval sender As System.Object, ByVal e As System.EventArgs) _ Handles btnstart.click ' FileSystemWatcher の監視条件設定 fswfilewatcher.path = "C: " fswfilewatcher.filter = "*.txt" fswfilewatcher.includesubdirectories = True fswfilewatcher.notifyfilter = IO.NotifyFilters.FileName ' FileSystemWatcher の監視開始 fswfilewatcher.enableraisingevents = True ' ボタン ( 監視停止 ) がクリックされた時の処理 Private Sub btnstop_click(byval sender As System.Object, ByVal e As System.EventArgs) _ Handles btnstop.click ' FileSystemWatcher の監視停止 fswfilewatcher.enableraisingevents = False Private Sub fswfilewatcher_created(byval sender As System.Object, _ ByVal e As System.IO.FileSystemEventArgs) Handles fswfilewatcher.created ' イベントの種類とフルパスの取得 Dim S As String = e.changetype.tostring() & ":" S &= e.fullpath ' リストボックスへ項目の追加 lstdisp.items.add(s) Private Sub fswfilewatcher_deleted(byval sender As System.Object, _ ByVal e As System.IO.FileSystemEventArgs) Handles fswfilewatcher.deleted ' イベントの種類とフルパスの取得 Dim S As String = e.changetype.tostring() & ":" S &= e.fullpath ' リストボックスへ項目の追加 lstdisp.items.add(s) End Class 省略 C# -7-
監視に依り作成 変更が通知されたファイルを開く 上記で解説した System.IO 名前空間の FileSystemWatcher クラスを利用するとファイルの作成や変更を即座に知る事が出来る 併し 其の通知を受けてファイルの加工や移動を行おうとすると失敗する場合が有る 本項では 此の様な状況を回避する方法に付いて説明する 作成や変更のイベントを受けて 直ちにファイルを操作しようと仕た場合に失敗するのは ファイルの作成や変更が開始されるタイミングでイベントの通知が発生する為で有る 詰まり イベントを受け取った時点では 他のプロセスが未だファイルを作成中や変更中の為 ファイルがロックされた状態で有ると考えられる 此の様な状況は 当然ファイルのサイズが大きい程 起こり易い FileSystemWatcher クラスでは ファイルの作成や変更の完了を知る事は出来ない為 上記の問題を回避するには ファイルがオープン出来る様に成る迄待つと謂うのが 1 つの方法で有る 下記に其の実装例を示す Imports System Imports System.IO Visual Basic Public Class FileWatcher Shared Sub Main( ) Dim Fw As FileSystemWatcher = new FileSystemWatcher( ) Fw.Path = "c: " Fw.Filter = "*.*" Fw.IncludeSubdirectories = true Fw.NotifyFilter = NotifyFilters.FileName AddHandler watcher.created, AddressOf FileWatching Fw.EnableRaisingEvents = true Console.Read( ) ' キー入力が有る迄待機 Shared Sub FileWatching (source As object, e As FileSystemEventArgs) Dim St As Stream = Nothing Dim Cnt As Integer = 10 For I As Integer = 0 To (Cnt - 1) Try St = File.Open(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None) If St IsNot Nothing Then Exit For End If Catch ex As Exception System.Threading.Thread.Sleep(1000) End Try Next If St IsNot Nothing Then -8-
' St.Close( ) ' ファイルの操作が可能 Else ' タイムアウト End If End Class using System; using System.IO; C# public class FileWatchAndOpen static void Main( ) FileSystemWatcher fw = new FileSystemWatcher( ); fw.path = @"c: "; fw.filter = "*.*"; fw.includesubdirectories = true; fw.notifyfilter = NotifyFilters.FileName; fw.created += new FileSystemEventHandler(FileWatching); fw.enableraisingevents = true; Console.Read( ); // キー入力が有る迄待機 static void FileWatching (object source, FileSystemEventArgs e) Stream st = null; int cnt = 10; for (int i = 0; i < cnt; i++) try if ((st = File.Open(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None))!= null) break; catch System.Threading.Thread.Sleep(1000); if (st!= null) // st.close( ); // ファイルの操作が可能 else -9-
// タイムアウト 此処では System.IO 名前空間の File クラスの Open メソッドに依りファイルのオープンを試み 失敗すれば ( 例外が発生 ) 1 秒間スリープすると謂う動作を既定回数丈繰り返す ファイルのオープンが成功すれば 大抵の場合は 他のプロセスに依る其のファイルへの操作は完了して居ると考えて良い 勿論 此の様な方法が旨く行くか何うかは 対象と成るファイルを作成や変更して居るアプリケーションの処理方法に依存する 其れが市販アプリケーション等の場合には 実務で利用する前に充分なテストが必要と成るのは謂う迄も無い 亦 上記のコードでは待ち時間を合計 10 秒に設定して居るが 作成や変更のイベントが頻繁に発生する様な場合には 余り待ち時間を長くすると FileSystemWatcher クラスの内部バッファがオーバーフローする可能性が有る 此の様な時には InternalBufferSize プロパティの値を大きめに設定する等の対処が必要で有る -10-