Jump to content

[NOW WORKING] a (broken) monitor file changes script which uses ReadDirectoryChangeW


Zomp
 Share

Recommended Posts

Please note: the idea of this script is not mine, but of AutoHotKey user SKAN:

http://www.autohotkey.com/forum/viewtopic....light=folderspy

The script does very well what it promises: it monitors any file change in a folder (and its subfolders).

I asked to SKAN (and obtained) the permission to convert his script in AutoIt language.

It was not very difficult to traslate the script, but I encountered a problem and I will ask you some help to make it working.

My GUI is more more stark than the original one. In particular, I do not insert changed file rows on the list view, but I call only consolewrite function to signal an event, and I use a "start" button instead of a "start/stop" button.

It seems to me that the other parts of the script are a faithful copies of the original ones.

Nevertheless, the script does not work. Indeed, pay attention at the sleep function at line 137:

1) If I launch the script after uncommenting it, I obtain the "Error parsing funcion call" message

2) If I launch the script leaving it commented, the script proceeds but without signaling any event (see consolewrite function at line 134 which will to be substituted by a call to Decode_FILE_NOTIFY_INFORMATION)

I will be very happy if someone is able to indicate me what has gone wrong.

Thanks for your attention.

#include <GUIConstants.au3>

Global $hDir,$PointerFNI, $SizeOfFNI, $WatchSubdirs, $nReadlen

Global const $FILE_LIST_DIRECTORY = 0x1, _
   $FILE_FLAG_BACKUP_SEMANTICS = 0x2000000, _
   $FILE_FLAG_OVERLAPPED = 0x40000000

;   $FILE_SHARE_READ = 0x1, _
;   $FILE_SHARE_WRITE = 0x2, _
;   $OPEN_EXISTING = 0x3, _
;   $FILE_SHARE_DELETE = 0x4, _
; are already declared in GUIConstants.au3

Global const _
   $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
    
$FNI=DllStructCreate("char  FNI[10000]")

if @error Then
    MsgBox(0,"","Error in DllStructCreate " & @error);
    exit
endif

$T1="00"
for $i=1 to 15
   $T1&=$T1
Next

DllStructSetData($FNI,1,$T1)
if @error Then
    MsgBox(0,"","Error in DllStructSetData " & @error);
    exit
endif

$WatchFolder  = "c:\temp\temp1\"
$WatchSubDirs = True

$SizeOfFNI=0x10000
$PointerFNI=DllStructGetPtr($FNI)

$handle = DLLCallbackRegister ("ReadDirectoryChanges", "Uint", "")   
$aHandle=DllCallbackGetPtr($handle)

Opt("GUIOnEventMode", 1)

$mainwindow = GUICreate("FolderSpy Clone")
$startbutton = GUICtrlCreateButton("Start", 10, 10, 60)
GUICtrlCreateListView("Time|Event|File/Folder Name|Size-KB|TimeStamp [Mod]|Attrib",10,50,350,300,-1)

GUICtrlSetOnEvent($startbutton, "StartButton")

GUISetOnEvent($GUI_EVENT_CLOSE, "CLOSEClicked")

GUISetState(@SW_SHOW)

while 1
   sleep(1000)
Wend
   
exit    

func ReadDirectoryChanges()
   consolewrite("RDC" & @CRLF)

;   http://msdn2.microsoft.com/en-us/library/aa365465.aspx

   $RDC =DllCall( _
      "kernel32.dll","Int","ReadDirectoryChangesW", _
      "UInt" , $hDir, _
      "UInt" , $PointerFNI, _
      "UInt" , $SizeOfFNI, _ 
      "UInt" , $WatchSubDirs, _
      "UInt" , $FILE_NOTIFY_CHANGE_FILE_NAME _
        +$FILE_NOTIFY_CHANGE_DIR_NAME _
        +$FILE_NOTIFY_CHANGE_ATTRIBUTES _
        +$FILE_NOTIFY_CHANGE_SIZE _
        +$FILE_NOTIFY_CHANGE_LAST_WRITE _
        +$FILE_NOTIFY_CHANGE_LAST_ACCESS _
        +$FILE_NOTIFY_CHANGE_CREATION _
        +$FILE_NOTIFY_CHANGE_SECURITY, _
      "UInt*", $nReadLen, _
      "UInt" , 0, _
      "UInt" , 0  _
   )
   
   if not @error=0 then msgbox(0,"ReadDirectoryChangesW",@error)

   Return $RDC
EndFunc
    
func StartButton()
   
  ; CreateFile: http://msdn2.microsoft.com/en-us/library/aa914735.aspx
     
   $hDir=DllCall("kernel32.dll","Int","CreateFile", _
      "Str"  ,  $WatchFolder , _
      "UInt" , $FILE_LIST_DIRECTORY , _
      "UInt" , $FILE_SHARE_READ+$FILE_SHARE_WRITE+$FILE_SHARE_DELETE , _
      "UInt" , 0 , _
      "UInt" , $OPEN_EXISTING , _
      "UInt", $FILE_FLAG_BACKUP_SEMANTICS + $FILE_FLAG_OVERLAPPED , _
      "UInt" , 0 _
   ) 
   
   if not @error=0 then msgbox(0,"CreateFile",@error)

   while true 
      $nReadLen  = 0
      $hThreadId = 0
      
     ; CreateThread   : http://msdn2.microsoft.com/en-us/library/ms682453.aspx

      $hThread   = DllCall( "kernel32.dll", "Int","CreateThread", _
     "UInt",0, _
     "UInt",0, _
     "UInt",$aHandle , _
     "UInt",0, _
     "UInt",0, _
     "UInt*",$hThreadId _
      )

      if not @error=0 then msgbox(0,"CreateThread",@error)

      while true
     If $nReadLen then 
        ConsoleWrite($nreadlen)
        ExitLoop
     endif
    ;Sleep 100
      wend
      
     ; TerminateThread : http://msdn2.microsoft.com/en-us/library/ms686717.aspx
     ; CloseHandle   : http://msdn2.microsoft.com/en-us/library/ms724211.aspx
          
      DllCall( "kernel32.dll","Int","TerminateThread", "UInt",$hThread, "UInt",0 )
      if not @error=0 then msgbox(0,"TerminateThread",@error)

      DllCall( "kernel32.dll","Int","CloseHandle", "UInt",$hThread )  
      if not @error=0 then msgbox(0,"CloseHandle",@error)
   
   wend
             
   Return

EndFunc

func CLOSEClicked()
   exit
EndFunc
Edited by Zomp
Link to comment
Share on other sites

  • Replies 63
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Perhaps I should have clarified what I was replying to.

Indeed, pay attention at the sleep function at line 137:

1) If I launch the script after uncommenting it, I obtain the "Error parsing funcion call" message

2) If I launch the script leaving it commented, the script proceeds but without signaling any event (see consolewrite function at line 134 which will to be substituted by a call to Decode_FILE_NOTIFY_INFORMATION)

Link to comment
Share on other sites

You forgot your parentheses, line 137 should be: Sleep(100)

Yes, you are right. Problem 1) is solved. Thanks.

I am not able yet to understand problem 2), that is why file modifications/aaditions are not notified.

Link to comment
Share on other sites

I don't think you are going to be able to implement your original post because AutoIt is not thread-safe. http://www.autoitscript.com/forum/index.ph...st&p=190906

I suspected so, even if in the script at least one thread is created and a call to function ReadDirectoryChange is made. But evidently this is not sufficient to prove that more than one thread can work. :D

Link to comment
Share on other sites

I read SKAN's post, and saw that he did his this way due to the blocking caused by the Synchronous method used. He said that an Asynchronous method could be used, and then wouldn't need the threads (if I understood right). Look into that.

I will try!

Link to comment
Share on other sites

your code is complete ?

because i don't see anything in the listview

and more than that, the cpu is about 100% when i run it

did i miss something ?

-- Arck System _ Soon -- Ideas make everything

"La critique est facile, l'art est difficile"

Projects :

[list] [*]Au3Service : Run your exe as service V3 / Updated 29/07/2013 Get it Here [/list]
Link to comment
Share on other sites

your code is complete ?

because i don't see anything in the listview

and more than that, the cpu is about 100% when i run it

did i miss something ?

Do not worry about listview content, I have not completed the code for it. Just clic the Start button and check the console.

Anyhow, the script should work, but it does not. The line

ConsoleWrite($nreadlen)
is never invoked.

I'm trying to make some variations using always a call to ReadDirectoryChangeW but without success.

Edited by Zomp
Link to comment
Share on other sites

I did some testing with threads, and, as the Devs who know these things have repeated several times, AutoIt doesn't do well with threads. In a more specific way, once you create a new thread and it starts running, it doesn't give up it's control back to the main thread till it's done, which might be why your not seeing anything (haven't tested your code that much).

Link to comment
Share on other sites

I did some testing with threads, and, as the Devs who know these things have repeated several times, AutoIt doesn't do well with threads. In a more specific way, once you create a new thread and it starts running, it doesn't give up it's control back to the main thread till it's done, which might be why your not seeing anything (haven't tested your code that much).

Yes, also other people did advice me.

In the thread on AHK forum I mention, there is a version of FolderSpy which does not need to create new threads. So I tried to translate it in AutoIt language, but it does not work, too. Maybe I made some mistakes (I am only a newbie), or maybe there are some other problem. Maybe there is someone interested in doing this job better than me.

Edited by Zomp
Link to comment
Share on other sites

Post it.

Sorry, I realized to have lost the code :D

I have tempted to write so many script that I have made a great confusion. Here is another attempt I have made using _DllCallBack

http://www.autoitscript.com/forum/index.php?showtopic=50768

to avoid creation of threads.

#include "DllCallBack.au3"
#include <GUIConstants.au3>

Global $hDir,$PointerFNI, $SizeOfFNI, $WatchSubdirs, $nReadlen

Global const $FILE_LIST_DIRECTORY = 0x1, _
   $FILE_FLAG_BACKUP_SEMANTICS = 0x2000000, _
   $FILE_FLAG_OVERLAPPED = 0x40000000

;   $FILE_SHARE_READ = 0x1, _
;   $FILE_SHARE_WRITE = 0x2, _
;   $OPEN_EXISTING = 0x3, _
;   $FILE_SHARE_DELETE = 0x4, _
; are already declared in GUIConstants.au3

Global const _
   $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
    
$FNI=DllStructCreate("char  FNI[10000]")

if @error Then
    MsgBox(0,"","Error in DllStructCreate " & @error);
    exit
endif

$T1="00"
for $i=1 to 15
   $T1&=$T1
Next

DllStructSetData($FNI,1,$T1)
if @error Then
    MsgBox(0,"","Error in DllStructSetData " & @error);
    exit
endif

$diroverlapped=DllStructCreate("char  diroverlapped[20]")

if @error Then
    MsgBox(0,"","Error in DllStructCreate " & @error);
    exit
endif

$T1="000000000000000000000000000000000"

DllStructSetData($diroverlapped,1,$T1)
if @error Then
    MsgBox(0,"","Error in DllStructSetData " & @error);
    exit
endif

$direvents=DllStructCreate("char  direvents[20]")

if @error Then
    MsgBox(0,"","Error in DllStructCreate " & @error);
    exit
endif

$T1="000000000000000000000000000000000"

DllStructSetData($direvents,1,$T1)
if @error Then
    MsgBox(0,"","Error in DllStructSetData " & @error);
    exit
endif

$WatchFolder  = "c:\temp\temp1\"
$WatchSubDirs = True

$SizeOfFNI=0x10000
$PointerFNI=DllStructGetPtr($FNI)

$SizeOfoverlapped=0x20
$PointerOverlapped=DllStructGetPtr($diroverlapped)

$SizeOfDirevents=0x20
$Pointerdirevents=DllStructGetPtr($direvents)
msgbox(0,"",$Pointerdirevents)

$handle = _DLLCallback("Timer1", "HWND hWnd;UINT uMsg;UINT idEvent;DWORD dwTime")    
;$aHandle=DllCallbackGetPtr($handle)

   $hDir=DllCall("kernel32.dll","Int","CreateFile", _
      "Str"  ,  $WatchFolder , _
      "UInt" , $FILE_LIST_DIRECTORY , _
      "UInt" , $FILE_SHARE_READ+$FILE_SHARE_WRITE+$FILE_SHARE_DELETE , _
      "UInt" , 0 , _
      "UInt" , $OPEN_EXISTING , _
      "UInt", $FILE_FLAG_BACKUP_SEMANTICS + $FILE_FLAG_OVERLAPPED , _
      "UInt" , 0 _
   ) 
   
   if not @error=0 then msgbox(0,"CreateFile",@error)

   $RDC =DllCall( _
      "kernel32.dll","UInt","ReadDirectoryChangesW", _
      "UInt" , $hDir, _
      "UInt" , $PointerFNI, _
      "UInt" , $SizeOfFNI, _ 
      "UInt" , $WatchSubDirs, _
      "UInt" , $FILE_NOTIFY_CHANGE_FILE_NAME _
        +$FILE_NOTIFY_CHANGE_DIR_NAME _
        +$FILE_NOTIFY_CHANGE_ATTRIBUTES _
        +$FILE_NOTIFY_CHANGE_SIZE _
        +$FILE_NOTIFY_CHANGE_LAST_WRITE _
        +$FILE_NOTIFY_CHANGE_LAST_ACCESS _
        +$FILE_NOTIFY_CHANGE_CREATION _
        +$FILE_NOTIFY_CHANGE_SECURITY, _
      "UInt", 0, _
      "UInt" , 0, _
      "UInt" , 0  _
   )
   
   if not @error=0 then msgbox(0,"ReadDirectoryChangesW",@error)

$hTimer1 = _SetTimer($handle, 1000)

while 1
   sleep(1000)
Wend
   
exit    

Func _SetTimer($pTimerFunc, $nTime, $nID = 0, $hWnd = 0)
    Local $aTmp = DllCall("user32.dll", "uint", "SetTimer", "hwnd", $hWnd, "uint", $nID, "uint", $nTime, "ptr", $pTimerFunc)
    If @error Then Return SetError(1, 0, 0)
    If $aTmp[0] = 0 Then SetError(2)
    Return $aTmp[0]
EndFunc  ;==>_SetTimer

Func Timer1($hWnd, $uMsg, $idEvent, $dwTime)
        
;   http://msdn2.microsoft.com/en-us/library/aa365465.aspx

   $r=DllCall("kernel32.dll","UInt", "MsgWaitForMultipleObjectsEx", "UInt", 1, "UInt", $Pointerdirevents _
                                            , "UInt", -1, "UInt", 0x4FF, "UInt", 0x6)

    if $r=0 then; WAIT_OBJECT_*
    
        $nReadLen = 0
        DllCall("kernel32.dll","UInt",  "GetOverlappedResult", "UInt", $hDir _
                    , "UInt", $Pointeroverlapped, "UInt*", $nReadLen, "Int", true )
    consolewrite ("*")
    endif 

   $RDC =DllCall( _
      "kernel32.dll","UInt","ReadDirectoryChangesW", _
      "UInt" , $hDir, _
      "UInt" , $PointerFNI, _
      "UInt" , $SizeOfFNI, _ 
      "UInt" , $WatchSubDirs, _
      "UInt" , $FILE_NOTIFY_CHANGE_FILE_NAME _
        +$FILE_NOTIFY_CHANGE_DIR_NAME _
        +$FILE_NOTIFY_CHANGE_ATTRIBUTES _
        +$FILE_NOTIFY_CHANGE_SIZE _
        +$FILE_NOTIFY_CHANGE_LAST_WRITE _
        +$FILE_NOTIFY_CHANGE_LAST_ACCESS _
        +$FILE_NOTIFY_CHANGE_CREATION _
        +$FILE_NOTIFY_CHANGE_SECURITY, _
      "UInt", 0, _
      "UInt" , 0, _
      "UInt" , 0  _
   )
   
   if not @error=0 then msgbox(0,"ReadDirectoryChangesW",@error)

        if $nreadlen then 
            ConsoleWrite($nreadlen)
        endif
EndFunc  ;==>Timer1
Link to comment
Share on other sites

i'm working on it, and here what i've constated :

- why thread ? thread not needed here

- your filenotifystructure are wrong, as the createfile call

- you need a buffer to get all the changes (i'm blocked here)

- you don't get what type of event occured (creation, deletion, and so on)

if somebody can tell me how to get a buffer in autoit, would be cute :D

here is the C++ code (remark the line FILE_NOTIFY_INFORMATION Buffer[1024] <= How to do that un AutoIt ??)

{
  USES_CONVERSION;

  HANDLE hDir = CreateFile( CString("c:\\Program Files"), // pointer to the file name
    FILE_LIST_DIRECTORY,                // access (read/write) mode
    FILE_SHARE_READ|FILE_SHARE_DELETE,  // share mode
    NULL,                              // security descriptor
    OPEN_EXISTING,                    // how to create
    FILE_FLAG_BACKUP_SEMANTICS,      // file attributes
    NULL                                // file with attributes to copy
  );

  FILE_NOTIFY_INFORMATION Buffer[1024];
  DWORD BytesReturned;
  while( ReadDirectoryChangesW(
                                hDir,                                 // handle to directory
                                &Buffer,                                    // read results buffer
                                sizeof(Buffer),                             // length of buffer
                                TRUE,                                // monitoring option
                                FILE_NOTIFY_CHANGE_SECURITY|
                                FILE_NOTIFY_CHANGE_CREATION|
                                FILE_NOTIFY_CHANGE_LAST_ACCESS|
                                FILE_NOTIFY_CHANGE_LAST_WRITE|
                                FILE_NOTIFY_CHANGE_SIZE|
                                FILE_NOTIFY_CHANGE_ATTRIBUTES|
                                FILE_NOTIFY_CHANGE_DIR_NAME|
                                FILE_NOTIFY_CHANGE_FILE_NAME,            // filter conditions
                                &BytesReturned,           // bytes returned
                                NULL,                         // overlapped buffer
                                NULL// completion routine
                                ))
    {
        CTime tm = CTime::GetCurrentTime();

    CString helper_txt;
    
    switch(Buffer[0].Action)
      {
      case FILE_ACTION_ADDED: helper_txt = "The file was added to the directory"; break; 
      case FILE_ACTION_REMOVED: helper_txt = "The file was removed from the directory"; break; 
      case FILE_ACTION_MODIFIED: helper_txt = "The file was modified. This can be a change in the time stamp or attributes."; break; 
      case FILE_ACTION_RENAMED_OLD_NAME: helper_txt = "The file was renamed and this is the old name."; break; 
      case FILE_ACTION_RENAMED_NEW_NAME: helper_txt = "The file was renamed and this is the new name."; break;
      }
    int i=0;
    do
      {
      m_Sec.Lock();
      int item = pList1->InsertItem(pList1->GetItemCount(), CString(Buffer[i].FileName).Left(Buffer[i].FileNameLength / 2) + " - " + helper_txt );
          pList1->SetItemText(item, 1, tm.Format("%Y/%m/%d/ - %H:%M:%S"));
      i++;
      m_Sec.Unlock();
      }
    while (!Buffer[i].NextEntryOffset);
    }
    }
Edited by arcker

-- Arck System _ Soon -- Ideas make everything

"La critique est facile, l'art est difficile"

Projects :

[list] [*]Au3Service : Run your exe as service V3 / Updated 29/07/2013 Get it Here [/list]
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...