监测文件(夹)知识汇总

自动化操作的基础就是感知,对文件(夹)的监测尤为重要,文件夹发生变化时往往最重要的触发契机。

下面我把相关的知识分享在这里供大家学习。

AutoHotkey本身不提供这个功能,但是系统提供了函数接口,我们可以是使用dllcall调用。

ReadDirectoryChangesW函数(winbase.h)

检索描述指定目录中的更改的信息。该函数不报告对指定目录本身的更改。

BOOL ReadDirectoryChangesW(
  HANDLE                          hDirectory,
  LPVOID                          lpBuffer,
  DWORD                           nBufferLength,
  BOOL                            bWatchSubtree,
  DWORD                           dwNotifyFilter,
  LPDWORD                         lpBytesReturned,
  LPOVERLAPPED                    lpOverlapped,
  LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

参量

hDirectory

要监视的目录的句柄。此目录必须以打开 FILE_LIST_DIRECTORY访问权限,或访问权限这样GENERIC_READ包含FILE_LIST_DIRECTORY访问权限。

lpBuffer

指向DWORD对齐格式的缓冲区的指针,将在其中返回读取结果。此缓冲区的结构由FILE_NOTIFY_INFORMATION结构定义 。根据打开目录的方式以及为lpOverlapped参数赋予的值,该缓冲区可以同步或异步填充。有关更多信息,请参见“备注”部分。

nBufferLength

lpBuffer参数指向的缓冲区大小,以字节为单位。

bWatchSubtree

如果此参数为TRUE,则该函数监视以指定目录为根的目录树。如果此参数为FALSE,则该函数仅监视hDirectory参数指定的目录。

dwNotifyFilter

函数检查以确定等待操作是否完成的筛选条件。此参数可以是以下一个或多个值。

表1
含义
FILE_NOTIFY_CHANGE_FILE_NAME
0x00000001
在监视的目录或子树中的任何文件名更改都会导致更改通知等待操作返回。更改包括重命名,创建或删除文件。
FILE_NOTIFY_CHANGE_DIR_NAME
0x00000002
在监视的目录或子树中的任何目录名称更改都会导致更改通知等待操作返回。更改包括创建或删除目录。
FILE_NOTIFY_CHANGE_ATTRIBUTES
0x00000004
被监视的目录或子树中的任何属性更改都会导致更改通知等待操作返回。
FILE_NOTIFY_CHANGE_SIZE
0x00000008
被监视目录或子树中任何文件大小的更改都会导致更改通知等待操作返回。仅当将文件写入磁盘时,操作系统才会检测到文件大小的变化。对于使用大量缓存的操作系统,仅在充分刷新了缓存后才进行检测。
FILE_NOTIFY_CHANGE_LAST_WRITE
0x00000010
对监视目录或子树中文件的最后写入时间的任何更改都会导致更改通知等待操作返回。仅当文件写入磁盘时,操作系统才会检测到上次写入时间的更改。对于使用大量缓存的操作系统,仅在充分刷新了缓存后才进行检测。
FILE_NOTIFY_CHANGE_LAST_ACCESS
0x00000020
对监视目录或子树中文件的最后访问时间的任何更改都会导致更改通知等待操作返回。
FILE_NOTIFY_CHANGE_CREATION
0x00000040
对监视目录或子树中文件的创建时间的任何更改都会导致更改通知等待操作返回。
FILE_NOTIFY_CHANGE_SECURITY
0x00000100
监视的目录或子树中的任何安全描述符更改都会导致更改通知等待操作返回。

lpBytesReturned

对于同步调用,此参数接收传输到lpBuffer参数中的字节 数。对于异步调用,此参数是未定义的。您必须使用异步通知技术来检索传输的字节数。

lpOverlapped

指向OVERLAPPED结构的指针,该结构提供异步操作期间要使用的数据。否则,该值为NULL。不使用此结构的 OffsetOffsetHigh成员。

lpCompletionRoutine

指向完成例程的指针,当操作已完成或取消并且调用线程处于可警报的等待状态时,将调用该例程。有关此完成例程的更多信息,请参见 FileIOCompletionRoutine

返回值

如果函数成功,则返回值为非零。对于同步调用,这意味着操作成功。对于异步调用,这表明操作已成功排队。

如果函数失败,则返回值为零。要获取扩展的错误信息,请调用 GetLastError

如果网络重定向器或目标文件系统不支持此操作,则该函数将失败并显示 ERROR_INVALID_FUNCTION

备注

若要获取目录的句柄,请使用 带有FILE_FLAG_BACKUP_SEMANTICS标志的CreateFile函数。

ReadDirectoryChangesW的调用可以同步或异步完成。要指定异步完成,请使用如上所述的CreateFile打开目录 ,但还要在dwFlagsAndAttributes 参数中指定 FILE_FLAG_OVERLAPPED属性。然后在调用ReadDirectoryChangesW时指定OVERLAPPED结构。

首次调用ReadDirectoryChangesW时,系统会分配一个缓冲区来存储更改信息。该缓冲区与目录句柄相关联,直到它被关闭并且其大小在其生命周期内保持不变。两次调用此函数之间发生的目录更改将添加到缓冲区中,然后在下一次调用时返回。如果缓冲区溢出,ReadDirectoryChangesW仍将返回true,但是缓冲区的全部内容将被丢弃,并且lpBytesReturned参数将为零,这表明缓冲区太小,无法容纳所有已发生的更改。

成功完成同步后,lpBuffer参数是一个格式化的缓冲区,写入该缓冲区的字节数在lpBytesReturned中可用。如果传输的字节数为零,则缓冲区太大,系统无法分配,或者缓冲区太小,无法提供有关目录或子树中发生的所有更改的详细信息。在这种情况下,您应该通过列举目录或子树来计算更改。

对于异步完成,您可以通过以下三种方式之一接收通知:

  • 使用GetOverlappedResult函数。要通过接收通知 GetOverlappedResult,不指定在完成例程用lpCompletionRoutine参数。确保将OVERLAPPED结构的hEvent成员 设置 为唯一事件。
  • 使用GetQueuedCompletionStatus 函数。要通过GetQueuedCompletionStatus接收通知 ,请不要在lpCompletionRoutine中指定完成例程。通过调用CreateIoCompletionPort函数将目录句柄 hDirectory与完成端口相关联 。
  • 使用完成例程。要通过完成例程接收通知,请勿将目录与完成端口关联。在lpCompletionRoutine中指定一个完成例程。只要线程处于可警报的等待状态,只要操作完成或取消,就会调用此例程。系统未使用OVERLAPPED结构的hEvent成员 ,因此您可以自己使用它。

欲了解更多信息,请参阅 同步和异步I / O

当缓冲区长度大于64 KB并且应用程序正在监视网络上的目录时,ReadDirectoryChangesW失败,并显示 ERROR_INVALID_PARAMETER。这是由于基础文件共享协议对数据包大小的限制。

当缓冲区未在DWORD 边界上对齐时,ReadDirectoryChangesW失败,并显示 ERROR_NOACCESS

如果使用短名称打开文件,则可以接收该短名称的更改通知。

以上就是这个系统提供的函数,AutoHotkey通过dllcall调用的方法,可以参考https://www.autoahk.com/archives/16307

为了避免大家重复造轮子,下面我给出三个写好的轮子供大家使用。

轮子1:

#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")
Inst2 := new FileMonitoring(folderPath2, notifyFilter, "OnDirectoryChanged2")
Return

OnDirectoryChanged1(filePath, event) 
{
   MsgBox, % ["添加", "移除", "修改", "重命名,旧名字", "重命名, 新名字"][event] ": " filePath
}

OnDirectoryChanged2(filePath, event) 
{
   MsgBox, % ["添加", "移除", "修改", "重命名,旧名字", "重命名, 新名字"][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", "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", 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", "UInt")
         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(Handle, hWnd, Msg, Timeout := -1) 
      {
         static 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")
         pWaitForSingleObject := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "WaitForSingleObject", "Ptr")
         hModule := DllCall("GetModuleHandle", "Str", "user32.dll", "Ptr")
         pPostMessageW := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "PostMessageW", "Ptr")
         NumPut(pWaitForSingleObject, ptr*1)
         NumPut(pPostMessageW, 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
      }
      
      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)
      }
   }
}

轮子2:俄国轮子

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
}

轮子3:最好的轮子,直接就是文件监视器

监测文件(夹)知识汇总

#NoEnv
#Warn
SetBatchLines, -1
; ----------------------------------------------------------------------------------------------------------------------------------
Gui, Margin, 20, 20
Gui, Add, Text, , 监测文件夹:
Gui, Add, Edit, xm y+3 w730 vWatchedFolder cGray +ReadOnly, 选择一个文件夹 ...
Gui, Add, Button, x+m yp w50 hp +Default vSelect gSelectFolder, ...
Gui, Add, Text, xm y+5, 监测变化:
Gui, Add, Checkbox, xm y+3 vSubTree, 包含子文件夹
Gui, Add, Checkbox, x+5 yp vFiles Checked, 文件
Gui, Add, Checkbox, x+5 yp vFolders Checked, 文件夹
Gui, Add, Checkbox, x+5 yp vAttr, 属性
Gui, Add, Checkbox, x+5 yp vSize, 大小
Gui, Add, Checkbox, x+5 yp vWrite, 上次写入
Gui, Add, Checkbox, x+5 yp vAccess, 上次进入
Gui, Add, Checkbox, x+5 yp vCreation, 新建
Gui, Add, Checkbox, x+5 yp vSecurity, 安全
Gui, Add, ListView, xm w800 r15 vLV, 计时|文件夹|动作|名字|是否文件夹|旧名|%A_Space%
Gui, Add, Button, xm w100 gStartStop vAction +Disabled, 开始
Gui, Add, Button, x+m yp wp gPauseResume vPause +Disabled, 暂停
Gui, Add, Button, x+m yp wp gCLear, 清除
Gui, Show, , 监测文件夹
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!
         Return
      }
      GuiControl, , Action, Stop
      GuiControl, Disable, Select
      GuiControl, Enable, Pause
   }
   Else {
      WatchFolder(WatchedFolder, "**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
; ----------------------------------------------------------------------------------------------------------------------------------
MyUserFunc(Folder, Changes) 
{
   Static Actions := ["1 (添加)", "2 (移除)", "3 (修改)", "4 (重命名)"]
   TickCount := A_TickCount
   GuiControl, -ReDraw, LV
   For Each, Change In Changes
      LV_Modify(LV_Add("", TickCount, Folder, Actions[Change.Action], Change.Name, Change.IsDir, Change.OldName, ""), "Vis")
   Loop, % LV_GetCount("Columns")
      LV_ModifyCol(A_Index, "AutoHdr")
   GuiControl, +Redraw, LV
}

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

给TA捐赠
共{{data.count}}人
人已捐赠
其他教程

AutoHotkey文件操作详解

2020-12-4 15:37:38

教程

2.2-程序的注释-单行注释和多行注释

2020-12-14 0:10:00

5 条回复 A文章作者 M管理员
  1. renzo

    为作者点赞.

  2. 绿小软

    我去 这也太牛了

  3. 荒废千年

    厉害了,就需要这个

  4. user16764

    好东西,是源代码不?是源代码给小白分享一下呗。

  5. gdzrh917

    感谢分享!😊

个人中心
购物车
优惠劵
有新私信 私信列表
搜索