FileSystemWatcher
BlitzMax Forums/BlitzMax Programming/FileSystemWatcher
| ||
This code will detect files and folders that are created, deleted, moved, and renamed in a specified directory and emit events. The only problem is the ReadDirectoryChangesW() does not have a timeout value. There is a way to get asynchronous results from the function, but it's pretty complicated: http://msdn.microsoft.com/en-us/library/aa365465%28VS.85%29.aspx Any ideas on how to do this? I am trying the file completion routine with no luck. SuperStrict Framework pub.win32 Import brl.eventqueue Import brl.standardio Private 'Externs Extern "win32" Function ReadDirectoryChangesW(hDirectory:Int,lpBuffer:Byte Ptr,nBufferLength:Int,bWatchSubtree:Int,dwNotifyFilter:Int,lpBytesReturned:Byte Ptr,lpOverlapped:Byte Ptr,lpCompletionRoutine:Byte Ptr) Function CreateFileA(lpFileName$z,dwDesiredAccess:Int,dwShareMode:Int,lpSecurityAttributes:Byte Ptr,dwCreationDisposition:Int,dwFlagsAndAttributes:Int,hTemplateFile:Int) Function GetOverlappedResult(hFile:Int,lpOverlapped:OVERLAPPED,lpNumberOfBytesTransferred:Byte Ptr,bWait:Int) EndExtern 'Constants Const FILE_FLAG_BACKUP_SEMANTICS:Int=$02000000 Const FILE_FLAG_OVERLAPPED:Int=$40000000 Const GENERIC_READ:Int=$80000000 Const FILE_SHARE_READ:Int=$00000001 Const OPEN_EXISTING:Int=3 Const INVALID_HANDLE_VALUE:Int=-1 Const FILE_SHARE_WRITE:Int=$00000002 Const WAIT_FAILED% = $FFFFFFFF Const WAIT_OBJECT_0% = $0 Const WAIT_ABANDONED% = $80 Const WAIT_TIMEOUT% = $102 Const FILE_NOTIFY_CHANGE_FILE_NAME:Int=$00000001 Const FILE_NOTIFY_CHANGE_DIR_NAME:Int=$00000002 Const FILE_NOTIFY_CHANGE_ATTRIBUTES:Int=$00000004 Const FILE_NOTIFY_CHANGE_SIZE:Int=$00000008 Const FILE_NOTIFY_CHANGE_LAST_WRITE:Int=$00000010 Const FILE_NOTIFY_CHANGE_LAST_ACCESS:Int=$00000020 Const FILE_NOTIFY_CHANGE_CREATION:Int=$00000040 Const FILE_NOTIFY_CHANGE_SECURITY:Int=$00000100 'Types Type OVERLAPPED Field Internal:Int Field InternalHigh:Int Field Offset:Int Field OffsetHigh:Int Field Pointer:Byte Ptr Field hEvent:Int EndType Const FILE_ACTION_ADDED:Int=$00000001 Const FILE_ACTION_REMOVED:Int=$00000002 Const FILE_ACTION_MODIFIED:Int=$00000003 Const FILE_ACTION_RENAMED_OLD_NAME:Int=$00000004 Const FILE_ACTION_RENAMED_NEW_NAME:Int=$00000005 Public 'Event constants Const EVENT_FILECREATED:Int=6870001 Const EVENT_FILEDELETED:Int=6870002 Const EVENT_FILERENAMED:Int=6870003 Const EVENT_FILEMODIFIED:Int=6870004 'FileSystemWatcher type Type TFileSystemWatcher Field path:String Field hfile:Int 'Field overlap:OVERLAPPED=New OVERLAPPED Field recursive:Int Function Create:TFileSystemWatcher(path:String="",recursive:Int=True) Local filesystemwatcher:TFileSystemWatcher=New TFileSystemWatcher filesystemwatcher.path=RealPath(path) filesystemwatcher.recursive=recursive filesystemwatcher.hfile=CreateFileA(path,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,Null,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,0)'|FILE_FLAG_OVERLAPPED Return filesystemwatcher EndFunction Method Check:Int() Local buffer:Byte[1024] Local bytesreturned:Int Local flags:Int Local bytestransferred:Int Local renamedfilepreviousname:String flags:+FILE_NOTIFY_CHANGE_FILE_NAME flags:+FILE_NOTIFY_CHANGE_DIR_NAME 'flags:+FILE_NOTIFY_CHANGE_ATTRIBUTES 'flags:+FILE_NOTIFY_CHANGE_SIZE flags:+FILE_NOTIFY_CHANGE_LAST_WRITE 'flags:+FILE_NOTIFY_CHANGE_LAST_ACCESS 'flags:+FILE_NOTIFY_CHANGE_CREATION 'flags:+FILE_NOTIFY_CHANGE_SECURITY If ReadDirectoryChangesW(Self.hfile,buffer,buffer.length,Self.recursive,flags,Varptr bytesreturned,Null,Null) If bytesreturned Local bank:TBank=CreateBank(bytesreturned) MemCopy(bank.buf(),buffer,bytesreturned) Local stream:TStream=CreateBankStream(bank) Local NextEntryOffset:Int Local Action:Int Local FileNameLength:Int Local FileName:String Local pos:Int While Not stream.Eof() pos=stream.pos() NextEntryOffset=stream.ReadInt() Print "NextEntryOffset: "+NextEntryOffset Action=stream.ReadInt() ' Print "Action: "+action FileNameLength=stream.ReadInt() ' Print "FileNameLength: "+FileNameLength FileName=stream.ReadString(FileNameLength) Local event:TEvent=New TEvent event.source=filename Select action Case FILE_ACTION_ADDED event.id=EVENT_FILECREATED Print "File ~q"+filename+"~q created." Case FILE_ACTION_REMOVED event.id=EVENT_FILEDELETED Print "File ~q"+filename+"~q deleted." Case FILE_ACTION_MODIFIED event.id=EVENT_FILEMODIFIED Print "File ~q"+filename+"~q modified." Case FILE_ACTION_RENAMED_OLD_NAME renamedfilepreviousname=filename event=Null Case FILE_ACTION_RENAMED_NEW_NAME event.id=EVENT_FILERENAMED event.extra=renamedfilepreviousname Print "File renamed from ~q"+renamedfilepreviousname+"~q to ~q"+filename+"~q." renamedfilepreviousname="" EndSelect If event EmitEvent event If NextEntryOffset stream.seek(pos+NextEntryOffset) Else Exit Wend Print "DONE" EndIf EndIf EndMethod EndType 'Example 'Create, delete, and rename files in the app directory, and watch the program output display your changes. Local filesystemwatcher:TFileSystemWatcher=TFileSystemWatcher.Create(AppDir,True) Repeat filesystemwatcher.Check() Delay 10 Forever |
| ||
Nevermind, you edited your question in. :) |
| ||
This is the OVERLAPPED structure:typedef struct _OVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; } ; PVOID Pointer; } ; HANDLE hEvent; } OVERLAPPED, *LPOVERLAPPED; Is this BMX code the same thing?: Type OVERLAPPED Method Delete() CloseHandle(hEvent) EndMethod Field Internal:Int Field InternalHigh:Int Field Offset:Int Field OffsetHigh:Int Field Pointer:Byte Ptr Field hEvent:Int=CreateEventA(0,0,False,"MyEvent") EndType |
| ||
Josh, this isn't the same. You *have* to match the type sizes listed, ie. add up the sizes of the fields in the C struct and make sure your Blitz type matches it. Typing while drunk, but the ULONG_PTR values in OVERLAPPED would probably be OK as "Byte Ptr". The union here is two Ints -- it can be accessed as integer values Offset and OffsetHigh in C/C++ (2 x 4 bytes) or as Pointer:Byte Ptr (treating Offset as a Byte Ptr, ignoring OffsetHigh). With unions, the same block of memory is used for two different possible values, depending on what the programmer wants to access. (The union is 8 bytes here -- Offset:Int plus OffsetHigh:Int, or, using the first 4 bytes only, Pointer:Byte Ptr, that is Pointer is at the same offset as Internal.) If it helps, the union looks like this in memory (two 4-byte ints in a row), and you can access it as version #1 or version #2: #1 [Offset: 00000000] [OffsetHigh: 00000000] #2 [Pointer: 00000000] [Ignored: 00000000] In C/C++ you'd just name the field you want to access (Internal/InternalHigh or Pointer), while in Blitz, you'd have to declare the two int fields as normal, access them as Offset and OffsetHigh, or treat the Offset offset in the object as a Byte Ptr, ie. the Pointer field in the struct), depending on the circumstances. I think the rest looks OK. Your Delete method is basically trying to cram in a 4-byte function pointer before the rest of the fields. Think of the C-struct as a fixed-size block of memory with a specific format -- add up the byte-sizes of the fields -- and you should see why this won't work. Converting C/C++ byte/short fields to Blitz can be tricky, but these are all 4-byte fields, which simplifies things greatly. God, I hope that makes some kind of sense. |
| ||
Can you just type what the BMX equivalent should be? The code is working here, but I would like your help to make sure that class is right: http://blitzmax.com/codearcs/codearcs.php?code=2747 |
| ||
Sorry for the delay -- I believe this would be right:Rem struct _OVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; } ; PVOID Pointer; } ; HANDLE hEvent; } OVERLAPPED, *LPOVERLAPPED; End Rem Type OVERLAPPED Field Internal:Byte Ptr Field InternalHigh:Byte Ptr Field Offset:Int Field OffsetHigh:Int Field hEvent:Int End Type Notice no "Pointer" field -- the union means it occupies the same space as Offset. The relevant function docs would tell you whether to treat this field as Offset or Pointer, depending on the scenario. Blitz doesn't do unions, so you'd have to treat the value in the Offset field as a Byte Ptr if you're told to use the Pointer field. You could probably just tack your Method on to the end, though theoretically Microsoft could expand the structure in future, in which case it would fail. Sorry, I mixed up Internal/InternalHigh and Offset/OffsetHigh last night in my post, which wouldn't have helped -- edited. |
| ||
Thanks! I also updated the code to handle the unicode strings. This is one of the coolest little routines I have seen: http://blitzmax.com/codearcs/codearcs.php?code=2747 |
| ||
Interesting.. wxMax does it too - in a cross-platform way. Yay to cool new things! |
| ||
I think your Internal/InternalOffset should be :Byte Ptr, but I guess it doesn't matter if you're not accessing the structure yourself -- four bytes is four bytes as far as Win32 is concerned. |
| ||
How does the wxMax code work? Is it using some OS feature, or does it do something funny like a constant file time check? |