[BlueLeaf1336]> PROBLEMS> tail>

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

historyTOP

2005/02/12:作成

2005/02/12TOP

「賢く」というのは、たとえば TTimer で1秒おきに変更があったかどうかに関係なく処理する、に対して、という程度のものです。Googleで検索してみるとぼこぼこと見つかるのですが、基本的に、監視はフォルダを対象とするもののようです。つまり、あるログファイルを監視したい場合に、「そのファイルが変更されたら教えて」というのは無理(?)のようで、「このフォルダの中身が変更された」ことまでを教えてくれるようです。もちろん、無条件に読み込むよりは、よっぽど「賢い」やり方といえそうです。

さて、仮にコレで監視したとして、たまたま同じフォルダにある全然関係のないファイルの変更に反応して読み込んでしまうのもあんまりなので、次のような手順にするのがよいと思われます。

  1. 監視したいファイルの入っているフォルダに、変更を通知してもらう
  2. 変更が通知されたら、監視対象のファイルが変更されたかどうかを調べる
  3. まさにそのファイルが変更されたのであれば、読み込んで再表示する

書きながら、当然のように浮かんできた疑問があります。「ファイルが変更されたかどうかというのをどうやって見極めるのか?」ただし、これについても参考にすべき基準があります。それは、フォルダの監視に使用する関数「FindFirstChangeNotification」は、監視する条件として、ファイルの追加・削除、ファイル名の変更・ディレクトリの追加・削除、ディレクトリ名の変更・属性の変更・ファイルのサイズの変更・ファイルの最終書き込み時刻の変更・セキュリティ記述子の変更、を指定することができる、という点です。

つまり、前述の条件以外の状態は検知されないので、逆にこの関数を使用する限り、前述の状態だけを調べればよい(これで充分かどうかではなく、これ以外を調べても仕方がない)といえそうです。もちろん、このうちの、ほとんどはフォルダの状態に関するものなので、結局のところ

を調べる、ということで落ち着けそうです。具体的には、監視を開始した時点で、ファイルサイズと最終書き込み時刻を記録しておいて、変更通知をもらうたびにチェック、変更を受けていたら再表示を行って、最新のファイルサイズと書き込み時刻を(次回の比較のために)記録しておく、という感じになりそうです。

ファイルサイズを取得するTOP

多分、以前に何かで見たに決まっているんですが、今回は何も見ずにコーディングできたので、参考サイトはありません。3種類ほどやってみました。

//  ---------------------------------------------------------------------------
//  ファイルサイズを取得する(TFileStream版)
function FileSizeByFileStream(const FileName: string): Int64;
begin
    Result := -1;
    try
        with TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone) do
        begin
            try
                Result := Size;
            finally
                Free;
            end;
        end;
    except
    end;
end;

//  ---------------------------------------------------------------------------
//  ファイルサイズを取得する(TSearchRec版)
function FileSizeBySearchRec(const FileName: string): Int64;
var
    SR: TSearchRec;
begin
    Result := -1;
    try
        if FindFirst(FileName, faAnyFile, SR) = 0 then
        begin
            try
                Result := SR.Size;
            finally
                FindClose(SR);
            end;
        end;
    except
    end;
end;

//  ---------------------------------------------------------------------------
//  ファイルサイズを取得する(FileSize版)
function FileSizeByFileSize(const FileName: string): Int64;
var
    F: file;                            //  型なしファイル
begin
    Result := -1;
    try
        AssignFile(F, FileName);
        try
            Reset(F, 1);                //  バイトファイル扱い
            Result := FileSize(F);
        finally
            CloseFile(F);
        end;
    except
    end;
end;

それぞれのやり方で、9999回ずつ試してみた場合の処理時間を計測したものです。どれを使うのがよいのかはわかりません。3番目のやり方が早いような感じはしますが、ひょっとしたら何度も読み込んだことによる、Windowsのキャッシュの問題かも知れません。

と思ったんですが、実行順序を入れ替えてみても同じ感じなので、3番目のやり方が一番早いようです。どうでもよいことですが。

最終更新日を取得するTOP

上2つは参考サイトそのままで、更新日以外を無視したものです。3つ目はたまたまヘルプで見つけた関数を使用したものです。

//  ---------------------------------------------------------------------------
//  最終更新日を取得する(FileAge版)
//  http://hp.vector.co.jp/authors/VA009712/take/delphi/kabesys.htm#datetimefile1
function FileModifiedByFileAge(const FileName: string): TDateTime;
begin
    Result := FileDateToDateTime(FileAge(FileName));
end;

//  ---------------------------------------------------------------------------
//  最終更新日を取得する(GetFileTime版)
//  http://hp.vector.co.jp/authors/VA009712/take/delphi/kabesys.htm#datetimefile2
function FileModifiedByGetFileTime(const FileName: string): TDateTime;
var
    FileHandle: integer;
    ftCreateDate: TFileTime;
    ftModifyDate: TFileTime;
    ftAccessDate: TFileTime;
    lftModifyDate: TFileTime;
    stModifyDate: TSystemTime;
begin
    Result := 0;
    try
        FileHandle := FileOpen(FileName, fmOpenRead or fmShareDenyNone);
        if (FileHandle >= 0) then
        begin
            try
                //  ファイルの日時を取得
                GetFileTime(FileHandle, @ftCreateDate, @ftAccessDate, @ftModifyDate);
                //  ローカル日時に変更
                FileTimeToLocalFileTime(ftModifyDate, lftModifyDate);
                //  システム日時に変更
                FileTimeToSystemTime(lftModifyDate, stModifyDate);
                Result := SystemTimeToDateTime(stModifyDate);
            finally
                FileClose(FileHandle);
            end;
        end;
    except
    end;
end;

//  ---------------------------------------------------------------------------
//  最終更新日を取得する(FileGetDate版)
//  http://hp.vector.co.jp/authors/VA009712/take/delphi/kabesys.htm#datetimefile1
function FileModifiedByFileGetDate(const FileName: string): TDateTime;
var
    FileHandle: integer;
begin
    Result := 0;
    try
        FileHandle := FileOpen(FileName, fmOpenRead or fmShareDenyNone);
        if (FileHandle >= 0) then
        begin
            try
                Result := FileDateToDateTime(FileGetDate(FileHandle));
            finally
                FileClose(FileHandle);
            end;
        end;
    except
    end;
end;

またもや3種類を比較してみました。FileAge版が手軽な分、遅いような気がします。でも、たまたま見つけたDelphi FAQ: ファイルサイズの取得法では(ファイルサイズの話ですが)、300ファイルで試した場合...という記述があり、ひょっとするとファイル数が増えると話が違ってくる可能性もあります。

今回のファイルサイズと最終更新日に使用したファイルは、1ファイル(2.5MB弱)だけなので、なんともいえないかと。かわりに9999回処理を繰り返してますが。

一番気になるのは、2番目の方法で取得した内容が、他の2つに比べて少し早めでおそらく一番正確そうな気がするということです。これを採用すべきのようです。

以上で「フォルダの変更が通知されたら監視対象ファイルの変更の有無を調べて」という目標のうち「監視対象ファイルの変更の有無を調べて」の部分だけがテストできました。

20050212tail_test.zip(206,884bytes)

EOFTOP