DIRECTORY BasicTime USING [nullGMT, GMT, Now], Convert, IO, MailParse USING [endOfInput], LoganBerry, LoganBerryEntry, Rope, RuntimeError USING [BoundsFault], SendMailOps USING [IsThisTheCurrentUser], UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc], VFonts USING [CharWidth, StringWidth], ViewerTools USING [TiogaContents], WalnutDefs USING [Error, MsgSet, SeFromToCcSuDaMid, WalnutOpsHandle], WalnutDB USING [ ChangeCountOfMsgs, ChangeCountInMsgSet, EntryObject, EntryRef, GeneralEnumerator, GeneralEnumeratorRec, GetMsgDisplayInfo], WalnutKernelDefs USING [MsgLogEntry], WalnutSchema; WalnutDBMsgImpl: CEDAR PROGRAM IMPORTS BasicTime, Convert, IO, LoganBerry, LoganBerryEntry, Rope, RuntimeError, SendMailOps, UserProfile, VFonts, WalnutDB, WalnutDefs EXPORTS WalnutDB, WalnutDefs = BEGIN OPEN WalnutDB, WalnutSchema; ROPE: TYPE = Rope.ROPE; GMT: TYPE = BasicTime.GMT; TiogaContents: TYPE = ViewerTools.TiogaContents; Relship: TYPE = LoganBerry.Entry; Address: TYPE = ROPE; Subject: TYPE = ROPE; Keyword: TYPE = ROPE; WalnutOpsHandle: TYPE = WalnutDefs.WalnutOpsHandle; SchemaHandle: TYPE = WalnutSchema.SchemaHandle; SchemaHandleRec: PUBLIC TYPE = WalnutSchema.SchemaHandleRec; MsgSet: TYPE = WalnutDefs.MsgSet; MsgLogEntry: TYPE = WalnutKernelDefs.MsgLogEntry; useFromFieldInTOC: BOOL ¬ UserProfile.Boolean[key: "Walnut.UseFromFieldInTOC", default: FALSE]; blankWidth: INT ¬ VFonts.CharWidth[' ]; -- in default font blanks: ROPE ¬ " "; -- lotsa blanks IsEntity: TYPE = RECORD [entity: LoganBerry.Entry, exists: BOOL]; NilEntity: IsEntity = [NIL, FALSE]; GeneralEnumerator: TYPE = WalnutDB.GeneralEnumerator; MsgExists: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS [exists: BOOL] = { RETURN[GetMsgEntity[opsH, msg].exists] }; GetIsInReplyTo: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[isInReplyTo: BOOL] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN RETURN[FALSE]; isInReplyTo ¬ LoganBerryEntry.V2B[LoganBerryEntry.GetAttr[m.entity, sH.mMIIsInReplyTo]]; }; GetHasBeenRead: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[has: BOOL] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN { has ¬ FALSE; RETURN }; has ¬ LoganBerryEntry.V2B[LoganBerryEntry.GetAttr[LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mDisplayInfo, msg]].entry, sH.mDIHasBeenRead]]; }; SetHasBeenRead: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; md: LoganBerry.Entry; IF ~m.exists THEN RETURN; md ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mDisplayInfo, msg]].entry; LoganBerryEntry.SetAttr[md, sH.mDIHasBeenRead, LoganBerryEntry.B2V[TRUE]]; LoganBerry.WriteEntry[db: opsH.db, entry: md, replace: TRUE]; }; AddNewMsg: PUBLIC PROC[opsH: WalnutOpsHandle, msg: MsgLogEntry] RETURNS[mExisted: BOOL] = { msgEName: ROPE = msg.msg; sH: SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msgEName]; IF mExisted ¬ m.exists THEN RETURN; -- id's assumed unique BEGIN me: ROPE = msgEName; herald, tocHead: ROPE; shortNameLen: INT; date: BasicTime.GMT = msg.date; sender: Address; [herald, tocHead, shortNameLen] ¬ ComputeHeraldAndTOC[msg]; sender ¬ SetAddresses[opsH, me, msg]; SetAllMsgInfo[opsH, me, msg, herald, shortNameLen, date, sender]; SetMsgDisplayInfo[opsH, me, FALSE, tocHead]; SetMsgInfo[opsH, me, msg.date, msg.show]; WalnutDB.ChangeCountOfMsgs[opsH, 1]; BEGIN -- put it in active - no checking necessary init: LoganBerry.Entry = LIST[ [$Key, Rope.Concat[sH.cdRelation, me]], [sH.cdMsg, me], [sH.cdMsgSet, opsH.schemaHandle.activeEntity], [sH.cdDate, LoganBerryEntry.T2V[msg.date]] ]; LoganBerry.WriteEntry[db: opsH.db, entry: init]; IF msg.show THEN WalnutDB.ChangeCountInMsgSet[opsH, opsH.schemaHandle.activeEntity, 1]; END; END; }; GetMsgEntryPosition: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[pos: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN {pos ¬ -1; RETURN}; pos ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, msg]].entry, sH.mMIEntryStart] ]; }; SetMsgEntryPosition: PUBLIC PROC[opsH: WalnutOpsHandle, to: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; IF sH.lastMsgEntity = NIL THEN RETURN; LoganBerryEntry.SetAttr[sH.lastMsgEntity, sH.mMIEntryStart, LoganBerryEntry.I2V[to]]; LoganBerry.WriteEntry[db: opsH.db, entry: sH.lastMsgEntity, replace: TRUE]; }; GetMsgDate: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[date: GMT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN { date ¬ BasicTime.nullGMT; RETURN} ELSE { rel: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mInfo, msg]].entry; date ¬ LoganBerryEntry.V2T[LoganBerryEntry.GetAttr[rel, sH.mDateIs]]; }; }; GetMsgTextInfo: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[textStart, textLen, formatLen: INT] = { m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN { textStart ¬ textLen ¬ formatLen ¬ 0; RETURN; }; [textStart, textLen, formatLen] ¬ GetTextInfo[opsH.schemaHandle, m.entity]; }; GetMsgText: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[textStart, textLen, formatLen: INT, herald: ROPE, shortNameLen: INT] = { m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN { textStart ¬ textLen ¬ formatLen ¬ shortNameLen ¬ 0; RETURN; }; [textStart, textLen, formatLen, herald, shortNameLen] ¬ GetAllMsgTextInfo[opsH.schemaHandle, m.entity]; }; GetDisplayProps: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS [hasBeenRead: BOOL ¬ TRUE, tocEntry: ROPE ¬ NIL, startOfSubject: INT ¬ 0] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF m.exists THEN [hasBeenRead, tocEntry, startOfSubject] ¬ WalnutDB.GetMsgDisplayInfo[opsH, LoganBerryEntry.GetAttr[m.entity, sH.mMIOf]]; }; GetCategories: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[msList: LIST OF ROPE] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; rel: Relship; IF ~m.exists THEN RETURN; rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.cdRelation, msg]].entry; msList ¬ LoganBerryEntry.GetAllAttrs[rel, sH.cdMsgSet]; }; SizeOfDatabase: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[messages, msgSets: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; rVersionInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry; messages ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgCount]]; msgSets ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetCount]]; }; GenerateEntriesPlusDate: PUBLIC PROC [opsH: WalnutOpsHandle, attr: ATOM, start: ROPE ¬ NIL, end: ROPE ¬ NIL, dateStart: ROPE ¬ NIL, dateEnd: ROPE ¬ NIL] RETURNS [genEnum: GeneralEnumerator ] = { IndexedField: PROC [db: LoganBerry.OpenDB, key: LoganBerry.AttributeType] RETURNS [BOOLEAN ¬ FALSE] ~ { schema: LoganBerry.SchemaInfo ¬ LoganBerry.Describe[NIL, db]; FOR i: LIST OF LoganBerry.IndexInfo ¬ schema.indices, i.rest WHILE i # NIL DO IF i.first.key = key THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; GenerateEntriesOrFullScan: PROC [db: LoganBerry.OpenDB, key: LoganBerry.AttributeType, start: LoganBerry.AttributeValue, end: LoganBerry.AttributeValue] RETURNS [cursor: LoganBerry.Cursor] ~ { IF key#NIL AND IndexedField[db, key] THEN cursor ¬ LoganBerry.GenerateEntries[db: db, key: key, start: start, end: end] ELSE cursor ¬ LoganBerry.GenerateEntries[db: db, key: $Key, start: sH.mMsgInfo, end: Rope.Concat[sH.mMsgInfo, "\255"]]; -- full scan }; sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; genEnum ¬ NEW[WalnutDB.GeneralEnumeratorRec]; genEnum.opsH ¬ opsH; SELECT attr FROM $Key => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $MsgSetName => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $MessageText => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $From => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $To => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $Cc => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $Date => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMIDate, start: start, end: end]; }; $Sender => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMISender, start: start, end: end]; }; $Subject => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMISubject, start: start, end: end]; }; ENDCASE => ERROR WalnutDefs.Error[$db, $invalidQueryAttr, IO.PutFR1["Attribute is %g", [atom[attr]]] ]; }; NextEntry: PUBLIC PROC[genEnum: GeneralEnumerator] RETURNS[entry: EntryRef ¬ NIL] = { sH: WalnutSchema.SchemaHandle = genEnum.opsH.schemaHandle; msgID: ROPE; e: LoganBerry.Entry ¬ LoganBerry.NextEntry[cursor: genEnum.cursor]; IF e = NIL THEN RETURN[NIL]; msgID ¬ LoganBerryEntry.GetAttr[e, sH.mMIOf]; entry ¬ NEW[WalnutDB.EntryObject]; entry.seFromToCcSuDaMid.sender ¬ LoganBerryEntry.GetAttr[e, sH.mMISender]; entry.seFromToCcSuDaMid.from ¬ GetFromList[genEnum.opsH, msgID]; entry.seFromToCcSuDaMid.to ¬ GetToList[genEnum.opsH, msgID]; entry.seFromToCcSuDaMid.cc ¬ GetCcList[genEnum.opsH, msgID]; entry.seFromToCcSuDaMid.keyword ¬ NIL; entry.seFromToCcSuDaMid.subject ¬ LoganBerryEntry.GetAttr[e, sH.mMISubject]; entry.seFromToCcSuDaMid.fullSubjectText ¬ LoganBerryEntry.GetAttr[e, sH.mMISubjectText]; entry.seFromToCcSuDaMid.date ¬ LoganBerryEntry.GetAttr[e, sH.mMIDate]; entry.seFromToCcSuDaMid.msgID ¬ msgID; entry.msgSetName ¬ GetCategories[genEnum.opsH, msgID].first; -- this probably isn't right }; ComputeHeraldAndTOC: PROC[mle: MsgLogEntry] RETURNS[herald, tocHead: ROPE, shortNameLen: INT] = { date, tocx: ROPE; starter: ROPE = IF useFromFieldInTOC THEN mle.from ELSE mle.sender; from: ROPE = IF SendMailOps.IsThisTheCurrentUser[starter] THEN Rope.Concat["To: ", mle.to] ELSE starter; IF mle.date = BasicTime.nullGMT THEN mle.date ¬ BasicTime.Now[]; date ¬ Rope.Substr[Convert.RopeFromTimeRFC822[mle.date], 0, 9]; tocx ¬ date.Cat[" ", from]; tocHead ¬ SquashRopeIntoWidth[tocx, 165]; herald ¬ RemoveComments[from]; herald ¬ Rope.Cat[herald, " ", date]; shortNameLen ¬ herald.Length[]; herald ¬ Rope.Cat[herald, " ", mle.subject]; IF herald.Length[] > 60 THEN herald ¬ Rope.Concat[herald.Substr[0, 55], " ..."]; IF herald.Length[] < shortNameLen THEN shortNameLen ¬ herald.Length[]; }; SquashRopeIntoWidth: PROC[s: ROPE, colWidth: INT] RETURNS[ROPE] = BEGIN blankCount: INT; width: INT; BEGIN ENABLE RuntimeError.BoundsFault => GOTO doItTheHardWay; width¬ VFonts.StringWidth[s]; DO IF width<= colWidth THEN EXIT; -- truncate BEGIN guessLength: INT¬ s.Length[] * colWidth / width; s¬ Rope.Concat[s.Substr[0, MAX[0, guessLength-4]], "..."]; width¬ VFonts.StringWidth[s]; END; ENDLOOP; EXITS doItTheHardWay => [width, s]¬ DoItTheHardWay[s, colWidth]; END; -- of enable -- At this point s is shorter than colWidth and we want to extend it with blanks blankCount¬ ((colWidth - width) / blankWidth) + 1; -- force at least one blank s¬ Rope.Concat[s, Rope.Substr[blanks, 0, MIN[blankCount, blanks.Length[]]]]; RETURN[s] END; DoItTheHardWay: PROC[s: ROPE, colWidth: INT] RETURNS[width: INT, s1: ROPE] = { thisWidth: INTEGER; dots: ROPE = "..."; nullWidth: INTEGER = VFonts.CharWidth['\000]; width¬ VFonts.StringWidth[dots]; FOR i: INT IN [0 .. s.Length[]) DO thisWidth¬ VFonts.CharWidth[s.Fetch[i] ! RuntimeError.BoundsFault => thisWidth¬ nullWidth ]; width¬ width + thisWidth; IF width > colWidth THEN { width¬ width - thisWidth; s1¬ Rope.Concat[s.Substr[0, MAX[0, i-1]], dots]; RETURN }; ENDLOOP; s1¬ s.Concat[dots]; }; RemoveComments: PROC[name: ROPE] RETURNS[shortName: ROPE] = { start, end: INT; name ¬ Rope.Concat[base: name, rest: " "]; start ¬ Rope.Find[name, "<"]; IF start > 0 THEN { end ¬ Rope.Find[s1: name, s2: ">", pos1: start+1]; IF end > 0 THEN name ¬ Rope.Replace[name, start, end-start+1] }; start ¬ Rope.Find[name, "("]; IF start > 0 THEN { end ¬ Rope.Find[s1: name, s2: ")", pos1: start+1]; IF end > 0 THEN name ¬ Rope.Replace[name, start, end-start+1] }; shortName ¬ Rope.Substr[name, 0, Rope.Length[name]-1] }; GetMsgEntity: PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[e: IsEntity] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; IF msg.Length[] = 0 THEN { sH.lastMsgEntity ¬ NIL; RETURN[NilEntity]; }; sH.lastMsgEntity ¬ e.entity ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, msg]].entry; e.exists ¬ sH.lastMsgEntity # NIL; }; GetAllMsgTextInfo: PROC[sH: SchemaHandle, m: LoganBerry.Entry] RETURNS[textStart, textLen, formatLen: INT, herald: ROPE, shortNameLen: INT] = { rel: Relship = m; textStart ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIEntryStart]] + LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextOffset]]; textLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextLen]]; formatLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIFormatLen]]; herald ¬ LoganBerryEntry.GetAttr[rel, sH.mMIHerald]; shortNameLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIShortNameLen]]; }; GetTextInfo: PROC[sH: SchemaHandle, m: LoganBerry.Entry] RETURNS[textStart, textLen, formatLen: INT] = { rel: Relship = m; textStart ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIEntryStart]] + LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextOffset]]; textLen¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextLen]]; formatLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIFormatLen]]; }; SetAllMsgInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, mle: MsgLogEntry, herald: ROPE, shortNameLen: INT, date: BasicTime.GMT, sender: Address] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; isInReplyTo: BOOL ¬ FALSE; sub: ROPE = mle.subject; subject: Subject; subName: ROPE; afterRe: INT ¬ 0; DO IF sub.Find["Re: ", afterRe, FALSE] # afterRe THEN EXIT; afterRe ¬ afterRe + 4; isInReplyTo ¬ TRUE; ENDLOOP; subName ¬ sub.Substr[start: afterRe, len: 20]; IF subName.Length[] = 0 THEN subName ¬ "No Subject Field"; subject ¬ subName; BEGIN init: LoganBerry.Entry = LIST[ [$Key, Rope.Concat[sH.mMsgInfo, m]], [sH.mMIOf, m], [sH.mMIHerald, herald], [sH.mMIShortNameLen, LoganBerryEntry.I2V[shortNameLen]], [sH.mMIEntryStart, LoganBerryEntry.I2V[mle.entryStart]], [sH.mMITextOffset, LoganBerryEntry.I2V[mle.textOffset]], [sH.mMITextLen, LoganBerryEntry.I2V[mle.textLen]], [sH.mMIFormatLen, LoganBerryEntry.I2V[mle.formatLen]], [sH.mMIDate, LoganBerryEntry.T2V[date]], [sH.mMISubject, subject], [sH.mMISubjectText, Rope.Substr[base: mle.subject, len: 99]], [sH.mMIIsInReplyTo, LoganBerryEntry.B2V[isInReplyTo]], [sH.mMISender, sender] ]; LoganBerry.WriteEntry[db: opsH.db, entry: init]; END; }; SetMsgDisplayInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, hasBeenRead: BOOL, tocHeadEntry: ROPE] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; init: LoganBerry.Entry = LIST[ [$Key, Rope.Concat[sH.mDisplayInfo, m]], [sH.mDIOf, m], [sH.mDITOCHeadEntry, tocHeadEntry], [sH.mDIHasBeenRead, LoganBerryEntry.B2V[hasBeenRead]] ]; LoganBerry.WriteEntry[db: opsH.db, entry: init]; }; SetMsgInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, date: GMT, show: BOOL] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; init: LoganBerry.Entry = LIST[ [$Key, Rope.Concat[sH.mInfo, m]], [sH.mInfoOf, m], [sH.mDateIs, LoganBerryEntry.T2V[date]], [sH.mShowIs, IF show THEN "NULL" ELSE opsH.schemaHandle.unacceptedEntity] ]; LoganBerry.WriteEntry[db: opsH.db, entry: init]; }; ParseForSender: PROC[sH: SchemaHandle, adr: ROPE] RETURNS[e: Address, defaultReg: ROPE] = { thisName: ROPE; thisName ¬ NameInWalnutFormat[adr]; defaultReg ¬ BreakName[thisName].reg; e ¬ thisName; }; BreakName: PROC[name: Rope.ROPE] RETURNS[sn, reg: Rope.ROPE] = { length: INT = name.Length[]; FOR i: INT DECREASING IN [0..length) DO IF name.Fetch[i] = '. THEN RETURN[ sn: name.Substr[start: 0, len: i], reg: name.Substr[start: i+1, len: length-(i+1)] ]; ENDLOOP; RETURN[sn: name, reg: NIL]; }; NameInWalnutFormat: PROC[nameIn: ROPE] RETURNS[ROPE] = { this: ROPE ¬ JustTheName[nameIn]; IF this.Fetch[0] = '" THEN { -- strip "'s other: INT = this.Find["\"", 1]; IF other = -1 THEN this ¬ this.Substr[1] ELSE { firstPart: ROPE = this.Substr[1, other-1]; IF other # this.Length[] - 1 THEN { rest: ROPE = this.Substr[other+1]; this ¬ Rope.Concat[firstPart, rest]; } ELSE this ¬ firstPart; }; }; RETURN[this]; }; JustTheName: PROC[nameIn: ROPE] RETURNS[name: ROPE] = { len: INT ¬ Rope.Length[nameIn]; lastChar: CHAR ¬ nameIn.Fetch[len-1]; comment: BOOL ¬ Rope.Match[pattern: "*(*)*", object: nameIn, case: FALSE]; route: BOOL ¬ Rope.Match[pattern: "*<*>*", object: nameIn, case: FALSE]; SELECT TRUE FROM comment => { len: INT ¬ Rope.Length[nameIn]; posLeft: INT ¬ Rope.Find[nameIn, "("]; posRight: INT ¬ Rope.Find[nameIn, ")", posLeft]; IF posLeft > len-posRight THEN name ¬ Rope.Substr[base: nameIn, start: 0, len: posLeft-1] ELSE name ¬ Rope.Substr[base: nameIn, start: posRight+1, len: len-posRight-1]; }; route => { posLeft: INT ¬ Rope.Find[nameIn, "<"]; posRight: INT ¬ Rope.Find[nameIn, ">", posLeft]; name ¬ Rope.Substr[base: nameIn, start: posLeft+1, len: posRight-posLeft-1]; }; ENDCASE => RETURN[nameIn]; name ¬ StripOffWhiteSpace[name]; IF name.Length[] = 0 THEN RETURN[nameIn]; }; StripOffTrailingWhiteSpace: PROC [r: ROPE] RETURNS [ROPE] = { len: INT ¬ Rope.Length[r]; skip: ROPE ¬ " \t"; WHILE len > 0 DO len ¬ len-1; IF Rope.Find[Rope.FromChar[Rope.Fetch[r, len]], skip] = -1 THEN EXIT; r ¬ Rope.Substr[r, 0, len]; ENDLOOP; RETURN[r]; }; StripOffLeadingWhiteSpace: PROC[rope: ROPE] RETURNS [clean: ROPE] = { skip: ROPE ¬ " \t"; len: INT ¬ Rope.Length[rope]; first: INT ¬ Rope.SkipOver[rope, 0, skip]; clean ¬ Rope.Substr[rope, first, len]; }; StripOffWhiteSpace: PROC[rope: ROPE, stripLeft: BOOL ¬ TRUE, stripRight: BOOL ¬ TRUE] RETURNS [clean: ROPE] = { clean ¬ rope; IF stripLeft THEN clean ¬ StripOffLeadingWhiteSpace[clean]; IF stripRight THEN clean ¬ StripOffTrailingWhiteSpace[clean]; }; ParseForAddressList: PROC[sH: SchemaHandle, adr: ROPE, defaultReg: ROPE] RETURNS[aList: LIST OF Address] = { nameList: LIST OF ROPE ¬ NIL; startOfAdr, charIndex: INT ¬ 0; -- nowAt reset at each call to OneName aLen: INT = adr.Length[]; NextChar: PROC RETURNS [char: CHAR] = { IF charIndex >= aLen THEN RETURN[MailParse.endOfInput]; char ¬ adr.Fetch[charIndex]; charIndex ¬ charIndex + 1; }; IF adr.Length[] = 0 THEN RETURN[NIL] ELSE DO ch: CHAR; this: ROPE; UNTIL (ch ¬ NextChar[]) = ', OR ch = MailParse.endOfInput DO ENDLOOP; SELECT ch FROM ', => this ¬ adr.Substr[startOfAdr, charIndex - startOfAdr -1]; ENDCASE => this ¬ adr.Substr[startOfAdr, charIndex - startOfAdr]; startOfAdr ¬ charIndex; IF this.Length[] # 0 THEN { this ¬ NameInWalnutFormat[this]; nameList ¬ CONS[FixReg[this, defaultReg], nameList]; }; IF ch = MailParse.endOfInput THEN EXIT; ENDLOOP; FOR nL: LIST OF ROPE ¬ nameList, nL.rest UNTIL nL = NIL DO ad: Address; IF nL.first.Length[] = 0 THEN LOOP; -- one more check ad ¬ nL.first; aList ¬ CONS[ad, aList]; ENDLOOP; }; FixReg: PROC[this, defaultReg: ROPE] RETURNS[ROPE] = { dotRope: ROPE = "."; pos: INT = Rope.FindBackward[this, dotRope]; IF pos = -1 THEN RETURN[ Rope.Cat[this, dotRope, defaultReg] ]; RETURN[this]; }; TrySomethingForThisName: PROC[bad: ROPE] RETURNS[something, reg: ROPE] = { match, prev: INT; matchRope: ROPE ¬ "\""; IF (match ¬ bad.Find["<"]) # -1 THEN matchRope ¬ ">" ELSE match ¬ bad.Find["\""]; IF match # -1 THEN { end: INT ¬ bad.Find[matchRope, match+1]; IF end = -1 THEN something ¬ bad ELSE something ¬ bad.Substr[match+1, end-match-1]; } ELSE something ¬ bad; -- use it all match ¬ prev ¬ -1; UNTIL (match ¬ something.Find[".", prev+1]) = -1 DO prev ¬ match; ENDLOOP; IF prev # -1 THEN reg ¬ something.Substr[prev+1]; }; SetAddresses: PROC[opsH: WalnutOpsHandle, m: ROPE, mle: MsgLogEntry] RETURNS[sender: Address]= { sH: SchemaHandle = opsH.schemaHandle; defaultReg: ROPE; from: LIST OF Address; to: LIST OF Address; cc: LIST OF Address; [sender, defaultReg] ¬ ParseForSender[sH, mle.sender]; IF opsH.completeSchema THEN { init: LoganBerry.Entry; from ¬ ParseForAddressList[sH, mle.from, defaultReg]; to ¬ ParseForAddressList[sH, mle.to, defaultReg]; cc ¬ ParseForAddressList[sH, mle.cc, defaultReg]; init ¬ LIST[ [$Key, Rope.Concat[sH.toRelation, m]], [sH.toMsg, m], [sH.toDate, LoganBerryEntry.T2V[mle.date]] ]; FOR tL: LIST OF Address ¬ to, tL.rest UNTIL tL = NIL DO init ¬ LoganBerryEntry.AddAttr[init, sH.toAddress, tL.first]; ENDLOOP; LoganBerry.WriteEntry[db: opsH.db, entry: init]; init ¬ LIST[ [$Key, Rope.Concat[sH.ccRelation, m]], [sH.ccMsg, m], [sH.ccDate, LoganBerryEntry.T2V[mle.date]] ]; FOR ccL: LIST OF Address ¬ cc, ccL.rest UNTIL ccL = NIL DO init ¬ LoganBerryEntry.AddAttr[init, sH.ccAddress, ccL.first]; ENDLOOP; LoganBerry.WriteEntry[db: opsH.db, entry: init]; init ¬ LIST[ [$Key, Rope.Concat[sH.fromRelation, m]], [sH.fromMsg, m], [sH.fromDate, LoganBerryEntry.T2V[mle.date]] ]; FOR fL: LIST OF Address ¬ from, fL.rest UNTIL fL = NIL DO init ¬ LoganBerryEntry.AddAttr[init, sH.fromAddress, fL.first]; ENDLOOP; LoganBerry.WriteEntry[db: opsH.db, entry: init]; }; }; GetToList: PROC[opsH: WalnutOpsHandle, me: ROPE] RETURNS[tL: LIST OF ROPE] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; rel: Relship; rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.toRelation, me]].entry; tL ¬ LoganBerryEntry.GetAllAttrs[rel, sH.toAddress]; }; GetFromList: PROC[opsH: WalnutOpsHandle, me: ROPE] RETURNS[fL: LIST OF ROPE] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; rel: Relship; rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.fromRelation, me]].entry; fL ¬ LoganBerryEntry.GetAllAttrs[rel, sH.fromAddress]; }; GetCcList: PROC[opsH: WalnutOpsHandle, me: ROPE] RETURNS[ccL: LIST OF ROPE] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; rel: Relship; rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.ccRelation, me]].entry; ccL ¬ LoganBerryEntry.GetAllAttrs[rel, sH.ccAddress]; }; SetVar: UserProfile.ProfileChangedProc = { useFromFieldInTOC ¬ UserProfile.Boolean[key: "Walnut.UseFromFieldInTOC", default: FALSE]; }; UserProfile.CallWhenProfileChanges[SetVar]; END. D WalnutDBMsgImpl.mesa Copyright Σ 1984, 1988, 1989, 1992 by Xerox Corporation. All rights reserved. Willie-Sue, September 11, 1989 11:13:48 am PDT Donahue, May 17, 1985 4:03:40 pm PDT (Changed treatment of new messages) Jack Kent, November 24, 1986 2:14:39 pm PST Doug Terry, December 3, 1990 4:25 pm PST Contents: procedures dealing with Msgs in the Walnut message database Initiated by Donahue, 19 April 1983 Willie-s, April 27, 1992 1:44 pm PDT GvNsMapExtras USING [GvSyntax, SyntaxOf], Types Private Variables Internal Types Operations on Messages The message invariants are as follows: All messages belong to at least one message set. A message which is a member of the Deleted message set cannot belong to any other message sets. A message is automatically added to the Active message set when it is created. A message cannot be destroyed unless it has been deleted. Does a message with this name exist in the database. DestroyMsg is not allowed; this is ONLY done by the Expunge operation returns TRUE if the subject field of this message started with "Re: " returns the mHasBeenReadIs attribute for msg sets the mHasBeenReadIs attribute for msg to TRUE (no check on old value) takes a parsed message from the log & puts it in the database -- if the message already exists in the database, then return TRUE. Note: this guy takes the LogEntry given and has the responsibility for constructing the TOCEntry and herald for the message returns the log position for the entry for this message (-1 if the message doesn't exist); used by the lazy evaluator Sets the message entry position to refer to the new value. We depend on the fact that SetMsgEntryPosition is ONLY called by the expunge code and that it has just done a MsgExists call, which sets the local cached variable lastMsgEntity returns the date by which the message is indexed in the database returns information needed to get the tioga text for msg from log returns information needed to get the tioga text for msg from log (also produce the herald to be used if displaying it in a viewer Return the display properties of a message (the hasbeenread flag and the table of contents entry. Return the msgSets that the message belongs to. This only works well on attributes that are indexed in the database (typically the Sender, Date, or Subject attribute). All other attributes result in a full scan. dateStart and dateEnd are completely ignored. This always does a full scan. We could get a cursor on the message set, but would need to run an additional query to get the message info for each message in the set. This does not fit well into the current model. Internal procedures Truncates s with "..." or expands it with blanks, so that it is about colWidth characters wide. Not exact, uses a few heuristics here... first remove any "< . . .>" in the name then do the same for any ( . . . ) in the name use _ GvNsMapExtras.GvSyntax[name: this, quoteIfNeeded: FALSE]; IF use = NIL THEN { append: ROPE; SELECT GvNsMapExtras.SyntaxOf[this] FROM gv => append _ ".gv"; ns => append _ ".ns"; ENDCASE => append _ ".unknown"; use _ this.Concat[append]; }; Κ˜–(cedarcode) style•NewlineDelimiter ™codešΟn™Kšœ ΟeœC™NK™.KšΟy$™$K™#KšŸ+™+K™(K™KšœE™EKšœ#™#K™$—K˜šΟk ˜ Kšœ  œ  œ˜$K˜Kšœ œ™)Kš œ˜Kšœ  œ˜K˜ K˜Kšœ˜Kšœ  œ˜!Kšœ  œ˜)Kšœ  œ7˜HKšœ œ˜&Kšœ  œ˜"Kšœ  œ5˜EKšœ  œ~˜ŒKšœ œ˜%K˜ K˜—šœ œ ˜š ˜Kšœ œ3˜IKšœ!˜!Kšœ˜—K˜Kš œ˜K˜Kš œ œ˜"—K™šΟb™Kš œ œ œ˜Kš œ œ  œ˜Kšœ œ˜0K˜Kšœ  œ˜!K˜Kšœ  œ œ˜Kšœ  œ œ˜Kšœ  œ œ˜K˜Kšœ œ˜3Kšœ œ˜/Kšœ œ œ ˜ œ˜E—Kšœ  œΟc˜:Kšœ œ8’˜T—K™š‘™K˜Kšœ  œ œ$ œ˜AKšœ œ œ˜#K˜Kšœ œ˜5—K™š‘™™&K™K™‘Kš œM™NK™9—K™š  œ œ œ œ œ  œ˜QKšœ4™4Kšœ œ#˜+K˜—KšœE™EK˜šœ œ œ œ˜=Kšœ œ œ˜K™EKšœ2˜2Kšœ&˜&Kš œ  œ œ œ˜ K˜XK˜K˜—šœ œ œ œ˜=Kšœ œ œ˜Kšœ,™,Kšœ2˜2Kšœ&˜&Kš œ  œ  œ œ˜+K˜€K˜—K˜šœ œ œ œ˜AKšœI™IKšœ2˜2Kšœ&˜&K˜Kš œ  œ œ˜K˜bKšœC œ˜JKšœ7 œ˜=K˜—K˜š œ œ œ)˜?Kšœ œ  œ˜Kšœώ™ώKšœ  œ ˜Kšœ%˜%Kšœ+˜+Kš œ œ œ’˜;š ˜Kšœ œ ˜Kšœ œ˜Kšœ œ˜Kšœ œ ˜K˜K˜;K˜%KšœA˜AKšœ œ ˜,Kšœ)˜)Kšœ$˜$š œ’+˜3šœ˜š œ˜Kšœ'˜'Kšœ˜Kšœ.˜.Kšœ*˜*K˜——Kšœ0˜0š œ  ˜KšœF˜F—Kš œ˜——Kš œ˜K˜—K˜šœ œ œ œ˜BKšœ œ œ˜K™vKšœ2˜2Kšœ&˜&Kš œ  œ  œ˜%K˜ K˜—K˜šœ œ œ œ˜DKšœμ™μKšœ2˜2Kš œ œ œ œ˜&KšœU˜UKšœE œ˜KK˜—K˜š  œ œ œ œ œ œ˜PKšœ@™@Kšœ2˜2Kšœ&˜&š œ  œ œ˜5š œ˜Kšœe˜eK˜EK˜——K˜—K˜šœ œ œ œ˜=Kšœ œ  œ˜0KšœA™AKšœ&˜&š œ  œ˜K˜$Kš œ˜K˜—K˜KK˜—K˜š œ œ œ œ œ  œ  œ œ˜ŠKšœ‚™‚Kšœ&˜&š œ  œ˜K˜3Kš œ˜K˜—˜7Kšœ0˜0—K˜—K˜šœ œ œ œ œ œ œ  œ œ œ ˜”Kšœa™aKšœ2˜2Kšœ&˜&š œ  ˜K˜x—K˜—K˜š œ œ œ œ˜[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]šœ œ ˜*K˜&K˜K˜—šœ œ œ  œ œ œ œ œ  œ˜oK˜ Kš œ  œ*˜;Kš œ  œ+˜=K˜K˜—šœ œ œ œ˜HKšœ œ œ œ ˜$Kš œ  œ œ œ œ˜Kšœ œ’&˜FKšœ œ˜šœ œ œ œ˜'Kš œ œ œ˜7K˜K˜K˜—š œ œ œ œ˜$š œ ˜Kšœ œ˜ Kšœ œ˜ Kš œ œ œ œ˜Eš œ ˜K˜?Kš œ:˜A—K˜š œ œ˜K˜ Kšœ  œ%˜4K˜—Kš œ œ œ˜'Kš œ˜——š  œ œ œ œ œ œ ˜:Kšœ ˜ Kš œ œ œ’˜6K˜Kšœ œ ˜Kš œ˜—K˜K˜—š œ œ œ œ œ˜6K–>[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]šœ  œ˜Kšœ œ$˜,Kš œ  œ œ(˜?Kš œ˜ K˜K˜—š œ œ œ œ œ˜JKšœ  œ˜Kšœ  œ˜š œ œ˜4Kš œ˜—š œ  œ˜Kšœ œ ˜(š œ  œ˜ Kš œ.˜2—K˜Kš œ’ ˜$—K˜Kš œ, œ œ˜JKš œ  œ ˜1K˜K˜—š œ œ œ˜DKšœ œ˜Kšœ%˜%Kšœ  œ˜Kšœ œ œ ˜Kšœ œ œ ˜Kšœ œ œ ˜K˜K˜6š œ œ˜Kšœ˜K˜5K˜1K˜1—˜šœ œ˜ Kšœ&˜&Kšœ˜Kšœ*˜*K˜—š  œ œ œ œ œ ˜7K˜=Kš œ˜—K˜0šœ œ˜ Kšœ&˜&Kšœ˜Kšœ*˜*K˜—š  œ œ œ œ œ ˜:K˜>Kš œ˜—K˜0šœ œ˜ Kšœ(˜(Kšœ˜Kšœ,˜,K˜—š  œ œ œ œ œ ˜9K˜?Kš œ˜—K˜0K˜—K˜K˜—š œ œ œ œ œ œ œ˜NKšœ2˜2K˜ K˜`K˜4K˜K˜—š œ œ œ œ œ œ œ˜PKšœ2˜2K˜ K˜bK˜6K˜K˜—š œ œ œ œ œ œ œ˜OKšœ2˜2K˜ K˜`K˜5K˜K˜—šœ$˜*˜Kšœ> œ˜E—K˜—K˜˜+K˜——Kš œ˜K™—…—YH$