Jump to content


Photo

detect file and directory changes


  • Please log in to reply
34 replies to this topic

#1 shimanov

shimanov
  • Members
  • 610 posts

Posted 05 January 2006 - 04:45 AM

The included code encapsulates and demonstrates an OS facility to detect changes to files and directories. The required functions are present with all versions of Windows.

The monitored events are:
* FILE_NAME
* DIR_NAME
* ATTRIBUTES
* SIZE
* LAST_WRITE
* SECURITYnote: further information can be found at MSDN

#Persistent

OnExit, HandleExit

; note: event(s) can be present in any combination
FindFirstChangeNotification( "testdir", true, "FILE_NAME|DIR_NAME|ATTRIBUTES|SIZE|LAST_WRITE|SECURITY", handles, array_handles )

SetTimer, timer_DetectChange, 1000
return

HandleExit:
	FindCloseChangeNotification( handles )
ExitApp

timer_DetectChange:
	loop,
	{
		index := WaitForMultipleObjects( array_handles )
		if ( index >= 1 and index <= 6 )
		{
			; note: index number correlates with order of event present during call
			;	to FindFirstChangeNotification
			if ( index = 1 )
				MsgBox, change has been detected: FILE_NAME
			else if ( index = 2 )
				MsgBox, change has been detected: DIR_NAME
			else if ( index = 3 )
				MsgBox, change has been detected: ATTRIBUTES
			else if ( index = 4 )
				MsgBox, change has been detected: SIZE
			else if ( index = 5 )
				MsgBox, change has been detected: LAST_WRITE
			else if ( index = 6 )
				MsgBox, change has been detected: SECURITY
				
			FindNextChangeNotification( handles, index )
		}
		else
			break
	}
return

EncodeInteger( p_value, p_size, p_address, p_offset )
{
	loop, %p_size%
		DllCall( "RtlFillMemory"
			, "uint", p_address+p_offset+A_Index-1
			, "uint", 1
			, "uchar", ( p_value >> ( 8*( A_Index-1 ) ) ) & 0xFF )
}

; return value: true = success, false = failure
FindFirstChangeNotification( p_path, p_recurse, p_events, byref r_handles, byref r_array_handles )
{
	r_handles=
	VarSetCapacity( r_array_handles, 25, 0 )

	count = 0
	loop, parse, p_events, |
	{
		count++
	
		if ( A_LoopField = "FILE_NAME" )
			event = 1
		else if ( A_LoopField = "DIR_NAME" )
			event = 2
		else if ( A_LoopField = "ATTRIBUTES" )
			event = 4
		else if ( A_LoopField = "SIZE" )
			event = 8
		else if ( A_LoopField = "LAST_WRITE" )
			event = 0x10
		else if ( A_LoopField = "SECURITY" )
			event = 0x100
	
		h_notification := DllCall( "FindFirstChangeNotification", "str", p_path, "int", p_recurse, "uint", event )
		if ( ErrorLevel or h_notification = -1 )
		{
			MsgBox, [FindFirstChangeNotification] failed: EL = %ErrorLevel% ~ h_notification = %h_notification%
			return, false
		}
		
		r_handles = %r_handles%|%h_notification%
		
		EncodeInteger( h_notification, 4, &r_array_handles, 1+( A_Index-1 )*4 )
	}
	
	r_handles = %r_handles%|
	
	EncodeInteger( count, 1, &r_array_handles, 0 )
	
	return, true
}

; return value: true = success, false = failure
FindNextChangeNotification( p_handles, p_index )
{
	StringGetPos, ix, p_handles, |, L%p_index%
	StringMid, handle, p_handles, ix+2, InStr( p_handles, "|", true, ix+2 )-( ix+2 )
	
	return, DllCall( "FindNextChangeNotification", "uint", handle )
}

; return value: true = success, false = failure
FindCloseChangeNotification( p_handles )
{
	StringMid, p_handles, p_handles, 2, StrLen( p_handles )-2

	result := true
	loop, parse, p_handles, |
		result := result && DllCall( "FindCloseChangeNotification", "uint", A_LoopField )
		
	return, result
}

; return value: [1,6], otherwise failure
WaitForMultipleObjects( byref r_array_handles )
{
	result := DllCall( "WaitForMultipleObjects", "uint", *( &r_array_handles ), "uint", &r_array_handles+1, "int", false, "uint", 0 )
	
	if ( result >= 0 and result < 6 )
		return, result+1
	else
		return, result
}


#2 Chris

Chris
  • Administrators
  • 10727 posts

Posted 05 January 2006 - 01:31 PM

Something like this should probably be included in the standard library when that feature finally appears.

Thanks for your R&D and friendly implementation.

#3 not-logged-in-daonlyfreez

not-logged-in-daonlyfreez
  • Guests

Posted 05 January 2006 - 01:44 PM

Very nice! :)

Would it be possible to return not only a change, but also which files/folders changed and what change was detected on them?

#4 kapege.de

kapege.de
  • Members
  • 192 posts

Posted 05 January 2006 - 05:35 PM

Fine work!

Buuuut: The polling is the main problem. To ask the drives every second for changes isn't the state of art. Sorry pal. Windows has an inbuilt function which broadcasts any changes of the file system. Sorrily I don't know much about the file system and DLL-calls, but it should be possible to intercept those messages and react.

(Sorry for the unwanted early sending)

#5 shimanov

shimanov
  • Members
  • 610 posts

Posted 05 January 2006 - 06:02 PM

Thanks for your R&D and friendly implementation.


Sure. Happy to contribute.

Would it be possible to return not only a change, but also which files/folders changed and what change was detected on them?


There is another Windows function, ReadDirectoryChangesW, but it requires a callback function and is only present with Windows NT 4.0 and later. Otherwise, identification of changes will require a custom implementation. You may find useful information in another thread, Storing MD5 Hash in a Variable using DllCall().

ask the drives every second for changes


I cannot say with absolute certainty, but I don't believe this is the method employed. It is more likely there is code in the path for file system access, which monitors for those changes configured by calling FindFirstChangeNotification. When an event matching that requested occurs, a flag is set and the invoking process will be notified. I used the WaitForMultipleObjects function precisely because it permits polling and can be used within AHk. Review RegisterWaitForSingleObject for a fully asynchronous method to receive notification. There are other methods, but they are outside the scope of this forum.

To verify my assertion, use File Monitor from Sysinternals, which captures file system access at a lower (driver) level.

#6 Laszlo

Laszlo
  • Fellows
  • 4713 posts

Posted 05 January 2006 - 06:34 PM

I get an error (h_notification = -1) in FindFirstChangeNotification. What does it mean?

#7 shimanov

shimanov
  • Members
  • 610 posts

Posted 05 January 2006 - 07:34 PM

I get an error (h_notification = -1) in FindFirstChangeNotification. What does it mean?


Have you verified that h_notification = -1 or ErrorLevel is non-zero? It would also help if you post the code used to call FindFirstChangeNotification.

If the function fails, the return value is INVALID_HANDLE_VALUE = -1. To get extended error information, call GetLastError.


Unfortunately, GetLastError does not return relevant information when called from AHk using DllCall. You can try the same calls from a native program (e.g., C program) to perform further diagnostics.

#8 Laszlo

Laszlo
  • Fellows
  • 4713 posts

Posted 10 January 2006 - 12:07 AM

I get an error (h_notification = -1) in FindFirstChangeNotification. What does it mean?

It meant, the directory "testdir" did not exist. My fault! When I changed it to "c:\t", which does exist, everything works!

#9 AHKnow

AHKnow
  • Members
  • 121 posts

Posted 13 March 2006 - 03:37 AM

Just seen this. Very nice.

#10 AHKnow*

AHKnow*
  • Guests

Posted 14 March 2006 - 08:42 PM

Very nice! :)

Would it be possible to return not only a change, but also which files/folders changed and what change was detected on them?


I agree. Specific information is needed to make this script more useful. You need to know which files and what change.

#11 majkinetor

majkinetor
  • Fellows
  • 4511 posts

Posted 13 September 2006 - 10:43 AM

Nice.

#12 Mystiq

Mystiq
  • Members
  • 83 posts

Posted 18 May 2007 - 01:13 PM

Is it possible to monitor multiple directories?

Ive checked the code but im not really sure, might be out of my expertise i think. :)

update:
Nevermind, i think i got it. Just awesome. :)

#13 majkinetor

majkinetor
  • Fellows
  • 4511 posts

Posted 18 May 2007 - 01:54 PM

 y

#14 Mystiq

Mystiq
  • Members
  • 83 posts

Posted 18 May 2007 - 07:50 PM

After lot of confusion ive managed to "wrap" these with new functions so that im easily able to monitor as many directories as i need but im wondering if its "recommended" to set up a lot of them?

Mainly im thinking of not using the recursive option so i can "limit" my search for the file(s) that have been added/changed. Basically, have directory and its subfolders all separate.

Any thoughts on this?

#15 Mystiq

Mystiq
  • Members
  • 83 posts

Posted 19 May 2007 - 01:02 PM

Desided not to use mutliple instances for each directory but theres one question.

Does this capture all the changes? What ive tested so far, these functions only capture up to two changes max. I mean that if i create two new files i get two file_name events. If i make more than two, it still gives only two file_name events. Is this normal?

edit:
Bit of searching i think found the answer. It seems that theres is a maximum of 2 per event. According to this website:

<!-- m -->http://netez.com/2xE...bas_xplore.html<!-- m -->