 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Lexikos
Joined: 17 Oct 2006 Posts: 7299 Location: Australia
|
Posted: Fri Apr 11, 2008 1:57 am Post subject: |
|
|
Select folders one at a time. It needs cleaning up, but hopefully you get the point. "Stop" is disabled and directories cannot be removed. (I'll leave it to someone else to add these features.)
| Code: | #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.97X
;: - - - - - - - - - - - - - - - - - - - - - -
Return ; [| End of Auto-execute section |]
;: - - - - - - - - - - - - - - - - - - - - - -
WatchFolder:
ReadDirectoryChanges()
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
BeginWatchingDirectory(WatchFolder, WatchSubDirs)
SetTimer, StartStop, -1
Return
;: - - - - - - - - - - - - - - - - - - - - - -
StartStop: ; For now, just Start.
GuiControl,Disable, StartStop
GuiControl,, StartStop, Stop
SetTimer, WatchFolder, 10
Return
;: - - - - - - - - - - - - - - - - - - - - - -
BeginWatchingDirectory(WatchFolder, WatchSubDirs=true)
{
local hDir, hEvent
DirIdx += 1
; 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 )
Dir%DirIdx% := hDir
Dir%DirIdx%Path := WatchFolder
Dir%DirIdx%Subdirs := WatchSubDirs
VarSetCapacity( Dir%DirIdx%FNI, SizeOf_FNI )
VarSetCapacity( Dir%DirIdx%Overlapped, 20, 0 )
hEvent := DllCall( "CreateEvent", UInt,0, Int,true, Int,false, UInt,0 )
NumPut( hEvent, Dir%DirIdx%Overlapped, 16 )
; Maintain array of event handles to wait on.
if ( VarSetCapacity(DirEvents) < DirIdx*4 )
{ ; Expand by 16 directories (64 bytes) at a time.
VarSetCapacity(DirEvents, DirIdx*4 + 60)
; Copy all previous event handles.
Loop %DirIdx%
NumPut( NumGet( Dir%A_Index%Overlapped, 16 ), DirEvents, A_Index*4-4 )
}
NumPut( hEvent, DirEvents, DirIdx*4-4 )
DllCall( "ReadDirectoryChangesW" ; http://msdn2.microsoft.com/en-us/library/aa365465.aspx
, UInt , hDir
, UInt , &Dir%DirIdx%FNI
, 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 )
, UInt , 0
, UInt , &Dir%DirIdx%Overlapped
, UInt , 0 )
}
; Handles one directory at a time.
; Returns non-zero if a change was detected.
; Returns zero if it timed out or a window message was received.
ReadDirectoryChanges(Timeout=-1)
{
local hDir, r
; Wait for any event object to be signaled or a window message to be received.
r := DllCall("MsgWaitForMultipleObjectsEx", UInt, DirIdx, UInt, &DirEvents
, UInt, Timeout, UInt, 0x4FF, UInt, 0x6)
if (r >= 0 && r < DirIdx) ; WAIT_OBJECT_*
{
r += 1
; At least one event object was signaled. Decode the FNI for this event.
; If more than one event object was signaled, this func must be called again.
WatchFolder := Dir%r%Path
PointerFNI := &Dir%r%FNI
nReadLen := 0
DllCall( "GetOverlappedResult", UInt, hDir
, UInt, &Dir%r%Overlapped, UIntP, nReadLen, Int, true )
gosub Decode_FILE_NOTIFY_INFORMATION
; Reset the event and call ReadDirectoryChangesW in async mode again.
DllCall( "ResetEvent", UInt,NumGet( Dir%r%Overlapped, 16 ) )
DllCall( "ReadDirectoryChangesW"
, UInt , Dir%r%
, UInt , &Dir%r%FNI
, UInt , SizeOf_FNI
, UInt , Dir%r%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 )
, UInt , 0
, UInt , &Dir%r%Overlapped
, UInt , 0 )
return r
}
return 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 )
ExitApp
Return
;: - - - - - - - - - - - - - - - - - - - - - -
ClearListView:
LV_Delete() , Sb_SetText("")
Return
;: - - - - - - - - - - - - - - - - - - - - - - |
Edit: Removed "hThread" remnants of SKAN's script.
Last edited by Lexikos on Fri Apr 11, 2008 10:31 pm; edited 2 times in total |
|
| Back to top |
|
 |
caterva
Joined: 09 Apr 2008 Posts: 14
|
Posted: Fri Apr 11, 2008 8:55 am Post subject: |
|
|
Many thanks for your work.
By now, just two questions:
1) What about the "TerminateThread" call? Unlike SKAN script, there is not any call to "OpenThread"
2) I do not understand the need of "CreateFile" call (that is present also in SKAN script")? Can you explain me why it is invoked? |
|
| Back to top |
|
 |
tic
Joined: 22 Apr 2007 Posts: 1786
|
Posted: Fri Apr 11, 2008 4:28 pm Post subject: |
|
|
| CreateFile gets a handle to a file. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7299 Location: Australia
|
Posted: Fri Apr 11, 2008 10:30 pm Post subject: |
|
|
In this script, CreateFile is used to open (retrieve a handle to) the directory/folder to be watched.
TerminateThread is a remnant from SKAN's script, and should be removed. (As I said, "it needs cleaning up.") |
|
| Back to top |
|
 |
caterva
Joined: 09 Apr 2008 Posts: 14
|
Posted: Tue Apr 15, 2008 5:18 pm Post subject: |
|
|
I realized anomalous behavious when watching folder "c:\documents and settings\user\Documents".
Folderspy (or, more precisely, ReadDirectoryChangeW), cyclically continues to signal the modification of all the files in that folder!
Moreover, as I have already written, whatever is the watched folder any modification event is reported twice or more times.
Last edited by caterva on Wed Apr 16, 2008 8:35 am; edited 1 time in total |
|
| Back to top |
|
 |
SKAN
Joined: 26 Dec 2005 Posts: 8688
|
Posted: Tue Apr 15, 2008 10:39 pm Post subject: |
|
|
@Lexikos : Many thanks for demonstrating the method.
@caterva: Further to our PMs, I post the DLL version of Synchronous method: Download: dirw.dll ( 16k only )
A template to use the above DLL follows:
| Code: | Gui +LastFound
Gui, Show, Hide w0 h0, DirWatchTargetWindow
DllCall( "LoadLibrary", Str, "dirw.dll" )
DllCall( "dirw\StartWatch", Str,"C:\" )
DllCall( "dirw\StartWatch", Str,"D:\" )
OnMessage( 0x4a, "WM_COPYDATA" )
Return ; // end of auto-execute section
WM_COPYDATA( wParam,lParam ) {
Data:=NumGet(lParam+8 ), Len:=NumGet(lParam+4), VarSetCapacity( Msg,Len )
DllCall( "lstrcpyn",Str,Msg, UInt,Data, UInt,Len+1 )
ToolTip % Msg
} |
The logic: The DLL creates as many worker threads for all StartWatch() calls, effecting multi-threading.
The original DLL was written for VB, and I had to patch the code to make it work in AHK. I compiled the source with VC++ 6.0 and have used UPX.
The source code follows: ( I am unable to remember/trace the source URL .. will try to credit the author soon )
| Code: | #include <windows.h>
#include <stdio.h>
int readyToReturn;
HWND hServer;
//my header files are old!
int (__stdcall *ReadDirectoryChangesW) (HANDLE, LPVOID, DWORD, BOOL, DWORD, LPDWORD,LPOVERLAPPED,LPOVERLAPPED_COMPLETION_ROUTINE);
typedef struct{
int dwFlag;
int cbSize;
int lpData;
} cpyData;
void FindAHKWindow(){
char *AHK_ClassName = "AutoHotkeyGUI" ;
char *AHK_WindowCaption = "DirWatchTargetWindow" ;
hServer = FindWindowA( AHK_ClassName, AHK_WindowCaption );
}
int Msg(char *Buffer){
if(hServer==0) FindAHKWindow();
cpyData cpStructData;
cpStructData.cbSize = strlen(Buffer) ;
cpStructData.lpData = (int)Buffer;
cpStructData.dwFlag = 3;
return SendMessage(hServer, WM_COPYDATA, (WPARAM)hServer,(LPARAM)&cpStructData);
}
//cheesy unicode to ascii conversion
void Convert(char* buf, char* wBuf, int wLen, int bLen=255){
for(int i=0,j=0; i<wLen;i++){
if(i>bLen) return;
if(wBuf[i] != 0) buf[j++] = wBuf[i];
}
}
DWORD WINAPI ThreadHandler(void* dirPointer){
FILE_NOTIFY_INFORMATION Buffer[1024]; //currently we only deal with first not all
DWORD BytesReturned;
char myDir[MAX_PATH];
char buf[MAX_PATH];
char msg[500];
char action[30];
int i=0;
strcpy(myDir, (char*)dirPointer);
readyToReturn = 1; //we have our copy now main thread can unblock
sprintf(buf , "Watching %s", myDir);
Msg(buf);
HANDLE hDir = CreateFile( (const char*)myDir, FILE_LIST_DIRECTORY, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,NULL );
while( ReadDirectoryChangesW(hDir, &Buffer, sizeof(Buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME, &BytesReturned, NULL, NULL)){
memset(buf,0,MAX_PATH);
Convert(buf, (char*)Buffer[i].FileName, Buffer[i].FileNameLength);
switch(Buffer[i].Action){
case FILE_ACTION_ADDED: strcpy(action,"Created = "); break;
case FILE_ACTION_REMOVED: strcpy(action,"Deleted = "); break;
case FILE_ACTION_MODIFIED: strcpy(action,"Modifed = "); break;
case FILE_ACTION_RENAMED_OLD_NAME: strcpy(action,"Renamed = "); break;
case FILE_ACTION_RENAMED_NEW_NAME: strcpy(action,"Renamed = "); break;
}
sprintf(msg,"%s%s\\%s", action, myDir, buf);
Msg(msg);
}
return 1;
}
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){
if(ul_reason_for_call==1){
HINSTANCE h = LoadLibrary("kernel32.dll");
GetProcAddress( h, "ReadDirectoryChangesW");
_asm mov ReadDirectoryChangesW, eax //lazy no long cast
if( (int)ReadDirectoryChangesW < 1 ){
MessageBox(0,"Could not Get pointer to ReadDirectoryChangesW!","",0);
return FALSE;
}
Msg("WatchDir Initilized OK");
}
return TRUE;
}
int __stdcall CloseWatch(HANDLE threadID){
return TerminateThread(threadID,1);
}
HANDLE __stdcall StartWatch(char* dirPath){
unsigned long threadID;
HANDLE hThread=0;
int delayCounter =0;
if( strlen(dirPath) >= MAX_PATH){
MessageBox(0,"dirPath must be less than MAX_PATH","",0);
return 0;
}
readyToReturn = 0;
hThread = CreateThread(NULL,0, ThreadHandler, dirPath,0, &threadID);
if( (int)hThread < 1 ) return 0;
while(readyToReturn == 0){ //delay return to main thread till child thread initilized
delayCounter++; //so that strign pointer is valid. Even using a global copy
Sleep(100); //of string doesnt entirly help cause clients could overlap
if(delayCounter > 300){ //calls to quickly and overwrite it, so better to block
CloseWatch(hThread);
return 0;
}
}
return hThread;
} |
I do not know C++. It would be helpful if somebody make this DLL to accept more parameters to offer flexibility.
 |
|
| Back to top |
|
 |
caterva
Joined: 09 Apr 2008 Posts: 14
|
Posted: Wed Apr 16, 2008 8:02 pm Post subject: |
|
|
| SKAN wrote: |
@caterva: Further to our PMs, I post the DLL version of Synchronous method: Download: dirw.dll ( 16k only )
A template to use the above DLL follows:
 |
Many many thanks for your wonderfully simple program. Anyhow, it seems to me that it intercepts only file creations and deletions. Do you know how to modify it to allow also notification of file modifications?
Edit: another problem. No more than two file additions/deletions are signaled at a time. The other ones are lost. 
Last edited by caterva on Wed Apr 16, 2008 8:30 pm; edited 1 time in total |
|
| Back to top |
|
 |
SKAN
Joined: 26 Dec 2005 Posts: 8688
|
Posted: Wed Apr 16, 2008 8:16 pm Post subject: |
|
|
| caterva wrote: | | Do you know how to modify it to allow also notification of file modifications? |
Oh! I will look into it.. but please allow me a day.  |
|
| Back to top |
|
 |
SKAN
Joined: 26 Dec 2005 Posts: 8688
|
Posted: Wed Apr 16, 2008 8:47 pm Post subject: |
|
|
| caterva wrote: | Edit: another problem. No more than two file additions/deletions are signaled at a time. The other ones are lost.  |
Oh! Try adding Critical .. might help:
| Code: | WM_COPYDATA( wParam,lParam ) {
Critical
Data:=NumGet(lParam+8 ), Len:=NumGet(lParam+4), VarSetCapacity( Msg,Len )
DllCall( "lstrcpyn",Str,Msg, UInt,Data, UInt,Len+1 )
ToolTip % Msg
} |
|
|
| Back to top |
|
 |
caterva
Joined: 09 Apr 2008 Posts: 14
|
Posted: Wed Apr 16, 2008 9:09 pm Post subject: |
|
|
| SKAN wrote: |
Oh! Try adding Critical .. might help:
|
Sorry, no change.
I suspect it depends on the dll. I do not know C, C+, C++, C#, but what about the following line?
| Code: | | FILE_NOTIFY_INFORMATION Buffer[1024]; //currently we only deal with first not all |
|
|
| Back to top |
|
 |
SKAN
Joined: 26 Dec 2005 Posts: 8688
|
Posted: Wed Apr 16, 2008 9:20 pm Post subject: |
|
|
Okay! I will look into it.  |
|
| Back to top |
|
 |
caterva
Joined: 09 Apr 2008 Posts: 14
|
Posted: Thu Apr 17, 2008 3:53 pm Post subject: |
|
|
I wish an explanation, if you can.
| Lexikos wrote: |
...and simplified loop in WatchFolder:
| Code: |
Loop {
ReadDirectoryChanges()
GoSub, Decode_FILE_NOTIFY_INFORMATION
Sleep 100
If !Watch
Break
}
|
|
I put a msgbox instruction at the beginning of sub Decode_FILE_NOTIFY_INFORMATION. So I realized that the sub is invoked only on addition/modification/deletion events.
I do not understand how this is possible without any if statement.
Maybe is there something in funcion ReadDirectoryChanges() in previous line which skips the successive instruction(s)? |
|
| Back to top |
|
 |
caterva
Joined: 09 Apr 2008 Posts: 14
|
Posted: Thu Apr 17, 2008 7:44 pm Post subject: |
|
|
| caterva wrote: | I wish an explanation, if you can.
| Code: |
Loop {
ReadDirectoryChanges()
GoSub, Decode_FILE_NOTIFY_INFORMATION
Sleep 100
If !Watch
Break
}
|
|
I apologize for my previous very stupid question. I did misunderstanding the functioning of ReadDirectoryChanges().
Now it seems to me that the script continues to "run" inside ReadDirectoryChanges() until an event is registered, while in the "multifolder" version of the script the ReadDirectoryChanges() function is continuous looped.
Then, suppose that I want to make some other operations contemporaneously to monitoring a folder. Am I right if I say that I have to use the "multiversion" version of FolderSpy and not the "singlefolder" version? |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7299 Location: Australia
|
Posted: Thu Apr 17, 2008 10:16 pm Post subject: |
|
|
| caterva wrote: | | Am I right if I say that I have to use the "multiversion" version of FolderSpy and not the "singlefolder" version? | Yes. The "single-folder" version waits until the event is signalled, while the "multi-folder" version waits until any event is signalled, a window message is received, or it times out. You'll have to specify a timeout (default is -1 = no timeout) or use another timer to run your other code (since timers use window messages.) |
|
| Back to top |
|
 |
caterva
Joined: 09 Apr 2008 Posts: 14
|
Posted: Sun Apr 20, 2008 2:50 pm Post subject: |
|
|
| SKAN wrote: | Okay! I will look into it.  |
Sorry, another problem with your dll.
In a "dos" window, I run "edit newfile.txt" to create a new file and edit its content. The creation is correctly signaled. But when I finish to edit the file I cannot save it. I always receive a "error 32" message. |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|