監視ディレクトリ内でのファイル移動を検出するには?

解決


柚子  2009-06-17 20:17:11  No: 70351

質問させて頂きます。

ReadDirectoryChangesWを用いてディレクトリの変更を監視しておりますが
監視ディレクトリ内で、ファイルが同じ監視ディレクトリ内に移動させた場合
削除と追加両方の結果が送られてくると思っておりましたが
実際にはファイルを削除した結果しか送ってきません。

その為、移動後のファイルの場所は監視できないままです。

ReadDirectoryChangesWで指定出来る定数を調べても移動という物は無い様ですが
これは、複数のReadDirectoryChangesWで監視するしかないでしょうか?

それですとスレッドを増やさなければならないので、極力避けたいとは思っておりますが…
ご教授の程よろしく御願いします。


aetos  2009-06-17 23:22:55  No: 70352

> ファイルが同じ監視ディレクトリ内に移動させた場合

とは?
同じディレクトリ内で切り取り&貼り付けした場合ですか?
エクスプローラではエラーになるし、コマンド プロンプトから move しても何も起きません。
単に切り取りをキャンセルすればよいだけですからね。

何が問題なのか把握できません。


subaru  2009-06-17 23:30:05  No: 70353

ReadDirectoryChangesWのdwNotifyFilterには何を指定していますか?
FILE_NOTIFY_CHANGE_FILE_NAMEでリネームした場合は
FILE_ACTION_RENAMED_OLD_NAMEとFILE_ACTION_RENAMED_NEW_NAMEの
2つの結果が得られます。

監視ディレクトリ内で別ディレクトリへのファイルの移動ということなら
1回目のReadDirectoryChangesWの呼び出し後に
もう一度ReadDirectoryChangesWを呼び出せば
次の操作結果として得られるかと。


柚子  2009-06-18 01:43:22  No: 70354

レス有難うございます

>aetos様
例えばc:\tempを監視ディレクトリ(サブディレクトリも対象)とします
c:\temp\hoge.txtを
c:\temp\target\ディレクトリに移動した時、移動した情報を取得したいのですが
ReadDirectoryChangesWでは、c:\temp\hoge.txtは削除された情報しか通知しません。
思惑では、c:\temp\hoge.txtが削除されて、c:\temp\target\hoge.txtが作成された通知が来ると思っていました。

>subaru様
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME を指定しています

>監視ディレクトリ内で別ディレクトリへのファイルの移動ということなら
>1回目のReadDirectoryChangesWの呼び出し後に
>もう一度ReadDirectoryChangesWを呼び出せば
>次の操作結果として得られるかと。
いえ、得られませんでした。削除通知だけ送られて
もう一度ReadDirectoryChangesWを実行しても待機状態に入ります。

サブディレクトリを監視せず、それぞれを(c:\temp,c:\temp\target)
ReadDirectoryChangesWで監視すれば、思惑通りの結果が得られるのですが
それですと、二つのスレッドを用意しなければなりません
上記方法で実装すると、サブディレクトリを監視する場合、その数だけスレッドを作成しなければならないため
抵抗があります。


aetos  2009-06-18 02:02:32  No: 70355

うん?
サブフォルダへの移動であれば、ちゃんと通知されましたよ。

バッファはどのくらい確保していますか?
また、バッファから結果を読み取るコードはどうなっていますか?

ReadDirectoryChangesW は、1回の呼び出しで複数の結果を返します。
OVERLAPPED を使わない同期呼び出しであれば、lpBytesReturned の値が、結果のバイト数になります。
このバイト数に達するまで、バッファから繰り返し結果を読み取る必要がありますが、そうやってますか?


subaru  2009-06-18 02:18:13  No: 70356

>いえ、得られませんでした。削除通知だけ送られて
>もう一度ReadDirectoryChangesWを実行しても待機状態に入ります。うーん、なぜでしょう???
こちらで試した限りでは1回目の呼び出しでFILE_ACTION_REMOVED
2回目の呼び出しでFILE_ACTION_ADDEDが取得できるのですが・・・

2回目の呼び出しの前にウエイトを置いたら取りこぼしが発生するかと
思ったけど、そうでもないみたい。
となると、2回目の呼び出しの前にディレクトリハンドルを一旦閉じて
開き直してる可能性はありそうですね。

ちなみにaetosさんの方でも成功しておられるようですが
私の方は完了ルーチンを使う方法で試したので
aetosさんの方法とは結果が違うかも?


aetos  2009-06-18 02:27:58  No: 70357

どういうコードを書いているのか載せてもらうのが一番確実ですね。
ちなみに、俺は同期と OVERLAPPED を使って試しました。

# Windows API の非同期 I/O って、方法がいろいろあって面白いなぁ


柚子  2009-06-18 06:18:03  No: 70358

>aetos様
>subaru様
レス有難うございます。

>2回目の呼び出しの前にディレクトリハンドルを一旦閉じて
仰るとおりです。非同期でReadDirectoryChangesWを呼んでいますが
結果を取得する辺りは関数化しています。
結果を取得(REMOVE)→ディレクトリハンドルを閉じる→結果を取得(待機)
となっていました。

デバッガで、REMOVE時にステートメントをReadDirectoryChangesWに変えた所再びADDが来た為、ハンドルを閉じた事が原因と確定できました。

ですが、まだ問題があります。
RENAMEのように、てっきりNextEntryOffsetで纏めて操作出来るかと思っていましたが
今回のケースでは、NextEntryOffsetは0が為
もう一度ReadDirectoryChangesWを呼ばなければなりません。
しかし、只のREMOVEの場合、その時点で待機をしてしまいます。

aetos様が仰った
>このバイト数に達するまで、バッファから繰り返し結果を読み取る必要がありますが、そうやってますか?
どは、具体的にはどのような手法でしょうか?

下記が現在私が行っている処理の一部を抜粋したコードです
bBufferは1024Byteです

ReadDirectoryChangesW( 
    hDir,  bBuffer,  sizeof(bBuffer), TRUE,
    FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_DIR_NAME,
    &dwResultBytes, &overLapped, NULL
 );

if( !((WaitForMultipleObjects( 2, hEvents, FALSE, INFINITE )) - WAIT_OBJECT_0) ) {
    bResult = GetOverlappedResult( hDir, &overLapped, &dwResultBytes, TRUE );
    i = 0;
    while(bResult) {
        pNotifyInfo = (FILE_NOTIFY_INFORMATION *)&bBuffer[i];
        if( pNotifyInfo->NextEntryOffset == 0 ) {
            break;
        }
        i += pNotifyInfo->NextEntryOffset;
    }
}
CloseHandle( hDir );


aetos  2009-06-18 11:03:15  No: 70359

> aetos様が仰った
>> このバイト数に達するまで、バッファから繰り返し結果を読み取る必要がありますが、そうやってますか?
> どは、具体的にはどのような手法でしょうか?

今やっている手法で OK です。
一度の呼び出しにつき1回しか結果を見ていないのではないかと思って。

ひょっとして、目的はディレクトリに起こるいろいろなイベントの監視ではなくて、ファイルの移動(削除を除く)だけを監視したいわけですか?
で、移動の一部である削除と、ただの削除を区別したい?

うーん…きれいな方法は思いつきませんね。
Remove 後、一定時間内に Add があり、かつファイル名が同じであれば移動と判断する…というような恰好悪い方法しか考えつきません。
一定時間のタイムアウトを設けるには、ReadDirectoryChangesW を非同期で使い、実際に待機する WaitForSingleObject にタイムアウトを設定することで実現できると思います。


aetos  2009-06-18 11:06:05  No: 70360

エクスプローラでの操作のみ対象としてしまってよいのであれば、ICopyHook(Windows Vista 以降であれば IFileOperationProgressSink)で、移動と削除を分けて監視できます。
が、エクスプローラ以外での操作は監視対象になりません。


subaru  2009-06-18 19:09:09  No: 70361

移動のみをピンポイントで監視するってことですか?
リネーム後に移動とか、想定外の操作をされそうですが・・・

普通に考えたら、移動(と思われる操作?)が発生するまでずっと監視を続け、
不要になった時点で監視をやめればいいような気もしますが。


柚子  2009-06-18 19:25:56  No: 70362

>aetos様
>subaru様
レス有難うございます。

今回の監視は、移動のみではなく削除といった監視も行っております。
移動はファイル名変更の様に、オフセットが設定されない為
同期周りを関数化してしまった事により、これの検出が難しくなっておりました。(関数からの結果は監視の結果の為)
“非同期”処理で実装という事を失念しておりました。

REMOVE時にもう一度ReadDirectoryChangesWを呼び
すぐに結果を取得→取得成功ならオフセットを操作して
新しくActionにMOVEを定義する事で解決しました。

この度は長々と質問してしまい申し訳ありません。
お二人方のお掛けで無事解決出来ました。
本当に有難うございます。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加