Lange Dateinamen mit GFA-BASIC


Hier erwarten Sie zunächst einige Erläuterungen zur Verwendung langer Dateinamen in GFA-BASIC-Programmen. Im Anschluß daran finden Sie ein ausführlich kommentiertes Beispiellisting, das die wesentlichen Routinen zum Umgang mit Verzeichnissen und Dateien unter allen Dateisystemen enthält. Ein Listing sagt bekanntlich mehr, als tausend Worte ... ;-)

Das Listing kann über das Clipboard ausgeschnitten und dann in den Interpreter geladen werden. Dort gestartet, wird es Ihnen sämtliche Dateien aller Verzeichnisse und Unterverzeichnisse Ihrer Festplatten-Partition 'C' auf den Bildschirm ausgeben. Sofern Ihr System lange Dateinamen beherrscht, werden selbstverständlich diese ausgegeben. Doch halt! Bevor Sie nun zur Schere greifen um das Listing sofort auszuprobieren, sollten Sie zum besseren Verständnis zunächst die folgenden Erläuterungen lesen. Danach wird es Ihnen sicher keine Mühe mehr bereiten, die Routinen in Ihren Programmen einzusetzen.

Grundsätze:

Das wohl größte Problem vor der Verwendung langer Dateinamen in GFA-BASIC-Programmen stellen die GFA-Dateifunktionen dar. Was ursprünglich einmal als Erleichterung für Programmierer konzipiert war, entpuppt sich auf modernen Dateisystemen mit langen Dateinamen als unüberwindbares Hindernis. Die GFA-Dateifunktionen OPEN, EXIST, FSFIRST/FSNEXT etc. kommen nur mit Namen klar, die entsprechend der DOS-Konvention aus 8 Zeichen mit maximal 3 Zeichen für die Extension bestehen. Zudem vergibt GFA-BASIC eigene Dateihandles, die nichts mit den GEMDOS-Dateihandles zu tun haben, die zur Verwendung langer Dateinamen jedoch unbedingt benötigt werden.
Anders ausgedrückt: Wer lange Dateinamen verarbeiten möchte, kann dazu keine GFA-Dateifunktionen verwenden, zumal einige wichtige Funktionen auch gar nicht implementiert sind.

Stattdessen müssen Dateizugriffe also ausschließlich über die entsprechenden GEMDOS-Funktionen erfolgen. Dies ist jedoch kein großes Problem, da die Funktionen leicht über einige kleine GFA-BASIC-Unterprogramme nachgebildet werden können. Ein positiver Nebeneffekt ist übrigens, dass diese Unterprogramme gegebenenfalls GEMDOS-Fehlernummern zurückliefern, mit denen dann eine ordentliche Fehlerbehandlung möglich ist. Doch das nur am Rande.

Wie Unterprogramme mit GEMDOS-Aufrufen im einzelnen aussehen, werden Sie im weiteren Verlauf sehen. Zur besseren Les- und Wartbarkeit der Listings werden hier als Funktions-Parameter an das GEMDOS übrigens ausschließlich Konstanten verwendet. Beachten Sie dazu bitte auch die Erläuterung im Listing.

Voraussetzung:

Ich empfehle Ihnen, Ihr Programm zunächst von den GFA-Dateifunktionen auf die entsprechenden GEMDOS-Funktionen umzustellen. Dies mag zwar ein Haufen Arbeit sein, wird sich jedoch langfristig aus vielerlei Gründen für Sie und für die Benutzer Ihres Programmes auszahlen. Die notwendigen GEMDOS-Aufrufe finden Sie als fertige Prozeduren im GFA-Utilities-Hypertext.

Um auf den aktuellen Stand der Dinge zu kommen, muß nun jedes Programm, das mit langen Dateinamen zu arbeiten gedenkt, dem System mitteilen, dass es mit diesen langen Dateinamen umgehen kann. Dies geschieht ganz einfach mit dem Aufruf der GEMDOS-Funktion Pdomain. Diese Funktion kann bedenkenlos auch auf Systemen aufgerufen werden, die Pdomain gar nicht kennen, da das GEMDOS den Aufruf dann lediglich mit der definierten Fehlermeldung EINVFN (-32) quittiert. (Gleiches gilt im übrigen für alle GEMDOS-Aufrufe.)

Verzeichnis lesen

Ein häufig genanntes Problem stellt das vollständige Lesen eines Verzeichnisses mit langen Dateinamen dar. Die unter Standard-Dateisystemen zum Lesen bislang ausreichenden Befehle FSFIRST und FSNEXT liefern lediglich 8+3-Namen, sind für diese Zwecke daher also gänzlich ungeeignet. Enthält das Verzeichnis nun auch noch weitere Unterverzeichnisse, so muß eine rekursive Routine her. Genau diese folgt nun in Form des oben bereits erwähnten Beispiellistings:


'
' (06/99 Ulli Gruszka, basierend auf Routinen von Frank Rüger)
' Diese Listing zeigt und erläutert einige wesentliche Routinen zum Umgang
' mit Verzeichnissen und Dateien unter allen Dateisystemen. Dies geschieht
' im Rahmen einer Prozedur zum Lesen und Ausgeben aller Dateien/Ordner eines
' Verzeichnisses. Eine weitere Funktion zeigt die Technik zum vollständigen,
' rekursiven Lesen bzw. Ausgeben aller Dateien/Ordner eines Verzeichnisses
' inklusive der Unterverzeichnisse.
'
@init                                   ! Konstanten belegen und
'                                         XATTR/DTA-Struktur anlegen.
IF @pdomain(dom_mint&)<>EINVFN%         ! In die MiNT-Domain schalten und
  domain&=@pdomain(-1)                  ! dann die nun aktuelle Domain merken.
ENDIF                                   ! (wird im Fehlerfall 0L = TOS-Domain)
'
pfad$="c:\"                             ! Startverzeichnis (beliebig)
mask$="*.*"                             ! Maske anzuzeigender Dateien (dito)
'                                         (Wildcards * und ? sind erlaubt)
attribute&=&X101111                     ! Bit   Finde folgende:
'                                     x    0    Schreibgeschützte Datei
'                                     x    1    Versteckte Datei
'                                     x    2    Systemdatei
'                                     x    3    Diskettenname
'                                          4    Ordner
'                                     x    5    Datei mit gesetztem Archivbit
'
dta%=FGETDTA()                          ! Alte DTA zum Restaurieren merken.
'
' Die Vorbereitungen sind erledigt, jetzt kann es losgehen:
'
~@alle_verzeichnisse(pfad$,mask$,attribute&) ! Rekursive Verzeichnisausgabe
'
' Sofern nur eine Ebene eines Verzeichnisses ausgegeben werden soll,
' muß der obere Funktionsaufruf auskommentiert und der folgende
' aktiviert werden. Gegebenenfalls noch Bit Nr. 4 der Attribute
' setzen, um die Unterordnernamen ebenfalls ausgeben zu lassen.
'
' @ein_verzeichnis(pfad$,mask$,attribute&) ! Einfache Verzeichnisausgabe
'
~FSETDTA(dta%)                          ! DTA zurücksetzen
'
EDIT                                    ! Ende der Durchsage.
'
' -- Die Verzeichnisroutinen ---------------------------------------------------
'
FUNCTION alle_verzeichnisse(pfad$,mask$,attribute&)
  $F%
  LOCAL neu_ordner$
  LOCAL neu_pfad$
  '
  ' Diese Funktion wird einmalig mit dem Startpfad, der gewünschten Dateimaske
  ' und den gewünschten Attributen aufgerufen. Von nun an ruft sich die
  ' Funktion solange selbst auf, bis die unterste Verzeichnisebene erreicht ist.
  ' Zunächst werden bei jedem Durchlauf die gesuchten Dateien der jeweiligen
  ' Ebene ausgegeben:
  '
  @ein_verzeichnis(pfad$,mask$,attribute&)
  '
  ' Ab hier werden die Unterverzeichnisse/Ordner der jeweiligen Ebene gesucht:
  '
  IF @my_fsfirst(pfad$+"*.*",&X10000,TRUE)=0    ! Der erste Ordner dieser Ebene.
    '                                            (zu TRUE siehe @my_fsfirst())
    '
    REPEAT
      '
      IF BYTE{gl_xattr_d_attrib%} AND &X10000   ! Ist es ein Ordner?
        neu_ordner$=CHAR{gl_xattr_d_fname%}     ! Ordner-Name holen und damit
        neu_pfad$=pfad$+neu_ordner$+"\"         ! den neuen Pfad bilden.
        '
        IF neu_ordner$<>"." and neu_ordner$<>".."  ! Pseudo-Ordner überspringen
        '                                            (Dank an Matthias Jaap!)
          IF @alle_verzeichnisse(neu_pfad$,mask$,attribute&)=FALSE  ! Rekursion!
            RETURN FALSE                        ! Eine Stufe zurück bzw. Ende.
          ENDIF
        ENDIF
        '                                         Den nächsten Ordner nach dem
        ~@my_fsfirst(pfad$+"*.*",&X10000,TRUE)  ! gerade gelesenen suchen, indem
        WHILE neu_ordner$<>CHAR{gl_xattr_d_fname%} ! die Namen verglichen werden,
          ~@my_fsnext(pfad$+"*.*",&X10000)<>0   ! um dort weiterzumachen.
        WEND
      ENDIF
      '                                           Auf der (übergeordneten)
    UNTIL @my_fsnext(pfad$+"*.*",&X10000)<>0    ! Ebene weitersuchen.
    '
  ENDIF
  '
  RETURN TRUE
  '
ENDFUNC
PROCEDURE ein_verzeichnis(pfad$,mask$,attr&)
  LOCAL ex&
  '
  ' Hier werden die passenden Dateien/Ordner einer Ebene gesucht und auf
  ' dem Bildschirm ausgegeben. Der jeweilige Name findet sich nach Aufruf
  ' der Verzeichnisfunktion in der DTA-Struktur. Von dort kann er natürlich
  ' auch in ein String-Array, in einen Speicherblock oder in eine Datei
  ' geschrieben werden. Die restlichen Dateiinformationen befinden sich
  ' ebenfalls in der DTA-Struktur.
  '
  IF @my_fsfirst(pfad$+mask$,attr&,TRUE)=0      ! Erste Datei
    '                                             (zu TRUE siehe @my_fsfirst())
    REPEAT
      '
      IF BYTE{gl_xattr_d_attrib%} AND &X10000             ! Ist es ein Ordner?
        PRINT "Ordner: ";pfad$;CHAR{gl_xattr_d_fname%}    ! Namen ausgeben
      ELSE IF BYTE{gl_xattr_d_attrib%} AND attr&          ! Ist es eine Datei?
        PRINT " Datei: ";pfad$;CHAR{gl_xattr_d_fname%}    ! Namen ausgeben
        ' PRINT "        ";{gl_xattr_d_length%};" Byte"   ! Dateilänge
      ENDIF
      '
      ex&=@my_fsnext(pfad$+mask$,attr&)         ! Weitersuchen bis zum Ende.
      '
    UNTIL ex&<>0                                ! Keine weiteren Dateien.
    '
  ENDIF
  '
RETURN
'
' -- Die Unterprogramme --------------------------------------------------------
'

FUNCTION pdomain(dom&)
  $F%
  ' dom&=-1     Abfrage der aktuellen Domain
  ' dom&=0      Schalte in die TOS-Domain
  ' dom&=1      Schalte in die MiNT-Domain
  RETURN GEMDOS(pdomain&,dom&)
ENDFUNC
'
FUNCTION my_fsfirst(datei$,attr&,next!)
  $F%
  ' Der Ersatz zu FSFIRST(). Achtung: Der zusätzliche Parameter (next!) muß
  ' auf TRUE gesetzt werden, wenn anschließend my_fsnext() aufgerufen wird.
  ' Wird my_fsfirst() nur einmalig aufgerufen (z.B. zur Existenzprüfung), so
  ' ist next! auf FALSE zu setzen!
  '
  LOCAL all_names!
  LOCAL match_name!
  LOCAL attrib&
  LOCAL fehler%
  LOCAL bufdatei$
  LOCAL dateiname$
  LOCAL path$
  '
  @chk_dirhandle
  '
  @pfad(datei$,path$)
  '
  IF domain&=1                                  ! Nur wenn wir uns auch in der
    gl_dirhandle%=@dopendir(path$,0)            ! MiNT-Domain befinden öffnen.
  ELSE
    gl_dirhandle%=einvfn%                       ! Normales FSFIRST/FSNEXT
  ENDIF
  '
  IF gl_dirhandle%=einvfn%                      ! (-32) Kein passendes Filesystem
    gl_dirhandle%=0                             ! oder nicht in der MiNT-Domain
    '
    gl_xattr_d_attrib%=ADD(gl_xattrbuf%,&H15)   ! Zum vereinfachten Zugriff auf
    gl_xattr_d_time%=ADD(gl_xattrbuf%,&H16)     ! die DTA (nimmt die Ergebnisse
    gl_xattr_d_date%=ADD(gl_xattrbuf%,&H18)     ! der Funktion auf) werden einige
    gl_xattr_d_length%=ADD(gl_xattrbuf%,&H1A)   ! globale Variablen mit den
    gl_xattr_d_fname%=ADD(gl_xattrbuf%,&H1E)    ! Struktur-Adressen belegt.
    '
    ~FSETDTA(gl_xattrbuf%)                      ! DTA setzen
    '
    RETURN FSFIRST(datei$,attr&)
    '
  ELSE                                          ! In der MiNT-Domain:
    fehler%=gl_dirhandle%
    IF (SHR(gl_dirhandle%,24) AND &HFF)<>&HFF
      gl_xattr_d_attrib%=ADD(gl_xattrbuf%,&H29) ! Variablen belegen (siehe oben)
      gl_xattr_d_time%=ADD(gl_xattrbuf%,&H1C)
      gl_xattr_d_date%=ADD(gl_xattrbuf%,&H1E)
      gl_xattr_d_length%=ADD(gl_xattrbuf%,&H10)
      gl_xattr_d_fname%=ADD(gl_fnamebuf%,&H4)
      '
      @filename(datei$,dateiname$)              ! Datei extrahieren
      IF dateiname$="*.*" OR dateiname$="*"
        dateiname$="*"
        all_names!=-1
      ENDIF
      CHAR{gl_fnamebuf%}=""                     ! Struktur bereinigen
      '
      fehler%=@dreaddir(255,gl_dirhandle%,gl_fnamebuf%) ! Lesen
      '
      WHILE fehler%=0
        bufdatei$=CHAR{gl_xattr_d_fname%}       ! Die gefundene Datei
        IF all_names!                           ! Gesucht wurde *.*
          match_name!=TRUE
        ELSE
          match_name!=@match(UPPER$(bufdatei$),UPPER$(dateiname$))
        ENDIF
        IF match_name!=TRUE                     ! entspricht dem Suchkriterium
          datei$=path$+bufdatei$                ! Kompletter Zugriffspfad
          IF @fxattr(0,datei$,gl_xattrbuf%)=0
            attrib&=INT{PRED(gl_xattr_d_attrib%)}
            IF attrib&=0 AND (attr& AND fa_file&)=fa_file&
              attrib&=fa_file&
            ENDIF
            IF (attrib& AND attr&) OR attr&=0
              IF NOT next!                      ! Dem my_fsfirst() folgt kein
                ~@dclosedir(gl_dirhandle%)      ! my_fsnext(), also Verzeichnis
                CLR gl_dirhandle%               ! wieder schließen.
              ENDIF
              RETURN 0
            ENDIF
          ENDIF
        ENDIF
        fehler%=@dreaddir(255,gl_dirhandle%,gl_fnamebuf%)
      WEND
      ~@dclosedir(gl_dirhandle%)
    ENDIF
  ENDIF
  '
  CLR gl_dirhandle%
  RETURN fehler%
  '
ENDFUNC
FUNCTION my_fsnext(datei$,attr&)
  $F%
  ' Der Ersatz zu FSNEXT().
  '
  LOCAL all_names!
  LOCAL match_name!
  LOCAL attrib&
  LOCAL fehler%
  LOCAL bufdatei$
  LOCAL dateiname$
  LOCAL path$
  '
  IF gl_dirhandle%                              ! Aufruf erfolgte aus MiNT-Domain
    '
    @pfad(datei$,path$)                         ! Pfad extrahieren
    @filename(datei$,dateiname$)                ! Dateiname extrahieren
    IF dateiname$="*.*" OR dateiname$="*"
      dateiname$="*"
      all_names!=TRUE
    ENDIF
    '
    CHAR{gl_fnamebuf%}=""                       ! Struktur bereinigen
    fehler%=@dreaddir(255,gl_dirhandle%,gl_fnamebuf%)   ! Lesen
    WHILE fehler%=0
      bufdatei$=CHAR{gl_xattr_d_fname%}
      IF all_names!                             ! Gesucht wurde *.*
        match_name!=TRUE
      ELSE
        match_name!=@match(UPPER$(bufdatei$),UPPER$(dateiname$))
      ENDIF
      IF match_name!=TRUE                       ! entspricht dem Suchkriterium
        datei$=path$+bufdatei$                  ! Kompletter Zugriffspfad
        IF @fxattr(0,datei$,gl_xattrbuf%)=0
          attrib&=INT{PRED(gl_xattr_d_attrib%)}
          IF attrib&=0 AND (attr& AND &H27)=&H27
            attrib&=&H27
          ENDIF
          IF (attrib& AND attr&) OR attr&=0
            RETURN 0
          ENDIF
        ENDIF
      ENDIF
      fehler%=@dreaddir(255,gl_dirhandle%,gl_fnamebuf%)
    WEND
    ~@dclosedir(gl_dirhandle%)
    CLR gl_dirhandle%
    RETURN fehler%
  ELSE IF gl_has_dopendir!
    RETURN enmfil%          ! -49
    '
  ELSE                                         ! Aufruf erfolgte aus TOS-Domain
    RETURN FSNEXT()
  ENDIF
  '
ENDFUNC
'
FUNCTION fxattr(flag&,fname$,buf%)
  $F%
  '
  fname$=fname$+CHR$(0)
  '
  RETURN GEMDOS(fxattr&,flag&,L:V:fname$,L:buf%)
  '
ENDFUNC
'
FUNCTION dopendir(dir$,flag&)
  $F%
  '
  LOCAL fehler%
  '
  LET dir$=dir$+CHR$(0)
  '
  fehler%=GEMDOS(dopendir&,L:V:dir$,flag&)
  '
  ' Internes Flag für dieses eine Verzeichnis:
  gl_has_dopendir!=fehler%<>einvfn%      ! -32
  '
  RETURN fehler%
  '
ENDFUNC
FUNCTION dclosedir(dirhandle%)
  $F%
  '
  RETURN GEMDOS(dclosedir&,L:dirhandle%)
  '
ENDFUNC
FUNCTION dreaddir(size&,dirhandle%,buf%)
  $F%
  '
  RETURN GEMDOS(dreaddir&,size&,L:dirhandle%,L:buf%)
  '
ENDFUNC
'
' --- Diverses ----------------------------------------------------------------
'
PROCEDURE init
  '
  ' 256 Bytes Puffer für Dreaddir() und 64 Bytes für
  ' XATTR/DTA-Struktur über ein INLINE bereitstellen:
  '
  INLINE gl_fnamebuf%,320
  gl_xattrbuf%=ADD(gl_fnamebuf%,256)
  '
  fa_file&=&H27

  '
  ' Die folgende Variablenliste enthält alle relevanten GEMDOS-Funktionsnummern
  ' und Parameter. Es handelt sich also nicht um echte Variablen, sondern um
  ' feststehende Werte bzw. Konstanten. Wir nennen sie symbolische Konstanten,
  ' da sie vom ergo!pro-Präprozessor als Symbole (siehe ++SYM-Kommando)
  ' interpretiert werden und vor dem Compilieren überall im Listing durch
  ' ihren Wert ersetzt werden.
  '
  ' Durch dieses Verfahren bleibt das Listing gut lesbar, ohne dass im Compilat
  ' unnötig Speicherplatz für sich nie ändernde Variablen beansprucht wird.
  ' Definierte, aber nicht verwendete Konstanten, werden vom Präprozessor
  ' natürlich ebenfalls rechtzeitig aus dem Listing entfernt.
  ' [Ende der Werbung :-]
  '
  ' Die (symbolischen) GEMDOS-Konstanten:
  '
  '     ++SYM
  '
  LET dsetdrv&=14
  LET dgetdrv&=25
  LET super&=32
  LET tgetdate&=42
  LET tgettime&=44
  LET sversion&=48
  LET dfree&=54
  LET dcreate&=57
  LET dsetpath&=59
  LET fcreate&=60
  LET fopen&=61
  ' Fopen()-Modi
  LET readonly&=0
  LET readwrite&=2
  '
  LET fclose&=62
  LET fread&=63
  LET fwrite&=64
  ' Standard-Kan&auml;le
  LET stdprn&=3
  LET fdelete&=65
  LET fseek&=66
  ' Modi
  LET seek_set&=0
  LET seek_cur&=1
  LET seek_end&=2
  LET mxalloc&=68
  ' Modi
  LET mx_stonly&=0
  LET mx_altonly&=1
  LET mx_prefst&=2
  LET mx_prefalt&=3
  ' MTOS-Speicherschutz (OR)
  LET mx_prot_default&=0
  LET mx_prot_private&=&H10
  LET mx_prot_global&=&H20
  LET mx_prot_super&=&H30
  LET mx_prot_readable&=&H40
  LET mx_prot_no_free&=&H4000
  LET dgetpath&=71
  LET pexec&=75
  ' Modi
  LET loadgo&=0
  ' ' Dateiattribute für Fsfirst()
  LET fa_rdonly&=&H1
  LET fa_hidden&=&H2
  LET fa_system&=&H4
  LET fa_label&=&H8
  LET fa_direc&=&H10
  LET fa_arch&=&H20
  LET fa_attrib&=&H17
  LET fa_file&=&H27
  LET fa_other&=&H18
  LET frename&=86
  LET fdatime&=87
  ' Get/Set
  LET fd_read&=0
  LET fd_write&=1
  LET mfork&=112
  LET pdomain&=281
  '  Domains
  LET dom_tos&=0
  LET dom_mint&=1
  LET dopendir&=296
  LET dreaddir&=297
  LET dclosedir&=299
  LET fxattr&=300
  LET dreadlabel&=338
  '
  e_ok%=0
  einvfn%=-32
  efilnf%=-33
  epthnf%=-34
  enhndl%=-35
  eaccdn%=-36
  eihndl%=-37
  ensmem%=-39
  eimba%=-40
  edrive%=-46
  ensame%=-48
  enmfil%=-49
  elocked%=-58
  enslock%=-59
  erange%=-64
  eintrn%=-65
  eplfmt%=-66
  egsbf%=-67
  ebreak%=-68
  excpt%=-69
  epthov%=-70
  '
  '     ++SYM
  '
RETURN
PROCEDURE chk_dirhandle
  IF gl_dirhandle%
    ~@dclosedir(gl_dirhandle%)
    CLR gl_dirhandle%
  ENDIF
RETURN
FUNCTION match(strng$,pattern$)
  $F%
  '
  LOCAL pattern0$
  LOCAL strng0$
  '
  pattern0$=pattern$+CHR$(0)
  strng0$=strng$+CHR$(0)
  '
  RETURN @search(V:strng0$,V:pattern0$)
  '
ENDFUNC
FUNCTION search(strng%,pattern%)
  $F%
  '
  LOCAL nochmal!
  '
  REPEAT
    CLR nochmal!
    IF BYTE{strng%}=0
      IF BYTE{pattern%}=0
        RETURN -1
      ELSE IF BYTE{pattern%}=42   !"*"
        INC pattern%
        nochmal!=-1
      ENDIF
    ELSE
      IF BYTE{pattern%}=42        !"*"
        RETURN @search(strng%,SUCC(pattern%)) OR @search(SUCC(strng%),pattern%)
      ELSE IF BYTE{pattern%}=BYTE{strng%} OR BYTE{pattern%}=63      !"?"
        INC pattern%
        INC strng%
        nochmal!=-1
      ENDIF
    ENDIF
  UNTIL NOT nochmal!
  RETURN 0
ENDFUNC
PROCEDURE filename(datei$,VAR match$)
  '
  ' Extrahiert einen Dateinamen
  '
  LOCAL match&
  match&=SUCC(RINSTR(datei$,"\"))
  IF match&>0
    match$=MID$(datei$,match&)
  ELSE
    CLR match$
  ENDIF
RETURN
PROCEDURE pfad(datei$,VAR match$)
  '
  ' Extrahiert einen Pfad
  '
  LOCAL match&
  match&=RINSTR(datei$,"\")
  IF match&>0
    match$=LEFT$(datei$,match&)
  ELSE
    CLR match$
  ENDIF
RETURN