-- FilesMain.mesa -- edited by Barth, October 14, 1980 2:26 PM -- edited by Brotz, October 22, 1982 10:23 AM -- edited by Schroeder, Wednesday Oct. 29, 1980 12:08 pm PST DIRECTORY AltoFileDefs USING [FP], Core USING [Close, Delete, Open], csD: FROM "CoreStreamDefs" USING [Close, Destroy, EndOfStream, GetLength, Open, OpenFromName, Read, StreamCopy, StreamHandle], DirectoryDefs USING [EnumerateDirectory], ImageDefs USING [BcdVersion], IODefs USING [ControlZ, CR, ESC, LineOverflow, ReadChar, ReadEditedString, Rubout, SP, TAB, WriteChar, WriteLine, WriteString], LaurelExecDefs USING [MakeMenuCommandCallable], MatchDefs USING [Capitalize, IsMatch], SegmentDefs USING [FileError, FileHandle, FileNameError, GetEndOfFile, GetFileTimes, NewFile, Read, ReleaseFile, SetFileTimes], Storage USING [FreeString, String], StreamDefs USING [ControlDELtyped, ResetControlDEL], String USING [AppendChar, AppendLongNumber, AppendNumber, AppendString, EquivalentStrings], TimeDefs USING [AppendDayTime, PackedTime, UnpackDT], VMDefs USING [CantOpen, FileHandle], VMSpecial USING [QuickAndDirtyAltoRename]; FilesMain: PROGRAM IMPORTS Core, csD, DirectoryDefs, ImageDefs, IODefs, LaurelExecDefs, MatchDefs, SegmentDefs, Storage, StreamDefs, String, TimeDefs, VMDefs, VMSpecial = BEGIN OPEN String, IODefs; -- Constants numBufferPages: CARDINAL = 2; promptChar: CHARACTER = '>; -- Types commandIndex: TYPE = {copy, delete, filestat, list, quit, rename, type}; -- Variables capFileFilter: STRING _ [50]; argList: STRING _ Storage.String[50]; fileStatString: STRING; cc: CARDINAL; sawFile: BOOLEAN; ci: commandIndex; -- Routines WriteError: PROCEDURE [error: STRING]= BEGIN WriteLine[""L]; WriteString["? "L]; WriteLine[error]; END; -- of WriteError IsSpace: PROCEDURE[c: CHARACTER] RETURNS [BOOLEAN] = {RETURN[c = TAB OR c = SP]}; GetCommand: PROCEDURE RETURNS [ci: commandIndex] = -- Entry Invariants: argList should contain the default command line, if any, if -- argList.length is zero then the default will be set to "list *.mail", the display -- should be at the beginning of the line the command is to be typed in on. -- Exit Invariants: ci will contain a valid commandIndex, argList will contain the -- remainder of the command line after parsing off the command name by -- looking for [SP]*[~SP]*[SP]*, the display will be at the beginning of the -- line following the line the command was typed on. BEGIN maxCommandLength: CARDINAL = 8; -- update this if maxlength(ac) changes ac: ARRAY commandIndex OF STRING = [copy: "COPY"L, delete: "DELETE"L, filestat: "FILESTAT"L, list: "LIST"L, quit: "QUIT"L, rename: "RENAME"L, type: "TYPE"L]; TestEndChar: PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] = {RETURN[c = CR OR c = '?]}; RubOutCommand: PROCEDURE = BEGIN argList.length _ 0; WriteLine[" XXX"L]; WriteChar[promptChar]; END; -- of RubOutCommand -- ParseCommand: PROCEDURE RETURNS [BOOLEAN] = BEGIN i, i2: CARDINAL _ 0; tci: commandIndex; matchCount: CARDINAL _ 0; cm: STRING _ [maxCommandLength]; SkipSpaces: PROCEDURE = {WHILE IsSpace[argList[i]] AND i < argList.length DO i _ i + 1 ENDLOOP}; IF argList.length = 0 THEN -- ignore blank lines -- {WriteLine[""L]; RETURN[FALSE]}; SkipSpaces[]; -- skip leading spaces UNTIL argList.text[i] = SP OR i >= argList.length DO -- pick out the command IF cm.length < cm.maxlength THEN {cm[cm.length] _ argList[i]; cm.length _ cm.length+1} ELSE {WriteError["Unknown Command"L]; RETURN[FALSE]}; i _ i+1; ENDLOOP; SkipSpaces[]; -- skip trailing spaces FOR i2 IN [i .. argList.length) DO argList[i2 - i] _ argList[i2] ENDLOOP; argList.length _ argList.length - i; -- reduce command line size by command length MatchDefs.Capitalize[cm, cm]; -- convert to canonical form for matching FOR tci IN commandIndex DO BEGIN IF cm.length > ac[tci].length THEN LOOP; FOR i2 IN [0 .. cm.length) DO IF cm[i2] # ac[tci].text[i2] THEN GOTO notfound; ENDLOOP; ci _ tci; -- this command matches at least partially, remember it IF cm.length = ac[tci].length THEN RETURN[TRUE]; -- exact match matchCount _ matchCount + 1; EXITS notfound => NULL; END; ENDLOOP; SELECT matchCount FROM = 1 => RETURN[TRUE]; -- only one partial match, use it > 1 => WriteError["Ambiguous Command"L]; ENDCASE => WriteError["Unknown Command"L]; RETURN[FALSE]; END; -- of ParseCommand -- IF argList.length = 0 THEN AppendString[to: argList, from: "list *.mail"L]; DO WriteChar[promptChar]; SELECT ReadEditedString[argList, TestEndChar, TRUE ! Rubout => {RubOutCommand; RESUME}; LineOverflow => {IncreaseStringSize[]; RESUME[argList]}] FROM CR => IF ParseCommand[] THEN {WriteLine[""L]; RETURN}; '? => BEGIN WriteLine["?"L]; WriteString["Commands are:"L]; FOR ci IN commandIndex DO WriteString[" "L]; WriteString[ac[ci]]; ENDLOOP; WriteLine[""L]; WriteLine["Control DEL cancels commands in progress."L]; END; ENDCASE; ENDLOOP; END; -- of GetCommand -- GetAtom: PROCEDURE [a: STRING] RETURNS [BOOLEAN]= BEGIN IF cc >= argList.length THEN RETURN[FALSE]; a.length _ 0; UNTIL IsSpace[argList[cc]] OR cc >= argList.length DO a[a.length] _ argList[cc]; a.length _ a.length + 1; cc _ cc + 1; ENDLOOP; WHILE IsSpace[argList[cc]] AND cc < argList.length DO cc _ cc + 1; ENDLOOP; RETURN[TRUE]; END; -- of GetAtom -- IncreaseStringSize: PROCEDURE = BEGIN temp: STRING _ Storage.String[argList.length + 50]; AppendString[temp, argList]; Storage.FreeString[argList]; argList _ temp; END; -- of IncreaseStringSize -- ScanDirectory: PROCEDURE [processFile: PROC [POINTER TO AltoFileDefs.FP, STRING] RETURNS [BOOLEAN]] = BEGIN fileFilter: STRING _ [50]; cc: CARDINAL _ 0; sawFile _ FALSE; WHILE GetAtom[fileFilter] DO IF StreamDefs.ControlDELtyped[] THEN EXIT; IF ci = type AND fileFilter[0] = '[ THEN [] _ TypeFile[NIL, fileFilter] ELSE BEGIN MatchDefs.Capitalize[s: fileFilter, capS: capFileFilter]; DirectoryDefs.EnumerateDirectory[processFile]; END; ENDLOOP; IF ~sawFile AND ~StreamDefs.ControlDELtyped[] THEN WriteLine["No such files"L] ELSE IF ci = list THEN WriteLine[""L]; END; -- of ScanDirectory -- FileMessage: PROCEDURE[msg: STRING, fileName: STRING] = BEGIN WriteString[msg]; WriteChar[SP]; WriteLine[fileName]; END; -- of FileMessage -- DeleteAFile: PROCEDURE [p: POINTER TO AltoFileDefs.FP, fileName: STRING] RETURNS [BOOLEAN] = BEGIN fileHandle: VMDefs.FileHandle; capFileName: STRING _ [50]; IF StreamDefs.ControlDELtyped[] THEN RETURN[TRUE]; MatchDefs.Capitalize[s: fileName, capS: capFileName]; IF ~MatchDefs.IsMatch[name: capFileName, pattern: capFileFilter] THEN RETURN[FALSE]; sawFile _ TRUE; BEGIN -- for EXITS -- WriteString["Delete File "L]; WriteString[fileName]; WriteString[" [Confirm]"L]; SELECT ReadChar[] FROM SP, CR, 'y, 'Y, ESC => BEGIN fileHandle _ Core.Open[fileName, update ! VMDefs.CantOpen => {FileMessage["Can't delete file"L, fileName]; GO TO Return}]; Core.Delete[fileHandle]; WriteLine[" Deleted"L]; END; ENDCASE => WriteLine[" Not deleted"L]; EXITS Return => NULL; END; RETURN[FALSE]; END; -- of DeleteAFile -- TypeFile: PROCEDURE [p: POINTER TO AltoFileDefs.FP, fileName: STRING] RETURNS [BOOLEAN] = BEGIN capFileName: STRING _ [50]; bsh: csD.StreamHandle; char: CHARACTER; inBravoStuff: BOOLEAN _ FALSE; lengthString: STRING _ [20]; MatchDefs.Capitalize[s: fileName, capS: capFileName]; IF ~MatchDefs.IsMatch[name: capFileName, pattern: capFileFilter] THEN RETURN[FALSE]; sawFile _ TRUE; bsh _ csD.OpenFromName[fileName, byte, read ! VMDefs.CantOpen => {FileMessage["Can't open file"L, fileName]; GO TO Return}]; WriteLine[""L]; WriteString["Contents of file "L]; WriteString[fileName]; WriteString[": (length = "L]; AppendLongNumber[lengthString, csD.GetLength[bsh], 10]; WriteString[lengthString]; WriteLine[" (decimal) bytes)"L]; WriteLine[""L]; DO IF StreamDefs.ControlDELtyped[] THEN EXIT; char _ csD.Read[bsh ! csD.EndOfStream => EXIT]; IF inBravoStuff THEN inBravoStuff _ char # CR ELSE inBravoStuff _ char = ControlZ; IF ~inBravoStuff THEN WriteChar[char]; ENDLOOP; WriteLine[""L]; csD.Destroy[bsh]; GO TO Return; EXITS Return => RETURN[StreamDefs.ControlDELtyped[]]; END; -- of TypeFile -- ListFile: PROCEDURE[p: POINTER TO AltoFileDefs.FP, fileName: STRING] RETURNS [BOOLEAN]= BEGIN capFileName: STRING _ [50]; IF StreamDefs.ControlDELtyped[] THEN RETURN[TRUE]; MatchDefs.Capitalize[s: fileName, capS: capFileName]; IF MatchDefs.IsMatch[name: capFileName, pattern: capFileFilter] THEN BEGIN IF fileName[fileName.length - 1] = '. THEN fileName.length _ fileName.length - 1; -- remove trailing period WriteString[fileName]; WriteString[" "L]; sawFile _ TRUE; END; RETURN[FALSE]; END; -- of ListFile -- oFileName: STRING _ [50]; obsh: csD.StreamHandle; InnerCopyLoop: PROCEDURE[p: POINTER TO AltoFileDefs.FP, iFileName: STRING] RETURNS [BOOLEAN] = BEGIN ibsh: csD.StreamHandle _ NIL; capFileName: STRING _ [50]; flagQuit: BOOLEAN _ FALSE; MatchDefs.Capitalize[s: iFileName, capS: capFileName]; IF ~MatchDefs.IsMatch[name: capFileName, pattern: capFileFilter] THEN RETURN[FALSE]; sawFile _ TRUE; BEGIN -- for EXITS -- ibsh _ csD.OpenFromName[iFileName, byte, read ! VMDefs.CantOpen => {FileMessage["Can't open file"L, iFileName]; flagQuit _ TRUE; GO TO Return}]; csD.StreamCopy[from: ibsh, to: obsh, fromItems: csD.GetLength[ibsh]]; csD.Destroy[ibsh]; flagQuit _ flagQuit OR StreamDefs.ControlDELtyped[]; EXITS Return => NULL; END; RETURN[flagQuit]; END; -- of InnerCopyLoop -- CopyFile: PROCEDURE = BEGIN ofh: VMDefs.FileHandle _ NIL; oFileName: STRING _ [50]; exists: BOOLEAN _ TRUE; IF ~GetAtom[oFileName] OR ~(argList[cc] = '_ AND IsSpace[argList[cc + 1]]) THEN {WriteLine["Form is: destination _ source source ..."L]; RETURN}; cc _ cc + 2; -- throw away seperator WHILE IsSpace[argList[cc]] DO cc _ cc+ 1; ENDLOOP; ofh _ Core.Open[oFileName, read ! VMDefs.CantOpen => {IF reason = notFound THEN exists _ FALSE; CONTINUE}]; IF ofh # NIL THEN Core.Close[ofh]; IF exists THEN {FileMessage["File already exists:"L, oFileName]; RETURN}; ofh _ Core.Open[oFileName, update]; obsh _ csD.Open[ofh, byte, overwrite]; ScanDirectory[InnerCopyLoop]; csD.Close[obsh]; Core.Close[ofh]; END; -- of CopyFile -- StatFile: PROCEDURE [p: POINTER TO AltoFileDefs.FP, fileName: STRING] RETURNS [BOOLEAN]= BEGIN capFileName: STRING _ [50]; afh: SegmentDefs.FileHandle; page, byte: CARDINAL; bytes: LONG CARDINAL; read, write, create: TimeDefs.PackedTime; WriteTime: PROCEDURE[s: STRING, t: TimeDefs.PackedTime]= BEGIN AppendString[fileStatString, s]; AppendChar[fileStatString, ':]; TimeDefs.AppendDayTime[fileStatString, TimeDefs.UnpackDT[t]]; AppendString[fileStatString, " "L]; END; -- of WriteTime -- IF StreamDefs.ControlDELtyped[] THEN RETURN[TRUE]; MatchDefs.Capitalize[s: fileName, capS: capFileName]; IF ~MatchDefs.IsMatch[name: capFileName, pattern: capFileFilter] THEN RETURN[FALSE]; sawFile _ TRUE; afh _ SegmentDefs.NewFile[fileName, SegmentDefs.Read ! SegmentDefs.FileNameError => {afh _ NIL; CONTINUE}]; IF afh = NIL THEN {FileMessage["Can't open file"L, fileName]; RETURN[FALSE]}; [read, write, create] _ SegmentDefs.GetFileTimes[afh]; [page, byte] _ SegmentDefs.GetEndOfFile[afh]; fileStatString.length _ 0; AppendString[fileStatString, fileName]; AppendString[fileStatString, " "L]; IF byte = 512 THEN {byte _ 0; page _ page + 1}; bytes _ byte + LONG[(page - 1)] * 512; AppendLongNumber[fileStatString, bytes, 10]; AppendString[fileStatString, " bytes "L]; AppendNumber[fileStatString, page + 1, 10]; AppendString[fileStatString, " pages "L]; WriteTime[" Create"L, create]; WriteTime["Write"L, write]; WriteTime["Read"L, read]; WriteLine[fileStatString]; SegmentDefs.SetFileTimes[afh, read, write, create]; SegmentDefs.ReleaseFile[afh ! SegmentDefs.FileError => CONTINUE]; RETURN[FALSE]; END; -- of StatFile -- RenameFile: PROCEDURE= BEGIN oldFileName: STRING _ [50]; newFileName: STRING _ [50]; fh: VMDefs.FileHandle; exists: BOOLEAN; ComplainSyntax: PROCEDURE = {WriteLine["Form is: oldfilename newfilename OR newfilename _ oldfilename"L]}; IF ~GetAtom[oldFileName] OR ~GetAtom[newFileName] THEN {ComplainSyntax; RETURN}; IF newFileName.length = 1 AND newFileName[0] = '_ THEN BEGIN newFileName.length _ 0; AppendString[newFileName, oldFileName]; IF ~GetAtom[oldFileName] THEN {ComplainSyntax; RETURN} END; IF ~EquivalentStrings[oldFileName, newFileName] THEN BEGIN fh _ Core.Open[oldFileName, read ! VMDefs.CantOpen => BEGIN WriteString["Can't rename: "L]; WriteString[oldFileName]; WriteLine[SELECT reason FROM notFound => " does not exist."L, alreadyExists => " in use."L, ENDCASE => "can't be opened."L]; GO TO Return END]; Core.Close[fh]; exists _ TRUE; fh _ NIL; fh _ Core.Open[newFileName, read ! VMDefs.CantOpen => {IF reason = notFound THEN exists _ FALSE; CONTINUE}]; IF fh # NIL THEN Core.Close[fh]; IF exists THEN {WriteString["Can't rename: "L]; WriteString[newFileName];WriteLine[" already exists."L]} ELSE IF ~VMSpecial.QuickAndDirtyAltoRename[oldFileName, newFileName] THEN WriteLine["Can't rename."L]; EXITS Return => NULL; END; END; -- of RenameFile -- WriteHerald: PROCEDURE = BEGIN time: STRING _ [25]; TimeDefs.AppendDayTime [time, TimeDefs.UnpackDT[ImageDefs.BcdVersion[].time]]; WriteLine[""L]; WriteString["Laurel File Utility of "L]; WriteLine[time]; WriteLine["Type ? for help."L]; END; -- of WriteHerald -- -- Main Program LaurelExecDefs.MakeMenuCommandCallable[newMail]; LaurelExecDefs.MakeMenuCommandCallable[mailFile]; LaurelExecDefs.MakeMenuCommandCallable[display]; LaurelExecDefs.MakeMenuCommandCallable[delete]; LaurelExecDefs.MakeMenuCommandCallable[undelete]; LaurelExecDefs.MakeMenuCommandCallable[moveTo]; LaurelExecDefs.MakeMenuCommandCallable[copy]; WriteHerald[]; DO ci _ GetCommand[]; cc _ 0; StreamDefs.ResetControlDEL; SELECT ci FROM rename => RenameFile; filestat => BEGIN fileStatString _ Storage.String[200]; ScanDirectory[StatFile]; Storage.FreeString[fileStatString]; END; delete => ScanDirectory[DeleteAFile]; type => ScanDirectory[TypeFile]; list => ScanDirectory[ListFile]; copy => CopyFile; quit => EXIT; ENDCASE; ENDLOOP; Storage.FreeString[argList]; END. -- of FilesMain -- (635)