WhiteboardDBImpl.mesa
Copyright (C) 1984 by Xerox Corporation. All rights reserved.
Last edited by
Jennifer Widom, August 24, 1984 8:38:52 pm PDT
Donahue, January 21, 1985 8:51:04 am PST
(Corrected destruction of viewer properties when playing log; simplified CarefullyApply)
(Changed reset to close transaction before doing redisplay so that cached pages aren't used -- this gets the proper stuff redisplayed!)
(Added WBError and changed error handling behavior in the code -- now most of it is in CarefullyApply, where it should have been all along)
Last Edited by: Winkler, December 18, 1984 4:49:16 pm PST
DIRECTORY
Booting,
DB,
DBIcons,
DBTools,
Nut,
Process,
Rope,
ViewerClasses USING [Viewer, ViewerRec],
ViewerLocks USING [CallUnderWriteLock, ReleaseWriteLock],
ViewerOps,
ViewerTools,
VirtualDesktops,
UserCredentials,
UserProfile,
WhiteboardDB,
WhiteboardDBPrivate,
WhiteboardDump,
WhiteboardLoad,
WhiteboardViewers;
WhiteboardDBImpl: CEDAR MONITOR
IMPORTS
Booting, DB, DBIcons, DBTools, Nut, Process, Rope, ViewerLocks, ViewerOps, ViewerTools, VirtualDesktops, WhiteboardDBPrivate, UserCredentials, UserProfile, WhiteboardViewers, WhiteboardDump, WhiteboardLoad
EXPORTS WhiteboardDB
SHARES ViewerLocks =
BEGIN OPEN DB, WhiteboardDBPrivate;
Viewer: TYPE = ViewerClasses.Viewer;
ROPE: TYPE = Rope.ROPE;
WhiteboardLog: TYPE = LIST OF WhiteboardLogEntry;
WhiteboardLogEntry: TYPE = RECORD[action: ATOM, to: Viewer];
Currently action can be one of:
$NewIcon
$NewBox
$Delete
$EditText
$Grow
$Move
$Erase
whiteboard: PUBLIC ATOM ← WhiteboardDBPrivate.wbType;
destroyedList: LIST OF Viewer;
Monitoring database activity
ticksToWait: Process.Ticks ← Process.SecondsToTicks[5*60];
stopped: PUBLIC BOOLFALSE; -- true if the Rollback proc has been executed and the transaction is to remain closed
readOnly: PUBLIC BOOLTRUE; -- default to the current database being read only
doingRestart: BOOLFALSE; -- true during the restart process, to keep iconic viewers from being opened
newWhiteboardDB: ROPE; -- the name of a new whiteboard database if pendingChange (it will be used when the first call to CarefullyApply is made)
pendingChange: BOOLFALSE; -- this is set by the rollback and credentials change procs to remember a potential change of state; it is checked by CarefullyApply, which will call ResetSchema if it is true
activity: BOOLEANTRUE; -- true if a database operation has been performed recently
WBError: PUBLIC ERROR[reason: ATOM] = CODE; -- the error that may be raised from this interface
WatchDBActivity: PROC[] = {
Don't keep a transaction open too long.
WHILE TRUE DO
Process.Pause[ticksToWait];
CheckConnection[]
ENDLOOP };
CheckConnection: ENTRY PROC[] = {
aborted: BOOLEANFALSE;
IF stopped THEN RETURN;
IF NOT activity THEN CloseTransaction[];
activity ← FALSE };
Close: PUBLIC ENTRY PROC[] = {
CloseTransaction[];
DBIcons.Close[];
DBTools.Close[];
stopped ← TRUE };
CloseTransaction: INTERNAL PROC [] = {
ENABLE DB.Error, DB.Failure => CONTINUE;
trans: DB.Transaction = DB.GetSegmentInfo[$Whiteboard].trans;
caughtAborted: BOOLFALSE;
IF trans # NIL THEN
DB.CloseTransaction[trans ! DB.Aborted => { caughtAborted ← TRUE; CONTINUE }];
IF caughtAborted THEN DB.AbortTransaction[trans] };
Routines that read and/or write the database
Display: PUBLIC ENTRY PROC[eName: ROPE, v: Viewer ← NIL] RETURNS [viewer: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
DoDisplay: PROC[] = { viewer ← InternalDisplay[eName, v] };
CarefullyApply[DoDisplay]
END;
InternalDisplay: PROC[eName: ROPE, v: Viewer ← NIL] RETURNS [Viewer] =
BEGIN
locked: BOOLEANFALSE;
wb: Whiteboard = DeclareWhiteboard[eName];
children: ChildSet = Children[wb];
newViewer: BOOLFALSE;
nextChild: WBItem;
child: Viewer;
x, y: INT;
DisplayNoteEntity: PROC[] =
BEGIN
text: ViewerTools.TiogaContents = GetContents[nextChild];
w, h: INT;
[w, h] ← Size[nextChild];
child ← WhiteboardViewers.AddTextBox[v, x, y, w, h, text]
END;
DisplayIconEntity: PROC[] =
BEGIN
label, icon: ROPE;
[label, icon] ← GetDisplayProps[nextChild];
child ← WhiteboardViewers.AddIcon[v, label, DBIcons.GetIcon[icon], x, y];
END;
DoDisplay: PROC = {
versionNumber: INTEGER;
locked ← TRUE; -- now we're inside the forbidden region
If v.newVersion then this is a reset and the name and menu are properly set
IF NOT v.newVersion THEN {
v.newVersion ← TRUE;
WhiteboardViewers.SetMenu[v];
WhiteboardViewers.SetWBName[v, eName];
};
Must make sure that the grid size is consistent with the current DB contents
BEGIN
gridSize: INT = GetGridSize[wb];
WhiteboardViewers.ResetGrid[v, gridSize]
END;
obtain version information
versionNumber ← GetVersion[wb];
ViewerOps.AddProp[v, $Version, NEW[INTEGER ← versionNumber]];
WHILE (nextChild ← NextChild[children]) # NIL DO
[x, y] ← Position[nextChild];
IF TypeOf[nextChild] = note THEN DisplayNoteEntity[] ELSE DisplayIconEntity[];
StoreEntity[child, DB.NameOf[nextChild]];
ENDLOOP;
v.newVersion ← FALSE;
ViewerOps.PaintViewer[v, all] };
BEGIN
ENABLE UNWIND => {
IF locked THEN ViewerLocks.ReleaseWriteLock[v];
IF newViewer THEN ViewerOps.DestroyViewer[v] };
IF v = NIL OR v.class.flavor # WhiteboardViewers.wb THEN {
v ← ViewerOps.CreateViewer[flavor: WhiteboardViewers.wb, info: [name: eName, iconic: TRUE]];
newViewer ← TRUE }
ELSE v.child ← NIL; -- flushes viewer
ViewerLocks.CallUnderWriteLock[DoDisplay, v];
locked ← FALSE;
IF NOT doingRestart AND v.iconic AND NOT v.destroyed THEN ViewerOps.OpenIcon[v];
ViewerOps.AddProp[v, $Log, NIL]; -- establish log
[] ← Nut.SetFrozenProperty[v, FALSE];
Nut.SetNutInfo[v, $Whiteboard, "Whiteboard", eName];
ViewerOps.AddProp[v, $Lines, NIL];
RETURN[v]
END
END;
Destroy: PUBLIC ENTRY PROC[eName: ROPE] =
BEGIN
ENABLE UNWIND => NULL;
DoDestroy: PROC[] = {
wb: Whiteboard = IF Exists[eName] THEN DeclareWhiteboard[eName] ELSE NIL;
IF readOnly THEN WBError[$ReadOnly];
IF wb = NIL THEN RETURN ELSE DestroyWhiteboard[wb];
DB.MarkTransaction[DB.GetSegmentInfo[$Whiteboard].trans]
};
v: Viewer;
CarefullyApply[DoDestroy];
Check if there's a viewer that needs to be destroyed
v ← ViewerOps.FindViewer[Rope.Cat["Whiteboard: ", eName]];
IF v # NIL THEN ViewerOps.DestroyViewer[v];
END;
New: PUBLIC ENTRY PROC[eName: ROPE] RETURNS[success: BOOL] =
BEGIN
ENABLE UNWIND => NULL;
wbEntity: DB.Entity;
DoNew: PROC[] = {
IF readOnly THEN WBError[$ReadOnly];
wbEntity ← DeclareWhiteboard[eName];
DB.MarkTransaction[DB.GetSegmentInfo[$Whiteboard].trans] };
CarefullyApply[DoNew];
success ← wbEntity # NIL
END;
Reset: PUBLIC ENTRY PROC[wb: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
wb.newVersion ← TRUE;
ViewerOps.PaintViewer[wb, caption];
CloseTransaction[];
InternalReset[wb];
END;
InternalReset: INTERNAL PROC[wb: Viewer] =
BEGIN
DoRedisplay: PROC[] = {
name: ROPE = GetWBName[wb];
IF WhiteboardDBPrivate.Exists[name] THEN [] ← InternalDisplay[name, wb]
ELSE ViewerOps.DestroyViewer[wb] };
ViewerOps.AddProp[wb, $Log, NIL]; -- Reset Log
[] ← Nut.SetFrozenProperty[wb, FALSE];
ViewerOps.AddProp[wb, $Lines, NIL];
CarefullyApply[DoRedisplay];
END;
Save: PUBLIC ENTRY PROC[wb: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
eventLog: WhiteboardLog = NARROW[ViewerOps.FetchProp[wb, $Log], WhiteboardLog];
versionNumber: INTEGER = NARROW[ViewerOps.FetchProp[wb, $Version], REF INTEGER]^;
DoSave: PROC[] = {
IF readOnly THEN WBError[$ReadOnly];
BEGIN
wbEntity: Whiteboard = DeclareWhiteboard[GetWBName[wb]];
IF GetVersion[wbEntity] # versionNumber THEN WBError[$WrongVersion];
FOR log: WhiteboardLog ← eventLog, log.rest UNTIL log = NIL DO
SELECT log.first.action FROM
$NewIcon => DoNewIcon[wbEntity, log.first.to];
$NewBox => DoNewBox[wbEntity, log.first.to];
$Delete => DoDelete[log.first.to];
$EditText => DoEditText[log.first.to];
$Grow => DoGrow[log.first.to];
$Move => DoMove[log.first.to];
$Erase => DoErase[wbEntity];
ENDCASE;
ENDLOOP;
SetGridSize[wbEntity, WhiteboardViewers.GetGrid[wb]];
SetVersion[wbEntity, versionNumber + 1];
DB.MarkTransaction[DB.GetSegmentInfo[$Whiteboard].trans]
END };
[] ← wb.class.scroll[wb, thumb, 0];
CarefullyApply[DoSave];
Now it's safe to throw away any destroyed viewers (ones on the destroyedList); they either participated in a committed transaction (and thus can have their properties released) or they no longer exist on the whiteboard in any case
DestroyViewerList[];
InternalReset[wb]
END;
DestroyViewerList: PROC[] = {
FOR l: LIST OF Viewer ← destroyedList, l.rest UNTIL l = NIL DO
l.first.props ← NIL;
l.first.data ← NIL
ENDLOOP;
destroyedList ← NIL };
GetWBName: PUBLIC PROC[wb: Viewer] RETURNS [name: ROPE] =
BEGIN
domain: ROPE;
[domain ~ domain, entity ~ name] ← Nut.GetNutInfo[wb];
IF Rope.Equal[domain, "Whiteboard"] THEN RETURN;
RETURN[NIL]
END;
WBExists: PUBLIC ENTRY PROC[eName: ROPE] RETURNS [alreadyExists: BOOL] =
BEGIN
ENABLE UNWIND => NULL;
DoCheck: PROC[] = {alreadyExists ← Exists[eName]};
CarefullyApply[DoCheck];
END;
CopyWB: PUBLIC ENTRY PROC[from, to: ROPE] =
BEGIN
ENABLE UNWIND => NULL;
DoCopy: INTERNAL PROC[] = {
IF readOnly THEN WBError[$ReadOnly];
BEGIN
fromWB: Whiteboard = DeclareWhiteboard[from];
toWB: Whiteboard = DeclareWhiteboard[to];
children: ChildSet = Children[fromWB];
FOR child: WBItem ← NextChild[children], NextChild[children] UNTIL child = NIL DO
IF TypeOf[child] = note THEN CopyNote[toWB, child]
ELSE CopyIcon[toWB, child]
ENDLOOP;
Commit the transaction
DB.MarkTransaction[DB.GetSegmentInfo[$Whiteboard].trans]
END };
CarefullyApply[DoCopy]
END;
CopyNote: INTERNAL PROC[to: Whiteboard, note: WBNote] =
BEGIN
text: ViewerTools.TiogaContents = GetContents[note];
x, y: INT;
w, h: INT;
[x, y] ← Position[note];
[w, h] ← Size[note];
[] ← NewNote[to, x, y, w, h, text]
END;
CopyIcon: INTERNAL PROC[to: Whiteboard, icon: WBIcon] =
BEGIN
x, y: INT;
name, label, iconName, argument: ROPE;
type: ATOM;
[x, y] ← Position[icon];
[label, iconName] ← GetDisplayProps[icon];
[name, type] ← WhiteboardDBPrivate.GetIconProps[icon];
argument ← WhiteboardDBPrivate.GetToolArgument[icon];
[] ← WhiteboardDBPrivate.NewIcon[to, x, y, name, label, iconName, argument, type]
END;
EnumerateWhiteboards: PUBLIC ENTRY PROC[eName: ROPENIL] RETURNS [wbList: LIST OF ROPE] =
BEGIN
ENABLE UNWIND => NULL;
EnumWBs: PROC[] = {
IF eName = NIL THEN {
entityList: LIST OF Whiteboard = Enumerate[];
FOR eL: LIST OF Whiteboard ← entityList, eL.rest UNTIL eL = NIL DO
wbList ← CONS[DB.NameOf[eL.first], wbList];
ENDLOOP
}
ELSE {
entityList: LIST OF WBItem;
children: ChildSet = Children[DeclareWhiteboard[eName], TRUE];
FOR child: WBItem ← NextChild[children], NextChild[children] UNTIL child = NIL DO
entityList ← CONS[child, entityList];
ENDLOOP;
FOR eL: LIST OF Whiteboard ← entityList, eL.rest UNTIL eL = NIL DO
wbList ← CONS[WhiteboardDBPrivate.GetIconProps[eL.first].name, wbList];
ENDLOOP }
};
CarefullyApply[EnumWBs]
END;
Dump: PUBLIC ENTRY PROC[to: ROPE] = {
ENABLE UNWIND => NULL;
DoDump: INTERNAL PROC[] = {WhiteboardDump.DumpToFile[to] };
CarefullyApply[DoDump] };
Load: PUBLIC ENTRY PROC[from: ROPE] = {
ENABLE UNWIND => NULL;
DoLoad: INTERNAL PROC[] = {
IF readOnly THEN WBError[$ReadOnly];
WhiteboardLoad.LoadFromFile[from] };
CarefullyApply[DoLoad] };
Routines that log whiteboard events w/o accessing the database
NewIcon: PUBLIC ENTRY PROC[wb: Viewer, x, y: INT, name: ROPE, type: ATOM, icon: ROPE, label: ROPENIL, argument: ROPENIL] RETURNS [new: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
new ← WhiteboardViewers.AddIcon[wb, IF label = NIL THEN name ELSE label, DBIcons.GetIcon[icon], x, y];
ViewerOps.AddProp[new, $EntityName, name];
ViewerOps.AddProp[new, $IconLabel, label];
ViewerOps.AddProp[new, $IconType, type];
ViewerOps.AddProp[new, $IconIcon, icon];
ViewerOps.AddProp[new, $IconArgument, argument];
LogAction[wb, $NewIcon, new];
ViewerOps.PaintViewer[new, all];
ViewerOps.PaintViewer[wb, caption];
END;
NewBox: PUBLIC ENTRY PROC[wb: Viewer, x, y, w, h: INT, contents: ViewerTools.TiogaContents ← NIL] RETURNS [new: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
new ← WhiteboardViewers.AddTextBox[wb, x, y, w, h, contents];
LogAction[wb, $NewBox, new];
ViewerOps.PaintViewer[new, all];
ViewerOps.PaintViewer[wb, caption];
END;
Delete: PUBLIC ENTRY PROC[child: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
logIt: BOOL = NOT WhiteboardViewers.DontLog[child];
props: Atom.PropList = child.props;
data: REF ANY = child.data;
Make sure the properties and clientdata don't go away just yet
IF logIt THEN LogAction[child.parent, $Delete, child];
ViewerOps.DestroyViewer[viewer: child, paint: FALSE];
IF logIt THEN {child.props ← props; child.data ← data}
END;
EditText: PUBLIC ENTRY PROC[box: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
LogAction[box.parent, $EditText, box];
END;
Grow: PUBLIC ENTRY PROC[box: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
LogAction[box.parent, $Grow, box];
END;
Move: PUBLIC ENTRY PROC[child: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
IF NOT WhiteboardViewers.DontLog[child] THEN
LogAction[child.parent, $Move, child];
END;
Erase: PUBLIC ENTRY PROC[wb: Viewer] =
BEGIN
ENABLE UNWIND => NULL;
LogAction[wb, $Erase, wb];
wb.child ← NIL; -- flushes viewer
ViewerOps.PaintViewer[wb, client];
END;
LogAction: PROC[wb: Viewer, action: ATOM, v: Viewer] =
BEGIN
log: WhiteboardLog ← NARROW[ViewerOps.FetchProp[wb, $Log], WhiteboardLog];
AddToEnd: PROC[log: WhiteboardLog, entry: WhiteboardLogEntry]
RETURNS[WhiteboardLog] = {
IF log = NIL THEN RETURN[LIST[entry]]
ELSE RETURN[CONS[log.first, AddToEnd[log.rest, entry]]] };
log ← AddToEnd[log, [action, v]];
ViewerOps.AddProp[wb, $Log, log];
END;
Playing the log
DoNewIcon: PROC[wb: Whiteboard, child: Viewer] =
Commit a $NewIcon action
BEGIN
name: ROPE = NARROW[ViewerOps.FetchProp[child, $EntityName], ROPE];
label: ROPE = NARROW[ViewerOps.FetchProp[child, $IconLabel], ROPE];
type: ATOM = NARROW[ViewerOps.FetchProp[child, $IconType], ATOM];
icon: ROPE = NARROW[ViewerOps.FetchProp[child, $IconIcon], ROPE];
argument: ROPE = NARROW[ViewerOps.FetchProp[child, $IconArgument], ROPE];
newIcon: WBItem = WhiteboardDBPrivate.NewIcon[wb, child.wx, child.wy, name, label, icon, argument, type];
StoreEntity[child, DB.NameOf[newIcon]] -- in case subsequent operations in the log reference this viewer (eg., to move it)
END;
DoNewBox: PROC[wb: Whiteboard, box: Viewer] =
Commit a $NewBox action
BEGIN
text: ViewerTools.TiogaContents = ViewerTools.GetTiogaContents[box];
newNote: WBItem = WhiteboardDBPrivate.NewNote[wb, box.wx, box.wy, box.ww, box.wh, text];
StoreEntity[box, DB.NameOf[newNote]] -- in case subsequent operations in the log reference this viewer (eg., to move it)
END;
DoDelete: PROC[child: Viewer] =
Commit a $Delete action
BEGIN
entity: WBItem = FetchEntity[child];
WhiteboardDBPrivate.Destroy[entity];
Save the child on the destroyedList so that you can release the props and data if the transaction succeeds
destroyedList ← CONS[child, destroyedList];
END;
DoEditText: PROC[box: Viewer] =
Commit an $EditText action
BEGIN
note: WBNote = FetchEntity[box];
text: ViewerTools.TiogaContents = ViewerTools.GetTiogaContents[box];
SetContents[note, text]
END;
DoGrow: PROC[box: Viewer] =
Commit a $Grow action
BEGIN
note: WBNote = FetchEntity[box];
WhiteboardDBPrivate.Grow[note, box.ww, box.wh]
END;
DoMove: PROC[child: Viewer] =
Commit a $Move action
BEGIN
entity: WBItem = FetchEntity[child];
WhiteboardDBPrivate.Move[entity, child.wx, child.wy]
END;
DoErase: PROC[wb: Whiteboard] =
Commit an $Erase action
BEGIN
name: ROPE = NameOf[wb];
WhiteboardDBPrivate.DestroyWhiteboard[wb];
[] ← DeclareWhiteboard[name]
END;
Support routines
FetchEntity: PUBLIC PROC[v: Viewer] RETURNS[e: DB.Entity] = {
name: ROPE = NARROW[ViewerOps.FetchProp[v, $Entity], ROPE];
IF name = NIL THEN RETURN[NIL];
SELECT v.class.flavor FROM
WhiteboardViewers.text => e ← FetchWBItem[name, note];
WhiteboardViewers.icon => e ← FetchWBItem[name, icon];
ENDCASE => e ← NIL };
StoreEntity: PUBLIC PROC[v: Viewer, name: ROPE] = {
ViewerOps.AddProp[v, $Entity, name ] };
Tool manipulation
GetIconProps: PUBLIC ENTRY PROC[wbIcon: Viewer] RETURNS[ name: ROPE, type: ATOM ] = {
ENABLE UNWIND => NULL;
DoGet: PROC[] = {
iconEntity: WBIcon = FetchEntity[wbIcon];
[name, type] ← WhiteboardDBPrivate.GetIconProps[iconEntity] };
name ← NARROW[ViewerOps.FetchProp[wbIcon, $EntityName]];
IF name # NIL THEN
type ← NARROW[ViewerOps.FetchProp[wbIcon, $IconType]]
ELSE CarefullyApply[DoGet] };
GetToolArgument: PUBLIC ENTRY PROC[wbIcon: Viewer] RETURNS[ argument: ROPE] = {
ENABLE UNWIND => NULL;
DoGet: PROC[] = {
iconEntity: WBIcon = FetchEntity[wbIcon];
If the entity name is NIL, then don't bother looking in the database for an argument
IF iconEntity = NIL THEN {argument ← NIL; RETURN};
argument ← WhiteboardDBPrivate.GetToolArgument[iconEntity] };
argument ← NARROW[ViewerOps.FetchProp[wbIcon, $IconArgument]];
IF argument = NIL THEN CarefullyApply[DoGet];
ViewerOps.AddProp[wbIcon, $IconArgument, argument] };
Database initialization
EstablishWhiteboardDB: PUBLIC ENTRY PROC[DBFile: ROPENIL] = {
Sets newWhiteboardDB to value of either DBFile or user profile entry (if a whiteboard DB was previously opened and the argument was NIL, then remember the previous DB file); also throws away old viewers on previous DB;
The value of success will be FALSE only if InternalResetViewers fails to reestablish a connection
ENABLE UNWIND => NULL;
IF DBFile = NIL THEN
IF WhiteboardDBPrivate.WBSegment # NIL THEN newWhiteboardDB ← WhiteboardDBPrivate.WBSegment
ELSE newWhiteboardDB ← UserProfile.Token[key: "Whiteboard.Segment", default: "[Luther.Alpine]<CedarDoc>Whiteboard.segment"]
ELSE newWhiteboardDB ← DBFile;
stopped ← FALSE;
pendingChange ← TRUE;
InternalResetViewers[newWhiteboardDB # WhiteboardDBPrivate.WBSegment] };
InternalResetViewers: INTERNAL PROC[destroy: BOOL] = {
IF destroy THEN {
DestroyProc: ViewerOps.EnumProc = {
IF v.class.flavor = $Whiteboard THEN ViewerOps.DestroyViewer[v];
RETURN[TRUE] };
aborted: BOOLFALSE;
Close transaction if there's one open
CloseTransaction[];
Destroy all Whiteboard viewers
VirtualDesktops.EnumerateViewers[enum: DestroyProc] }
ELSE {
ResetProc: INTERNAL ViewerOps.EnumProc = {
Leave edited or dead whiteboards alone!!
IF v.class.flavor = $Whiteboard AND NOT v.newVersion AND NOT v.destroyed
THEN InternalReset[v];
RETURN[TRUE] };
doingRestart ← TRUE;
VirtualDesktops.EnumerateViewers[enum: ResetProc];
The enumeration will fail and stop if the reset fails; this also sets the returned value of success
doingRestart ← FALSE } };
CarefullyApply: INTERNAL PROC [proc: PROC[]] ~ {
ENABLE BEGIN
DB.Error => ERROR WBError[$InternalError];
DB.Failure => ERROR WBError[$ServerDown];
DB.Aborted => ERROR WBError[$TransactionAbort];
END;
aborted: BOOLFALSE;
IF stopped THEN RETURN; -- don't bother trying to do the operation
activity ← TRUE;
IF pendingChange THEN {
pendingChange ← FALSE;
Initialize[newWhiteboardDB];
readOnly ← DB.GetSegmentInfo[whiteboard].readOnly };
BEGIN
ENABLE DB.Aborted => { aborted ← TRUE; CONTINUE };
[] ← WhiteboardDBPrivate.ResetSchema[];
proc[]
END;
IF NOT aborted THEN RETURN; -- no aborted occurred
DB.AbortTransaction[DB.GetSegmentInfo[$Whiteboard].trans];
WhiteboardDBPrivate.ResetSchema[];
proc[]; -- don't bother trying to restart here --
};
CloseWB: ENTRY Booting.CheckpointProc = {
ENABLE UNWIND => NULL;
CloseTransaction[];
stopped ← TRUE };
OpenUp: Booting.RollbackProc = { DB.Initialize[nCachePages: 256] };
NewUserReset: ENTRY UserCredentials.CredentialsChangeProc = {
ENABLE UNWIND => NULL;
DestroyProc: ViewerOps.EnumProc = {
IF v.class.flavor = $Whiteboard THEN ViewerOps.DestroyViewer[v];
RETURN[TRUE] };
Close transaction if there's one open
CloseTransaction[];
Destroy all whiteboard viewers
VirtualDesktops.EnumerateViewers[enum: DestroyProc] };
ProfileChangeReset: ENTRY UserProfile.ProfileChangedProc = {
ENABLE UNWIND => NULL;
DestroyProc: ViewerOps.EnumProc = {
IF v.class.flavor = $Whiteboard THEN ViewerOps.DestroyViewer[v];
RETURN[TRUE] };
SELECT reason FROM
firstTime, rollBack => {
newDB: ROPE = UserProfile.Token[key: "Whiteboard.Segment", default: "[Luther.Alpine]<CedarDoc>Whiteboard.segment"];
IF NOT Rope.Equal[WhiteboardDBPrivate.WBSegment, newDB] THEN {
newWhiteboardDB ← newDB;
pendingChange ← TRUE } }; -- don't open up the new database yet; simply remember that it must be done
rollBack => RETURN;
edit => { -- Only do something if the Whiteboard segment has changed
newWhiteboardDB ← UserProfile.Token[key: "Whiteboard.Segment", default: "[Luther.Alpine]<CedarDoc>Whiteboard.segment"];
IF NOT Rope.Equal[newWhiteboardDB, WhiteboardDBPrivate.WBSegment] THEN {
pendingChange ← TRUE;
InternalResetViewers[TRUE] } }
ENDCASE };
Initialization
TRUSTED {
[] ← EstablishWhiteboardDB[];
Process.Detach[FORK WatchDBActivity[]];
Booting.RegisterProcs[c: CloseWB, r: OpenUp];
UserCredentials.RegisterForChange[NewUserReset];
UserProfile.CallWhenProfileChanges[ProfileChangeReset] }
END...