WatchFolder() - updated on 2021-10-14

Post your working scripts, libraries and tools for AHK v1.1 and older
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

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

02 Sep 2019, 01:52

boiler wrote:
01 Sep 2019, 22:07
You can build the list with a loop using the same UserFunc for all of them. The return parameters will identify what changed and how, so you can sort out the feedback in your user function. I’m not seeing what you are trying to get out of it with your “d” variable in your example.
Oh yes. Too bad I didn't think it would work like that. In this case, the problem is solved. Thank you very much.

Also:

Is there a way to get the old name of a renamed file? (just getting me noted below) I didn't want to write another message again and make it crowded. Thanks just me.
just me wrote:
02 Sep 2019, 04:32
Yes, I'd suggest to read the description at the top of the script. ;)
Last edited by hasantr on 05 Sep 2019, 03:18, edited 2 times in total.
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

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

02 Sep 2019, 04:32

Yes, I'd suggest to read the description at the top of the script. ;)
User avatar
SirSocks
Posts: 360
Joined: 26 Oct 2018, 08:14

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

06 Dec 2019, 10:06

absolutely perfect! WatchFolder() is an amazing function! :bravo:
LeSeb
Posts: 17
Joined: 25 Oct 2017, 14:50

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

26 Jun 2020, 02:05

Thanks for this great function! :D

I'm using it here to squeeze pdf's (using coherent pdf) as soon as i automatically extract them from my mail software, which is Thunderbird.

Great function that i also have to study, i'm sure to learn a lot that way!
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

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

08 Oct 2020, 07:58

Is there any way to extract a watched folder from watching?
User avatar
boiler
Posts: 16767
Joined: 21 Dec 2014, 02:44

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

08 Oct 2020, 08:04

Per the instructions (UserFunc parameter):
Pass the string "**DEL" to remove the directory from the list of watched folders.
MrDoge
Posts: 151
Joined: 27 Apr 2020, 21:29

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

25 Nov 2020, 19:54

I am making a dual-pane file manager in ahk and it seems that is it freezing randomly.
after doing some tests (I intentionally did not avoid duplicates by stop watching):

It FREEZES forever if I do:

Code: Select all

#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#SingleInstance, force
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.

gui, add, text,, foobar
gui, show, w400 h200

startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3")
startWatchFolder("C:\Users\Public\AHK\notes\tests")
startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3")
startWatchFolder("C:\Users\Public\AHK\notes\tests")
;Freezes forever
return  


startWatchFolder(WatchedFolder)
{
    global
    If !WatchFolder(WatchedFolder, "Watch1", 0, 3) { ;files and folders
        ; If !WatchFolder(WatchedFolder, "Watch" whichSide, 0, 3) { ;files and folders
        MsgBox, 0, Error, Call of WatchFolder() failed!
        Return
    }
}
stopWatchFolder(WatchedFolder) 
{
    global
    WatchFolder(WatchedFolder, "**DEL")
}
Watch1(Folder, Changes)
{

}

; ==================================================================================================================================
; 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
}

return

f3::Exitapp
it's because of duplicates, because calling stopWatchFolder IMMEDIATELY AFTER each startWatchFolder fixes the FREEZE.

if I stopWatchFolder "after", but not "IMMEDIATELY AFTER" each startWatchFolder, it also FREEZES.

so if there are stuff in between, it freezes:
here, there are stuff in between
startWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;not stopped
and
stopWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;closes something in the past (or something not started immediately before)

Code: Select all

startWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;not stopped
; stopWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;either one of these fixes the problem

;right pane

startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3")

stopWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3")
startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3\New Folder")

stopWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3\New Folder")
startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3") ;not stopped
; stopWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3") ;either one of these fixes the problem

;left pane

stopWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;closes something in the past (or something not started immediately before)
here is the FULL example :

Code: Select all

#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#SingleInstance, force
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.

gui, add, text,, foobar
gui, show, w400 h200
;first
;left pane
startWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher")

stopWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher")
startWatchFolder("C:\Users\Public\AHK\notes\tests")

stopWatchFolder("C:\Users\Public\AHK\notes\tests")
startWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;not stopped
; stopWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;either one of these fixes the problem

;right pane

startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3")

stopWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3")
startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3\New Folder")

stopWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3\New Folder")
startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3") ;not stopped
; stopWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3") ;either one of these fixes the problem

;left pane

stopWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;closes something in the past (or something not started immediately before)
startWatchFolder("C:\Users\Public\AHK\notes\tests")

stopWatchFolder("C:\Users\Public\AHK\notes\tests")
startWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher") ;not stopped

;right pane

stopWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3") ;closes something in the past (or something not started immediately before)
startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3\New Folder")

stopWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3\New Folder")
startWatchFolder("C:\Users\Public\AHK\notes\tests\New Folder 3")
;Freezes forever
return  


startWatchFolder(WatchedFolder)
{
    global
    If !WatchFolder(WatchedFolder, "Watch1", 0, 3) { ;files and folders
        ; If !WatchFolder(WatchedFolder, "Watch" whichSide, 0, 3) { ;files and folders
        MsgBox, 0, Error, Call of WatchFolder() failed!
        Return
    }
}
stopWatchFolder(WatchedFolder) 
{
    global
    WatchFolder(WatchedFolder, "**DEL")
}
Watch1(Folder, Changes)
{

}

; ==================================================================================================================================
; 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
}

return

f3::Exitapp

I have tried WatchDirectory by HotKeyIt : https://autohotkey.com/board/topic/60125-ahk-lv2-watchdirectory-report-directory-changes/
it doesn't freeze
but when I stop watching a folder, I cannot restart watching it, it just doesn't work anymore.

Code: Select all

startWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher")
stopWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher")
startWatchFolder("C:\Users\Public\AHK\notes\tests\File Watcher")

I can try using 3 scripts and make them communicate. (to avoid the in betweens)
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

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

25 Nov 2020, 20:44

Why would you need to “watch” the same directory more than once?
MrDoge
Posts: 151
Joined: 27 Apr 2020, 21:29

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

26 Nov 2020, 09:45

MrDoge wrote:
25 Nov 2020, 19:54
I am making a dual-pane file manager in ahk
so I need to watch the Dir I am currently in

if I go to parent Dir then comes back, I need to rewatch
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

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

26 Nov 2020, 11:35

TBH, I do not know what happens in the background when you call an asynch ReadDirectoryChangesW in combination with WaitForMultipleObjects. But IMO it makes no sense to try to register the same folder using the same options more than once at the same time.

Your "FULL" example doesn't contain any reasonable code. So its hardly possibe to help you.
MrDoge
Posts: 151
Joined: 27 Apr 2020, 21:29

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

26 Nov 2020, 12:01

just me wrote:
26 Nov 2020, 11:35
But IMO it makes no sense to try to register the same folder using the same options more than once at the same time.
well it freezes even if I put delays in between,
the FULL example is a real use case
in the real use, there are delays between changes of Directory (in a file manager, so I stop watching the Dir I just left, and start watching the Dir I go to)


I think that WatchDirectory by HotKeyIt doesn't freeze because
it doesn't let me rewatch a folder
even if I stopped watching the folder

but I need to rewatch a folder... so this isn't helpful


I will try using 3 scripts and make them communicate using WM_COPYDATA_READ
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

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

26 Nov 2020, 12:16

MrDoge wrote:... the FULL example is a real use case ...
It contains absolutely nothing showing how you process the data.
MrDoge
Posts: 151
Joined: 27 Apr 2020, 21:29

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

26 Nov 2020, 12:29

process what/which data ?

the paths don't make sense to you, you can replace them with your own to reproduce the freeze.

There are 4 paths, you should be able to find and replace all. (any 4 different paths should work)
In the left pane, I switch between
"C:\Users\Public\AHK\notes\tests\File Watcher" and "C:\Users\Public\AHK\notes\tests"

In the right pane, I switch between
"C:\Users\Public\AHK\notes\tests\New Folder 3" and "C:\Users\Public\AHK\notes\tests\New Folder 3\New Folder"

In this example, I wanted to show that it freezes when I do this SEQUENCE/ORDER of "start and stop watch folders" (regardless of delay in between)
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

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

26 Nov 2020, 12:45

Your example is calling startWatching on the same folder twice before calling stopWatching. Seems like if you don’t do that you’d be fine...
MrDoge
Posts: 151
Joined: 27 Apr 2020, 21:29

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

26 Nov 2020, 12:58

can you please tell me which lines ? which path ?
I have not spotted it
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

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

26 Nov 2020, 14:11

The very first code block in your first post...
MrDoge
Posts: 151
Joined: 27 Apr 2020, 21:29

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

26 Nov 2020, 14:20

couldn't you say it was the first code block ? I searched in the full example...
MrDoge wrote:
25 Nov 2020, 19:54
after doing some tests (I intentionally did not avoid duplicates by stop watching):
In my full example, I am not calling startWatching on the same folder twice before calling stopWatching
and it still freezes
that was what I was trying to show
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

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

26 Nov 2020, 17:19

If you want support, support us with reasonable code. And switch to the "Ask for Help" section.
n00b
Posts: 29
Joined: 24 May 2016, 16:42

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

04 Dec 2020, 20:39

Hi, can someone plz help me change the WatchFolder_sample? I want the GUI to start completely hidden, it should read the folders to watch from a ini file. In case a change (create, delete, change) occurs in a watched folder the GUI should pop up. Thanks. Edit: I guess figured it out myself, used TrayMinimizer function by evilc, also big thanks fly out to @just me for this very useful piece of art.
nschoonover
Posts: 5
Joined: 09 Dec 2020, 09:09

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

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
Last edited by gregster on 09 Dec 2020, 09:26, edited 1 time in total.
Reason: Links fixed.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 109 guests