DIRECTORY BasicTime USING [earliestGMT, Now, Period], Convert USING [IntFromRope, RopeFromInt], IO USING [card, GetCard, GetInt, int, PutFR, RIS, rope, STREAM], LoganBerryStub USING [AttributeType, AttributeValue, Entry, Error, Open, OpenDB, ReadEntry, WriteEntry], Rope USING [Concat, ROPE], Thrush USING [EncryptionKey], VoiceTemp USING [TuneID], UserCredentials USING [CredentialsChangeProc, Get, RegisterForChange], VoiceRopeDB; VoiceRopeDBImpl: CEDAR PROGRAM -- Should this be a monitor? IMPORTS BasicTime, Convert, IO, Rope, UserCredentials, LoganBerry: LoganBerryStub EXPORTS VoiceRopeDB ~ BEGIN OPEN VoiceRopeDB; ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; Error: PUBLIC ERROR [ec: ATOM, explanation: Rope.ROPE _ NIL] = CODE; Open: PUBLIC PROC[dbName: Rope.ROPE] RETURNS [handle: Handle _ NIL] = { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; handle _ NEW[HandleRec _ [voiceRopeDBName: dbName.Concat[".df"], voiceInterestDBName: dbName.Concat["Refs.df"]]]; handle.voiceRopeDB _ LoganBerry.Open[dbName: handle.voiceRopeDBName]; }; Read: PUBLIC PROC [handle: Handle, ropeID: ID] RETURNS [header: Header, length: INT, struct: TuneList] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; header _ LoganBerry.ReadEntry[db: handle.voiceRopeDB, key: $VRID, value: ropeID].entry; [length, struct] _ EntryToTuneList[header]; }; Write: PUBLIC PROC [handle: Handle, struct: TuneList] RETURNS [ropeID: ID, length: INT] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; entry: LoganBerry.Entry; start, len: INT; list: TuneList _ struct; length _ 0; UNTIL list = NIL DO [start: start, length: len, rest: list] _ NextTuneOnList[list]; length _ length + len; ENDLOOP; ropeID _ GenerateUniqueID[]; entry _ struct; entry _ CONS[[$Length, Convert.RopeFromInt[length]], entry]; entry _ CONS[[$Creator, UserCredentials.Get[].name], entry]; entry _ CONS[[$VRID, ropeID], entry]; LoganBerry.WriteEntry[db: handle.voiceRopeDB, entry: entry ! LoganBerry.Error => IF ec = $ValueNotUnique THEN {ropeID _ BadUniqueID[]; entry.first.value _ ropeID; RETRY}]; }; SimpleTuneList: PUBLIC PROC [tuneID: VoiceTemp.TuneID, start: INT, length: INT, key: Thrush.EncryptionKey] RETURNS [list: TuneList] ~ { list _ LIST[[$TID, Convert.RopeFromInt[tuneID]], [$Key, MarshalKey[key]], [$SL, MarshalInterval[start, length]]]; }; NextTuneOnList: PUBLIC PROC [list: TuneList] RETURNS [tuneID: VoiceTemp.TuneID, start: INT, length: INT, key: Thrush.EncryptionKey, rest: TuneList] ~ { tuneID _ Convert.IntFromRope[list.first.value]; key _ UnmarshalKey[list.rest.first.value]; [start, length] _ UnmarshalInterval[list.rest.rest.first.value]; rest _ list.rest.rest.rest; }; EntryToTuneList: PROC [entry: LoganBerry.Entry] RETURNS [length: INT _ -1, struct: TuneList] ~ { IF entry = NIL THEN RETURN[length: 0, struct: NIL]; UNTIL entry.first.type = $TID DO IF entry.first.type = $Length THEN length _ Convert.IntFromRope[entry.first.value]; entry _ entry.rest; ENDLOOP; struct _ entry; }; TuneListInterval: PUBLIC PROC [list: TuneList, start: INT, length: INT] RETURNS [new: TuneList] ~ { tuneStart, tuneLength: INT; -- the current tune's interval prevSamples: INT; -- number of samples in entry excluding the current tune interval samples: INT; -- number of samples in entry including the current tune interval intervalMustChange: BOOLEAN _ FALSE; IF length = -1 THEN length _ LAST[INT]; prevSamples _ 0; [tuneStart, tuneLength] _ UnmarshalInterval[list.rest.rest.first.value]; samples _ tuneLength; UNTIL samples > start DO prevSamples _ samples; list _ list.rest.rest.rest; IF list = NIL THEN RETURN[NIL]; [tuneStart, tuneLength] _ UnmarshalInterval[list.rest.rest.first.value]; samples _ samples + tuneLength; ENDLOOP; new _ list; IF prevSamples # start THEN { -- don't want first part of existing interval tuneStart _ tuneStart + start - prevSamples; tuneLength _ tuneLength - (start - prevSamples); intervalMustChange _ TRUE; }; IF (length # LAST[INT]) AND (samples >= start + length) THEN { -- interval contained within a single tune interval tuneLength _ length; list.rest.rest.rest _ NIL; -- truncate list since we have has much as we need intervalMustChange _ TRUE; }; IF intervalMustChange THEN new.rest.rest.first.value _ MarshalInterval[tuneStart, tuneLength]; IF (length = LAST[INT]) OR (samples >= start + length) THEN -- no need to go on RETURN[new]; UNTIL samples >= start + length DO prevSamples _ samples; list _ list.rest.rest.rest; IF list = NIL THEN RETURN[new]; [tuneStart, tuneLength] _ UnmarshalInterval[list.rest.rest.first.value]; samples _ samples + tuneLength; ENDLOOP; IF samples # start + length THEN { -- want only first part of tune interval list.rest.rest.first.value _ MarshalInterval[tuneStart, start + length - prevSamples]; }; list.rest.rest.rest _ NIL; -- truncate list since we have has much as we need RETURN[new]; }; MarshalKey: PROC [key: Thrush.EncryptionKey] RETURNS [r: ROPE] ~ TRUSTED { cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL=LOOPHOLE [LONG[@key]]; r _ IO.PutFR["%bB %bB", IO.card[cardKey[0]], IO.card[cardKey[1]]]; }; UnmarshalKey: PROC [r: ROPE] RETURNS [key: Thrush.EncryptionKey] ~ TRUSTED { cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL=LOOPHOLE[LONG[@key]]; keyStream: IO.STREAM _ IO.RIS[r]; cardKey[0] _ IO.GetCard[keyStream]; cardKey[1] _ IO.GetCard[keyStream]; }; MarshalInterval: PROC [start: INT, length: INT] RETURNS [r: ROPE] ~ INLINE { r _ IO.PutFR["%g %g", IO.int[start], IO.int[length]]; }; UnmarshalInterval: PROC [r: ROPE] RETURNS [start: INT, length: INT] ~ INLINE { s: IO.STREAM _ IO.RIS[r]; start _ IO.GetInt[s]; length _ IO.GetInt[s]; }; userName: ROPE; counter: INT; kicks: INT; -- for instrumentation purposes GenerateUniqueID: PROC [] RETURNS [id: ROPE] ~ { timestamp: INT _ MAX[counter, BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]]; id _ IO.PutFR["%g#%g", IO.rope[userName], IO.int[timestamp]]; counter _ counter + 1; }; BadUniqueID: PROC [] RETURNS [id: ROPE] ~ { counter _ BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]+1; -- kick the counter kicks _ kicks + 1; id _ GenerateUniqueID[]; }; NewUser: UserCredentials.CredentialsChangeProc ~ { userName _ UserCredentials.Get[].name; }; InitUniqueID: PROC [] RETURNS [] ~ { counter _ BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]; kicks _ 0; userName _ UserCredentials.Get[].name; UserCredentials.RegisterForChange[NewUser]; }; InitUniqueID[]; END. $VoiceRopeDBImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Doug Terry, June 5, 1986 3:07:30 pm PDT Swinehart, June 25, 1986 11:22:54 am PDT Routines for storing voice ropes in a database and manipulating their structure. It is unresolved where a tune's encryption key should be stored. For now, it is kept with the tune interval; though this is the wrong place. It should probably be kept in the tune's header. Can't do that since FinchSmarts wants to be passed the key. Database operations Voice ropes are stored in a LoganBerry database as a header followed by a list of tune intervals (a TuneList). Interests in voice ropes are also maintained in a LoganBerry database. Prepares the given database for service. handle.voiceInterestDB _ LoganBerry.Open[dbName: handle.voiceInterestDBName]; Returns the structure of the given voice rope. Writes the voice rope information into the LoganBerry database. compute length of voice rope will have to change how we get creator across RPC connection. Voice rope structure A tune list, the structure of a voice rope, is represented in a LoganBerry database as an ordered list of $TID (the tune's ID), $Key (the tune's encryption key), and $SL (the start and length of a tune interval) attributes. The tune's encryption key should probably be kept in the tune's header, but that can't be done if FinchSmarts is used to record and play tunes. Builds a tune list with a single tune's information. Returns information about the next tune on the list; assumes that list really points to a properly structured TuneList so doesn't bother to check attribute types. The rest of the list is returned so this routine can be repetitively called to get all the tunes on the list. Returns the tune list representing the structure of the voice rope. Returns the tune list representing the structure of the voice rope interval. Warning: this operation modifies the original list! at this point: prevSamples <= interval.start < samples Note: there's a possibility that interval.start + interval.length could cause an integer overflow; do I want to pay the cost to check for this? at this point: prevSamples <= start + length <= samples Conversions (marshalling) Writes a start and length field into a rope. Parses the input rope into a start and length field. Generating unique identifiers A unique identifier is generated by concatenating a user's Rname with a timestamp. This assumes that the same user is not simultaneously creating voice ropes from two different workstations. This problem would not arise if machine names were used instead of user names. The technique used for obtaining the user's name will have to change when this code runs on the voice server instead of on client machines. The timestamp is taken to be the maximum of the current time (converted to an integer) and a simple counter. The current time alone is not sufficient since it has a granularity of seconds. Several voice ropes may be created within a second, but the long-term creation rate should be much less than one per second. The counter is initialized to the current time, which could cause some problems if this module is rerun before the current time has a chance to catch up to the old counter (or if a machine's clock is reset to an earlier time). This problem is detected by $ValueNotUnique errors from LoganBerry when one attempts to write a new voice rope. Note that the counter, as maintain by these routines, is sufficient as a unique ID if this code is run on the voice server. However, having a user's name and current timestamp as part of the permanent voice rope ID provides information that might be useful. This routine should be called if some generated ID does not turn out to be unique. It trys once again to generate a unique ID after advancing the counter. Initializations Doug Terry, June 3, 1986 4:19:14 pm PDT Extracted database operations from VoiceRopeImpl.mesa. changes to: VoiceRopeDBImpl , EXPORTS , ~ , WriteVoiceRope  Doug Terry, June 3, 1986 5:23:23 pm PDT changes to: VoiceRopeDBImpl, EXPORTS, ~, Open, WriteVoiceRope, END, DIRECTORY, IMPORTS, Read, SimpleTuneList, NextTuneOnList, TuneListInterval, EntryToTuneList Doug Terry, June 5, 1986 11:47:40 am PDT changes to: Open, Read, Write, EntryToTuneList, Error, DIRECTORY Doug Terry, June 5, 1986 3:07:30 pm PDT changes to: EntryToTuneList, TuneListInterval Swinehart, June 25, 1986 9:33:36 am PDT New Thrush, including silly elimination of VoiceInterval type. changes to: DIRECTORY, Write, SimpleTuneList, NextTuneOnList, TuneListInterval, MarshalInterval, UnmarshalInterval Κ²˜codešœ™Kšœ Οmœ1™Kšœ £f™r—K™—…—24