-- 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 --