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 monitor subdirectories created at runtime with the create event.
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 are now no longer receivable $hWatch.Events[Watch.Move] = True ' Instead, the Move event is now of interest
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.
Property | Data type | Description |
---|---|---|
Cookie | Integer | A cookie is used to link events. This is currently (12 July 2018) 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 6.8.2.1.1 : Static properties of the Watch class
Property | Data type | Description |
---|---|---|
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 6.8.2.2.1 : Properties of the class Watch
The Watch class has only these two methods:
Method | Description |
---|---|
Pause | Pauses a Watch object, preventing it from triggering events. |
Resume | Ends the pause mode of a Watch object. |
Table 6.8.2.3.1 : Methods of the Watch class
The Watch class has these events:
Event | Description |
---|---|
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 a read-only manner, 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 6.8.2.4.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 in different ways (open, change, move, delete).
Figure 6.8.3.1: Monitoring protocol
The special feature of this project is the triggering of events by an external script. It must be realised via a task, because on the one hand the monitoring should take place 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 in chapter 20.6.0). If the script were to be 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. Thus, it can no longer be monitored and, in particular, the process does not receive the (past) events that occurred below this subdirectory.
Figure 6.8.3.2: Monitoring log - end
The processing of the directories and the file is done by the own class in ExternalScript.class.
The source code for the presented project is given in full. The source code of the class ExternalScript.class follows:
' 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() 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 "Finished" Flush Do ' Wait for the main process to finish the task 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$() Dim sDir As String = Temp$() FMain.Resizable = False TextArea1.ReadOnly = True $hTmp = New Watch(File.Dir(sFile)) As "Tmp" ' The script must be a task because the events are as close as possible to each other. ' at the time they are triggered. If the script would be executed in this process, ' you received the events after the complete script has run. So could For example, ' you cannot intercept events in the subdirectories created at runtime. $hScript = New ExternalScript(sFile, sDir) As "ExternalScript" Spinner1.Start() End Public Sub ExternalScript_Read(Data As String) If Trim$(Data) <> "Finished" Then Return $hScript.Stop() ' Stops the task as a background process TextArea1.Insert(gb.NewLine & "● ● ● External script terminated. ● ● ●") TextArea1.Background = &HF5FFE6 Spinner1.Stop() Spinner1.Visible = False End Public Sub Tmp_Read() Note("Lesen") End Public Sub Tmp_Create() Dim hSubdir As Watch If Watch.IsDir Then ' The following try is necessary here to intercept a 'race condition': ' The Subdirectory could have been created and deleted before this ' event handler has been called. The New Watch(..) could fail because ' edit a Create event whose subject has already been deleted. Try hSubdir = New Watch(Last.Path &/ Watch.Name) As "Subdir" If Not Error Then Note("New subdirectory") $aSubdirs.Add(hSubdir) Endif Else Note("Create", "initial mode" & Stat(Last.Path &/ Watch.Name).Auth) Endif End Public Sub Tmp_Open() Note("Open") End Public Sub Tmp_Close() Note("Close") End Public Sub Tmp_Write() Note("Write") End Public Sub Tmp_Move() Note("Rename") End Public Sub Tmp_MoveFrom() Note("Rename", "[Source]", Subst$(" (Cookie &1)", Watch.Cookie)) End Public Sub Subdir_MoveTo() Note("Rename", "[Destination]", Subst$(" (Cookie &1)", Watch.Cookie)) End Public Sub Tmp_Delete() Note("Delete") End Public Sub Subdir_Delete() Note(Subst$("Delete &1subdirectory", 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("Data query", Subst$("Modus &1, last accsess &2", .Auth, .LastAccess)) End With Catch ' Last.Path &/ Watch.Name might no longer exist and Stat might fail. End ' The three dots indicate that the Note() procedure has a variable argument list. ' After the mandatory parameter sWhat, k >= 0 other arguments of any type can follow. ' Read more about this at http://gambaswiki.org/wiki/lang/methoddecl and ' http://gambaswiki.org/wiki/comp/gb/param Private Sub Note(sWhat As String, ...) 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) Print Param.Count End Public Sub btnClose_Click() FMain.Close End Public Sub Form_Close() If $hScript.Running Then $hScript.Stop $hTmp = Null $aSubdirs.Clear() Wait 1 End
All monitored events are displayed when they are triggered by the script, as you can see in the monitoring log in Figure 6.8.3.2.
Project