Das Zusammenfassen von Dateien in Ordnern führt häufig dass man nicht mehr weiss wo welche Datei abgespeichert ist. Das Programm WO sucht die Datei für Sie.
Die dabei verwendete Suchroutine Search ist das Kernstück der Include-Datei WO.INC. Die Include-Datei bietet den Vorteil eines universellen Einsatzes der Suchroutine, die häufig und in sehr unterschiedlichen Programmen benötigt wird. Hier wird sie in zwei verschiedene Programmrahmen eingebunden:
Nach dem Übersetzen und Binden muß die entstandene Datei WO.PRG in WO.ACC umbenannt und der Rechner neu gebootet werden. Danach steht das Accessory im ersten Pull-Down-Menü zur Verfügung: WO erfragt den Namen der zusuchenden Datei in einer Fileselect-Box. Bei der Eingabe sind selbstverständlich auch die Metazeichen ’*’ und ‘?’ erlaubt. Nach kurzer Suchzeit werden die Namen der gefundenen Dateien mit kompletter Pfadangabe in Alertboxen ausgegeben. Durch die Verwendung von Alert- und Fileselectboxen bei Ein- und Ausgabe wird der Programmieraufwand und damit die Länge des Programms erheblich reduziert. Beide Prozeduren lassen sich jedoch ersetzen ohne die eigentliche Suchroutine zu verändern. Neben der für Accessories obligatorischen Event-Endlosschleife enthält das Hauptprogramm die Prozedur Kill_List. Sie sorgt dafür, daß nach Ablauf des Programms der nicht mehr benötigte Speicherplatz freigegeben wird (dynamische Liste). Die zentrale Routine der Include-Datei ist die Prozedur Search. Sie durchsucht rekursiv alle Ordner nach dem eingegebenen Dateinamen. Da die verwendeten GEMDOS-Funktionen bereits in früheren Ausgaben der ST-Computer ausführlich beschrieben wurden, will ich mich hier auf die Erläuterung des Suchalgorithmus’ und der Speicherstruktur (lineare Liste) beschränken.
Search durchsucht zuerst das eingegebene Verzeichnis nach Ordnern:
Da sich die Anzahl der gefundenen Dateinamen nicht im voraus festlegen lässt, muß zu deren Speicherung eine dynamische Struktur verwendet werden. Die hier verwendete Implementation einer linearen Liste mit Kopf- und Schwanzzeiger vereinfacht das Anfügen von Elementen am Ende der Liste (Prozedur Append). Kopf- und Schwanzzeiger zeigen auf zwei Dummy-Elemente, zwischen denen sich die eigentlichen Listenelemente befinden. Die leere Liste ist also eine Liste mit zwei Dummy-Elementen. Dabei wird in der Prozedur Init_List (willkürlich) festgelegt, daß die next-Komponente des Schwanzzeigers auf das vorangehende Element zeigt (Abb. 1 und 2). Die restlichen Routinen der Include-Datei dienen lediglich der Typkonvertierung String < — > PACKED ARRAY OF CHAR. Die GEMDOS-Routinen verwenden für die Parameterübergabe Zeichenketten, die durch ein Nullbyte terminiert werden, die Fileselect- bzw. Alertbox-Routinen des ST Pascal Plus hingegen den Typ String.
Das zweite Programm (Listing 3) verwendet die Suchroutine zur Erweiterung der ST Pascal-Funktion Load_Resource. Die Prozedur Load_RSC sucht mittels WO eine Resource-Datei und lädt diese gegebenenfalls. Programm und zugehörige RSC-Datei müssen so nicht mehr im gleichen Ordner abgespeichert sein. Existieren jedoch mehrere Dateien gleichen Namens, kann es sein, daß die falsche geladen wird! Die Prozedur WO kann aber leicht erweitert werden, so daß neben den Namen der gefundenen Dateien auch deren Länge ausgegeben wird (Variable length im Typ DTA). Bei der Entwicklung des Programms traten zwei weitere Probleme auf, die ich nicht verschweigen möchte:
1.) Die Alertbox-Routine verlangt, daß die Größe der auszugebenden Box 25% des ganzen Bildschirms nicht überschreitet. Bei der Ausgabe sehr langer Pfadnamen kann es dadurch zu Systemabstürzen kommen. Meine Versuche, den auszugebenden Pfad durch Einfügen von ‘|’ in mehrere Zeilen zu zerlegen, schlugen fehl, obwohl der Ausgabe-String syntaktisch korrekt war.
2.) Ist an den Rechner kein Laufwerk B: angeschlossen, reagiert die Fileselect-Routine fehlerhaft: ändert man den Pfad der Box in ‘B:\‘ und klickt das grau-gepunktete Feld des Dateifensters an, so kommt es nach der Aufforderung v zum Crash (Teile der Alertbox werden zum Pfadnamen). Dieser Fehler läßt sich m.E. nur durch eine neue Fileselect-Routine, wie sie z.B. Tempus verwendet, umgehen (s.a. PD-Software).
Literatur:
H.-D. Jankowski, J.F. Reschke, D. Rabich:
ATARI ST-Profibuch, Sybex-Verlag, 1988.
Alex Esser:
Auf der Schwelle zum Licht, Directory-Verwaltung, ST Computer 7/8/9 1988.
{$A+, D-} { Compileroptionen für Accessory }
{ falls nötig, Stapel mit S-Option vergrößern }
PROGRAM Wo_Accessory(INPUT,OUTPUT);
{ ***************************
* *
* LISTING 2 *
* (c) MAXON Computer GmbH *
***************************
Accessory-Implementation der Datei-Suchroutine Wo
Entwickelt mit ST Pascal Plus 1.20 von CCD
Florian Nold, Lessingstr. 4, 7830 Emmendingen
Version 1R2 27.02.1989
}
CONST {$I GEMCONST.PAS }
TYPE {$I GEMTYPE.PAS }
file_type = PACKED ARRAY [1..14] OF CHAR;
path_type = PACKED ARRAY[1..Max_Path] OF CHAR;
path_string = STRING [Max_Path] ;
listpointer = ^list; { lineare Liste mit Kopf- }
list = RECORD { und Schwanz-Zeiger. }
path : path_string;
next : listpointer;
END;
VAR startpath : path_string;
ap_id,menu_id : integer;
head,tail : listpointer;
acc_name : Str255;
{$I GEMSUBS.PAS )
{$I WO.INC }
PROCEDURE Get_Searchpath(VAR startpath:path_string) ;
{ Eingaberoutine mit Fileselectbox für Startsuchpfad }
VAR ok : boolean;
FUNCTION DGetDrv:integer; GEMDOS($19);
{ ermittelt akt. Laufwerk: 0=A, 1=B, ... }
BEGIN
startpath:=concat(chr(DGetDrv+65),':\*.*');
ok:=Get_In_File(startpath,startpath);
IF NOT ok
THEN startpath;
END; { Get_Searchpath }
PROCEDURE Print_List(head,tail:listpointer);
{gibt Elemente der lin. Liste in Alertboxen aus}
VAR h_pointer : listpointer;
alertstring : str255;
button : integer;
BEGIN
h_pointer:=head^.next;
button:=2;
{Solange weder das Listenende erreicht,noch der
Abbruch-Button geklickt ist, wird ausgegeben}
WHILE (h_pointer<>tail) AND (button=2) DO
BEGIN
alertstring:=h_pointer^.path;
alertstring:=concat('[0][Gefunden : | ',alertstring);
alertstring:=concat(alertstring,'][Abbruch| Weiter]');
button:=Do_Alert(alertstring,2);
h_pointer:=h_pointer^.next;
END; { WHILE }
button:=Do_Alert('[3][Keine weiteren Files][OK]',1);
END; { Print_List }
PROCEDURE Kill_List(VAR head,tail:listpointer);
{ Löscht Liste, d.h. gibt den reserv. Speicher frei }
VAR h_pointer1,h_pointer2 : listpointer;
BEGIN
h_pointer1:=head;
WHILE h_pointer1<>tail DO
BEGIN
h_pointer2:=h_pointer1;
h_pointer1:=h_pointer2^.next;
dispose(h_pointer2);
END; { WHILE }
dispose(tail);
END; { Kill_List }
PROCEDURE Event_Loop;
{ Endlosschleife zur Erfassung von Ereignissen }
VAR event, dummy : integer;
msg : Message_Buffer;
BEGIN
WHILE true DO {Ein Accessory wird nie beendet ! }
BEGIN
{ Erfassen eines Ereignisses }
event:=Get_Event(E_Message,0,0,0,0,false,0,0,0,0,
false, 0,0,0,0, msg, dummy, dummy,
dummy,dummy,dummy,dummy);
{ Es wird nur das Anklicken des Menüpunkts 'WO ?' verarbeitet. }
CASE msg[0] OF
AC_Open: IF msg[4]=menu_id
THEN
BEGIN
Get_Searchpath(startpath);
IF startpath<>''
THEN
BEGIN
Wo(startpath,head,tail);
Print_List(head,tail);
Kill_List(head,tail);
END;
END; { AC_0pen }
END; { CASE }
END; { WHILE }
END; { Event_Loop }
BEGIN { MAIN }
acc_name:=' WO ?';
ap_id:=Init_GEM;
IF ap_id>=0
THEN
BEGIN
{ Acc.-Name in Menüleiste eintragen }
menu_id:=Menu_Register(ap_id,acc_name);
Event_Loop; { springt in Endlosschleife }
END;
END. { WO_Accessory }
PROGRAM Wo_Load_Resource(INPUT,OUTPUT);
{ ***************************
* *
* LISTING 3 *
* (c) MAXON Computer GmbH *
***************************
Verwendung der Include-Datei WO.INC beim Laden
einer Resource-Datei.
Entwickelt mit ST Pascal Plus 1.20 von CCD
Florian Nold, Lessingstr. 4, 7830 Emmendingen
Version 1R2 27.02.1989 }
CONST {$I GEMCONST.PAS }
TYPE {$I GEMTYPE.PAS }
path_string = STRING [Max_JPath] ;
listpointer = ^list;
list = RECORD
path : path_string;
next : listpointer;
END;
VAR rsc_name : path_string;
dummy : integer;
{$I GEMSUBS.PAS }
{$I WO.INC }
FUNCTION Load_RSC(filename:path_string):boolean;
{ sucht die Resource-Datei in allen Ordnern und lädt
die Datei, falls vorhanden }
VAR head,tail listpointer;
PROCEDURE Kill_List(VAR head,tail:listpointer);
{ Löscht Liste, d.h. gibt den reserv. Speicher frei }
VAR h_pointer1,h_pointer2 : listpointer;
BEGIN
h_pointer1:=head;
WHILE h_pointer1<>tail DO
BEGIN
h_pointer2:=h_pointer1;
h_pointer1:=h_pointer2^.next;
dispose(h_pointer2);
END;
dispose(tail);
END; { Kill_List }
BEGIN
Wo(filename,head, tail);
IF head^.next<>tail
THEN
Load_RSC:=Load_Resource(head^.next^.path)
ELSE Load_RSC:=false;
Kill_List(head,tail);
END; { Load_RSC }
BEGIN { MAIN }
dummy:=Init_GEM;
rsc_name:='A:\PASCAL.RSC';
IF Load_RSC(rsc_name) = false
THEN dummy:=Do_Alert('[3][RSC-Datei nicht|gefunden][OK]',1);
Exit_GEM
END.
{ ***************************
* *
* LISTING 1 *
* (c) MAXON Computer GmbH *
***************************
Include-Modul WO.INC: Durchsucht alle Ordnerebenen
einer Disk/Partition nach dem eingegebenen Namen.
Entwickelt mit ST Pascal Plus 1.20 von CCD
Florian Nold, Lessingstr. 4, 7830 Emmendingen
Version 1R2 27.02.1989 }
PROCEDURE Wo(inpathstr:path_string;
VAR head,tail:listpointer);
TYPE file_type = PACKED ARRAY [1..14] OF CHAR;
path_type = PACKED ARRAY[1..Max_Path] OF CHAR;
{ Definition der Disk-Transfer-Adress }
DTA = RECORD
reserved : PACKED ARRAY[0..19] OF BYTE;
attribut : integer;
time : integer;
date : integer;
length : long_integer;
filename : file_type;
END;
VAR filename,backslash,allfiles : file_type;
path : path_type;
i : integer;
{ Benötigte Gemdos-Routinen ; }
PROCEDURE Fsetdta(VAR file_daten:DTA);
GEMDOS($1A);
{ Setzt die Anfangsadresse der DTA }
FUNCTION Fsfirst(VAR name:path_type;
attr:integer):integer;
GEMDOS($4E);
{ Durchsucht das (akt.) Directory nach Dateien
bzw. Ordner, auf die der angegebene Name und
das Attribut passen. }
FUNCTION Fsnext:integer; GEMDOS($4F);
{ Setzt die mit Fsfirst begonnene Suche fort. }
PROCEDURE Init_List(VAR head,tail: listpointer);
{ Initialisiert eine leere lineare Liste }
BEGIN
new(head);
new(tail);
head^.next:=tail;
tail^.next:=head;
END; { Init_List }
PROCEDURE Append(path : path_string;
VAR head,tail:listpointer);
{ hängt Element path ans Ende der lineare Liste an. }
VAR h_pointer : listpointer;
BEGIN
h_pointer:=tail;
new(tail^.next);
tail^.next^.next:=h_pointer;
tail^.next^.path:=tail^.path;
tail^.path:=path;
tail:=tail^.next;
END; { Append }
PROCEDURE Merge_Path_File(VAR path:path_type;
filename:file_type);
{ Verbindet den aktuellen Pfad mit (neu gefunden) Ordnernamen.}
VAR i,j : integer;
BEGIN
i:=1;
WHILE path[i]<>chr(0) DO
i:=i+1;
j:=1;
REPEAT
path[i+j-1]:=filename[j];
j:=j+1;
UNTIL filename[j]=chr(0);
END; { Merge_Path_File }
PROCEDURE PathToStr(inpath:path_type;
VAR outstr:Path_string);
{Wandelt Zeichenkette vom Type path in STRING}
VAR i : integer;
BEGIN
i:=1;
outstr:='';
WHILE (inpath[i] <> chr(0)) AND (i < Max_path) DO
BEGIN
outstr:=concat(outstr,inpath[i]);
i:=i+1;
END;
END; { PathToStr }
PROCEDURE StrToPath(inpath: path_string;
VAR path:path_type;
VAR filename: file_type);
{ Zerlegt den Eingabe-Pfad (STRING) in Pfad und
Dateiname (PACKED ARRAYS).
z.B. A:\AUTO\WO.* ---> A:\AUTO\ und WO.* }
VAR i,backslashpos : integer;
h_path: path_string;
BEGIN
FOR i:=1 TO 14 DO
filename[i]:=chr(0);
FOR i:=1 TO Max_path DO
path[i]:=chr(0);
{ Ermittle Position des letzten \ }
backslashpos:=1;
FOR i:=1 TO length(inpath) DO
IF inpath[i] = '\'
THEN backslashpos:=i;
h_path:=copy(inpath,1,backslashpos);
inpath:=copy(inpath,backslashpos+1,length(inpath)-backslashpos);
h_path:=concat(h_path,chr(0));
inpath:=concat(inpath,chr(0));
FOR i:=1 TO length(h_path) DO
path[i]:=h_path[i];
FOR i:=1 TO length(inpath) DO
filename[i]:=inpath[i];
END; { StrToPath }
PROCEDURE Search(path:path_type;searchfile:file_type;
VAR head,tail:listpointer);
{ eigentliche Suchroutine }
VAR aktdta : DTA;
error,i : integer;
h_path : path_type;
h_string: STRING[Max_Path];
BEGIN
h_path:=path;
Merge_Path_File(h_path,allfiles);
Fsetdta(aktdta);
{ Suche zuerst alle Ordner einer Directory-Ebene }
error:=Fsfirst(h_path,$10);
WHILE error=0 DO
BEGIN
IF (aktdta.attribut&$10 <> 0) AND (aktdta.filename[1] <> '.')
THEN
BEGIN { Ordner gefunden }
h_path:=path;
Merge_Path_File(h_path,aktdta.filename);
Merge_Path_File(h_path,backslash);
Search(h_path,searchfile,head,tail);
Fsetdta(aktdta);
END;
error:=Fsnext;
END;
{ keine weiteren Ordner in dieser Ebene. ==> suche nach passenden Dateien }
h_path:=path;
Merge_Path_File(h_path,searchfile);
error:=Fsfirst(h_path,$0);
WHILE error=0 DO
BEGIN
IF (aktdta.attribut&$18 = 0)
THEN
BEGIN
h_path:=path;
Merge_Path_File(h_path,aktdta.filename);
PathToStr(h_path,h_string);
Append(h_string,head,tail);
END; { Of THEN }
h_path:=path;
error:=Fsnext;
END; { Of WHILE }
END; { Search }
BEGIN { PROCEDURE Wo }
{initialisiere die 'konstanten' Pfade *.* und\}
allfiles[1]:='*';allfiles[2]:='.';allfiles[3]:='*';
FOR i:=4 TO 14 DO allfiles[i]:=chr(0);
backslash[1]:='\';
FOR i:=2 TO 14 DO backslash[i]:=chr(0);
Init_List(head,tail); { erzeuge leere Liste }
{trenne Eingabe-String in Pfad und Filename }
StrToPath(inpathstr,path,filename);
{ ... und beginne die Suche }
Search(path,filename,head,tail);
END; { WO.INC }