深い階層のファイルの変更を検知するには?

解決


RYO  2009-05-15 08:34:20  No: 34412

ファイルをコピーする(バックアップする)ソフトを作ろうとしています。バックアップ対象のフォルダに深い階層を持ったディレクトリを指定した場合に、深い階層のファイルの更新を検知するのに何か良い方法はないでしょうか?


深い?  2009-05-15 20:22:02  No: 34413

フォルダの階層の深さは関係ないと思うけど。
フォルダの変更を監視するAPIを使えばいいんじゃない?
ところで、前の質問は解決したの?


RYO  2009-05-16 00:06:32  No: 34414

前の質問は解決にしたつもりでした。
そのAPIが知りたいのですが。


Mr.XRAY  2009-05-16 02:34:19  No: 34415

Mr.XRAYです.

>深い階層のファイルの変更を検知するには

というこですが,浅い階層で検知するのができていれば,同じ方法が使えませんか?

残念ですが,私はファイルの変更(何の変更か)を検出する方法を知りません.
あるフォルダ内の変更を監視するAPIというのはありますが,
これは,フォルダ内のどのファイルに変更があったかはわかりません.
(この場合の変更とは,ファイルの削除,追加,名前の変更のことです)

「変更」の内容というか意味によっても何か方法があるかも知れません.
私だったら,更新日時ぐらいしか思いつきません.
更新日時であれば,階層には関係ないですよね.


KHE00221  2009-05-16 04:23:28  No: 34416

これでいいだろ?

if SearchRec.Attr and faArchive = faArchive then
begin
end;

コピーしたら A 属性解除忘れずに


RYO  2009-05-16 05:36:41  No: 34417

ご回答ありがとうございます。変更というのはおっしゃる通り、ファイル・ディレクトリの削除・追加・名前の変更のことです。また、更新されたかどうかは、やはり更新日時で判断しようとしています。
言葉が足りなかったようで補足しておきます。
  「深い階層」と言っているのは、例えば、あるディレクトリをバックアップ対象として設定した場合、その直下のサブディレクトリについては、その中にファイルが作成されたり、さらにサブディレクトリが作られるとタイムスタンプが更新されます。この場合はFindFirst〜FindNextで知ることができます。さらに、作成したサブディレクトリ内にフォルダやファイルが作成された場合を考えると、そのサブディレクトリもタイムスタンプが更新されます。しかし、そのサブディレクトリ内にさらにディレクトリを作った場合には、当初対象にした2階層上のディレクトリのタイムスタンプは更新されません。(解りにくい表現で済みません。)
  つまりは、あるディレクトリの中のサブディレクトリの中のディレクトリ(のさらにその下のディレクトリも含めて)深いところで変更があった場合、おおもとのディレクトリのタイムスタンプを見ただけでは解らないということになって、これを検知する方法がないか、と思ったわけです。
  やはりサブディレクトリがあった場合は、その中を調べて、またサブディレクトリがあったらまたその中を調べる、という以外に方法はない、ということになりますでしょうか。


やっぱり冗談?  2009-05-17 19:47:49  No: 34418

真琴:「ハルコさん、階層が深いSubフォルダのファイル変更もこれで監視できるの?」
春子:「そぅよ、EditにフォルダPATHを入れて、Startボタン押して確かめてみたら?」
真琴:「あっ、そのSubフォルダのファイル書き換えや新規作成、コピーなんかでも、そのファイル名が表示されるね」
春子:「でしょ? もちろん深いSubフォルダ名の変更、作成の検知もOK」
真琴:「なるほど、スレッドを使って監視してるのね? でも、ハルコさん…」
春子:「ん? なに?」
真琴:「けっこう面倒よね?この方法も^^;、API一発で分かるって方法とかない?^^;;」
春子:「世の中そんな甘くないって」

type
 PFileNotifyInformation = ^TFileNotifyInformation;
 TFileNotifyInformation = record
   NextEntryOffset: DWORD;
   Action: DWORD;
   FileNameLength: DWORD;
   FileName: array[0..0] of WideChar;
 end;

const
 FILE_LIST_DIRECTORY = $0001;

 ActionDsp: array[FILE_ACTION_ADDED..FILE_ACTION_RENAMED_NEW_NAME] of string =
  ('ADDED      %s','DELETED    %s','MODIFIED   %s','RENAME_Old %s','RENAME_New %s');
var
 NotifyBuf: array[0..$1000] of Byte;
 NotifyFilter: DWORD;
 hDirectory: THandle;
 hCompletionPort: THandle;
 Overlapped: TOverlapped;
 rwBytes: DWORD;

type
 TWatchThread = class(TThread)
 private
  procedure ShowInformation;
 public
  procedure Execute; override;
 end;

var
 WatchThread := TWatchThread;

procedure TWatchThread.ShowInformation;
var
 pFNI: PFileNotifyInformation;
 Offset: Integer;
begin
 pFNI := @NotifyBuf[0];
 repeat
  Offset := pFNI^.NextEntryOffset;
  Form1.ListBox1.Items.Add(Format(ActionDsp[pFNI^.Action], [WideCharLenToString(@(pFNI^.FileName), pFNI^.FileNameLength)]));
  inc(PChar(pFNI), Offset);
 until Offset = 0;
end;

procedure TWatchThread.Execute;
var
 CompletionKey: DWORD;
 lpOverlapped: POverlapped;
begin
 while (not Terminated)and(not Application.Terminated) do begin
  lpOverlapped := @Overlapped;
  GetQueuedCompletionStatus(hCompletionPort, rwBytes, CompletionKey, lpOverlapped, INFINITE);
  if CompletionKey <> 0 then begin
   Synchronize(ShowInformation);
   ZeroMemory(@NotifyBuf, SizeOf(NotifyBuf));
   ReadDirectoryChanges(hDirectory, @NotifyBuf, SizeOf(NotifyBuf), True, NotifyFilter, @rwBytes, @Overlapped, nil);
  end
 end;
end;

procedure TForm1.StartButtonClick(Sender: TObject);
begin
 if Assigned(WatchThread)and(not WatchThread.Suspended) then exit;
 ListBox1.Clear;
 hDirectory := CreateFile(PChar(EditPath.Text),
   FILE_LIST_DIRECTORY,
   FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
   nil, OPEN_EXISTING,
   FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0);
 if hDirectory = INVALID_HANDLE_VALUE then begin
  hDirectory := 0;
  ShowMessage(SysErrorMessage(GetLastError));
  exit;
 end;
 NotifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME
              or FILE_NOTIFY_CHANGE_LAST_WRITE or FILE_NOTIFY_CHANGE_CREATION;
 hCompletionPort := CreateIoCompletionPort(hDirectory, 0, DWORD(Pointer(Self)), 0);
 ZeroMemory(@NotifyBuf, SizeOf(NotifyBuf));
 if not ReadDirectoryChanges(hDirectory, @NotifyBuf, SizeOf(NotifyBuf), True, NotifyFilter, @rwBytes, @Overlapped, nil) then
 begin
  CloseHandle(hDirectory);
  CloseHandle(hCompletionPort);
  hDirectory := 0;
  hCompletionPort := 0;
  ShowMessage(SysErrorMessage(GetLastError));
  exit;
 end;
 EditPath.Enabled := False;
 WatchThread := TWatchThread.Create(True);
 WatchThread.FreeOnTerminate := False;
 WatchThread.Resume;
end;

procedure TForm1.StopButtonClick(Sender: TObject);
begin
 if hCompletionPort = 0 then exit;
 PostQueuedCompletionStatus(hCompletionPort, 0, 0, nil);
 if Assigned(WatchThread) then begin
  WatchThread.Terminate;
  WatchThread.WaitFor;
  WatchThread.Free;
  WatchThread := nil;
 end;
 CloseHandle(hDirectory);
 hDirectory := 0;
 CloseHandle(hCompletionPort);
 hCompletionPort := 0;
 EditPath.Enabled := True;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
 StopButton.Click;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 ListBox1.Font.Name := 'MS ゴシック';
 hCompletionPort := 0;
 hDirectory := 0;
 ZeroMemory(@Overlapped, SizeOf(Overlapped));
end;

春子:「ところで、マコト、どう? 久しぶりの中学生活はもう慣れたかな?」
真琴:「それがまだ…、忘れてたコトも多いから友達と話をあわせるのがもう大変」
春子:「ま、4年ぶりだからね」
真琴:「でも、ハルコさん、どうしてアタシの話をあっさり信じてくれたの?」
春子:「そっかぁ、マコトは憶えてないんだね、4年前に戻ったワケを」
真琴:「えっ、ワケって、ハルコさんはそれを知ってるの?」
春子:「モチロン…、ジツはネ、マコトはまた、時の迷子になっちゃったのよ」
真琴:「時の迷子? それってどういうコト? よく分からないけど、ナンで?」
春子:「ナンでって…理由はアタシの方が聞きたいよ、もう、めちゃアセッタんだから」
真琴:「だって、そんな記憶ゼンゼンないもん、それでどうなったの?」
春子:「1回目の時はまだアタシの能力があって助けられたけど、今回はもうダメかと」
真琴:「でも、またハルコさんが助けてくれたの? えっ、それじゃ、もしかして…」
春子:「ホントに何も憶えてない? どうしても元に戻る出口が見つからず、やっと見つけたのが4年前の出口だったってコトを」
真琴:「憶えてない…」
春子:「もう死ぬ覚悟で、最後の力を振り絞って助けたんだから、感謝してよネ」
真琴:「それがホントならモチロン大感謝だけど…、もしかしてまた冗談話とか^^;」
春子:「こうして冗談話が聞けるのも助かったからでしょ? …あっ、予約のお客様が到着したのかな?」
真琴:「え〜? やっぱり冗談? それともホント? ねぇどっちなのぉ?」


RYO  2009-05-19 05:12:26  No: 34419

ありがとうございます。これはすごい!  こんなに教えていただいてしまって大感謝です。これからどういう風に応用していこうか考えてみます。


RYO  2009-05-20 18:57:01  No: 34420

リアルタイム監視ということで、制作中のアプリケーションに組み込むには一工夫いりそうですが、とりあえず使える方法があるということでスレッドはクローズいたします。
ご返答くださった方々、ありがとうございました。


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

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






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