<> <> <> <> <> DIRECTORY BasicTime USING [GMT, Now], DB USING [ BoolType, EntitySet, GMT, IntType, RopeType, TimeType, CreateRelship, DeclareAttribute, DeclareDomain, DeclareEntity, DeclareIndex, DeclareRelation, DeclareRelship, DestroyEntity, DestroyRelship, DomainSubset, Eq, GetF, GetName, GetP, GetPList, NextEntity, NextRelship, Null, RelationSubset, ReleaseEntitySet, ReleaseRelshipSet, SetP, SetPList, S2V, T2V, V2I, V2T], IO, Rope, WalnutDB, WalnutVoice USING [MakeInterestEntry, VoiceMoveTo], WalnutLog USING [MsgRec, MessageRecObject, currentSegment, msgIDRope, GenerateNewMsgID, RopeFromLog, TiogaTextFromLog], WalnutSendOps USING [userRName, simpleUserName, RFC822Date], WalnutStream USING [MakeLogEntry]; WalnutDBImpl: CEDAR MONITOR IMPORTS DB, IO, Rope, BasicTime, WalnutDB, WalnutLog, WalnutSendOps, WalnutStream, WalnutVoice EXPORTS WalnutDB = BEGIN OPEN DB, WalnutDB; MsgDomain: PUBLIC Domain; MsgSetDomain: PUBLIC Domain; activeMsgSet: PUBLIC MsgSet; deletedMsgSet: PUBLIC MsgSet; <<********************************************************>> <> msNumInSet: PUBLIC Relation; msNumInSetOf: PUBLIC Attribute; -- MsgSet msNumInSetIs: PUBLIC Attribute; -- INT mDateCode: PUBLIC Relation; mDateCodeOf: PUBLIC Attribute; -- Msg mDateCodeIs: PUBLIC Attribute; -- Time mSubject: PUBLIC Relation; mSubjectOf: PUBLIC Attribute; -- Msg mSubjectIs: PUBLIC Attribute; -- string mCategory: PUBLIC Relation; mCategoryOf: PUBLIC Attribute; -- Msg mCategoryIs: PUBLIC Attribute; -- MsgSet mCategoryDate: PUBLIC Attribute; -- Index on Category & Date mInReplyTo: PUBLIC Relation; mInReplyToOf: PUBLIC Attribute; -- Msg mInReplyToMsg: PUBLIC Attribute; -- Msg mInReplyToIs: PUBLIC Attribute; -- the string from the msg mTOCEntry: PUBLIC Relation; mTOCEntryOf: PUBLIC Attribute; -- Msg mTOCEntryIs: PUBLIC Attribute; -- string mLogPos: PUBLIC Relation; mLogPosOf: PUBLIC Attribute; -- Msg mPrefixPos: PUBLIC Attribute; -- int mHeadersPos: PUBLIC Attribute; -- int mMsgLength: PUBLIC Relation; mMsgLengthOf: PUBLIC Attribute; -- Msg mMsgLengthIs: PUBLIC Attribute; -- int mHasBeenRead: PUBLIC Relation; mHasBeenReadOf: PUBLIC Attribute; -- Msg mHasBeenReadIs: PUBLIC Attribute; -- bool NumInNegative: SIGNAL[msgSet: ROPE] = CODE; <<********************************************************>> SetUpdatesPendingProc: PROC[BOOL]_ DefaultUpdateProc; <<********************************************************>> <> <> <> DeclareMsg: PUBLIC ENTRY PROC[mName: ROPE, version: Version_ NewOrOld] RETURNS [msg: Msg, existed: BOOL] = <> <> <> BEGIN ENABLE UNWIND => NULL; msg_ DeclareEntity[MsgDomain, mName, OldOnly]; IF msg#NIL THEN RETURN[msg, TRUE]; IF version = NewOrOld THEN { msg_ DeclareEntity[MsgDomain, mName]; SetUpdatesPendingProc[TRUE]; }; RETURN[msg, FALSE] END; DeclareMsgSet: PUBLIC ENTRY PROC[msName: ROPE, version: Version_ NewOrOld] RETURNS [msgSet: MsgSet, existed: BOOL] = <> <> <> BEGIN ENABLE UNWIND => NULL; msgSet_ DeclareEntity[MsgSetDomain, msName, OldOnly]; IF msgSet#NIL THEN RETURN[msgSet, TRUE]; IF version = NewOrOld THEN { msgSet_ DeclareEntity[MsgSetDomain, msName]; SetUpdatesPendingProc[TRUE]; }; RETURN[msgSet, FALSE]; END; <> DestroyMsgSet: PUBLIC ENTRY PROC[msgSet: MsgSet] RETURNS[newRelList: LIST OF Relship] = <> BEGIN ENABLE UNWIND => NULL; rel, newRel: Relship; relListEnd: LIST OF Relship; rs: RelshipSet_ RelationSubset[mCategory, LIST[[mCategoryIs, msgSet]]]; UNTIL DB.Null[rel_ NextRelship[rs]] DO msg: Msg_ V2E[GetF[rel, mCategoryOf]]; IF (newRel_ RemoveFrom[msg, msgSet, rel]) # NIL THEN IF relListEnd=NIL THEN newRelList_ relListEnd_ CONS[newRel, NIL] ELSE relListEnd.rest_ relListEnd_ CONS[newRel, NIL]; ENDLOOP; ReleaseRelshipSet[rs]; IF ~EqEntities[msgSet, activeMsgSet] THEN DestroyEntity[msgSet]; SetUpdatesPendingProc[TRUE]; END; AddMsgToMsgSet: PUBLIC ENTRY PROC[msg: Msg, msgSet: MsgSet] RETURNS[rel: Relship, existed: BOOL] = <> <> { ENABLE UNWIND => NULL; IF Eq[msgSet, deletedMsgSet] THEN RETURN[NIL, FALSE]; -- can't AddTo "Deleted" [rel, existed]_ AddTo[msg, msgSet] }; RemoveMsgFromMsgSet: PUBLIC ENTRY PROC[msg: Msg, msgSet: MsgSet, rel: Relship_ NIL] RETURNS[newRel: Relship] = <> <> <> <> { ENABLE UNWIND => NULL; RETURN[RemoveFrom[msg, msgSet, rel]]}; SetMsgHasBeenRead: PUBLIC ENTRY PROC[msg: Msg] = <> BEGIN ENABLE UNWIND => NULL; []_ SetP[msg, mHasBeenReadIs, B2V[TRUE]]; SetUpdatesPendingProc[TRUE]; END; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> <> InitializeDBVars: PUBLIC ENTRY PROC = BEGIN ENABLE UNWIND => NULL; MsgDomain_ DeclareDomain[name: "Msg", segment: WalnutLog.currentSegment, estRelships: 10]; MsgSetDomain_ DeclareDomain["MsgSet", WalnutLog.currentSegment]; msNumInSet_ DeclareRelation["msNumInSet", WalnutLog.currentSegment]; msNumInSetOf_ DeclareAttribute[msNumInSet, "of", MsgSetDomain, Key]; -- MsgSet msNumInSetIs_ DeclareAttribute[msNumInSet, "is", IntType]; -- INT mDateCode_ DeclareRelation["mDateCode", WalnutLog.currentSegment]; mDateCodeOf_ DeclareAttribute[mDateCode, "of", MsgDomain, Key]; -- Msg mDateCodeIs_ DeclareAttribute[mDateCode, "is", TimeType]; -- Time: []_ DeclareIndex[mDateCode, LIST[mDateCodeIs], NewOrOld]; mSubject_ DeclareRelation["mSubject", WalnutLog.currentSegment]; mSubjectOf_ DeclareAttribute[mSubject, "of", MsgDomain, Key]; -- Msg mSubjectIs_ DeclareAttribute[mSubject, "is", RopeType]; -- string mCategory_ DeclareRelation["mCategory", WalnutLog.currentSegment]; mCategoryOf_ DeclareAttribute[mCategory, "of", MsgDomain]; -- Msg mCategoryIs_ DeclareAttribute[mCategory, "is", MsgSetDomain --, link: FALSE --]; -- MsgSet mCategoryDate_ DeclareAttribute[mCategory, "date", TimeType]; -- Time []_ DeclareIndex[mCategory, LIST[mCategoryIs, mCategoryDate], NewOrOld]; mInReplyTo_ DeclareRelation["mInReplyTo", WalnutLog.currentSegment]; mInReplyToOf_ DeclareAttribute[mInReplyTo, "of", MsgDomain, OptionalKey]; -- Msg mInReplyToIs_ DeclareAttribute[mInReplyTo, "is", RopeType]; -- Msg mTOCEntry_ DeclareRelation["mTOCEntry", WalnutLog.currentSegment]; mTOCEntryOf_ DeclareAttribute[mTOCEntry, "of", MsgDomain, Key]; -- Msg mTOCEntryIs_ DeclareAttribute[mTOCEntry, "is", RopeType]; -- string mLogPos_ DeclareRelation["mLogPos", WalnutLog.currentSegment]; mLogPosOf_ DeclareAttribute[mLogPos, "of", MsgDomain, Key]; -- Msg mPrefixPos_ DeclareAttribute[mLogPos, "prefix", IntType]; -- int mHeadersPos_ DeclareAttribute[mLogPos, "headers", IntType]; -- int mMsgLength_ DeclareRelation["mMsgLength", WalnutLog.currentSegment]; mMsgLengthOf_ DeclareAttribute[mMsgLength, "of", MsgDomain, Key]; -- Msg mMsgLengthIs_ DeclareAttribute[mMsgLength, "length", IntType]; -- int mHasBeenRead_ DeclareRelation["mHasBeenRead", WalnutLog.currentSegment]; mHasBeenReadOf_ DeclareAttribute[mHasBeenRead, "of", MsgDomain, Key]; -- Msg mHasBeenReadIs_ DeclareAttribute[mHasBeenRead, "is", BoolType]; -- bool activeMsgSet_ DeclareEntity[MsgSetDomain, "Active"]; -- NewOrOld deletedMsgSet_ DeclareEntity[MsgSetDomain, "Deleted"]; END; TiogaMsgFromLog: PUBLIC PROC[msg: Msg] RETURNS[contents: TiogaContents, startPos, length: INT] = <> BEGIN startPos_ V2I[GetP[msg, mHeadersPos]]; length_ V2I[GetP[msg, mMsgLengthIs]]; contents_ WalnutLog.TiogaTextFromLog[startPos, length]; END; NumInMsgSet: PUBLIC ENTRY PROC[msgSet: MsgSet] RETURNS[INT] = { ENABLE UNWIND => NULL; RETURN[V2I[GetP[msgSet, msNumInSetIs]]] }; ArchiveMsgSet: PUBLIC ENTRY PROC[msgSet: MsgSet, strm: IO.STREAM, doDelete: BOOL] RETURNS[newRelList: LIST OF Relship] = <> <> BEGIN ENABLE UNWIND => NULL; rel, newRel: Relship; endRL: LIST OF Relship_ NIL; rs: RelshipSet_ RelationSubset[mCategory, LIST[[mCategoryIs, msgSet]]]; restOfPrefix: ROPE_ Rope.Cat["\nCategories: ", DB.GetName[msgSet], "\n"]; UNTIL DB.Null[rel_ NextRelship[rs]] DO msg: Msg_ V2E[GetF[rel, mCategoryOf]]; startPos: INT_ V2I[GetP[msg, mHeadersPos]]; length: INT_ V2I[GetP[msg, mMsgLengthIs]]; fullText: ROPE_ WalnutLog.RopeFromLog[startPos, length]; prefix: ROPE_ Rope.Cat[WalnutLog.msgIDRope, ": ", DB.GetName[msg], restOfPrefix]; []_ WalnutStream.MakeLogEntry[strm, message, fullText, prefix]; IF doDelete THEN { newRel_ RemoveFrom[msg, msgSet, rel]; IF newRelList = NIL THEN newRelList_ endRL_ CONS[newRel, NIL] ELSE endRL_ endRL.rest_ CONS[newRel, NIL]; }; ENDLOOP; ReleaseRelshipSet[rs]; END; NameToEntity: PUBLIC ENTRY PROC[d: Domain, name: ROPE, version: Version] RETURNS[e: Entity] = BEGIN ENABLE UNWIND => NULL; e_ DeclareEntity[d, name, OldOnly]; IF e=NIL AND version=NewOrOld THEN e_ DeclareEntity[d, name, NewOnly]; END; GetEntitiesInDomain: PUBLIC ENTRY PROC[d: Domain, alphaOrder: BOOL] RETURNS[eL: LIST OF Entity] = <> BEGIN ENABLE UNWIND => NULL; eLend: LIST OF Entity; es: EntitySet_ IF alphaOrder THEN DomainSubset[d, "", "\177"] ELSE DomainSubset[d]; e: Entity_ NextEntity[es]; IF e=NIL THEN RETURN[NIL]; eL_ eLend_ CONS[e, NIL]; UNTIL DB.Null[e_ NextEntity[es]] DO eLend_ eLend.rest_ CONS[e, NIL]; ENDLOOP; ReleaseEntitySet[es]; END; RelationSubsetList: PUBLIC ENTRY PROC[r: Relation, constraint: AttributeValueList_ NIL] RETURNS[relList: LIST OF Relship] = BEGIN ENABLE UNWIND => NULL; rs: RelshipSet_ RelationSubset[r, constraint]; rel: Relship_ NextRelship[rs]; rLend: LIST OF Relship; IF rel = NIL THEN RETURN; relList_ rLend_ CONS[rel, NIL]; UNTIL DB.Null[rel_ NextRelship[rs]] DO rLend_ rLend.rest_ CONS[rel, NIL]; ENDLOOP; ReleaseRelshipSet[rs]; END; GetFE: PUBLIC ENTRY PROC[rel: Relship, a: Attribute] RETURNS [Entity] = { ENABLE UNWIND => NULL; RETURN[V2E[DB.GetF[rel, a]]] }; GetName: PUBLIC ENTRY PROC[e: Entity] RETURNS [ROPE] = { ENABLE UNWIND => NULL; RETURN[DB.GetName[e]] }; Null: PUBLIC ENTRY PROC[e: Entity] RETURNS [BOOL] = { ENABLE UNWIND => NULL; RETURN[DB.Null[e]] }; MGetP: PUBLIC ENTRY PROC[m: Msg, prop: Attribute] RETURNS [Value] = <> <> { ENABLE UNWIND => NULL; RETURN[DB.GetP[m, prop]] }; MSetPList: PUBLIC ENTRY PROC[m: Msg, prop: Attribute, vl: LIST OF Value] = <> <> BEGIN ENABLE UNWIND => NULL; DB.SetPList[m, prop, vl]; SetUpdatesPendingProc[TRUE]; END; MSetP: PUBLIC ENTRY PROC[m: Msg, prop: Attribute, v: Value] RETURNS [rel: Relship] = <> <> <> BEGIN ENABLE UNWIND => NULL; rel_ DB.SetP[m, prop, v]; SetUpdatesPendingProc[TRUE]; END; AcquireDBLock: PUBLIC ENTRY PROC[procToCall: PROC] = <> BEGIN ENABLE UNWIND => NULL; procToCall[]; END; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> RegisterUpdatesPendingProc: PUBLIC ENTRY PROC[proc: PROC[BOOL]] = { ENABLE UNWIND => NULL; SetUpdatesPendingProc_ proc}; UnRegisterUpdatesPendingProc: PUBLIC ENTRY PROC[proc: PROC[BOOL]] = { ENABLE UNWIND => NULL; IF SetUpdatesPendingProc = proc THEN SetUpdatesPendingProc_ DefaultUpdateProc }; DefaultUpdateProc: PROC[BOOL] = { NULL }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> MsgRecToMsg: PUBLIC PROC[mr: WalnutLog.MsgRec] RETURNS[msg: Msg, mExisted: BOOL, newMsgSetList: LIST OF MsgSet, newRelList: LIST OF RelshipMsgSetPair] = <> BEGIN CategoryRelshipList: PROC[msgSetName: ROPE] = BEGIN msgSet: MsgSet; msExisted, rExisted: BOOL; rel: Relship; [msgSet, msExisted]_ DeclareMsgSet[msgSetName, NewOrOld]; IF ~msExisted THEN newMsgSetList_ CONS[msgSet, newMsgSetList]; [rel, rExisted]_ AddToMsgSet[msg, msgSet]; IF ~rExisted THEN newRelList_ CONS[[rel, msgSet], newRelList]; END; DO [msg, mExisted]_ DeclareMsg[mr.gvID, NewOrOld]; IF ~mExisted THEN BEGIN <> pos: INT_ mr.gvID.Find["$"]+1; -- $ is not allowed in names, so it's unique date: ROPE_ GetTocDate[mr]; toc: ROPE_ IF mr.from.Equal[WalnutSendOps.userRName, FALSE] OR mr.from.Equal[WalnutSendOps.simpleUserName, FALSE] THEN Rope.Concat["To: ", mr.to] ELSE mr.from; mr.tocEntry_ Rope.Cat[date, " ", toc]; SetMsgAttributes[msg, mr]; IF mr.voiceID.Length[] # 0 THEN WalnutVoice.MakeInterestEntry[mr.voiceID, mr.gvID]; IF mr.categoriesList = NIL THEN CategoryRelshipList["Active"] -- add to msgSets ELSE FOR cL: LIST OF ROPE_ mr.categoriesList, cL.rest UNTIL cL = NIL DO CategoryRelshipList[cL.first] ENDLOOP; RETURN END -- new msg ELSE BEGIN <> len: INT_ V2I[GetP[msg, mMsgLengthIs]]; subj: ROPE; IF len # mr.msgLength THEN { WalnutLog.GenerateNewMsgID[mr]; LOOP}; subj_ V2S[GetP[msg, mSubjectIs]]; IF ~Rope.Equal[subj, mr.subject] THEN { WalnutLog.GenerateNewMsgID[mr]; LOOP}; RETURN; -- old msg END; ENDLOOP; -- in case need to change mr.gvID END; GetTocDate: PROC[mr: WalnutLog.MsgRec] RETURNS[date: ROPE] = BEGIN tyme: BasicTime.GMT; IF mr.date = NIL THEN mr.date_ WalnutSendOps.RFC822Date[]; BEGIN -- get date into canonical form ris: IO.STREAM_ IO.RIS[mr.date]; tyme_ IO.GetTime[ris ! IO.Error => GOTO badTime]; EXITS badTime => tyme_ BasicTime.Now[]; END; date_ Rope.Substr[WalnutSendOps.RFC822Date[tyme], 0, 9]; -- only want first 9 chars of date DO i: INT_ date.Find["-"]; IF i < 0 THEN RETURN; date_ date.Replace[i, 1, " "]; ENDLOOP; END; AddToMsgSet: ENTRY PROC[msg: Msg, msgSet: MsgSet] RETURNS[rel: Relship, existed: BOOL] = { ENABLE UNWIND => NULL; [rel, existed]_ AddTo[msg, msgSet] }; SetMsgAttributes: ENTRY PROC[msg: Msg, mr: WalnutLog.MsgRec] = BEGIN ENABLE UNWIND => NULL; []_ SetP[msg, mDateCodeIs, T2V[LOOPHOLE[mr.dateCode, DB.GMT]]]; []_ SetP[msg, mInReplyToIs, S2V[mr.inReplyTo]]; []_ SetP[msg, mHasBeenReadIs, B2V[mr.hasBeenRead]]; []_ SetP[msg, mTOCEntryIs, S2V[mr.tocEntry]]; []_ SetP[msg, mSubjectIs, S2V[mr.subject]]; []_ SetP[msg, mPrefixPos, I2V[mr.prefixPos]]; []_ SetP[msg, mMsgLengthIs, I2V[mr.msgLength]]; []_ SetP[msg, mHeadersPos, I2V[mr.headersPos]]; END; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> AddTo: INTERNAL PROC[msg: Msg, msgSet: MsgSet] RETURNS[rel: Relship, existed: BOOL] = BEGIN avl: AttributeValueList; <> <<{ avl_ LIST[[mCategoryOf, msg], [mCategoryIs, deletedMsgSet],>> <<[mCategoryDate, GetP[msg, mDateCodeIs]]];>> <> <> <<{ DestroyRelship[rel]; ChangeNumInSet[deletedMsgSet, -1]};>> <<};>> avl_ LIST[[mCategoryOf, msg], [mCategoryIs, msgSet], [mCategoryDate, GetP[msg, mDateCodeIs]]]; rel_ DeclareRelship[mCategory, avl, OldOnly]; IF existed_ (rel # NIL) THEN RETURN; -- msg is already in this msgSet rel_ CreateRelship[mCategory, avl]; ChangeNumInSet[msgSet, 1]; SetUpdatesPendingProc[TRUE]; WalnutVoice.VoiceMoveTo[DB.GetName[msgSet], DB.GetName[msg]]; END; RemoveFrom: INTERNAL PROC[msg: Msg, msgSet: MsgSet, rel: Relship] RETURNS[newRel: Relship] = <> <> BEGIN IF rel = NIL THEN { rs: RelshipSet_ RelationSubset[mCategory, LIST[[mCategoryOf, msg]]]; UNTIL DB.Null[rel_ NextRelship[rs]] DO IF Eq[msgSet, V2E[GetF[rel, mCategoryIs]]] THEN EXIT; ENDLOOP; ReleaseRelshipSet[rs]; }; IF rel = NIL THEN RETURN; -- ignore this call DestroyRelship[rel]; -- MEraseP[msg, mCategoryIs, msgSet]; ChangeNumInSet[msgSet, -1]; IF GetPList[msg, mCategoryIs] = NIL THEN newRel_ AddTo[msg, deletedMsgSet].rel; SetUpdatesPendingProc[TRUE]; END; ChangeNumInSet: INTERNAL PROC[msgSet: MsgSet, inc: INT] = INLINE BEGIN num: INT_ V2I[GetP[msgSet, msNumInSetIs]] + inc; IF (inc < 0) AND (num = -1) THEN SIGNAL NumInNegative[DB.GetName[msgSet]]; []_ SetP[msgSet, msNumInSetIs, I2V[num]]; END; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> <> notExist: ROPE = "MsgSet: %g doesn't exist"; msNumIn: ROPE = "msNumInSetIs for MsgSet: %g is %g messages"; enumNumIn: ROPE = "Enumeration of MsgSet: %g says %g messages"; NumIn: PROC[msName: ROPE] RETURNS[ans: ROPE] = BEGIN msgSet: MsgSet_ DeclareEntity[MsgSetDomain, msName, OldOnly]; num: INT; IF msgSet = NIL THEN RETURN[IO.PutFR[notExist, IO.rope[msName]]]; num_ V2I[GetP[msgSet, msNumInSetIs]]; RETURN[IO.PutFR[msNumIn, IO.rope[msName], IO.int[num]]]; END; Enum: PROC[msName: ROPE] RETURNS[ans: ROPE] = BEGIN msgSet: MsgSet_ DeclareEntity[MsgSetDomain, msName, OldOnly]; num: INT_ 0; rel: Relship; rs: RelshipSet; IF msgSet = NIL THEN RETURN[IO.PutFR[notExist, IO.rope[msName]]]; rs_ RelationSubset[mCategory, LIST[[mCategoryIs, msgSet]]]; UNTIL DB.Null[rel_ NextRelship[rs]] DO num_ num + 1; ENDLOOP; ReleaseRelshipSet[rs]; RETURN[IO.PutFR[enumNumIn, IO.rope[msName], IO.int[num]]]; END; SetNumIn: PROC[msName: ROPE, num: INT] RETURNS[ans: ROPE] = BEGIN msgSet: MsgSet_ DeclareEntity[MsgSetDomain, msName, OldOnly]; IF msgSet = NIL THEN RETURN[IO.PutFR[notExist, IO.rope[msName]]]; []_ SetP[msgSet, msNumInSetIs, I2V[num]]; RETURN[IO.PutFR["msNumInSetIs for MsgSet: %g has been set to %g", IO.rope[msName], IO.int[num]]]; END; CheckAll: PROC RETURNS[ans: ROPE] = BEGIN rs: RelshipSet; rel: Relship; num, enum: INT; msgSet: Entity; eL: LIST OF Entity_ GetEntitiesInDomain[d: MsgSetDomain, alphaOrder: TRUE]; FOR elT: LIST OF Entity_ eL, elT.rest UNTIL elT=NIL DO msgSet_ elT.first; num_ V2I[GetP[msgSet, msNumInSetIs]]; enum_ 0; rs_ RelationSubset[mCategory, LIST[[mCategoryIs, msgSet]]]; UNTIL DB.Null[rel_ NextRelship[rs]] DO enum_ enum + 1; ENDLOOP; ReleaseRelshipSet[rs]; IF num # enum THEN ans_ Rope.Cat[ans, " ", GetName[msgSet]]; ENDLOOP; IF ans.Length[] = 0 THEN RETURN["All msgsets are ok"]; END; DateCode: PROC[mName: ROPE] RETURNS[ans: ROPE] = BEGIN msg: Msg_ DeclareEntity[MsgDomain, mName, OldOnly]; date: BasicTime.GMT_ V2T[GetP[msg, mDateCodeIs]]; dr: ROPE_ WalnutSendOps.RFC822Date[date]; IF msg = NIL THEN RETURN[IO.PutFR["Msg: %g doesn't exist", IO.rope[mName]]]; date_ V2T[GetP[msg, mDateCodeIs]]; RETURN[IO.PutFR["Msg: %g has DateCode: %g", IO.rope[mName], IO.rope[dr]]]; END; GetMsgAttributes: PROC[name: ROPE] RETURNS[mr: WalnutLog.MsgRec] = BEGIN msg: Msg_ DeclareEntity[MsgDomain, name, OldOnly]; IF DB.Null[msg] THEN RETURN[NIL]; mr_ NEW[WalnutLog.MessageRecObject]; mr.dateCode_ V2T[GetP[msg, mDateCodeIs]]; mr.inReplyTo_ V2S[GetP[msg, mInReplyToIs]]; mr.hasBeenRead_ V2B[GetP[msg, mHasBeenReadIs]]; mr.tocEntry_ V2S[GetP[msg, mTOCEntryIs]]; mr.subject_ V2S[GetP[msg, mSubjectIs]]; mr.prefixPos_ V2I[GetP[msg, mPrefixPos]]; mr.msgLength_ V2I[GetP[msg, mMsgLengthIs]]; mr.headersPos_ V2I[GetP[msg, mHeadersPos]]; END; END.