AutoHotkey Community

It is currently May 25th, 2012, 4:50 pm

All times are UTC [ DST ]




Post new topic Reply to topic  [ 97 posts ]  Go to page 1, 2, 3, 4, 5 ... 7  Next
Author Message
PostPosted: September 6th, 2007, 2:35 pm 
Offline
User avatar

Joined: December 26th, 2005, 4:40 pm
Posts: 8775
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!

Image

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

;:   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -


:)


Last edited by SKAN on March 4th, 2008, 7:05 pm, edited 1 time in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 7th, 2007, 12:14 am 
Offline

Joined: March 9th, 2007, 2:47 am
Posts: 509
Location: Unknown
Cool!


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 17th, 2007, 4:06 pm 
Offline

Joined: April 22nd, 2007, 6:33 pm
Posts: 1832
8)
pretty cool skan.....

I'd asked for help with this a while ago but couldnt get it to work, and now youve just gone and done it all!

I like it a lot and would love to use it in my programs :D


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 21st, 2007, 4:01 am 
Offline

Joined: January 7th, 2007, 1:43 pm
Posts: 107
well down!
I like it! : 8) :lol: :P :P
Your anothor scripts from *.tw cannot be download now.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 21st, 2007, 7:37 am 
Offline
User avatar

Joined: December 26th, 2005, 4:40 pm
Posts: 8775
poetbox wrote:
Your anothor scripts from *.tw cannot be download now.


:?: :roll:


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 21st, 2007, 10:06 am 
Offline

Joined: January 7th, 2007, 1:43 pm
Posts: 107
Its http://www.autohotkey.com/forum/topic21630.html :P :lol:


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 21st, 2007, 10:09 am 
Offline
User avatar

Joined: December 26th, 2005, 4:40 pm
Posts: 8775
I confirm you that it works for me .. and BTW it is Windows SPY. :)


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 1st, 2007, 10:52 am 
Offline

Joined: April 22nd, 2007, 6:33 pm
Posts: 1832
little question skan. how would you go about monitoring more than 1 drive? say you wish to watch the C:\ D:\ and E:\ drive simultaneously. cheers


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 1st, 2007, 11:26 am 
start script 3 times :D


Report this post
Top
  
Reply with quote  
 Post subject:
PostPosted: October 1st, 2007, 11:30 am 
Offline

Joined: April 22nd, 2007, 6:33 pm
Posts: 1832
majkinetor ! wrote:
start script 3 times :D


:? thats not really a viable option, as I wish to integrate it into one of my programs (MOVEit) and the user can currently specify as many rules as they want, so it wouldn't really be an option to run a new exe as many times as there are rules, as the exe would need to be created first and then there would be loads running!


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 1st, 2007, 12:29 pm 
Offline

Joined: May 24th, 2006, 2:49 pm
Posts: 4511
Location: Belgrade
That is the only option for you as this script creates thread. As far as my tests can say, creating threads in AHK is bug prone and if I remember correctly Skan said that creating more then 3 will crash AHK. In my case results are even worst, single thread randomly crashes AHK, which seems to depend on what function you execute.

Doing it without threads will block your script.

So, the only viable solution is to use original script aunch it N times externaly, and transmit data to your script. Check out IPC module for details about interprocess communication.

This is ofcourse far from sophisticated as N processes will run for N "rules", but we are out of domain of AHK here.

Perhaps AHK can be updated to support creating threads in safe way, the way that wouldn't crash it, via some command similar to RegisterCallback, but that is for the wish list, if possible at all.

_________________
Image


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 1st, 2007, 1:55 pm 
Offline
User avatar

Joined: December 26th, 2005, 4:40 pm
Posts: 8775
tic wrote:
say you wish to watch the C:\ D:\ and E:\ drive simultaneously


You may call the ReadDirectoryChangesW as many times in Asynchronous mode with a SetTimer or in a Loop. In Asynchronous mode, there is no wait state and the structure FILE_NOTIFY_INFORMATION will contain folder events that occured between the calls.
However, you have to use your own methods to decide whether some folder event had occured during the function calls:

I am able to think of two methods:

1) Hash the stucture after every call and if changed call the routine Decode_FILE_NOTIFY_INFORMATION
2) Use FindNextChangeNotification / FindNextChangeNotification to detect changes and on detection call ReadDirectoryChangesW followed by Decode_FILE_NOTIFY_INFORMATION

Synchronous Vs Asynchronous
Which method you really need depends on your actual usage.
Synchronous method is superior when timing is very critical.
For example some X program fileinstalls a file, loads it and deletes it.
In Synchronous mode, I might be able to duplicate a copy of the file before it is deleted.

Otherwise, most of the time Asynchronous mode should suffice

Let me know if you are interested and I will post an example for Asynchronous mode.

:)


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 2nd, 2007, 12:45 am 
Offline

Joined: December 4th, 2006, 10:35 am
Posts: 561
Location: Galil, Israel
Quote:
how would you go about monitoring more than 1 drive?


likely there are better ways, and did not extensively test it, but seemed to work fine for me:



set up multiple callback functions, each using a DIFFERENT refrence for the directory to watch, but using all else the same.


call each of them (multiple thread creation) same as single is called now.



worked for me with 10 threads at once... no crash... etc.



likely ways to improve upon it,

note that seems many standard AHK functions/commands will cause termination of thread after execution of that one command......

_________________
Joyce Jamce


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 2nd, 2007, 1:40 am 
Offline

Joined: April 22nd, 2007, 6:33 pm
Posts: 1832
Quote:
Let me know if you are interested and I will post an example for Asynchronous mode.


I am extremely interested!

Quote:
set up multiple callback functions, each using a DIFFERENT refrence for the directory to watch, but using all else the same.


I went and simplified skans original script to just show the changes as tooltips to make it easier for me to understand his code. would you be able to show me how you created N threads.

Code:
#Persistent
SetBatchLines, -1
Process, Priority,, High
OnExit, ShutApp

WatchFolder  := "C:\"
WatchSubDirs := "1"

EventString := "New File,Deleted,Modified,Renamed From,Renamed To"
StringSplit, EventArray, EventString, `,

DllCall("shlwapi\PathAddBackslashA", UInt, &Watchfolder)

CBA_ReadDir := RegisterCallback("ReadDirectoryChanges")

SizeOf_FNI := ( 64KB := 1024 * 64 )
VarSetCapacity( FILE_NOTIFY_INFORMATION, SizeOf_FNI, 0 )
PointerFNI := &FILE_NOTIFY_INFORMATION

hDir := DllCall( "CreateFile", Str  , WatchFolder, UInt, "1", UInt , "7", UInt, 0, UInt, "3", UInt, "1107296256", UInt , 0 )

Loop
{
   nReadLen  := 0
   hThreadId := 0

   hThread   := DllCall( "CreateThread", UInt, 0, UInt, 0, UInt, CBA_ReadDir, UInt, 0, UInt,0, UIntP,hThreadId )
                  
   Loop
   {
      If nReadLen
      {
         PointerFNI := &FILE_NOTIFY_INFORMATION

         Loop
         {
            NextEntry   := NumGet( PointerFNI + 0  )
            Action      := NumGet( PointerFNI + 4  )
            FileNameLen := NumGet( PointerFNI + 8  )
            FileNamePtr :=       ( PointerFNI + 12 )
   
            Event := EventArray%Action%      

            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)
            Tooltip, %Time%`n%Event%`n%File%
            Else
            {
               Loop %FullPath%
               {
                  FormatTime, TStamp, %A_LoopFileTimeModified%, yyyy-MM-dd  HH:mm:ss
                  Tooltip, %Time%`n%Event%`n%File%`n%A_LoopFileSizeKB%`n%TStamp%`n%A_LoopFileAttrib%
               }
            }   

            If !NextEntry
            Break
            Else
            PointerFNI += NextEntry
         }
         Break
      }           
   
      Sleep 100
   }

   DllCall( "TerminateThread", UInt,hThread, UInt,0 )
   DllCall( "CloseHandle", UInt,hThread )
}
Return

ReadDirectoryChanges()
{
   Global hDir,PointerFNI, Sizeof_FNI, WatchSubdirs, nReadlen
   Return DllCall( "ReadDirectoryChangesW", UInt, hDir, UInt, PointerFNI, UInt, SizeOf_FNI, UInt, WatchSubDirs, UInt, "375", UIntP, nReadLen, UInt, 0, UInt, 0 )
}

GuiClose:
ShutApp:
DllCall( "CloseHandle", UInt,hDir )
DllCall( "TerminateThread", UInt,hThread, UInt,0 )
DllCall( "CloseHandle", UInt,hThread )
ExitApp
Return


Last edited by tic on October 2nd, 2007, 10:26 am, edited 1 time in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 2nd, 2007, 5:38 am 
Offline
User avatar

Joined: December 26th, 2005, 4:40 pm
Posts: 8775
Joy2DWorld wrote:
likely there are better ways, and did not extensively test it, but seemed to work fine for me:set up multiple callback functions, each using a DIFFERENT refrence for the directory to watch, but using all else the same.


I tried callback but it never worked for me. Can you post some working example. It would be very useful.

@tic: Please allow me sometime.

:)


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 97 posts ]  Go to page 1, 2, 3, 4, 5 ... 7  Next

All times are UTC [ DST ]


Who is online

Users browsing this forum: Bon, Jaaaaaaaaay, Rajat, XX0 and 17 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group