FinchSmartsImpl.mesa
Last Edited by: Swinehart, November 14, 1983 4:56 pm
DIRECTORY
BasicTime USING [ Now, nullGMT, OutOfRange, Update ],
FinchSmarts,
IO,
Lark USING [ KeyTable, SHHH ],
Log USING [Problem, Report ],
LupineRuntime USING [ BindingError ],
Names USING [ CurrentPasskey, CurrentRName, GetDefaultInstance, GetGVDetails, GVDetails, OwnNetAddress, Results, StartConversation ],
Nice,
Process USING [ Detach, SecondsToTicks, SetTimeout, Ticks ],
Rope,
RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, VersionRange ],
RuntimeError USING [ UNCAUGHT ],
ThParty USING [ Advance, Alert, ConversationsForParty, CreateParty, Deregister, DescribeParty, GetJayParty, GetNumbersForRName, GetParty, GetPartyFromNumber, OtherParty, Register, RegisterKey, ReleaseTrunkParty, SameConvClass, SetInterval ],
ThPartyRpcControl,
Thrush USING[
CallUrgency, ConversationHandle, ConvEvent, Disposition, EncryptionKey, IntervalSpec, IntervalSpecBody, IntSpecType, NB, none, nullConvHandle, nullHandle, nullKey, nullTune, PartyHandle, Reason, ROPE, SmartsHandle, StateID, StateInConv, ThHandle, Tune, unencrypted, VoiceDirection, VoiceInterval ],
ThSmarts,
ThSmartsRpcControl,
ThVersions USING [ GetThrushVR, FinchVersion, FinchVR ],
UserProfile USING [ Token]
;
FinchSmartsImpl: CEDAR MONITOR
IMPORTS BasicTime, IO, Log, LupineRuntime, Names, Nice, Process, RPC, Rope, RuntimeError, ThParty, ThPartyRpcControl, ThSmartsRpcControl, ThVersions, UserProfile
EXPORTS FinchSmarts, ThSmarts = {
OPEN IO;
Declarations
CallUrgency: TYPE = Thrush.CallUrgency;
ConvDesc: TYPE = FinchSmarts.ConvDesc;
ConversationHandle: TYPE = Thrush.ConversationHandle;
nullConvHandle: ConversationHandle = Thrush.nullConvHandle;
Disposition: TYPE = Thrush.Disposition;
FinchInfo: TYPE = FinchSmarts.FinchInfo;
IntervalSpec: TYPE = Thrush.IntervalSpec;
NB: TYPE = Thrush.NB;
none: SHHH = Thrush.none;
nullHandle: Thrush.ThHandle = Thrush.nullHandle;
PartyHandle: TYPE = Thrush.PartyHandle;
Reason: TYPE = Thrush.Reason;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Lark.SHHH;
SmartsHandle: TYPE = Thrush.SmartsHandle;
StateInConv: TYPE = Thrush.StateInConv;
PD: TYPE = RECORD [
cPr: REF Ctr←NEW[Ctr←[0,1000000]],
cSp: REF Ctr←NEW[Ctr←[0,1000000]],
cRs: REF Ctr←NEW[Ctr←[0,1000000]],
cNw: REF Ctr←NEW[Ctr←[0,1000000]],
doReports: BOOLFALSE,
doNice: BOOLFALSE,
timeoutNoAction: INTEGER ← 30,
noActionTicks: Process.Ticks ← Process.SecondsToTicks[30],
timeoutJayConnect: INTEGER ← 1,
noJayTicks: Process.Ticks ← Process.SecondsToTicks[1],
encryptionRequested: BOOLEANTRUE,
interfacesAreImported: BOOLEANFALSE,
smartsIsExported: BOOLEANFALSE,
waitsForConnect: NAT𡤆,
queueIt: BOOLFALSE,
finchOn: BOOLEANFALSE
];
info: FinchInfo;
pd: REF PDNEW[PD←[]];
stopSpec: Thrush.IntervalSpec = NEW[Thrush.IntervalSpecBody←[
interval: [length: 0], direction: play, queueIt: FALSE] ];
Ctr: TYPE = RECORD[
count: INT,
stop: INT
];
Report: PROC[what: ROPE, ctr: REF Ctr] = {
IF NOT pd.doReports THEN RETURN;
Log.Report[what, $Finch];
ctr.count𡤌tr.count+1;
IF ctr.count>ctr.stop THEN { Problem["Report overflow"]; };
};
BeNice: PROC[r: REF, d: INT] = {
IF NOT pd.doNice THEN RETURN;
Nice.BeNice[r, d, $Finch, NIL];
};
Supervision
Progress: PUBLIC ENTRY PROC[
shh: SHHH,
smartsID: Thrush.SmartsHandle,
event: Thrush.ConvEvent,
yourParty: BOOL,
latestEvent: BOOL,
informationOnly: BOOL
] RETURNS [ d: Thrush.Disposition ] = {
cDesc: ConvDesc;
IF info=NIL THEN RETURN[pass];
d←pass;
Update local state information
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["---- FnProg: %g(%d) %g %g yr=%g ",
PutFTime[event.credentials.convID], int[event.credentials.stateID],
refAny[NEW[StateInConv𡤎vent.state]], rope[info.myRName], bool[yourParty]],
IO.PutFR["lt=%g in=%g, ky=%g\n",
bool[latestEvent], bool[event.intervalSpec#NIL], bool[event.keyTable#NIL]]],
pd.cPr];
cDesc ← GetConv[event.credentials.convID, FALSE];
IF event.credentials.stateID <= cDesc.cState.credentials.stateID THEN RETURN[pass]; -- Old news!
Fields always extracted
cDesc.cState.credentials.smartsID ← smartsID;
cDesc.cState.credentials.stateID ← event.credentials.stateID;
<<Need to check for increasing stateID here, and reject old ones? If so, enable
test in alrt case below.>>
Extracted if present
IF event.keyTable#NIL THEN {
cDesc.cState.keyTable ← event.keyTable; cDesc.newKeys←TRUE; };
IF event.intervalSpec#NIL THEN {
thisInterval: Thrush.IntervalSpec ← cDesc.currentRecordIntervalSpec;
thisRec: BOOL=thisInterval#NIL AND event.intervalSpec.intID = thisInterval.intID;
thisType: Thrush.IntSpecType = event.intervalSpec.type;
SELECT TRUE FROM
thisType = request => GOTO Null;
thisType = started AND thisRec => thisInterval^ ← event.intervalSpec^;
thisType = finished AND thisRec => {
thisInterval^ ← event.intervalSpec^; cDesc.currentRecordIntervalSpec←NIL; };
ENDCASE=> cDesc.currentRecordIntervalSpec←NIL;
EnqueueInterval[cDesc, event.intervalSpec];
EXITS Null=>NULL; };
IF event.address#NIL THEN { cDesc.cState.address𡤎vent.address; cDesc.newAddress←TRUE; };
Extracted if the event changes our state.
IF yourParty THEN {
Other: PROC={
oD: ROPE;
IF (~latestEvent) OR cDesc.otherPartyID#nullHandle THEN RETURN;
[,cDesc.otherPartyID, oD, cDesc.conference] ←
ThParty.OtherParty[shhh: info.shh, credentials: cDesc.cState.credentials];
IF oD#NIL THEN cDesc.otherPartyDesc ← oD;
};
cDesc.descValid ← TRUE;
cDesc.cState.credentials.partyID ← event.credentials.partyID;
cDesc.cState.state ← event.state;
cDesc.cState.comment ← event.comment;
cDesc.cState.reason ← event.reason;
IF event.spec#NIL THEN { cDesc.cState.spec ← event.spec; cDesc.newSpec←TRUE; };
Extracted if non-standard?
IF event.comment#NIL THEN cDesc.cState.comment ← event.comment;
IF event.urgency#normal THEN cDesc.cState.urgency ← event.urgency;
IF event.alertKind#standard THEN cDesc.cState.alertKind ← event.alertKind;
SELECT event.state FROM
reserved, parsing => cDesc.originator ← us;
initiating => { cDesc.originator ← us; Other[]; };
pending => { cDesc.originator ← them; Other[]; };
maybe, ringing, active, canActivate, inactive => Other[];
ENDCASE;
};
BeNice[event, 4];
BeNice[cDesc, 6];
cDesc.newEvent←TRUE;
IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process.
};
Supervise: PUBLIC ENTRY PROC[info: FinchInfo ] = {
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
IF info.apprise THEN DO
ENABLE {
UNWIND => NULL;
Specific fatal error condition, if any => GOTO Failing;
RuntimeError.UNCAUGHT => Problem["Unknown Finch Smarts Supervisor Failure"];
When you know what ones occur, catch them, and emulate...
ANY => { Problem["Unknown Finch Smarts Supervisor Failure"]; GOTO Failing; };
};
cDesc: ConvDesc;
prevL: ConvDesc ← NIL;
nb: NB←success;
trans: Transition;
numC, numDead: NAT𡤀
info.apprise ← FALSE;
FOR cDesc ← info.conversations,
cDesc.next WHILE cDesc#NIL DO
stateNow: StateInConv = cDesc.cState.state;
newEvent: BOOL=cDesc.newEvent;
ours: BOOL ← (info.currentConvID = cDesc.cState.credentials.convID);
cDesc.newEvent←FALSE; -- For things that must be done once-only per new state.
IF newEvent OR stateNow#idle THEN {
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["**** FnSup: %g(%d) %g %g->%g",
PutFTime[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID],
rope[info.myRName], refAny[NEW[StateInConv←stateNow]], refAny[NEW[StateInConv�sc.desiredState]]],
IO.PutFR[" %g%g\n",
refAny[NEW[Transition←transForStates[stateNow][cDesc.desiredState]]],
rope[IF ours THEN " (ours)" ELSE ""]]],
pd.cSp];
BeNice[cDesc, 6];
};
IF NOT cDesc.descValid THEN LOOP;
Record whether or not this is now "our" conversation
numC←numC+1;
SELECT stateNow FROM
idle => IF ours THEN info.currentConvID ← nullConvHandle;
parsing, reserved, initiating, maybe, ringing, canActivate, active, inactive => {
ours←TRUE;
info.currentConvID ← cDesc.cState.credentials.convID;
};
pending, any => NULL;
ENDCASE => NULL;
State and substate (below) and desired state indicate there's something to do. Do it:
SELECT (trans←transForStates[stateNow][cDesc.desiredState]) FROM
noop => NULL;
elim => {
IF newEvent THEN info.bluejayConnection ← FALSE ELSE numDead←numDead+1;
cDesc.currentRecordIntervalSpec←NIL;
cDesc.cState.credentials.stateID𡤀 -- In case re-used.
};
alrt => { -- placing call, or reserving conversation
convID: ConversationHandle;
IF NOT ours THEN Problem["What to do in a race?"];
[nb, convID] ← ThParty.Alert [
credentials: cDesc.cState.credentials,
calledPartyID: cDesc.desiredPartyID,
state: initiating,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
IF trans=alrt AND nb#stateMismatch THEN
ThParty.ReleaseTrunkParty[partyID: cDesc.desiredPartyID];
Will release iff attempt to alert failed to get into conversation.
IF nb=success THEN {
cDesc.cState.credentials.convID ← info.currentConvID ← convID;
ours←TRUE;
};
};
idle, actv => -- Simple transitions
nb ← ThParty.Advance [
shhh: info.shh,
credentials: cDesc.cState.credentials,
state: cDesc.desiredState,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
spvs =>
IF cDesc.desireKey THEN {-- Deal with requested intervals.
keyIndex: [0..17B];
[nb, keyIndex] ← ThParty.RegisterKey[
shh: info.shh,
credentials: cDesc.cState.credentials,
key: cDesc.desiredKey
];
IF nb=success THEN {
cDesc.desireKey←FALSE;
IF cDesc.desiredInterval#NIL THEN {
info.apprise←TRUE;
cDesc.desiredInterval.keyIndex ← keyIndex;
};
};
}
ELSE IF cDesc.desiredInterval#NIL AND
cDesc.desiredInterval.intID=0 THEN {
-- Deal with requested intervals.
intID: Thrush.StateID=cDesc.cState.credentials.stateID+1;
nb ← ThParty.SetInterval [
shhh: info.shh,
credentials: cDesc.cState.credentials,
intervalSpec: cDesc.desiredInterval
];
IF nb#success THEN GOTO Null;
IF cDesc.desiredInterval.direction=record THEN {
cDesc.currentRecordIntervalSpec ← cDesc.desiredInterval;
cDesc.currentRecordIntervalSpec.intID ← intID;
}
ELSE cDesc.desiredInterval←NIL;
EXITS Null=>NULL; };
invl => {
Problem["FinchSmarts: Invalid state transition request."];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Invalid state transition";
};
ntiy => {
Problem["FinchSmarts: State transition not yet implemented."];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Unimplemented state transition";
};
ENDCASE => ERROR;
Analyze any results of trying to reach a different state.
IF nb#success THEN
Report[IO.PutFR[" ** Results: nb=%g\n", refAny[NEW[Thrush.NB←nb]]], pd.cRs];
SELECT nb FROM
success, stateMismatch => NULL;
noSuchParty2 => -- Have to get to error tone here. No such party.
Complain[info, cDesc, notFound, "Called party not found"];
narcissism => -- Have to get to error tone here. No such party.
Complain[info, cDesc, notFound, "Attempt to call self rejected on philosophical grounds"];
partyNotEnabled => {
info.ReportConversationState[noSuchSmarts, cDesc, "Your Lark is not registered with the telephone server"];
<< How to disarm the cDesc, which will continue to generate noise?>>
};
invalidTransition, convNotActive, convStillActive => {
Complain and transition to idle
comment: ROPE="Party-level detected invalid state transition request";
Problem[comment];
Complain[info, cDesc, error, comment];
};
notInConv, noSuchConv => { -- Complain, zap, go idle and repeat.
comment: ROPE="NotInConv or NoSuchConv";
Problem[comment];
IF ours THEN info.currentConvID ← nullConvHandle;
cDesc.cState.credentials.convID ← nullConvHandle;
cDesc.cState.credentials.stateID ← 0;
Complain[info, cDesc, error, comment];
};
noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning.
Problem["NoSuchParty or NoSuchSmarts reported, must try to go away"];
GOTO Failing;
};
ENDCASE => ERROR;
Set up switching and tones to match state.
IF newEvent THEN info.ReportConversationState[success, cDesc, NIL];
cDesc.newIntervals ← NIL; -- in case any were reported.
prevL ← cDesc;
ENDLOOP;
IF info.conversations = NIL OR numC=numDead THEN EXIT;
IF NOT info.apprise THEN WAIT info.thAction;
IF info.conversations = NIL THEN EXIT;
REPEAT Failing => UninitFinchSmarts[]; -- now Failed
ENDLOOP;
info.thProcess ← NIL;
};
Client Functions
GetHistory: PUBLIC PROC[ convID: Thrush.ConversationHandle, toState: INT𡤀 -- i.e., all-- ]
RETURNS [ s: Thrush.EventSequence ] = {
s←ThParty.GetHistory[shhh: info.shh, convID: convID, firstState: 1, lastState: toState];
};
GetRname: PUBLIC PROC[partyID: Thrush.PartyHandle] RETURNS [rName: ROPE] = {
RETURN[ThParty.DescribeParty[shh: info.shh, partyID: partyID]];
};
DisconnectCall: PUBLIC ENTRY PROC[
convID: Thrush.ConversationHandle, -- not used yet
reason: Thrush.Reason←terminating,
comment: ROPENIL] = {
cDesc: ConvDesc; state: StateInConv;
IF NOT(([,cDesc, state]𡤏inchOn[]).on) THEN RETURN;
info.bluejayConnection ← FALSE;
SELECT state FROM
idle, any => {
info.ReportConversationState[convNotActive, NIL, "No conversation to disconnect"];
RETURN;
};
ringing, pending => IF reason=terminating THEN reason ← busy;
ENDCASE;
Request[info, cDesc, idle, reason, comment];
};
AnswerCall: PUBLIC ENTRY PROC[convID: Thrush.ConversationHandle -- not used yet -- ] = {
cDesc: ConvDesc; state: StateInConv;
IF NOT(([,cDesc, state]𡤏inchOn[]).on) THEN RETURN;
SELECT state FROM
ringing, pending => NULL;
IF convID#info.currentConvID THEN {
info.ReportConversationState[noSuchConv, NIL, "Can't answer that call"]; RETURN; };
ENDCASE=>RETURN;
Request[info, cDesc, active];
};
PlaceCall: PUBLIC ENTRY PROC [
convID: Thrush.ConversationHandle, -- not used yet --
rName: ROPE, -- rName or description
number: ROPE, -- telephone number, if present
urgency: Thrush.CallUrgency←normal,
useNumber: BOOLFALSE] = {
ENABLE { UNWIND=>NULL; RPC.CallFailed => { UninitFinchSmarts[]; CONTINUE; }; };
Get fone for dest
calledPartyID: PartyHandle;
fullRName: ROPE;
IF NOT (FinchOn[].on) THEN RETURN;
calledPartyID ← SELECT TRUE FROM
~useNumber AND
(calledPartyID ← ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: rName]) #nullHandle =>
calledPartyID,
number#NIL =>
ThParty.GetPartyFromNumber[shh: info.shh, partyID: info.partyID, phoneNumber: number, description: rName, trunkOK: TRUE],
rName=NIL OR useNumber => nullHandle,
([fullRName, number,] ← ThParty.GetNumbersForRName[shh: info.shh, rName: rName]).number # NIL =>
ThParty.GetPartyFromNumber[
shh: info.shh, partyID: info.partyID, phoneNumber: number,
description: fullRName, trunkOK: TRUE],
ENDCASE => nullHandle;
IF calledPartyID = nullHandle THEN {
info.ReportConversationState[noSuchParty2, NIL,
IF rName=NIL THEN "You did not supply a call destination"
ELSE "No telephone number could be found for called party"];
RETURN;
};
[]𡤌onnect[calledPartyID, FALSE, urgency];
};
RecordTune: PUBLIC ENTRY PROC [
useTune: Thrush.Tune,
useInterval: Thrush.VoiceInterval
]
RETURNS[
reason: FinchSmarts.RecordReason←hopeless,
tune: Thrush.Tune←Thrush.nullTune,
interval: Thrush.VoiceInterval←[],
key: Thrush.EncryptionKey←Thrush.nullKey
] = {
ENABLE UNWIND=>NULL;
inst: ThMessageDB.MessageInstance = ThMessageDB.GetMessage["Beep.Lark", 1];
Out of luck, for a while . . . no way to talk about it here!!!!
IF inst#NIL THEN PlBaMe[tune, key: extract from equiv. of inst.];
<<If useTune#newTune, probably won't work -- no key. Nuthatch will solve.>>
<<<Won't work (key troubles) if new tunes are supplied. Vestigial>>>
ourSpec: Thrush.IntervalSpec;
cDesc: ConvDesc; state: StateInConv;
started: BOOLFALSE;
IF NOT (([,cDesc,state]←JayConnection[TRUE]).jayOpen) THEN RETURN;
That's all, folks . . . for now.
ourSpec ← NEW[Thrush.IntervalSpecBody ← [
type: request, direction: record, queueIt: pd.queueIt -- inst#NIL--,
tune: useTune, interval: useInterval, keyIndex: 1
]];
Wait for recording to (fail to) begin.
TRUSTED { Process.SetTimeout[@info.thAction, pd.noJayTicks]; };
FOR i: NAT IN [0..pd.waitsForConnect) DO
IF ourSpec#cDesc.desiredInterval THEN
Wait for previous request to be initiated
IF cDesc.desiredInterval#NIL THEN { WAIT info.thAction; LOOP; }
ELSE { cDesc.desiredInterval←ourSpec; Apprise[info]; };
WAIT info.thAction; -- <<The supervisor condition vbl; will this work?>>
state�sc.cState.state;
IF state=idle OR (started←(ourSpec.type#request)) THEN EXIT;
ENDLOOP;
cDesc.desiredInterval ← NIL;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
IF ~started OR cDesc.cState.keyTable=NIL THEN {
Recording didn't start or can't be decrypted; return with error value.
IF state#idle THEN Complain[info, cDesc, error, "Voice server connection failed"];
RETURN[hopeless, Thrush.nullTune, , ];
};
Wait for recording or conversation to end.
WHILE cDesc.cState.state#idle AND cDesc.currentRecordIntervalSpec=ourSpec DO
WAIT info.thAction; -- <<the supervisor condition vbl; will this work?>>
ENDLOOP;
RETURN[ok, ourSpec.tune, ourSpec.interval, cDesc.cState.keyTable[ourSpec.keyIndex]];
If state is idle, possibly the interval doesn't know its actual duration.
};
StopTune: PUBLIC ENTRY PROC [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
IF NOT (([,cDesc,]←JayConnection[FALSE]).jayOpen) THEN RETURN; -- Stop only if started
cDesc.desiredInterval ← stopSpec;
Apprise[info];
};
PlaybackTune: PUBLIC ENTRY PROC [
tune: Thrush.Tune,
interval: Thrush.VoiceInterval,
key: Thrush.EncryptionKey,
queueIt: BOOLFALSE
] = {
ENABLE UNWIND=>NULL;
PlBaMe[tune, interval, key, queueIt];
};
PlBaMe: INTERNAL PROC [
tune: Thrush.Tune,
interval: Thrush.VoiceInterval,
key: Thrush.EncryptionKey,
queueIt: BOOL�LSE]= {
cDesc: ConvDesc; state: StateInConv;
IF tune<=Thrush.nullTune THEN {
info.ReportConversationState[convNotActive, NIL, "No such tune"]; RETURN; }; -- UGH!
IF NOT (([,cDesc,state]←JayConnection[TRUE]).jayOpen) THEN RETURN;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noJayTicks]; };
FOR i: NAT IN [0..pd.waitsForConnect) WHILE cDesc.desiredInterval#NIL DO
WAIT info.thAction;
ENDLOOP;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
IF cDesc.desiredInterval#NIL THEN RETURN;
cDesc.desiredKey ← key;
cDesc.desireKey ← TRUE;
cDesc.desiredInterval ← NEW[Thrush.IntervalSpecBody ← [
type: request, direction: play, queueIt: queueIt,
tune: tune, interval: interval, keyIndex: 0 ] ]; -- will be filled in by Key registration
Apprise[info];
};
Registration
InitFinchSmarts: PUBLIC PROC [
ReportSystemState: PROC[ on: BOOL ],
ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ]
] = {
ENABLE RPC.CallFailed => GOTO InitFailed;
partyID: Thrush.PartyHandle;
smartsID: Thrush.SmartsHandle;
details: Names.GVDetails;
results: Names.Results;
thVR: RPC.VersionRange = ThVersions.GetThrushVR;
UninitFinchSmarts[];
info ← NEW[FinchSmarts.FinchInfoBody];
info.ReportSystemState←ReportSystemState;
info.ReportConversationState←ReportConversationState;
info.myName ← [
type: "ThSmarts.Lark",
instance: Names.GetDefaultInstance[netAddress: Names.OwnNetAddress[]],
version: ThVersions.FinchVR];
info.myRName ← Names.CurrentRName[];
info.myPassword ← Names.CurrentPasskey[];
info.serverInstanceName ← UserProfile.Token[key: "ThrushClientServerInstance",
default: "Strowger.Lark"];
ThSmartsRpcControl.ExportInterface[
interfaceName: info.myName,
user: info.myRName,
password: info.myPassword];
pd.smartsIsExported←TRUE;
info.shh ← IF NOT pd.encryptionRequested THEN Thrush.unencrypted
ELSE Names.StartConversation [
caller: info.myRName,
callee: info.serverInstanceName,
key: info.myPassword,
level: --<<ECB>>--CBCCheck !
RPC.AuthenticateFailed=> GOTO InitFailed];
Implement the GV lookup portion of importing the Thrush instance explicitly, in order to get the benefit of the local Grapevine cache.
[results, details] ← Names.GetGVDetails[rName: info.serverInstanceName, mode: ok];
IF results#ok OR ~details.valid THEN GOTO InitFailed;
info.serverInstance ← details.connect;
ThPartyRpcControl.ImportInterface[
interfaceName: [type: "ThParty.Lark", instance: info.serverInstance, version: thVR] ! --
RPC.ImportFailed=> {
IF why=wrongVersion THEN
Log.Report[IO.PutFR["Finch version %d too old; import failed",
card[ThVersions.FinchVersion]], $Finch];
GOTO InitFailed;
}];
pd.interfacesAreImported←TRUE;
partyID←ThParty.CreateParty[shh: info.shh, type: individual, rName: info.myRName];
IF partyID=Thrush.nullHandle THEN GOTO InitFailed;
smartsID←ThParty.Register[
shh: info.shh, partyID: partyID, interface: NEW[ThSmartsRpcControl.InterfaceName←info.myName],
properties: [x: manager[machine: Names.OwnNetAddress[]]]] ;
IF smartsID=Thrush.nullHandle THEN GOTO InitFailed; -- <<try to delete the Party?>>
info.smartsID ← smartsID;
info.partyID ← partyID;
ThParty.ConversationsForParty[shh: info.shh, partyID: partyID];
Will shortly update our knowledge of what's already going on!!
pd.finchOn←TRUE;
info.ReportSystemState[pd.finchOn];
EXITS
InitFailed => UninitFinchSmarts[];
};
UninitFinchSmarts: PUBLIC PROC[problem: BOOLFALSE] = {
<<Must eliminate participation in any conversation that only we know enough about to get rid of, and eliminate any evidence of our partipation in any others. For now, do only the latter thing>>
ENABLE RPC.CallFailed => GOTO Failed;
IF problem THEN Problem[];
IF info#NIL THEN {
IF pd.finchOn AND info.partyID#nullHandle AND info.smartsID#nullHandle THEN
ThParty.Deregister[info.shh, info.smartsID!RPC.CallFailed=>CONTINUE];
info.shh←none;
info.ReportSystemState[FALSE];
WHILE info.conversations#NIL DO -- a bug?!
cDesc: ConvDesc=info.conversations;
info.conversations ← cDesc.next;
cDesc.next�sc.prev←NIL;
cDesc.clientData←NIL;
ENDLOOP;
};
pd.finchOn←FALSE;
info←NIL;
IF pd.interfacesAreImported THEN
ThPartyRpcControl.UnimportInterface[!LupineRuntime.BindingError=>CONTINUE];
pd.interfacesAreImported←FALSE;
IF pd.smartsIsExported THEN
ThSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE];
pd.smartsIsExported←FALSE;
EXITS
Failed => pd.interfacesAreImported ← pd.smartsIsExported ← FALSE;
};
FinchIsRunning: PUBLIC PROC RETURNS [finchIsRunning: BOOL] = { RETURN[pd.finchOn]; };
Utilities
GetConv: PUBLIC INTERNAL PROC[convID: ConversationHandle, validIfNew: BOOL
] RETURNS [ cDesc: ConvDesc←NIL ] = --INLINE-- {
pDesc: ConvDesc←NIL;
IF convID#nullConvHandle THEN
FOR cDesc ← info.conversations, cDesc.next WHILE cDesc#NIL DO
descID: ConversationHandle = cDesc.cState.credentials.convID;
IF descID = convID THEN RETURN;
IF cDesc.cState.state=idle AND ThParty.SameConvClass[convID, descID] THEN EXIT;
pDesc�sc; ENDLOOP;
IF cDesc=NIL THEN { -- If not, we're reusing a related entry.
cDesc ← NEW[FinchSmarts.ConvDescBody←[]];
cDesc.prev ← pDesc;
IF pDesc#NIL THEN pDesc.next ← cDesc ELSE info.conversations ← cDesc;
cDesc.otherPartyDesc ← "unknown party";
};
cDesc.descValid ← validIfNew;
cDesc.startTime ← BasicTime.Now[];
cDesc.desiredState ← any;
cDesc.desiredPartyID ← nullHandle;
cDesc.desiredReason ← wontSay;
cDesc.desiredComment←NIL;
cDesc.desiredInterval ← NIL;
cDesc.desireKey←FALSE;
cDesc.conference← cDesc.completed ← cDesc.failed ← FALSE;
Other fields are intentionally retained.
cDesc.cState.credentials ← [
convID: convID, smartsID: info.smartsID, partyID: info.partyID, stateID: 0];
IF pd.doReports THEN
Report[IO.PutFR[" ** NewConv %g %g, vl=%g\n", PutFTime[convID], rope[info.myRName], bool[validIfNew]], pd.cNw];
};
PutFTime: PROC[convID: ConversationHandle] RETURNS [IO.Value] = {
ehsMemorialBool: BOOLFALSE; -- wouldn't need if could return from catch phrase.
IF convID=nullConvHandle OR convID=BasicTime.nullGMT
THEN RETURN[rope["(not assigned)"]];
[]�sicTime.Update[convID, 0!BasicTime.OutOfRange=> {
ehsMemorialBool←TRUE; CONTINUE; }];
IF ehsMemorialBool THEN RETURN[int[LOOPHOLE[convID, INT]]]
ELSE RETURN[time[convID]];
};
GetConvDesc: PUBLIC PROC[convID: ConversationHandle]RETURNS[cDesc: ConvDesc←NIL ] = {
Not at present protected by monitor -- FinchToolImpl back-pointer problem.
FOR cDesc ← info.conversations, cDesc.next WHILE cDesc#NIL DO
IF cDesc.cState.credentials.convID = convID THEN EXIT; ENDLOOP;
RETURN[IF cDesc#NIL AND cDesc.descValid THEN cDesc ELSE NIL];
};
GetCDesc: PROC[] RETURNS [cDesc: ConvDesc←NIL ] = {
convID: ConversationHandle=info.currentConvID;
IF convID=nullConvHandle THEN RETURN;
RETURN[GetConvDesc[convID]];
};
Apprise: PUBLIC INTERNAL PROC[info: FinchInfo] = --INLINE-- {
IF info.thProcess=NIL THEN
TRUSTED { Process.Detach[info.thProcess ← FORK Supervise[info]]; };
info.apprise ← TRUE;
BROADCAST info.thAction;
};
EnqueueInterval: INTERNAL PROC[cDesc: ConvDesc, int: IntervalSpec] = INLINE {
iL: LIST OF IntervalSpec = LIST[int];
IF cDesc.newIntervals#NIL THEN cDesc.iTail.rest ← iL ELSE cDesc.newIntervals ← iL;
cDesc.iTail ← iL;
};
DequeueInterval: PUBLIC INTERNAL PROC[cDesc: ConvDesc] RETURNS [ int: IntervalSpec←NIL ] = {
IF cDesc.newIntervals=NIL THEN RETURN;
int�sc.newIntervals.first;
cDesc.newIntervals ← cDesc.newIntervals.rest;
};
Complain: INTERNAL PROC[info: FinchInfo, cDesc: ConvDesc, reason: Reason←wontSay, comment: ROPENIL] = {
cDesc.desiredState ← idle;
cDesc.desiredReason ← reason;
cDesc.desiredComment ← comment;
cDesc.desiredPartyID ← nullHandle;
cDesc.desiredInterval ← NIL;
Apprise[info]; -- BROADCAST is sometimes meaningless (running monitor.)
};
FinchOn: INTERNAL PROC RETURNS [
on: BOOLFALSE, cDesc: ConvDesc←NIL, state: StateInConv←idle] = {
IF ~pd.finchOn THEN {
Log.Report["Your Finch is not running", $Finch]; RETURN; };
on←TRUE;
cDesc ← GetCDesc[];
state←IF cDesc=NIL THEN idle ELSE cDesc.cState.state;
};
Request: INTERNAL PROC[
info: FinchInfo,
cDesc: ConvDesc,
state: StateInConv,
reason: Reason←wontSay,
comment: ROPENIL
] = {
cDesc.desiredState ← state;
cDesc.desiredReason ← reason;
cDesc.desiredComment ← comment;
Apprise[info];
};
Connect: INTERNAL PROC [
calledPartyID: PartyHandle, bluejayConnection: BOOL, urgency: Thrush.CallUrgency]
RETURNS [ cDesc: ConvDesc ] = {
state: StateInConv;
[,cDesc, state]𡤏inchOn[];
SELECT state FROM
idle => { cDesc ← GetConv[nullConvHandle, TRUE]; cDesc.cState.state𡤊ny; };
reserved => NULL;
ENDCASE => {
ThParty.ReleaseTrunkParty[shh: info.shh, partyID: calledPartyID];
info.ReportConversationState[convStillActive, NIL, "Conversation already in progress"];
RETURN;
};
info.bluejayConnection ← bluejayConnection;
cDesc.desiredPartyID ← calledPartyID;
cDesc.desiredInterval ← NIL;
Request[info, cDesc, active];
};
JayConnection: INTERNAL PROC[newConn: BOOLTRUE] RETURNS [
jayOpen: BOOL�LSE,
cDesc: ConvDesc←NIL,
state: StateInConv←idle
] = {
calledPartyID: PartyHandle;
IF NOT (([,cDesc, state]𡤏inchOn[]).on) THEN {
jayOpen←RepRet[FALSE, noSuchParty, "Your Finch is not running"]; RETURN; };
SELECT state FROM
idle, reserved => IF newConn THEN {
calledPartyID ← ThParty.GetJayParty[shh: info.shh, partyID: info.partyID];
IF calledPartyID = Thrush.nullHandle THEN RETURN--[FALSE]--;
cDesc ← Connect[calledPartyID, TRUE, normal];
};
ENDCASE;
jayOpen←RepRet[info.bluejayConnection, noSuchConv, "Connection to voice server failed"];
};
RepRet: PROC[bool: BOOL, nb: NB, remark: Rope.ROPE]
RETURNS[sameBool: BOOL] = {
IF bool=FALSE THEN
IF info#NIL THEN info.ReportConversationState[nb, NIL, remark]
ELSE Log.Report[remark, $Finch];
sameBool𡤋ool;
};
Problem: PROC[remark: ROPE←NIL] = { Log.Problem[remark, $Finch]; };
State Transition Tables
Transition: TYPE = {
Just codes to dispatch on in Supervisor; explained there
noop, elim, idle, alrt, actv, spvs, invl, ntiy
};
transForStates: ARRAY StateInConv OF ARRAY StateInConv OF Transition ← [
idle resrv pars init pend mayb ring canAc activ inact any -- desired
[ elim, invl, invl, invl, invl, invl, invl, invl, elim, invl, elim ], -- idle  (current)
[ idle, invl, invl, invl, invl, invl, invl, invl, alrt, ntiy, noop ], -- reserved
[ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, noop ], -- parsing
[ idle, invl, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- initiating
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- pending
[ idle, invl, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- maybe
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- ringing
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, ntiy, ntiy ], -- canActivate
[ idle, invl, invl, invl, invl, invl, invl, invl, spvs, ntiy, spvs ], -- active
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, invl, ntiy ], -- inactive
[ elim, invl, invl, invl, invl, invl, invl, invl, alrt, ntiy, invl ] -- any (nonex)
];
NB: examine relationship to validity table in PartyOpsImpl someday.
Debugging nonsense
Nice.View[pd, "Finch PD"];
}.