Benutzer-Werkzeuge

Webseiten-Werkzeuge


k19:k19.4:start

19.4 Komponente gb.inotify

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:

  • Es wird nicht spezifiziert, wie lange das Auftreten eines Dateisystem-Ereignisses und das Auftreten des entsprechenden Gambas-Ereignisses auseinander liegen können.
  • Wenn beispielsweise eine Datei erstellt wird, kann der Gambas-Prozess a priori erst eine Stunde später davon erfahren. Zu diesem Zeitpunkt kann die Datei aber schon wieder gelöscht worden sein. Das liegt in der Natur der Sache, denn die Dateisystem-Ereignisse sind asynchron.
  • Unter normaler Betriebslast auf dem Rechner ist der Kernel aber bemüht, die Ereignisse zeitnah zu versenden.
  • Ein Gambas-Programm, das die einzige Klasse Watch der Komponente gb.inotify einsetzt, wird durch den Linux-Kernel über Aktivitäten im überwachten Verzeichnis informiert, in dem Gambas-Ereignisse des Watch-Objekts ausgewertet werden.

19.4.1 Klasse Watch

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.

19.4.2 Erzeugen eines Watch-Objekts

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.

19.4.2.1 Statische Eigenschaften

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.

EigenschaftDatentypBeschreibung
CookieIntegerEin 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.
IsDirBooleanGibt an, ob sich die Beobachtung auf ein Verzeichnis bezieht oder bezog.
NameStringGilt 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.
UnmountBooleanGibt 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

19.4.2.2 Eigenschaften

EigenschaftDatentypBeschreibung
Events.Watch.EventsGibt 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.
IsPausedBooleanGibt zurück, ob der Watch momentan pausiert (→ Methoden Pause und Resume).
PathStringGibt den überwachten Pfad zurück.
Tag VariantDen Einsatz dieser Eigenschaft kann der Gambas-Programmierer frei bestimmen.

Tabelle 19.4.2.2.1 : Eigenschaften der Klasse Watch

19.4.2.3 Methoden

Die Klasse Watch verfügt nur über diese zwei Methoden:

MethodeBeschreibung
PauseLässt ein Watch-Objekt pausieren und hindert es so daran, Ereignisse auszulösen.
ResumeBeendet den Pause-Modus eines Watch-Objekts.

Tabelle 19.4.2.3.1 : Methoden der Klasse Watch

19.4.2.4 Ereignisse

Die Klasse Watch verfügt über diese Ereignisse:

EreignisBeschreibung
CloseDas Ereignis wird ausgelöst, wenn die überwachte Datei oder eine Datei im überwachten Verzeichnis geschlossen wird.
CreateDas Ereignis wird ausgelöst, wenn ein Eintrag (Datei oder Verzeichnis) im überwachten Verzeichnis erzeugt wird.
DeleteDas Ereignis wird ausgelöst, wenn ein Eintrag im überwachten Verzeichnis oder das überwachte Objekt selbst gelöscht wurde.
MoveDas Ereignis wird ausgelöst, wenn das überwachte Objekt verschoben wurde.
MoveFromDas Ereignis wird ausgelöst, wenn ein Eintrag des überwachten Verzeichnisses verschoben wird (Ereignis für das Quellverzeichnis der Verschiebung).
MoveToDas Ereignis wird ausgelöst, wenn ein Eintrag in das überwachte Verzeichnis verschoben wird (Ereignis für das Zielverzeichnis einer Verschiebung).
OpenDas Ereignis wird ausgelöst, wenn die überwachte Datei oder ein Eintrag im überwachten Verzeichnis geöffnet wird.
ReadDas 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.
StatDas Ereignis wird ausgelöst, wenn Meta-Daten oder Datei-Attribute des überwachten Objekts verändert wurden.
WriteDas 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.

19.4.3 Projekt

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).

Bild 1 - GUI

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.

Download

k19/k19.4/start.txt · Zuletzt geändert: 20.06.2016 (Externe Bearbeitung)

Seiten-Werkzeuge