In some of my projects I needed to monitor certain folders for changes. They must detect file or subfolder renames, additions, content changes, even security attribute changes. To accomplish this, I was using the help of Windows api ReadDirectoryChangesW. Since the real operation involves many steps and involved separate thread (I don't want the monitoring process to block the main thread), I wrapped the monitoring codes into classes.
If you have used anti-virus that provides real-time virus scanner (in Windows, of course), then you already enjoyed the benefit of this monitor for folder changes. These anti-virus would monitor the root of a drive for changes (including subfolders), and for each detected change (when it's a file) they will scan the changed file.
And here I would like to share the codes along with explanation to their key concepts.
This class was designed for the "public" interface of our folder monitoring system. That is why it was declared in interface section of the containing unit (FolderMon.pas). It contains properties that needed for the monitoring process. The information are:
- Folder to be monitored
- Kind of changes to be monitored
- Whether it is wanted to also monitor the subfolder(s)
- Whether the monitor is active
This class is descendant of TThread, and since it is not designed to be available to "public", it is declared in the implementation section of the FolderMon.pas unit.
Being a descendant of TThread, the thread's behavior was coded by overriding Execute method. The pseudocode of the overriding Execute would be:
procedure Execute; begin SetUp; while ThreadNotTerminated do begin if ReadDirectoryChanges then for each change do NotifyChange; end; TearDown; end;
However, in the actual implemention we do the SetUp part in the thread's constructor. This is to minimize the possibility of read/write clash. Thread's constructor will be executed in the caller's thread context. In our case, this fact makes it safe for our thread to access information from it's corresponding TFolderMon instance. For example, our thread needs monitored folder name information which is presented as string. And I believe you already know that string access is not thread-safe. The thread actually does not need the folder name to monitor changes. It only needs folder handle obtained through the folder name. Once this handle is obtained, the folder name is no longer relevant. So moving the SetUp part to constructore will be good.
Full implementation would be like shown below.
constructor TFolderMonWorker.Create(AOwner: TFolderMon); begin Owner := AOwner; if Owner=nil then raise Exception.Create('Reference to TFolderMon instance must be specified'); inherited Create(False); FreeOnTerminate := True; SetUp; end;
procedure TFolderMonWorker.SetUp; var i: TChangeType; begin FFolder := CreateFile(PChar(Owner.Folder) , FILE_LIST_DIRECTORY or GENERIC_READ , FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE , nil , OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , 0); FMonFilter := 0; for i := Low(TChangeType) to High(TChangeType) do if i in Owner.MonitoredChanges then FMonFilter := FMonFilter or NOTIFY_FILTERS[i]; end;
procedure TFolderMonWorker.Execute; const cBufSize = 32 * 1024; // 32k var B: Pointer; vCount: DWord; vOffset: DWord; vFileInfo: PFILE_NOTIFY_INFORMATION; begin GetMem(B, cBufSize); try while not Terminated do begin if Owner=nil then Exit; if ReadDirectoryChangesW(FFolder , B , cBufSize , Owner.MonitorSubFolders , FMonFilter , @vCount , nil , nil ) and (vCount > 0) then begin if Owner=nil then Exit; vFileInfo := B; repeat vOffset := vFileInfo.NextEntryOffset; FFolderItemInfo.Name := WideCharLenToString(@vFileInfo^.FileName, vFileInfo^.FileNameLength); SetLength(FFolderItemInfo.Name, vFileInfo^.FileNameLength div 2); case vFileInfo^.Action of FILE_ACTION_ADDED : FFolderItemInfo.Action := faNew; FILE_ACTION_REMOVED : FFolderItemInfo.Action := faRemoved; FILE_ACTION_MODIFIED : FFolderItemInfo.Action := faModified; FILE_ACTION_RENAMED_OLD_NAME: FFolderItemInfo.Action := faRenamedOld; FILE_ACTION_RENAMED_NEW_NAME: FFolderItemInfo.Action := faRenamedNew; end; Synchronize(DoFolderItemChange); PByte(vFileInfo) := PByte(DWORD(vFileInfo) + vOffset); until vOffset=0; end; end; finally TearDown; FreeMem(B, cBufSize); end; end;
procedure TFolderMonWorker.TearDown; begin CloseHandle(FFolder); end;
Note that :
- We call TearDown in the thread's own context, since beside our thread there is none interested with the folder handle. So it's safe.
- Owner refers to instance of TFolderMon that created the thread. Through this reference the thread know the correct event to raise.
For testing TFolderMon, I have written a simple demo project. This demo project uses an instance of TFolderMon to monitor a folder and provides some ways to add, delete, and change some files of the monitored folder.
Running the Demo Project
- Upon running the demo project, you will get like shown below.
- To actually monitor a folder, we need to activate TFolderMon. Click on the Activate button, to start monitor the selected folder.
- Let's add some new files to the monitored folder. Click on the Add random text file a few times. Each click will add a text file to the monitored folder. The name and content of the file will be random.
- Now let's delete a file. Click on the Delete random text file button.
- Let's modify the content of a file. Click on Change content/size of random text file button. It will randomly pick a text file inside the monitored folder and generate random content for it. And you will get something like shown below.
- The last test is to try rename a file. Click on Rename a random text file button. A random text file in the monitored folder will be selected and then renamed into random name. And you will get something like shown below.
That's it. It's easy to monitor a folder for changes. Full source code of FolderMon.pas unit and the demo project can be downloaded here: FolderChangeMonitor_Demo.zip 991.79KB 4044 downloads. Feel free to use it for any use.