DIRECTORY BasicTime USING [GMT, earliestGMT, Now, Period, Update], Convert USING [AtomFromRope, BoolFromRope, IntFromRope, TimeFromRope, RopeFromAtom, RopeFromBool, RopeFromInt, RopeFromTime], FS USING [ComponentPositions, ExpandName], IO USING [card, GetCard, GetInt, int, PutFR, RIS, rope, STREAM], LoganBerry USING [AttributeType, AttributeValue, Cursor, DeleteEntry, EndGenerate, Entry, Error, GenerateEntries, NextEntry, Open, OpenDB, ReadEntry, WriteEntry], Rope USING [Concat, Equal, Find, Replace, ROPE, Substr], Thrush USING [EncryptionKey, Tune, VoiceInterval], UserCredentials USING [CredentialsChangeProc, Get, RegisterForChange], ScriptDB; ScriptDBImpl: CEDAR PROGRAM -- Should this be a monitor? IMPORTS BasicTime, Convert, FS, IO, Rope, UserCredentials, LoganBerry: LoganBerryStub EXPORTS ScriptDB ~ BEGIN OPEN ScriptDB; 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]; sDB, eDB: ROPE; cp: FS.ComponentPositions; IF dbName = NIL THEN RETURN[NIL]; [sDB, cp] _ FS.ExpandName[dbName]; IF cp.ext.length = 0 THEN sDB _ Rope.Concat[sDB, ".df"]; eDB _ Rope.Replace[base: sDB, start: cp.base.start+cp.base.length, len: 0, with: "Entries"]; handle _ NEW[HandleRec _ [scriptDBName: sDB, scriptEntriesDBName: eDB]]; handle.scriptDB _ LoganBerry.Open[dbName: handle.scriptDBName]; handle.scriptEntriesDB _ LoganBerry.Open[dbName: handle.scriptEntriesDBName]; }; ReadScriptDesc: PUBLIC PROC [handle: Handle, sid: ScriptID] RETURNS [script: Script] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; lbe: LBEntry _ NIL; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; lbe _ LoganBerry.ReadEntry[db: handle.scriptDB, key: $SID, value: sid].entry; script _ LBEntryToScriptDesc[lbe]; }; WriteScriptDesc: PUBLIC PROC [handle: Handle, script: Script] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; lbe: LoganBerry.Entry _ ScriptDescToLBEntry[script]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; LoganBerry.WriteEntry[db: handle.scriptDB, entry: lbe]; }; DeleteScriptDesc: PUBLIC PROC [handle: Handle, sid: ScriptID] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; LoganBerry.DeleteEntry[db: handle.scriptDB, key: $SID, value: sid]; }; FindScriptsStartingInFile: PUBLIC PROC [handle: Handle, file: FileID] RETURNS [scriptList: ScriptList]~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; continue: BOOLEAN _ TRUE; cursor: LoganBerry.Cursor; lbe: LoganBerry.Entry; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; scriptList _ NIL; cursor _ LoganBerry.GenerateEntries[db: handle.scriptDB, key: $StartingFile, start: file.name]; WHILE (lbe _ LoganBerry.NextEntry[cursor: cursor]) # NIL DO script _ LBEntryToScriptDesc[lbe]; fEntry _ ReadScriptEntryDesc[handle, script.firstEntry]; IF (fEntry.internal AND script.startingFile.createTime>=file.createTime) OR script.startingFile.createTime=file.createTime THEN scriptList _ CONS[script, scriptList]; ENDLOOP; LoganBerry.EndGenerate[cursor: cursor]; }; ReadScriptEntryDesc: PUBLIC PROC [handle: Handle, eid: ScriptEntryID] RETURNS [scriptEntry: ScriptEntry] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; lbe: LBEntry _ NIL; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; lbe _ LoganBerry.ReadEntry[db: handle.scriptEntryDB, key: $EID, value: eid].entry; scriptEntry _ LBEntryToScriptEntryDesc[lbe]; }; WriteScriptEntryDesc: PUBLIC PROC [handle: Handle, scriptEntry: ScriptEntry] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; lbe: LoganBerry.Entry _ ScriptEntryDescToLBEntry[scriptEntry]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; LoganBerry.WriteEntry[db: handle.scriptEntryDB, entry: lbe]; }; DeleteScriptEntryDesc: PUBLIC PROC [handle: Handle, eid: ScriptEntryID] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; LoganBerry.DeleteEntry[db: handle.scriptEntryDB, key: $EID, value: eid]; }; ScriptDescToLBEntry: PUBLIC PROC [s: Script] RETURNS [lbe: LBEntry] ~ { lbe _ LIST[NEW[Attribute _ [$StartingFiledate, Convert.RopeFromTime[from: s.startingFile.createTime, end: seconds]]]]; lbe _ CONS[NEW[Attribute _ [$StartingFilename, s.startingFile.name], list]]; lbe _ CONS[NEW[Attribute _ [$NumEntries, Convert.RopeFromInt[s.numEntries]], list]]; lbe _ CONS[NEW[Attribute _ [$Creation, Convert.RopeFromTime[from: s.sCreateInfo.creation, end: seconds]], list]]; lbe _ CONS[NEW[Attribute _ [$Creator, s.sCreateInfo.creator], list]]; lbe _ CONS[NEW[Attribute _ [$Desc, s.sDesc], list]]; lbe _ CONS[NEW[Attribute _ [$Name, s.sName], list]]; lbe _ CONS[NEW[Attribute _ [$FirstEntry, s.firstEntry.eid], list]]; lbe _ CONS[NEW[Attribute _ [$SID, s.sid], list]]; }; LBEntryToScriptDesc: PUBLIC PROC [lbe: LBEntry] RETURNS [s: Script] ~ { s _ NEW[defaultScript]; WHILE lbe#NIL DO SELECT lbe.first.type FROM $SID => s.sid _ lbe.first.value; $FirstEntry => s.firstEntry.eid _ lbe.first.value; $Name => s.sName _ lbe.first.value; $Desc => s.sDesc _ lbe.first.value; $Creator => s.sCreateInfo.creator _ lbe.first.value; $Creation => s.sCreateInfo.creation _ Convert.TimeFromRope[lbe.first.value]; $NumEntries => s.numEntries _ Convert.IntFromRope[lbe.first.value]; $StartingFilename => s.startingFile.name; $StartingFiledate => s.startingFile.createTime _ Convert.TimeFromRope[lbe.first.value]; ENDCASE; lbe _ lbe.rest; ENDLOOP; }; ScriptEntryDescToLBEntry: PUBLIC PROC [se: ScriptEntry] RETURNS [lbe: LBEntry] ~ { lbe _ LIST[NEW[Attribute _ [$Creation, Convert.RopeFromTime[from: se.sCreateInfo.creation, end: seconds]], list]]; lbe _ CONS[NEW[Attribute _ [$Creator, se.sCreateInfo.creator], list]]; lbe _ CONS[NEW[Attribute _ [$Desc, se.eDesc], list]]; lbe _ CONS[NEW[Attribute _ [$Name, se.eName], list]]; lbe _ CONS[NEW[Attribute _ [$NextEntries, FlattenNextEntries[se.nextEntries]], list]]; lbe _ CONS[NEW[Attribute _ [$PauseAfter, Convert.RopeFromInt[se.pauseAfter]], list]]; lbe _ CONS[NEW[Attribute _ [$PauseBefore, Convert.RopeFromInt[se.pauseBefore]], list]]; lbe _ CONS[NEW[Attribute _ [$Action, se.action], list]]; lbe _ CONS[NEW[Attribute _ [$CharsLen, Convert.RopeFromInt[se.chars.len]], list]]; lbe _ CONS[NEW[Attribute _ [$CharsStart, Convert.RopeFromInt[se.chars.start]], list]]; lbe _ CONS[NEW[Attribute _ [$Class, Convert.RopeFromAtom[se.class]], list]]; lbe _ CONS[NEW[Attribute _ [$Internal, Convert.RopeFromBool[se.internal]], list]]; lbe _ CONS[NEW[Attribute _ [$Filedate, Convert.RopeFromTime[from: se.file.createTime, end: seconds]]]]; lbe _ CONS[NEW[Attribute _ [$Filename, se.file.name], list]]; lbe _ CONS[NEW[Attribute _ [$SID, se.scriptHeader.sid], list]]; lbe _ CONS[NEW[Attribute _ [$EID, se.eid], list]]; }; LBEntryToScriptEntryDesc: PUBLIC PROC [lbe: LBEntry] RETURNS [s:e ScriptEntry] ~ { se _ NEW[defaultScriptEntry]; WHILE lbe#NIL DO SELECT lbe.first.type FROM $EID => se.eid _ lbe.first.value; $SID => se.scriptHeader.sid _ lbe.first.value; $Filename => se.file.name _ lbe.first.value; $Filedate => se.file.createTime _ Convert.TimeFromRope[lbe.first.value]; $Internal => se.internal _ Convert.BoolFromRope[lbe.first.value]; $Class => se.class _ Convert.AtomFromRope[lbe.first.value]; $CharsStart => se.chars.start _ Convert.IntFromRope[lbe.first.value]; $CharsLen => se.chars.len _ Convert.IntFromRope[lbe.first.value]; $Action => se.action _ lbe.first.value; $PauseBefore => se.pauseBefore _ Convert.IntFromRope[lbe.first.value]; $PauseAfter => se.pauseAfter _ Convert.IntFromRope[lbe.first.value]; $NextEntries => se.nextEntries _ ParseNextEntries[lbe.first.value]; $Name => se.eName _ lbe.first.value; $Desc => se.eDesc _ lbe.first.value; $Creator => se.sCreateInfo.creator _ lbe.first.value; $Creation => se.sCreateInfo.creation _ Convert.TimeFromRope[lbe.first.value]; ENDCASE; lbe _ lbe.rest; ENDLOOP; }; FlattenNextEntries: PROC [nextEntryList: NextEntryList] RETURNS [r: ROPE] ~ { WHILE nextEntryList#NIL DO r _ r.Concat[nextEntryList.first.cond, nextEntryList.first.entry.???] nextEntryList _ nextEntryList.rest ENDLOOP; }; ParseNextEntries: PROC [r: ROPE] RETURNS [nextEntryList: NextEntryList] ~ { nextEntryList _ NEW[]; }; EntryToTuneList: PROC [entry: LoganBerry.Entry] RETURNS [struct: TuneList] ~ { IF entry = NIL THEN RETURN[NIL]; UNTIL entry.first.type = $TID DO entry _ entry.rest; IF entry = NIL THEN Error[$BadTuneList, "No TID in entry"]; ENDLOOP; struct _ entry; }; TuneListInterval: PUBLIC PROC [list: TuneList, interval: Thrush.VoiceInterval] RETURNS [new: TuneList] ~ { tuneInterval: Thrush.VoiceInterval; -- 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 list = NIL THEN RETURN[NIL]; IF interval.length = -1 THEN interval.length _ LAST[INT]; prevSamples _ 0; tuneInterval _ UnmarshalInterval[list.rest.rest.first.value]; samples _ tuneInterval.length; UNTIL samples > interval.start DO prevSamples _ samples; list _ list.rest.rest.rest; IF list = NIL THEN RETURN[NIL]; tuneInterval _ UnmarshalInterval[list.rest.rest.first.value]; samples _ samples + tuneInterval.length; ENDLOOP; new _ list; IF prevSamples # interval.start THEN { -- don't want first part of existing interval tuneInterval.start _ tuneInterval.start + interval.start - prevSamples; tuneInterval.length _ tuneInterval.length - (interval.start - prevSamples); intervalMustChange _ TRUE; }; IF (interval.length # LAST[INT]) AND (samples >= interval.start + interval.length) THEN { -- interval contained within a single tune interval tuneInterval.length _ interval.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[tuneInterval]; IF (interval.length = LAST[INT]) OR (samples >= interval.start + interval.length) THEN -- no need to go on RETURN[new]; UNTIL samples >= interval.start + interval.length DO prevSamples _ samples; list _ list.rest.rest.rest; IF list = NIL THEN RETURN[new]; tuneInterval _ UnmarshalInterval[list.rest.rest.first.value]; samples _ samples + tuneInterval.length; ENDLOOP; IF samples # interval.start + interval.length THEN { -- want only first part of tune interval list.rest.rest.first.value _ MarshalInterval[[start: tuneInterval.start, length: interval.start + interval.length - prevSamples]]; }; list.rest.rest.rest _ NIL; -- truncate list since we have has much as we need RETURN[new]; }; PackHeader: PROC [info: VoiceRopeInfo] RETURNS [header: Header] ~ { entry: LoganBerry.Entry; entry _ info.struct; entry _ CONS[[$Length, Convert.RopeFromInt[info.length]], entry]; entry _ CONS[[$Creator, info.creator], entry]; entry _ CONS[[$VRID, info.vrID], entry]; RETURN[entry]; }; ReplaceID: PROC [header: Header] RETURNS [newID: ID] ~ { newID _ BadUniqueID[]; header.first.value _ newID; }; UnpackHeader: PUBLIC PROC [header: Header] RETURNS [info: VoiceRopeInfo] ~ { list: LoganBerry.Entry _ header; IF header = NIL THEN RETURN; info.vrID _ list.first.value; list _ list.rest; list _ list.rest; info.length _ Convert.IntFromRope[list.first.value]; [info.creator, info.timestamp] _ ParseUniqueID[info.vrID]; info.struct _ list.rest; }; AddInterest: PUBLIC PROC [handle: Handle, interest: InterestInfo] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; entry: LoganBerry.Entry _ NIL; oldEntry: LoganBerry.Entry; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; oldEntry _ ReadInterest[handle, interest.vrID, interest.class, interest.refID]; interest.interestID _ IF oldEntry = NIL THEN GenerateUniqueID[] ELSE oldEntry.first.value; entry _ PackInterest[interest]; LoganBerry.WriteEntry[db: handle.voiceInterestDB, entry: entry, replace: oldEntry # NIL ! LoganBerry.Error => IF ec = $ValueNotUnique AND oldEntry = NIL THEN {interest.interestID _ ReplaceInterestID[entry]; RETRY}]; }; DropInterest: PUBLIC PROC [handle: Handle, interest: InterestInfo] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; entry: LoganBerry.Entry; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; entry _ ReadInterest[handle, interest.vrID, interest.class, interest.refID]; IF entry = NIL THEN RETURN; LoganBerry.DeleteEntry[db: handle.voiceInterestDB, key: $IID, value: entry.first.value]; }; ReadInterest: PROC [handle: Handle, ropeID: ID _ NIL, class: Rope.ROPE _ NIL, refID: Rope.ROPE] RETURNS [entry: LoganBerry.Entry] ~ { info: InterestInfo; cursor: LoganBerry.Cursor _ LoganBerry.GenerateEntries[db: handle.voiceInterestDB, key: $RefID, start: refID, end: refID]; entry _ LoganBerry.NextEntry[cursor: cursor]; UNTIL entry = NIL DO info _ UnpackInterest[entry]; IF (ropeID = NIL OR Rope.Equal[info.refID, refID]) AND (class = NIL OR Rope.Equal[info.class, class]) THEN EXIT; entry _ LoganBerry.NextEntry[cursor: cursor]; ENDLOOP; LoganBerry.EndGenerate[cursor: cursor]; }; InterestForRef: PUBLIC PROC [handle: Handle, class: Rope.ROPE, refID: Rope.ROPE] RETURNS [interest: Interest] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; interest _ ReadInterest[handle: handle, class: class, refID: refID]; }; InterestInVoiceRope: PUBLIC PROC [handle: Handle, ropeID: ID] RETURNS [interest: Interest] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; interest _ LoganBerry.ReadEntry[db: handle.voiceInterestDB, key: $VRID, value: ropeID].entry; }; EnumerateInterestClass: PUBLIC PROC [handle: Handle, class: Rope.ROPE, proc: InterestProc] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; continue: BOOLEAN _ TRUE; entry: LoganBerry.Entry; info: InterestInfo; cursor: LoganBerry.Cursor; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; IF proc = NIL THEN Error[$NoProc, "No procedure passed to EnumerateInterestClass"]; cursor _ LoganBerry.GenerateEntries[db: handle.voiceInterestDB, key: $Class, start: class, end: class]; entry _ LoganBerry.NextEntry[cursor: cursor]; UNTIL entry = NIL OR NOT continue DO info _ UnpackInterest[entry]; continue _ proc[info]; entry _ LoganBerry.NextEntry[cursor: cursor]; ENDLOOP; LoganBerry.EndGenerate[cursor: cursor]; }; PackInterest: PUBLIC PROC [info: InterestInfo] RETURNS [interest: Interest] ~ { interest _ NIL; IF info.data # NIL THEN interest _ CONS[[$Data, info.data], interest]; interest _ CONS[[$RefID, info.refID], interest]; interest _ CONS[[$Class, info.class], interest]; interest _ CONS[[$VRID, info.vrID], interest]; interest _ CONS[[$IID, info.interestID], interest]; }; ReplaceInterestID: PROC [interest: Interest] RETURNS [newID: ID] ~ { newID _ BadUniqueID[]; interest.first.value _ newID; }; UnpackInterest: PUBLIC PROC [interest: Interest] RETURNS [info: InterestInfo] ~ { list: LoganBerry.Entry _ interest; IF interest = NIL THEN RETURN; info.interestID _ list.first.value; list _ list.rest; info.vrID _ list.first.value; list _ list.rest; info.class _ list.first.value; list _ list.rest; info.refID _ list.first.value; list _ list.rest; IF list # NIL THEN info.data _ list.first.value; [info.creator, info.timestamp] _ ParseUniqueID[info.interestID]; }; 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 [interval: Thrush.VoiceInterval] RETURNS [r: ROPE] ~ INLINE { r _ IO.PutFR["%g %g", IO.int[interval.start], IO.int[interval.length]]; }; UnmarshalInterval: PROC [r: ROPE] RETURNS [interval: Thrush.VoiceInterval] ~ INLINE { s: IO.STREAM _ IO.RIS[r]; interval.start _ IO.GetInt[s]; interval.length _ IO.GetInt[s]; }; userName: ROPE; processorID: ROPE; counter: INT; GenerateUniqueID: PROC [] RETURNS [id: ROPE] ~ { timestamp: INT _ MAX[counter, BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]]; id _ IO.PutFR["%g#%g", IO.rope[processorID], IO.int[timestamp]]; counter _ counter + 1; }; ParseUniqueID: PROC [id: ROPE] RETURNS [creatingProcessor: ROPE, timestamp: BasicTime.GMT] ~ { seconds: INT; i: INT _ Rope.Find[s1: id, s2: "#"]; creatingProcessor _ Rope.Substr[base: id, start: 0, len: i]; seconds _ Convert.IntFromRope[Rope.Substr[base: id, start: i+1]]; timestamp _ BasicTime.Update[BasicTime.earliestGMT, seconds]; }; NewUser: UserCredentials.CredentialsChangeProc ~ { userName _ UserCredentials.Get[].name; }; InitUniqueID: PROC [] RETURNS [] ~ { counter _ BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]; kicks _ 0; processorID _ ThisMachine.ProcessorID[$Hex]; counter _ CheckCounter[counter, processorID]; userName _ UserCredentials.Get[].name; UserCredentials.RegisterForChange[NewUser]; }; CheckCounter: PROC [tentativeCounter: INT, pid: ROPE] RETURNS [guaranteedCounter: INT] ~ { }; InitUniqueID[]; END. #ScriptDBImpl.mesa Copyright ำ 1987 by Xerox Corporation. All rights reserved. Polle Zellweger (PTZ) January 28, 1987 6:58:29 pm PST Routines for storing scripts in a database and manipulating their structure. Script database operations Script descriptors are stored in a LoganBerry database. Script entry descriptors are also maintained in a LoganBerry database. Prepares the given database for service. The name of the entries database is derived from the script database name by adding "Entries" after the base name. Returns the descriptor of the given script. Writes the script descriptor into the LoganBerry database.  ! LoganBerry.Error => IF ec = $ValueNotUnique THEN {script.sid _ ReplaceID[lbe]; RETRY} -- currently, script IDs are created long before they are presented to the db for storage. Others probably have already included references to them. Therefore, if they are not already unique, we got trouble. Deletes the given script (regardless of what entries may exist). Find all scripts that start in the given file. This includes all scripts with an internal first entry whose create date is not later than the file and all scripts with an external first entry whose create date is equal to the file. For this to work as written, filenames should not have version parts! Returns the descriptor of the given script. Writes the script descriptor into the LoganBerry database.  ! LoganBerry.Error => IF ec = $ValueNotUnique THEN {scriptEntry.eid _ ReplaceID[lbe]; RETRY} -- currently, script entry IDs are created long before they are presented to the db for storage. Others probably have already included references to them. Therefore, if they are not already unique, we got trouble. Deletes the given script entry (regardless of what entries may exist). Voice rope structure WARNING: For efficiency reasons, much of this code relies on the exact structure of entries stored in a LoganBerry database. For instance, it assumes that the first attribute of an entry is a voice rope (or interest) ID and does not check the attribute type to verify this. Do NOT reorder attributes unless care is taken to change the code dependencies. 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. Converts a script descriptor to a list of (attribute, value) pairs suitable for storing in the db. Converts a list of (attribute, value) pairs from the db into a script descriptor. Converts a script entry descriptor to a list of (attribute, value) pairs suitable for storing in the db. Converts a list of (attribute, value) pairs from the db into a script entry descriptor. 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 <= interval.start + interval.length <= samples the creator field is not really needed, but is included for compatibility with existing databases Returns the information associated with a voice rope header. ignore creator field Interests database Adds an entry to the interest database if a similar entry does not already exist. Removes an entry from the interest database. Searches for an entry with matching ropeID, class, and refID. Returns an interest with the given class and refID, if one exists. Returns an interest for the voice rope, if one exists. Calls the enumeration procedure for every interest of the given class. Stops when proc returns FALSE or the end of the database is encountered. Conversions (marshalling) Writes a start and length field into a rope. Parses the input rope into a start and length field. Generating unique identifiers (& keeping track of the username) A unique identifier is generated by concatenating a processor id with a pseudo-timestamp. This scheme tries harder to ensure unique ids in the first place than the voice rope scheme (which uses Rname and pseudo-timestamp) because scriptids and scriptentryids are generated and probably stored long before they are saved in the databases. Another alternative would be to store script headers and entries in the database right away, but that both makes it difficult to experiment with a script and increases the database storage and accesses. To allow experimentation, one could use id#temp, but then you're back to not knowing about uniqueness for id alone. The pseudo-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 script entries could conceivably 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 solved by checking the counter's value with the databases in the initialization code. This code depends on running on the client's machine. This routine should check pid#tentativeCounter in the scriptDB and the scriptEntriesDB to make sure that all greater UIDs have a different pid. If not, they should return a new counter that satisfies that property. Very unlikely to be needed. 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 Doug Terry, June 10, 1986 5:55:22 pm PDT changes to: AddInterest, DropInterest, LookupInterest, Write, Delete, ~, LookupVoiceRope, LookupTune Doug Terry, June 11, 1986 3:40:47 pm PDT changes to: LookupTune, Enumerate, LookupVoiceRope, EnumerateInterests, ParseInterestEntry, AddInterest, DropInterest, LookupInterest, CRQuery, RCRQuery Doug Terry, June 12, 1986 4:45:50 pm PDT changes to: Open, ReadVoiceRope, EntryToTuneList, ~, WriteVoiceRope, PackHeader, ReplaceID, UnpackHeader, PackInterest, UnpackInterest, DeleteVoiceRope, VoiceRopeContainingTune, EnumerateVoiceRopes, AddInterest, DropInterest, ReplaceInterestID, ReadInterest, InterestForRef, InterestInVoiceRope, EnumerateInterestClass, BadUniqueID, ParseUniqueID, DIRECTORY Doug Terry, June 17, 1986 12:02:00 pm PDT changes to: UnpackHeader Doug Terry, June 26, 1986 2:08:18 pm PDT changes to: TuneListInterval Doug Terry, June 26, 1986 5:03:38 pm PDT changes to: Open, DIRECTORY, IMPORTS Doug Terry, June 30, 1986 1:11:17 pm PDT changes to: PackInterest Doug Terry, June 30, 1986 2:36:30 pm PDT changes to: UnpackHeader, Open, ReadVoiceRope, WriteVoiceRope, DeleteVoiceRope, VoiceRopeContainingTune, EnumerateVoiceRopes, AddInterest, DropInterest, InterestForRef, InterestInVoiceRope, EnumerateInterestClass, NextTuneOnList, EntryToTuneList, UnpackInterest Polle Zellweger (PTZ) January 8, 1987 3:56:18 pm PST changes to: DIRECTORY, ~, ReadScriptDesc, WriteScriptDesc, DeleteScriptDesc, VoiceRopeContainingTune, FindScriptsInFile, ScriptDescToLBEntry Polle Zellweger (PTZ) January 9, 1987 12:07:17 pm PST changes to: FindScriptsStartingInFile Polle Zellweger (PTZ) January 9, 1987 5:07:47 pm PST changes to: processorID, InitUniqueID, CheckCounter, processorID, counter, GenerateUniqueID, ParseUniqueID, ScriptDescToLBEntry, LBEntryToScriptDesc, NextTuneOnList Polle Zellweger (PTZ) January 14, 1987 9:58:23 pm PST changes to: ScriptDescToLBEntry, LBEntryToScriptDesc, ScriptEntryDescToLBEntry, NextTuneOnList Polle Zellweger (PTZ) January 20, 1987 7:18:32 pm PST changes to: ScriptEntryDescToLBEntry, LBEntryToScriptDesc, ParseNextEntries, FindScriptsStartingInFile, ReadScriptEntryDesc, WriteScriptEntryDesc, DeleteScriptEntryDesc, FlattenNextEntries Polle Zellweger (PTZ) January 21, 1987 5:45:54 pm PST changes to: FlattenNextEntries Polle Zellweger (PTZ) January 28, 1987 6:55:22 pm PST changes to: Open, FindScriptsStartingInFile, ReadScriptEntryDesc, ScriptDescToLBEntry, LBEntryToScriptDesc, ScriptEntryDescToLBEntry, LBEntryToScriptEntryDesc, DIRECTORY สa˜codešœ™Kšœ<™˜>Kšœ œœ!˜5šœ<˜˜LKšœœœF˜TKšœœœc˜qKšœœœ7˜EKšœœœ&˜4Kšœœœ&˜4Kšœœœ5˜CKšœœœ#˜1K˜K˜—š œ œœ˜GK™QKšœœ˜šœœ˜šœ˜Kšœ ˜ Kšœ2˜2Kšœ#˜#Kšœ#˜#Kšœ4˜4KšœL˜LKšœC˜CKšœ)˜)KšœW˜WKšœ˜—K˜Kšœ˜—K˜K˜—š œ œœ˜RK™hKšœœœd˜rKšœœœ8˜FKšœœœ'˜5Kšœœœ'˜5KšœœœH˜VKšœœœG˜UKšœœœI˜WKšœœœ*˜8KšœœœD˜RKšœœœH˜VKšœœœ>˜LKšœœœD˜RKšœœœY˜gKšœœœ/˜=Kšœœœ1˜?Kšœœœ$˜2K˜K˜—š œ œœ˜RK™WKšœœ˜šœœ˜šœ˜Kšœ!˜!Kšœ.˜.Kšœ,˜,KšœH˜HKšœA˜AKšœ;˜;KšœE˜EKšœA˜AKšœ'˜'KšœF˜FKšœD˜DKšœC˜CKšœ$˜$Kšœ$˜$Kšœ5˜5KšœM˜MKšœ˜—K˜Kšœ˜—K˜K˜—š œœ œœ˜Mšœœ˜KšœAกœก˜GKšœ"˜"Kšœ˜—K˜K˜—š œœœœ#˜KKšœœ˜K˜K˜—š œœœ˜NK™CKš œ œœœœ˜ šœœ˜!Kšœ˜Kšœ œœ(˜;Kšœ˜—Kšœ˜K˜K™—š œ œ2œ˜jK™Kšœ%Ÿ˜CKšœ œŸA˜TKšœ œŸA˜PKšœœœ˜$Kš œœœœœ˜Kšœœœœ˜9Kšœ˜Kšœ=˜=Kšœ˜šœ˜!Kšœ˜Kšœ˜Kš œœœœœ˜Kšœ=˜=Kšœ(˜(Kšœ˜—Kšœ6™6Kšœ ˜ šœœŸ-˜UKšœG˜GKšœK˜KKšœœ˜K˜—Kšœ™š œœœœ/œŸ3˜Kšœ&˜&KšœœŸ2˜NKšœœ˜K˜—šœœ˜Kšœ:˜:—š œœœœ/œŸ˜jKšœ˜ —šœ-˜4Kšœ˜Kšœ˜Kšœœœœ˜Kšœ=˜=Kšœ(˜(Kšœ˜—KšœI™Išœ,œŸ(˜^Kšœ‚˜‚K˜—KšœœŸ2˜NKšœ˜ K˜—K˜š  œœœ˜CK˜Kšœ˜Kšœœ5˜AK™aKšœœ#˜/Kšœœ˜(Kšœ˜K˜—K˜š  œœœ œ˜8Kšœ˜Kšœ˜K˜—K˜š  œœœœ˜LJ™>Kšœ ˜ Kšœ œœœ˜Kšœ˜Kšœ˜Kšœ™Kšœ˜Kšœ4˜4Kšœ:˜:Kšœ˜K˜——™š  œ œ-˜EK™QJšœœ˜8Kšœœ˜Kšœ˜Kšœ œœ!˜5KšœO˜OKš œœ œœœ˜ZKšœ˜Kš œTœœœ œœ2œ˜ืK˜K˜—š  œ œ-˜FK™,Jšœœ˜8Kšœ˜Kšœ œœ!˜5KšœL˜LKšœ œœœ˜KšœX˜XK˜K˜—š   œœœ œœœ˜…Kšœ=™=Kšœ˜Kšœz˜zK–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-šœ œ˜Kšœ˜Kšœ œœ œ œœ œœ˜pK–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-Kšœ˜—Kšœ'˜'K˜K˜—š  œ œœœœ˜qK™BJšœœ˜8Kšœ œœ!˜5KšœD˜DK˜K˜—š œ œœœ˜^K™6Jšœœ˜8Kšœ œœ!˜5Kšœ^˜^K˜K˜—š œ œœ˜^K™Jšœœ˜8K–ซ[conv: LoganBerry.Conv _ NIL, db: LoganBerry.OpenDB, key: LoganBerry.AttributeType, start: LoganBerry.AttributeValue _ NIL, end: LoganBerry.AttributeValue _ NIL]šœ œœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ œœ!˜5KšœœœA˜SKšœg˜gK–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-š œ œœœ ˜$Kšœ˜Kšœ˜K–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-Kšœ˜—Kšœ'˜'K˜K˜—š  œ œœ˜OKšœ œ˜šœ œ˜Kšœ œ˜.—Kšœ œ!˜0Kšœ œ!˜0Kšœ œ˜.Kšœ œ$˜3K˜—K˜š œœœ œ˜DKšœ˜Kšœ˜K˜K˜—š œ œœ˜QKšœ"˜"Kšœ œœœ˜Kšœ#˜#Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜šœœ˜Kšœ˜—Kšœ@˜@K˜K˜——™š   œœœœœ˜JJšœ œœœœœœœœœ˜LJšœœœœ˜BK˜K˜—š   œœœœœ˜LJšœ œœœœœœœœœ˜KJš œ œœœœ˜!Jšœ œ˜#Jšœ œ˜#K˜K˜—š  œœ"œœœ˜SK™,KšœG˜GK˜K™—š  œœœœ$œ˜UK™4Kšœ˜Kšœ˜Kšœ˜K˜K™——™?Kšœา™าK™K™พK™Kšœž™žK™K™5K˜Kšœ œ˜Kšœ œ˜Kšœ œ˜ K˜š œœœœ˜0Kšœ œœD˜XKšœ@˜@K˜K˜—K˜š   œœœœœœ˜^K–>[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]šœ œ˜ Kšœœ˜$Kšœ<˜