Detect file change Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
icc2icc
Posts: 18
Joined: 04 Aug 2021, 21:24

Re: Detect file change

30 Jan 2022, 21:37

teadrinker wrote:
30 Jan 2022, 20:40
Can't reproduce such error.

Code: Select all

settimer , start, 10 
return
start:
Inst2 := new FileMonitoring(folderPath2, notifyFilter, "OnDirectoryChanged2")
Return
(of course I didn't do that fast refresh, but with 10 ms it errors out within seconds)

[Mod edit: Fixed quote... I guess.]
icc2icc
Posts: 18
Joined: 04 Aug 2021, 21:24

Re: Detect file change

30 Jan 2022, 21:58

teadrinker wrote:
30 Jan 2022, 20:40
If my class does not work perfectly for you, you can try another approach.
Thanks for the link
Would you be willing to explain me the differences between those 2 approaches?
I see you both use the ReadDirectoryChangesW API but can that other script usuch a uge ammount & not freeze
I tried to write 1000 from a script, it took about 3 sec & it cached every event & didn't miss even one, while the current script crashes at about 20 files

try this
@echo off
FOR /L %%a IN (1,1,1000) DO (
echo.. >test%%a.txt
)
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

30 Jan 2022, 22:16

It makes no sense to update the instance so often. Just all the actions in the __Delete() do not have time to work out.
icc2icc
Posts: 18
Joined: 04 Aug 2021, 21:24

Re: Detect file change

30 Jan 2022, 22:43

teadrinker wrote:
30 Jan 2022, 22:16
It makes no sense to update the instance so often. Just all the actions in the __Delete() do not have time to work out.
as I wrote
(of course I didn't do that fast refresh, but with 10 ms it errors out within seconds)
I did it every 5 seconds & I still got the same error, is it still too fast?

anyway, can you explain any advantages of your script over the other you linked to?
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

30 Jan 2022, 23:21

I don't know if my approach has advantages over others, I haven't compared them. It is of theoretical interest because it doesn't use timers for monitoring.
I did it every 5 seconds & I still got the same error
I think adding Sleep, 100 at the end of __Delete() can help.
icc2icc
Posts: 18
Joined: 04 Aug 2021, 21:24

Re: Detect file change

08 Feb 2022, 13:30

teadrinker wrote:
29 May 2020, 21:48
@Helgef
Appreciate your help, added what you suggested.
Spoiler

I just downloaded the script again & it crashes right away
(I use ahk ver. 1.1.33.09)
icc2icc
Posts: 18
Joined: 04 Aug 2021, 21:24

Re: Detect file change

08 Feb 2022, 13:52

Thanks for the quick reply
works as expected
:superhappy:
tester
Posts: 84
Joined: 10 Jun 2021, 23:03

Re: Detect file change

24 Sep 2022, 04:24

teadrinker wrote:
29 May 2020, 21:48
@Helgef
Appreciate your help, added what you suggested.

Code: Select all

#NoEnv
SetBatchLines -1

FILE_NOTIFY_CHANGE_FILE_NAME  := 0x1
FILE_NOTIFY_CHANGE_DIR_NAME   := 0x2
FILE_NOTIFY_CHANGE_ATTRIBUTES := 0x4
FILE_NOTIFY_CHANGE_SIZE       := 0x8

notifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME
              | FILE_NOTIFY_CHANGE_DIR_NAME
              | FILE_NOTIFY_CHANGE_ATTRIBUTES
              | FILE_NOTIFY_CHANGE_SIZE

folderPath1 := A_Desktop
folderPath2 := A_ScriptDir
              
Inst1 := new FileMonitoring(folderPath1, notifyFilter, "OnDirectoryChanged1", true)
Inst2 := new FileMonitoring(folderPath2, notifyFilter, "OnDirectoryChanged2")
Return

OnDirectoryChanged1(filePath, event) {
   MsgBox, % ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][event] ": " filePath
}

OnDirectoryChanged2(filePath, event) {
   MsgBox, % ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][event] ": " filePath
}

class FileMonitoring
{
   __New(folderPath, notifyFilter, UserFunc, watchSubtree := false) {
      this.Event := new this._Event()
      this.SetCapacity("buffer", 1024)
      pBuffer := this.GetAddress("buffer")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      this.pOverlapped := this.GetAddress("overlapped")
      this.Directory := new this._ReadDirectoryChanges( folderPath, notifyFilter, watchSubtree
                                                      , pBuffer, this.pOverlapped, this.Event.handle )
      this.EventSignal := new this._EventSignal(this.Directory, this.Event.handle, pBuffer, UserFunc)
      this.Directory.Read()
   }
   
   __Delete() {
      DllCall("CancelIoEx", "Ptr", this.Directory.handle, "Ptr", this.pOverlapped)
      this.Event.Set()
      this.EventSignal.Clear()
      this.Directory.Clear()
      this.SetCapacity("buffer", 0)
      this.buffer := ""
   }
   
   class _Event {
      __New() {
         this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 0, "Int", 0, "Ptr")
      }
      Set() {
         DllCall("SetEvent", "Ptr", this.handle)
      }
      __Delete() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _ReadDirectoryChanges {
      __New(dirPath, notifyFilter, watchSubtree, pBuffer, pOverlapped, hEvent) {
         static OPEN_EXISTING := 3
              , access := (FILE_SHARE_READ := 1) | (FILE_SHARE_WRITE := 2)
              , flags := (FILE_FLAG_OVERLAPPED := 0x40000000) | (FILE_FLAG_BACKUP_SEMANTICS := 0x2000000)
              
         for k, v in ["notifyFilter", "watchSubtree", "pBuffer", "pOverlapped", "hEvent"]
            this[v] := %v%
         this.handle := DllCall("CreateFile", "Str", dirPath, "UInt", 1, "UInt", access, "Int", 0
                                            , "UInt", OPEN_EXISTING, "UInt", flags, "Int", 0, "Ptr")
      }
      
      Read() {
         DllCall("RtlZeroMemory", "Ptr", this.pOverlapped, "Ptr", A_PtrSize*3 + 8)
         NumPut(this.hEvent, this.pOverlapped + A_PtrSize*2 + 8, "Ptr")
         ; there is only a Unicode version of this api
         Return DllCall("ReadDirectoryChangesW", "Ptr", this.handle, "Ptr", this.pBuffer, "UInt", 1024, "UInt", this.watchSubtree
                                               , "UInt", this.notifyFilter, "Ptr", 0, "Ptr", this.pOverlapped, "Ptr", 0)
      }
      
      Clear() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _EventSignal {
      __New(Directory, hEvent, pBuffer, UserFunc) {
         this.WM_EVENTSIGNAL := DllCall("RegisterWindowMessage", "Str", "WM_EVENTSIGNAL")
         for k, v in ["Directory", "hEvent", "pBuffer"]
            this[v] := %v%
         this.UserFunc := IsObject(UserFunc) ? UserFunc : Func(UserFunc)
         this.OnEvent := ObjBindMethod(this, "On_WM_EVENTSIGNAL")
         OnMessage(this.WM_EVENTSIGNAL, this.OnEvent)
         this.startAddress := this.CreateWaitFunc(this.hEvent, A_ScriptHwnd, this.WM_EVENTSIGNAL)
         this.Thread := new this._Thread(this.startAddress)
      }
      
      On_WM_EVENTSIGNAL(wp) {
         if !( wp = this.hEvent
            && DllCall("GetOverlappedResult", "Ptr", this.hEvent, "Ptr", this.pBuffer, "UIntP", written, "UInt", false) )
            Return
         
         addr := this.pBuffer
         offset := 0
         Loop {
            addr += offset
            eventType  := NumGet(addr + 4, "UInt")
            objectName := StrGet(addr + 12, NumGet(addr + 8, "UInt")//2, "UTF-16") ; always in Unicode
            timer := this.UserFunc.Bind(objectName, eventType)
            SetTimer, % timer, -10
         } until !offset := NumGet(addr + 0, "UInt")
         this.Thread.Wait()
         this.Thread := new this._Thread(this.startAddress)
         this.Directory.Read()
      }

      CreateWaitFunc(hEvent, hWnd, msg, timeout := -1) {
         params := ["UInt", MEM_COMMIT := 0x1000, "UInt", PAGE_EXECUTE_READWRITE := 0x40, "Ptr"]
         ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", A_PtrSize = 4 ? 49 : 85, params*)
         hModule      := DllCall("GetModuleHandle", "Str", "kernel32.dll", "Ptr")
         pWaitForObj  := DllCall("GetProcAddress" , "Ptr", hModule, "AStr", "WaitForSingleObject", "Ptr")
         hModule      := DllCall("GetModuleHandle", "Str", "user32.dll", "Ptr")
         pPostMessage := DllCall("GetProcAddress" , "Ptr", hModule, "AStr", "PostMessageW", "Ptr")
         NumPut(pWaitForObj , ptr + 0)
         NumPut(pPostMessage, ptr + A_PtrSize)

         if (A_PtrSize = 4) {
            NumPut(0x68   , ptr +  8)
            NumPut(timeout, ptr +  9)          , NumPut(0x68  , ptr + 13)
            NumPut(hEvent , ptr + 14)          , NumPut(0x15FF, ptr + 18)
            NumPut(ptr    , ptr + 20)          , NumPut(0x6850, ptr + 24)
            NumPut(hEvent , ptr + 26)          , NumPut(0x68  , ptr + 30)
            NumPut(msg    , ptr + 31)          , NumPut(0x68  , ptr + 35)
            NumPut(hWnd   , ptr + 36)          , NumPut(0x15FF, ptr + 40)
            NumPut(ptr + 4, ptr + 42)          , NumPut(0xC2  , ptr + 46, "UChar")
            NumPut(4      , ptr + 47, "UShort")
         }
         else {
            NumPut(0x53      , ptr + 16)
            NumPut(0x20EC8348, ptr + 17)        , NumPut(0xBACB8948, ptr + 21)
            NumPut(timeout   , ptr + 25)        , NumPut(0xB948    , ptr + 29)
            NumPut(hEvent    , ptr + 31)        , NumPut(0x15FF    , ptr + 39)
            NumPut(-45       , ptr + 41)        , NumPut(0xB849    , ptr + 45)
            NumPut(hEvent    , ptr + 47)        , NumPut(0xBA      , ptr + 55)
            NumPut(msg       , ptr + 56)        , NumPut(0xB948    , ptr + 60)
            NumPut(hWnd      , ptr + 62)        , NumPut(0xC18941  , ptr + 70)
            NumPut(0x15FF    , ptr + 73)        , NumPut(-71       , ptr + 75)
            NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B    , ptr + 83, "UShort")
         }
         Return ptr + A_PtrSize*2
      }
      
      class _Thread {
         __New(startAddress) {
            if !this.handle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
               throw Exception("Failed to create thread.`nError code: " . A_LastError)
         }
         Wait() {
            DllCall("WaitForSingleObject", "Ptr", this.handle, "Int", -1)
         }
         __Delete() {
            DllCall("CloseHandle", "Ptr", this.handle)
         }
      }
      
      Clear() {
         this.Thread.Wait()
         OnMessage(this.WM_EVENTSIGNAL, this.OnEvent, 0)
         this.OnEvent := ""
         DllCall("VirtualFree", "Ptr", this.startAddress - A_PtrSize*2, "Ptr", A_PtrSize = 4 ? 49 : 85, "UInt", MEM_DECOMMIT := 0x4000)
      }
   }
}
I'm wondering if it works on network folders as it doesn't in my environment. The WMI method posted earlier in this topic works well, however.
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

24 Sep 2022, 05:04

tester wrote: I'm wondering if it works on network folders
Only info about this issue I found is this one:
ReadDirectoryChangesW —> Return value wrote:If the network redirector or the target file system does not support this operation, the function fails with ERROR_INVALID_FUNCTION.
tester
Posts: 84
Joined: 10 Jun 2021, 23:03

Re: Detect file change

25 Sep 2022, 01:06

teadrinker wrote:
24 Sep 2022, 05:04
Only info about this issue I found is this one:
ReadDirectoryChangesW —> Return value wrote:If the network redirector or the target file system does not support this operation, the function fails with ERROR_INVALID_FUNCTION.
I see. So it depends on the environment. Thank you.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Araphen, Descolada, Joey5, JPMuir, matt101, Thorlian and 180 guests