--ILTFloppyImpl.mesa
--Created by
--   JFung.PASA	  	14-Dec-83 12:37:04
--	a modified version taken from FloppyStuff.mesa
--last edited by
--   JFung.PASA	   	20-Dec-83 15:22:23



DIRECTORY
     AccessFloppy USING [
          Attributes, AttributesRecord, Close, CreateFile, currentVersion,
          DeleteFile, Error, ErrorType, GetAttributes, InconsistentFile,
          InvalidVersion, leaderLength, LookUp, maxDataSize, maxNameLength,
          NoRoomForClientData, Open, tFloppyLeaderPage],
     Environment USING [bytesPerPage],
     Exec USING [
          Confirm, ExecProc, FreeTokenString, GetToken, Handle, MatchPattern,
          Outcome, OutputProc],
     File USING [Type],
     FileTypes USING [tUntypedFile],
     Floppy USING [
          AlreadyFormatted, Close, CopyFromPilotFile, CopyToPilotFile, Density,
          Error, ErrorType, FileHandle, Format, GetAttributes, GetFileAttributes,
          GetNextFile, maxCharactersInLabel, nullFileID, Open, Sides,
          VolumeHandle],
     Format USING [Char, Date, Line, LongDecimal, LongNumber, Number, StringProc],
     FormSW USING [
          AllocateItemDescriptor, ClientItemsProcType, CommandItem, Destroy,
          Display, DisplayItem, Enumerated, EnumeratedItem, FindItem,
          FreeHintsProcType, ItemHandle, newLine, MenuProcType, ProcType,
          SetTagPlaces, StringItem],
     Heap USING [systemZone],
     LispToolOps,
     MFile USING [
          Error, ErrorCode, GetProperties, Handle, ReadOnly, Release,
          SetProperties, Type, WriteOnly],
     NSString USING [StringFromMesaString],
     Process USING [Pause, SecondsToTicks],
     Put,
     SpecialMFile USING [GetCapaWithAccess, LeaderPages],
     String USING [
          AppendLongDecimal, AppendString, Copy, Equivalent, InvalidNumber,
          Replace, StringToNumber],
     StringLookUp USING [InTable, noMatch],
     Time USING [Current],
     Tool USING [
          Create, Destroy, MakeFileSW, MakeFormSW, MakeMsgSW, MakeSWsProc,
          UnusedLogName],
     ToolDriver USING [Address, NoteSWs, RemoveSWs],
     ToolWindow USING [Activate, Deactivate, DisplayProcType, TransitionProcType],

     Token USING [Decimal, FreeStringHandle, Handle, StringToHandle, SyntaxError],
     Volume,
     Window USING [
          BitmapPlace, Box, GetChild, GetDisplayProc, GetParent, Handle,
          SetDisplayProc, Stack, ValidateTree];



ILTFloppyImpl: PROGRAM
     IMPORTS
          AccessFloppy, Exec, Floppy, Format, FormSW, Heap, LispToolOps, MFile,
          NSString, Process, Put, SpecialMFile, String, StringLookUp, Time, Tool,
          ToolDriver, ToolWindow, Token, Volume, Window


     EXPORTS LispToolOps =

     BEGIN OPEN ILT: LispToolOps;

     -- TYPEs
     Indicator: TYPE = {off, left, right};
     VolHints: TYPE = RECORD [names: SEQUENCE length: CARDINAL OF LONG STRING];
     SizeHints: TYPE = RECORD [names: SEQUENCE length: CARDINAL OF CARDINAL];


     DataHandle: TYPE = LONG POINTER TO Data;
     Data: TYPE = MACHINE DEPENDENT RECORD [
          -- Message subwindow stuff
          msgSW(0): Window.Handle ← NIL,
          -- File subwindow stuff
          fileSW(2): Window.Handle ← NIL,

          -- Form subwindow stuff
          -- Note: enumerateds and booleans must be word boundary
          -- aligned as addresses for them must be generated
          --formSW: Window.Handle ← NIL,
          paramSW(4): Window.Handle ← NIL,
          commandSW(6): Window.Handle ← NIL,

          busy(8): BOOLEAN ← FALSE,  -- command is running
          volName(9): LONG STRING ← NIL,
          pattern(11): LONG STRING ← NIL,
          indicator(13): Indicator ← left];


     EnumProc: TYPE = PROCEDURE [
          attributes: AccessFloppy.Attributes, fH: Floppy.FileHandle,
          name: LONG STRING] RETURNS [stop: BOOLEAN ← FALSE];


     active: BOOLEAN ← FALSE;
     floppyWH: Window.Handle ← NIL;
     debug: BOOLEAN ← TRUE;
     toolData: DataHandle ← NIL;

     formDisplay: ToolWindow.DisplayProcType ← NIL;
     heraldName: STRING ← [50];
     indicatorBox: Window.Box = [[10, 10], [16, 16]];
     volume: Volume.ID ← Volume.nullID;



     dataVersion: CARDINAL = 02222;  -- version of clientData
     Abort: ERROR [s: STRING] = CODE;
     SetOutcome: SIGNAL [value: Exec.Outcome] = CODE;


     DeleteFiles: PROCEDURE[] =
          BEGIN
          name: LONG STRING ← NIL;
          v: Floppy.VolumeHandle;

          DeleteOne: EnumProc =
               BEGIN
               Put.Text[toolData.fileSW, name];
               Put.Text[toolData.fileSW, "... "L];
               AccessFloppy.DeleteFile[NSString.StringFromMesaString[name] !
                    AccessFloppy.Error =>
                         SELECT type FROM
                              fileNotFound => {
                                   Put.Line[toolData.fileSW, "not found"L];
				  -- Process.Pause[Process.SecondsToTicks[10]];
                                   GOTO error};
                              ENDCASE];
               Put.Line[toolData.fileSW, "deleted"L];
	      --Process.Pause[Process.SecondsToTicks[10]];
               EXITS error => Put.Line[toolData.fileSW, "error"L];
               END;  --DeleteOne

          v ← AccessFloppy.Open[
               ! AccessFloppy.Error => ERROR Abort["Can't open floppy"L]];
	  BEGIN     
          ENABLE UNWIND => {AccessFloppy.Close[ ! Floppy.Error => CONTINUE]};

               --name ← toolData.pattern;
               --IF toolData.pattern = NIL THEN EXIT;
               IF WildCards[toolData.pattern] THEN EnumerateFloppyFiles[v, DeleteOne, toolData.pattern]
               ELSE [] ← DeleteOne[NIL, [v, Floppy.nullFileID], toolData.pattern];
          END;
          AccessFloppy.Close[];
          END;  --DeleteFiles



     EnumerateFloppyFiles: PROCEDURE [
          v: Floppy.VolumeHandle, proc: EnumProc, pattern: LONG STRING ← NIL] =
          BEGIN
          nullFile: Floppy.FileHandle = [volume: v, file: Floppy.nullFileID];
          attributes: AccessFloppy.Attributes ← Heap.systemZone.NEW[
               AccessFloppy .AttributesRecord[AccessFloppy.maxDataSize]];
          name: LONG STRING = LOOPHOLE[@attributes.length];

          IF debug THEN {
               Put.Line[toolData.fileSW, "EnumerateFloppyFiles...."L];
               --Process.Pause[Process.SecondsToTicks[5]];
               };

          FOR current: Floppy.FileHandle ← Floppy.GetNextFile[nullFile].nextFile,
               Floppy.GetNextFile[current].nextFile WHILE current # nullFile DO
               ENABLE UNWIND => Heap.systemZone.FREE[@attributes];
               IF Floppy.GetFileAttributes[current].type #
                    AccessFloppy.tFloppyLeaderPage THEN LOOP;
	       IF debug THEN {
               Put.Line[toolData.fileSW, "Floppy.GetNextFile"L];
               --Process.Pause[Process.SecondsToTicks[5]];
               };

               AccessFloppy.GetAttributes[current, attributes];
	       Put.Text[toolData.fileSW, "Pattern = "L];
	       Put.Line[toolData.fileSW, pattern];
               IF
                    --(
                    pattern = NIL
                    --OR Exec.MatchPattern[string: name, pattern: pattern])
                    OR proc[attributes, current, name] THEN {
		    	Put.Line[toolData.fileSW, "Exited loop."L];
			EXIT;
			};
               ENDLOOP;
          Heap.systemZone.FREE[@attributes];
          END;  --EnumerateFloppyFiles




     FloppyStuff: PUBLIC PROCEDURE =
          BEGIN
          aborted: BOOLEAN ← FALSE;

          IF toolData = NIL THEN toolData ← Heap.systemZone.NEW[Data ← []];
          IF debug THEN {
               --Put.Line[ILT.toolData.fileSW, "FloppyStuff...."L];
               --Process.Pause[Process.SecondsToTicks[5]];
               };
          floppyWH ← MakeTool[];

          BEGIN
          ENABLE {
               Abort => {Put.Line[toolData.fileSW, s]; aborted ← TRUE; };
               AccessFloppy.Error => {
                    Put.Text[toolData.fileSW, "unexpected "L];
                    WriteAccessFloppyError[type];
                    Put.CR[toolData.fileSW];
                    aborted ← TRUE;
                    };
               AccessFloppy.InconsistentFile => {
                    Put.Line[toolData.fileSW, "AccessFloppy.InconsistentFile"L];
                    aborted ← TRUE;
                    };
               AccessFloppy.InvalidVersion => {
                    Put.Line[toolData.fileSW, "AccessFloppy.InvalidVersion"L];
                    aborted ← TRUE;
                    };
               AccessFloppy.NoRoomForClientData => {
                    Put.Line[toolData.fileSW, "AccessFloppy.NoRoomForClientData"L];
                    aborted ← TRUE;
                    };
               Floppy.Error => {
                    IF error = writeInhibited THEN
                         Put.Line[toolData.fileSW, "Floppy is write protected"L]
                    ELSE {
                         Put.Text[toolData.fileSW, "unexpected "L];
                         --WriteFloppyError[error];
                         Put.CR[toolData.fileSW];
                         };
                    aborted ← TRUE;
                    };
               };

          END;
          END;  --FloppyStuff


     <<
     FormatDisk: PROCEDURE [gSwitches: LONG STRING, h: Exec.Handle] =
          BEGIN
          Write: Format.StringProc = Exec.OutputProc[h];
          density: Floppy.Density ← default;
          sides: Floppy.Sides ← default;
          label: STRING ← [Floppy.maxCharactersInLabel];
          arg, switches: LONG STRING ← NIL;
          nFiles: CARDINAL ← 64;
          BEGIN
          gotError: BOOLEAN ← FALSE;
          v: Floppy.VolumeHandle;
          v ← Floppy.Open[
               !
               Floppy.Error =>
                    SELECT error FROM
                         invalidFormat => GOTO virgin;
                         needsScavenging => {gotError ← TRUE; CONTINUE};
                         ENDCASE];
          IF ~gotError THEN {
               [] ← Floppy.GetAttributes[v, label];
               Floppy.Close[v];
               Write["Floppy volume """L];
               Write[label];
               Write[""" already formatted"L]}
          ELSE Write["Floppy probably contains valid information"L];
          Write["; formatting will destroy previous contents"L];
          IF ~Exec.Confirm[h] THEN ERROR Abort[NIL];
          Format.Line[Write, " yes"L];
          EXITS virgin => NULL;
          END;
          label.length ← 0;
          String.AppendString[label, "UnnamedFloppy"L];
          DO
               arg ← Exec.FreeTokenString[arg];
               switches ← Exec.FreeTokenString[switches];
               [arg, switches] ← Exec.GetToken[h];
               IF arg = NIL AND switches = NIL THEN EXIT;
               IF switches = NIL THEN LOOP;
               FOR i: CARDINAL IN [0..switches.length) DO
                    SELECT switches[i] FROM
                         'n => {
                              label.length ← 0;
                              FOR j: CARDINAL IN
                                   [0..label.length ← MIN[
                                         arg.length, label.maxlength]) DO
                                   label[j] ← arg[j]; ENDLOOP};
                         'f =>
                              nFiles ← String.StringToNumber[
                                   arg ! String.InvalidNumber => CONTINUE];
                         ENDCASE;
                    ENDLOOP;
               ENDLOOP;
          Write["Formatting... "L];
          Floppy.Format[
               0, nFiles, label, density, sides !
               Floppy.Error =>
                    IF error = badDisk THEN
                         ERROR Abort[
                              "Can't format this disk; may be write protected"L];
               Floppy.AlreadyFormatted => RESUME ];
          Format.Line[Write, "...done"L];
          InfoDisk[];
          END;
>>


     FormSWDeleteProc: FormSW.ProcType =
          BEGIN
          IF debug THEN {
               Put.Line[toolData.fileSW, "FormSWDeleteProc...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };
          DeleteFiles;
          END;  --FormSWDeleteProc



     FormSWDuplicateProc: FormSW.ProcType =
          BEGIN
          IF debug THEN {
               Put.Line[toolData.fileSW, "FormSWDuplicateProc...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };
          --InfoDisk;
          END;  --FormSWDuplicateProc




     FormSWFormatProc: FormSW.ProcType =
          BEGIN
          IF debug THEN {
               Put.Line[toolData.fileSW, "FormSWFormatProc...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };
          
          END;  --FormSWFormatProc




     FormSWInfoProc: FormSW.ProcType =
          BEGIN
          IF debug THEN {
               Put.Line[toolData.fileSW, "FormSWInfoProc...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };
          InfoDisk;
          END;  --FormSWInfoProc



     FormSWListProc: FormSW.ProcType =
          BEGIN
          IF debug THEN {
               Put.Line[toolData.fileSW, "FormSWListProc...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };
          ListFiles;
          END;  --FormSWListProc



     FormSWReadProc: FormSW.ProcType =
          BEGIN

          IF toolData # NIL THEN
               BEGIN Tool.Destroy[floppyWH]; Heap.systemZone.FREE[@toolData]; END;
          END;  --FormSWReadProc



     FormSWWriteProc: FormSW.ProcType =
          BEGIN

          IF toolData # NIL THEN
               BEGIN Tool.Destroy[floppyWH]; Heap.systemZone.FREE[@toolData]; END;
          END;  --FormSWWriteProc



     FormSWQuitProc: FormSW.ProcType =
          BEGIN

          IF toolData # NIL THEN
               BEGIN Tool.Destroy[floppyWH]; Heap.systemZone.FREE[@toolData]; END;
          END;  --FormSWQuitProc



     FormSWVolHintsProc: FormSW.MenuProcType =
          BEGIN
          RETURN[
               hints: DESCRIPTOR[
               @ILT.toolData.volHints[0], ILT.toolData.volHints.length],
                    freeHintsProc: HintsNoLongerBusy, replace: TRUE];
          END;


     HintsNoLongerBusy: FormSW.FreeHintsProcType = BEGIN END;


     InfoDisk: PROCEDURE[] =
          BEGIN
          density: Floppy.Density;
          sides: Floppy.Sides;
          label: STRING ← [Floppy.maxCharactersInLabel];
          freeSpace, largestBlock: LONG CARDINAL;
          v: Floppy.VolumeHandle;

          IF debug THEN {
               Put.Line[toolData.fileSW, "AccessFloppy.Open...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };

          v ← AccessFloppy.Open[
               ! AccessFloppy.Error => ERROR Abort["Can't open floppy"L]];

          IF debug THEN {
               Put.Line[toolData.fileSW, "GetAttributes...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };

          [freeSpace, largestBlock, , , density, sides] ← Floppy.GetAttributes[
               v, label];
          Put.Text[toolData.fileSW, "Floppy """L];
          Put.Text[toolData.fileSW, label];
          Put.Text[toolData.fileSW, """; "L];
          Put.Text[toolData.fileSW, IF sides = one THEN "single"L ELSE "double"L];
          Put.Text[toolData.fileSW, " sided; "L];
          Put.Text[toolData.fileSW, IF density = single THEN "single"L ELSE "double"L];
          Put.Line[toolData.fileSW, " density"L];
          Put.LongDecimal[toolData.fileSW, freeSpace];
          Put.Text[toolData.fileSW, " free pages; largest free block = "L];
          Put.LongDecimal[toolData.fileSW, largestBlock];
          Put.Line[toolData.fileSW, " pages"L];
          AccessFloppy.Close[];
          END;  --InfoDisk


     ListFiles: PROCEDURE[] =
          BEGIN
          pattern: LONG STRING ← NIL;
          v: Floppy.VolumeHandle;
	  
          ListOne: EnumProc =
               BEGIN
               Put.CR[toolData.fileSW];
               Put.Text[toolData.fileSW, name];
               FOR i: CARDINAL IN [name.length + WritePartial[attributes]..24) DO
                    Put.Text[toolData.fileSW, " "]; ENDLOOP;
               Put.Number[toolData.fileSW, attributes.type, [10, FALSE, TRUE, 5]];
               Put.Text[toolData.fileSW, "  "L];
               Put.LongNumber[
                    toolData.fileSW, attributes.totalSizeInBytes, [
                    10, FALSE, TRUE, 9]];
               Put.Text[toolData.fileSW, "  "L];
               Put.Date[toolData.fileSW, attributes.createDate, noSeconds];
               Put.Text[toolData.fileSW, "  "L];
               Put.Date[toolData.fileSW, attributes.lastWrittenDate, noSeconds];
               END;  --ListOne

          IF debug THEN {
               Put.Line[toolData.fileSW, "ListFiles: AccessFloppy.Open...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };
          v ← AccessFloppy.Open[
               ! AccessFloppy.Error => ERROR Abort["Can't open floppy"L]];
          
          Put.Text[toolData.fileSW, " NAME                   "L]; --23 
          Put.Text[toolData.fileSW, " TYPE  "L];  -- 5+2
          Put.Text[toolData.fileSW, "   LENGTH  "L];  -- 9+2
          Put.Text[toolData.fileSW, "      CREATE          "L];  -- 20+2
          Put.Line[toolData.fileSW, "  WRITE"L];
          EnumerateFloppyFiles[
               v, ListOne, pattern !
               UNWIND => {AccessFloppy.Close[ ! Floppy.Error => CONTINUE]}];
          AccessFloppy.Close[];
          END;  --ListFiles



     MakeAttributes: PROCEDURE [
          file: MFile.Handle, name: LONG STRING,
          attributes: AccessFloppy.Attributes, type: File.Type] =
          BEGIN
          attributes.version ← AccessFloppy.currentVersion;
          attributes.type ← type;
          attributes.lastWrittenDate ← Time.Current[];
          attributes.name ← ALL[0];
          attributes.clientDataLength ← 2;
          attributes.clientData[0] ← dataVersion;
          [length: attributes.totalSizeInBytes, create: attributes.createDate,
               type: attributes.clientData[1]] ← MFile.GetProperties[file];
          attributes.totalSize ← PagesFromBytes[attributes.totalSizeInBytes];
          attributes.size ← attributes.totalSize;
          attributes.length ← MIN[name.length, AccessFloppy.maxNameLength];
          FOR i: CARDINAL IN [0..attributes.length) DO
               attributes.name[i] ← LOOPHOLE[name[i]]; ENDLOOP
          END;  --MakeAttributes



     MakeCommands: FormSW.ClientItemsProcType =
          BEGIN OPEN FormSW;

          tabs: ARRAY [0..5) OF CARDINAL ← [0, 20, 40, 60, 70];
          nItems: CARDINAL = 8;
          items ← AllocateItemDescriptor[nItems];

          items[0] ← CommandItem[
               tag: "Info"L, place: newLine, proc: FormSWInfoProc];

          items[1] ← CommandItem[tag: "List"L, proc: FormSWListProc];

          items[2] ← CommandItem[tag: "Read"L, proc: FormSWReadProc];

          items[3] ← CommandItem[tag: "Write"L, proc: FormSWWriteProc];

          items[4] ← CommandItem[
               tag: "Format"L, place: newLine, proc: FormSWFormatProc];

          items[5] ← CommandItem[tag: "Delete"L, proc: FormSWDeleteProc];

          items[6] ← CommandItem[tag: "Duplicate"L, proc: FormSWDuplicateProc];

          items[7] ← CommandItem[tag: "Quit"L, proc: FormSWQuitProc];


          SetTagPlaces[items, DESCRIPTOR[tabs], FALSE];
          RETURN[items, TRUE];
          END;  --MakeCommands




     MakeParams: FormSW.ClientItemsProcType =
          BEGIN OPEN FormSW;
          i, nVols: CARDINAL;
          tabs: ARRAY [0..4) OF CARDINAL ← [0, 30, 60, 75];
          nItems: CARDINAL = 2;

          items ← AllocateItemDescriptor[nItems];
          nVols ← ILT.ListLogicalVolumes[];

          String.Copy[toolData.volName, ILT.toolData.volHints[0]];
          FOR i IN [0..nVols) DO
               IF String.Equivalent[ILT.toolData.volHints[i], "Lisp"L] THEN
                    BEGIN
                    String.Replace[
                         @toolData.volName, ILT.toolData.volHints[i],
                         Heap.systemZone];
                    EXIT;
                    END;
               ENDLOOP;

          items[0] ← StringItem[
               tag: "Source Volume"L, place: newLine, string: @toolData.volName,
               inHeap: TRUE, menuProc: FormSWVolHintsProc];


          items[1] ← StringItem[
               tag: "Pattern"L, string: @toolData.pattern, inHeap: TRUE];

          SetTagPlaces[items, DESCRIPTOR[tabs], FALSE];
          RETURN[items, TRUE]
          END;  --MakeParams




     MakeSWs: Tool.MakeSWsProc =
          BEGIN

          addresses: ARRAY [0..4) OF ToolDriver.Address;
          logName: STRING ← [20];

          IF debug THEN {
               Put.Line[ILT.toolData.fileSW, "MakeSWs...."L];
               Process.Pause[Process.SecondsToTicks[5]];
               };

          Tool.UnusedLogName[unused: logName, root: "FloppyOptions.log"L];
          toolData.msgSW ← Tool.MakeMsgSW[window: window, lines: 1];
          toolData.paramSW ← Tool.MakeFormSW[window: window, formProc: MakeParams];
          toolData.commandSW ← Tool.MakeFormSW[
               window: window, formProc: MakeCommands];

          --note: logName is compulsory, else it bombs     
          toolData.fileSW ← Tool.MakeFileSW[window: window, name: logName];

          --Supervisor.AddDependency[client: agent, implementor: Event.toolWindow];

          -- do the ToolDriver stuff
          addresses ← [
               [name: "msgSW"L, sw: toolData.msgSW],
               --[name: "formSW"L, sw: toolData.formSW],
               [name: "ParamSW"L, sw: toolData.paramSW], [
               name: "CmdSW"L, sw: toolData.commandSW], [
               name: "fileSW"L, sw: toolData.fileSW]];
          ToolDriver.NoteSWs[
               tool: "FloppyOptions"L, subwindows: DESCRIPTOR[addresses]];

          END;  --MakeSWs



     MakeTool: PROCEDURE RETURNS [wh: Window.Handle] =
          BEGIN

          RETURN[
               Tool.Create[
                    makeSWsProc: MakeSWs, initialState: default,
                    --clientTransition: ClientTransition,
                    name: "Floppy Options"L]];
          --initialBox: [
          --place: sw.BitmapPlace[[10, hisBox.dims.h]],
          --dims: [hisBox.dims.w - 20, 180]]];
          END;  --MakeTool


     NameFromAttributes: PROCEDURE [attributes: AccessFloppy.Attributes]
          RETURNS [LONG STRING] = INLINE {RETURN[LOOPHOLE[@attributes.length]]};


     ParsePartial: PROCEDURE [name: LONG STRING]
          RETURNS [offset: CARDINAL ← 0, length: CARDINAL ← LAST[CARDINAL]] =
          BEGIN
          h: Token.Handle;
          i: CARDINAL;
          IF name = NIL THEN RETURN;
          FOR i IN [1..name.length) DO
               IF name[i] = '[ THEN EXIT; REPEAT FINISHED => RETURN ENDLOOP;
          h ← Token.StringToHandle[name, i + 1];
          BEGIN
          ENABLE UNWIND => [] ← Token.FreeStringHandle[h];
          offset ← Token.Decimal[h];
          SELECT h.break FROM
               '. =>
                    IF h.getChar[h] = '. THEN
                         length ← Token.Decimal[h] - offset + 1
                    ELSE ERROR Token.SyntaxError[NIL];
               '! => length ← Token.Decimal[h];
               '] => NULL;
               ENDCASE => ERROR Token.SyntaxError[NIL];
          END;
          name.length ← i;
          [] ← Token.FreeStringHandle[h];
          END;  --ParsePartial


     PagesFromBytes: PROCEDURE [bytes: LONG CARDINAL] RETURNS [LONG CARDINAL] =
          BEGIN
          RETURN[(bytes + Environment.bytesPerPage - 1)/Environment.bytesPerPage];
          END;  --PagesFromBytes



     ReadFiles: PROCEDURE [gSwitches: LONG STRING, h: Exec.Handle] =
          BEGIN
          attributes: AccessFloppy.Attributes ← NIL;
          name, altName, switches: LONG STRING ← NIL;
          Write: Format.StringProc = Exec.OutputProc[h];
          v: Floppy.VolumeHandle;
          ReadOne: EnumProc =
               BEGIN
               mFile: MFile.Handle;
               type: MFile.Type ← unknown;
               destName: LONG STRING = IF altName = NIL THEN name ELSE altName;
               Write[name];
               Write["... "L];
               IF attributes.clientDataLength = 2
                    AND attributes.clientData[0] = dataVersion THEN
                    type ← attributes.clientData[1];
               Write["copying to "L];
               Write[destName];
               [] ← WritePartial[attributes];
               Write["... "L];
               mFile ← MFile.WriteOnly[
                    destName, [], type, attributes.totalSizeInBytes !
                    MFile.Error => {
                         Format.Line[Write, "can't write on destination file"L];
                         SetOutcome[error];
                         GOTO return}];
               MFile.SetProperties[
                    file: mFile, type: type, length: attributes.totalSizeInBytes,
                    create: attributes.createDate];
               IF attributes.size # 0 THEN
                    Floppy.CopyToPilotFile[
                         floppyFile: fH,
                         pilotFile: SpecialMFile.GetCapaWithAccess[mFile],
                         firstFloppyPage: AccessFloppy.leaderLength,
                         firstPilotPage:
                         SpecialMFile.LeaderPages[] + attributes.offset,
                         count: attributes.size];
               Format.Line[Write, "copied"L];
               MFile.Release[mFile];
               EXITS return => NULL;
               END;
          v ← AccessFloppy.Open[
               ! AccessFloppy.Error => ERROR Abort["Can't open floppy"L]];
          DO
               ENABLE
                    UNWIND => {
                         altName ← Exec.FreeTokenString[altName];
                         name ← Exec.FreeTokenString[name];
                         switches ← Exec.FreeTokenString[switches];
                         Heap.systemZone.FREE[@attributes];
                         AccessFloppy.Close[ ! Floppy.Error => CONTINUE]};
               fFile: Floppy.FileHandle;
               failed: BOOLEAN ← FALSE;
               IF attributes = NIL THEN
                    attributes ← Heap.systemZone.NEW[
                         AccessFloppy .AttributesRecord[AccessFloppy.maxDataSize]];
               altName ← Exec.FreeTokenString[altName];
               name ← Exec.FreeTokenString[name];
               switches ← Exec.FreeTokenString[switches];
               [name, switches] ← Exec.GetToken[h];
               IF name = NIL AND switches = NIL THEN EXIT;
               IF WildCards[name] THEN {
                    EnumerateFloppyFiles[v, ReadOne, name]; LOOP};
               IF switches # NIL AND switches.length # 0 AND switches[0] = 's THEN
                    {
                    switches ← Exec.FreeTokenString[switches];
                    [altName, switches] ← Exec.GetToken[h]};
               fFile ← AccessFloppy.LookUp[
                    NSString.StringFromMesaString[name], attributes !
                    AccessFloppy.Error =>
                         IF type = fileNotFound THEN {failed ← TRUE; CONTINUE}
                         ELSE ERROR Abort["Floppy Error"L]];
               IF failed THEN {
                    Write[name];
                    Format.Line[Write, " not found"L];
                    SetOutcome[error];
                    LOOP}
               ELSE
                    [] ← ReadOne[
                         attributes, fFile, NameFromAttributes[attributes]];
               ENDLOOP;
          Heap.systemZone.FREE[@attributes];
          AccessFloppy.Close[];
          END;  -- ReadFile


     WriteAccessFloppyError: PROCEDURE [type: AccessFloppy.ErrorType] =
          BEGIN
          Put.Text[toolData.fileSW, "AccessFloppy.Error["L];

          Put.Text[
               toolData.fileSW,
               SELECT type FROM
                    attributesNotAllowed => "attributesNotAllowed"L,
                    fileNotFound => "fileNotFound"L,
                    invalidParameter => "invalidParameter"L,
                    nameInUse => "nameInUse"L,
                    volumeNotOpen => "volumeNotOpen"L,
                    ENDCASE => "?"L];

          Put.Text[toolData.fileSW, "]"L];
          END;  --WriteAccessFloppyError




     WriteFloppyError: PROCEDURE [
          Write: Format.StringProc, error: Floppy.ErrorType] =
          BEGIN
          Write["Floppy.Error["L];
          Write[
               SELECT error FROM
                    badDisk => "badDisk"L,
                    badSectors => "badSectors"L,
                    endOfFile => "endOfFile"L,
                    fileListFull => "fileListFull"L,
                    fileNotFound => "fileNotFound"L,
                    hardwareError => "hardwareError"L,
                    incompatibleSizes => "incompatibleSizes"L,
                    invalidFormat => "invalidFormat"L,
                    invalidPageNumber => "invalidPageNumber"L,
                    invalidVolumeHandle => "invalidVolumeHandle"L,
                    insufficientSpace => "insufficientSpace"L,
                    needsScavenging => "needsScavenging"L,
                    noSuchDrive => "noSuchDrive"L,
                    notReady => "notReady"L,
                    onlyOneSide => "onlyOneSide"L,
                    onlySingleDensity => "onlySingleDensity"L,
                    initialMicrocodeSpaceNotAvailable =>
                         "initialMicrocodeSpaceNotAvailable"L,
                    stringTooShort => "stringTooShort"L,
                    volumeNotOpen => "volumeNotOpen"L,
                    writeInhibited => "writeInhibited"L,
                    zeroSizeFile => "zeroSizeFile"L,
                    ENDCASE => "?"L];
          Format.Char[Write, ']];
          END;  --WriteFloppyError



     WritePartial: PROCEDURE [attributes: AccessFloppy.Attributes]
          RETURNS [chars: CARDINAL ← 0] =
          BEGIN
          CountedNumber: PROCEDURE [n: LONG CARDINAL] RETURNS [CARDINAL] = {
               s: STRING = [12];
               String.AppendLongDecimal[s, n];
               Put.Text[toolData.fileSW, s];
               RETURN[s.length]};
          IF attributes.offset # 0 OR attributes.size # attributes.totalSize THEN {
               chars ← 4;
               Put.Char[toolData.fileSW, '[];
               chars ← chars + CountedNumber[attributes.offset];
               -- Write[".."L];
               chars ←
                    chars + CountedNumber[attributes.offset + attributes.size - 1];
               Put.Char[toolData.fileSW, ']]};
          END;  --WritePartial


     WriteFiles: PROCEDURE [gSwitches: LONG STRING, h: Exec.Handle] =
          BEGIN
          attributes: AccessFloppy.Attributes ← NIL;
          name, altName, switches: LONG STRING ← NIL;
          fileType: File.Type ← FileTypes.tUntypedFile;
          mFile: MFile.Handle;
          Write: Format.StringProc = Exec.OutputProc[h];
          IF gSwitches # NIL THEN
               fileType ← [
                    String.StringToNumber[
                    gSwitches ! String.InvalidNumber => CONTINUE]];
          [] ← AccessFloppy.Open[
               ! AccessFloppy.Error => ERROR Abort["Can't open floppy"L]];
          DO
               ENABLE
                    UNWIND => {
                         IF altName # name THEN
                              altName ← Exec.FreeTokenString[altName];
                         name ← Exec.FreeTokenString[name];
                         switches ← Exec.FreeTokenString[switches];
                         IF mFile # NIL THEN MFile.Release[mFile];
                         Heap.systemZone.FREE[@attributes];
                         AccessFloppy.Close[ ! Floppy.Error => CONTINUE]};
               fFile: Floppy.FileHandle;
               failed: BOOLEAN ← FALSE;
               mFileError: MFile.ErrorCode;
               offset, length: CARDINAL;
               IF attributes = NIL THEN
                    attributes ← Heap.systemZone.NEW[
                         AccessFloppy .AttributesRecord[AccessFloppy.maxDataSize]];
               mFile ← NIL;
               IF altName # name THEN altName ← Exec.FreeTokenString[altName];
               name ← Exec.FreeTokenString[name];
               switches ← Exec.FreeTokenString[switches];
               [name, switches] ← Exec.GetToken[h];
               IF name = NIL AND switches = NIL THEN EXIT;
               [offset, length] ← ParsePartial[
                    name !
                    Token.SyntaxError => {
                         Write[name];
                         Format.Line[Write, " - bad name syntax"L];
                         SetOutcome[error];
                         LOOP}];
               IF switches # NIL AND switches.length # 0 THEN
                    SELECT switches[0] FROM
                         's => {
                              switches ← Exec.FreeTokenString[switches];
                              [altName, switches] ← Exec.GetToken[h]};
                         't => {
                              fileType ← [
                                   String.StringToNumber[
                                   name ! String.InvalidNumber => CONTINUE]];
                              switches ← Exec.FreeTokenString[switches];
                              LOOP};
                         ENDCASE
               ELSE altName ← name;
               mFile ← MFile.ReadOnly[
                    name, [] ! MFile.Error => {mFileError ← code; CONTINUE}];
               IF mFile = NIL THEN
                    SELECT mFileError FROM
                         noSuchFile => {
                              Format.Line[Write, " not found"L];
                              SetOutcome[error];
                              LOOP};
                         conflictingAccess => {
                              Format.Line[Write, " conflicting access"L];
                              SetOutcome[error];
                              LOOP};
                         ENDCASE => ERROR Abort[" unexpected MFile.Error"L];
               MakeAttributes[mFile, altName, attributes, fileType];
               attributes.offset ← offset;
               attributes.size ← MIN[length, attributes.totalSize - offset];
               Write[name];
               [] ← WritePartial[attributes];
               Write["... "L];
               Write["copying to "L];
               Write[altName];
               Write["... "L];
               fFile ← AccessFloppy.CreateFile[
                    attributes !
                    AccessFloppy.Error =>
                         IF type = nameInUse THEN {failed ← TRUE; CONTINUE};
                    Floppy.Error =>
                         SELECT error FROM
                              fileListFull => ERROR Abort["too many files"L];
                              insufficientSpace => ERROR Abort["floppy is full"L];
                              ENDCASE];
               IF failed THEN {
                    Format.Line[Write, "floppy file already exists - skipped"L];
                    SetOutcome[error]}
               ELSE {
                    IF attributes.size # 0 THEN
                         Floppy.CopyFromPilotFile[
                              floppyFile: fFile,
                              pilotFile: SpecialMFile.GetCapaWithAccess[mFile],
                              firstFloppyPage: AccessFloppy.leaderLength,
                              firstPilotPage: SpecialMFile.LeaderPages[] + offset,
                              count: attributes.size];
                    Format.Line[Write, "copied"L]};
               MFile.Release[mFile];
               ENDLOOP;
          Heap.systemZone.FREE[@attributes];
          AccessFloppy.Close[];
          END;  -- WriteFiles



     WildCards: PROCEDURE [pattern: LONG STRING] RETURNS [BOOLEAN] =
          BEGIN
          IF pattern # NIL THEN
               FOR i: CARDINAL IN [0..pattern.length) DO
                    SELECT pattern[i] FROM '*, '# => RETURN[TRUE]; ENDCASE;
                    ENDLOOP;
	  IF debug THEN {
	  	Put.Line[toolData.fileSW, " NOT WildCards"L];
		};
          RETURN[FALSE];
          END;


     <<	  
     DoIt: Exec.ExecProc =
          BEGIN
          MyCommands: TYPE = MACHINE DEPENDENT{
               read(0), write, list, delete, information, format,
               noMatch(StringLookUp.noMatch)};
          DefinedOptions: TYPE = MyCommands [read..format];
          commandTable: ARRAY DefinedOptions OF LONG STRING ← [
               read: "Read"L, write: "Write"L, list: "List"L, delete: "Delete"L,
               information: "Information"L, format: "Format"L];
          index: MyCommands;
          Write: Format.StringProc = Exec.OutputProc[h];
          command, switches: LONG STRING ← NIL;
          WHILE outcome = normal DO
               ENABLE
                    UNWIND => {
                         command ← Exec.FreeTokenString[command];
                         switches ← Exec.FreeTokenString[switches]};
               [command, switches] ← Exec.GetToken[h];
               IF command = NIL AND switches = NIL THEN EXIT;
               BEGIN
               ENABLE {
                    Abort => {Format.Line[Write, s]; GOTO abort};
                    AccessFloppy.Error => {
                         Write["unexpected "L];
                         WriteAccessFloppyError[Write, type];
                         Format.Char[Write, Ascii.CR];
                         GOTO abort};
                    AccessFloppy.InconsistentFile => {
                         Format.Line[Write, "AccessFloppy.InconsistentFile"L];
                         GOTO abort};
                    AccessFloppy.InvalidVersion => {
                         Format.Line[Write, "AccessFloppy.InvalidVersion"L];
                         GOTO abort};
                    AccessFloppy.NoRoomForClientData => {
                         Format.Line[Write, "AccessFloppy.NoRoomForClientData"L];
                         GOTO abort};
                    Floppy.Error => {
                         IF error = writeInhibited THEN
                              Format.Line[Write, "Floppy is write protected"L]
                         ELSE {
                              Write["unexpected "L];
                              WriteFloppyError[Write, error];
                              Format.Char[Write, Ascii.CR]};
                         GOTO abort};
                    SetOutcome => {
                         IF outcome < value THEN outcome ← value; RESUME }};
               index ← LOOPHOLE[StringLookUp.InTable[
                    key: command,
                    table: DESCRIPTOR[BASE[commandTable], LENGTH[commandTable]]]];
               SELECT index FROM
                    read => ReadFiles[switches, h];
                    write => WriteFiles[switches, h];
                    list => ListFiles[switches, h];
                    delete => DeleteFiles[switches, h];
                    information => InfoDisk[switches, h];
                    format => FormatDisk[switches, h];
                    ENDCASE => {Write["Unknown command"L]; GOTO abort};
               EXITS abort => outcome ← abort;
               END;
               command ← Exec.FreeTokenString[command];
               switches ← Exec.FreeTokenString[switches];
               ENDLOOP;
          END;
>>

     Help: Exec.ExecProc =
          BEGIN
          Write: Format.StringProc = Exec.OutputProc[h];
          Write[
               "
Floppy.~ <command> <args>
<args>:
    <fileList>: list of file names; /s on a name uses next name as destination
    <wildList>: names may contain * and #
<command>:
  Read <fileList> | <wildList> - copies files from floppy
  Write <fileList> - copies files to floppy
    number/t sets File.Type for following files (default tUntypedFile)
    add [firstPage..lastPage] to name for writing partial files
  List/ltdwv <wildList> - shows files on floppy; switches show:
    l => length, t => type, d => create date, w => write date, v => everything
  Delete <wildList> - deletes files from floppy
  Info - gives information about the floppy volume
  Format - formats floppy destroying previous contents, args are:
    name/n gives the volume a name (quote names with special characters)
    number/f sets max number of files to number (default 64)"L]
          END;


     END.