File: DBIconsImpl.mesa
-- Edited by Teitelman on April 12, 1983 4:53 pm
-- Last Edited by: Donahue, August 11, 1983 2:28 pm
-- Last Edited by: Widom, July 2, 1984 11:23:50 am PDT
DIRECTORY
FS USING [Error, StreamOpen],
Booting USING [CheckpointProc, RegisterProcs, RollbackProc],
Convert USING [RopeFromCard],
DB,
DBIcons,
IO,
Basics USING[LowHalf],
IOClasses USING [CreateCommentFilterStream ],
NutOps USING[ EntityToName, Separator ],
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, Length, SkipTo, ROPE],
UserProfile USING [ Token ]
;
DBIconsImpl: CEDAR MONITOR
IMPORTS Basics, Booting, FS, Convert, IO, MessageWindow, VM, Process,
Rope, Icons, DB, List, UserProfile, NutOps, IOClasses
EXPORTS DBIcons
= BEGIN
OPEN DB, Rope;
Types
Failed: PUBLIC ERROR [why: DBIcons.Failure, reason: Rope.ROPE] = CODE;
ROPE: TYPE = Rope.ROPE;
Global variables for accessing the database
IconDomain: DB.Domain; -- the domain of icons
IconRelation: DB.Relation; -- the relation giving the properties of an icon
The tuples in the relation have the structure
IconRelation[ IconIsAttr: IconDomain, IconFileAttr: ROPE, IconIndexAttr: INT, IconFlavorAttr: Icons.IconFlavor ] (where the flavor is stored as an UNSPECIFIED)
IconFileAttr: DB.Attribute; -- the name of the file for an icon
IconIndexAttr: DB.Attribute; -- the index in the file
IconIsAttr: 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 ROPENIL;
readOnly: PUBLIC BOOLEANTRUE; -- true if the segment is readonly
activity: BOOLEANFALSE; -- 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.ROPENIL] = {
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.
[] ← InternalRegisterIcon[iconName, fileName, index]
};
GetIcon: PUBLIC ENTRY 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.
ENABLE UNWIND => NULL;
cachedFlavor: REF ANY = CheckCache[iconName, iconCache];
flavor: Icons.IconFlavor ← unInit;
success: BOOL;
DoGetIcon: PROC[] = {
icon: Entity = DeclareEntity[IconDomain, iconName, OldOnly];
IF icon = NIL THEN {flavor ← default; RETURN};
{ fileName: ROPE = V2S[ GetP[ icon, IconFileAttr, IconIsAttr ] ];
index: CARDINAL = Basics.LowHalf[LOOPHOLE[V2I[GetP[icon, IconIndexAttr, IconIsAttr]]]];
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
acorn: Icons.IconFlavor← NewIcon["Nut.icons", 3];
SetIcon: PUBLIC PROCEDURE[e: Entity, iconFile: ROPE, fileIndex: CARDINAL] =
BEGIN
name: ROPE = IF DB.Eq[DomainOf[e], DomainDomain] THEN DB.NameOf[e]
     ELSE NutOps.EntityToName[e, DB.SegmentOf[e]];
IF IsRegistered[name].file = NIL THEN
  RegisterIcon[ name, iconFile, fileIndex ]
END;
icons: IconList;
IconList: TYPE = LIST OF RECORD[file: ROPE, index: INTEGER, icon: Icons.IconFlavor];
NewIcon: PUBLIC PROC[file: ROPE, index: INTEGER] RETURNS[icon: Icons.IconFlavor ← acorn] =
BEGIN
ENABLE FS.Error =>
TRUSTED {IF error.group = bug OR error.group = environment THEN REJECT
     ELSE GOTO notFound};
FOR list: IconList ← icons, list.rest WHILE list # NIL DO
IF list.first.index # index THEN LOOP;
IF ~Rope.Equal[list.first.file, file] THEN LOOP;
RETURN[list.first.icon];
ENDLOOP;
icon ← Icons.NewIconFromFile[file, index];
icons ← CONS[[file, index, icon], icons];
EXITS
notFound => -- if not given full path name, then try [Indigo]<Squirrel>Icons>file
BEGIN ENABLE FS.Error =>
TRUSTED {IF error.group = bug OR error.group = environment THEN REJECT
     ELSE CONTINUE};
IF file.SkipTo[pos: 0, skip: "[/"] # file.Length[] THEN RETURN; --given full path, give up
icon ← Icons.NewIconFromFile[Rope.Cat["[Indigo]<Squirrel>Icons>", file], index];
icons ← CONS[[file, index, icon], icons];
END;
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.ROPENIL, fileName: Rope.ROPENIL, index: CARDINALLAST[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).
DoRegisterCheck: PROC[] =
{ IF iconName # NIL THEN
{ icon: Entity = DeclareEntity[IconDomain, iconName, OldOnly];
IF icon = NIL THEN
{ file ← NIL; name ← NIL; i ← 0 }
ELSE
{ file ← V2S[ GetP[ icon, IconFileAttr, IconIsAttr ] ];
i ← Basics.LowHalf[LOOPHOLE[V2I[GetP[icon, IconIndexAttr, IconIsAttr]]]] } }
ELSE
{iconRels: RelshipSet = RelationSubset[IconRelation,
       LIST[AttributeValue[IconFileAttr, S2V[fileName]],
         AttributeValue[IconIndexAttr, U2V[index]]]];
iconRel: Relship = NextRelship[iconRels];
IF iconRel = NIL THEN
{ ReleaseRelshipSet[iconRels];
file ← NIL; name ← NIL; i ← 0 }
ELSE
{ name ← DB.NameOf[V2E[GetF[iconRel, IconIsAttr]]];
file ← fileName;
i ← index;
ReleaseRelshipSet[iconRels] } } };
IF NOT CarefullyApply[DoRegisterCheck] THEN RETURN[NIL, NIL, 0]
};
InvalidateCache: PUBLIC ENTRY PROC [iconName: Rope.ROPENIL] = {
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.
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] = {
p: SAFE PROCESSNIL;
DoRegisterIcon: PROC[] = {
icon: DB.Entity = DeclareEntity[IconDomain, iconName];
iconRel: Relship = DeclareRelship[ IconRelation, LIST[AttributeValue[IconIsAttr, icon]] ];
SetF[iconRel, IconFileAttr, S2V[fileName]];
SetF[iconRel, IconIndexAttr, U2V[index]] };
IF readOnly OR IconDB = NIL THEN RETURN; -- can't register things in this segment
p ← FORK ResetSchema[];
TRUSTED {JOIN p};
success ← CarefullyApply[DoRegisterIcon]
};
Parsing Icon Catalogues
Parse: INTERNAL PROCEDURE[ stream: IO.STREAM, errlog: IO.STREAM ] = {
OPEN IO;
error: BOOLEANFALSE;
{ ENABLE IO.EndOfStream => GOTO Out;
DO
sepCount: INT;
DBNameProc: IO.BreakProc = {
find a colon after seeing zero or two separators (one for the domain,
one for the segment
IF char = NutOps.Separator THEN
{ sepCount ← sepCount+1; RETURN[other] };
IF char = ': AND (sepCount = 2 OR sepCount = 0) THEN
RETURN[sepr];
RETURN[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;
position: INT;
[] ← IO.SkipWhitespace[stream];
position ← stream.GetIndex[];
sepCount ← 0;
iconName ← IO.GetTokenRope[stream, DBNameProc].token;
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[position]], errLog: errlog];
GOTO Out }
ENDLOOP;
EXITS
Out => NULL;
};
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] = {
stream: IO.STREAM;
InternalWrite: PROC[] = {
OPEN IO;
icons: EntitySet = DomainSubset[IconDomain];
FOR e: Entity ← NextEntity[icons], NextEntity[icons] UNTIL e = NIL DO
fileName: ROPE = V2S[ GetP[e, IconFileAttr, IconIsAttr] ];
index: CARDINAL = Basics.LowHalf[LOOPHOLE[V2I[GetP[e, IconIndexAttr, IconIsAttr]]]];
stream.PutF["\n%g: %g %d", rope[DB.NameOf[e]], 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[] = {
success: BOOLEAN;
activity ← TRUE;
IF DB.TransactionOf[$Icons] # NIL THEN RETURN;
[success, readOnly] ← SetUpSegment[];
IF NOT success THEN RETURN;
IF NOT DB.Null[IconDomain] THEN
{ BROADCAST schemaSet; RETURN }; -- all is well, don't bother flushing cache
IconDomain ← DeclareDomain["Icon", $Icons];
IconRelation ← DeclareRelation[ "IconInfo", $Icons ];
IconFileAttr ← DeclareAttribute[IconRelation, "file", RopeType];
IconIndexAttr ← DeclareAttribute[IconRelation, "index", IntType];
IconIsAttr ← DeclareAttribute[IconRelation, "of", IconDomain, Key];
iconCache ← NIL;
BROADCAST schemaSet };
ReadCatalogue: PUBLIC ENTRY PROC[file: Rope.ROPE, errlog: IO.STREAMNIL] = TRUSTED {
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};
p: SAFE PROCESSNIL;
transactionToBeAborted: DB.Transaction ← NIL;
succeeded ← TRUE;
proc[ ! DB.Aborted =>
{ DB.AbortTransaction[trans]; succeeded ← FALSE; CONTINUE };
DB.Error =>
{IF code # TransactionNotOpen THEN REJECT; succeeded ← FALSE; CONTINUE} ];
IF succeeded = TRUE THEN RETURN; -- no aborted occurred, things either succeeded or failed
wait for the reset to finish and then try again
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 --
EXITS
Quit => NULL;
};
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.