[BlueLeaf1336]> PROBLEMS> tail>

tail > ファイルの変更を賢く監視する(2)

historyTOP

2005/02/13:作成

2005/02/13TOP

本来なら、ここでフォルダの変更について検索したサイトを参考に、変更をキャッチする処理を作る必要があるんですが、Delphi6 Personal には、「ShellChangeNotifier」というコンポーネントが「Samples」タブに存在します。

このコンポーネントは、基本的にソースコードの添付されていないPersonal版においても、ヘルプが存在しないかわりにソースコードがついています。

これがどういう風に扱われることを期待しているのか、今回の場合でいうと転載してよいかどうかわからないのですが、多分駄目ということで話を進めます。ただし、このページを見て、この内容を参考にしようとしている人(がいると仮定して)は、Delphi6を持っているわけで、一部を切り取ったものを配布するのなら(元のコピーライトさえ書いておけば)よいのではないかと、勝手な解釈をすることにします。

なぜこんなことを書くかというと、上記のユニットファイルは、いろんな Shell系の処理を詰め込んであって、サイズが96KBもあるわけです。そんなユニットファイルを前提にするコンポーネントを、ヘナチョコプログラムに落としたくないなぁという身勝手な理由があるんです。実際、今回必要としている、ShellChangeNotifierコンポーネントについていえば、8KB分の処理で充分だし、TComponentから派生する必要も特にないわけで、そんなこんなで、そんなこんなです。

本当にまるまま、その部分だけコピってきてもちゃんと動いているようですんで、そういうことにしておきます。で、その一部分だけを抜書きしたユニットファイルを「ShellNotify.pas」と名づけることにします(さすがに掲載するのは止めておきます)。

すると、あるフォルダの変更を監視するためのソースはこんな感じにできます。

uses
    ShellNotify;

//-----------------------------------------------------------------------------
//  テストのため、アプリケーションでひとつ
var
    ShellNotifier: TShellChangeNotifier = nil;

//-----------------------------------------------------------------------------
//  監視開始
procedure TForm1.Button1Click(Sender: TObject);
begin
    //  (本当はフォルダですが)横着してファイルを選択してもらいます
    if OpenDialog1.Execute then
    begin
        //  変更通知クラスを作成して
        ShellNotifier := TShellChangeNotifier.Create();
        //  変更条件は「サイズ変更」「書き込み日変更」
        ShellNotifier.NotifyFilters := [nfSizeChange, nfWriteChange];
        //  監視フォルダは、選択ファイルの入っているフォルダ
        ShellNotifier.Root := ExtractFilePath(OpenDialog1.FileName);
        //  表示
        LblTarget.Caption  := ExtractFileName(OpenDialog1.FileName);
        LblTarget.Hint     := OpenDialog1.FileName;
        LblTarget.ShowHint := true;
        //  変更通知イベントをセットする(とクラスが変更を監視し始める)
        ShellNotifier.OnChange := OnChangeMethod;
        //  ボタンを適当に処理
        Button1.Enabled := false;
        Button2.Enabled := true;
    end;
end;

//-----------------------------------------------------------------------------
//  監視終了
procedure TForm1.Button2Click(Sender: TObject);
begin
    //  クラスを解放する
    ShellNotifier.Free;
    ShellNotifier := nil;
    //  ボタンを適当に処理
    Button1.Enabled := true;
    Button2.Enabled := false;
end;

//-----------------------------------------------------------------------------
//  テスト用の変更通知イベント
procedure TForm1.OnChangeMethod();
begin
    //  実際は目的のファイルの変更を調べて
    ;
    //  変更されていれば読み込んで再表示
    ;
    Memo1.Lines.Add(FormatDateTime('c', Now()) + ' >> 変更がありました');
end;

//-----------------------------------------------------------------------------
initialization
    ;
finalization
    //  クラスを解放する
    if (ShellNotifier <> nil) then ShellNotifier.Free;
end.

何も考えずに、一部を切り出した割にはうまく動いています。

気をつけないと駄目かもしれないのが、たとえばエディタで監視対象フォルダのいずれかのファイル(テストではフォルダレベルで変更を捕まえているので)をエディタで開いて保存するだけで、2回通知イベントが走ることぐらいですか。

しかし、これについても、サイズと最終更新日さえ比較用に保持しておけば、2回目の変更はおそらくスルーされるはずなので大丈夫かと思います。もしそうでなければほぼ同時に同じ処理を2回連続でやることになるので微妙な感じもしますが、まぁ大丈夫かと。

さて、これで処理はそろったようです。

  1. 監視対象のファイルのサイズと最終更新日を調べて保持しておく
  2. このクラスを使って、監視対象のファイルの含まれるフォルダの変更をキャッチする
  3. 変更通知が来たら、監視対象のファイルのサイズと最終更新日を調べる
  4. 保持している内容とどちらか一方でも異なるようであれば
    1. まるごと読み込む
    2. 後ろから何行か(最近更新された分を想定するも行数はユーザー指定)を表示する
    3. 新しいサイズと最終更新日を上書き保持しておく

できそうです。ただ、実際には、使いやすくなることを期待して、MDI形式で(?)複数のファイルを同時に監視できるようにするべきだと思います。また、ドラッグアンドドロップなどで簡単に監視するファイルを指定できるようにもしたいし、いろいろやるべきことはありそうですが、先が見えてきました。

と思ったんですが、もう一度使ってみると、(ちょっと怪しいのはおいといて)監視終了後(クラスを解放しているにもかかわらず)、1回分だけ通知が飛んでくるようです。まぁ回避しようがありますが。たとえば、「クラスが解放されていれば」という条件を通知イベント内で判定するとか。

20050213tail_test.zip(171,967bytes)

EOFTOP