views:

165

answers:

4

I need to determine which folders contain files that have been modified "recently" (within a certain interval). I notice that folder datestamps seem to get updated whenever a contained file is modified, but this behaviour doesn't propagate up the tree, i.e. the datestamp of the folder containing the folder that contains the modified file doesn't get updated.

I can work with this behaviour, but I suspect it depends on platform/file system/network or local drive, etc. I would still like to take advantage of it where I could, so I need a boolean function to return true if the platform/disk running my app supports this behaviour.

I'm quite happy to recurse through the tree. What I want to avoid is having to do a FindFirst/FindNext for every file in every folder to see if any have been modified in (say) the last day - if I can avoid doing that for folders that don't have their datestamps modified within the last day it will save a great deal of time.

+3  A: 

Check the FindFirstChangeNotification and FindNextChangeNotification functions another option is use the TJvChangeNotify JEDI component.

addionally you can check this link

RRUZ
A: 

Hi, you should have a look at http://help.delphi-jedi.org/item.php?Id=172977 which is a ready solution. If you do not want to download & install whole JVCL (which is however a great piece of code ;) ) you might want to see the file source online - http://jvcl.svn.sourceforge.net/viewvc/jvcl/trunk/jvcl/run/JvChangeNotify.pas?revision=12481&view=markup

migajek
+1  A: 

The solutions that have been posted so far are about obtaining notifications as they happen, and they'll work well for that purpose. If you want to look into the past and see when something was last changed, as opposed to monitoring it in real time, then it gets tricker. I think there's no way to do that except by recursively searching through the folder tree and checking datestamps.

EDIT: In response to the OP's comment, yeah, it doesn't look like there's any way to configure FindFirst/FindNext to only hit directories and not files. But you can skip checking the dates on the files with this filter: (SearchRec.Attr and SysUtils.faDirectory <> 0). That should speed things up a little. Don't check the dates on the files at all. You'll probably still have to scan through everything, though, since the Windows API doesn't provide any way (that I know of) to only query for folders and not files.

Mason Wheeler
I'm quite happy to recurse through the tree. What I want to avoid is having to do a FindFirst/FindNext for every file in every folder to see if any have been modified in (say) the last day - if I can avoid doing that for folders that don't have their datestamps modified within the last day it will save a great deal of time.
@user89691: Edited.
Mason Wheeler
@user8961, don't worry about checking time/size/attributes on files, the CPU is not going to get dizzy from all that recursion. Seriously, once the information about the structure of any given folder is already in memory, the time penalty is insignificant (the I/O penalty has already been paid - and that's where the bottle-neck is). Besides, things might happen within the directory that date and time alone can't tell. Example: One might copy a new file to the directory (copied files retain creation and modification times), and that operation might not change the mod time on the directory.
Cosmin Prund
+1  A: 

I wrote a code for this purpose for one of my projects. This uses FindFirstChangeNotification and FindNextChangeNotification API functions. Here is the code (I removed some project specific portions):

/// <author> Ali Keshavarz </author>
/// <date> 2010/07/23 </date>

unit uFolderWatcherThread;

interface

uses
  SysUtils, Windows, Classes, Generics.Collections;

type
  TOnThreadFolderChange = procedure(Sender: TObject; PrevModificationTime, CurrModificationTime: TDateTime) of object;
  TOnThreadError = procedure(Sender: TObject; const Msg: string; IsFatal: Boolean) of object;

  TFolderWatcherThread = class(TThread)
  private
    class var TerminationEvent : THandle;
  private
    FPath : string;
    FPrevModificationTime : TDateTime;
    FLatestModification : TDateTime;
    FOnFolderChange : TOnThreadFolderChange;
    FOnError : TOnThreadError;
    procedure DoOnFolderChange;
    procedure DoOnError(const ErrorMsg: string; IsFatal: Boolean);
    procedure HandleException(E: Exception);
  protected
    procedure Execute; override;

  public
    constructor Create(const FolderPath: string;
                       OnFolderChangeHandler: TOnThreadFolderChange;
                       OnErrorHandler: TOnThreadError);
    destructor Destroy; override;
    class procedure PulseTerminationEvent;
    property Path: string read FPath;
    property OnFolderChange: TOnThreadFolderChange read FOnFolderChange write FOnFolderChange;
    property OnError: TOnThreadError read FOnError write FOnError;
  end;

  /// <summary>
  /// Provides a list container for TFolderWatcherThread instances.
  /// TFolderWatcherThreadList can own the objects, and terminate removed items
  ///  automatically. It also uses TFolderWatcherThread.TerminationEvent to unblock
  ///  waiting items if the thread is terminated but blocked by waiting on the
  ///  folder changes.
  /// </summary>
  TFolderWatcherThreadList = class(TObjectList<TFolderWatcherThread>)
  protected
    procedure Notify(const Value: TFolderWatcherThread; Action: TCollectionNotification); override;
  end;

implementation

{ TFolderWatcherThread }

constructor TFolderWatcherThread.Create(const FolderPath: string;
  OnFolderChangeHandler: TOnThreadFolderChange; OnErrorHandler: TOnThreadError);
begin
  inherited Create(True);
  FPath := FolderPath;
  FOnFolderChange := OnFolderChangeHandler;
  Start;
end;

destructor TFolderWatcherThread.Destroy;
begin
  inherited;
end;

procedure TFolderWatcherThread.DoOnFolderChange;
begin
  Queue(procedure
        begin
          if Assigned(FOnFolderChange) then
            FOnFolderChange(Self, FPrevModificationTime, FLatestModification);
        end);
end;

procedure TFolderWatcherThread.DoOnError(const ErrorMsg: string; IsFatal: Boolean);
begin
  Synchronize(procedure
              begin
                if Assigned(Self.FOnError) then
                  FOnError(Self,ErrorMsg,IsFatal);
              end);
end;

procedure TFolderWatcherThread.Execute;
var
  NotifierFielter : Cardinal;
  WaitResult : Cardinal;
  WaitHandles : array[0..1] of THandle;
begin
 try
    NotifierFielter := FILE_NOTIFY_CHANGE_DIR_NAME +
                       FILE_NOTIFY_CHANGE_LAST_WRITE +
                       FILE_NOTIFY_CHANGE_FILE_NAME +
                       FILE_NOTIFY_CHANGE_ATTRIBUTES +
                       FILE_NOTIFY_CHANGE_SIZE;
    WaitHandles[0] := FindFirstChangeNotification(PChar(FPath),True,NotifierFielter);
    if WaitHandles[0] = INVALID_HANDLE_VALUE then
      RaiseLastOSError;
    try
      WaitHandles[1] := TerminationEvent;
      while not Terminated do
      begin
        //If owner list has created an event, then wait for both handles;
        //otherwise, just wait for change notification handle.
        if WaitHandles[1] > 0 then
         //Wait for change notification in the folder, and event signaled by
         //TWatcherThreads (owner list).
          WaitResult := WaitForMultipleObjects(2,@WaitHandles,False,INFINITE)
        else
          //Wait just for change notification in the folder
          WaitResult := WaitForSingleObject(WaitHandles[0],INFINITE);

        case WaitResult of
          //If a change in the monitored folder occured
          WAIT_OBJECT_0 :
          begin
            // notifiy caller.
            FLatestModification := Now;
            DoOnFolderChange;
            FPrevModificationTime := FLatestModification;
          end;

          //If event handle is signaled, let the loop to iterate, and check
          //Terminated status.
          WAIT_OBJECT_0 + 1: Continue;
        end;
        //Continue folder change notification job
        if not FindNextChangeNotification(WaitHandles[0]) then
          RaiseLastOSError;
      end;
    finally
      FindCloseChangeNotification(WaitHandles[0]);
    end;  
  except
    on E: Exception do
      HandleException(E);
  end;
end;

procedure TFolderWatcherThread.HandleException(E: Exception);
begin
  if E is EExternal then
  begin
    DoOnError(E.Message,True);
    Terminate;
  end
  else
    DoOnError(E.Message,False);
end;

class procedure TFolderWatcherThread.PulseTerminationEvent;
begin
  /// All instances of TFolderChangeTracker which are waiting will be unblocked,
  ///  and blocked again immediately to check their Terminated property.
  ///  If an instance is terminated, then it will end its execution, and the rest
  ///  continue their work.
  PulseEvent(TerminationEvent);
end;


{ TFolderWatcherThreadList }

procedure TFolderWatcherThreadList.Notify(const Value: TFolderWatcherThread;
  Action: TCollectionNotification);
begin
  if OwnsObjects and (Action = cnRemoved) then
  begin
    /// If the thread is running, terminate it, before freeing it.
    Value.Terminate;
    /// Pulse global termination event to all TFolderWatcherThread instances.
    TFolderWatcherThread.PulseTerminationEvent;
    Value.WaitFor;
  end;

  inherited;
end;

end.

This provides two classes; a thread class which monitors a folder for changes, and if a change is detected, it will return the current change time and the previous change time through OnFolderChange event. And a list class for storing a list of monitoring threads. This list terminates each own thread automatically when the thread is removed from list.

I hope it helps you.

vcldeveloper
@vcldeveloper, thanks. Probably not what I am looking for (in the general case wouldn't I need one thread per folder that I wanted to monitor? - in my case I will be scanning a whole disk to look for files that need backing up). Some generally useful code in there though and I'm sure I will use it sometime.