|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||
Diskarchive - Anmelden auf der WorkbenchIm letzten Teil des Workshops fassen wir das Wissen der anderen Teile zusammen, um zu guter letzt mit einem Aufruf ein Archiv scheinbar als Diskette anzumelden - als ob jemand eine eingelegt hätte. Alles, was es dazu braucht, ist ein bißchen Wissen über ARexx und Einträge in die Mountlist. VoraussetzungenFür ein gutes Gelingen sollten folgende Dateien vorhanden sein:
Weiters braucht es ein Assign namens fms:, z.B. durch folgende Zeile in der s:user-startup:
Das entsprechende Verzeichnis muß natürlich schon angelegt sein, z.B. durch Eingabe im CLI von
Wer genauer wissen will, wozu das alles gut ist, der sei auf Teil 1 und Teil 2 des Workshops verwiesen Das im zweiten Teil bezüglich FMS beschriebene kryptische Geplänkel mit der Mountlist ist übrigens nicht notwendig. Warum, wird unten erläutert. BedienungBevor wir uns mit den Innereien beschäftigen, ist es wohl interessanter zu erfahren, was man mit dem MountDiskArchive.rexx Skript alles machen kann. Hauptzweck ist, ein Archiv in ein FMS-Gerät zu entpacken, ohne irgendwelche zusätzlichen Parameter angeben zu müssen. Das Skript ist schlau genug, anhand des Dateityps zu entscheiden, ob das Archiv an TransADF oder an dms weitergegeben werden soll. Folgendes ist also möglich, sofern eine freie Diskette in df0: eingelegt ist:
Und auch:
Das ist zwar nett, aber nichts, was nicht jeder halbwegs erfahrene Amiganer mit drei Zeilen ARexx (oder weniger) machen könnte. Praktischer ist schon folgender Aufruf:
Es sollte sich etwa folgende Ausgabe zeigen:
Und nach ein paar Sekunden hat die Workbench eine neue Disk namens Sepp-Disk erhalten. Lustiger wird es, wenn man das Skript ein weiteres mal aufruft, mit einem anderen Archiv als Parameter, z.B.
Es sollte sich etwa folgende Ausgabe zeigen:
Was ist passiert? Offensichtlich erwartet das Skript zwei Parameter: den Namen des Archivs und optional ein Ziellaufwerk, wohin es extrahiert werden soll. Gibt man kein Ziellaufwerk an, so wird ein neues FMS-Gerät angelegt, bevor das Archiv dorthin extrahiert wird. Das Praktische an diesem künstlich angelegten FMS-Gerät ist, daß man es nicht selbst anmelden ("mounten") muß. Noch besser, man braucht überhaupt nicht mit der devs:Mountlist herumfuddeln. Das vereinfacht die FMS-Installation beträchtlich. Verwendung in Directory Opus & Co.Der CLI zwar ganz nett zum herumexperimentieren, aber wen interessiert schon das Eingeben eines Befehls? Viele Amiganer haben die Unbrauchbarkeit der Workbench erkannt und bedienen sich gerne alternativer Programme wie Directory Opus, Filer und wie sie alle heißen mögen. Aus Gründen der Veranschaulichung beschreiben wir hier, wie man das Skript in Directory Opus einbindet, sodaß man ein DMS/ADF/ADZ Archiv nur mehr zu doppelklicken braucht, und - schwupps - ist ein neues Piktogramm auf der Workbench. Zuerst muß ein neuer Dateityp angelegt werden. Im Grunde gibt es drei Formate für Diskarchive: DMS, ADF und AFZ. Die letzten beiden lassen sich aus unserer Sicht zu einem zusammenfassen, weil beide mit dem selben Programm, nämlich TransADF, verarbeitet werden. Bei all seinen Mängeln hat DMS einen Vorteil: es ist einfach zu erkennen, da die ersten vier Byte den Text "DMS!" enthalten müssen. Bei ADF ist es schon schwieriger, da es sich hier nur um eine unstrukturierte Datenwurscht handelt. Man kann nicht einmal davon Ausgehen, daß sie 880K (also 901120 Bytes) lang ist. Theoretisch könnte man damit sogar ganze Festplattenpartitionen als ADF speichern (Für die meisten Anwedungsfälle ist man dafür aber mit einem Backup-Programm besser beraten). Noch schlimmer wird es bei ADZ: hier gibt es zwar eine Art Dateikennung am Anfang, nur ist es dieselbe wie bei den populären Packern ZIP und GZip - weil eben derselbe Algorithmus zum Komprimieren verwendet wird. Es wäre natürlich wenig sinnvoll, jedes ZIP-Archive einmal prophylaktisch als ADZ aufzufassen. Alles, was hier bleibt, ist die Dateiendung: .adf oder .adz. Das ist natürlich selten dämlich, aber mehr ist Dank der üblichen Ad-hoc Programmierphilosophie der Unix-Männer rund um Bernd "UAE" Schmidt leider nicht drin. Immerhin baut bei uns nicht das ganze Betriebssystem seine Dateitypen an solchen Konzepten auf (schlage nach bei Windows). Der Autor von TransADF trägt sich übrigens mit dem Gedanken, einmal ein vernünftiges, erweitertes ADF-Format zu definieren. Das hätte dann am Anfang eine Kennung und Informationen über die Größe und die Struktur der gespeicherten Disk. Rein technisch wäre ja nicht viel dahinter. Nur hat sich ADZ wahrscheinlich schon als "Standard" zu sehr etabliert. Aber hoffen darf man ja noch. Unabhängig davon kann jetzt dennoch ein Dateityp für Diskarchive definiert werden. Daten für die Eingabefelder sind hier fett, Feldbezeichnungen sind in normalen Text:
Danach bietet sich an, das Ereignis "Doppelklick" wie folgt festzulegen:
Die Argumentvariable {f} gibt die erste selektierte Datei an. Die Attribute "Ausgabe im Fenster" und "Schließsymbol" sind deshalb gesetzt, damit im Falle eines Fehlers dieser auch angezeigt wird - und nicht nur nichts passiert. Das Ereignis "Nehmen & Ablegen" könnte so aussehen:
Dies erlaubt es, ein Archiv auf einem bereits existierenden Disk-Piktogramm fallenzulassen. Natürlich muß es sich um ein Gerät mit einer Kapazität von 880K handeln. Das kann eine Floppy, ein FMS-Gerät oder eine richtig dimensionierte resetfeste Ram-Disk sein. Mit üblicherweise größeren Festplattenpartitionen oder der normalen Ram-Disk haben TransADF und DMS nicht viel Freude. Und weil wir gerade dabei sind, können sich Benutzer von Directory Opus u.Ä. folgende Funktion definieren, um den Inhalt einer Disk in df0: in einem ADZ-Archiv abzuspeichern. Dazu braucht man nicht einmal ein ARexx-Skript:
Die Argumentvariable {RF} öffnet ein Dateiauswahlfenster, in dem man den Namen angeben kann, unter dem das Archiv gespeichert werden soll. Vorzugsweise sollte er die Endung .adz erhalten, darum ist sie im Namensfeld des Fensters gleich eingetragen. Als Vorgabe des Zielverzeichnisses wird hier ram: verwendet, was natürlich jeder für sich ändern kann. Am besten aufgehoben ist so eine Funktion wahrscheinlich in einem Benutzermenü. Hinter den KulissenFür Interessierte folgt hier noch eine Erläuterung, wie die Sache intern abläuft. Die ersten paar Zeilen sollten für jeden mit etwas ARexx-Erfahrung verständlich sein: Sie prüfen die Parameter, kümmern sich um Fehler und zeigen gegebenenfalls einen kurzen Hilfstext an. Wenn alles glatt lief, enthält die Variable archive_name den Dateinamen (z.B. "sepp.adz"), suffix die Endung (z.B. "ADZ) und drive_name das Gerät, auf das extrahiert werden soll (z.B. "df0:"). Interessant für den Ablauf sind folgende Zeilen:
Wurde kein Zielgerät angegeben, so wird ein neues angelegt. Dafür zuständig ist die Prozedur mount_new_drive(). Diese Routine ist eines genaueren Blickes würdig. Zuerst wird nach einem noch nicht existenten FMS-Gerät gesucht. Das geht leicht, indem man zuerst schaut, ob ff0: angelegt ist. Wenn ja, probieren wir es mit ff1:, wenn das auch schon da ist, mit ff2: und so weiter. Folgende Schleife macht genau das:
Als Ergebnis enthält die Variable new_drive_unit die Zahl, die das anzulegende Gerät angibt. Ein Wert von 0 entspricht also dem Gerätenamen ff0:, 1 wäre ff1: und so weiter. Eben dieser Name wird in new_drive_name abgelegt. Danach wird es etwas gefinkelt. Jetzt wird nämlich ein Mountlist-Eintrag für das neue FMS-Gerät erzeugt. Vorerst wird dieser nur in einer Zeichenkette namens mount_entry gespeichert:
Um diese lange Zuweisung übersichtlicher zu machen, ist sie auf mehrere Zeilen verteilt. Jede Zeile endet aber mit einem Komma (,), sodaß die derzeitige und die nächste Zeile als eine einzige zusammengehörende aufgefaßt werden. Die Variable lf enthält ein einzelnes Zeichen, nämlich ein "linefeed", also jenes Zeichen, das einem Drücken der Enter-Taste entspricht. Sie wurde am Anfang des Programms gesetzt:
Für ff0: würde mount_entry daher folgenden Inhalt haben:
Die genaue Bedeutung dieser Zeilen muß nicht unbedingt jedem klar sein. Wichtig ist nur, das Device den Wert fmsdisk.device enthält, und Unit den Inhalt der Variable new_device_unit. Der Name, hier ff0:, wird am Anfang festgelegt und der Variable new_device_name entnommen. All die anderen Felder geben im Grunde nur an, daß es sich um ein Gerät mit einer Kapazität von 880K handelt. Wer mehr darüber wissen will, der sei an die AmigaDOS Dokumentation verwiesen. Folgende Zeilen schreiben den Inhalt von mount_entry in eine temporäre Datei:
Enthält drive_unit den Wert 0, so wird die Datei t:Mountlist.ff0 angelegt. Dabei handelt es sich im einen kleinen Bruder von devs:Mountlist, der nur einen einzigen Eintrag enthält, nämlich den für ff0:. Jetzt könnte man im CLI mit folgendem Befehl selbiges Gerät anmelden:
Das erledigt in unserem Fall natürlich das Skript. Da die temporäre Mountlist nicht mehr gebraucht wird, wird sie gleich danach gelöscht. Im Grunde bedarf dies nur eines Aufrufs von Address Command und etwas Wissen über den entsprechenden CLI-Befehl. Die Delete() Funktion ist aus der rexxsupport.library.
Um die Fehlerbehandlung zu vereinfachen, wird hier eine Prozedur namens address_command() verwendet. Die nimmt als ersten Parameter den erwarteten maximalen Rückgabewert und bricht das Skript ab, falls der CLI-Befehl einen größeren Wert liefert. Zusätzlich wird die Ausgabe des auszuführenden Befehls mittels ">>" an ein Logfile angehängt, auf dessen Bedeutung weiter unten näher eingegangen wird. Übrigens sorgt Address Command automatisch dafür, daß der Rückgabewert des letzten ausgeführten Befehls in der globalen Variable RC gespeichert wird:
Danach ist das neue Gerät fertig, um benutzt zu werden. Das eigentliche Extrahieren des Archives abhängig von der Dateiendung ist simpel:
Der Aufruf von "dms <nil:rx ..." bedient sich eines kleines Tricks: Dies gibt nämlich an, daß dms Tastatureingaben von nil: lesen soll. Dabei handelt es sich bekanntlich um das sogenannte Null-Device, das physikalisch gar nicht existiert. Wozu soll das gut sein? Vor dem eigentlichen Loswerkeln wartet dms nämlich auf einen Druck der Enter Taste. Das ermöglicht die Verwendung auf Rechnern mit nur einem Laufwerk und ohne Festplatte. In unserem Fall muß das FMS-Gerät natürlich nicht eingelegt werden, und ein jedesmal Enter drücken will auch niemand, wenn ein Archive entpackt werden soll. Der Clou ist: Weil nil: beim Lesen immer eine "Ende der Datei"-Kennung zurückgibt, gibt sich dms damit zufrieden - genauso, als ob Enter das Ende einer Tastatureingabe angegeben hätte. TransADF werkt übrigens auch ohne gutes Zureden, hier reicht der normale Aufruf. Hallo, Laufwerk! Jemand zuhause?Einer genaueren Erläuterung bedarf noch die schon zuvor angesprochene Funktion drive_exists(). Eigentlich würde ARexx mit exists() bereits eine Standardfunktion zur Verfügung stellen, die nicht nur für Dateien, sondern auch Geräte funktioniert. Dabei zeigen sich aber zwei Probleme: Erstens öffnet exists() ein Dialogfenster, das bittet, die entsprechende Disk einzulegen, falls ein Gerät eben nicht existiert. Das ist natürlich hier wenig sinnvoll, weil das Skript ja immer sucht, bis ein Gerät nicht existiert. Dem könnte man entgegenwirken, indem man mit dem weniger bekannten Pragma('Window', ...) jene Dialogfenster ausschaltet. Zusätzlich aber funktioniert die Standardfunktion exists() nur mit "normalen" DOS-Laufwerken. Viele Diskarchive enthalten jedoch Demos und Spiele mit eigenen Formaten. Diese werden von exists() als nicht vorhanden angesehen. Bevor man auch dafür einen Workaround sucht (z.B. basierend auf "Address Command 'assign exists ff0:'") lohnt es sich, gleich einen anderen Ansatz zu verwenden. Dieser basiert auf der Funktion ShowList(), die in der rexxsupport.library enthalten ist. Diese Bibliothek ist von Haus aus bei jedem AmigaOS dabei, stellt also keine erweiterten Voraussetzungen dar. ShowList() erlaubt es u.a., diverse systeminterne Listen nach bestimmten Einträgen zu untersuchen. Ein Aufruf erfolgt nach dem Schema
Die Variable found enthält dann 1, wenn entry in der mit list spezifizierten Liste enthalten war. Für uns interessant sind folgende Listen:
Wichtig ist vor allem die Handler Liste, weil sie auch unformatierte Disks enthält, die exists() nicht finden würde. Streng genommen bräuchten wir die anderen beiden Listen gar nicht anzusehen. Wir tun es aber trotzdem, um Komplikationen zu vermeiden falls ein Benutzer aus irgendwelchen Gründen einen Assign oder eine Partition mit einem Namen wie ff0: belegt hat. Etwas Vorsicht ist noch angebracht, da die Namen in diesen Listen keinen Doppelpunkt (:) enthalten. Dieser muß also zuvor von drive_name entfernt werden. Außerdem sind alle Einträge in der Liste in Großschrift, sodaß drive_name zuvor umgewandelt werden muß. Beides läßt sich praktischerweise gleich bei der Auswertung der Parameter erledigen:
Damit sollte die Grundstruktur klar sein. Ein paar Feinheiten sind vielleicht doch noch eine Erwähnung wert: Ein paar KleinigkeitenDas Skript wartet ein Logfile, in dem es mitprotokoliert, was so alles passiert. Dies wird in eine Datei namens t:MountDiskArchive.log geschrieben. Normalerweise wird das Logfile am Ende gelöscht, außer es tritt ein Fehler auf oder der Schalter Debug wurde beim Aufruf des Skripts gesetzt. In so einem Fall kann man es sich ansehen. Für Leute, die gerne mit dem Skript herumspielen oder etwaige Bugs einkreisen wollen, kann das von Interesse sein. Für das Protokollieren zuständig sind übrigens die beiden Prozeduren append_log_line() und append_log_heading(). Zusätzlich sorgt address_command() dafür, daß CLI-Befehle ihre Ausgabe dort dranhängen. Weil die meisten Amiganer Multitasking gewohnt sind, kann leicht jemand versucht sein, nebeneinander mehrere Archive anzumelden. Für die meisten Aktionen, die das Skript ausführt, ist es egal, wieviele Skripts zur selben Zeit das machen. Bei anderen Dingen darf sich der Benutzer nicht wundern, wenn es nicht klappt, z.B. wenn er versucht, drei verschiedene Archive gleichzeitig als df0: anzumelden. Für solche Fälle haben TransADF und dms vorgesorgt. Anders verhält es sich jedoch beim automatischen Anmelden eines neuen FMS-Geräts. Dafür ist nur unser Skript zuständig. Theoretisch könnte das Skript zweimal laufen und praktisch gleichzeitig zu der Entscheidung kommen, daß z.B. ff3: angelegt werden muß. Versuchen jetzt aber beide Skripts, ein
so präsentiert sich dem Benutzer ein etwas lapidares
Weil die meisten nicht wissen werden (und gar nicht wissen wollen), wie das Programm funktioniert, hilft diese Meldung nicht viel weiter. Das Problem ließe sich eigentlich einfach lösen, indem man das Skript nach kurzer Zeit nochmals mit den selben Parametern aufruft. Um dem vorzubeugen, muß die bereits behandelte Funktion mount_new_drive() einen eingebauten Schutz kriegen, sodaß sie nur einmal zur selben Zeit laufen kann. Im Grunde gibt es zwei Ansätze, um das zu lösen: Erstens kann man brachial das Multitasking ausschalten. Dazu gäbe es Forbid() und Permit(). Das ist aber selten eine elegante Lösung, vor allem nicht in ARexx, daß bei seinen gemächlichen Ausführungszeiten schon mal ein paar Sekunden den Rechner stehen lassen könnte. Besser ist es, eine Art "systemweite" Variable einzuführen, die ein Skript für sich reservieren muß. War zuvor schon ein Skript schneller damit, so weiß das nächste Skript, daß es nicht alleine ist - und kann diesen Umstand mit einer wesentlich konstruktiveren Meldung kundtun. Eine Datenstruktur, die solche eine systemweite Variable mit Zugriffregelung zuläßt ein öffentlicher Message-Port. Zwar kann man damit einiges mehr anstellen, für unsere Zwecke reicht aber folgendes: Mit OpenPort(name) wird ein neuer Port mit dem Namen name angelegt und der Wert 1 zurückgeliefert. Existiert so ein Port aber bereits, so wird er nicht erneut angelegt, sondern nur 0 zurückgeliefert. Das erlaubt folgende Kontrolle am Anfang von mount_new_drive():
Nach getaner Arbeit kann man den Port wieder schließen:
Eine kleine Unschönheit ist wie gesagt, daß hier mit Kanonen auf Spatzen geschossen wird. Ein Message-Port ist in Wahrheit eine komplexe Datenstruktur, die es erlaubt, zwischen verschiedenen Tasks Nachrichten auszutauschen. So ist z.B. jeder ARexx-Port, an den man Befehle schicken kann, so ein Message-Port. Wir aber mißbrauchen ihn mangels besserer Alternativen als eine simple systemweite Variable mit eingebauter Zugriffsregelung. Normalerweise nennt man so einen Mechanismus Semaphore. Die kann das AmigaOS zwar auch, nur kann man sie hier nicht so schön mit einem Namen ansprechen, sondern muß mit den aus anderen Programmiersprachen berüchtigten Zeigern (pointer) herumwurschteln. Außerdem weiß ein Task nicht von Anfang an, wo genau eine solche Semaphore anzufinden ist. Alles Dinge, mit denen sich ein gelegentlicher ARexx-Programmierer lieber nicht beschäftigen will, darum findet sich auch kein Äquivalent dafür in der rexxsupport.library. Besser als MountImage?Im ersten Teil wurde kurz MountImage der Apfelmänner, das große Vorbild aller Diskarchivprogramme, angesprochen. Vergleicht man unser kleines ARexx-Skript damit, so muß man feststellen, daß das Vorbild nicht ganz erreicht wurde. Zwar ist die Anzahl der gleichzeitig angemeldeten Archive nur von der Festplattengröße eingeschränkt, und die Bedienung ist nach Einbindung in DOpus und Verwandte auch kinderleicht - nix mehr CLI und kryptische Optionen. Vorteilhaft wirkt sich auch die Tatsache aus, daß es trivial ist, etwaige zukünftige Diskarchivformate zu unterstützen. Denn obwohl ADZ etliche Mängel von ADF und DMS behebt, so fehlt wie bereits erörtert noch einiges wie z.B. ein Header, der über die Blockstruktur der gespeicherten Disk Auskunft gibt. Ein großer Nachteil des Skripts ist die Tatsache, daß man ein Archive nicht wieder "abmelden" kann. Das heißt, der Platz in fms: bleibt belegt, selbst nach einem Reset. Denn obwohl ein etwaiges ff0: nur temporär angelegt und angemeldet wird, so bleibt das entsprechende fms:Unit0 erhalten. Dazu gibt es drei Lösungsansätze: erstens, die Platte ist groß genug und es ist einem wurscht wenn 20MB herumverfilzen, zweitens kann man in der s:user-startup ein "delete quiet fms:Unit#?" machen und unerwünschte Nebenwirkungen zumindest beim nächsten Neustart beseitigen oder drittens dekadent sein und sagen: "assign fms: t:". Letzteres ist wohl nur für Leute mit viel RAM interessant. Unlustig ist das Verhalten von FMS, wenn man versucht, dasselbe Archiv auf mehrere FMS-Geräten anzumelden oder wenn zwei Disks den selben Namen haben. Auch wenn das nicht unbedingt sinnvoll ist, so ist es doch eine Unschönheit in FMS. Weiters ist die Fehlerbehandlung nicht ganz astrein wenn man versucht, ein Archiv auf ein Gerät zu extrahieren, daß dies nicht unterstützt:
Daraufhin präsentiert sich uns folgende Ausgabe:
Zwar enthält die zweite Zeile - TransADF sei dank - eine einigermaßen brauchbare Beschreibung des Problems, nur geht dies etwas unter in den beiden (völlig redundanten) folgenden Zeilen. Wirklich amüsant wird es, wenn man das gleiche mit dms versucht: da kommt dann nämlich die böse Endlosschleife, bis nach einiger Zeit der Speicher ausgeht... Hier muß leider der Benutzer für den Programmierer denken. FazitWir verbeugen uns also vor den Apfelmännern und räumen ein, daß wir von ihnen noch was lernen können. Nichtdestotrotz haben wir mit ein paar Zeilen ARexx ein halbwegs vergleichbares Ergebnis mit zugegeben einigen unschönen Kratzern erreicht. Bei den Apfelmänner braucht es dazu etliche Kilobyte gefinkelte Systemprogrammierung in Unsprachen wie C. Das Ergebnis spricht wieder einmal dafür, das der Amiga ein Rechner ist, der gerne um den Benutzer herum wächst. Dabei muß man aber nicht entweder warten, bis jemand einen doddlsicheren Fertigteil produziert oder etwa in nicht endendwollende Untiefen kryptischer und unaussprechbarer Befehlszeilen und schlechter Dokumentation eintauchen. Flexibilität muß nicht nur Shell-Machos vorbehalten bleiben. Das beliebte Trio CLI/ARexx/Workbench-Ersatz hat es einmal mehr erlaubt, mit wenig Aufwand und ohne großes Dazulernen neuer Konzepte, Durchprobieren von Konfigurationsoptionen und Anwenden von 76432 verschiedenen Skript- und Macrosprachen selbst komplizierte Abläufe auf einen Doppelklick zu reduzieren. Sogar, wenn man dabei über 10 Jahre Geschichte Revue passieren lassen kann und Dinge verbindet, die nie dazu gedacht waren, zusammenzuarbeiten. |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||