这段代码是一个完整的文件夹监视工具,基于 AutoHotkey 编写。主要功能包括:
- GUI 界面,用户可以选择要监视的文件夹,并设置监视选项。
- 使用 WinAPI 进行文件夹变更监视。
- 实时更新监视结果,并显示在 ListView 控件中。
具体的实现细节通过注释进行了详细说明。
; 创建一个新的 GUI 界面,并设置边距
ui := Gui.New(), ui.MarginX := ui.MarginY := 20
; 添加文本控件,显示“文件夹监视”标签
ui.Add("Text", , '文件夹监视')
; 添加一个只读的编辑框,用于显示当前选择的文件夹路径,默认值为桌面路径
ui.Add("Edit", 'xm y+5 w730 cGray +ReadOnly vFolder', A_Desktop)
; 添加一个按钮用于选择文件夹,当点击时触发 SelectFolder 函数
ui.Add("Button", 'x+m yp w50 +Default', '...').OnEvent('Click', 'SelectFolder')
; 添加“选项”标签
ui.Add("Text", 'xm y+10', '选项')
; 添加多个复选框,用于选择监视的内容,包括子目录、文件名、目录名、属性、文件大小、最后写入时间、最后访问时间、创建时间、安全描述符
for _ in [['xm y+10 vSubTree', '子目录'], ['x+5 yp vFiles Checked', '文件名'], ['x+5 yp vFolders Checked', '目录名'], ['x+5 yp vAttributes', '属性'],
['x+5 yp vSize', '文件大小'], ['x+5 yp vLast_write', '最后写入时间'], ['x+5 yp vLast_access', '最后访问时间'], ['x+5 yp vCreation', '创建时间'], ['x+5 yp vSecurity', '安全描述符']]
ui.Add("Checkbox", _[1], _[2])
; 添加一个 ListView 控件,用于显示文件夹监视的变化日志,列包括修改时间、路径、行为、文件(夹)名、旧文件(夹)名
ui.Add("ListView", 'xm w800 r15 vLV', ['修改时间','路径','行为','文件(夹)名','旧文件(夹)名',' '])
; 添加一个“开始”按钮,当点击时触发 StartorStop 函数
ui.Add("Button", 'xm w100 vStart', '开始').OnEvent('Click', 'StartorStop')
; 添加一个“停止”按钮,当点击时触发 StartorStop 函数,初始状态为禁用
ui.Add("Button", 'x+m yp wp vStop +Disabled', '停止').OnEvent('Click', 'StartorStop')
; 添加一个“清除”按钮,当点击时删除 ListView 中的所有记录
ui.Add("Button", 'x+m yp wp', '清除').OnEvent('Click', (ctl, *) => ctl.Gui['LV'].Delete())
; 设置窗口标题并显示 GUI,设置关闭事件处理函数
ui.Title := '文件夹监视', ui.Show(), ui.OnEvent('Close', (*) => ExitApp())
; 结束自动执行部分,等待用户操作
return
; 选择文件夹的回调函数
SelectFolder(ctl, *) {
ui := ctl.Gui
if (folder := DirSelect()) ; 弹出文件夹选择对话框
ui['Folder'].Text := folder, ui['Start'].Enabled := true ; 更新编辑框文本,并启用“开始”按钮
}
; 开始或停止监视的回调函数
StartorStop(ctl, *) {
static mon := '', _ := OnExit((*) => (mon := '')) ; 静态变量 mon 用于保存监视器实例
ui := ctl.Gui
if (ctl.Name = 'Start') { ; 如果点击的是“开始”按钮
ui.Submit(false), filter := 0 ; 提交表单数据,初始化过滤器
if !InStr(FileExist(ui['Folder'].Text), 'D') ; 检查选择的路径是否有效
return MsgBox(ui['Folder'].Text '不是一个有效的文件夹', 'Error')
; 根据复选框的选择设置过滤器
for i, v in ['Files', 'Folders', 'Attributes', 'Size', 'Last_write', 'Last_access', 'Creation', 'Security']
filter |= ui[v].Value ? [1, 2, 4, 8, 16, 32, 64, 256][i] : 0
; 创建文件监视器实例,并启动监视
mon := FileMonitoring.New(ui['Folder'].Text, filter, Func('update'), ui['SubTree'].Value), ui['Start'].Enabled := !(ui['Stop'].Enabled := true)
} else mon := '', ui['Start'].Enabled := !(ui['Stop'].Enabled := false) ; 如果点击的是“停止”按钮,停止监视
}
; 更新 ListView 的回调函数
update(change) {
; 添加一条新的记录到 ListView
ui['LV'].Add('vis', FormatTime(, 'yyyy/MM/dd hh:mm:ss'), ui['Folder'].Text, ['新建', '删除', '修改', '重命名'][change.Action], change.Name, change.HasOwnProp('OldName') ? change.OldName : '', '')
ui['LV'].ModifyCol() ; 自动调整列宽
}
; 文件监视类定义
class FileMonitoring {
static NOTIFY := {FILE_NAME: 0x1, DIR_NAME: 0x2, ATTRIBUTES: 0x4, CHANGE_SIZE: 0x8, LAST_WRITE: 0x10, LAST_ACCESS: 0x20, CREATION: 0x40, SECURITY: 0x100} ; FILE_NOTIFY_CHANGE
__New(folderPath, notifyFilter, UserFunc, watchSubtree := false) {
; 创建事件对象和缓冲区
this.Event := FileMonitoring._Event.New(), this.buffer := BufferAlloc(1024), pBuffer := this.buffer.Ptr, this.overlapped := BufferAlloc(A_PtrSize * 3 + 8), this.pOverlapped := this.overlapped.Ptr
; 初始化目录监视
this.Directory := FileMonitoring._ReadDirectoryChanges.New(folderPath, notifyFilter, watchSubtree, pBuffer, this.pOverlapped, this.Event.Ptr)
; 创建事件信号处理对象并启动监视
this.EventSignal := FileMonitoring._EventSignal.New(this.Directory, this.Event.Ptr, pBuffer, UserFunc), this.EventSignal.folderPath := folderPath, this.Directory.Read()
}
__Delete() {
; 停止监视,清理资源
DllCall('CancelIoEx', 'Ptr', this.Directory.Handle, 'Ptr', this.pOverlapped)
this.Event.Set(), this.EventSignal.Clear(), this.Directory.Clear(), this.buffer := ''
}
class _Event {
__New() => (this.Ptr := DllCall('CreateEvent', 'Int', 0, 'Int', 0, 'Int', 0, 'Int', 0, 'Ptr')) ; 创建事件对象
Set() => DllCall('SetEvent', 'Ptr', this) ; 设置事件对象的状态
__Delete() => DllCall('CloseHandle', 'Ptr', this) ; 关闭事件对象句柄
}
class _EventSignal {
__New(Directory, hEvent, pBuffer, UserFunc) {
; 初始化事件信号对象
for v in ['Directory', 'hEvent', 'pBuffer']
this.%v% := %v%
; 注册自定义消息
OnMessage(this.WM_EVENTSIGNAL := DllCall('RegisterWindowMessage', 'Str', 'WM_EVENTSIGNAL', 'UInt'), this.OnEvent := ObjBindMethod(this, 'On_WM_EVENTSIGNAL'))
this.UserFunc := IsObject(UserFunc) ? UserFunc : Func(UserFunc), this.startAddress := this.CreateWaitFunc(this.hEvent, A_ScriptHwnd, this.WM_EVENTSIGNAL)
; 创建等待线程
this.Thread := FileMonitoring._EventSignal._Thread.New(this.startAddress)
}
On_WM_EVENTSIGNAL(wp, *) {
; 处理事件信号
if !(wp = this.hEvent && DllCall('GetOverlappedResult', 'Ptr', this.hEvent, 'Ptr', this.pBuffer, 'UInt*', written := 0, 'UInt', false))
return
addr := this.pBuffer, offset := 0, preName := '', preAction := 0
Loop {
; 解析监视事件
addr += offset, Action := NumGet(addr + 4, 'UInt'), Name := StrGet(addr + 12, NumGet(addr + 8, 'UInt') // 2, 'UTF-16')
if (Name == preName && Action == preAction)
continue
if (Action = 4) {
; 重命名事件的旧名称
Change := {Action: Action, OldName: Name}, preName := Name, preAction := Action
continue
} else if (Action = 5 && preAction = 4) {
; 重命名事件的新名称
Change.Path := this.folderPath '' Name, Change.Name := Name
} else Change := {Action: Action, Name: Name, Path: this.folderPath '' Name}
SetTimer(this.UserFunc.Bind(Change), -1), preName := Name, preAction := Action
} until !(offset := NumGet(addr + 0, 'UInt'))
; 重新启动监视
this.Thread.Wait(), this.Thread := FileMonitoring._EventSignal._Thread.New(this.startAddress), this.Directory.Read()
}
CreateWaitFunc(Handle, hWnd, Msg, Timeout := -1) {
; 创建等待函数,用于等待事件信号
ptr := DllCall('VirtualAlloc', 'Ptr', 0, 'Ptr', A_PtrSize = 4 ? 49 : 85, 'UInt', MEM_COMMIT := 0x1000, 'UInt', PAGE_EXECUTE_READWRITE := 0x40, 'Ptr')
pWaitForSingleObject := DllCall('GetProcAddress', 'Ptr', DllCall('GetModuleHandle', 'Str', 'kernel32.dll', 'Ptr'), 'AStr', 'WaitForSingleObject', 'Ptr')
pPostMessageW := DllCall('GetProcAddress', 'Ptr', DllCall('GetModuleHandle', 'Str', 'user32.dll', 'Ptr'), 'AStr', 'PostMessageW', 'Ptr')
NumPut('Ptr', pWaitForSingleObject, 'Ptr', pPostMessageW, ptr + 0)
if (A_PtrSize = 4) {
; 32 位系统下的等待函数实现
NumPut('UChar', 0x68, ptr + 8), NumPut('UInt', Timeout, ptr + 9), NumPut('UChar', 0x68, ptr + 13)
NumPut('Ptr', Handle, ptr + 14), NumPut('UShort', 0x15FF, ptr + 18), NumPut('Ptr', ptr, ptr + 20)
NumPut('UShort', 0x6850, ptr + 24), NumPut('Ptr', Handle, ptr + 26), NumPut('UChar', 0x68, ptr + 30)
NumPut('UInt', Msg, ptr + 31), NumPut('UChar', 0x68, ptr + 35), NumPut('Ptr', hWnd, ptr + 36), NumPut('UShort', 0x15FF, ptr + 40)
NumPut('Ptr', ptr + 4, ptr + 42), NumPut('UChar', 0xC2, ptr + 46), NumPut('UShort', 4, ptr + 47)
} else {
; 64 位系统下的等待函数实现
NumPut('UChar', 0x53, ptr + 16), NumPut('UInt', 0x20EC8348, ptr + 17), NumPut('UInt', 0xBACB8948, ptr + 21)
NumPut('UInt', Timeout, ptr + 25), NumPut('UShort', 0xB948, ptr + 29), NumPut('Ptr', Handle, ptr + 31)
NumPut('UShort', 0x15FF, ptr + 39), NumPut('UInt', -45, ptr + 41), NumPut('UShort', 0xB849, ptr + 45)
NumPut('Ptr', Handle, ptr + 47), NumPut('UChar', 0xBA, ptr + 55), NumPut('UInt', Msg, ptr + 56), NumPut('UShort', 0xB948, ptr + 60)
NumPut('Ptr', hWnd, ptr + 62), NumPut('UInt', 0xC18941, ptr + 70), NumPut('UShort', 0x15FF, ptr + 73)
NumPut('UInt', -71, ptr + 75), NumPut('UInt', 0x20C48348, ptr + 79), NumPut('UShort', 0xC35B, ptr + 83)
}
return ptr + A_PtrSize * 2
}
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)
}
class _Thread {
; 线程类定义
__New(startAddress) {
; 创建新线程
if !(this.handle := DllCall('CreateThread', 'Ptr', 0, 'UInt', 0, 'Ptr', startAddress, 'Ptr', 0, 'UInt', 0, 'UInt', 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) ; 关闭线程句柄
}
}
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 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('Ptr', this.hEvent, this.pOverlapped + A_PtrSize * 2 + 8)
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) ; 关闭目录句柄
}
}
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
V2??版本太多也不是什么好事,脚本不能共享,语法有差异
学习
注释非常详细,感谢分享!