{ ***************************************************************************

  Copyright (c) 2015-2019 Kike Pérez

  Unit        : Quick.FileMonitor
  Description : Watch for single file changes
  Author      : Kike Pérez
  Version     : 1.2
  Created     : 11/09/2017
  Modified    : 29/01/2019

  This file is part of QuickLib: https://github.com/exilon/QuickLib

 ***************************************************************************

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

 *************************************************************************** }

unit Quick.FileMonitor;

interface

{$i QuickLib.inc}

uses
  Classes,
  {$IFDEF MSWINDOWS}
  Windows,
  SyncObjs,
  {$ELSE}
  SyncObjs,
  {$ENDIF}
  SysUtils,
  {$IFDEF FPC}
  Quick.Files;
  {$ELSE}
  System.IOUtils;
  {$ENDIF}

type

  TMonitorNotify = (mnNone, mnFileCreated, mnFileModified, mnFileDeleted);
  TMonitorWatch = set of TMonitorNotify;
  TFileChangeNotify = procedure(MonitorNofify : TMonitorNotify) of object;

  TFileMonitor = class(TThread)
  private
    fFileName : string;
    fTickEvent : TSimpleEvent;
    fInterval : Integer;
    fNotifies : TMonitorWatch;
    fEnabled : Boolean;
    fExists : Boolean;
    fModifedDate : TDateTime;
    fCurrentMonitorNotify : TMonitorNotify;
    fOnChangeNotify : TFileChangeNotify;
    procedure SetEnabled(Status : Boolean);
    procedure NotifyEvent;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
    property FileName : string read fFileName write fFileName;
    property Interval : Integer read fInterval write fInterval;
    property Notifies : TMonitorWatch read fNotifies write fNotifies;
    property OnFileChange : TFileChangeNotify read fOnChangeNotify write fOnChangeNotify;
    property Enabled : Boolean read fEnabled write SetEnabled;
  end;

  TQuickFileMonitor = TFileMonitor;

implementation

constructor TFileMonitor.Create;
begin
  inherited Create(True);
  Self.FreeOnTerminate := False;
  fInterval := 1000;
  fExists := False;
  fModifedDate := 0;
  fCurrentMonitorNotify := mnNone;
  fNotifies := [mnFileCreated,mnFileModified,mnFileDeleted];
  {$IFDEF FPC}
  fTickEvent := TSimpleEvent.Create;
  {$ELSE}
  fTickEvent := TSimpleEvent.Create(nil,True,False,'');
  {$ENDIF}
end;

destructor TFileMonitor.Destroy;
begin
  if not Terminated then Terminate;
  Self.WaitFor;
  fTickEvent.SetEvent;
  fTickEvent.Free;
  inherited;
end;

procedure TFileMonitor.Execute;
var
  LastModifiedDate : TDateTime;
begin
   inherited;
  while not Terminated do
  begin
    fCurrentMonitorNotify := mnNone;
    if fTickEvent.WaitFor(fInterval) = TWaitResult.wrTimeout then
    begin
      if fEnabled then
      begin
        if TFile.Exists(fFileName) then
        begin
          if fExists then
          begin
            if mnFileModified in fNotifies then
            begin
              LastModifiedDate := TFile.GetLastWriteTime(fFileName);
              if LastModifiedDate > fModifedDate then
              begin
                fCurrentMonitorNotify := mnFileModified;
                fModifedDate := LastModifiedDate;
              end;
            end;
          end
          else
          begin
            if mnFileCreated in fNotifies then fCurrentMonitorNotify := mnFileCreated;
            LastModifiedDate := TFile.GetLastWriteTime(fFileName);
            fModifedDate := LastModifiedDate;
          end;
          fExists := True;
        end
        else
        begin
          //check if file deleted
          if mnFileDeleted in fNotifies then
          begin
            if fExists then
            begin
              fExists := False;
              fCurrentMonitorNotify := mnFileDeleted;
            end;
          end;
        end;
        if fCurrentMonitorNotify <> mnNone then
        begin
          if IsConsole then NotifyEvent
            else Synchronize(NotifyEvent);
        end;
      end;
    end;
  end;
end;

procedure TFileMonitor.SetEnabled(Status : Boolean);
begin
  if (Status) and {$IFNDEF FPC}(not Started){$ELSE}(Suspended){$ENDIF} then Start;

  if fEnabled <> Status then
  begin
    fEnabled := Status;
    //gets current values
    if TFile.Exists(fFileName) then
    begin
      fExists := True;
      fModifedDate := TFile.GetLastWriteTime(fFileName);
    end;
  end;
end;

procedure TFileMonitor.NotifyEvent;
begin
  if Assigned(fOnChangeNotify) then fOnChangeNotify(fCurrentMonitorNotify);
end;

end.
