自动化操作的基础就是感知,对文件(夹)的监测尤为重要,文件夹发生变化时往往最重要的触发契机。
下面我把相关的知识分享在这里供大家学习。
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。不使用此结构的 Offset和OffsetHigh成员。
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
}
为作者点赞.
我去 这也太牛了
厉害了,就需要这个
好东西,是源代码不?是源代码给小白分享一下呗。
感谢分享!😊