ファイル更新監視

ひとりWikiで外部エディタを使えるようにするための前準備として、表示中のファイルが更新されたら表示も更新する処理を入れる必要があります。そのためにファイル更新を検知する仕組みが必要になります。タイマーで一定時間毎にファイルのタイムスタンプをチェックするというのでも目的は果たせますが、あんまり頻繁にタイムスタンプを調べるのも今ひとつだと思い FindFirstChangeNotification を使う方法にしました。

そうするとスレッドだなということで作ったのが以下のクラス。コンストラクタで、ファイルが更新されたことを検知した時に呼ばれるメソッドを指定し、SetNotifyFile で監視するファイルを指定したら Resume します。

unit UFileChangeNotification;

interface

uses
  Classes, Windows;

type
  TFileChangeNotificationProc = procedure(FileName: string) of object;

  TFileChangeNotification = class(TThread)
  private
    FChangeHandle: THandle;
    FFileChangeNotificationProc: TFileChangeNotificationProc;
    FLastErrorCode: integer;
    FNotifyFile: string;
    FUpdateTime: integer;

    procedure CallBack;
    function GetFileUpdateTime(FileName: string): integer;
  protected
    procedure Execute; override;
  public
    constructor Create(
      FileChangeNotificationProc: TFileChangeNotificationProc);

    function SetNotifyFile(FileName: string): boolean;

    property LastErrorCode: integer read FLastErrorCode;
    property NotifyFile: string read FNotifyFile;
  end;

implementation

uses SysUtils;

{ TFileChangeNotification }

procedure TFileChangeNotification.CallBack;
begin
  FFileChangeNotificationProc(FNotifyFile);
end;

constructor TFileChangeNotification.Create(
  FileChangeNotificationProc: TFileChangeNotificationProc);
begin
  inherited Create(True);

  FChangeHandle := INVALID_HANDLE_VALUE;
  FFileChangeNotificationProc := FileChangeNotificationProc;
end;

procedure TFileChangeNotification.Execute;
var
  Ret: integer;
  UpdateTime: integer;
begin
  while not Terminated do begin
    Ret := WaitForSingleObject(FChangeHandle, 100);
    if Ret = WAIT_OBJECT_0 then begin
      UpdateTime := GetFileUpdateTime(FNotifyFile);
      if UpdateTime > FUpdateTime then begin
        FUpdateTime := UpdateTime;
        Synchronize(CallBack);
        FindNextChangeNotification(FChangeHandle);
      end;
    end;
  end;

  if FChangeHandle <> INVALID_HANDLE_VALUE then begin
    FindCloseChangeNotification(FChangeHandle);
  end;
end;

function TFileChangeNotification.GetFileUpdateTime(FileName: string): integer;
var
  Ret: integer;
  SearchRec: TSearchRec;
begin
  Ret := FindFirst(FileName, faAnyFile, SearchRec);

  if Ret <> 0 then begin
    result := 0;
    exit;
  end;

  result := SearchRec.Time;
  FindClose(SearchRec);
end;

function TFileChangeNotification.SetNotifyFile(
  FileName: string): boolean;
begin
  if FChangeHandle <> INVALID_HANDLE_VALUE then begin
    FindCloseChangeNotification(FChangeHandle);
  end;

  FChangeHandle := FindFirstChangeNotification(
    PChar(ExtractFilePath(FileName)), False, FILE_NOTIFY_CHANGE_LAST_WRITE);

  if FChangeHandle = INVALID_HANDLE_VALUE then begin
    FLastErrorCode := GetLastError;
    result := False;
    exit;
  end;

  FUpdateTime := GetFileUpdateTime(FileName);
  FNotifyFile := FileName;
  result := True;
end;

end.

改めてみるとコメントが全くないですね。