I am experimenting with various versions of
FolderSpy and this particular version calls
ReadDirectoryChangesW in
Synchronous mode.
ReadDirectoryChangesW requires
OS >= Win 2000
The problem with synchronous mode in AHK is that AHK is single threaded and
ReadDirectoryChangesW can halt/freeze every other operation when it is waiting for an event in the watched folder.
Halt means: you cannot start an another thread via hotkey /settimer .. the GUI ( if any ) would not respond for any messages ( including mouse ).. not even a right click on tray icon would work!
To overcome the said effect, the script calls
ReadDirectoryChangesW in a seperate thread using
CreateThread() and
RegisterCallback().
When called in synchronous mode,
ReadDirectoryChangesW sets the variable
nReadLen (
with number of bytes ) when it exits the wait state.
Therefore, the script calls
ReadDirectoryChangesW in a seperate thread and loops to check the variable
nReadLen to ascertain whether the wait state has ended.
An interesting fact is that
ReadDirectoryChangesW can spy without enough access rights to the folder.
Irrespective of mode,
ReadDirectoryChangesW keeps updating the structure and it is upto the user to decide on how frequently to call the
ReadDirectoryChangesW. The user may even call it once per hour but the structure size should be large enough to hold all the folder events that may occur during the period, else it will reset and start from the beginning of the Structure. This script uses a
64KB structure.
Disclaimer: I would not say that the script is perfect as it had to undergo a lot of cosmetic changes in last few days in order to make it legible for the average user. I have been testing it over months and had no problems except that I had to clear the
ListView when it grows very large ( owing to my system's meagre 256MB RAM ). The script may be considered more as a demo for multi-threading. I will post an another simple script using asynchronous mode, which should be enough for most of the needs.
To
stress-test this script, press the
Start button and load any MSDN page ( like
this ) in
Internet Explorer. I get around 2000+ notifications in a few seconds!
Code:
; * Folder Spy v0.97 * by Skan || Created : 14-Apr-2007 /// LastModified : 06-Sep-2007
#Persistent
SetBatchLines, -1
Process, Priority,, High
OnExit, ShutApp
Menu, Tray, Icon, Shell32.dll, 4
Menu, Tray, Tip , FolderSpy
WatchFolder := A_Temp . "orary Internet Files"
WatchSubDirs := True
Loop %WatchFolder%, 1
WatchFolder := A_LoopFileLongPath
DllCall( "shlwapi\PathAddBackslashA", UInt,&Watchfolder )
CBA_ReadDir := RegisterCallback("ReadDirectoryChanges")
; FILE_NOTIFY_INFORMATION : http://msdn2.microsoft.com/en-us/library/aa364391.aspx
SizeOf_FNI := ( 64KB := 1024 * 64 )
VarSetCapacity( FILE_NOTIFY_INFORMATION, SizeOf_FNI, 0 )
PointerFNI := &FILE_NOTIFY_INFORMATION
Gui, Margin, 5, 5
Gui, Add, Edit , x5 w574 h22 +ReadOnly vWatchFolder, %WatchFolder%
Gui, Add, Button , x+5 w25 h22 gSelectFolder vBrowseButton, ...
Gui, Add, CheckBox , x+20 h22 vWatchSubDirs Checked 0x20, Sub Folders
Gui, Add, ListView , x5 w700 h480 +Grid vSpyLV
, Time|Event|File/Folder Name|Size-KB|TimeStamp [Mod]|Attrib
Gui, Add, Button , x550 w64 h22 +Default vClear gClearListView, Clear
Gui, Add, Button , x+10 w64 h22 +Default vStartStop gStartStop, Start
LV_ModifyCol( 1, "54" )
LV_ModifyCol( 2, "75 Center " )
LV_ModifyCol( 3, "327 " )
LV_ModifyCol( 4, "55 Integer" )
LV_ModifyCol( 5, "120 " )
LV_ModifyCol( 6, "46 " )
Gui, Add, StatusBar
SB_SetParts( 100, 500 )
GuiControl, Focus, StartStop
Gui, Show, , FolderSpy v0.96
;: - - - - - - - - - - - - - - - - - - - - - -
Return ; [| End of Auto-execute section |]
;: - - - - - - - - - - - - - - - - - - - - - -
WatchFolder:
GuiControlGet, WatchFolder
GuiControlGet, WatchSubDirs
; CreateFile: http://msdn2.microsoft.com/en-us/library/aa914735.aspx
hDir := DllCall( "CreateFile"
, Str , WatchFolder
, UInt , ( FILE_LIST_DIRECTORY := 0x1 )
, UInt , ( FILE_SHARE_READ := 0x1 )
| ( FILE_SHARE_WRITE := 0x2 )
| ( FILE_SHARE_DELETE := 0x4 )
, UInt , 0
, UInt , ( OPEN_EXISTING := 0x3 )
, UInt , ( FILE_FLAG_BACKUP_SEMANTICS := 0x2000000 )
| ( FILE_FLAG_OVERLAPPED := 0x40000000 )
, UInt , 0 )
Loop {
nReadLen := 0
hThreadId := 0
; CreateThread : http://msdn2.microsoft.com/en-us/library/ms682453.aspx
hThread := DllCall( "CreateThread", UInt,0, UInt,0, UInt,CBA_ReadDir
, UInt,0, UInt,0, UIntP,hThreadId )
Loop {
If nReadLen
{
GoSub, Decode_FILE_NOTIFY_INFORMATION
Break
}
If !Watch
Break
Sleep 100
}
; TerminateThread : http://msdn2.microsoft.com/en-us/library/ms686717.aspx
; CloseHandle : http://msdn2.microsoft.com/en-us/library/ms724211.aspx
DllCall( "TerminateThread", UInt,hThread, UInt,0 )
DllCall( "CloseHandle", UInt,hThread )
If !Watch
Break
}
Return
;: - - - - - - - - - - - - - - - - - - - - - -
SelectFolder:
FileSelectFolder, SelFolder, *%WatchFolder%, , Select Watch Folder
If ( SelFolder = "" )
Return
Loop %SelFolder%, 1
WatchFolder := A_LoopFileLongPath
DllCall( "shlwapi\PathAddBackslashA", UInt,&Watchfolder )
GuiControl,, WatchFolder, %WatchFolder%
GuiControl, Focus, StartStop
DllCall( "CloseHandle", UInt,hThread )
SetTimer, StartStop, -1
Return
;: - - - - - - - - - - - - - - - - - - - - - -
StartStop:
Watch := !Watch
If ( Watch ) {
GuiControl,Disable, BrowseButton
GuiControl,Disable, WatchSubDirs
GuiControl,, StartStop, Stop
SetTimer, WatchFolder, -1
} Else {
GuiControl,Enable, BrowseButton
GuiControl,Enable, WatchSubDirs
GuiControl,, StartStop, Start
}
Return
;: - - - - - - - - - - - - - - - - - - - - - -
ReadDirectoryChanges() { ; http://msdn2.microsoft.com/en-us/library/aa365465.aspx
Global hDir,PointerFNI, Sizeof_FNI, WatchSubdirs, nReadlen
Return DllCall( "ReadDirectoryChangesW"
, UInt , hDir
, UInt , PointerFNI
, UInt , SizeOf_FNI
, UInt , WatchSubDirs
, UInt , ( 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 )
, UIntP, nReadLen
, UInt , 0
, UInt , 0 )
}
;: - - - - - - - - - - - - - - - - - - - - - -
Decode_FILE_NOTIFY_INFORMATION:
PointerFNI := &FILE_NOTIFY_INFORMATION
Loop {
NextEntry := NumGet( PointerFNI + 0 )
Action := NumGet( PointerFNI + 4 )
FileNameLen := NumGet( PointerFNI + 8 )
FileNamePtr := ( PointerFNI + 12 )
If ( Action = 0x1 ) ; FILE_ACTION_ADDED
Event := "New File"
If ( Action = 0x2 ) ; FILE_ACTION_REMOVED
Event := "Deleted"
If ( Action = 0x3 ) ; FILE_ACTION_MODIFIED
Event := "Modified"
If ( Action = 0x4 ) ; FILE_ACTION_RENAMED_OLD_NAME
Event := "Renamed Fm"
If ( Action = 0x5 ) ; FILE_ACTION_RENAMED_NEW_NAME
Event := "Renamed To"
VarSetCapacity( FileNameANSI, FileNameLen )
DllCall( "WideCharToMultiByte", UInt,0, UInt,0, UInt,FileNamePtr, UInt
, FileNameLen, Str,FileNameANSI, UInt,FileNameLen, UInt,0, UInt,0 )
File := SubStr( FileNameANSI, 1, FileNameLen/2 )
FullPath := WatchFolder . File
FileGetAttrib, Attr, %FullPath%
FormatTime, Time , %A_Now%, HH:mm:ss
If ( FileExist( FullPath ) = "" )
{
LV_Insert( 1, "", Time, Event, File )
Sb_SetText( "`t" LV_GetCount() )
}
Else
Loop %FullPath%
{
FormatTime, TStamp, %A_LoopFileTimeModified%, yyyy-MM-dd HH:mm:ss
LV_Insert( 1, "", Time, Event, File, A_LoopFileSizeKB, TStamp, A_LoopFileAttrib )
Sb_SetText( "`t" LV_GetCount() )
}
If !NextEntry
Break
Else
PointerFNI := PointerFNI + NextEntry
}
Return
;: - - - - - - - - - - - - - - - - - - - - - -
GuiClose:
ShutApp:
DllCall( "CloseHandle", UInt,hDir )
DllCall( "TerminateThread", UInt,hThread, UInt,0 )
DllCall( "CloseHandle", UInt,hThread )
ExitApp
Return
;: - - - - - - - - - - - - - - - - - - - - - -
ClearListView:
LV_Delete() , Sb_SetText("")
Return
;: - - - - - - - - - - - - - - - - - - - - - -
