File: DBIconsImpl.mesa
-- Edited by Teitelman on July 18, 1984 9:12:44 am PDT
-- Edited by: Donahue, August 15, 1984 9:10:47 am PDT
-- Last Edited by: Widom, August 8, 1984 9:14:17 pm PDT
DIRECTORY
FS USING [Error, StreamOpen],
Booting USING [CheckpointProc, RegisterProcs, RollbackProc],
Convert USING [RopeFromCard],
DB,
DBIcons,
IO,
Basics USING[LowHalf],
IOClasses USING [CreateCommentFilterStream ],
DBNames USING[ EntityToName, MakeName],
Icons USING [NewIconFromFile, IconFlavor],
List USING[ DotCons, DottedPair, AList ],
MessageWindow USING [Append, Blink],
Process USING[Ticks, SecondsToTicks, Detach, Pause],
VM USING [AddressFault],
Rope USING [Cat, Equal, ROPE],
UserProfile USING [ Token ]
;
DBIconsImpl: CEDAR MONITOR
IMPORTS Basics, Booting, DBNames, FS, Convert, IO, MessageWindow, VM, Process,
Rope, Icons, DB, List, UserProfile, IOClasses
EXPORTS DBIcons
Types
Failed: PUBLIC ERROR [why: DBIcons.Failure, reason: Rope.ROPE] = CODE;
ROPE: TYPE = Rope.ROPE;
Global variables for accessing the database
IconRelation:
DB.Relation;
-- the relation giving the properties of an icon
The tuples in the relation have the structure
IconRelation[ IconNameAttr: ROPE, IconFileAttr: ROPE, IconIndexAttr: INT ]
IconFileAttr: DB.Attribute; -- the name of the file for an icon
IconIndexAttr: DB.Attribute; -- the index in the file
IconNameAttr: DB.Attribute; -- the icon itself (the other attributes are treated as properties)
iconCache: List.AList ← NIL; -- the internal cache of mappings from icon names to flavors
IconDB: PUBLIC ROPE ← NIL;
readOnly: PUBLIC BOOLEAN ← TRUE; -- true if the segment is readonly
activity: BOOLEAN ← FALSE; -- true if a database operation has been performed recently
ticksToWait: Process.Ticks ← Process.SecondsToTicks[5*60];
schemaSet: CONDITION;
Establishing the database
EstablishIconDB:
PUBLIC
PROC [file: Rope.
ROPE ←
NIL] = {
IconDB ←
IF file =
NIL
THEN
UserProfile.Token[key: "Icons.Segment", default: "[Luther.Alpine]<CedarDoc>Icons.Segment"] ELSE file;
ResetSchema[];
iconCache ← NIL };
WatchDBActivity:
PROC[] = {
WHILE
TRUE
DO
Process.Pause[ticksToWait];
CheckConnection[]
ENDLOOP
};
CheckConnection:
ENTRY
PROC[] = {
IF
NOT activity
THEN
{ trans:
DB.Transaction =
DB.TransactionOf[$Icons];
IF trans # NIL THEN DB.CloseTransaction[trans]
}; -- don't keep the connection open too long
activity ← FALSE;
};
Accessing Icons
RegisterIcon:
PUBLIC
ENTRY
PROC [iconName: Rope.
ROPE, fileName: Rope.
ROPE, index:
CARDINAL] = {
Changes RegisteredIcons.Catalogue to reflect the indicated association. This definition will replace any previous definition. comments are for inclusion in the catalogue to help the user figure out what the icon looks like.
ENABLE UNWIND => NULL;
[] ← InternalRegisterIcon[iconName, fileName, index]
};
GetIcon:
PUBLIC
ENTRY
PROC [iconName: Rope.
ROPE, default: Icons.IconFlavor ← unInit]
RETURNS [Icons.IconFlavor] = {
ENABLE UNWIND => NULL;
RETURN[InternalGetIcon[iconName, default]];
};
InternalGetIcon:
INTERNAL
PROC[iconName: Rope.
ROPE, default: Icons.IconFlavor ← unInit]
RETURNS [Icons.IconFlavor] = {
Obtain an icon flavor for the icon associated with iconName. Create a new icon flavor if one has not previously been created.
If for some reason it could not obtain the icon, and default # unInit, returns the default. Otherwise, raises Error (defined below) with appropriate parameters.
cachedFlavor: REF ANY = CheckCache[iconName, iconCache];
flavor: Icons.IconFlavor ← unInit;
success: BOOL;
DoGetIcon:
PROC[] = {
attributeValue: DB.AttributeValue = [attribute: IconNameAttr, lo: S2V[iconName]];
iconRelship: DB.Relship = DeclareRelship[IconRelation, LIST[attributeValue], OldOnly];
IF iconRelship = NIL THEN {flavor ← default; RETURN};
{ fileName: ROPE = V2S[ GetF[iconRelship, IconFileAttr] ];
index: CARDINAL = Basics.LowHalf[LOOPHOLE[V2I[GetF[iconRelship, IconIndexAttr]]]];
flavor ← Icons.NewIconFromFile[fileName, index !
FS.Error =>
IF default # unInit THEN flavor ← default
ELSE ERROR Failed[fileNotFound, fileName];
VM.AddressFault =>
IF default # unInit THEN flavor ← default
ELSE ERROR
Failed[invalidIndex, Convert.RopeFromCard[index]]]; -- NewIconFromFile raises this if given badindex.
} };
IF cachedFlavor # NIL THEN RETURN[NARROW[cachedFlavor, REF Icons.IconFlavor]^];
IF IconDB = NIL THEN RETURN[default];
success ← CarefullyApply[DoGetIcon];
IF NOT success THEN RETURN[default];
IF flavor # unInit
THEN
{ iconCache ← Cache[ List.DotCons[iconName,
NEW[Icons.IconFlavor ← flavor]],
iconCache ];
RETURN[flavor] };
IF default # unInit THEN RETURN[default];
ERROR Failed[noSuchIcon, iconName]
};
****************** setting/getting icons for entities ****************
-- works for Entities or Domains
SetIcon: PUBLIC PROCEDURE[e: Entity, iconFile: ROPE, fileIndex: CARDINAL] =
BEGIN
name: ROPE = IF DB.IsSystemEntity[e] THEN DB.NameOf[e]
ELSE DBNames.EntityToName[e, DB.SegmentOf[e]];
IF IsRegistered[name].file = NIL THEN
RegisterIcon[ name, iconFile, fileIndex ]
END;
acorn: Icons.IconFlavor = Icons.NewIconFromFile["///Commands/DB.Icons", 0];
GetIconForEntity:
PUBLIC
ENTRY
PROC[eName, domain:
ROPE, segment:
DB.Segment]
RETURNS [Icons.IconFlavor] = {
ENABLE UNWIND => NULL;
IF eName = NIL OR domain = NIL OR segment = NIL THEN RETURN[acorn];
IF Rope.Equal[domain, "Domain"] THEN RETURN[InternalGetIcon[eName, acorn]];
BEGIN
icon: Icons.IconFlavor ← unInit;
fullName: ROPE = DBNames.MakeName[segment, domain, eName];
icon ← InternalGetIcon[fullName ! Failed => CONTINUE];
IF icon # unInit THEN RETURN[icon];
now try the name of the domain of the entity
RETURN[InternalGetIcon[domain, acorn]]
END
};
Cache:
INTERNAL
PROC[ pair: List.DottedPair, aList: List.AList ]
RETURNS[ List.AList ] = {
x, x1: List.AList ← NIL;
x ← aList;
UNTIL x =
NIL
DO
IF Rope.Equal[
NARROW[x.first.key,
ROPE],
NARROW[pair.key,
ROPE]]
THEN
BEGIN
x.first.val ← pair.val;
RETURN[aList];
END;
x1←x;
x ← x.rest;
ENDLOOP;
key not found on x
x ← CONS[pair, NIL];
IF x1 =
NIL
THEN
RETURN[x]
ELSE IF x1.rest = NIL THEN x1.rest ← x -- add at end
ELSE ERROR ; -- defensive programming
RETURN[aList];
};
CheckCache:
INTERNAL
PROC[ name: Rope.
ROPE, in: List.AList ]
RETURNS[ val:
REF
ANY ] = {
UNTIL in =
NIL
DO
IF Rope.Equal[NARROW[in.first.key, Rope.ROPE], name] THEN RETURN[in.first.val];
in ← in.rest;
ENDLOOP;
RETURN[NIL]};
IsRegistered:
PUBLIC
ENTRY
PROC[iconName: Rope.
ROPE ←
NIL, fileName: Rope.
ROPE ←
NIL, index:
CARDINAL ←
LAST[
CARDINAL]]
RETURNS[name: Rope.
ROPE, file: Rope.
ROPE, i:
CARDINAL] = {
Can be called with either iconName, or (fileName and index); does not force creation of icon flavor (only GetIcon does that).
ENABLE UNWIND => NULL;
DoRegisterCheck:
PROC[] =
{
IF iconName #
NIL
THEN
{ attributeValue: DB.AttributeValue = [attribute: IconNameAttr, lo: S2V[iconName]];
iconRelship: DB.Relship = DeclareRelship[IconRelation, LIST[attributeValue], OldOnly];
IF iconRelship = NIL THEN { file ← NIL; name ← NIL; i ← 0 }
ELSE
{ file ← V2S[ GetF[ iconRelship, IconFileAttr ] ];
i ← Basics.LowHalf[
LOOPHOLE[V2I[GetF[iconRelship, IconIndexAttr]]]];
name ← iconName } }
ELSE {
iconRels: RelshipSet = RelationSubset[IconRelation,
LIST[AttributeValue[IconFileAttr, S2V[fileName]],
AttributeValue[IconIndexAttr, U2V[index]]]];
iconRel: Relship = NextRelship[iconRels];
ReleaseRelshipSet[iconRels];
IF iconRel = NIL THEN{ file ← NIL; name ← NIL; i ← 0 }
ELSE
{ name ← V2S[GetF[iconRel, IconNameAttr]]; file ← fileName; i ← index } } };
IF
NOT CarefullyApply[DoRegisterCheck]
THEN
RETURN[
NIL,
NIL, 0]
};
InvalidateCache:
PUBLIC
ENTRY
PROC [iconName: Rope.
ROPE ←
NIL] = {
If an iconFlavor was previous created for this icon, discard the flavor, i.e. the next call to GetIcon will create a new flavor. If iconName = NIL, do this for all registered icons. For use by iconeditor.
ENABLE UNWIND => NULL;
IF iconName = NIL THEN iconCache ← NIL
ELSE iconCache ← Cache[ List.DotCons[iconName, NIL], iconCache ]
};
InternalRegisterIcon:
INTERNAL
PROC[iconName: Rope.
ROPE, fileName: Rope.
ROPE, index:
CARDINAL]
RETURNS[success:
BOOLEAN] = {
DoRegisterIcon:
PROC[] = {
attributeValue: DB.AttributeValue = [IconNameAttr, iconName];
iconRel: Relship = DeclareRelship[IconRelation, LIST[attributeValue]];
SetF[iconRel, IconFileAttr, S2V[fileName]];
SetF[iconRel, IconIndexAttr, U2V[index]] };
IF readOnly OR IconDB = NIL THEN RETURN; -- can't register things in this segment
success ← CarefullyApply[DoRegisterIcon]
};
Parsing Icon Catalogues
Parse:
INTERNAL
PROCEDURE[ stream:
IO.
STREAM, errlog:
IO.
STREAM ] = {
OPEN IO;
error: BOOLEAN ← FALSE;
{
ENABLE
IO.EndOfStream =>
GOTO Out;
DO
DBNameProc:
IO.BreakProc = {
just scan to a right paren (or be prepared to quit at a CR if you don't find one)
IF char = ') THEN RETURN[break];
RETURN[IF char = CR THEN break ELSE other] };
whiteSpace:
IO.BreakProc = {
RETURN[
SELECT char FROM
CR => break,
IO.SP, IO.ESC, IO.LF, IO.TAB, ',, ':, '; => sepr,
ENDCASE => other ]
};
isACR:
IO.BreakProc = {
RETURN[IF char = CR THEN break ELSE other]
};
tokenProc:
IO.BreakProc = {
RETURN[
SELECT char
FROM
IO.SP, IO.TAB, ', => sepr,
IO.CR => break,
ENDCASE => other
];
};
iconName, fileName: Rope.ROPE;
index: CARDINAL;
[] ← IO.SkipWhitespace[stream];
IF stream.GetChar[] # '( THEN
{ error ← TRUE;
Report[msg: Rope.Cat["Transaction Aborted at: ",
Convert.RopeFromCard[stream.GetIndex[]]], errLog: errlog];
GOTO Out };
iconName ← IO.GetTokenRope[stream, DBNameProc].token;
IF stream.GetChar[] # ') THEN
{ error ←
TRUE;
Report[msg: Rope.Cat["Transaction Aborted at: ",
Convert.RopeFromCard[stream.GetIndex[]]], errLog: errlog];
GOTO Out };
[] ← IO.SkipWhitespace[stream]; -- skip any intervening whitespace
fileName ← IO.GetTokenRope[stream, whiteSpace].token;
index ← IO.GetCard[stream];
IF
NOT InternalRegisterIcon[iconName, fileName, index]
THEN
{ error ← TRUE;
Report[msg: Rope.Cat["Transaction Aborted at: ",
Convert.RopeFromCard[stream.GetIndex[]]], errLog: errlog];
GOTO Out }
ENDLOOP;
};
IF error
THEN {
MessageWindow.Append["problems encountered in reading icon file."];
MessageWindow.Blink[];
IF errlog # NIL THEN errlog.Close[];
};
};
WriteCatalogue:
PUBLIC
ENTRY
PROC [file: Rope.
ROPE] = {
ENABLE UNWIND => NULL;
stream: IO.STREAM;
InternalWrite:
PROC[] = {
OPEN IO;
iconRels: RelshipSet = RelationSubset[IconRelation];
FOR r: Relship ← NextRelship[iconRels], NextRelship[iconRels]
UNTIL r =
NIL
DO
fileName: ROPE = V2S[ GetF[IconFileAttr, IconFileAttr] ];
iconName: ROPE = V2S[ GetF[IconFileAttr, IconNameAttr] ];
index: CARDINAL = Basics.LowHalf[LOOPHOLE[V2I[GetF[r, IconIndexAttr]]]];
stream.PutF["\n(%g) %g %d", rope[iconName], rope[fileName], int[index]];
ENDLOOP;
stream.Close[] };
stream ← FS.StreamOpen[file, $append ! FS.Error => {stream ← NIL; CONTINUE}];
IF stream # NIL THEN [] ← CarefullyApply[InternalWrite]
};
Reporting errors
Report:
INTERNAL
PROCEDURE [msg: Rope.
ROPE, errLog:
IO.
STREAM] = {
OPEN IO;
IF errLog # NIL THEN errLog.PutF["\n\n%g", rope[msg]];
};
Opening, closing segment, making/reading catalogue
OpenUp: Booting.RollbackProc = {
DB.Initialize[nCachePages: 256];
InvalidateCache[] };
CloseTrans: Booting.CheckpointProc = {
DB.CloseTransaction[trans: DB.TransactionOf[segment: $Icons]] };
ResetSchema:
ENTRY
PROC[] = {
ENABLE UNWIND => NULL;
success: BOOLEAN;
activity ← TRUE;
IF DB.TransactionOf[$Icons] # NIL THEN RETURN;
[success, readOnly] ← SetUpSegment[];
IF NOT success THEN RETURN;
IF
NOT
DB.Null[IconRelation]
THEN
{ BROADCAST schemaSet; RETURN }; -- all is well, don't bother flushing cache
IconRelation ← DeclareRelation[ "IconInfo", $Icons ];
IconFileAttr ← DeclareAttribute[IconRelation, "file", RopeType];
IconIndexAttr ← DeclareAttribute[IconRelation, "index", IntType];
IconNameAttr ← DeclareAttribute[IconRelation, "name", RopeType, Key];
[] ← DeclareIndex[IconRelation, LIST[IconNameAttr]];
iconCache ← NIL;
BROADCAST schemaSet };
ReadCatalogue:
PUBLIC
ENTRY
PROC[file: Rope.
ROPE, errlog:
IO.
STREAM ←
NIL] =
TRUSTED {
ENABLE UNWIND => NULL;
stream: IO.STREAM;
stream ← FS.StreamOpen[file ! FS.Error => { stream ← NIL; CONTINUE }];
IF stream = NIL THEN RETURN;
stream ← IOClasses.CreateCommentFilterStream[stream];
Parse[ stream, errlog ];
DB.MarkTransaction[DB.TransactionOf[$Icons]] };
CarefullyApply:
INTERNAL
PROC [proc:
PROC[]]
RETURNS [succeeded:
BOOL] ~ {
ENABLE DB.Error, DB.Failure, DB.Aborted => {succeeded ← FALSE; GOTO Quit};
transactionToBeAborted: DB.Transaction ← NIL;
succeeded ← TRUE;
proc[ !
DB.Aborted => { transactionToBeAborted ← trans; succeeded ←
FALSE;
CONTINUE };
DB.Error => {IF code # TransactionNotOpen THEN REJECT; succeeded ← FALSE; CONTINUE} ];
IF succeeded THEN RETURN; -- no aborted occurred
IF transactionToBeAborted # NIL THEN DB.AbortTransaction[transactionToBeAborted];
TRUSTED {Process.Detach[ FORK ResetSchema[] ]};
WAIT schemaSet; -- ResetSchema is doing its job since we forked it
proc[]; -- don't bother trying to restart here --
succeeded ← TRUE;
};
SetUpSegment:
INTERNAL
PROC []
RETURNS [success:
BOOL, readOnly:
BOOL] ~ {
ENABLE DB.Aborted, DB.Failure, DB.Error => {success ← FALSE; CONTINUE};
segment: ATOM = $Icons;
readOnly ← FALSE;
success ← TRUE;
DB.Initialize[nCachePages: 256];
IF
DB.TransactionOf[segment] #
NIL
THEN
{readOnly← DB.GetSegmentInfo[segment].readOnly; RETURN};
DB.DeclareSegment[IconDB, segment, 140B, FALSE];
DB.OpenTransaction[segment !
DB.Error =>
TRUSTED {
success← FALSE;
SELECT code
FROM
ProtectionViolation => {readOnly ← TRUE; CONTINUE};
ENDCASE => GOTO AlreadyDone} ];
IF success THEN GOTO AlreadyDone;
readOnly must be true to get here; try opening the segment for reading
DB.CloseTransaction[DB.TransactionOf[segment]];
DB.DeclareSegment[IconDB, segment, 140B, TRUE];
DB.OpenTransaction[segment];
IF NOT success THEN DB.CloseTransaction[DB.TransactionOf[segment]]
EXITS
AlreadyDone => NULL
};
Initialization
TRUSTED {
Booting.RegisterProcs[c: CloseTrans, r: OpenUp];
EstablishIconDB[];
Process.Detach[ FORK WatchDBActivity[] ] };
END.