With the component gb.inotify by Tobias Boege you can access the linux-specific interface “inotify” in Gambas programs. Information about this interface can be found at https://wiki.ubuntuusers.de/inotify. The interface allows you to intercept selected file system events. You can thus monitor a path of the file system behind which a file or directory may be located.
However, when using the component, please note the following:
The Watch class (gb.inotify) represents a file system object to be monitored. You can create the class. The class behaves like a static array whose values can only be read. Watching a directory is not recursive for its subdirectories. This means that you only get events from the directory itself and for its immediate entries. However, you can create a separate Watch object for each subdirectory of a base directory and even use the Create event to monitor subdirectories created at runtime.
You can create Watch objects regularly using the New instruction. The signature of the constructor is as follows:
hWatch = New Watch ( Path As String [ , NoFollowLink As Boolean, Events As Integer ] ) As "EventName"
First, the path of the file system entry to be watched is specified. With the optional parameter NoFollowLink you can forbid following symbolic links when interpreting your path. By default, links are followed. The last optional argument Events is a bitmask of events to monitor. Use the constants of the Watch class for this. For example, if you want to monitor the create and delete events of a path sPath, create the Watch object like this:
hWatch = New Watch(sPath, False, Watch.Create + Watch.Delete) As „MyWatch”
If you do not specify the events mask, the Watch class automatically determines all events for which you have created event handlers under the specified event name. If you have entered
Private $hWatch As Watch Public Sub Form_Open() ... $hWatch = New Watch(sPath) As "MyWatch" End Public Sub MyWatch_Create() ... End Public Sub MyWatch_Delete() ... End
the Watch class recognises that you want to intercept Create and Delete events from MyWatch and sets these - and only these - events to receivable. For performance reasons, you should never specify more events in the Events mask than you need. You can later change the receivable events via the Events property of the Watch class. You use this property like an array of Boolean values and index them using the Watch constants:
$hWatch.Events[Watch.Create] = False ' Create-Events sind nun nicht mehr empfangbar $hWatch.Events[Watch.Move] = True ' Stattdessen interessiert nun das Move-Event
Note that the only job of Watch objects is to trigger events! If you forget to give a Watch object an event name, it cannot trigger events and is useless.
The Watch class has four static properties that provide additional information for event handlers and should only be used in such. The static properties are used to store data from the kernel during event handler routines.
|Cookie||Integer||A cookie is used to link events. This is currently (1.10.2015) only necessary for linking MoveFrom and MoveTo events of the same file.|
|IsDir||Boolean||Indicates whether the observation refers or referred to a directory.|
|Name||String||Only applies to monitored directories: Returns the name of the file or subdirectory from which the event originated. The name is relative to the monitored directory. If the directory itself triggered the event, name = zero. For example, if the directory “abc” is monitored and a file with the file name “xyz” is created there, the create event of “abc” is triggered and Name is set to “xyz”.|
|Unmount||Boolean||Returns whether the file system in which the observed path was located was unmounted. If so, the observed object is invalidated immediately after the event is triggered.|
Table 220.127.116.11.1 : Static properties of the Watch class
|Events||.Watch.Events||Returns a virtual class to specify the event watch bitmask. This virtual class is used to manage which events are monitored by a particular Watch object.|
|IsPaused||Boolean||Returns whether the Watch is currently paused (→ methods Pause and Resume).|
|Path||String||Returns the monitored path.|
|Tag||Variant||The use of this property can be freely determined by the Gambas programmer.|
Table 18.104.22.168.1 : Properties of the class Watch
The Watch class has only these two methods:
|Pause||Pauses a Watch object, preventing it from triggering events.|
|Resume||Ends the pause mode of a Watch object.|
Table 22.214.171.124.1 : Methods of the Watch class
The Watch class has these events:
|Close||The event is triggered when the monitored file or a file in the monitored directory is closed.|
|Create||The event is triggered when an entry (file or directory) is created in the monitored directory.|
|Delete||The event is triggered when an entry in the monitored directory or the monitored object itself is deleted.|
|Move||The event is triggered when the monitored object has been moved.|
|MoveFrom||The event is triggered when an entry of the monitored directory is moved (event for the source directory of the move).|
|MoveTo||The event is triggered when an entry is moved to the monitored directory (event for the target directory of a move).|
|Open||The event is triggered when the monitored file or an entry in the monitored directory is opened.|
|Read||The event is triggered when the monitored file or an entry in the monitored directory has been accessed in read-only mode, including execution.|
|Stat||The event is triggered when meta data or file attributes of the monitored object have been changed.|
|Write||The event is triggered when the monitored file or an entry in the monitored directory has been write-accessed.|
Table 126.96.36.199.1 : Events of the class Watch
As you can see, the meaning and interpretation of an event depends on whether a file or directory is being watched.
The project demonstrates the monitoring of selected temporary directories and a temporary file. The file and the directories are created at runtime and edited (open, modified, moved, deleted) in different ways.
Figure 188.8.131.52: Monitoring protocol
The special feature of this project by the author of the component gb.inotify is the triggering of events by an external script. It has to be realised via a task, because on the one hand the monitoring should be as close to real time as possible and on the other hand the task in the project is there to provide for events so that you automatically get to see something when you start the project (→ Task object → Chapter 20.6.0). If the script were executed synchronously, then all editing of the directories and the file would first take place and only later would all monitoring results be received. In the source text you can also see that a separate watch object is created for each new directory and inserted into a watch[ ] array. The corresponding entry in the $aSubDirs array of the type Watch[ ] is removed when the object to be watched has been deleted. If - without a task - the events were only received after the entire script has run through, the created subdirectory would already be deleted when the Gambas process receives its create event. It can therefore no longer be monitored and the process in particular does not receive the events (lying in the past) that have occurred below this subdirectory.
The processing of the directories and the file is done by a separate class → ExternalScript.class.
The source texts are presented in their entirety. The following is the source code in the file ExternalScript.class:
' Gambas class file Inherits Task Private $sFile As String Private $sDir As String Public Sub _new(sFile As String, sDir As String) $sFile = sFile $sDir = sDir End Public Sub Main() ' &1 ist eine Datei, &2 ist ein Verzeichnis, &3 ist eine Unterverzeichnis in &2 Shell Subst$("touch &1; sleep 1; chmod a+w &1; sleep 1;" "touch &3; sleep 1; cat &1; sleep 1;" "echo 'test' > &1; sleep 2;" "mkdir &2; sleep 1; mv &1 &2; sleep 1;" "rm &4; sleep 1; rmdir &2; sleep 1", $sFile, $sDir, File.Dir($sFile), $sDir &/ File.Name($sFile)) Wait Print "Fertig" Flush() Do ' Darauf warten, dass der Hauptprozess den Task beendet Wait 1 Loop End
Source code FMain.class:
' Gambas class file Private $hTmp As Watch Private $aSubdirs As New Watch Private $hScript As ExternalScript Public Sub Form_Open() Dim sFile As String = Temp$(), sDir As String = Temp$() FMain.Center FMain.Resizable = True TextArea1.ReadOnly = True $hTmp = New Watch(File.Dir(sFile)) As "Tmp" ' Erzeugen eines Watch-Objektes ' Das Skript muss ein Task sein, weil Sie ja die Events so nah wie möglich zu der Zeit empfangen ' wollen, zu der sie ausgelöst werden. Wenn das Skript in diesem Prozess ausgeführt würde, ' erhielten Sie die Events gesammelt, nachdem das komplette Skript durchgelaufen ist. So könnten ' Sie zum Beispiel keine Ereignisse in den zur Laufzeit erstellten Unterverzeichnissen abfangen. $hScript = New ExternalScript(sFile, sDir) As "ExternalScript" Spinner1.Start() End Public Sub ExternalScript_Read(Data As String) If Trim$(Data) <> "Fertig" Then Return $hScript.Stop() ' Stoppt den Task als Hintergrund-Prozess TextArea1.Insert(gb.NewLine & "*** Externes Skript beendet. ***") End Public Sub Tmp_Read() Note("Lesen") End ' Tmp_Read() Public Sub Tmp_Create() Dim hSubdir As Watch If Watch.IsDir Then ' Das Try steht hier, um eine 'race condition' abzufangen: Das Unterverzeichnis könnte erstellt und schon ' wieder gelöscht worden sein, bevor dieser Event-Handler aufgerufen wurde. Das New Watch(..) könnte ' fehlschlagen, weil Sie ein Create-Event bearbeiten, dessen Subjekt schon wieder gelöscht wurde. Try hSubdir = New Watch(Last.Path &/ Watch.Name) As "Subdir" If Not Error Then Note("Neues Unterverzeichnis") $aSubdirs.Add(hSubdir) Endif Else Note("Erstellen", "initialer Modus " & Stat(Last.Path &/ Watch.Name).Auth) Endif End Public Sub Tmp_Open() Note("Öffnen") End Public Sub Tmp_Close() Note("Schließen") End Public Sub Tmp_Write() Note("Schreiben") End Public Sub Tmp_Move() Note("Umbenennen") End Public Sub Tmp_MoveFrom() Note(Subst$("Umbenennen [ Quelle ] (Cookie &1)", Watch.Cookie)) End Public Sub Subdir_MoveTo() Note(Subst$("Umbenennen [ Ziel ] (Cookie &1)", Watch.Cookie)) End Public Sub Tmp_Delete() Note("Löschen") End Public Sub Subdir_Delete() Note(Subst$("Löschen &1Unterverzeichnis", IIf(Watch.Name, "in ", ""))) If Not Watch.Name Then $aSubdirs.Remove($aSubdirs.Find(Last)) End Public Sub Tmp_Stat() With Stat(Last.Path &/ Watch.Name) Note("Datenabfrage", Subst$("Modus &1, letzter Zugriff &2", .Auth, .LastAccess)) End With Catch ' Last.Path &/ Watch.Name könnte nicht mehr existieren und Stat könnte deshalb fehlschlagen. End Private Sub Note(sWhat As String, …) ' Information: http://gambaswiki.org/wiki/comp/gb/param Dim sArg As String TextArea1.Insert(sWhat & " " & Last.Path &/ Watch.Name) For Each sArg In Param TextArea1.Insert(" " & sArg) Next TextArea1.Insert(gb.NewLine) TextArea1.Pos = Len(TextArea1.Text) End Public Sub btnClose_Click() FMain.Close() End Public Sub Form_Close() If $hScript.Running Then $hScript.Stop $hTmp = Null $aSubdirs.Clear() Spinner1.Stop() Wait 1 End
All monitored events are displayed when triggered by the script → Figure 184.108.40.206 Monitoring log.