WatchFolder() - updated on 2021-10-14

Post your working scripts, libraries and tools for AHK v1.1 and older
just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: WatchFolder() - updated on 2016-11-30

Post by just me » 10 Dec 2020, 04:34

Hi,

I'm pretty sure the error window pops up before the file has been written to the storage device, so IMO WatchFolder() cannot help you.

just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: WatchFolder() - updated on 2016-11-30

Post by just me » 10 Dec 2020, 05:40

n00b wrote:
04 Dec 2020, 20:39
...
Hi n00b, I sent you a PM.

n00b
Posts: 29
Joined: 24 May 2016, 16:42

Re: WatchFolder() - updated on 2016-11-30

Post by n00b » 11 Dec 2020, 15:20

nschoonover wrote:
09 Dec 2020, 09:18
So, what do you all think? Can this function be used to do what I am trying to do?
as the author of the function already wrote, you're on the wrong track.

you probably should write a simple script instead yourself and run it on the server.
the script would wait for the message to appear, and do the renaming part automatically for you.

in terms of only bypassing the message, there might be a simpler solution although:
let the script simply close the window /kill the process whenever the message pops up.

in case you don't know how to achieve that, have a look e.g. here

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: WatchFolder() - updated on 2016-11-30

Post by hasantr » 11 Dec 2020, 17:29

nschoonover wrote:
09 Dec 2020, 09:18
Hi All!
This sounds like a great tool. I am going to start experimenting with this now. I would like to use this for a specific scenario.

We have a network scanner that scans files directly to a network folder in PDF format. We name the file using the interface at the scanner. However, if the name you type in on the scanner already exists, the scanner doesn't have the ability to sense that. As a result, the scanner tries to save a file to the network where there is already a file. This pops a windows error that allows you to save both or skip. The error appears on the server PC, so a user can't really see it and fix it. The result is that all future scans get held up, waiting for the error message on there server to be addressed by a user.

I would like to use the WatchFolder() function to sense an incoming file that is already exists and rename it to include a (1) at the end. Ideally this would happen without any user input, effectively bypassing the windows error message and just automatically renaming the file if the name already exists.

I have scoured the scanner tools to find a similar built-in function, but to no avail! See my previous attempts here:
https://www.copytechnet.com/forums/sharp/152689-network-scanner-tool-duplicate-filenames.html
https://social.msdn.microsoft.com/Forums/en-US/f60eb786-c6a8-4ff6-b7c1-ea9d583eecad/duplicate-filenames-error-sharp-network-scan-tool?forum=wcf

So, what do you all think? Can this function be used to do what I am trying to do?
Thanks for your time!
Nate

Tüm dosyaların listesini saklayıp oradan kontrol ederek işlem yapmak bir seçenek değil mi? İngilizcem iyi değil ve sizi eksik anlamış olabilirim.

Mod edit: Google translated this as:
Isn't it an option to keep a list of all files and check them from there? I am not good at English and I may have not understood you well.

nschoonover
Posts: 5
Joined: 09 Dec 2020, 09:09

Re: WatchFolder() - updated on 2016-11-30

Post by nschoonover » 22 Dec 2020, 13:29

Hi Everyone,
I am back at the WatchFolder script. It is just too useful to let it go. In the example below I have built on the shoulders of giants to create a program to watch two network folders from our server PC. If a file appears in a folder and the first two letters of the filename are "VA", then automatically rename the file to include a unique tick count. If the file does not begin with VA, then ignore it.

The script works like a charm. However, I want it to be as secure and robust as possible. Can anyone review my code and suggest changes to increase security and reliability?

Code: Select all

#NoEnv
#Warn
#Include WatchFolder.ahk
SetBatchLines, -1
; ----------------------------------------------------------------------------------------------------------------------------------
Gui, Margin, 20, 20
Gui, Add, Text, , Watch Folder:
Gui, Add, Edit, xm y+3 w730 vWatchedFolder cGray +ReadOnly, Select a folder ...
Gui, Add, Button, x+m yp w50 hp +Default vSelect gSelectFolder, ...
Gui, Add, Text, xm, Watch Folder 2:
Gui, Add, Edit, xm y+3 w730 vWatchedFolder2 cGray +ReadOnly, Select a 2nd folder ...
Gui, Add, Button, x+m yp w50 hp +Default vSelect2 gSelectFolder2, ...
Gui, Add, Text, xm y+5, Watch Changes:
Gui, Add, Checkbox, xm y+3 vSubTree, In Sub-Tree
Gui, Add, Checkbox, x+5 yp vFiles Checked, Files
Gui, Add, Checkbox, x+5 yp vFolders Checked, Folders
Gui, Add, Checkbox, x+5 yp vAttr, Attributes
Gui, Add, Checkbox, x+5 yp vSize, Size
Gui, Add, Checkbox, x+5 yp vWrite, Last Write
Gui, Add, Checkbox, x+5 yp vAccess, Last Access
Gui, Add, Checkbox, x+5 yp vCreation, Creation
Gui, Add, Checkbox, x+5 yp vSecurity, Security
Gui, Add, ListView, xm w800 r15 vLV, TickCount|Folder|Action|Name|IsDir|OldName|%A_Space%
Gui, Add, Button, xm w100 gStartStop vAction +Disabled, Start
Gui, Add, Button, x+m yp wp gPauseResume vPause +Disabled, Pause
Gui, Add, Button, x+m yp wp gCLear, Clear
Gui, Show, , Watch Folder
GuiControl, Focus, Select
Return
; ----------------------------------------------------------------------------------------------------------------------------------
GuiClose:
ExitApp
; ----------------------------------------------------------------------------------------------------------------------------------
Clear:
   LV_Delete()
Return
; ----------------------------------------------------------------------------------------------------------------------------------
PauseResume:
   GuiControlGet, Caption, , Pause
   If (Caption = "Pause") {
      WatchFolder("**PAUSE", True)
      GuiControl, Disable, Action
      GuiControl, , Pause, Resume
   }
   ELse {
      WatchFolder("**PAUSE", False)
      GuiControl, Enable, Action
      GuiControl, , Pause, Pause
   }
Return
; ----------------------------------------------------------------------------------------------------------------------------------
StartStop:
   Gui, +OwnDialogs
   Gui, Submit, NoHide
   If !InStr(FileExist(WatchedFolder), "D") {
      MsgBox, 0, Error, "%WatchedFolder%" isn't a valid folder name!
      Return
   }
   GuiControlGet, Caption, , Action
   If (Caption = "Start") {
      Watch := 0
      Watch |= Files ? 1 : 0
      Watch |= Folders ? 2 : 0
      Watch |= Attr ? 4 : 0
      Watch |= Size ? 8 : 0
      Watch |= Write ? 16 : 0
      Watch |= Access ? 32 : 0
      Watch |= Creation ? 64 : 0
      Watch |= Security ? 256 : 0
      If (Watch = 0) {
         GuiControl, , Files, 1
         GuiControl, , Folders, 1
         Watch := 3
      }
	  If !WatchFolder(WatchedFolder, "MyUserFunc", SubTree, Watch) {
         MsgBox, 0, Error, Call of WatchFolder() failed! You must specify a folder.
         Return
      }
	  If !WatchFolder(WatchedFolder2, "MyUserFunc", SubTree, Watch) {
         MsgBox, 0, Error, Call of WatchFolder() failed! You must specify a second folder.
         Return
      }
      GuiControl, , Action, Stop
      GuiControl, Disable, Select
      GuiControl, Enable, Pause
   }
   Else {
      WatchFolder(WatchedFolder, "**DEL")
      WatchFolder(WatchedFolder2, "**DEL")
	  GuiControl, , Action, Start
      GuiControl, Enable, Select
      GuiControl, Disable, Pause
   }
Return
; ----------------------------------------------------------------------------------------------------------------------------------
SelectFolder:
   FileSelectFolder, WatchedFolder
   If !(ErrorLevel) {
      GuiControl, +cDefault, WatchedFolder
      GuiControl, , WatchedFolder, %WatchedFolder%
      GuiControl, Enable, Action
   }
Return
; ----------------------------------------------------------------------------------------------------------------------------------
SelectFolder2:
   FileSelectFolder, WatchedFolder2
   If !(ErrorLevel) {
      GuiControl, +cDefault, WatchedFolder2
      GuiControl, , WatchedFolder2, %WatchedFolder2%
      GuiControl, Enable, Action
   }
Return
; ----------------------------------------------------------------------------------------------------------------------------------

MyUserFunc(Folder, Changes) {
   Static Actions := ["1 (added)", "2 (removed)", "3 (modified)", "4 (renamed)"]
   TickCount := A_TickCount
   GuiControl, -ReDraw, LV
   For Each, Change In Changes
      Name := Change.Name
	  StringLeft, NameOnly, Name, (InStr(Name, ".")-1)
	  StringRight, ExtOnly, Name, 3
	  SplitPath, Name, smallname
	  StringLeft, IsVA, smallname, 2
	  LV_Modify(LV_Add("", TickCount, Folder, Actions[Change.Action], Change.Name, Change.IsDir, Change.OldName, ""), "Vis")
		  ; Look out for a file being added to the folder.
		  If (Change.Action = 1) {
		  ;MsgBox Renaming File %Name% to %NameOnly%_%TickCount%.%ExtOnly% %smallname% %IsVA%
			  ; If the file begins with VA, then autorename it.
			  If (IsVA = "va") {
			  FileMove, %Name%, %NameOnly%_%TickCount%.%ExtOnly%  ; Rename a single file.
			  }
			}
   Loop, % LV_GetCount("Columns")
      LV_ModifyCol(A_Index, "AutoHdr")
   GuiControl, +Redraw, LV
}
Thank you for your time!
Nate

User avatar
TheArkive
Posts: 1027
Joined: 05 Aug 2016, 08:06
Location: The Construct
Contact:

Re: WatchFolder() - updated on 2016-11-30

Post by TheArkive » 23 Dec 2020, 14:11

:shock: I can't believe I'm just now seeing this. Nice job @just me!

Mind if I tinker with this and make an AHK v2 version? I might add a filter option too...


EDIT: Looks like Helgef beat me to it a while ago... i still might like to try and add a filtering option...

just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: WatchFolder() - updated on 2016-11-30

Post by just me » 24 Dec 2020, 06:11

@TheArkive, do whatever you want to do. I'm still believing that a filter option within the function isn't necessary.

nschoonover
Posts: 5
Joined: 09 Dec 2020, 09:09

Re: WatchFolder() - updated on 2016-11-30

Post by nschoonover » 30 Dec 2020, 07:58

Hi again!
I have fine-tuned an application of your script to monitor a folder constantly. If any file shows up there, it checks to see if the file begins with a certain string. If it does, then it appends a tick count to the filename. Thank you for making this possible!

Code: Select all

#NoEnv
#Warn
#Include WatchFolder.ahk
;SetBatchLines, -1 ; set this option to -1 to run full speed - i am not sure this is nessecary
WatchedFolder := "C:\Users\MyUser\Desktop\temp" ;change this to the folder you want to watch
; ----------------------------------------------------------------------------------------------------------------------------------
Gui, Margin, 20, 20
Gui, Add, Text, , Watching this folder for new files: %WatchedFolder%
Gui, Add, Text, , New files will be appended with a unique number if the filename begins with 'VA'.
Gui, Add, ListView, xm w800 r15 vLV, TickCount|Folder|Action|Name|OldName|%A_Space%
Gui, Add, Button, xm w800 wp gCLear, Clear
Gui, Show, , Watch Folder
GuiControl, Focus, Select
GuiControl, Enable, Action
goto StartStop
Return
; ----------------------------------------------------------------------------------------------------------------------------------
GuiClose:
ExitApp
; ----------------------------------------------------------------------------------------------------------------------------------
Clear:
   LV_Delete()
Return
; ----------------------------------------------------------------------------------------------------------------------------------
StartStop:
   Gui, +OwnDialogs
   Gui, Submit, NoHide
	 If !WatchFolder(WatchedFolder, "MyUserFunc", , 1) {
	 MsgBox, 0, Error, Call of WatchFolder() failed! You must specify a valid folder.
	 Return
	}
Return
; ----------------------------------------------------------------------------------------------------------------------------------
MyUserFunc(Folder, Changes) {
   Static Actions := ["1 (added)", "2 (removed)", "3 (modified)", "4 (renamed)"]
   TickCount := A_TickCount
   GuiControl, -ReDraw, LV
   For Each, Change In Changes
      Name := Change.Name
	  StringLeft, NameOnly, Name, (InStr(Name, ".")-1)
	  StringRight, ExtOnly, Name, 3
	  SplitPath, Name, smallname
	  StringLeft, IsVA, smallname, 2
	  LV_Modify(LV_Add("", TickCount, Folder, Actions[Change.Action], Change.Name, Change.OldName, ""), "Vis")
		  ; Look out for a file being added to the folder.
		  If (Change.Action = 1) {
			  ; If the file begins with VA, then autorename it.
			  If (IsVA = "va") {
			  FileMove, %Name%, %NameOnly%_%TickCount%.%ExtOnly%  ; Rename a single file.
			  }
			}
   Loop, % LV_GetCount("Columns")
      LV_ModifyCol(A_Index, "AutoHdr")
   GuiControl, +Redraw, LV
}

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: WatchFolder() - updated on 2016-11-30

Post by hasantr » 17 Jan 2021, 23:11

How it can be used most efficiently to monitor single files. Do we have to monitor the entire folder for a file?

just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: WatchFolder() - updated on 2016-11-30

Post by just me » 18 Jan 2021, 09:29

Yes, that's why it's called WatchFolder().

carno
Posts: 265
Joined: 20 Jun 2014, 16:48

Re: WatchFolder() - updated on 2016-11-30

Post by carno » 23 Jan 2021, 21:26

This script is great with the GUI; however, I would like a simple version so that I can get a simple " MsgBox, File Changed " or " MsgBox, Folder Changed " for a selected file or folder whenever a change is made to that file or folder.

User avatar
boiler
Posts: 16768
Joined: 21 Dec 2014, 02:44

Re: WatchFolder() - updated on 2016-11-30

Post by boiler » 24 Jan 2021, 08:03

@carno - The very first script in this thread is just the function (without a GUI or any other interface), which you can call and invoke your own function that simply shows a MsgBox as you would like. The example script with a GUI that follows is just that — an example. The function describes how to set up the various parameters with use with your own function. If you search the forum, you’ll find examples of other implementations, in case that helps.

carno
Posts: 265
Joined: 20 Jun 2014, 16:48

Re: WatchFolder() - updated on 2016-11-30

Post by carno » 25 Jan 2021, 14:48

@boiler - Thanks boiler, you are always very helpful.

carno
Posts: 265
Joined: 20 Jun 2014, 16:48

Re: WatchFolder() - updated on 2016-11-30

Post by carno » 26 Jan 2021, 08:00

I tested the following test script. It works fine for Actions 1, 2 and 4, but never for 3, which is "The file was modified" when I modify a file by changing the contents and saving it. What is the problem? Thanks.

Code: Select all

#Persistent
#NoEnv
#SingleInstance Force

WatchFolder("C:\Test", "ReportFunction")

; WatchFolder - Reportfunctions
ReportFunction(Directory, Changes) { 
 For Each, Change In Changes {
       Action := Change.Action
       Name := Change.Name
	   ; -------------------------------------------------------------------------------------------------------------------------
       ; Action 1 (added) = File gets added in the watched folder
	   If (Action = 1)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 2)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 3)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 4)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 5)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 6)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 7)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 8)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 16)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 32)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 64)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
	   If (Action = 256)
		 MsgBox % Name . "`n" . Action ;Run, %Name%
       }
}

just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: WatchFolder() - updated on 2016-11-30

Post by just me » 26 Jan 2021, 10:39

Code: Select all

;     Watch       -  The kind of changes to watch for. This can be one or any combination of the FILE_NOTIFY_CHANGES_...
;                    values specified below.
;                    Default: 0x03 - FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_DIR_NAME

Code: Select all

WatchFolder("C:\Test", "ReportFunction")
You're calling WatchFolder() for the default change notifications.

carno
Posts: 265
Joined: 20 Jun 2014, 16:48

Re: WatchFolder() - updated on 2016-11-30

Post by carno » 26 Jan 2021, 22:19

Thanks just me for replying to my post. This issue has come up 2-3 times in the past and I understand it has something to do with the Default: 0x03. The full GUI version works great with all actions (add file, delete file, modify file, change filename, etc.). All these actions work as expected. However, when I try the simple non-GUI version (just the function as I posted above) I run into problems (as reported in several other posts in page 3). I tried to replace 0x03 with 0x04 and this works with modify file and change filename, but other actions such as add file and delete file are disabled. I actually tried to replace the Default 0x03 throughout the script with 0x01, 0x02, 0x05, 0x06, 0x07, etc., and with these values nothing works at all. So my question is what is the appropriate value for 0x03 that works with all actions (add file, delete file, modify file, change name, etc.) the same way that it works with the full GUI version of this function when all boxes under Watch Changes: label are checked? I'm basically looking for a stripped-down non-GUI function-only version that works flawlessly with all actions (as though they are all checked in the GUI version) and I can confirm them with simple message boxes for notification.

HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: WatchFolder() - updated on 2016-11-30

Post by HotKeyIt » 26 Jan 2021, 23:14

Watch for all changes: WatchFolder("C:\Test", "ReportFunction", false, 1| 2 | 4 | 8 |16 | 32 | 64 | 256)

EDIT: added missing parameter, thanks @kczx3 .

carno
Posts: 265
Joined: 20 Jun 2014, 16:48

Re: WatchFolder() - updated on 2016-11-30

Post by carno » 27 Jan 2021, 07:39

Thank HotKeyIt. I tried your reply code in the following script, but still file modification is not working (only add, delete and change filename works). I am most interested in notifications for any changes to a file (file modifications):

Code: Select all

#NoEnv
#SingleInstance Force

WatchFolder("C:\Test", "ReportFunction", 1 | 2 | 4 | 8 |16 | 32 | 64 | 256)

ReportFunction(Folder, Changes) { 
 For Each, Change In Changes {
       Action := Change.Action
       Name := Change.Name
		 MsgBox % Name
		 MsgBox % Action
       }
}

; ==================================================================================================================================
; ==================================================================================================================================
; ==================================================================================================================================
; Function:       Notifies about changes within folders.
;                 This is a rewrite of HotKeyIt's WatchDirectory() released at
;                    http://www.autohotkey.com/board/topic/60125-ahk-lv2-watchdirectory-report-directory-changes/
; Tested with:    AHK 1.1.23.01 (A32/U32/U64)
; Tested on:      Win 10 Pro x64
; Usage:          WatchFolder(Folder, UserFunc[, SubTree := False[, Watch := 3]])
; Parameters:
;     Folder      -  The full qualified path of the folder to be watched.
;                    Pass the string "**PAUSE" and set UserFunc to either True or False to pause respectively resume watching.
;                    Pass the string "**END" and an arbitrary value in UserFunc to completely stop watching anytime.
;                    If not, it will be done internally on exit.
;     UserFunc    -  The name of a user-defined function to call on changes. The function must accept at least two parameters:
;                    1: The path of the affected folder. The final backslash is not included even if it is a drive's root
;                       directory (e.g. C:).
;                    2: An array of change notifications containing the following keys:
;                       Action:  One of the integer values specified as FILE_ACTION_... (see below).
;                                In case of renaming Action is set to FILE_ACTION_RENAMED (4).
;                       Name:    The full path of the changed file or folder.
;                       OldName: The previous path in case of renaming, otherwise not used.
;                       IsDir:   True if Name is a directory; otherwise False. In case of Action 2 (removed) IsDir is always False.
;                    Pass the string "**DEL" to remove the directory from the list of watched folders.
;     SubTree     -  Set to true if you want the whole subtree to be watched (i.e. the contents of all sub-folders).
;                    Default: False - sub-folders aren't watched.
;     Watch       -  The kind of changes to watch for. This can be one or any combination of the FILE_NOTIFY_CHANGES_...
;                    values specified below.
;                    Default: 0x03 - FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_DIR_NAME
; Return values:
;     Returns True on success; otherwise False.
; Change history:
;     1.0.02.00/2016-11-30/just me        -  bug-fix for closing handles with the '**END' option.
;     1.0.01.00/2016-03-14/just me        -  bug-fix for multiple folders
;     1.0.00.00/2015-06-21/just me        -  initial release
; License:
;     The Unlicense -> http://unlicense.org/
; Remarks:
;     Due to the limits of the API function WaitForMultipleObjects() you cannot watch more than MAXIMUM_WAIT_OBJECTS (64)
;     folders simultaneously.
; MSDN:
;     ReadDirectoryChangesW          msdn.microsoft.com/en-us/library/aa365465(v=vs.85).aspx
;     FILE_NOTIFY_CHANGE_FILE_NAME   = 1   (0x00000001) : Notify about renaming, creating, or deleting a file.
;     FILE_NOTIFY_CHANGE_DIR_NAME    = 2   (0x00000002) : Notify about creating or deleting a directory.
;     FILE_NOTIFY_CHANGE_ATTRIBUTES  = 4   (0x00000004) : Notify about attribute changes.
;     FILE_NOTIFY_CHANGE_SIZE        = 8   (0x00000008) : Notify about any file-size change.
;     FILE_NOTIFY_CHANGE_LAST_WRITE  = 16  (0x00000010) : Notify about any change to the last write-time of files.
;     FILE_NOTIFY_CHANGE_LAST_ACCESS = 32  (0x00000020) : Notify about any change to the last access time of files.
;     FILE_NOTIFY_CHANGE_CREATION    = 64  (0x00000040) : Notify about any change to the creation time of files.
;     FILE_NOTIFY_CHANGE_SECURITY    = 256 (0x00000100) : Notify about any security-descriptor change.
;     FILE_NOTIFY_INFORMATION        msdn.microsoft.com/en-us/library/aa364391(v=vs.85).aspx
;     FILE_ACTION_ADDED              = 1   (0x00000001) : The file was added to the directory.
;     FILE_ACTION_REMOVED            = 2   (0x00000002) : The file was removed from the directory.
;     FILE_ACTION_MODIFIED           = 3   (0x00000003) : The file was modified.
;     FILE_ACTION_RENAMED            = 4   (0x00000004) : The file was renamed (not defined by Microsoft).
;     FILE_ACTION_RENAMED_OLD_NAME   = 4   (0x00000004) : The file was renamed and this is the old name.
;     FILE_ACTION_RENAMED_NEW_NAME   = 5   (0x00000005) : The file was renamed and this is the new name.
;     GetOverlappedResult            msdn.microsoft.com/en-us/library/ms683209(v=vs.85).aspx
;     CreateFile                     msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx
;     FILE_FLAG_BACKUP_SEMANTICS     = 0x02000000
;     FILE_FLAG_OVERLAPPED           = 0x40000000
; ==================================================================================================================================
WatchFolder(Folder, UserFunc, SubTree := False, Watch := 0x03) {
   Static DummyObject := {Base: {__Delete: Func("WatchFolder").Bind("**END", "")}}
   Static TimerID := "**" . A_TickCount
   Static TimerFunc := Func("WatchFolder").Bind(TimerID, "")
   Static MAXIMUM_WAIT_OBJECTS := 64
   Static MAX_DIR_PATH := 260 - 12 + 1
   Static SizeOfLongPath := MAX_DIR_PATH << !!A_IsUnicode
   Static SizeOfFNI := 0xFFFF ; size of the FILE_NOTIFY_INFORMATION structure buffer (64 KB)
   Static SizeOfOVL := 32     ; size of the OVERLAPPED structure (64-bit)
   Static WatchedFolders := {}
   Static EventArray := []
   Static HandleArray := []
   Static WaitObjects := 0
   Static BytesRead := 0
   Static Paused := False
   ; ===============================================================================================================================
   If (Folder = "")
      Return False
   SetTimer, % TimerFunc, Off
   RebuildWaitObjects := False
   ; ===============================================================================================================================
   If (Folder = TimerID) { ; called by timer
      If (ObjCount := EventArray.Length()) && !Paused {
         ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", &WaitObjects, "Int", 0, "UInt", 0, "UInt")
         While (ObjIndex >= 0) && (ObjIndex < ObjCount) {
            FolderName := WatchedFolders[ObjIndex + 1]
            D := WatchedFolders[FolderName]
            If DllCall("GetOverlappedResult", "Ptr", D.Handle, "Ptr", D.OVLAddr, "UIntP", BytesRead, "Int", True) {
               Changes := []
               FNIAddr := D.FNIAddr
               FNIMax := FNIAddr + BytesRead
               OffSet := 0
               PrevIndex := 0
               PrevAction := 0
               PrevName := ""
               Loop {
                  FNIAddr += Offset
                  OffSet := NumGet(FNIAddr + 0, "UInt")
                  Action := NumGet(FNIAddr + 4, "UInt")
                  Length := NumGet(FNIAddr + 8, "UInt") // 2
                  Name   := FolderName . "\" . StrGet(FNIAddr + 12, Length, "UTF-16")
                  IsDir  := InStr(FileExist(Name), "D") ? 1 : 0
                  If (Name = PrevName) {
                     If (Action = PrevAction)
                        Continue
                     If (Action = 1) && (PrevAction = 2) {
                        PrevAction := Action
                        Changes.RemoveAt(PrevIndex--)
                        Continue
                     }
                  }
                  If (Action = 4)
                     PrevIndex := Changes.Push({Action: Action, OldName: Name, IsDir: 0})
                  Else If (Action = 5) && (PrevAction = 4) {
                     Changes[PrevIndex, "Name"] := Name
                     Changes[PrevIndex, "IsDir"] := IsDir
                  }
                  Else
                     PrevIndex := Changes.Push({Action: Action, Name: Name, IsDir: IsDir})
                  PrevAction := Action
                  PrevName := Name
               } Until (Offset = 0) || ((FNIAddr + Offset) > FNIMax)
               If (Changes.Length() > 0)
                  D.Func.Call(FolderName, Changes)
               DllCall("ResetEvent", "Ptr", EventArray[D.Index])
               DllCall("ReadDirectoryChangesW", "Ptr", D.Handle, "Ptr", D.FNIAddr, "UInt", SizeOfFNI, "Int", D.SubTree
                                              , "UInt", D.Watch, "UInt", 0, "Ptr", D.OVLAddr, "Ptr", 0)
            }
            ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", &WaitObjects, "Int", 0, "UInt", 0, "UInt")
            Sleep, 0
         }
      }
   }
   ; ===============================================================================================================================
   Else If (Folder = "**PAUSE") { ; called to pause/resume watching
      Paused := !!UserFunc
      RebuildObjects := Paused
   }
   ; ===============================================================================================================================
   Else If (Folder = "**END") { ; called to stop watching
      For K, D In WatchedFolders
         If K Is Not Integer
            DllCall("CloseHandle", "Ptr", D.Handle)
      For Each, Event In EventArray
         DllCall("CloseHandle", "Ptr", Event)
      WatchedFolders := {}
      EventArray := []
      Paused := False
      Return True
   }
   ; ===============================================================================================================================
   Else { ; called to add, update, or remove folders
      Folder := RTrim(Folder, "\")
      VarSetCapacity(LongPath, SizeOfLongPath, 0)
      If !DllCall("GetLongPathName", "Str", Folder, "Ptr", &LongPath, "UInt", SizeOfLongPath)
         Return False
      VarSetCapacity(LongPath, -1)
      Folder := LongPath
      If (WatchedFolders[Folder]) { ; update or remove
         Handle := WatchedFolders[Folder, "Handle"]
         Index  := WatchedFolders[Folder, "Index"]
         DllCall("CloseHandle", "Ptr", Handle)
         DllCall("CloseHandle", "Ptr", EventArray[Index])
         EventArray.RemoveAt(Index)
         WatchedFolders.RemoveAt(Index)
         WatchedFolders.Delete(Folder)
         RebuildWaitObjects := True
      }
      If InStr(FileExist(Folder), "D") && (UserFunc <> "**DEL") && (EventArray.Length() < MAXIMUM_WAIT_OBJECTS) {
         If (IsFunc(UserFunc) && (UserFunc := Func(UserFunc)) && (UserFunc.MinParams >= 2)) && (Watch &= 0x017F) {
            Handle := DllCall("CreateFile", "Str", Folder . "\", "UInt", 0x01, "UInt", 0x07, "Ptr",0, "UInt", 0x03
                                          , "UInt", 0x42000000, "Ptr", 0, "UPtr")
            If (Handle > 0) {
               Event := DllCall("CreateEvent", "Ptr", 0, "Int", 1, "Int", 0, "Ptr", 0)
               Index := EventArray.Push(Event)
               WatchedFolders[Index] := Folder
               WatchedFolders[Folder] := {Func: UserFunc, Handle: Handle, Index: Index, SubTree: !!SubTree, Watch: Watch}
               WatchedFolders[Folder].SetCapacity("FNIBuff", SizeOfFNI)
               FNIAddr := WatchedFolders[Folder].GetAddress("FNIBuff")
               DllCall("RtlZeroMemory", "Ptr", FNIAddr, "Ptr", SizeOfFNI)
               WatchedFolders[Folder, "FNIAddr"] := FNIAddr
               WatchedFolders[Folder].SetCapacity("OVLBuff", SizeOfOVL)
               OVLAddr := WatchedFolders[Folder].GetAddress("OVLBuff")
               DllCall("RtlZeroMemory", "Ptr", OVLAddr, "Ptr", SizeOfOVL)
               NumPut(Event, OVLAddr + 8, A_PtrSize * 2, "Ptr")
               WatchedFolders[Folder, "OVLAddr"] := OVLAddr
               DllCall("ReadDirectoryChangesW", "Ptr", Handle, "Ptr", FNIAddr, "UInt", SizeOfFNI, "Int", SubTree
                                              , "UInt", Watch, "UInt", 0, "Ptr", OVLAddr, "Ptr", 0)
               RebuildWaitObjects := True
            }
         }
      }
      If (RebuildWaitObjects) {
         VarSetCapacity(WaitObjects, MAXIMUM_WAIT_OBJECTS * A_PtrSize, 0)
         OffSet := &WaitObjects
         For Index, Event In EventArray
            Offset := NumPut(Event, Offset + 0, 0, "Ptr")
      }
   }
   ; ===============================================================================================================================
   If (EventArray.Length() > 0)
      SetTimer, % TimerFunc, -100
   Return (RebuildWaitObjects) ; returns True on success, otherwise False
}

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: WatchFolder() - updated on 2016-11-30

Post by kczx3 » 27 Jan 2021, 08:48

@carno
You're missing two things. First, you need to add #Persistent. Second, you are missing the third parameter to the WatchFolder function. You need to pass true/false as the third argument and then the listed of changes to watch afterwards.

WatchFolder("C:\Test", "ReportFunction", false, 1 | 2 | 4 | 8 |16 | 32 | 64 | 256)

MrDoge
Posts: 151
Joined: 27 Apr 2020, 21:29

Re: WatchFolder() - updated on 2016-11-30

Post by MrDoge » 27 Jan 2021, 08:54

you need to add #Persistent
ty, I didn't know that

Post Reply

Return to “Scripts and Functions (v1)”