Created
May 6, 2012 16:04
-
-
Save seraphy/2623100 to your computer and use it in GitHub Desktop.
ファイル監視のための、いくつかの方法
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// fwatch1v.cpp : コンソール アプリケーションのエントリ ポイントを定義します。 | |
// | |
#include "stdafx.h" | |
// stdafx.hプリコンパイルヘッダでの定義は以下のとおり | |
// (XP SP2以降) | |
//#define _WIN32_WINNT 0x0502 | |
//#include <stdio.h> | |
//#include <locale.h> | |
//#include <tchar.h> | |
//#include <vector> | |
//#include <conio.h> | |
//#include <Windows.h> | |
// エラーの表示 | |
static void ShowError(LPCTSTR msg) | |
{ | |
DWORD errcode = GetLastError(); | |
_tprintf(_T("%s errorcode: %lx\r\n"), msg, errcode); | |
} | |
// メインエントリ | |
int _tmain(int argc, _TCHAR* argv[]) | |
{ | |
// コンソール出力を日本語可能に | |
setlocale(LC_ALL, ""); | |
// オプション引数の値を保持する | |
LPCTSTR pDir = _T("C:\\temp\\fwatch2v"); | |
int waittime = 0; | |
bool hasError = false; | |
// 引数の解析 | |
_TCHAR **pArg = &argv[1]; | |
while (*pArg) { | |
if (_tcsicmp(_T("/w"), *pArg) == 0) { | |
// ウェイト時間 | |
pArg++; | |
if (*pArg) { | |
waittime = _ttoi(*pArg); | |
} | |
} else if (**pArg != '/') { | |
// 監視先ディレクトリ | |
pDir = *pArg; | |
break; | |
} else { | |
_ftprintf(stderr, _T("不明な引数: %s\r\n"), *pArg); | |
hasError = true; | |
} | |
pArg++; | |
} | |
if (hasError) { | |
return 2; | |
} | |
if (waittime <= 0) { | |
waittime = 0; | |
} | |
_tprintf(_T("監視先: %s\r\nループ毎の待ち時間: %ld\r\n"), pDir, waittime); | |
// 監視条件 | |
DWORD filter = | |
FILE_NOTIFY_CHANGE_FILE_NAME | // ファイル名の変更 | |
FILE_NOTIFY_CHANGE_DIR_NAME | // ディレクトリ名の変更 | |
FILE_NOTIFY_CHANGE_ATTRIBUTES | // 属性の変更 | |
FILE_NOTIFY_CHANGE_SIZE | // サイズの変更 | |
FILE_NOTIFY_CHANGE_LAST_WRITE; // 最終書き込み日時の変更 | |
// 対象のディレクトリを監視用にオープンする. | |
HANDLE hWatch = FindFirstChangeNotification( | |
pDir, // 監視先ディレクトリ | |
TRUE, // サブディレクトリも監視 | |
filter // 監視条件 | |
); | |
if (hWatch == INVALID_HANDLE_VALUE) { | |
ShowError(_T("FindFirstChangeNotificationでの失敗")); | |
return 1; | |
} | |
// 変更通知を受け取りつづけるループ | |
for (;;) { | |
// qで終了. | |
if (_kbhit()) { | |
if (_getch() == 'q') { | |
break; | |
} | |
} | |
// 変更通知まち | |
DWORD waitResult = WaitForSingleObject(hWatch, 500); // 0.5秒待ち | |
if (waitResult == WAIT_TIMEOUT) { | |
// 待ち受け表示 | |
_tprintf(_T(".")); | |
continue; | |
} | |
// 変更通知があった場合 (イベントがシグナル状態になった場合) | |
_tprintf(_T("\r\n★ファイルが変更されました!★\r\n")); | |
// Sleepを使い、イベントハンドリングが遅いケースをエミュレートする. | |
// Sleep表示中にファイルを変更した場合に検知漏れが発生しないか確認できる. | |
const int mx = waittime * 10; | |
for (int idx = 0; idx < mx; idx++) { | |
_tprintf(_T("sleep... %d/%d \r"), idx + 1, mx); | |
Sleep(100); | |
} | |
// 監視の継続 | |
if (!FindNextChangeNotification(hWatch)) | |
{ | |
ShowError(_T("FindNextChangeNotificationでの失敗")); | |
break; | |
} | |
} | |
// 監視の終了 | |
FindCloseChangeNotification(hWatch); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "stdafx.h" | |
// stdafx.hプリコンパイルヘッダでの定義は以下のとおり | |
// (XP SP2以降) | |
//#define _WIN32_WINNT 0x0502 | |
//#include <stdio.h> | |
//#include <locale.h> | |
//#include <tchar.h> | |
//#include <vector> | |
//#include <conio.h> | |
//#include <Windows.h> | |
// エラーの表示 | |
static void ShowError(LPCTSTR msg) | |
{ | |
DWORD errcode = GetLastError(); | |
_tprintf(_T("%s errorcode: %lx\r\n"), msg, errcode); | |
} | |
// キー入力のチェック | |
static inline bool CheckQuitKey() | |
{ | |
return _kbhit() && (_getch() == 'q'); | |
} | |
// メインエントリ | |
int _tmain(int argc, _TCHAR* argv[]) | |
{ | |
// コンソール出力を日本語可能に | |
setlocale(LC_ALL, ""); | |
// オプション引数の値を保持する | |
LPCTSTR pDir = _T("C:\\temp\\fwatch2v"); | |
size_t bufsiz = 0; | |
int waittime = 0; | |
bool hasError = false; | |
// 引数の解析 | |
_TCHAR **pArg = &argv[1]; | |
while (*pArg) { | |
if (_tcsicmp(_T("/b"), *pArg) == 0) { | |
// バッファサイズ | |
pArg++; | |
if (*pArg) { | |
bufsiz = _ttol(*pArg); | |
} | |
} else if (_tcsicmp(_T("/w"), *pArg) == 0) { | |
// ウェイト時間 | |
pArg++; | |
if (*pArg) { | |
waittime = _ttoi(*pArg); | |
} | |
} else if (**pArg != '/') { | |
// 監視先ディレクトリ | |
pDir = *pArg; | |
break; | |
} else { | |
_ftprintf(stderr, _T("不明な引数: %s\r\n"), *pArg); | |
hasError = true; | |
} | |
pArg++; | |
} | |
if (hasError) { | |
return 2; | |
} | |
if (bufsiz <= 0) { | |
bufsiz = 1024 * 8; | |
} | |
if (waittime <= 0) { | |
waittime = 0; | |
} | |
_tprintf(_T("監視先: %s\r\nバッファサイズ: %ld\r\nループ毎の待ち時間: %ld\r\n"), | |
pDir, bufsiz, waittime); | |
// 対象のディレクトリを監視用にオープンする. | |
// 共有ディレクトリ使用可、対象フォルダを削除可 | |
// 非同期I/O使用 | |
HANDLE hDir = CreateFile( | |
pDir, // 監視先 | |
FILE_LIST_DIRECTORY, | |
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
NULL, | |
OPEN_EXISTING, | |
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // ReadDirectoryChangesW用 | |
NULL | |
); | |
if (hDir == INVALID_HANDLE_VALUE) { | |
ShowError(_T("CreateFileでの失敗")); | |
return 1; | |
} | |
// 監視条件 (FindFirstChangeNotificationと同じ) | |
DWORD filter = | |
FILE_NOTIFY_CHANGE_FILE_NAME | // ファイル名の変更 | |
FILE_NOTIFY_CHANGE_DIR_NAME | // ディレクトリ名の変更 | |
FILE_NOTIFY_CHANGE_ATTRIBUTES | // 属性の変更 | |
FILE_NOTIFY_CHANGE_SIZE | // サイズの変更 | |
FILE_NOTIFY_CHANGE_LAST_WRITE; // 最終書き込み日時の変更 | |
// 変更されたファイルのリストを記録するためのバッファ. | |
// 最初のReadDirectoryChangesWの通知から次のReadDirectoryChangesWまでの | |
// 間に変更されたファイルの情報を格納できるだけのサイズが必要. | |
// バッファオーバーとしてもファイルに変更が発生したことは感知できるが、 | |
// なにが変更されたかは通知できない。 | |
std::vector<unsigned char> buf(bufsiz); | |
void *pBuf = &buf[0]; | |
// 非同期I/Oの完了待機用, 手動リセットモード。 | |
// 変更通知のイベント発報とキャンセル完了のイベント発報の | |
// 2つのイベントソースがあるためイベントの流れが予想できず | |
// 自動リセットイベントにするのは危険。 | |
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | |
// 変更通知を受け取りつづけるループ | |
for (;;) { | |
// Sleepを使い、イベントハンドリングが遅いケースをエミュレートする. | |
// ループ2回目以降ならばSleep表示中にファイルを変更しても、 | |
// 変更が追跡されていることを確認できる. | |
// またバッファサイズを超えるほどにたくさんのファイルを変更すると | |
// バッファオーバーを確認できる。 | |
const int mx = waittime * 10; | |
for (int idx = 0; idx < mx; idx++) { | |
_tprintf(_T("sleep... %d/%d \r"), idx + 1, mx); | |
Sleep(100); | |
} | |
_tprintf(_T("\r\nstart.\r\n")); | |
// イベントの手動リセット | |
ResetEvent(hEvent); | |
// 非同期I/O | |
// ループ中でのみ使用・待機するので、ここ(スタック)に置いても安全. | |
OVERLAPPED olp = {0}; | |
olp.hEvent = hEvent; | |
// 変更を監視する. | |
// 初回呼び出し時にシステムが指定サイズでバッファを確保し、そこに変更を記録する. | |
// 完了通知後もシステムは変更を追跡しており、後続のReadDirectoryChangeWの | |
// 呼び出しで、前回通知後からの変更をまとめて受け取ることができる. | |
// バッファがあふれた場合はサイズ0で応答が返される. | |
if (!ReadDirectoryChangesW( | |
hDir, // 対象ディレクトリ | |
pBuf, // 通知を格納するバッファ | |
bufsiz, // バッファサイズ | |
TRUE, // サブディレクトリを対象にするか? | |
filter, // 変更通知を受け取るフィルタ | |
NULL, // (結果サイズ, 非同期なので未使用) | |
&olp, // 非同期I/Oバッファ | |
NULL // (完了ルーチン, 未使用) | |
)) { | |
// 開始できなかった場合のエラー | |
ShowError(_T("ReadDirectoryChangesWでの失敗")); | |
break; | |
} | |
// 完了待機ループ (qキーで途中終了) | |
bool quit; | |
while (!(quit = CheckQuitKey())) { | |
// 変更通知まち | |
DWORD waitResult = WaitForSingleObject(hEvent, 500); // 0.5秒待ち | |
if (waitResult != WAIT_TIMEOUT) { | |
// 変更通知があった場合 (イベントがシグナル状態になった場合) | |
break; | |
} | |
// 待ち受け表示 | |
_tprintf(_T(".")); | |
} | |
_tprintf(_T("\r\n")); | |
if (quit) { | |
// 途中終了するなら非同期I/Oも中止し、 | |
// Overlapped構造体をシステムが使わなくなるまで待機する必要がある. | |
CancelIo(hDir); | |
WaitForSingleObject(hEvent, INFINITE); | |
break; | |
} | |
// 非同期I/Oの結果を取得する. | |
DWORD retsize = 0; | |
if (!GetOverlappedResult(hDir, &olp, &retsize, FALSE)) { | |
// 結果取得に失敗した場合 | |
ShowError(_T("GetOverlappedResultでの失敗")); | |
break; | |
} | |
// 変更通知をコンソールにダンプする. | |
_tprintf(_T("returned size=%ld\r\n"), retsize); | |
if (retsize == 0) { | |
// 返却サイズ、0ならばバッファオーバーを示す | |
_tprintf(_T("buffer overflow!!\r\n")); | |
} else { | |
// 最初のエントリに位置付ける | |
FILE_NOTIFY_INFORMATION *pData = | |
reinterpret_cast<FILE_NOTIFY_INFORMATION*>(pBuf); | |
size_t offset = 0; | |
// エントリの末尾まで繰り返す | |
for (;;) { | |
// アクションタイプを可読文字に変換 | |
TCHAR *pActionMsg = _T("UNKNOWN"); | |
switch (pData->Action) { | |
case FILE_ACTION_ADDED: | |
pActionMsg = _T("Added"); | |
break; | |
case FILE_ACTION_REMOVED: | |
pActionMsg = _T("Removed"); | |
break; | |
case FILE_ACTION_MODIFIED: | |
pActionMsg = _T("Modified"); | |
break; | |
case FILE_ACTION_RENAMED_OLD_NAME: | |
pActionMsg = _T("Rename Old"); | |
break; | |
case FILE_ACTION_RENAMED_NEW_NAME: | |
pActionMsg = _T("Rename New"); | |
break; | |
} | |
// ファイル名はヌル終端されていないので | |
// 長さから終端をつけておく. | |
DWORD lenBytes = pData->FileNameLength; // 文字数ではなく, バイト数 | |
std::vector<WCHAR> fileName(lenBytes / sizeof(WCHAR) + 1); // ヌル終端用に+1 | |
memcpy(&fileName[0], pData->FileName, lenBytes); | |
// アクションと対象ファイルを表示. | |
// (ファイル名は指定ディレクトリからの相対パスで通知される.) | |
_tprintf(_T("[%s]<%s>\r\n"), pActionMsg, &fileName[0]); | |
if (pData->NextEntryOffset == 0) { | |
// 次のエントリは無し | |
break; | |
} | |
// 次のエントリの位置まで移動する. (現在アドレスからの相対バイト数) | |
pData = reinterpret_cast<FILE_NOTIFY_INFORMATION*>( | |
reinterpret_cast<unsigned char*>(pData) + pData->NextEntryOffset); | |
} | |
} | |
} | |
// ハンドルの解放 | |
CloseHandle(hEvent); | |
CloseHandle(hDir); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fwatch_java7; | |
import java.io.Console; | |
import java.io.File; | |
import java.io.IOException; | |
import java.nio.file.WatchEvent.Kind; | |
import java.nio.file.*; | |
import java.util.concurrent.TimeUnit; | |
/** | |
* JavaSE7のファイル監視の実験 | |
* @author seraphy | |
*/ | |
public class FWatch_java7 implements Runnable { | |
/** | |
* 監視先ディレクトリ | |
*/ | |
private String dirName; | |
/** | |
* イベントごとのウェイト時間(秒) | |
*/ | |
private int waitCnt; | |
/** | |
* オプションの修飾子 | |
*/ | |
private WatchEvent.Modifier[] extModifiers; | |
/** | |
* 実行部 | |
*/ | |
@Override | |
@SuppressWarnings({"SleepWhileInLoop", "CallToThreadDumpStack"}) | |
public void run() { | |
try { | |
// ファイル監視などの機能は新しいNIO2クラスで拡張されたので | |
// 旧File型から、新しいPath型に変換する. | |
Path dirPath = new File(dirName).toPath(); | |
System.out.println(String.format("監視先: %s\n待機時間: %d\n", dirName, waitCnt)); | |
// ディレクトリが属するファイルシステムを得る | |
FileSystem fs = dirPath.getFileSystem(); | |
// ファイルシステムに対応する監視サービスを構築する. | |
// (一つのサービスで複数の監視が可能) | |
try (WatchService watcher = fs.newWatchService()) | |
{ | |
// ディレクトリに対して監視サービスを登録する. | |
WatchKey watchKey = dirPath.register(watcher, new Kind[]{ | |
StandardWatchEventKinds.ENTRY_CREATE, // 作成 | |
StandardWatchEventKinds.ENTRY_MODIFY, // 変更 | |
StandardWatchEventKinds.ENTRY_DELETE, // 削除 | |
StandardWatchEventKinds.OVERFLOW}, // 特定不能時 | |
extModifiers); // オプションの修飾子、不要ならば空配列 | |
// 監視が有効であるかぎり、ループする. | |
// (監視がcancelされるか、監視サービスが停止した場合はfalseとなる) | |
while (watchKey.isValid()) { | |
try{ | |
// スレッドの割り込み = 終了要求を判定する. | |
if (Thread.currentThread().isInterrupted()) { | |
throw new InterruptedException(); | |
} | |
// ファイル変更イベントが発生するまで待機する. | |
WatchKey detecedtWatchKey = watcher.poll(500, TimeUnit.MILLISECONDS); | |
if (detecedtWatchKey == null) { | |
// タイムアウト | |
System.out.print("."); | |
continue; | |
} | |
System.out.println(); | |
// イベント発生元を判定する | |
if (detecedtWatchKey.equals(watchKey)) { | |
// 発生したイベント内容をプリントする. | |
for (WatchEvent event : detecedtWatchKey.pollEvents()) { | |
// 追加・変更・削除対象のファイルを取得する. | |
// (ただし、overflow時などはnullとなることに注意) | |
Path file = (Path) event.context(); | |
System.out.println(event.kind() + | |
": count=" + event.count() + | |
": path=" + file); | |
} | |
} | |
// イベントのハンドリングに時間がかかるケースを | |
// Sleepでエミュレートする. | |
// (この間のファイル変更イベントを取りこぼすか否かを確かめられる) | |
for (int cnt = 0; cnt < waitCnt; cnt++) { | |
System.out.print(String.format("%d/%d...\r", cnt + 1, waitCnt)); | |
Thread.sleep(1000); | |
} | |
// イベントの受付を再開する. | |
detecedtWatchKey.reset(); | |
} catch (InterruptedException ex) { | |
// スレッドの割り込み = 終了要求なので監視をキャンセルしループを終了する. | |
System.out.println("監視のキャンセル"); | |
watchKey.cancel(); | |
} | |
} | |
} | |
} catch (RuntimeException | IOException ex) { | |
ex.printStackTrace(); | |
} | |
System.out.println("スレッドの終了"); | |
} | |
/** | |
* 第一引数に監視対象のディレクトリを指定する. | |
* @param args the command line arguments | |
*/ | |
public static void main(String[] args) throws Exception { | |
// 監視先 | |
String dirName = "C:\\temp"; | |
// イベントごとのウェイト | |
int waitCnt = 0; | |
WatchEvent.Modifier[] extModifiers = new WatchEvent.Modifier[0]; | |
// オプションを解析する. | |
int mx = args.length; | |
for (int idx = 0; idx < mx; idx++) { | |
String arg = args[idx]; | |
if (arg.startsWith("-w:")) { | |
waitCnt = Integer.parseInt(arg.substring(3)); | |
} else if (arg.equals("-r")) { | |
extModifiers = new WatchEvent.Modifier[] { | |
// ファイルツリーを監視する非標準の修飾子 | |
// ※ Windows以外では正しく機能しない. | |
com.sun.nio.file.ExtendedWatchEventModifier.FILE_TREE | |
}; | |
} else { | |
dirName = arg; | |
break; | |
} | |
} | |
// スレッドの開始 | |
FWatch_java7 inst = new FWatch_java7(); | |
inst.dirName = dirName; | |
inst.waitCnt = waitCnt; | |
inst.extModifiers = extModifiers; | |
Thread thread = new Thread(inst); | |
thread.start(); | |
// エンターキーが押されるまで実行(コンソールがある場合) | |
Console cons = System.console(); | |
if (cons != null) { | |
cons.printf("エンターキーで終了.\n"); | |
cons.readLine(); | |
// スレッドへの終了要求と終了待機 | |
thread.interrupt(); | |
} | |
// スレッド終了まで待機 | |
thread.join(); | |
System.out.println("done."); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment