-- file: InstallLaurel.Mesa
-- edited by Levin, January 16, 1981 11:14 AM.
-- edited by Brotz, April 2, 1981 4:41 PM
-- edited by Schroeder, March 14, 1981 12:36 PM.

DIRECTORY
AltoDefs,
AltoFileDefs,
Ascii,
BFSDefs,
ControlDefs,
crD: FROM "CoreDefs",
csD: FROM "CoreStreamDefs",
displayCommon,
DMSTimeDefs,
drD: FROM "LaurelDriverDefs",
dsD: FROM "DisplayDefs",
DiskDefs,
exD: FROM "ExceptionDefs",
FrameDefs,
FrameOps,
ImageDefs,
Inline,
intCommon,
inD: FROM "InteractorDefs",
LaurelHardcopyDefs,
lsD: FROM "LaurelStateDefs",
MailParse,
MiscDefs,
ovD: FROM "OverviewDefs",
SegmentDefs,
Storage,
StringDefs,
SwapperOps,
TimeDefs;

InstallLaurel: PROGRAM
IMPORTS BFSDefs, crD, csD, disC: displayCommon, DMSTimeDefs, exD, Inline,
intC: intCommon, inD, FrameDefs, FrameOps, LaurelHardcopyDefs, lsD,
MailParse, MiscDefs, SegmentDefs, Storage, StringDefs, SwapperOps, TimeDefs
EXPORTS drD, lsD
SHARES lsD =

BEGIN
OPEN drD;

stateFH: SegmentDefs.FileHandle;
stateHeaderSeg: SegmentDefs.FileSegmentHandle;
stateHeader: POINTER TO lsD.StateHeader;
stateSegPages: AltoDefs.PageCount;

heapFirstFree: POINTER;
heapLimit: POINTER;

profileInUserCm: BOOLEAN;

videoBackground: PUBLIC dsD.backgtype;

logFile: crD.UFileHandle;
log: csD.StreamHandle;

InitializeState: PROCEDURE[imageFile: SegmentDefs.FileHandle,
heapDS: SegmentDefs.DataSegmentHandle] =
BEGIN
OPEN SegmentDefs;
stateFileName: STRING = "Laurel.state"L;
GetGlobalFrameSize: PROCEDURE[link: UNSPECIFIED] RETURNS[length: CARDINAL] =
BEGIN
OPEN ControlDefs;
frame: GlobalFrameHandle ← FrameDefs.GlobalFrame[link];
codeSeg: FileSegmentHandle ← FrameOps.CodeHandle[frame];
seg: FileSegmentHandle ←
NewFileSegment[codeSeg.file, codeSeg.base, codeSeg.pages, Read];
fcb: FrameCodeBase ← frame.code;
prefix: PrefixHandle;
p: POINTER;
SwapIn[seg];
IF fcb.out THEN fcb.out ← FALSE
ELSE
fcb.shortbase ← fcb.shortbase - LOOPHOLE[FileSegmentAddress[codeSeg], CARDINAL];
prefix ← p ← FileSegmentAddress[seg] + fcb.offset;
length ← (p+prefix.entry[MainBodyIndex].initialpc-1)↑;
Unlock[seg];
DeleteFileSegment[seg];
END;
intCLength: CARDINAL = GetGlobalFrameSize[intC] - SIZE[ControlDefs.GlobalFrame];
disCLength: CARDINAL = GetGlobalFrameSize[disC] - SIZE[ControlDefs.GlobalFrame];
imageFileLength: CARDINAL = GetEndOfFile[imageFile].page;
daTableLength: CARDINAL = imageFileLength+3;
diskrequest: DiskDefs.DiskRequest;
scratchSeg: DataSegmentHandle = NewDataSegment[DefaultBase, 1];
DAs: DESCRIPTOR FOR ARRAY [-1..0) OF AltoFileDefs.vDA;

heapLimit ← DataSegmentAddress[heapDS];
heapFirstFree ← heapLimit+AltoDefs.PageSize*heapDS.pages-1;
IF (stateFH ← crD.LookupInFileCache[stateFileName]) = NIL THEN
BEGIN
stateFH ← NewFile[stateFileName, ReadWriteAppend, NewFileOnly];
crD.InsertInFileCache[stateFileName, stateFH];
END
ELSE SetFileAccess[stateFH, ReadWriteAppend];
stateSegPages ← Storage.PagesForWords[SIZE[lsD.StateHeader] + intCLength + disCLength];
SetEndOfFile[stateFH, stateSegPages, AltoDefs.BytesPerPage];
stateHeaderSeg ← NewFileSegment[stateFH, 1, stateSegPages, ReadWrite];
SwapIn[stateHeaderSeg];
stateHeader ← FileSegmentAddress[stateHeaderSeg];

stateHeader.cachedHeapTop ← heapDS.VMpage + heapDS.pages;
stateHeader.imageFP ← imageFile.fp;
stateHeader.imageTime ← lsD.GetWrittenTime[imageFile];
stateHeader.intCOffset ← SIZE[lsD.StateHeader];
stateHeader.disCOffset ← stateHeader.intCOffset + intCLength;
stateHeader.headerFF ← stateHeader.disCOffset + disCLength;
stateHeader.imageDATableSeg ←
lsD.DefineStateSegment[Storage.PagesForWords[daTableLength]];
DAs ← DESCRIPTOR[lsD.SwapInStateSegment[stateHeader.imageDATableSeg],
daTableLength];
diskrequest ← DiskDefs.DiskRequest [
ca: DataSegmentAddress[scratchSeg],
fixedCA: TRUE,
da: @DAs[0],
fp: @imageFile.fp,
firstPage: 0,
lastPage: imageFileLength,
action: ReadD,
lastAction: ReadD,
signalCheckError: FALSE,
option: update[cleanup: BFSDefs.GetNextDA]];
MiscDefs.SetBlock[@DAs[-1], AltoFileDefs.fillinDA, daTableLength];
DAs[0] ← imageFile.fp.leaderDA;
[] ← BFSDefs.ActOnPages[LOOPHOLE[@diskrequest]];
lsD.WriteStateSegment[stateHeader.imageDATableSeg];
lsD.ReleaseStateSegment[stateHeader.imageDATableSeg];
DeleteDataSegment[scratchSeg];
END; -- of InitializeState


InstallState: PROCEDURE[imageFile: SegmentDefs.FileHandle,
heapDS: SegmentDefs.DataSegmentHandle] =
BEGIN
OPEN SegmentDefs;
profileFile: FileHandle ←
crD.LookupInFileCache[IF profileInUserCm THEN "User.cm"L ELSE "Laurel.profile"L];
fontsWidthsFile: FileHandle ← crD.LookupInFileCache["Fonts.widths"L];
lowestHeapPage: PageNumber = PageFromAddress[heapFirstFree+1];
heapPages: PageCount = heapDS.pages-(lowestHeapPage-heapDS.VMpage);
heapFS: FileSegmentHandle;
ShrinkHeap: PROCEDURE =
BEGIN
IF lowestHeapPage = heapDS.VMpage THEN RETURN;
SwapperOps.Update[heapDS.VMpage, lowestHeapPage-heapDS.VMpage,
SwapperOps.FreePage, FALSE];
heapDS.VMpage ← lowestHeapPage;
heapDS.pages ← heapPages;
END;

IF profileFile = NIL THEN stateHeader.profileFP ← AltoFileDefs.NullFP
ELSE
BEGIN
stateHeader.profileFP ← profileFile.fp;
stateHeader.profileTime ← lsD.GetWrittenTime[profileFile];
stateHeader.profileInUserCm ← profileInUserCm;
END;
IF fontsWidthsFile = NIL THEN stateHeader.fontsWidthsFP ← AltoFileDefs.NullFP
ELSE
BEGIN
stateHeader.fontsWidthsFP ← fontsWidthsFile.fp;
stateHeader.fontsWidthsTime ← lsD.GetWrittenTime[fontsWidthsFile];
END;
Inline.COPY[to: stateHeader+stateHeader.intCOffset,
from: @LOOPHOLE[intC, ControlDefs.GlobalFrameHandle].global[0],
nwords: stateHeader.disCOffset-stateHeader.intCOffset];
Inline.COPY[to: stateHeader+stateHeader.disCOffset,
from: @LOOPHOLE[disC, ControlDefs.GlobalFrameHandle].global[0],
nwords: stateHeader.headerFF-stateHeader.disCOffset];
ShrinkHeap[];
lsD.InstallSegments[stateHeader];
stateHeader.heapSegFirstPage ← GetEndOfFile[stateFH].page+1;
stateHeader.heapSegPages ← heapPages;
SetEndOfFile[stateFH, stateHeader.heapSegFirstPage+heapPages-1,
AltoDefs.BytesPerPage];
heapFS ← NewFileSegment[stateFH, stateHeader.heapSegFirstPage,
stateHeader.heapSegPages, Read+Write];
CopyDataToFileSegment[heapDS, heapFS]; -- writes heap to state file
stateHeader.heapSegDA ← GetFileSegmentDA[heapFS];
DeleteFileSegment[heapFS];
stateHeaderSeg.write ← TRUE;
Unlock[stateHeaderSeg];
DeleteFileSegment[stateHeaderSeg];
END; -- of InstallState


AllocateStateNode: PUBLIC PROCEDURE[size: CARDINAL] RETURNS [base: POINTER] =
BEGIN
base ← heapFirstFree-size+1;
IF LOOPHOLE[base, CARDINAL] < LOOPHOLE[heapLimit, CARDINAL] THEN
exD.SysBug[];
heapFirstFree ← base-1;
END;


AllocateStateString: PUBLIC PROCEDURE [chars: CARDINAL] RETURNS [p: STRING] =
BEGIN
p ← AllocateStateNode[StringDefs.WordsForString[chars]];
p↑ ← StringBody[length: 0, maxlength: chars, text: ];
END;


BuildInteractor: PROCEDURE =
-- establishes static structures for interactor
BEGIN
InstallFont[];

inD.BuildScreenStructures[];

disC.bitMapReady ← FALSE;
intC.haveMailFile ← FALSE;
intC.composedMessageEdited ← FALSE;
intC.timeMayBeBogus ← FALSE;
intC.currentCommand ← NIL;
intC.commandType ← noCommand;
intC.editorMenuState ← singleLine;
intC.target ← intC.source ← inD.TextSelection[mnp: intC.cmTextNbr, start: 0, end: 0, point: 0,
mode: char, pendingDelete: FALSE];
intC.actionPoint ← 0;
intC.currentSelection ← target;
intC.commandMode ← TRUE;
intC.runCommandMode ← FALSE;
intC.secondarySelectionEnabled ← FALSE;
END; -- of BuildInteractor --


InstallFont: PROCEDURE =
-- Assumes ’font’ points to the font. Sets the charPropTable to describe the font, assuming
-- the smudges have already been installed.
BEGIN
ch: CHARACTER;
i: CARDINAL;
whiteString: STRING = "

"L;
punctuationString: STRING = "!@#$%~&*()-`=+[{]};:’"",.<>/?\|←↑"L;

FOR ch IN dsD.LegalCharacters DO
disC.charPropertyTable[ch] ← alphaNumeric;
ENDLOOP;

disC.charPropertyTable[140C] ← punctuation;

FOR i IN [0 .. whiteString.length) DO
disC.charPropertyTable[whiteString[i]] ← white;
ENDLOOP;
disC.charPropertyTable[Ascii.TAB + (ovD.LineBreakValue - 0C)] ← white;
disC.charPropertyTable[Ascii.CR + (ovD.LineBreakValue - 0C)] ← white;
FOR i IN [0 .. punctuationString.length) DO
disC.charPropertyTable[punctuationString[i]] ← punctuation;
ENDLOOP;
END; -- of InstallFont --


ReadLaurelProfile: PROCEDURE RETURNS [installError: drD.InstallError] =
-- Opens Laurel.Profile, parses it, and extracts information for intCommon.
BEGIN
keyWord: STRING ← [50];
value: STRING ← [100];
laurelProfileHandle: csD.StreamHandle;
pH: MailParse.ParseHandle;
start: csD.Position ← 0;
eof: csD.Position;
offEnd: CARDINAL;
crSeen: BOOLEAN;

-- A LARGE number of local procedures for ReadLaurelProfile follow --

ProcessUserCm: PROCEDURE [section: STRING, proc: PROCEDURE]
RETURNS[found: BOOLEAN] =
BEGIN
OPEN StringDefs;

AdvancePastCR: PROCEDURE RETURNS [BOOLEAN] =
BEGIN
DO
IF csD.Read[laurelProfileHandle !
csD.Error => IF reason = ovD.endOfStream THEN EXIT] = Ascii.CR THEN
RETURN[TRUE];
ENDLOOP;
RETURN[FALSE]
END; -- of AdvancePastCR --

found ← TRUE;
laurelProfileHandle ← csD.OpenFromName["User.cm"L, intC.user, byte, read, 2
! csD.Error => IF reason = ovD.fileNotFound THEN {found ← FALSE; CONTINUE}];
IF ~found THEN RETURN;
found ← FALSE;
BEGIN
ENABLE UNWIND => csD.Close[laurelProfileHandle];
DO
SELECT csD.Read[laurelProfileHandle !
csD.Error => IF reason = ovD.endOfStream THEN GO TO Done] FROM
’[ =>
BEGIN
sectionHeading: STRING ← [9];
sectionHeading.length ← csD.ReadBlock[laurelProfileHandle, @sectionHeading.text,
0, section.length];
IF sectionHeading.length ~= section.length THEN GO TO Done; -- eof
IF EquivalentString[sectionHeading, section] THEN EXIT;
csD.SetPosition[laurelProfileHandle, csD.GetPosition[laurelProfileHandle] -
section.length];
END;
Ascii.CR => LOOP;
ENDCASE;
IF ~AdvancePastCR[] THEN GO TO Done; -- section not found
ENDLOOP;
-- [section] found; scan past CR
IF ~AdvancePastCR[] THEN GO TO Done; -- malformed user.cm; ignore it.
found ← TRUE;
start ← csD.GetPosition[laurelProfileHandle];
eof ← csD.GetLength[laurelProfileHandle];
DO
SELECT csD.Read[laurelProfileHandle !
csD.Error => IF reason = ovD.endOfStream THEN EXIT] FROM
’[ => {eof ← csD.GetPosition[laurelProfileHandle] - 1; EXIT};
Ascii.CR => NULL;
ENDCASE => IF ~AdvancePastCR[] THEN EXIT;
ENDLOOP;
csD.SetPosition[laurelProfileHandle, start];
FOR i: csD.Position IN [start..eof) DO
IF csD.Read[laurelProfileHandle] ~= Ascii.CR THEN
{csD.SetPosition[laurelProfileHandle, i]; EXIT};
ENDLOOP;
proc[];
END; -- ENABLE --
GO TO Done;
EXITS
Done => csD.Close[laurelProfileHandle];
END; -- of ProcessUserCm --


ProcessLaurelProfile: PROCEDURE RETURNS [found: BOOLEAN] =
BEGIN
found ← TRUE;
laurelProfileHandle ← csD.OpenFromName["Laurel.Profile"L, intC.user, byte, read, 2
! csD.Error => IF reason = ovD.fileNotFound THEN {found ← FALSE; CONTINUE}];
IF ~found THEN RETURN;
BEGIN
ENABLE UNWIND => csD.Close[laurelProfileHandle];
start ← 0;
eof ← csD.GetLength[laurelProfileHandle];
FOR i: csD.Position IN [start..eof) DO
IF csD.Read[laurelProfileHandle] ~= Ascii.CR THEN
{csD.SetPosition[laurelProfileHandle, i]; EXIT};
ENDLOOP;
DoLaurelPart[];
END; -- ENABLE --
csD.Close[laurelProfileHandle];
END; -- of ProcessLaurelProfile --


InitParse: PROCEDURE =
BEGIN
offEnd ← 0;
crSeen ← FALSE;
pH ← MailParse.InitializeParse[GetProfileChar, BackupProfileChar];
END; -- of InitParse --


GetProfileChar: PROCEDURE RETURNS [char: CHARACTER] =
BEGIN
OPEN Ascii;
inBravoTrailer: BOOLEAN ← FALSE;
DO
IF offEnd > 0 OR csD.GetPosition[laurelProfileHandle] >= eof THEN GO TO noChar;
IF (char ← csD.Read[laurelProfileHandle]) ~= CR THEN
BEGIN
IF ~inBravoTrailer THEN
IF char = ControlZ THEN inBravoTrailer ← TRUE ELSE EXIT;
END
ELSE IF ~crSeen THEN EXIT;
REPEAT
noChar => {char ← CR; offEnd ← offEnd + 1};
ENDLOOP;
crSeen ← char = CR;
END; -- of GetProfileChar --


BackupProfileChar: PROCEDURE =
BEGIN
IF offEnd > 0 THEN offEnd ← offEnd - 1
ELSE csD.SetPosition[laurelProfileHandle, csD.GetPosition[laurelProfileHandle] - 1];
END; -- of BackupProfileChar --


WriteStringToLog: PROCEDURE [s: STRING] =
BEGIN
IF logFile = NIL OR installError = noLog THEN RETURN;
IF csD.GetPosition[log] = 0 THEN InitLog[];
csD.WriteBlock[log, @s.text, 0, s.length !
csD.Error => {installError ← noLog; CONTINUE}];
END; -- of WriteStringToLog --


WriteCharToLog: PROCEDURE [char: CHARACTER] =
BEGIN
IF logFile = NIL OR installError = noLog THEN RETURN;
IF csD.GetPosition[log] = 0 THEN InitLog[];
csD.Write[log, char ! csD.Error => {installError ← noLog; CONTINUE}];
END; -- of WriteCharToLog --

WriteLogEntry: PROCEDURE [s: STRING] =
BEGIN
WriteStringToLog[s];
WriteStri
ngToLog["

"L]
;
END; -- of WriteLogEntry --

FinishLogEntry: PROCEDURE = {WriteLogEntry
[""L]};

WriteBadValueMsgInLog: PROCEDURE [fieldName: STRING] =
BEGIN
WriteStringToLog["The ’"L];
WriteStringToLog[fieldName];
WriteLogEntry["’ entry in the profile is specified incorrectly."];
END; -- WriteBadValueMsgInLog --



InitLog: PROCEDURE =
BEGIN
preamble: STRING = "*start*

00000 00024 UU
Date: "L;
postamble: STRING = "

Subject: Installation Difficulties
From: Laurel

Laurel discovered the following problem(s) during installation:

"L;
dateString: STRING ← [DMSTimeDefs.timeStringLength];
csD.WriteBlock[log, @preamble.text, 0, preamble.length ! csD.Error => CONTINUE];
DMSTimeDefs.MapPackedTimeToTimeZoneString
[LOOPHOLE[TimeDefs.CurrentDayTime[]], dateString, laurelMsg];
WriteStringToLog[dateString];
WriteStringToLog[postamble];
installError ← inLog;
END; -- of InitLog --

FinishLog: PROCEDURE =
BEGIN
IF installError ~= none THEN
BEGIN
s: STRING ← [5];
csD.SetPosition[log, 8];
StringDefs.AppendDecimal[s, Inline.LowHalf[csD.GetLength[log]]];
THROUGH [0 .. 5 - s.length) DO WriteCharToLog[’0] ENDLOOP;
WriteStringToLog[s];
csD.Close[log];
END;
END; -- of FinishLog --


DoLaurelPart: PROCEDURE =
BEGIN
OPEN StringDefs;
discard: STRING ← [0];
DO
IF ~MailParse.GetFieldName[pH, keyWord] THEN EXIT;
SELECT TRUE FROM
EquivalentString[keyWord, "Registry"L] =>
FillFromProfile[@intC.profileRegistry];
EquivalentString[keyWord, "Send"L] =>
FillFromProfile[@intC.profileSend];
EquivalentString[keyWord, "Retrieve"L] =>
FillFromProfile[@intC.profileRetrieve];
(EquivalentString[keyWord, "Printer"L] OR EquivalentString[keyWord, "Hardcopy"L])
=> FillFromProfile[@intC.hardcopyHost];
EquivalentString[keyWord, "PrintedBy"L] =>
FillFromProfile[@intC.hardcopyUserName];
EquivalentString[keyWord, "LaurelSupport"L] =>
FillFromProfile[@intC.bugReportee];
EquivalentString[keyWord, "RunPath"L] =>
FillFromProfile[@intC.runPath];
EquivalentString[keyWord, "RemoteFilePath"L] =>
FillFromProfile[@intC.remoteFilePath];
EquivalentString[keyWord, "ArpaHostNames"L] =>
FillArpaHosts[];
EquivalentString[keyWord, "Font"L] =>
BEGIN
MailParse.GetFieldBody[pH, value, TRUE];
ProcessHardcopyCode[LaurelHardcopyDefs.ParseFont[value]];
END;
EquivalentString[keyWord, "HardcopyForm"L] =>
[] ← LaurelHardcopyDefs.ParseHardcopyForm[pH];
EquivalentString[keyWord, "DefaultHardcopyForm"L] =>
FillFromProfile[@intC.defaultHardcopyFormName];
EquivalentString[keyWord, "Boundary"L] =>
BEGIN
bad: BOOLEAN ← FALSE;
item: {command, toc, dm, cm, bad} ← command;
keyNo: CARDINAL;
ProcessOne: PROC [n, r, h: STRING, nameInfo: MailParse.NameInfo]
RETURNS [BOOLEAN] =
BEGIN OPEN StringDefs;
val: CARDINAL;
IF n.length = 0 OR r.length ~= 0 OR h.length ~= 0
OR nameInfo.nesting # none THEN
GO TO bogus;
val ← StringToDecimal[n ! InvalidNumber => GO TO bogus];
SELECT item FROM
command => IF (keyNo ← val) ~IN [0..9] THEN GO TO bogus;
toc => intC.boundarySet[keyNo].toc ← val;
dm => intC.boundarySet[keyNo].dm ← val;
cm => intC.boundarySet[keyNo].cm ← val;
ENDCASE => GO TO bogus;
item ← SUCC[item];
RETURN[FALSE];
EXITS
bogus => {bad ← TRUE; RETURN[FALSE]};
END; -- of ProcessOne --
MailParse.ParseNameList[pH: pH, process: ProcessOne, suppressWhiteSpace: TRUE];
IF bad THEN WriteBadValueMsgInLog[keyWord];
END;
EquivalentString[keyWord, "NewMailTune"L] =>
FillFromProfile[@intC.newMailTune];
ENDCASE =>
BEGIN
MailParse.GetFieldBody[pH, value];
SELECT TRUE FROM
-- Report decommissioned fields.
EquivalentString[keyWord, "Authenticate"L],
EquivalentString[keyWord, "ForceMTPSend"L],
EquivalentString[keyWord, "Herald"L],
EquivalentString[keyWord, "HeraldFont"L],
EquivalentString[keyWord, "Logo"L],
EquivalentString[keyWord, "LogoFont"L],
EquivalentString[keyWord, "Poll"L],
EquivalentString[keyWord, "DisplayErrorPups"L] =>
BEGIN
WriteStringToLog["The profile field ’"L]; WriteStringToLog[keyWord];
WriteLogEntry["’ is no longer used."];
END;
EquivalentString[keyWord, "Comment"L] => NULL;
EquivalentString[keyWord, "C"L] => NULL;
EquivalentString[keyWord, "DendroicaStriata"L] =>
BEGIN
pollingInterval: CARDINAL ← StringToDecimal[value];
intC.mailcheckPollingInterval ← IF pollingInterval = 0 THEN 15
ELSE ((pollingInterval + 14) / 15) * 15;
-- polling interval will be a multiple of 15 seconds and greater than 0.
END;
EquivalentString[keyWord, "Leaf"L] => FillProfileBoolean[@intC.leafOk];
EquivalentString[keyWord, "Background"L] =>
SELECT TRUE FROM
EquivalentString[value, "white"L] => videoBackground ← white;
EquivalentString[value, "black"L] => videoBackground ← black;
ENDCASE => WriteBadValueMsgInLog[keyWord];
EquivalentString[keyWord, "SendMode"L] =>
SELECT TRUE FROM
EquivalentString[value, "mtp"L] => intC.profileSendMode ← mtp;
EquivalentString[value, "gv"L] => intC.profileSendMode ← gv;
EquivalentString[value, "auto"L] => intC.profileSendMode ← auto;
ENDCASE => WriteBadValueMsgInLog[keyWord];
EquivalentString[keyWord, "GVTestingMode"L] =>
FillProfileBoolean[@intC.gvTestingMode];
EquivalentString[keyWord, "NewFormAfterDelivery"L] =>
FillProfileBoolean[@intC.newFormAfterDelivery];
EquivalentString[keyWord, "DisplayAfterDelete"L] =>
FillProfileBoolean[@intC.displayAfterDelete];
EquivalentString[keyWord, "CopiesField"L] =>
intC.cForCopies ← EquivalentString[value, "c"L];
EquivalentString[keyWord, "Editor"L] =>
IF EquivalentString[value, "modeless"L] THEN
intC.editorType ← modeless;
EquivalentString[keyWord, "Copies"L] =>
BEGIN
n: CARDINAL ← 0;
n ← StringToDecimal[value ! InvalidNumber => CONTINUE];
IF n IN [1 .. 99] THEN intC.defaultHardCopies ← n;
END;
EquivalentString[keyWord, "FromField"L] =>
intC.fromRightX ← inD.leftMargin + StringToDecimal[value
! InvalidNumber => CONTINUE];
EquivalentString[keyWord, "SubjectField"L] =>
intC.subjectLeftX ← inD.leftMargin + StringToDecimal[value
! InvalidNumber => CONTINUE];
EquivalentString[keyWord, "SubjectFieldExtension"L] =>
intC.subjectExtensionLeftX ← inD.leftMargin + StringToDecimal[value
! InvalidNumber => CONTINUE];
EquivalentString[keyWord, "Click"L] =>
FillProfileTicks[@intC.multiClickTimeOut];
EquivalentString[keyWord, "Tap"L] =>
FillProfileTicks[@intC.tapTimeOut];
EquivalentString[keyWord, "ScrollTimeOut"L] =>
FillProfileTicks[@intC.continuousScrollTimeOut];
EquivalentString[keyWord, "ScrollDelay"L] =>
FillProfileTicks[@intC.continuousScrollDelay];
EquivalentString[keyWord, "BracketTimeOut"L] =>
FillProfileTicks[@intC.nextBracketTimeout];
EquivalentString[keyWord, "BracketDelay"L] =>
FillProfileTicks[@intC.nextBracketDelay];
EquivalentString[keyWord, "TwoSided"L] =>
FillProfileBoolean[@intC.twoSidedPrintingDefault];
EquivalentString[keyWord, "Private"L] =>
FillProfileBoolean[@intC.passwordPrintingDefault];
EquivalentString[keyWord, "WriteProtected"L] =>
FillProfileBoolean[@intC.disableWriting];
EquivalentString[keyWord, "HomoTollens"L] =>
intC.controlRedEnabled ← ~EquivalentString[value, "TRUE"L];
EquivalentString[keyWord, "BluePendingDelete"L] =>
FillProfileBoolean[@intC.bluePendingDelete];
EquivalentString[keyWord, "ErrorKeys"L] =>
IF EquivalentString[value, "LaurelX"L] THEN
intC.exceptionType ← LaurelX;
ENDCASE =>
BEGIN
WriteCharToLog[’’]; WriteStringToLog[keyWord];
WriteLogEntry["’ in the profile is unrecognizable."L];
END;
END;
ENDLOOP;
END; -- of DoLaurelPart --


DoHardcopyPart: PROCEDURE =
BEGIN
OPEN StringDefs;
discard: STRING ← [0];
DO
IF ~MailParse.GetFieldName[pH, keyWord] THEN EXIT;
SELECT TRUE FROM
EquivalentString[keyWord, "Press"L] => FillFromProfile[@intC.hardcopyHost];
EquivalentString[keyWord, "PrintedBy"L] =>
FillFromProfile[@intC.hardcopyUserName];
ENDCASE => MailParse.GetFieldBody[pH, discard];
ENDLOOP;
END; -- of DoHardcopyPart --


FillFromProfile: PROCEDURE [s: POINTER TO STRING] =
BEGIN
MailParse.GetFieldBody[pH, value];
FillProfileString[s, value];
END; -- of FillFromProfile --


FillProfileString: PROCEDURE [s: POINTER TO STRING, value: STRING] =
BEGIN
IF s↑ # NIL THEN RETURN;
s↑ ← AllocateStateString[value.length];
s↑.length ← 0;
StringDefs.AppendString[s↑, value];
END; -- of FillProfileString --


FillProfileBoolean: PROCEDURE [b: POINTER TO BOOLEAN] =
BEGIN
b↑ ← StringDefs.EquivalentString[value, "TRUE"L]
OR StringDefs.EquivalentString[value, "Yes"L];
END; -- of FillProfileBoolean --

FillProfileTicks: PROCEDURE [c: POINTER TO CARDINAL] =
BEGIN
c↑ ←(StringDefs.StringToDecimal[value ! StringDefs.InvalidNumber =>CONTINUE]+37)/38;
END; -- of FillProfileTicks --

NameIndex: TYPE = [0..5);
names: ARRAY NameIndex OF STRING;
firstFree: CARDINAL ← FIRST[NameIndex];

FillArpaHosts: PROCEDURE = INLINE
BEGIN
MailParse.ParseNameList[pH: pH, process: ProcessHost, suppressWhiteSpace: TRUE];
END; -- of FillArpaHosts --

ProcessHost: PROCEDURE[host, ignore1, ignore2: STRING, ignore3: MailParse.NameInfo]
RETURNS [BOOLEAN] =
BEGIN
IF firstFree > LAST[NameIndex] THEN RETURN[FALSE];
names[firstFree] ← AllocateStateString[host.length];
StringDefs.AppendString[names[firstFree], host];
firstFree ← firstFree + 1;
RETURN[TRUE]
END; -- of ProcessHost --

ProcessHardcopyCode: PROCEDURE [error: ovD.ErrorCode] =
BEGIN
SELECT error FROM
ovD.ok => RETURN;
ovD.profileBadFont => -- ParseFont only
BEGIN
WriteStringToLog["The profile Font entry ’"L]; WriteStringToLog[value];
WriteLogEntry["’ does not contain a valid font number."L];
END;
ovD.badFontsWidths =>
WriteLogEntry["The file ’Fonts.Widths’ is missing or unreadable."L];
ovD.fontNotInFontsWidths =>
BEGIN
IF value.length = 0 THEN WriteStringToLog["A default font"L]
ELSE {WriteCharToLog[’’]; WriteStringToLog[value]; WriteCharToLog[’’]};
WriteLogEntry[" is not in Fonts.Widths."L];
END;
ENDCASE => exD.SysBug[];
intC.hardcopyInstallError ← TRUE;
END; -- of ProcessHardcopyCode --

-- Main body of ReadLaurelProfile follows --

installError ← none;

-- initialize non-string defaultable fields --

intC.profileRegistry ← NIL;
intC.mailFileHandle ← NIL;
intC.profileSend ← NIL;
intC.profileRetrieve ← NIL;
intC.gvTestingMode ← FALSE;
intC.autoConfirm ← FALSE;
intC.profileSendMode ← auto;
intC.profileRetrieveMode ← auto;
intC.newFormAfterDelivery ← FALSE;
intC.displayAfterDelete ← FALSE;
intC.hardcopyHost ← NIL;
intC.hardcopyUserName ← NIL;
intC.newMailTune ← NIL;
intC.bugReportee ← NIL;
intC.runPath ← NIL;
intC.remoteFilePath ← NIL;
intC.leafOk ← FALSE;
intC.mailcheckPollingInterval ← 300;
intC.multiClickTimeOut ← 10;
intC.tapTimeOut ← 10;
intC.continuousScrollTimeOut ← 26;
intC.continuousScrollDelay ← 5;
intC.nextBracketTimeout ← 19;
intC.nextBracketDelay ← 19;
intC.dateLeftX ← inD.leftMargin + 50;
intC.dateRightX ← inD.leftMargin + 100;
intC.fromLeftX ← inD.leftMargin + 110;
intC.fromRightX ← inD.leftMargin + 250;
intC.subjectLeftX ← inD.leftMargin + 260;
intC.subjectExtensionLeftX ← inD.leftMargin + 275;
videoBackground ← white;
intC.cForCopies ← FALSE;
intC.disableWriting ← FALSE;
intC.controlRedEnabled ← TRUE;
intC.bluePendingDelete ← FALSE;
intC.pendingDeleteSetByControl ← FALSE;
intC.audioEnabled ← FALSE;
intC.hardcopyInstallError ← FALSE;
intC.editorType ← modal;
intC.defaultHardCopies ← 1;
intC.passwordPrintingDefault ← FALSE;
intC.twoSidedPrintingDefault ← FALSE;
intC.exceptionType ← Laurel;
intC.boundarySet ← lsD.AllocateStateNode[SIZE[inD.BoundarySetArray]];
intC.boundarySet↑ ← ALL[[toc: 12, dm: 19, cm: 16]];
intC.boundarySet[1] ← [toc: 1, dm: 0, cm: 0];
intC.boundarySet[2] ← [toc: 0, dm: 1, cm: 0];
intC.boundarySet[3] ← [toc: 0, dm: 0, cm: 1];

-- acquire profile information
IF logFile # NIL THEN log ← csD.Open[logFile, byte, overwrite, 2];
LaurelHardcopyDefs.InitHardcopyFonts[];
InitParse[];
profileInUserCm ← FALSE;
BEGIN
ENABLE
BEGIN
MailParse.ParseError =>
BEGIN
context: CARDINAL = 20;
s: STRING ← [context];
position: csD.Position ← csD.GetPosition[laurelProfileHandle];
WriteStringToLog["Syntax error in profile near: ’"L];
position ← IF position < start + context THEN start ELSE position - context;
csD.SetPosition[laurelProfileHandle, position];
s.length ← csD.ReadBlock[laurelProfileHandle, @s.text, 0, context];
WriteStringToLog[s]; WriteCharToLog[’’]; FinishLogEntry[];
CONTINUE
END;
csD.Error =>
BEGIN
SELECT reason FROM
ovD.diskError, ovD.diskCorrupted => WriteLogEntry["Disk error reading profile!"L];
ENDCASE => installError ← noLog;
CONTINUE
END;
END;
IF ~ProcessLaurelProfile[] THEN
BEGIN
profileInUserCm ← ProcessUserCm[section: "Laurel]"L, proc: DoLaurelPart];
IF profileInUserCm AND installError = none THEN
BEGIN
MailParse.FinalizeParse[pH];
InitParse[];
[] ← ProcessUserCm[section: "Hardcopy]"L, proc: DoHardcopyPart];
END;
END;

END; -- of ENABLE --

MailParse.FinalizeParse[pH];

IF Storage.StringLength[intC.profileRegistry] = 0
THEN WriteLogEntry["No ’Registry’ field in profile."L]
ELSE IF intC.profileRegistry.length > 40
THEN WriteLogEntry["Registry name is too long."L];

-- provide defaults (overwrites only if string is still NIL)

FillProfileString[@intC.profileRegistry, "NoRegistry"L];
FillProfileString[@intC.hardcopyHost, ""L];
FillProfileString[@intC.hardcopyUserName, "$"L];
FillProfileString[@intC.defaultHardcopyFormName, "Blank"L];
IF intC.hardcopyUserName[0] = ’" AND
intC.hardcopyUserName[intC.hardcopyUserName.length-1] = ’" THEN
BEGIN
i: CARDINAL;
FOR i IN [0..intC.hardcopyUserName.length-2) DO
intC.hardcopyUserName[i] ← intC.hardcopyUserName[i+1];
ENDLOOP;
intC.hardcopyUserName.length ← intC.hardcopyUserName.length - 2;
END;
intC.passwordPrinting ← intC.passwordPrintingDefault;
intC.twoSidedPrinting ← intC.twoSidedPrintingDefault;
intC.hardCopies ← intC.defaultHardCopies;

FillProfileString[@intC.bugReportee, "LaurelSupport.PA"L];

IF firstFree = 0 THEN
BEGIN
[] ← ProcessHost["PARC-MAXC"L, NIL, NIL, [none, FALSE, normal]];
[] ← ProcessHost["PARC"L, NIL, NIL, [none, FALSE, normal]];
[] ← ProcessHost["MAXC"L, NIL, NIL, [none, FALSE, normal]];
END;
intC.arpaGatewayHostNames ← DESCRIPTOR[AllocateStateNode[firstFree], firstFree];
FOR i: CARDINAL IN [0 .. firstFree) DO
intC.arpaGatewayHostNames[i] ← names[i]
ENDLOOP;

value.length ← 0;
ProcessHardcopyCode[LaurelHardcopyDefs.InstallHardcopy[]];
FinishLog[];
END; -- of ReadLaurelProfile --


GetImageFileName: PROCEDURE =
BEGIN
OPEN SegmentDefs;
file: FileHandle = FrameOps.CodeHandle[FrameDefs.GlobalFrame[intC]].file;
seg: FileSegmentHandle ← NewFileSegment[file, 0, 1, Read];
leader: POINTER TO AltoFileDefs.LD;
SwapIn[seg];
leader ← FileSegmentAddress[seg];
intC.imageFileName ← lsD.AllocateStateString
[LOOPHOLE[@leader.name, POINTER TO StringDefs.BcplSTRING].length];
StringDefs.BcplToMesaString[LOOPHOLE[@leader.name], intC.imageFileName];
intC.imageFileName.length ← intC.imageFileName.length - 1; -- get rid of dot. --
Unlock[seg];
DeleteFileSegment[seg];
END; -- of GetImageFileName --


CreateLaurelState: PUBLIC PROCEDURE[heapDS: SegmentDefs.DataSegmentHandle]
RETURNS [installError: drD.InstallError] =
BEGIN
error: ovD.ErrorCode;
savedCursor: dsD.CursorBitMap;
installCursor: dsD.CursorBitMap =
[002000B, 001000B, 000400B, 001000B, 002000B, 007600B, 037740B, 177777B,
147631B, 140031B, 142031B, 142037B, 143430B, 160070B, 077760B, 017700B];
imageFile: SegmentDefs.FileHandle = FrameOps.CodeHandle[FrameDefs.GlobalFrame[intC]].file;
savedCursor ← dsD.cursorBM↑;
dsD.cursorBM↑ ← installCursor;
InitializeState[imageFile, heapDS];
[error, logFile] ← crD.OpenFile[intC.user, "InstallErrors.mail"L, update];
IF (installError ← ReadLaurelProfile[]) ~= none THEN
BEGIN
-- ensure that state will be recomputed next time. This guarantees that the user
-- cannot use Laurel until the profile problem is fixed. If this code is removed,
-- Laurel will install the defaults in the state and use them until the user edits
-- user.cm or laurel.profile.
lpFh: SegmentDefs.FileHandle ← crD.LookupInFileCache["Laurel.profile"L];
profileInUserCm ← FALSE;
IF lpFh # NIL THEN crD.FreeCacheEntry[lpFh];
END;
IF error # ovD.ok THEN installError ← noLog
ELSE [] ← (IF installError = none THEN crD.DeleteFile ELSE crD.CloseFile)[logFile];
BuildInteractor[];
GetImageFileName[];
InstallState[imageFile, heapDS];
dsD.cursorBM↑ ← savedCursor;
END; -- of CreateLaurelState --


END. -- of InstallLaurel --