Reputation: 5630
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.
Upvotes: 4
Views: 5087
Reputation: 7489
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.
Upvotes: 2
Reputation: 84620
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.
Upvotes: 2
Reputation: 136431
Check the FindFirstChangeNotification
and FindNextChangeNotification
functions
another option is use the TJvChangeNotify
JEDI component.
addionally you can check this link
Upvotes: 3
Reputation: 8614
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
Upvotes: 0