(***********************************************************************)
(*                                                                     *)
(* Programm 'Tree'       : (c) by Andreas Meuli                        *)
(* Version               : 1.0                                         *)
(* Aufruf                : Aus dem CLI, mit 'Tree dfx:[<pfad>]         *)
(* Enwicklungssystem     : Modula-2, M2Amiga V3.2d von A&L-AG          *)
(*                                                                     *)
(***********************************************************************)


MODULE Tree;


FROM SYSTEM    IMPORT ADR;
FROM DOS       IMPORT FileLockPtr,FileInfoBlockPtr,Lock,UnLock,Examine,
                      ExNext,IoErr,CurrentDir;
FROM InOut     IMPORT WriteString,WriteLn,WriteInt,WriteCard;
FROM Exec      IMPORT AllocMem,MemReqs,MemReqSet,FreeMem;
FROM Arguments IMPORT GetArg,NumArgs;


TYPE FileName = ARRAY [0..107] OF CHAR;


VAR err   : LONGINT;     (* Variable in der Fehler gespeicher wird     *)
    dir   : FileName;    (* no Comment                                 *)
    ok    : BOOLEAN;     (* Speicher für Prozedurrückgabewert          *)
    i     : INTEGER;     (* dito                                       *)
    total : LONGINT;     (* Grösse des durchsuchten Baumes             *)


PROCEDURE CheckDir(dir  : ARRAY OF CHAR;(* Alles lokale Variablen,     *)
                   level: CARDINAL);    (* die sich auf dem Stack      *)
VAR flp,old : FileLockPtr;              (* befinden                    *)
    FibPtr : FileInfoBlockPtr;          (* ...                         *)

BEGIN
  FibPtr := AllocMem(SIZE(FibPtr^),MemReqSet{chip,memClear});
  flp    := Lock(ADR(dir),-2);          (* Pathname 'locken'           *)
  IF flp=NIL THEN                       (* Pathname ist nicht ok       *)
    WriteString('*** Error - falscher Pathname');
    WriteLn;
  ELSE                                  (* Pathname ist ok             *)
    ok := Examine(flp,FibPtr);
    IF FibPtr^.dirEntryType>0 THEN      (* Ist Argument ein Directory? *)
      old := CurrentDir(flp);           (* CurrentDir auf Suchpfad     *)
      ok  := ExNext(flp,FibPtr);        (* alter Ort sichern und neues *)
      err := IoErr();                   (* Directory untersuchen.      *)
      WHILE err=0 DO                    (* Ist das Directory leer      *)
        WriteInt(FibPtr^.size,8);       (* Filegrösse ausgeben         *)
        total := total + FibPtr^.size;  (* Totalgrösse ermiteln        *)
        FOR i := 1 TO level DO          (* Einrücken...                *)
          WriteString('    ');
        END; (* FOR *)
        WriteString(FibPtr^.fileName);  (* File/Dir-Name ausgeben      *)
        IF FibPtr^.dirEntryType>0 THEN  (* Ist der nächste gefundene   *)
          WriteString(' (dir)');        (* Eintrag ein Directory...    *)
          WriteLn;
          CheckDir(FibPtr^.fileName,level+1); (* -> Rekursion!!!       *)
        ELSE                            (* ...oder ein 'normales' File *)
          WriteLn;
        END; (* IF *)
        ok  := ExNext(flp,FibPtr);      (* Nächstes Lock holen, bis... *)
        err := IoErr();
      END; (* WHILE *)                  (* ..untersuchtes Dir leer ist *)
      flp := CurrentDir(old);           (* CurrentDir auf altes Dir    *)
    ELSE                                (* Argument ist ein File       *)
      WriteString
      ('*** Error - Angegebener Pfad ist Filename nicht Directory.');
      WriteLn;
    END; (* IF dirEntryType>0 *)
    UnLock(flp);                        (* Lock wieder freigeben       *)
  END; (* IF Flp=NIL *)                 (* Directory ist durchsucht,   *)
  FreeMem(FibPtr,SIZE(FibPtr^));        (* die Rekursion ist beendet   *)
END CheckDir;                           (* Ende der Prozedur           *)


BEGIN                            (* Begin des Hauptprogrammes          *)
  IF NumArgs()>0 THEN            (* Keine Argumente übergeben ??       *)
    total := 0;                  (* Variable für Gesamtgrösse auf 0    *)
    GetArg(1,dir,i);             (* Argument holen                     *)
    CheckDir(dir,1);             (* Erster Aufruf von CheckDir         *)
    WriteString('--------');
    WriteLn;
    WriteInt(total,8);           (* Nur noch das Total ausgegeben      *)
    WriteLn;
  ELSE                           (* Kein Argument übergeben......      *)
    WriteString('*** Error - Bitte Pfadnamen eingeben !');
    WriteLn;
  END;
END Tree.                        (* Das war's schon ! Grüsse an Pat... *)
                                 (* ... hoi Du altä Häcker !!!         *)




Programmbeschreibung:

Tree durchsucht ein Directory auf einer Diskette oder einer Harddisk und gibt alle Files in allen Directories und Subdirectories aus.

Wenn das eingegebene Argument ein korrekter Pfadname ist (Lock() gibt nicht NIL zurück), kann der Speicherbereich, auf den die Variable FileInfoBlockPtr zeigt, mit Examine() gefüllt werden. Somit erhält man die Informationen über das 'gelockte' Diskettenobjekt.

Ist das Argument ein Directory kann ein CurrentDir() darauf gemacht werden. Fals es ein Filename ist, bekommt man eine Fehlermeldung. Jetzt ist man im Besitz der nötigen Informationen, die gebraucht werden, damit ein Directory durchgescannt werden kann. Dies geschieht mit ExNext(), bis IoErr einen Fehler (Rückgabewert # 0) anzeigt.

Findet man mit ExNext() ein normales File, muss keine besondere Aktion erfolgen. Nicht aber, wenn ein Directory gefunden wird. Dann nähmlich untersucht das Programm dieses Subdirectory mittels CheckDir(). Dabei beginnt die Rekursion und dauert, wenn keine weiteren Subdirectories mehr gefunden werden, bis IoError die Meldung 'noMoreEntries' (IoError()=232) sendet. Somit gibt es eine Abbruchbedingung innerhalb der Prozedur und das Programm endet nicht mit einem Stack-Overflow. Vor dem Ausstieg wird noch ein CurrentDir() auf das alte Directory gemacht. Damit ist gewährleistet, dass das aktuelle nach jedem Prozeduraufruf das selbe wie vorher.

Doch wie weiss das Programm beim Ende der Prozedur wo in der Disketenhierarchie weitergesucht werden muss? Ganz einfach!  Alle nötigen Informationen werden in lokalen Variablen auf dem Stack angelegt. Bei einem Aufruf von CheckDir vergrössert sich also der Stack und beim Beenden der Prozedur schrumpft er wieder um die selbe Grösse. Das selbe gilt auch für die Parameter der Prozedur.

Wenn level ein VAR-Parameter wäre, würde das Einrücken nicht klappen und bei jedem Prozeduraufruf um eins wachsen. Dies ist aber gar nicht im Sinne des Erfinders! Wer aber einen gewissen Lerneffekt erreichen will, kann es ja trotzdem einmal versuchen.Ein weiterer Möglichkeit zur Modifikation wäre die Zeile mit dem Aufruf CheckDir() einmal zu entfernen, dann wird nur ein Directory durchsucht und es findet keine Rekursion statt. (Erfinde weitere lustige Beispiele...)

Die Wichtigste Erkentnis, die ich beim Entwickeln des Programmes gemacht habe war, dass die Informationen, die ExNext() für das Weitersuchen braucht, aus dem FileInfoBlock gewonnen werden und nicht wie ursprünglich angenommen aus dem FileLockPtr. Als ich den FileInfoBlock als globale Variable definiert und nur einmal Speicher dafür angefordert habe suchte mein Programm an einem völlig falschen Ort weiter, was mir langes Kopfzerbrechen bereitete...

Das Programm ist in der vorliegenden Version natürlich sehr einfach ausgefallen. Mein Vorschlag zur Weiterverarbeitung wäre ein Disketteneditor (ähnlich Norton-Commander beim PC), der alle Directories einer Diskette baumförmig anzeigt und die dann mit dem Cursor angesprungen werden können. Der Phantasie sind eigentlich keine Grenzen gesetzt, denn im FileInfoBlock gibt es noch weitere Angaben, die ausgewertet werden könnten (Grössenangabe in Blocks, Protectionbits, etc...)

Ich hoffe, dass ich mit meinem kleinen Programm der Modula-2 interessierten Leserschaft einen kleinen Input geben kann für das weitere Arbeiten mit dem AMIGA.


