Detect file change Topic is solved

Get help with using AutoHotkey and its commands and hotkeys
swagfag
Posts: 3617
Joined: 11 Jan 2017, 17:59

Re: Detect file change

22 May 2020, 12:40

yeah makes sense i suppose

Code: Select all

DWORD __fastcall ThreadProc(LPVOID lpParameter) {
    DWORD WaitEvent = WaitForSingleObject(Handle, Timeout);
    return (DWORD)SendMessageW(hWnd, Msg, (WPARAM)Handle, WaitEvent);
}
teadrinker
Posts: 1458
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

22 May 2020, 14:11

I've added GetOverlappedResult.

Code: Select all

#NoEnv
SetBatchLines -1

folderPath1 := A_Desktop
folderPath2 := A_ScriptDir

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
              
OnDirectoryChanges(folderPath1, "UserFunc", notifyFilter)
OnDirectoryChanges(folderPath2, "UserFunc", notifyFilter)

UserFunc(filePath, action) {
   MsgBox % A_ThisFunc . "`n" . ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][action] ": " filePath
}

OnDirectoryChanges(dirPath, procName := -1, notifyFilter := 3) {
   static DirList := {}, EventList, WM_ReadDirectoryChanges := 0xFFF

   IsObject(EventList) || EventList := DirectoryChangesEventListener(-1, 0, 0)

   if (procName = -1) {
      return EventList[DirList[dirPath]].UserDefinedProc.Name
   }
   else if (procName = "") {
      if DirList.HasKey(dirPath) && eventHandle := DirList.Delete(dirPath)
         return EventList.Delete(eventHandle).UserDefinedProc.Name
   }
   else if IsFunc(procName) {
      if DirList.HasKey(dirPath) && eventHandle := DirList.Delete(dirPath)
         priorProcName := EventList.Delete(eventHandle).UserDefinedProc.Name
      else priorProcName := procName

      if OnMessage(WM_ReadDirectoryChanges) != "DirectoryChangesEventListener"
         OnMessage(WM_ReadDirectoryChanges, "DirectoryChangesEventListener")

      Event := new _Event
      Event.Proc := new _AsyncReadDirectoryChanges(dirPath, notifyFilter)
      Event.Wait := new _AsyncWaitFunc(Event.handle, A_ScriptHWND, WM_ReadDirectoryChanges)
      Event.UserDefinedProc := Func(procName)
      Event.dirPath := dirPath

      DirList[dirPath] := Event.handle
      EventList[Event.handle] := Event

      Event.Proc()
      Event.Wait()

      return priorProcName
   }
   else return false
}

DirectoryChangesEventListener(wParam, lParam, msg) {
   static EventList := {}, WM_ReadDirectoryChanges := 0xFFF
   if (msg = WM_ReadDirectoryChanges && Event := EventList[wParam]) {
      if !DllCall("GetOverlappedResult", "Ptr", Event.Proc.handle, "Ptr", Event.Proc.GetAddress("buffer"), "UIntP", written, "UInt", true)
         Return
      action := Event.Proc.GetEventType()
      name := Event.Proc.GetObjectName()
      path := Event.dirPath . "\" . name
      Proc := Event.UserDefinedProc
      %Proc%(path, action)

      Event.Proc()
      Event.Wait()
   }
   else if (wParam = -1)
      return EventList
}

class _Event
{
   __New() {
      this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 1, "Int", 0, "Ptr")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      addr := this.GetAddress("overlapped")
      DllCall("RtlZeroMemory", "Ptr", addr, "Ptr", A_PtrSize*3 + 8)
      NumPut(this.handle, addr + A_PtrSize*2 + 8, "Ptr")
   }

   __Delete() {
      DllCall("CloseHandle", "Ptr", this.handle)
   }
}

class _AsyncWaitFunc
{
   __New(eventObjHandle, hWnd, msg, timeout := -1) {
      if !this.startAddress := CreateWaitFunc(eventObjHandle, hWnd, msg, timeout)
         throw Exception("Failed to create wait function.`n`nError code:`t" . A_LastError)
   }

   __Call(EventObj) {
      if IsObject(EventObj) {
         if this.threadHandle {
            DllCall("TerminateThread", "Ptr", this.threadHandle, "UInt", 0)
            DllCall("CloseHandle", "Ptr", this.threadHandle)
         }
         if !this.threadHandle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", this.startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
            throw Exception("Failed to create thread.`n`nError code:`t" . A_LastError)
         return this.threadHandle
      }
   }
   
   __Delete() {
      if this.threadHandle {
         DllCall("TerminateThread", "Ptr", this.threadHandle, "UInt", 0)
         DllCall("CloseHandle", "Ptr", this.threadHandle)
      }
   }
}

class _AsyncReadDirectoryChanges
{
   __New(dirPath, notifyFilter) {
     static FILE_SHARE_READ  := 1
          , FILE_SHARE_WRITE := 2
          , OPEN_EXISTING    := 3
          , FILE_FLAG_OVERLAPPED       := 0x40000000
          , FILE_FLAG_BACKUP_SEMANTICS := 0x02000000
      this.SetCapacity("buffer", 1024)
      this.notifyFilter := notifyFilter
      this.handle := DllCall("CreateFile", "Str", dirPath, "UInt", 1, "UInt", FILE_SHARE_READ|FILE_SHARE_WRITE, "Int", 0
                                                                    , "UInt", OPEN_EXISTING
                                                                    , "UInt", FILE_FLAG_OVERLAPPED|FILE_FLAG_BACKUP_SEMANTICS, "Int", 0, "Ptr")
   }

   GetEventType() {
      return NumGet(this.GetAddress("buffer") + 4,  "UInt")
   }

   GetObjectName() {
      bufferRef := this.GetAddress("buffer")
      return StrGet(bufferRef + 12, NumGet(bufferRef + 8, "UInt")//2)
   }

   __Call(EventObj) {
      if IsObject(EventObj) {
         return DllCall("ReadDirectoryChanges", "Ptr", this.handle, "Ptr", this.GetAddress("buffer"), "UInt", 1024, "Int", 1
                                              , "UInt", this.notifyFilter, "UInt", 0, "Ptr", EventObj.GetAddress("overlapped"), "Int", 0)
      }
   }

   __Delete() {
      DllCall("CloseHandle", "Ptr", this.handle)
   }
}

CreateWaitFunc(Handle, hWnd, Msg, Timeout=-1)
{
   static MEM_COMMIT := 0x1000, PAGE_EXECUTE_READWRITE := 0x40
   ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", A_PtrSize = 4 ? 49 : 85, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
   hModule := DllCall("GetModuleHandle", "Str", "kernel32.dll", "Ptr")
   pWaitForSingleObject := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "WaitForSingleObject", "Ptr")
   hModule := DllCall("GetModuleHandle", "Str", "user32.dll", "Ptr")
   pSendMessageW := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "SendMessageW", "Ptr")
   NumPut(pWaitForSingleObject, ptr*1)
   NumPut(pSendMessageW, ptr + A_PtrSize)
   if (A_PtrSize = 4)  {
      NumPut(0x68, ptr + 8, "UChar")
      NumPut(Timeout, ptr + 9, "UInt"), NumPut(0x68, ptr + 13, "UChar")
      NumPut(Handle, ptr + 14), NumPut(0x15FF, ptr + 18, "UShort")
      NumPut(ptr, ptr + 20), NumPut(0x6850, ptr + 24, "UShort")
      NumPut(Handle, ptr + 26), NumPut(0x68, ptr + 30, "UChar")
      NumPut(Msg, ptr + 31, "UInt"), NumPut(0x68, ptr + 35, "UChar")
      NumPut(hWnd, ptr + 36), NumPut(0x15FF, ptr + 40, "UShort")
      NumPut(ptr+4, ptr + 42), NumPut(0xC2, ptr + 46, "UChar"), NumPut(4, ptr + 47, "UShort")
   }
   else  {
      NumPut(0x53, ptr + 16, "UChar")
      NumPut(0x20EC8348, ptr + 17, "UInt"), NumPut(0xBACB8948, ptr + 21, "UInt")
      NumPut(Timeout, ptr + 25, "UInt"), NumPut(0xB948, ptr + 29, "UShort")
      NumPut(Handle, ptr + 31), NumPut(0x15FF, ptr + 39, "UShort")
      NumPut(-45, ptr + 41, "UInt"), NumPut(0xB849, ptr + 45, "UShort")
      NumPut(Handle, ptr + 47), NumPut(0xBA, ptr + 55, "UChar")
      NumPut(Msg, ptr + 56, "UInt"), NumPut(0xB948, ptr + 60, "UShort")
      NumPut(hWnd, ptr + 62), NumPut(0xC18941, ptr + 70, "UInt")
      NumPut(0x15FF, ptr + 73, "UShort"), NumPut(-71, ptr + 75, "UInt")
      NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B, ptr + 83, "UShort")
   }
   Return ptr + A_PtrSize*2
}
What else could be done to improve safety? Can TerminateThread be replaced with somewhat without CreateWaitFunc rewriting?
Helgef
Posts: 4298
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

23 May 2020, 02:19

The best way to terminate a thread is to let it finish. You can signal the event (SetEvent) so the wait (WaitForSingleObject) end.

Cheers.
teadrinker
Posts: 1458
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

23 May 2020, 20:40

Thanks! Damn, I had to guess myself! :headwall: I will post the code later.
malcev
Posts: 526
Joined: 12 Aug 2014, 12:37

Re: Detect file change

23 May 2020, 21:25

swagfag wrote:
21 May 2020, 23:56
what does this do? post the source
The code made by Александр_.

Code: Select all

; Функция потока
; Ожидает возврата из WaitForSingleObject и отправляет сообщение окну
; lParam содержит возвращаемое WaitForSingleObject значение
VarSetCapacity(springboard, 38)
NumPut(0x68, springboard, 0, "Char")
NumPut(INFINITE, springboard, 1)
;push -1
NumPut(0x68, springboard, 5, "Char")
;NumPut(hChangeHandle, springboard, 6)
;push hChangeHandle
NumPut(0x15ff, springboard, 10, "Short")
NumPut(&WaitForSingleObjectAddr, springboard, 12)
;call WaitForsingleObject
NumPut(0x50, springboard, 16, "Char")
;push EAX
NumPut(0x6A, springboard, 17, "Char")
NumPut(0x00, springboard, 18, "Char")
;push 0
NumPut(0x68, springboard, 19, "Char")
NumPut(WM_USER, springboard, 20)
;push 0x400
NumPut(0x68, springboard, 24, "Char")
NumPut(hWnd, springboard, 25)
;push hWnd
NumPut(0x15ff, springboard, 29, "Short")
NumPut(&PostMessageAddr, springboard, 31)
;Call PostMessage
NumPut(0xC2, springboard, 35,"char")
NumPut(4, springboard, 36,"short")
;ret 4
http://forum.script-coding.com/viewtopic.php?id=6273

Also there is another code by YMP:

Code: Select all

DirName := A_ScriptDir          ; Папка для слежения.
LogFile := A_Desktop . "\DirLog.txt"

F10:: WatchDirectory(DirName)   ; Начать слежение.
F11:: WatchDirectory(0)         ; Остановить.


; ================ Функции =====================================================

WM_DIRECTORYCHANGE(BytesReturned, pOutBuf)
{
    Global LogFile
    ;FILE_ACTION_ADDED := 1, FILE_ACTION_REMOVED := 2, FILE_ACTION_MODIFIED := 3
    ;FILE_ACTION_RENAMED_OLD_NAME := 4, FILE_ACTION_RENAMED_NEW_NAME := 5
    Static Actions := ["Файл добавлен:  ", "Файл удалён:    ", "Файл изменён:   "
                     , "Файл переименован с имени: ", "Файл переименован на имя:  "]
    If (pOutBuf = 0) {
        MsgBox, Ошибка в ReadDirectoryChangeW
        Return
    }
    DateTime := A_DD "." A_MM "." A_YYYY " " A_Hour ":" A_Min ":" A_Sec
    Addr := pOutBuf, Next := 0  ; Адрес текущей записи и смещение до следующей.
    Loop    ; Чтение из буфера записей о событиях и реакция на них.
    {
        Addr += Next, Next := NumGet(Addr+0, 0, "uint") ; Смещение следующей записи.
        ActionCode := NumGet(Addr+0, 4, "uint") ; Код события (см. в начале функции).
        If (Action := Actions[ActionCode]) {    ; Описание события из массива.
            cbFile := NumGet(Addr+0, 8, "uint") ; Длина имени файла в байтах.
            FileName := StrGet(Addr+12, cbFile // 2, "utf-16")
            Msg .= DateTime " " Action FileName "`n"
        }
        If (!Next)  ; Если смещение равно 0, больше записей в буфере нет.
            Break
    }
    If (Msg) {
        ToolTip, %Msg%
        If (LogFile)
            FileAppend, %Msg%, %LogFile%
        Sleep, 1000
        ToolTip
    }
    WatchDirectory(-1)  ; Продолжить слежение.
}

WatchDirectory(DirName)
{
    Static hDir, hThread, pData, pThreadStart, OutBuf, OutBufSize := 0x400  ; 1 KB
    Static BytesReturned, WM_DIRECTORYCHANGE := 0x401
    ;FILE_NOTIFY_CHANGE_FILE_NAME := 0x1, FILE_NOTIFY_CHANGE_DIR_NAME := 0x2
    ;FILE_NOTIFY_CHANGE_ATTRIBUTES := 0x4, FILE_NOTIFY_CHANGE_SIZE := 0x8
    ;FILE_NOTIFY_CHANGE_LAST_WRITE := 0x10, FILE_NOTIFY_CHANGE_LAST_ACCESS := 0x20
    ;FILE_NOTIFY_CHANGE_CREATION := 0x40, FILE_NOTIFY_CHANGE_SECURITY := 0x100
    Static NotifyFilter := 0x11     ; Комбинация из флагов выше (сумма).
    If (DirName = -1) {
        DllCall("CloseHandle", "ptr", hThread)
        Goto NewThread
    }
    Else If (DirName = 0) {
        If (hThread) {
            DllCall("TerminateThread", "ptr", hThread, "int", 0)
            DllCall("CloseHandle", "ptr", hThread), hThread := 0
        }
        If (hDir)
            DllCall("CloseHandle", "ptr", hDir), hDir := 0
        Return
    }
    If (hDir)
        WatchDirectory(0) ; Остановить текущее слежение.
    If (!OutBuf) {
        VarSetCapacity(OutBuf, OutBufSize, 0), VarSetCapacity(BytesReturned, 4, 0)
        OnMessage(WM_DIRECTORYCHANGE, "WM_DIRECTORYCHANGE")
        If !(pReadDirectoryChanges := GetProcAddress("kernel32.dll", "ReadDirectoryChangesW"))
            Return Error("GetProcAddress - ReadDirectoryChangesW")
        If !(pPostMessage := GetProcAddress("user32.dll", "PostMessage" . (A_IsUnicode? "W":"A")))
            Return Error("GetProcAddress - PostMessage")
        If !(pThreadStart := CreateMachineFunc())
            Return Error("CreateMachineFunc")
        pData := CreateStruct(pReadDirectoryChanges, hDir, &OutBuf, OutBufSize, 0
                            , NotifyFilter, &BytesReturned, 0, 0
                            , pPostMessage, A_ScriptHwnd, WM_DIRECTORYCHANGE)
    }
    If !(hDir := OpenDirectory(DirName))
        Return Error("OpenDirectory")
    NumPut(hDir, pData+0, A_PtrSize, "ptr")
NewThread:
    If !(hThread := CreateThread(pThreadStart, pData))
        Return Error("CreateThread")
    Return True
}

OpenDirectory(Dir)
{
    Static FILE_LIST_DIRECTORY := 1, FILE_SHARE_READ := 1, FILE_SHARE_WRITE := 2
    Static OPEN_EXISTING := 3, FILE_FLAG_BACKUP_SEMANTICS := 0x02000000
    Static INVALID_HANDLE_VALUE := -1
    hDir := DllCall("CreateFile", "str", Dir, "uint", FILE_LIST_DIRECTORY
            , "uint", FILE_SHARE_READ | FILE_SHARE_WRITE, "ptr", 0, "uint", OPEN_EXISTING
            , "uint", FILE_FLAG_BACKUP_SEMANTICS, "ptr", 0, "ptr")
    Return hDir = INVALID_HANDLE_VALUE? 0:hDir      
}

CreateStruct(Members*)
{
    Static Struct
    cMembers := Members.MaxIndex()
    VarSetCapacity(Struct, cMembers * A_PtrSize, 0)
    addr := &Struct
    Loop, %cMembers%
        addr := NumPut(Members[A_Index], addr+0, 0, "ptr")
    Return &Struct
}

GetProcAddress(Lib, Func)
{
    hLib := DllCall("LoadLibrary", "str", Lib, "ptr")
    If (hLib = 0)
        Return 0
    Return DllCall("GetProcAddress", "ptr", hLib, "astr", Func, "ptr")
}

CreateMachineFunc()
{
    MEM_RESERVE := 0x2000, MEM_COMMIT := 0x1000, PAGE_EXECUTE_READWRITE := 0x40
    If (A_PtrSize = 8) {
        Hex = 
        ( Join LTrim
        488B0151FF7140FF7138FF7130FF71284883EC204C8B49204C8B4118488B5110488B4908FFD04
        C8B5424404D31C985C04D0F454A10498B4230448B00498B5258498B4A5041FF52484883C448C3
        )
    }
    Else {
        Hex =
        ( Join LTrim
        8B54240452FF7220FF721CFF7218FF7214FF7210FF720CFF7208FF7204FF125A85C00F4542088
        B4A1850FF31FF722CFF7228FF5224C20400
        )
    }
    Len := StrLen(Hex) // 2
    pFunc := DllCall("VirtualAlloc", "ptr", 0, "ptr", Len
                                   , "uint", MEM_RESERVE | MEM_COMMIT
                                   , "uint", PAGE_EXECUTE_READWRITE, "ptr")
    If (pFunc = 0)
        Return 0
    Loop, % Len
        NumPut("0x" . SubStr(Hex, A_Index * 2 - 1, 2), pFunc + 0
                                 , A_Index - 1, "uchar")
    Return pFunc
}

CreateThread(StartAddr, Param)
{
    Return DllCall("CreateThread", "ptr", 0, "ptr", 0, "ptr", StartAddr
                                 , "ptr", Param, "uint", 0, "ptr", 0, "ptr")
}

Error(Func)
{
    MsgBox, %Func% failed.
    Return False
}
Machine code GoAsm:

Code: Select all

#if x64

    mov rax,[rcx]
    push rcx,[rcx+40h],[rcx+38h],[rcx+30h],[rcx+28h]
    sub rsp,20h
    mov r9,[rcx+20h]
    mov r8,[rcx+18h]
    mov rdx,[rcx+10h]
    mov rcx,[rcx+8h]
    call rax            ; Вызов ReadDirectoryChangeW
    mov r10,[rsp+40h]
    xor r9,r9
    test eax,eax
    cmovnz r9,[r10+10h]
    mov rax,[r10+30h]
    mov r8d,[rax]
    mov rdx,[r10+58h]
    mov rcx,[r10+50h]
    call [r10+48h]      ; Вызов PostMessage
    add rsp,48h
    ret

#else

    mov edx,[esp+4]
    push edx
    push [edx+20h],[edx+1Ch],[edx+18h],[edx+14h],[edx+10h],[edx+0Ch],[edx+8h],[edx+4h]
    call [edx]
    pop edx
    test eax,eax
    cmovnz eax,[edx+8h]
    mov ecx,[edx+18h]
    push eax,[ecx],[edx+2Ch],[edx+28h]
    call [edx+24h]
    ret 4

#endif
Helgef
Posts: 4298
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

23 May 2020, 23:25

The code by YMP is not safe, but can probably be made safe by calling CancelIoEx appropriately. It can only watch one directory at a time.

Edit, I think the simplest way to use ReadDirectoryChangeW is to specify a completion routine. I will write a function for v2 to demonstrate when I get time.

Return to “Ask For Help”

Who is online

Users browsing this forum: Albireo, Bing [Bot], dtsmarin2, Gavnar, Google [Bot], pasqualedk, Rohwedder, VIJJU and 67 guests