DIRECTORY
Atom USING [ GetProp, PutProp ],
Convert USING [ CardFromRope, RopeFromCard ],
FinchSmarts USING [ ConvDesc, EnumerateIncreasing, FinchInfo, FinchInfoBody, info, lock, pd, Problem, ReportProcs, ReportProcsBody, ReportSystemStateProc, ReportConversationStateProc, ReportRequestStateProc, SHHH ],
IO,
MBQueue USING [ QueueClientAction ],
NameDB USING [ GetAttribute ],
Process USING [ Detach, SecondsToTicks, SetTimeout ],
RedBlackTree USING [ Create, Compare, EachNode, GetKey, Insert, LookupNextLarger, LookupNextSmaller, Table ],
Rope USING [ Equal, ROPE ],
RPC USING [ InterfaceName ],
ThParty,
ThPartySunRPControl USING [ ReportProblem, ReportState, ThrushConnect, ThrushDisconnect, ThrushReportState ],
Thrush,
ThSmartsSunExport USING [ ExportInterface ],
ThVersions USING [ FinchVR ],
UserProfile USING [ Token ],
VoiceUtils USING [ CurrentRName, CurrentPasskey, OwnNetAddress, Report ],
VoiceUtilsExtras USING [ RopeFromSunAddr ]
;
High-level registration code, keep-alive code.
InitFinchSmarts:
PUBLIC
ENTRY
PROC [
thrushInstance: Thrush.ROPE←NIL,
key: ATOM,
s: FinchSmarts.ReportSystemStateProc ← NIL,
c: FinchSmarts.ReportConversationStateProc ← NIL,
r: FinchSmarts.ReportRequestStateProc ← NIL] = {
ENABLE UNWIND => NULL;
problem: ROPE←NIL; nb: NB←$serverDown;
prevInfo: FinchSmarts.FinchInfo ← info;
IF info#NIL AND info.connected THEN UninitFinchSmartsInt[];
info ← NEW[FinchSmarts.FinchInfoBody]; -- Dump any old one!
IF prevInfo#NIL THEN NOTIFY prevInfo.pollCondition;
info.conversations ← NIL;
info.connected ← FALSE;
RegisterForReportsInt[key: $FinchSmarts,
s: ClearInfo, other: $all, before:
FALSE];
Must be the very last thing
RegisterForReportsInt[key: key, s: s, c: c, r: r];
thrushInstance ←
SELECT
TRUE
FROM
thrushInstance#NIL => thrushInstance,
prevInfo#NIL AND prevInfo.prevThrushInstance#NIL => prevInfo.prevThrushInstance,
ENDCASE=> UserProfile.Token["ThrushSunClientServerInstance", "Growler"];
info.prevThrushInstance ← thrushInstance;
uniqueSuffix ← ThSmartsSunExport.ExportInterface[port: uniqueSuffix];
info.
myName ← [
type: "ThSmarts.Lark",
instance: VoiceUtilsExtras.RopeFromSunAddr[port: uniqueSuffix],
version: ThVersions.FinchVR]; -- Sun RPC code doesn't use this version yet
info.myRName ← VoiceUtils.CurrentRName[];
info.myPassword ← VoiceUtils.CurrentPasskey[];
Now connect to server and register with Thrush
info.
shh ← ThPartySunRPControl.ThrushConnect[
server: thrushInstance, rName: info.myRName, type: $individual,
interface: info.myName, properties: [$controller, VoiceUtils.OwnNetAddress[]],
reportState: ReportStateFromThPartyRPC, reportProblem: ReportProblemFromThPartyRPC];
TRUSTED { Process.Detach[FORK PollWaiter[info]]; };
};
UninitFinchSmarts:
PUBLIC
ENTRY
PROC[disable:
BOOL←
TRUE] = {
ENABLE UNWIND=>NULL;
UninitFinchSmartsInt[disable];
UninitFinchSmartsInt:
INTERNAL
PROC[disable:
BOOL←
TRUE] = {
shhh: FinchSmarts.SHHH ← IF info#NIL THEN info.shh ELSE NIL;
IF shhh=NIL THEN RETURN;
ThPartySunRPControl.ThrushDisconnect[shhh, disable];
IF disable THEN ThPartySunRPControl.ReleaseConversation[shhh];
That's just a cleanliness, and I'm worried about not adequately recording the last Progress and Substitution reports as they drag in. Before putting this back, analyze some typical traffic flow and see what's needed. Perhaps the previous stuff should be put on a timer, then destroyed. Worry, if ever we don't destroy info and create a new one, about the possibility that old reports will encounter strange credentials. DCS, September 13, 1990 11:57:37 am PDT
};
FinchIsRunning:
PUBLIC
PROC
RETURNS [finchIsEnabled: BOOLLSE, finchIsRunning: BOOLLSE, voicePath: BOOLLSE] = {
IF info=NIL THEN RETURN;
RETURN[info.enabled, info.connected, info.voicePath];
};
ClearInfo:
ENTRY FinchSmarts.ReportSystemStateProc = {
ENABLE UNWIND => NULL;
IF connected OR info=NIL THEN RETURN;
FOR cs:
LIST
OF FinchSmarts.ConvDesc ← info.conversations, cs.rest
WHILE cs#
NIL
DO
cs.first.clientData ← NIL;
cs.first.props ← NIL; -- probably unnecessary.
ENDLOOP;
info.conversations ← NIL;
};
Could be made part of ThPartyToThPartySunRPC, sort of.
nextSched:
INT ← pd.pollDefault;
PollWaiter:
ENTRY
PROC[myInfo: FinchSmarts.FinchInfo] = {
ENABLE UNWIND => NULL;
nextSched ← pd.pollDefault;
WHILE myInfo = info
DO
nextSched ← MIN[nextSched, pd.pollDefault];
TRUSTED { Process.SetTimeout[@myInfo.pollCondition, Process.SecondsToTicks[nextSched]]; };
WAIT info.pollCondition;
IF myInfo#info THEN RETURN;
IF info.nextScheduledCheck#0
THEN {
nextSched ← info.nextScheduledCheck; info.nextScheduledCheck ← 0; LOOP; }
ELSE nextSched ← nextSched + 30;
Ordinary case: received another CheckIn report before timeout.
IF ~info.enabled OR ~pd.doPollingTimeouts THEN LOOP;
Timeout occurred before next CheckIn report, and either we're disconnected or we've been told to worry if we haven't heard from the server (or it's been quite a while, we've been told not to worry, and we're paranoid. Calling any ThParty function will either succeed or the failure will trigger the best available recovery activities.
[] ← ThParty.CheckIn[shh: info.shh, credentials: [
partyID: info.partyID, smartsID: info.smartsID, convID: Thrush.nullConvID]];
ENDLOOP;
};
State report, error management routines.
ReportSystemStateToFinchClients:
PROC [r:
REF] ~ {
reportProcsTable: RedBlackTree.Table ← NARROW[Atom.GetProp[$ReportProcs, $rpTable]];
EachRSS: RedBlackTree.EachNode = {
procs: FinchSmarts.ReportProcs ← NARROW[data];
IF procs.reportSystemState#
NIL
THEN
procs.reportSystemState[info.enabled, info.connected, info.voicePath];
};
IF reportProcsTable#
NIL
THEN
FinchSmarts.EnumerateIncreasing[reportProcsTable, EachRSS];
};
RecordSystemStateFromSmartsReport:
PUBLIC
INTERNAL
PROC[
nb: NB, remark: Rope.ROPE, connected: BOOL, enabled: BOOL, voicePath: BOOL, remoteRemark: Rope.ROPE ]
RETURNS[echoNB: NB] -- A convenience for callers -- = {
IF info.shh#
NIL
THEN
ThPartySunRPControl.ThrushReportState[info.shh, enabled, connected];
RETURN[RecSS[nb, remark, connected, enabled, voicePath, remoteRemark]];
};
ReportProblemFromThPartyRPC:
--already ENTRY-- ThPartySunRPControl.ReportProblem ~ {
[]←RecSS[nb: ec, remark: expl];
};
ReportStateFromThPartyRPC:
--already ENTRY-- ThPartySunRPControl.ReportState ~ {
IF info#
NIL
THEN {
info.partyID ← myPartyID;
info.smartsID ← mySmartsID;
};
[]←RecSS[connected: registered, enabled: enabled];
};
Generalized reporting utility
RecSS:
--may be INTERNAL--
PROC[
nb: NB←NIL,
remark: Rope.ROPE←NIL,
connected: BOOL←info.connected,
enabled: BOOL←info.enabled,
voicePath: BOOL←info.voicePath,
remoteRemark: Rope.ROPE←NIL
]
RETURNS[echoNB: NB] -- A convenience for callers -- = {
IF nb#
NIL
OR connected#info.connected
OR enabled#info.enabled
THEN {
IF nb#NIL THEN FinchSmarts.Problem[remark, nb]
ELSE IF remark#NIL THEN VoiceUtils.Report[remark, $Finch];
};
info.connected ← connected;
info.enabled ← enabled;
info.voicePath ← voicePath;
pd.requests.QueueClientAction[ReportSystemStateToFinchClients, NIL];
IF remoteRemark#
NIL
THEN {
Relate each distinct remote remark exactly once.
IF info=
NIL
OR ~remoteRemark.Equal[info.lastRemoteRemark]
THEN VoiceUtils.Report[remoteRemark, $Finch];
IF info#NIL THEN info.lastRemoteRemark ← remoteRemark;
};
RETURN[nb];
};
More state reporting code.
RegisterForReports:
PUBLIC
ENTRY
PROC [key:
ATOM, s:
FinchSmarts.ReportSystemStateProc ←
NIL, c:
FinchSmarts.ReportConversationStateProc ←
NIL, r:
FinchSmarts.ReportRequestStateProc ←
NIL, other:
ATOM←
NIL, before:
BOOL ←
TRUE] = {
ENABLE UNWIND => NULL;
RegisterForReportsInt[key, s, c, r, other, before]; };
RegisterForReportsInt:
INTERNAL
PROC [key:
ATOM, s:
FinchSmarts.ReportSystemStateProc ←
NIL, c:
FinchSmarts.ReportConversationStateProc ←
NIL, r:
FinchSmarts.ReportRequestStateProc ←
NIL, other:
ATOM←
NIL, before:
BOOL ←
TRUE] = {
lowPos: INT ← 0;
highPos: INT ← LAST[INT]/2;
newPos: INT;
oldPos: INT;
newProcs: FinchSmarts.ReportProcs ←
NEW[FinchSmarts.ReportProcsBody ← [key, 0, s, c, r, TRUE]];
oldProcs: FinchSmarts.ReportProcs ← NARROW[Atom.GetProp[$ReportProcs, key]];
reportProcsTable: RedBlackTree.Table ← NARROW[Atom.GetProp[$ReportProcs, $rpTable]];
IF reportProcsTable=
NIL
THEN {
reportProcsTable ← RedBlackTree.Create[getKey: RPGetKey, compare: RPCompare];
Atom.PutProp[$ReportProcs, $rpTable, reportProcsTable];
};
IF oldProcs#
NIL
THEN {
newProcs.position ← oldProcs.position; oldProcs^ ← newProcs^; RETURN; };
IF other=$all THEN newPos ← IF before THEN lowPos ELSE highPos
ELSE {
neighborProcs: FinchSmarts.ReportProcs;
IF other#
NIL
THEN {
oldProcs ← NARROW[Atom.GetProp[$ReportProcs, other]];
IF oldProcs=NIL THEN ERROR; -- nothing to be before or after
}
ELSE oldProcs ← NARROW[RedBlackTree.LookupNextLarger[reportProcsTable, newProcs]];
oldPos ← IF oldProcs=NIL THEN highPos ELSE oldProcs.position;
newProcs.position ← oldPos;
IF before
THEN {
highPos ← oldPos;
neighborProcs ← NARROW[RedBlackTree.LookupNextSmaller[reportProcsTable,newProcs]];
IF neighborProcs#NIL THEN lowPos ← neighborProcs.position;
}
ELSE {
lowPos ← oldPos;
neighborProcs ← NARROW[RedBlackTree.LookupNextLarger[reportProcsTable,newProcs]];
IF neighborProcs#NIL THEN highPos ← neighborProcs.position;
};
newPos ← (lowPos+highPos)/2;
};
newProcs.position ← newPos;
Atom.PutProp[$ReportProcs, key, newProcs];
RedBlackTree.Insert[reportProcsTable, newProcs, newProcs];
};
UnRegisterForReports:
PUBLIC
ENTRY
PROC [key:
ATOM] = {
ENABLE UNWIND => NULL;
procs: FinchSmarts.ReportProcs ← NARROW[Atom.GetProp[$ReportProcs, key]];
IF procs#NIL THEN procs.enabled ← FALSE;
};
RPGetKey: RedBlackTree.GetKey = { RETURN[data]; };
RPCompare: RedBlackTree.Compare = {
kv: INT ← NARROW[k, FinchSmarts.ReportProcs].position;
dv: INT ← NARROW[data, FinchSmarts.ReportProcs].position;
RETURN[IF kv<dv THEN less ELSE IF kv>dv THEN greater ELSE equal];
};
instanceSuffix: Rope.ROPE ← NARROW[Atom.GetProp[$ReportProcs, $instanceSuffix]];
uniqueSuffix ← IF instanceSuffix=NIL THEN 1 ELSE Convert.CardFromRope[instanceSuffix]+1;
Atom.PutProp[$ReportProcs, $instanceSuffix, Convert.RopeFromCard[uniqueSuffix]];