Mit der Komponente gb.inotify von Tobias Boege können Sie in Gambas-Programmen auf die linux-spezifische Schnittstelle „inotify“ zugreifen. Informationen zu dieser Schnittstelle finden Sie unter https://wiki.ubuntuusers.de/inotify. Die Schnittstelle ermöglicht es Ihnen, ausgewählte Dateisystem-Ereignisse abzufangen. Sie können so einen Pfad des Dateisystems überwachen, hinter dem eine Datei oder ein Verzeichnis stehen kann.
Beim Einsatz der Komponente ist allerdings das Folgende zu beachten:
Die Klasse Watch (gb.inotify) repräsentiert ein zu überwachendes Dateisystem-Objekt. Sie können die Klasse erzeugen. Die Klasse verhält sich wie ein statisches Array, dessen Werte nur gelesen werden können. Das Beobachten eines Verzeichnisses erfolgt nicht rekursiv für seine Unterverzeichnisse. Das bedeutet, dass Sie nur Ereignisse aus dem Verzeichnis selbst und für seine unmittelbaren Einträge erhalten. Man kann aber für jedes Unterverzeichnis eines Basisverzeichnisses ein separates Watch-Objekt anlegen und mit dem Create-Ereignis sogar zur Laufzeit erzeugte Unterverzeichnisse überwachen.
Sie können Watch-Objekte regulär über die New-Instruktion erstellen. Die Signatur des Konstruktors ist folgende:
hWatch = New Watch ( Path As String [ , NoFollowLink As Boolean, Events As Integer ] ) As "EventName"
Zuerst wird der Pfad des zu überwachenden Dateisystem-Eintrags angegeben. Mit dem optionalen Parameter NoFollowLink können Sie das Verfolgen symbolischer Links bei der Interpretation Ihres Pfades verbieten. Standardmäßig werden Links verfolgt. Das letzte optionale Argument Events ist eine Bitmaske von zu überwachenden Ereignissen. Benutzen Sie hierfür die Konstanten der Watch-Klasse. Wenn Sie zum Beispiel die Create- und Delete-Ereignisse eines Pfades sPath überwachen wollen, dann erzeugen Sie das Watch-Objekt so:
hWatch = New Watch(sPath, False, Watch.Create + Watch.Delete) As „MyWatch”
Geben Sie die Events-Maske nicht an, ermittelt die Watch-Klasse selbstständig alle Events, für die Sie Event-Handler unter dem angegebenen Event-Namen angelegt haben. Haben Sie
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
deklariert, so erkennt die Watch-Klasse, dass Sie Create- und Delete-Ereignisse von MyWatch abfangen möchten und stellt diese – und nur diese – Ereignisse auf empfangbar. Sie sollten aus Performanzgründen nie mehr Ereignisse in der Events-Maske angeben als Sie benötigen. Über die Events-Eigenschaft der Watch-Klasse können Sie später die empfangbaren Events verändern. Sie verwenden diese Eigenschaft wie ein Array von Boolean-Werten und indizieren Sie mittels der Watch-Konstanten:
$hWatch.Events[Watch.Create] = False ' Create-Events sind nun nicht mehr empfangbar $hWatch.Events[Watch.Move] = True ' Stattdessen interessiert nun das Move-Event
Beachten Sie, dass die einzige Aufgabe von Watch-Objekten das Auslösen von Events ist! Wenn Sie vergessen, einem Watch-Objekt einen Event-Namen zu geben, kann es keine Events auslösen und ist nutzlos.
Die Klasse Watch besitzt vier statische Eigenschaften, die Zusatzinformationen für Event-Handler liefern und nur in solchen verwendet werden sollten. Die statischen Eigenschaften werden verwendet, um Daten aus dem Kernel während der Ereignisbehandlungsroutinen zu speichern.
Eigenschaft | Datentyp | Beschreibung |
---|---|---|
Cookie | Integer | Ein Cookie wird verwendet, um Ereignisse zu verknüpfen. Das ist gegenwärtig (1.10.2015) nur für die Verknüpfung von MoveFrom- und MoveTo-Ereignissen der selben Datei nötig. |
IsDir | Boolean | Gibt an, ob sich die Beobachtung auf ein Verzeichnis bezieht oder bezog. |
Name | String | Gilt nur für überwachte Verzeichnisse: Zurückgegeben wird der Name der Datei oder des Unterverzeichnisses, von dem das Ereignis ausging. Der Name ist relativ zum überwachten Verzeichnis. Wenn das Verzeichnis selbst das Ereignis ausgelöst hat, ist Name = Null. Wird beispielsweise das Verzeichnis „abc“ überwacht und dort eine Datei mit dem Dateinamen „xyz“ erzeugt, wird das Create-Event von „abc“ ausgelöst und Name auf „xyz“ gesetzt. |
Unmount | Boolean | Gibt zurück, ob das Dateisystem ausgehängt wurde, in dem der beobachtete Pfad lag. In diesem Fall wird das Beobachtungsobjekt unmittelbar nach dem Auslösen des Ereignisses für ungültig erklärt. |
Tabelle 19.4.2.1.1 : Statische Eigenschaften der Klasse Watch
Eigenschaft | Datentyp | Beschreibung |
---|---|---|
Events | .Watch.Events | Gibt eine virtuelle Klasse zurück, um die Bitmaske der Ereignisüberwachung anzugeben. Diese virtuelle Klasse wird verwendet, um zu verwalten, welche Ereignisse von einem bestimmten Watch-Objekt überwacht werden. |
IsPaused | Boolean | Gibt zurück, ob der Watch momentan pausiert (→ Methoden Pause und Resume). |
Path | String | Gibt den überwachten Pfad zurück. |
Tag | Variant | Den Einsatz dieser Eigenschaft kann der Gambas-Programmierer frei bestimmen. |
Tabelle 19.4.2.2.1 : Eigenschaften der Klasse Watch
Die Klasse Watch verfügt nur über diese zwei Methoden:
Methode | Beschreibung |
---|---|
Pause | Lässt ein Watch-Objekt pausieren und hindert es so daran, Ereignisse auszulösen. |
Resume | Beendet den Pause-Modus eines Watch-Objekts. |
Tabelle 19.4.2.3.1 : Methoden der Klasse Watch
Die Klasse Watch verfügt über diese Ereignisse:
Ereignis | Beschreibung |
---|---|
Close | Das Ereignis wird ausgelöst, wenn die überwachte Datei oder eine Datei im überwachten Verzeichnis geschlossen wird. |
Create | Das Ereignis wird ausgelöst, wenn ein Eintrag (Datei oder Verzeichnis) im überwachten Verzeichnis erzeugt wird. |
Delete | Das Ereignis wird ausgelöst, wenn ein Eintrag im überwachten Verzeichnis oder das überwachte Objekt selbst gelöscht wurde. |
Move | Das Ereignis wird ausgelöst, wenn das überwachte Objekt verschoben wurde. |
MoveFrom | Das Ereignis wird ausgelöst, wenn ein Eintrag des überwachten Verzeichnisses verschoben wird (Ereignis für das Quellverzeichnis der Verschiebung). |
MoveTo | Das Ereignis wird ausgelöst, wenn ein Eintrag in das überwachte Verzeichnis verschoben wird (Ereignis für das Zielverzeichnis einer Verschiebung). |
Open | Das Ereignis wird ausgelöst, wenn die überwachte Datei oder ein Eintrag im überwachten Verzeichnis geöffnet wird. |
Read | Das Ereignis wird ausgelöst, wenn auf die überwachte Datei oder einen Eintrag im überwachten Verzeichnis lesend zugegriffen wurde, was u.a. auch das Ausführen einschließt. |
Stat | Das Ereignis wird ausgelöst, wenn Meta-Daten oder Datei-Attribute des überwachten Objekts verändert wurden. |
Write | Das Ereignis wird ausgelöst, wenn auf die überwachte Datei oder einen Eintrag im überwachten Verzeichnis schreibend zugegriffen wurde. |
Tabelle 19.4.2.4.1 : Ereignisse der Klasse Watch
Wie Sie sehen, hängt die Bedeutung und Interpretation eines Ereignisses davon ab, ob eine Datei oder ein Verzeichnis überwacht wird.
Das Projekt demonstriert die Überwachung von ausgewählten temporären Verzeichnissen und einer temporären Datei. Die Datei und die Verzeichnisse werden zur Laufzeit angelegt und auf unterschiedliche Weise bearbeitet (öffnen, geändert, verschoben, gelöscht).
Abbildung 19.4.3.1: Überwachungsprotokoll
Das Besondere an diesem Projekt vom Autor der Komponente gb.inotify ist das Auslösen von Ereignissen durch ein externes Skript. Es muss über einen Task realisiert werden, denn einerseits soll die Überwachung möglichst zeitnah erfolgen und andererseits ist der Task im Projekt dazu da, um für Ereignisse zu sorgen, damit man automatisch etwas zu sehen bekommt, wenn man das Projekt startet (→ Task-Objekt → Kapitel 20.6.0). Würde das Skript synchron ausgeführt, dann würden erst alle Bearbeitungen der Verzeichnisse und der Datei erfolgen und später erst alle Überwachungsergebnisse erhalten. Im Quelltext erkennen Sie auch, dass zu jedem neuen Verzeichnis ein eigenes Watch-Objekt erzeugt wird und in ein Watch[ ]-Array eingefügt wird. Der entsprechende Eintrag im Array $aSubDirs vom Typ Watch[ ] wird entfernt, wenn das zu beobachtende Objekt gelöscht wurde. Wenn man – ohne Task – die Ereignisse erst erhalten würde, nachdem das gesamte Skript durchgelaufen ist, wäre das erstellte Unterverzeichnis bereits gelöscht, wenn der Gambas-Prozess sein Create-Ereignis empfängt. Es kann also nicht mehr überwacht werden und der Prozess erhält insbesondere nicht die (in der Vergangenheit liegenden) Ereignisse, die sich unterhalb dieses Unterverzeichnisses ereignet haben.
Die Bearbeitung der Verzeichnisse und der Datei leistet eine eigene Klasse → ExternalScript.class.
Die Quelltexte werden komplett vorgestellt. Es folgt der Quelltext in der Datei 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 ' _new(sFile As String, sDir As String) 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 ' Main()
Quelltext 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 ' Form_Open() 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 ' ExternalScript_Done() 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 ' Watch.IsDir ? End ' Tmp_Create() Public Sub Tmp_Open() Note("Öffnen") End ' Tmp_Open() Public Sub Tmp_Close() Note("Schließen") End ' Tmp_Close() Public Sub Tmp_Write() Note("Schreiben") End ' Tmp_Write() Public Sub Tmp_Move() Note("Umbenennen") End ' Tmp_Move() Public Sub Tmp_MoveFrom() Note(Subst$("Umbenennen [ Quelle ] (Cookie &1)", Watch.Cookie)) End ' Tmp_MoveFrom() Public Sub Subdir_MoveTo() Note(Subst$("Umbenennen [ Ziel ] (Cookie &1)", Watch.Cookie)) End ' Subdir_MoveTo() Public Sub Tmp_Delete() Note("Löschen") End ' Tmp_Delete() Public Sub Subdir_Delete() Note(Subst$("Löschen &1Unterverzeichnis", IIf(Watch.Name, "in ", ""))) If Not Watch.Name Then $aSubdirs.Remove($aSubdirs.Find(Last)) End ' Subdir_Delete() 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 ' Tmp_Stat() 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 ' Note(sWhat As String, ...) Public Sub btnClose_Click() FMain.Close End ' btnClose_Click() Public Sub Form_Close() If $hScript.Running Then $hScript.Stop $hTmp = Null $aSubdirs.Clear() Spinner1.Stop Wait 1 End ' Form_Close()
Alle überwachten Ereignisse werden angezeigt, wenn diese durch das Skript ausgelöst werden → Abbildung 19.4.3.1 Überwachungsprotokoll.