-- File: WalnutDBAccessImpl.mesa -- Contents: -- types and procedures for conversion of record-form message to entity. -- actual access to walnut database (WalnutDBLockImpl does the locking) -- Created by: Willie-Sue November 2, 1982 -- Last edited by: -- Rick Cattell on XXX -- Willie-Sue Hoo-Hah on March 24, 1983 10:06 am DIRECTORY DB, DateAndTime, Rope, IO, VFonts, WalnutDB, WalnutDBAccess, WalnutDBLock, WalnutDBLog, WalnutParse, WalnutSendMail, WalnutWindow; WalnutDBAccessImpl: CEDAR PROGRAM IMPORTS DB, DateAndTime, Rope, VFonts, WalnutDB, WalnutDBLock, WalnutDBLog, WalnutSendMail, WalnutWindow EXPORTS WalnutDB, WalnutDBAccess = BEGIN OPEN DB, WalnutDB; -- ******************************************************** -- Walnut DB types and data SchemaVersionTime: PUBLIC GreenwichMeanTime_ DateAndTime.Parse["March 24, 1983 9:24 am"].dt; Msg, MsgSet: TYPE = Entity; MsgDomain: PUBLIC Domain; MsgSetDomain: PUBLIC Domain; walnutLogInfo: PUBLIC Relation; -- used to keep current info about the log file wExpectedLength: PUBLIC Attribute; -- int (how long the log file is) wExpectedDBPos: PUBLIC Attribute; -- int (how much of the log has been parsed into Msgs) wStartExpungePos: PUBLIC Attribute; -- int (where in log file the last/current expunge started) wCopyInProgress: PUBLIC Attribute; -- bool (TRUE while Copying onto tail of log file) wSchemaVersion: PUBLIC Attribute; -- time (for keeping track of changes in walnut's schema) walnutInfoRelship: PUBLIC Relship; -- need fetch & check only once -- Relations on Msg entities: all have one relship per Msg unless otherwise noted. msNumInSet: PUBLIC Relation; msNumInSetOf: PUBLIC Attribute; -- MsgSet msNumInSetIs: PUBLIC Attribute; -- int mDateCode: PUBLIC Relation; mDateCodeOf: PUBLIC Attribute; -- Msg mDateCodeIs: PUBLIC Attribute; -- TIME, from date in msg 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; --Time 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 -- schemaVersion is num found in database SchemaMismatch: PUBLIC SIGNAL[schemaVersion: GreenwichMeanTime] = CODE; -- ******************************************************** blankWidth: INT_ VFonts.CharWidth[' ]; -- in default font blanks: ROPE_ " "; -- lotsa blanks -- ******************************************************** DoInitializeDBVars: PUBLIC PROC = BEGIN MsgDomain_ DeclareDomain[name: "Msg", segment: $Walnut, estRelships: 10]; MsgSetDomain_ DeclareDomain["MsgSet", $Walnut]; walnutLogInfo_ DeclareRelation["walnutLogInfo", $Walnut, OldOnly]; IF walnutLogInfo#NIL THEN DO -- check schema version numbers { rs: RelshipSet; curVersion: GreenwichMeanTime; wSchemaVersion_ DeclareAttribute[walnutLogInfo, "wSchemaVersion", TimeType]; rs_ RelationSubset[walnutLogInfo]; walnutInfoRelship_ NextRelship[rs]; -- should only be one relship in this relation IF NextRelship[rs] # NIL THEN ERROR; IF walnutInfoRelship = NIL THEN {ReleaseRelshipSet[rs]; EXIT}; ReleaseRelshipSet[rs]; curVersion_ V2T[DB.GetF[walnutInfoRelship, wSchemaVersion]]; IF curVersion#SchemaVersionTime THEN SIGNAL SchemaMismatch[curVersion]; EXIT; }; ENDLOOP ELSE { walnutLogInfo_ DeclareRelation["walnutLogInfo", $Walnut]; wSchemaVersion_ DeclareAttribute[walnutLogInfo, "wSchemaVersion", TimeType]; }; wExpectedLength_ DeclareAttribute[walnutLogInfo, "wExpectedLength", IntType]; wExpectedDBPos_ DeclareAttribute[walnutLogInfo, "wExpectedDBPos", IntType]; wStartExpungePos_ DeclareAttribute[walnutLogInfo, "wStartExpungePos", IntType]; wCopyInProgress_ DeclareAttribute[walnutLogInfo, "wCopyInProgress", BoolType]; BEGIN rs: RelshipSet_ RelationSubset[walnutLogInfo]; walnutInfoRelship_ NextRelship[rs]; -- should only be one relship in this relation IF NextRelship[rs] # NIL THEN ERROR; IF walnutInfoRelship = NIL THEN walnutInfoRelship_ CreateRelship[walnutLogInfo]; ReleaseRelshipSet[rs]; END; []_ SetF[walnutInfoRelship, wSchemaVersion, T2V[SchemaVersionTime]]; msNumInSet_ DeclareRelation["msNumInSet", $Walnut]; msNumInSetOf_ DeclareAttribute[msNumInSet, "of", MsgSetDomain, Key]; -- MsgSet msNumInSetIs_ DeclareAttribute[msNumInSet, "is", IntType]; -- INT mDateCode_ DeclareRelation["mDateCode", $Walnut]; mDateCodeOf_ DeclareAttribute[mDateCode, "of", MsgDomain, Key]; -- Msg mDateCodeIs_ DeclareAttribute[mDateCode, "is", TimeType]; -- Time: mSubject_ DeclareRelation["mSubject", $Walnut]; mSubjectOf_ DeclareAttribute[mSubject, "of", MsgDomain, Key]; -- Msg mSubjectIs_ DeclareAttribute[mSubject, "is", StringType]; -- string mCategory_ DeclareRelation["mCategory", $Walnut]; 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", $Walnut]; mInReplyToOf_ DeclareAttribute[mInReplyTo, "of", MsgDomain, OptionalKey]; -- Msg mInReplyToIs_ DeclareAttribute[mInReplyTo, "is", StringType]; -- Msg mTOCEntry_ DeclareRelation["mTOCEntry", $Walnut]; mTOCEntryOf_ DeclareAttribute[mTOCEntry, "of", MsgDomain, Key]; -- Msg mTOCEntryIs_ DeclareAttribute[mTOCEntry, "is", StringType]; -- string mLogPos_ DeclareRelation["mLogPos", $Walnut]; mLogPosOf_ DeclareAttribute[mLogPos, "of", MsgDomain, Key]; -- Msg mPrefixPos_ DeclareAttribute[mLogPos, "prefix", IntType]; -- int mHeadersPos_ DeclareAttribute[mLogPos, "headers", IntType]; -- int mMsgLength_ DeclareRelation["mMsgLength", $Walnut]; mMsgLengthOf_ DeclareAttribute[mMsgLength, "of", MsgDomain, Key]; -- Msg mMsgLengthIs_ DeclareAttribute[mMsgLength, "length", IntType]; -- int mHasBeenRead_ DeclareRelation["mHasBeenRead", $Walnut]; mHasBeenReadOf_ DeclareAttribute[mHasBeenRead, "of", MsgDomain, Key]; -- Msg mHasBeenReadIs_ DeclareAttribute[mHasBeenRead, "is", BoolType]; -- bool WalnutWindow.activeMsgSet_ DeclareEntity[MsgSetDomain, "Active"]; -- NewOrOld WalnutWindow.deletedMsgSet_ DeclareEntity[MsgSetDomain, "Deleted"]; END; DoMsgRecToMsg: PUBLIC PROC[mr: MsgRec] RETURNS[msg: Msg, existed: BOOL] = -- Converts msg from record to entity form BEGIN date, toc: ROPE; pos: INT; [msg, existed] _ DoDeclareMsg[mr.gvID]; mr.msg_ msg; IF existed THEN RETURN; -- make up TOC entry pos_ mr.gvID.Find["$"]+1; -- $ is not allowed in names, so it's unique date_ mr.gvID.Substr[mr.gvID.Find["@", pos]+1, 9]; --magic, almost toc_ IF mr.from.Equal[WalnutSendMail.userRName, FALSE] OR mr.from.Equal[WalnutSendMail.simpleUserName, FALSE] THEN Rope.Concat["To: ", mr.to] ELSE mr.from; mr.tocEntry_ SquashRopeIntoWidth[Rope.Cat[date, " ", toc], 170]; -- mCategoryIs is set by the receiver, so can update displays []_ DB.SetP[e: msg, aIs: mDateCodeIs, v: T2V[mr.dateCode]]; []_ DB.SetP[e: msg, aIs: mInReplyToIs, v: S2V[mr.inReplyTo]]; []_ DB.SetP[e: msg, aIs: mHasBeenReadIs, v: B2V[mr.hasBeenRead]]; []_ DB.SetP[e: msg, aIs: mTOCEntryIs, v: S2V[mr.tocEntry]]; []_ DB.SetP[e: msg, aIs: mSubjectIs, v: S2V[mr.subject]]; END; SquashRopeIntoWidth: PROC[s: ROPE, colWidth: INT] RETURNS[ROPE] = -- Truncates s with "..." or expands it with blanks, so that it is about -- colWidth characters wide. Not exact, uses a few heuristics here... BEGIN blankCount: INT; width: INT_ VFonts.StringWidth[s]; DO IF width<= colWidth THEN EXIT; -- truncate BEGIN guessLength: INT_ s.Length[] * colWidth / width; s_ Rope.Cat[s.Substr[0, MAX[0, guessLength-4]], "..."]; width_ VFonts.StringWidth[s]; END; ENDLOOP; -- At this point s is shorter than colWidth and we want to extend it with blanks blankCount_ (colWidth - width) / blankWidth; IF blankCount>0 THEN s_ Rope.Cat[s, Rope.Substr[blanks, 0, MIN[blankCount, blanks.Length[]]]]; RETURN[s] END; GetExpectedLogLength: PUBLIC PROC RETURNS[INT] = -- Retrieves last stored log length from database (used for recovery from crash) { RETURN[WalnutDBLock.GetWalnutInfo[wExpectedLength]]}; GetExpectedDBLogPos: PUBLIC PROC RETURNS[INT] = -- Retrieves last stored DB log pos from database (used for recovery from crash) { RETURN[WalnutDBLock.GetWalnutInfo[wExpectedDBPos]]}; GetStartExpungePos: PUBLIC PROC RETURNS[INT] = -- Retrieves the time at which new mail was last retrieved { RETURN[WalnutDBLock.GetWalnutInfo[wStartExpungePos]]}; GetCopyInProgress: PUBLIC PROC RETURNS [BOOL] = -- returns TRUE if a copy onto tail of log file is in progress { RETURN[WalnutDBLock.IsCopyInProgress[]]}; DoDeclareMsg: PUBLIC PROC[mName: ROPE] RETURNS [msg: Msg, existed: BOOL] = -- Creates a new Msg entity, and sets its name to be mName. -- is called when messages have been read from the log or another file. -- If the Msg already exists, returns it and returns existed=TRUE. BEGIN msg_ DeclareEntity[MsgDomain, mName, OldOnly]; IF msg#NIL THEN RETURN[msg, TRUE]; msg_ DeclareEntity[MsgDomain, mName]; WalnutWindow.SetWalnutUpdatesPending[TRUE]; RETURN[msg, FALSE] END; DoDeclareMsgSet: PUBLIC PROC[mName: ROPE] RETURNS [msgSet: MsgSet, existed: BOOL] = -- Creates a new MsgSet entity, and sets its name to be mName. -- is called when user creates a message set, or reads a log file -- If the MsgSet already exists, returns it. BEGIN msgSet_ DeclareEntity[MsgSetDomain, mName, OldOnly]; IF msgSet#NIL THEN RETURN[msgSet, TRUE]; msgSet_ WalnutDBLog.DoCreateMsgSet[mName]; WalnutWindow.SetWalnutUpdatesPending[TRUE]; RETURN[msgSet, FALSE]; END; -- ******************************************************** DoEraseP: PUBLIC PROC[m: Msg, prop: Attribute, v: Entity] = BEGIN -- UnSets the value of prop for m from v. -- Useful when m has list value for this prop and want to remove one elt. r: Relation_ V2E[GetP[prop, aRelationIs]]; first: Attribute_ FirstAttributeExcept[r, prop]; rs: LIST OF Relship_ RelshipSetToList[RelationSubset[V2E[GetP[first, aRelationIs]], LIST[[first, m]] ]]; FOR rsT: LIST OF Relship_ rs, rsT.rest UNTIL rsT=NIL DO IF Eq[v, V2E[GetF[rsT.first, prop]]] THEN { DestroyRelship[rsT.first]; WalnutWindow.SetWalnutUpdatesPending[TRUE]} ENDLOOP; END; FirstAttributeExcept: PROC[r: Relation, exc: Attribute] RETURNS [Attribute] = -- Returns first attribute of r unless it's exc, in which case it returns second BEGIN es: LIST OF Attribute_ VL2EL[GetPList[r, aRelationOf]]; first: Attribute_ es.first; IF Eq[first, exc] THEN first_ es.rest.first; RETURN[first] END; DoNameToEntity: PUBLIC PROC[d: Domain, name: ROPE, oldOnly: BOOL] RETURNS[e: Entity] = BEGIN e_ DeclareEntity[d, name, OldOnly]; IF e = NIL AND ~oldOnly THEN e_ DB.DeclareEntity[d, name, NewOnly] END; DoGetEntitiesInDomain: PUBLIC PROC[d: Domain, alphaOrder: BOOL] RETURNS[eL: LIST OF Entity] = -- returns list of entities in given domain BEGIN 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; DoRelationSubsetList: PUBLIC PROC[r: Relation, constraint: AttributeValueList_ NIL] RETURNS[relList: LIST OF Relship] = BEGIN 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; -- ******************************************************** EntityInList: PUBLIC PROC[e: Entity, el: LIST OF Value] RETURNS [BOOL] = { FOR elT: LIST OF Value_ el, elT.rest UNTIL elT=NIL DO IF Eq[e, V2E[elT.first]] THEN RETURN[TRUE] ENDLOOP; RETURN[FALSE] }; EntityListToNameList: PUBLIC PROC [el: LIST OF Value] RETURNS [nl: LIST OF RName] = -- Turns a list of entities into a list of names BEGIN IF el=NIL THEN RETURN[NIL] ELSE RETURN[CONS[V2S[GetName[V2E[el.first]]], EntityListToNameList[el.rest]]]; END; END. ChangeLog: WSH on March 4, 1983: strip off RName & DateCode stuff Êg˜JšžÏc.œIœHœ>œœ1œÏk œžœˆž œžœižœžœžœXœžœRžœžœžœžœ 0œžœ "œžœ 7œžœ <œžœ 3œžœ 9œžœ  œSœ žœžœ  œžœ œ žœžœ œžœ œ žœžœ œžœ  œ žœžœ œžœ  œžœ œ žœžœ œžœ œžœ œ žœžœ œžœ  œ žœžœ œžœ œžœ œ žœžœ œžœ œžœžœ œžœ œ)œž œ%žœœ<œ žœœžœ8œ<ÏnœžœžœžœÃžœžœžœžœ œ×/œžœžœžœžœžœžœžœžœ1žœ0žœžœžœ"žœžœžœßžœ\/œžœžœžœžœžœžœžœMžœÈ œ>œyœ= œsœ= œqœIžœ œ@ œÎœAœvœ? œoœ=œ?œ|œBœ„œCœE œGžœŸ œžœžœ žœžœ+œžœžœ žœ=žœ žœžœœ,œNžœ)žœžœ3žœžœ"žœ œB@œŸžœŸœžœžœ žœžœžœIœGœžœ žœ žœžœžœžœžœ œžœžœ?žœDžœžœQœ1žœžœ+žœ#žœžœŸœžœžœžœžœQœžœ2ŸœžœžœžœžœQœžœ1Ÿœžœžœžœžœ;œžœ3Ÿœžœžœžœžœ?œžœ%Ÿ œžœžœžœžœžœ<œHœCœžœ6žœžœžœžœžœTžœžœžœžœŸœžœžœžœžœžœ?œBœ-œžœ<žœžœžœžœ žœYžœžœ žœžœ<Ÿœžœžœ)žœtœfžœžœLžœžœžœžœžœžœžœžœ#žœHžœžœžœŸœžœžœQœžœYžœžœžœ žœŸœžœžœžœ žœžœžœ'žœžœžœ žœ)žœŸœžœžœžœžœžœžœ ,œžœ žœžœžœ žœžœ/žœžœžœžœžœžœžœžœžœžœžœžœžœŸœžœžœ.žœžœ žœžœžœYžœžœ žœžœžœžœžœžœžœžœžœžœžœžœ<œŸ œžœžœžœžœžœžœžœžœžœžœžœžœžœžœžœžœžœžœžœŸœžœžœžœžœžœžœžœ 1œžœžœžœžœžœžœžœžœžœAžœžœžœ3˜Æg—…—3È:5