<> <> <> <> <> DIRECTORY DB USING [ Aborted, Error, Failure ], -- Cheery interface, in't it? DBEnvironment USING [ ErrorCode ], GVBasics, IO, Nuthatch, NuthatchLog, NuthatchDB, NuthatchUpdater, Rope, BasicTime, Thrush; NuthatchUpdaterImpl: CEDAR MONITOR IMPORTS DB, IO, NuthatchLog, NuthatchDB, Rope EXPORTS NuthatchUpdater = { OPEN NuthatchDB, NuthatchUpdater; <> <<>> <<1. Opens a readonly transaction on the Nuthatch segment and the user's Nuthatch log. Under the same transaction, examine the log to get its length, and compare it against the user's log-read-point information in the database. If they're the same, no updating is necessary. Otherwise,>> <<>> <<2. Prepare to update the database according to the log. Upgrade the transaction to read-write the database. (Still readonly for the log.) For each line in the log between the read point and the end,>> <<>> <<. parse it into a valid database update command,>> <<. call the Nuthatch DB routine to perform the update. Update the read-point.>> <<. mark the transaction>> <<>> <<3. Close the transaction. >> <<>> ROPE: TYPE = Rope.ROPE; DoLogEntries: PUBLIC ENTRY PROC [nuthatchUserHandle: Nuthatch.NuthatchUserHandle, close: BOOL] = TRUSTED { ENABLE UNWIND => NULL; <> cTrans: REF ANY_NIL; cWhat: ATOM_NIL; cInfo: ROPE_NIL; cCode: DBEnvironment.ErrorCode; cError: BOOL_FALSE; TRUSTED { ENABLE { NuthatchLog.Error => SELECT code FROM aborted => RETRY; <> failed => NULL; -- Communication failure or other environmental problem. give up. <> ENDCASE; DB.Aborted => { cTrans _ trans; RETRY; }; DB.Error => { cCode _ code; cError _ TRUE; GOTO Failed; }; DB.Failure => { cTrans _ trans; cWhat _ what; cInfo_info; GOTO Failed; }; }; logEntryStream: IO.STREAM; token: Rope.ROPE; userName: GVBasics.RName; tuneNumber: INT; recordedTime: BasicTime.GMT; referenceCount: INT; samples: INT; startingSample: INT; encryptionKeyRope: Rope.ROPE; type: Rope.ROPE; voiceFileID: Nuthatch.VoiceFileID; refIDType: ROPE; refID: ROPE; DoProc: SAFE PROC[ voiceFileID:Nuthatch.VoiceFileID, refIDType: Nuthatch.IDType, refID: Nuthatch.ID, user: GVBasics.RName, close: BOOL ]; doType: NAT_0; IF cTrans#NIL THEN NuthatchDB.AbortTransaction[]; cTrans _ NIL; NuthatchLog.SetLogIndex[nuthatchUserHandle]; DO IF ([logEntryStream,] _ NextEntryStream[nuthatchUserHandle]).endOfLog THEN EXIT; token _ IO.GetCedarTokenRope[logEntryStream].token; SELECT TRUE FROM token.Equal["Catalog"] => doType_3; token.Equal["Create"] => { doType_1; DoProc_NuthatchDB.MakeInterestEntry; }; token.Equal["AddRef"] => { doType_1; DoProc_NuthatchDB.AddInterest; }; token.Equal["AddUserRef"] => { doType_2; DoProc_NuthatchDB.AddInterest; }; token.Equal["LoseRef"] => { doType_1; DoProc_NuthatchDB.LoseInterest; }; token.Equal["LoseUserRef"] => { doType_2; DoProc_NuthatchDB.LoseInterest; }; token.Equal["RemoveRef"] => { doType_1; DoProc_NuthatchDB.RemoveInterestEntry; }; token.Equal["RemoveUserRef"] => { doType_2; DoProc_NuthatchDB.RemoveInterestEntry; }; ENDCASE; SELECT doType FROM 1 => { voiceFileID _ GetVoiceFileID[logEntryStream]; logEntryStream _ NextEntryStream[nuthatchUserHandle].s; userName _GetUserName[logEntryStream]; [refIDType, refID] _ GetRefIDS[nuthatchUserHandle]; DoProc[voiceFileID, refIDType, refID, userName, FALSE] }; 2 => { userName _ GetUserName[logEntryStream]; [refIDType, refID] _ GetRefIDS[nuthatchUserHandle]; DoProc[NIL, refIDType, refID, userName, FALSE]; }; 3 => { voiceFileID _ GetVoiceFileID[logEntryStream]; logEntryStream _ NextEntryStream[nuthatchUserHandle].s; userName _GetUserName[logEntryStream]; tuneNumber _IO.GetInt[logEntryStream]; recordedTime_logEntryStream.GetTime[]; logEntryStream _ NextEntryStream[nuthatchUserHandle].s; referenceCount_IO.GetInt[logEntryStream]; samples_IO.GetInt[logEntryStream]; startingSample_IO.GetInt[logEntryStream]; encryptionKeyRope _ IO.PutFR["%bB %bB", IO.card[IO.GetCard[logEntryStream]], IO.card[IO.GetCard[logEntryStream]]]; type_IO.GetCedarTokenRope[logEntryStream].token; [] _ NuthatchDB.CatalogVoiceFile[ msg: voiceFileID, tuneNumber: tuneNumber, recordTime: recordedTime, creator: userName, samples: samples, startSample: startingSample, encryptionKeyRope: encryptionKeyRope, type: type, referenceCount: referenceCount, close: FALSE ]; }; ENDCASE => ERROR; ENDLOOP; NuthatchDB.MarkTransaction[]; -- be sure that updated read point is truth NuthatchLog.UpdateLogIndex[nuthatchUserHandle]; -- If aborts, it's before getting new val. NuthatchDB.SetReadPoint[nuthatchUserHandle, close]; -- Aborted retry does only this EXITS Failed => { NuthatchDB.AbortTransaction[]; IF cError THEN DB.Error[cCode] ELSE DB.Failure[cTrans, cWhat, cInfo]; }; }; }; GetVoiceFileID: PROC[s: IO.STREAM] RETURNS [voiceFileID: ROPE] = { []_s.SkipWhitespace[]; -- flush blanks -- voiceFileID _s.GetLineRope[]; }; NextEntryStream: PROC[nuthatchUserHandle: Nuthatch.NuthatchUserHandle] RETURNS [s: IO.STREAM_NIL, endOfLog: BOOL] = { logEntryRope: ROPE; [logEntryRope, endOfLog] _ NuthatchLog.ReadLogEntry[nuthatchUserHandle]; RETURN[IO.RIS[logEntryRope], endOfLog]; }; GetRefIDS: PROC[nuthatchUserHandle: Nuthatch.NuthatchUserHandle] RETURNS [refIDType, refID: ROPE_NIL] = { s: IO.STREAM = NextEntryStream[nuthatchUserHandle].s; refIDType _ s.GetCedarTokenRope[].token; []_s.SkipWhitespace[]; -- flush blanks refID _ s.GetLineRope[]; -- rest of line is ID }; GetUserName: PROC[stream: IO.STREAM] RETURNS [userName: Rope.ROPE] = { RETURN[stream.GetTokenRope[IO.IDProc].token]; }; }.