NotYet.mesa
Swinehart, December 8, 1985 4:20:32 pm PST
Here's some incomplete descriptions of how creation and registration work now:
Registering an Etherphone
LarkSmarts.Register[netAddress, authenticated, netAddressAsRope] => smartsHandle:
Obtain RName from caller ident in RPC call (!)
We now know both the machine identification and the assumed RName.
Unregister if registered
party𡤌reateParty[individual or service, RName]:
Muck with RNames, since they're a mess
smarts←ThParty.RegisterLocal[party, ..., netAddress]:
Make a smartsInfo and put a bunch of autoAnswer, radio, textToSpeech bits in it
$SmartsData[smarts]=smartsInfo
ThSmartsPrivate.RegisterTrunk[ party, smarts, info]:
Create the trunk party (always NEW!)
$TrunkParty[party]=trunkParty
Make trunkSmarts, trunkSmartsInfo, $SmartsData[trunkSmarts]=trunkSmartsInfo
RegisterLocal the trunk smarts
Enable if on-hook
From SupImpl.progress
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["---- LkProg: %t(%d) %g %g yr=%g ",
time[event.credentials.convID], int[event.credentials.stateID],
refAny[NEW[StateInConv𡤎vent.state]], TU.RefAddr[info], bool[yourParty]],
IO.PutFR["lt=%g in=%g, ky=%g\n",
bool[latestEvent], bool[event.intervalSpecs#NIL OR event.proseSpecs#NIL], bool[event.keyTable#NIL]]],
info];
More of same
Update local state information
cDesc ← GetConv[info, 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.intervalSpecs#NIL THEN
May accept more later.
EnqueueIntervals[cDesc, event.intervalSpecs];
IF event.proseSpecs#NIL THEN {
req: BOOLFALSE;
FOR pSL: ProseSpecs ← event.proseSpecs, pSL.rest WHILE pSL#NIL DO
IF pSL.first.type=request THEN {
req ← TRUE;
IF ~pSL.first.queueIt AND info.larkInfo.textToSpeech THEN {
Flush proseSpecs that haven't been sent yet. It doesn't matter if we remove started and finished reports here; the server doesn't care about those anyway. But what about REFs? Will no one else get to see these either?
event.proseSpecs ← pSL;
cDesc.newProses ← NIL;
};
};
ENDLOOP;
IF req OR ~info.larkInfo.textToSpeech THEN
EnqueueProses[cDesc, event.proseSpecs];
};
IF event.address#NIL THEN { cDesc.cState.address𡤎vent.address; cDesc.newAddress←TRUE; };
Extracted if the event changes our state.
IF yourParty THEN {
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;
<< When Conferencing is added, put this back in.>>
cDesc.cState.conferenceHost ← event.conferenceHost;
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, initiating => cDesc.originator ← us;
maybe => {
cDesc.originator ← us;
Yucchh! Progress is shared between Trunk and Lark smarts. Want to do it only in the Lark case. This is the only way to tell, at present!
IF info.Supervise = LarkSupervise THEN info.larkInfo.ringTune ← event.ringTune;
};
pending => cDesc.originator ← them;
ringing => { cDesc.originator ← them; info.larkInfo.ringTune ← event.ringTune; };
ENDCASE;
};
BeNice[event, 4, info];
BeNice[cDesc, 6, info];
cDesc.newEvent ← TRUE;
IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process.
};
Supervisor, other functions from LarkSmartsSupImpl
EnqueueIntervals: INTERNAL PROC[cDesc: ConvDesc, int: IntervalSpecs] = INLINE {
IF cDesc.newIntervals#NIL THEN cDesc.iTail.rest ← int ELSE cDesc.newIntervals ← int;
FOR iL: IntervalSpecs ← int, iL.rest WHILE iL#NIL DO cDesc.iTail ← iL; ENDLOOP;
};
EnqueueProses: INTERNAL PROC[cDesc: ConvDesc, pro: ProseSpecs] = {
FOR pL: ProseSpecs ← pro, pL.rest WHILE pL#NIL DO
Copies, so that changes can be made in order to report back to ThParty.
newSpec: ProseSpecs ← LIST[NEW[Thrush.ProseSpecBody ← pL.first^]];
IF cDesc.newProses#NIL THEN cDesc.pTail.rest ← newSpec
ELSE cDesc.newProses ← newSpec;
cDesc.pTail ← newSpec;
ENDLOOP;
};
ReportProseDone: PUBLIC ENTRY PROC[info: SmartsInfo, proseSpec: Thrush.ProseSpec] = {
cDesc: ConvDesc ← GetConv[info, info.currentConvID, FALSE];
proseSpec.type ← finished; -- Check this one!
EnqueueProses[cDesc, LIST[proseSpec]];
cDesc.pTail.first.type ← finished; -- make sure this is okay!
Apprise[info];
};
Old Supervisor procedure
Apprise: PUBLIC INTERNAL PROC[info: SmartsInfo] = TRUSTED --INLINE-- {
IF info.thProcess=NIL THEN
Process.Detach[info.thProcess ← FORK info.Supervise[info]];
info.apprise ← TRUE;
NOTIFY info.thAction;
};
LarkSupervise: PUBLIC ENTRY PROC[info: SmartsInfo ] = {
TRUSTED {
Process.SetTimeout[@info.thAction, Process.SecondsToTicks[pd.timeoutNoAction]]; };
IF info.apprise THEN DO
ENABLE {
UNWIND => NULL;
ThSmartsPrivate.LarkFailed => GOTO Failing;
};
prevL: OpenConversations ← NIL;
nb: NB←success;
convL: OpenConversations;
trans: Transition;
info.apprise ← FALSE;
FOR convL ← info.conversations,
convL.rest WHILE convL#NIL DO
cDesc: ConvDesc = convL.first;
stateNow: StateInConv = cDesc.cState.state;
ours: BOOL ← ( cDesc.cState.credentials.convID = info.currentConvID );
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["**** LkSup: %t(%d) %g %g->%g",
time[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID],
TU.RefAddr[info], 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 ""]]],
info];
BeNice[cDesc, 6, info];
IF NOT cDesc.descValid THEN LOOP;
IF info.currentConvID = nullConvID --AND stateNow#idle-- THEN {
<<This stuff feels like it's in the wrong place. Will feel more so when multi-calls
are dealt with. >>
ours←TRUE; info.currentConvID ← cDesc.cState.credentials.convID;
};
IF stateNow=idle THEN {
Conv. is now idle: forget about it or re-use it, sort of.
Remove dead conversation from list.
IF pd.doReports THEN
ReportFR[" ** Zap %t\n", info, time[cDesc.cState.credentials.convID]];
IF prevL#NIL THEN {
prevL.rest ← convL.rest; convL ← prevL; }
ELSE info.conversations𡤌onvL.rest;
IF ours THEN {
reason: Thrush.Reason = cDesc.cState.reason;
newConvID: ConversationID�sc.cState.credentials.convID;
newDesc: ConvDesc;
info.currentConvID ← nullConvID;
SELECT info.larkInfo.hookState FROM
onhook, spkr =>
SELECT reason FROM
terminating, wontSay => GOTO Exit;
ENDCASE => IF cDesc.originator#us THEN GOTO Exit;
ENDCASE =>
SELECT TRUE FROM
reason=terminating, reason=wontSay, cDesc.originator#us => {
Set to look like reserving brand new conversation
newConvID ← nullConvID;
info.currentConvID ← nullConvID;
};
ENDCASE;
newDesc ← GetConv[info, newConvID, TRUE];
ChangeState[info, newDesc, reserved, cDesc.cState.reason, cDesc.cState.comment];
-- Now go around the loop once, if only to coerce Lark into idle state.
EXITS Exit => NULL;
};
}
ELSE IF NOT ours THEN
Conv isn't the one we're interested in, and doesn't look like it's going idle: idle it.
Should probably consult transForStates for validity.
nb ← ThParty.Advance[
credentials: cDesc.cState.credentials,
state: idle,
reason: busy,
comment: "One conversation at a time, please."
]
ELSE {
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 => ERROR; -- handled in stateNow=idle case above.
rsrv, rers, prsg, alrt => { -- placing call, or reserving conversation
convID: ConversationID;
calledPartyID: PartyID =
IF trans#alrt THEN nullID ELSE cDesc.desiredPartyID;
nb ← IF trans=alrt AND calledPartyID=nullID
THEN noSuchParty2 ELSE success;
IF NOT ours THEN Problem["What to do in a race?", info];
IF nb=success THEN [nb, convID] ← ThParty.Alert [
credentials: cDesc.cState.credentials,
calledPartyID: calledPartyID,
state: IF trans = alrt THEN initiating ELSE cDesc.desiredState,
reason: cDesc.desiredReason,
newConv: trans=rsrv,
comment: cDesc.desiredComment
];
IF nb=success THEN {
cDesc.cState.credentials.convID ← info.currentConvID ← convID;
ours←TRUE;
};
};
idle, actv => -- Simple transitions
nb ← ThParty.Advance [
credentials: cDesc.cState.credentials,
state: cDesc.desiredState,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
spvs => { -- Supervision while active.
cDesc.desiredState�tive; -- Reflect reality
FOR iL: IntervalSpecs ← cDesc.newIntervals, iL.rest WHILE iL#NIL DO
May do more later. For now, record the intervals that start; when you see the last one that started end, it's time to shut down the connection.
iS: Thrush.IntervalSpec = iL.first;
IF iS.interval.length#0 OR iS.queueIt THEN -- else flush: ignore
SELECT iS.type FROM
started => cDesc.desiredIntID ← iS.intID;
finished =>
IF iL.rest=NIL AND iS.intID = cDesc.desiredIntID THEN
SELECT info.larkInfo.hookState FROM
spkr => ChangeState[info, cDesc, idle]; ENDCASE;
ENDCASE;
ENDLOOP;
cDesc.newIntervals ← NIL;
EnterLarkState, below, may cause new keys to be distributed.
When voice messages again supported, will need to initiate intervals here.
IF cDesc.newProses#NIL AND info.larkInfo.textToSpeech THEN {
copyNewProses: ProseSpecs ← cDesc.newProses;
cDesc.newProses ← NIL;
EnqueueProses[cDesc, copyNewProses];
Make another copy of the newProses -- this copy will belong only to the Lark (but it may include started and finished reports, which the Lark must ignore). We can then smash the prose ROPE of the original newProses inside the loop below.
ThSmartsPrivate.EnterLarkState[
info.larkInfo, LarkStateForState[cDesc, stateNow, info.larkInfo.larkState], info];
cDesc.newProses ← copyNewProses;
};
FOR pL: ProseSpecs ← cDesc.newProses, pL.rest WHILE pL#NIL DO
May do more later. For now, record the requests that start; when you see the last one that started end, it's time to shut down the connection.
pS: Thrush.ProseSpec = pL.first;
SELECT pS.type FROM
request => IF info.larkInfo.textToSpeech THEN {
pS.type ← started;
pS.prose ← "";
Avoid sending long RPC packets back to acknowledge things
};
started => cDesc.desiredProseID ← pS.intID;
finished =>
IF ~info.larkInfo.textToSpeech AND pL.rest=NIL AND pS.intID = cDesc.desiredProseID THEN
SELECT info.larkInfo.hookState FROM
spkr => ChangeState[info, cDesc, idle];
ENDCASE;
ENDCASE;
ENDLOOP;
IF cDesc.newProses#NIL AND info.larkInfo.textToSpeech THEN {
nb ← ThParty.SetProse[
shhh: info.larkInfo.shh,
credentials: cDesc.cState.credentials,
proseSpecs: cDesc.newProses
];
IF nb=success THEN cDesc.newProses←NIL;
}
ELSE
cDesc.newProses ← NIL;
};
EnterLarkState, below, may cause new keys to be distributed.
When voice messages again supported, will need to initiate intervals here.
ring => -- This is how you say you'll ring an incoming call
IF info.larkInfo.autoAnswer THEN {
info.apprise←TRUE;
cDesc.desiredState ← active;
}
ELSE nb ← ThParty.Advance [
credentials: cDesc.cState.credentials,
state: ringing
];
invl => {
comment: ROPE="Invalid state transition";
Problem[comment, info];
ChangeState[info, cDesc, idle, error, comment];
};
ntiy => {
comment: ROPE= "Unimplemented state transition";
Problem[comment, info];
ChangeState[info, cDesc, idle, error, comment];
};
ENDCASE => ERROR;
};
Analyze any results of trying to reach a different state.
IF nb#success THEN
ReportFR[" ** Results: nb=%g\n", info, refAny[NEW[Thrush.NB←nb]]];
SELECT nb FROM
success, stateMismatch => NULL;
noSuchParty2, narcissism => -- Have to get to error tone here. No such party.
ChangeState[info, cDesc, idle, notFound, "Called party not found, or calling self"];
partyNotEnabled => ours←FALSE;
invalidTransition, convNotActive, convStillActive => {
Complain and transition to idle
comment: ROPE="Party-level detected invalid state transition request";
Problem[comment, info];
ChangeState[info, cDesc, idle, error, comment];
};
notInConv, noSuchConv => { -- Complain, zap, go idle and repeat.
comment: ROPE="NotInConv or NoSuchConv";
Problem[comment, info];
IF ours THEN info.currentConvID ← nullConvID;
cDesc.cState.credentials.convID ← nullConvID;
cDesc.cState.credentials.stateID ← 0;
ChangeState[info, cDesc, idle, error, comment];
};
noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning.
Problem[
"LkSmts: NoSuchParty or NoSuchSmarts reported, must try to go away", info];
GOTO Failing;
};
ENDCASE => ERROR;
Set up switching and tones to match state.
IF ours THEN ThSmartsPrivate.EnterLarkState[
info.larkInfo, LarkStateForState[cDesc, stateNow, info.larkInfo.larkState], info];
prevL ← convL;
ENDLOOP;
IF NOT info.apprise THEN WAIT info.thAction;
IF info.conversations = NIL THEN EXIT;
REPEAT Failing => ThSmartsPrivate.Deregister[info]; -- now Failed
ENDLOOP;
info.thProcess ← NIL;
};
DoGoIdle: INTERNAL PROC[
cDesc: ConvDesc,
reason: Thrush.Reason←$terminating, comment: Rope.ROPENIL,
conditions: { $unconditional, $orphan } ← $orphan] = {
Terminate involvement in conversation.
nb: NB;
SELECT conditions FROM
$unconditional => NULL;
$orphan => {
nb: NB;
cInfo: ThParty.ConversationInfo;
[nb, cInfo] ← ThParty.GetConversationInfo[credentials];
IF nb = $success AND
(cInfo.numParties-cInfo.numIdle) > 1 THEN RETURN; -- We're not alone
IF reason # NIL THEN {
reason ← $error; comment ← "System Error: Inconsistent information";
Report["Inconsistent information", info];
};
};
ENDCASE => ERROR;
ChangeState[cDesc, $idle, reason, comment];
};
From ThPartyPrivate:
RingMethod: TYPE = ATOM; -- { $standard=NIL, $ownTune, $bothTunes };
From ThPartyPrivate.PartyBody:
Fields used by parties representing telephones, individuals, and some services
ringEnable: Thrush.RingEnable ← $on, -- are we noisily accepting calls?
defaultRingEnable: Thrush.RingEnable ← $on, -- are we noisily accepting calls?
ringDo: RingMethod ← NIL, -- ($standard) we use the ring tune?
ringTime: BasicTime.GMTNULL, -- if not, when again?
ringTune: LarkPlay.ToneSpec, -- Specification of ring tune
ringTuneRope: Thrush.ROPENIL,
From ThPartyInitImpl:
GetStdRingInfo: PUBLIC PROC [partyID: PartyID] = {
party: PartyData←ThPartyPrivate.DehandleParty[partyID];
larkRname: ROPE;
rdAtom: ATOM;
IF party=NIL THEN RETURN;
SELECT party.type FROM
$individual, $telephone => {
larkRname ← GetRnameFromParty[party].Concat[larkRegistry];
IF larkRname=larkRegistry THEN RETURN;
party.ringEnable ← SELECT
NamesGV.GVGetAttribute[rName: larkRname, attribute: $ringmode, default: "R"].Fetch[0] FROM
'R => $on,
'S => $subdued,
'O => $off,
ENDCASE => $on;
rdAtom ← VoiceUtils.MakeAtom[NamesGV.GVGetAttribute[rName: larkRname, attribute: $dotune, default: "false"]];
party.ringDo ← SELECT rdAtom FROM
$true => ownTune,
$false => standard,
$tune => ownTune,
$both => bothTunes,
ENDCASE => standard;
party.ringTuneRope ← Rope.Cat[NamesGV.GVGetAttribute[larkRname, $tunea, NIL],
NamesGV.GVGetAttribute[larkRname, $tuneb, NIL],
NamesGV.GVGetAttribute[larkRname, $tunec, NIL]];
};
ENDCASE;
};
SetStdRingInfo: PUBLIC PROC[partyID: PartyID, update: BOOLFALSE] = {
party: PartyData←ThPartyPrivate.DehandleParty[partyID];
larkRname: ROPE;
IF party=NIL THEN RETURN;
SELECT party.type FROM
$telephone, $individual => {
r: ROPE;
larkRname ← GetRnameFromParty[party].Concat[larkRegistry];
IF larkRname=larkRegistry THEN RETURN;
r ← SELECT party.ringEnable FROM $on => "R", $subdued => "S", $off => "O", ENDCASE => NIL;
IF r#NIL THEN NamesGV.GVSetAttribute[larkRname, $ringmode, r];
IF update THEN NamesGV.GVUpdate[larkRname];
};
ENDCASE;
};
SetRingEnable: PUBLIC PROC[shh: SHHH←none, partyID: PartyID, ringEnable: Thrush.RingEnable←$noChange, ringInterval: INT𡤀, update: BOOLFALSE] = {
party: PartyData←ThPartyPrivate.DehandleParty[partyID];
IF party=NIL THEN RETURN;
GetStdRingInfo[partyID];
SELECT party.type FROM
$telephone, $individual => {
IF ringEnable=$telephone, $noChange THEN ringEnable ← party.ringEnable;
party.ringEnable ← ringEnable;
party.ringTime ← BasicTime.Update[BasicTime.Now[], ringInterval];
[] ← PrepareRingTune[party];
SELECT ringEnable FROM
$on, $subdued, $off => {
IF update AND ringEnable#party.defaultRingEnable THEN
SetStdRingInfo[partyID, ThNet.pd.autoGVUpdate];
party.defaultRingEnable ← ringEnable;
};
ENDCASE;
};
ENDCASE;
};
PrepareRingTune: PROC[party: PartyData] RETURNS[ringTune: LarkPlay.ToneSpec] = {
SELECT party.type FROM
individual => {
Set Defaults
IF ThNet.pd.ringsInvalid THEN {
ThNet.pd.ringsInvalid ← FALSE;
ringTone ← NEW[LarkPlay.ToneSpecRec ← [
repeatIndefinitely: TRUE, volume: ThNet.pd.defaultRingVolume,
tones: LIST[LIST[
[f1: 440, f2: 480, on: 2000, off: 4000],
[f1: 440, f2: 480, on: 2000, off: 4000]]]]];
subduedRingTone ← NEW[LarkPlay.ToneSpecRec ← [
repeatIndefinitely: FALSE,
volume: ThNet.pd.defaultRingVolume+ThNet.pd.subduedVolumeInterval,
tones: LIST[LIST[[f1: 440, f2: 480, on: 500, off: 0]]]
]];
outsideRingTune ← LarkPlay.PlayString[outsideRingTuneRope, FALSE, ThNet.pd.defaultRingVolume];
};
IF party.ringDo#standard AND party.ringTuneRope#NIL THEN
ringTune ← LarkPlay.PlayString[music: party.ringTuneRope, file: FALSE, volume: ThNet.pd.defaultRingVolume]
ELSE {
pattern: LarkPlay.ToneSpec = SELECT party.ringEnable FROM
subdued, subduedTimed => subduedRingTone, ENDCASE => ringTone;
ringTune ← NEW[LarkPlay.ToneSpecRec←pattern^];
};
IF ringTune#NIL THEN SELECT party.ringEnable FROM
subdued, subduedTimed => {
ringTune.volume ← ringTune.volume + ThNet.pd.subduedVolumeInterval;
ringTune.repeatIndefinitely ← FALSE;
};
ENDCASE;
party.ringTune ← ringTune;
};
ENDCASE;
};
Things removed from ThPartyPrivate.ConversationBody
creatorPartyID: Thrush.PartyID ← Thrush.nullID, -- now expressed in Triple
newKeyTable: BOOLFALSE,  -- IF key table has changed but not yet been posted
log: Thrush.EventSequence -- no longer needed; MBQueues form log, and once-only events are no longer reported that way.
Old version of CFRef/CFRec
CFRef: TYPE = REF CFRec; -- Ref to party/conversation record
CFRec: TYPE = RECORD [ -- One such CFRec for each Party-Conversation pair
event: Thrush.ConvEvent, -- describes a state and the event that put it there.
Will be computed when needed
voiceSmartsID: Thrush.SmartsID←Thrush.nullID,
-- the one that will carry the conversation load for this Party in this conversation
Now a constant of the conversation as a whole
sockID: LONG CARDINALNULL,  -- local socket ID for this participant.
MBQueue stuff eliminates the need.
lastPostedID: Thrush.StateID𡤀, -- last time event referring to this party was posted
lastNotedID: Thrush.StateID𡤀  -- last time this party's smarts were informed.
];
Old Party->smarts distribution declarations
Supervisor: PROC[party: PartyData]; -- supervision process root.
Supervise: PROC[party: PartyData]; -- Creates or joggles a Supervisor. Use where one would normally do a NOTIFY party.actionNeeded
DistributionProc: TYPE = PROC[ smarts: SmartsData ]
RETURNS [ d: Thrush.Disposition ]; -- controls whether distribution will progress.
Distribute: PROC[ party: PartyData, proc: DistributionProc]
RETURNS [ d: Thrush.Disposition ]; -- accept/reject/pass, depending on whether anybody did.
GetEvent: PROC[conv: ConversationData, stateID: Thrush.StateID]
RETURNS [ event: Thrush.ConvEvent ];
Activation code, other things removed from OpsImpl.
Activate: INTERNAL PROC[conv: ConversationData, myCfRef: CFRef, myParty: PartyData]
Generate a Lark.ConnectionSpec for this conversant.
RETURNS[isSpec: BOOLFALSE] = {
sockets: ARRAY[0..2) OF Lark.VoiceSocket;
parties: ARRAY[0..2) OF PartyData;
specIndex: NAT𡤀
GetOneActive: INTERNAL Triples.ForeachProc = {
cfRef: CFRef;
smarts: SmartsData;
sockID: PupTypes.PupSocketID;
IF specIndex>=2 THEN RETURN[FALSE];
WITH trip.att SELECT FROM r: CFRef => cfRef ← r; ENDCASE=> RETURN[TRUE];
IF cfRef.voiceSmartsID = nullID THEN RETURN[TRUE];
smarts ← UnsealSmarts[cfRef.voiceSmartsID];
IF smarts=NIL THEN RETURN--[FALSE]--; -- fail back
sockID ← LOOPHOLE[cfRef.sockID];
WITH smarts.properties SELECT FROM
$voiceTerminal => sockets[specIndex] ← [ [ machine.net], [machine.host], sockID ];
ENDCASE => VoiceUtils.Problem["Non-voice terminal trying to be voice smarts", $System];
parties[specIndex] ← NARROW[trip.val];
specIndex ← specIndex+1; };
myParty.partyActive←TRUE;
conv.numActive ← conv.numActive + 1;
Triples.Foreach[Triples.Any, conv, Triples.Any, GetOneActive];
IF specIndex#2 THEN RETURN[FALSE];
SELECT myParty FROM
=parties[0] => specIndex ← 0;
=parties[1] => specIndex ← 1;
ENDCASE => VoiceUtils.Problem["Impossible", $System];
myCfRef.event.spec ← NEW[Lark.ConnectionSpecRec ← [
protocol: interactive,
encoding: $muLaw,
sampleRate: 8000,
packetSize: 160, -- sample bytes, that is.
buffer: out1,
keyIndex: IF ThNet.pd.encryptVoice THEN 1 ELSE 0,
localSocket: sockets[specIndex],
remoteSocket: sockets[1-specIndex],
blankA: 0
]];
RETURN[TRUE];
};
Other Stuff, removed from OpsImpl.DoAdvance
OtherProc: Triples.ForeachProc ← SELECT state FROM
$idle => IdleLast,
$ringing, $active => FindCaller,
ENDCASE => NIL;
IdleLast: Triples.ForeachProc = {
If there is exactly one remaining party that is not idle . . . now it will be.
WITH trip.att SELECT FROM
r: CFRef => {
otherState ← $idle;
SELECT r.event.state FROM
$idle => RETURN[TRUE];
ENDCASE=>NULL;
IF otherParty#NIL THEN { otherParty←NIL; RETURN[FALSE]; };
otherParty←NARROW[trip.val];
otherCfRef ← r; };
ENDCASE; };
FindCaller: Triples.ForeachProc = {
If callee is answering or threatening to answer, advance caller, too.
WITH trip.att SELECT FROM
r: CFRef => {
otherState ← IF state = $ringing THEN $maybe ELSE $active;
SELECT r.event.state FROM
$initiating, $maybe => NULL;
ENDCASE => RETURN[TRUE];
otherParty ← NARROW[trip.val];
otherCfRef ← r;
RETURN[FALSE]; };
ENDCASE; };
IF OtherProc#NIL THEN Triples.Foreach[Triples.Any, conv, Triples.Any, OtherProc];
IF otherParty = NIL THEN RETURN;
Transition second party too, in small number of matched cases
SELECT validTransitions[otherState][otherCfRef.event.state] FROM
$no => {
VoiceUtils.Problem[IO.PutFR["%g, %g, %g: State mismatch among parties",
TU.RefAddr[party], TU.RefAddr[otherParty], TU.RefAddr[cfRef]], $System];
RETURN[nb: $stateMismatch];
};
Must match the other, valid, one.
$cantReq, $valid => NULL;
$noop => RETURN;
ENDCASE => ERROR;
[]←PostConvEvent[
If another party to be changed was found; do it.
conv: conv, party: otherParty, state: otherState, reason: reason, comment: comment];
validTransitions: ARRAY Thrush.StateInConv OF ARRAY Thrush.StateInConv OF Validity = [
Columns represent present state: rows represent proposed new states
[ noop, valid, valid, valid, valid, valid, valid, valid, valid, valid, no ], -- idle
[ valid, noop, valid, no, no, no, no, no, no, no, no ], -- reserved
[ valid, valid, noop, no, no, no, no, no, no, no, no ], -- parsing
[ valid, valid, valid, noop, no, no, no, no, no, no, no ], -- initiating
[ cantReq, cantReq, cantReq, no, noop, no, no, no, no, no, no ], -- pending
[ no, no, no, cantReq, no, noop, no, no, no, no, no ], -- maybe
[ no, no, no, no, valid, no, noop, no, no, no, no ], -- ringing
[ cantReq, cantReq, cantReq, cantReq, cantReq, cantReq, cantReq, noop, no, cantReq, no ], -- canAct
[ valid, valid, valid, cantReq, valid, cantReq, valid, valid, noop, valid, no ], -- active
[ no, no, no, no, no, no, no, valid, valid, noop, no ], -- inactive
[ no, no, no, no, no, no, no, no, no, no, no ] -- any
];
Validity: TYPE = {
$noop,   -- Already in this state; no transition action required
$valid,   -- This transition is all right if other tests check out.
$no,   -- This transition is not allowed, nohow.
$cantReq  -- This transition may not be requested by the Party-level client.
};
Ferreting out voice smarts, activation in PostConvEvent
Ferret out voice smarts, if need be.
SELECT state FROM
$initiating, $active, $canActivate => IF cfRef.voiceSmartsID=nullID THEN {
smarts: SmartsData ← UnsealSmarts[smartsID];
IF smarts#NIL THEN WITH smarts.properties SELECT FROM
$backstop, $supervisor => NULL; -- << ERROR? >>
voiceTerminal => cfRef.voiceSmartsID ← smartsID;
$manager => {
smarts ← NARROW[Triples.Select[$AdjacentTerminal, party, -- smarts --]];
IF smarts # NIL THEN cfRef.voiceSmartsID ← H[smarts]; };
ENDCASE; };
ENDCASE;
Maintain active party counts, generate connection specifications.
lastEvent ← cfRef.event;
lastState ← IF lastEvent=NIL THEN $idle ELSE lastEvent.state;
cfRef.lastPostedID ← conv.currentStateID;
IF state=$active THEN {
IF lastState#$active THEN
IF party.partyActive THEN event.state ← $canActivate
ELSE [] ← Activate[conv, cfRef, party] } --<<Should complain if result is false, here?>>
Old conversation log event management
GetEvent: PUBLIC INTERNAL PROC[conv: ConversationData, stateID: Thrush.StateID]
RETURNS [ Thrush.ConvEvent←NIL ] = {
IF conv=NIL THEN VoiceUtils.ProblemFR["%g: No such conversation", $System, NIL, TU.RefAddr[conv]]
ELSE IF stateID=0 THEN RETURN[NIL]
ELSE IF stateID>=conv.log.size THEN VoiceUtils.Problem["State ID out of range", $System]
ELSE RETURN[conv.log[stateID]]; };
InsertEvent: INTERNAL PROC[conv: ConversationData, entry: ConvEvent] = {
IF conv.currentStateID >= conv.log.size THEN {
oldLog: Thrush.EventSequence = conv.log;
newLog: Thrush.EventSequence =
NEW[Thrush.EventSequenceBody[oldLog.size+ThPartyPrivate.logSizeIncrement]];
FOR i: NAT IN [0..oldLog.size) DO newLog[i]←oldLog[i]; ENDLOOP;
conv.log ← newLog; };
conv.log[conv.currentStateID] ← entry; };
Almost certainly obsolete stuff from SupervisorImpl
GetHistory: PUBLIC ENTRY PROC[
shhh: Thrush.SHHH,
credentials: Credentials,
firstState: StateID,
lastState: StateID -- default: get latest
] RETURNS [ nb: Thrush.NB, events: Thrush.EventSequence←NIL ] = {
ENABLE UNWIND => NULL;
conv: ConversationData;
numStates: NAT;
[conv, , , nb] ← ThPartyPrivate.Verify[credentials];
IF nb#success OR conv=NIL OR conv.currentStateID=0 THEN RETURN;
IF lastState=0 OR lastState>conv.currentStateID THEN lastState𡤌onv.currentStateID;
SELECT firstState FROM
<1 => firstState𡤁
>conv.currentStateID => RETURN;
ENDCASE;
IF lastState<firstState THEN RETURN;
numStates ← lastState-firstState+1;
events←NEW[Thrush.EventSequenceBody[numStates]];
FOR i: NAT IN [0..numStates) DO events[i] ← conv.log[i+firstState]; ENDLOOP; };
Almost certainly misplaced stuff from SupervisorImpl
SetupRingTunes: PROC[event: Thrush.ConvEvent] = TRUSTED {
partyID: Thrush.PartyID = event.credentials.partyID;
party: PartyData = ThPartyPrivate.UnsealParty[partyID];
otherParty: PartyData;
otherTune: LarkPlay.ToneSpec;
otherMethod: ThPartyPrivate.RingMethod ← standard;
divisor: NAT ← 1;
If timed, see if time has expired. If so, switch back to default ring tune
SELECT event.state FROM ringing, maybe => NULL; ENDCASE => RETURN;
SELECT party.type FROM
individual => {
SELECT party.ringEnable FROM
offTimed, subduedTimed =>
IF BasicTime.Period[from: party.ringTime, to: BasicTime.Now[]] > 0 THEN
ThParty.SetRingEnable[partyID: partyID, ringEnable: party.defaultRingEnable];
ENDCASE;
SELECT party.ringEnable FROM
offTimed, off => RETURN;
ENDCASE;
otherParty ← ThPartyPrivate.UnsealParty[
ThPartyPrivate.FindOtherParty[partyID,
ThPartyPrivate.UnsealConv[event.credentials.convID]].partyID];
IF otherParty#NIL THEN SELECT otherParty.type FROM
individual => {
divisor ← 2; otherMethod ← otherParty.ringDo;
IF otherMethod # standard THEN otherTune ← otherParty.ringTune;
};
trunk => IF event.state=ringing THEN {
otherTune ← ThPartyPrivate.outsideRingTune; otherMethod ← bothTunes;
};
ENDCASE;
SELECT event.state FROM
maybe => {
IF otherTune=NIL THEN RETURN;
event.ringTune ← NEW[LarkPlay.ToneSpecRec ← otherTune^];
event.ringTune.volume ← ThNet.pd.tonesVolume+2;
event.ringTune.repeatIndefinitely ← TRUE;
RETURN;
};
ringing => NULL;
ENDCASE => RETURN;
event.ringTune ← party.ringTune;
IF party.ringDo # bothTunes OR otherMethod # bothTunes OR otherTune=NIL THEN RETURN;
event.ringTune ←
LarkPlay.MergeToneSpecs[event.ringTune, otherTune, divisor, ringTuneDelay];
};
ENDCASE;
};
ringTuneDelay: NAT ← 2400; -- ms. delay between tune starts.
Crockery extracted from ThPartySupervisorImpl dealing with ring tunes and interval/prose stuff.
From Supervisor
IF event.keyTable=NIL AND event.intervalSpecs=NIL AND (event.proseSpecs=NIL OR (event.proseSpecs.first.type=request AND party.type=individual)) AND
(NOT yourParty) AND (event.state#initiating) THEN RETURN;
Don't report uninteresting events. Allow request proseSpecs to get to service (for speech) and/or trunk (for feeping) smarts. (Note: a proseSpec list will be either wholly requests or some combination of started and finished at this level.) Need to change if proseSpecs used to communicate with own Lark.
I think this is a hack. July 23, 1984 3:06 pm PDT
IF yourParty AND smarts.properties.role=voiceTerminal THEN
SetupRingTunes[event];
Tell only voiceTerminals about the ring or ringback tune
EndParty[party];
From ScanPostOrWait
IF r.lastNotedID<conv.currentStateID THEN cfRef ← r
ELSE IF r.event.state=idle THEN Dissolve[r, conv, party];
Dissolve conversation after everybody's idle.
Dissolve: INTERNAL PROC[cfRef: CFRef, conv: ConversationData, party: PartyData] = {
If Post generates new work to do, stop and do it, then cycle again.
Post is probably the wrong word for this activity, now. But the work needs doing.
IF cfRef.event.state#idle THEN RETURN;
Triples.Erase[cfRef, conv, party];
conv.numParties ← conv.numParties-1;
party.numConvs ← party.numConvs-1;
IF conv.numParties=0 THEN ThPartyPrivate.DestroyConversation[conv];
When a trunk party leaves a conversation, it loses its identity (<< something wrong here? >>)
When a recording party leaves, it reverts to available.
TRUSTED { SELECT party.type FROM
trunk => party.outgoing ← NIL;
service => TU.MakeUnique[$RnameForParty, party,
ThPartyPrivate.MakeServiceRname[party.serviceName].serviceRnameAtom];
ENDCASE;
};
};
Kill Party structures after party has died.
EndParty: ENTRY PROC[party: PartyData] = {
[]←RefID.Release[Reseal[party]];
Triples.Erase[Triples.Any, party, Triples.Any];
party.supervisor ← NIL;
};
From LarkSmartsInitImpl, autoAnswer and such
larkInfo: NEW[ThSmartsPrivate.LarkInfoBody ← [
interface: larkInterface,
shh: larkSh,
netAddress: netAddress,
model: model,
autoAnswer:
NamesGV.GVGetAttribute[dbRname, $autoanswer, "FALSE"].Equal["TRUE", FALSE],
radio: NamesGV.GVGetAttribute[dbRname, $radio, "FALSE"].Equal["TRUE", FALSE],
textToSpeech: Rope.Equal[serviceName, "Text-to-Speech", FALSE]
]] ]];
And corresponding bits from ThSmartsPrivate.LarkInfoBody:
Special attributes. << Not clear how set >>
autoAnswer: BOOLFALSE, -- answers when called.
radio: BOOLFALSE, -- used with hotline; connects line in instead of telset when called.
monitor: BOOLFALSE,-- speaker repeats telset receiver in telset mode.
Things removed from Thrush.ConvEvent (=> ConvState)
These can be obtained on demand by calling other ThParty functions. They are states of the conversation set (usually) by the originator.
urgency: CallUrgency← NIL, -- ($normal) urgency supplied in connection attempt during this event.
alertKind: AlertKind ← NIL, -- ($standard) connection type hint
Will also need to store the unique socket number for the conversation.
KeyTable is derived from all the key provisions during the conversation. It is provided on demand.
keyTable: Lark.KeyTable ← NIL, -- whenever there's a spec
Conversation socket number plus machine number yields the information needed for a spec. These are party/smarts values. ThParty functions will return the necessary values.
spec: Lark.ConnectionSpec ← NIL, -- machine, really
address: ROPENIL,   -- calling trunks and the like: external addressing information.
These will be presented in separate notifications through ThSmarts.
intervalSpecs: IntervalSpecs ← NIL, -- if non-NIL, a SetInterval is being specified.
proseSpecs: ProseSpecs ← NIL, -- if non-NIL, a SetProse is being specified.
This will be obtained directly from the database
ringTune: LarkPlay.ToneSpec ← NIL, -- if non-NIL, the ringing tune to use (to voiceTerminal smarts only)
From ThPartyOpsImpl
OtherParty: PUBLIC ENTRY PROC[
shhh: SHHH←none,
credentials: Credentials
] RETURNS[
nb: NB,
partyID: PartyID←nullID,
description: Thrush.ROPE,
conference: BOOLFALSE ] = {
conv: ConversationData;
[conv, , , nb] ← Verify[credentials];
IF nb=$stateMismatch THEN nb←$success;
IF nb#$success THEN RETURN;
[partyID, conference] ← FindOtherParty[credentials.partyID, conv];
description ← SELECT TRUE FROM
conference => "Group of parties",
partyID#nullID => ThPartyPrivate.DoDescribeParty[partyID],
ENDCASE => NIL;
};
FindOtherParty: PUBLIC PROC[myPartyID: PartyID, conv: ConversationData]
RETURNS[partyID: PartyID←nullID, conference: BOOLFALSE ] = {
FOR i: NAT IN [1..conv.currentStateID] DO
ce: ConvEvent = conv.log[i];
IF myPartyID=ce.credentials.partyID THEN LOOP;
partyID ← ce.credentials.partyID;
IF conv.numParties>2 THEN { conference←TRUE; RETURN; };
EXIT; ENDLOOP;
};
MergeConversations: PUBLIC PROC[
shhh: SHHH,
credentials: Credentials, -- of surviving conversation
otherStateID: Thrush.StateID, -- of dissolving conversation
otherConvID: ConversationID
] RETURNS [ nb: NB ] = {NULL};
SetIntervals: PUBLIC ENTRY PROC[
shhh: SHHH ← none,
credentials: Credentials,
intervalSpecs: Thrush.IntervalSpecs
] RETURNS [ nb: NB ] = {
ENABLE UNWIND => NULL;
conv: ConversationData;
party: PartyData;
cfRef: CFRef;
WachetAuf: INTERNAL Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => { party←NARROW[trip.val]; ThPartyPrivate.Supervise[party]; }; ENDCASE; };
[conv, party, cfRef, nb] ← Verify[credentials];
IF nb#$success THEN RETURN;
IF cfRef=NIL OR cfRef.event=NIL OR cfRef.event.state#$active THEN RETURN[$convNotActive];
[] ← PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , ,intervalSpecs];
FOR iSL: Thrush.IntervalSpecs ← intervalSpecs, iSL.rest WHILE iSL#NIL DO
iS: Thrush.IntervalSpec = iSL.first;
IF ~ThNet.pd.encryptVoice THEN iS.keyIndex𡤀
IF iS.type=$request THEN iS.intID.stateID ← conv.currentStateID;
ENDLOOP;
Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf];
Wake all parties to conversation, now. Potential new key.
};
SetProse: PUBLIC ENTRY PROC[
shhh: SHHH ← none,
credentials: Credentials,
proseSpecs: Thrush.ProseSpecs
] RETURNS [ nb: NB ] = {
ENABLE UNWIND => NULL;
conv: ConversationData;
party: PartyData;
cfRef: CFRef;
WachetAuf: INTERNAL Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => { party←NARROW[trip.val]; ThPartyPrivate.Supervise[party]; }; ENDCASE; };
[conv, party, cfRef, nb] ← Verify[credentials];
IF nb#$success THEN RETURN;
IF cfRef=NIL OR cfRef.event=NIL OR cfRef.event.state#$active THEN RETURN[$convNotActive];
[] ← PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , , , proseSpecs];
FOR pSL: Thrush.ProseSpecs ← proseSpecs, pSL.rest WHILE pSL#NIL DO
pS: Thrush.ProseSpec = pSL.first;
IF pS.type=$request THEN pS.intID.stateID ← conv.currentStateID;
ENDLOOP;
Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf];
Wake all parties to conversation, now. Potential new key.
};
DescribeInterval: PUBLIC PROC[
shhh: SHHH ← none,
credentials: Credentials,
targetInterval: Thrush.IntervalSpec,
minSilence: Thrush.VoiceTime ← 1 -- Smallest silent interval that will be considered silence.
]
RETURNS [ nb: NB, exists: BOOLFALSE, intervals: Thrush.IntervalSpecs←NIL ] = {
conv: ConversationData;
party: PartyData;
cfRef: CFRef;
[conv, party, cfRef, nb] ← VerifyEnt[credentials];
IF nb#$success AND nb#$stateMismatch THEN RETURN;
[exists, intervals] ← BluejayUtils.DescribeInterval[targetInterval.tune, targetInterval.interval, minSilence];
};
SetSubject: PUBLIC ENTRY PROC[ shh: SHHH, convID: ConversationID, subject: ROPE ] = {
ENABLE UNWIND => NULL;
conv: ConversationData = UnsealConv[convID];
IF conv=NIL THEN RETURN ELSE conv.subject ← subject; };
GetSubject: PUBLIC ENTRY PROC[ shh: SHHH, convID: ConversationID ] RETURNS [subject: ROPE] = {
ENABLE UNWIND => NULL;
conv: ConversationData = UnsealConv[convID];
RETURN[IF conv=NIL THEN NIL ELSE conv.subject]; };
ConversationsForParty: PUBLIC ENTRY PROC [ shh: SHHH←none, partyID: PartyID ] = {
party: PartyData ← UnsealParty[partyID];
ReallyWachetAuf: INTERNAL Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => {
conv: ConversationData ← NARROW[trip.obj];
r.lastNotedID ← 0;
ThPartyPrivate.Supervise[party];
};
ENDCASE;
};
IF party#NIL THEN Triples.Foreach[Triples.Any, Triples.Any, party, ReallyWachetAuf];
};