<> <> <> <> <<>> <> <> <> <<>> DIRECTORY Atom USING [GetPName], BlackCherry USING [AddDisplayerProc, MsgHandle, MsgSetInfo, GetMsgContents, GetMsgID, ProcessNewMailProc, InsertMsgsProc, MsgButtonTextProc, CustomProcs, CustomProcsRec, RegisterCustomProcs, Report], Convert USING [IntFromRope], IO USING [atom, int, PutFR, RIS, rope, RopeFromROS, ROS, STREAM], LoganBerryEntry USING [GetAllAttrs, GetAttr], LoganQuery USING [AttributePattern, AttributePatternRec, AttributePatterns, WriteAttributePatterns], Menus USING [MenuProc], PopUpSelection USING [Request], Rope USING [Cat, Concat, Find, Substr, ROPE], SimMatch USING [Tokenize], TapFilter USING [AddFilter, Agent, Annotation, CreateAgent, DeleteFilter, Error, ExistsFilter, GetAnnotations, IsAgentIdle, LookupFilter, MonitorAgent, MonitorProc, ParseMsgIntoFields, Query, WakeupAgent], TapMsgQueue USING [EntryFromMsg, Msg, MsgQueue, Put, Create], UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Token], ViewerOps USING [FetchProp]; TapInBlackCherry: CEDAR PROGRAM IMPORTS Atom, BlackCherry, Convert, IO, LoganBerryEntry, LoganQuery, PopUpSelection, Rope, SimMatch, TapFilter, TapMsgQueue, UserProfile, ViewerOps ~ BEGIN ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; MsgHandle: TYPE ~ BlackCherry.MsgHandle; MsgSetInfo: TYPE ~ BlackCherry.MsgSetInfo; debugging: BOOL _ FALSE; userName: ROPE _ NIL; filterDBName: ROPE _ "TapFiltersLB.df"; annotationDBName: ROPE _ "TapAnnotationsLB.df"; checkProfile: BOOLEAN _ TRUE; MsgData: TYPE ~ REF MsgDataRec; -- for caching info in BlackCherry.MsgHandle MsgDataRec: TYPE ~ RECORD [ ilevel: INT _ -1, parsedMsg: TapMsgQueue.Msg _ NIL ]; filteringAgent: TapFilter.Agent _ NIL; filterFeeder: TapMsgQueue.MsgQueue; defaultILevel: INT _ 50; newMail: BOOLEAN _ FALSE; <> <> tapProcs: BlackCherry.CustomProcs _ NEW[BlackCherry.CustomProcsRec _ [newMail: FilterMessages, insertMsgs: AddInInterestOrder, msgButtonText: TOCWithInterestLevel]]; TOCWithInterestLevel: BlackCherry.MsgButtonTextProc ~ { <> text _ IO.PutFR["%3g %g", IO.int[GetMsgILevel[msgH]], IO.rope[msgH.toc]]; }; AddInInterestOrder: BlackCherry.InsertMsgsProc ~ { <> <> IF msgH = NIL THEN RETURN; SELECT TRUE FROM msInfo.first=NIL => { -- first batch of msgs msInfo.first _ SortMsgsIntoMsgs[unsorted: msgH, sorted: NIL]; msInfo.last _ msInfo.first; }; NOT newMail => { -- insert msgs into message set msInfo.first _ SortMsgsIntoMsgs[unsorted: msgH, sorted: msInfo.first]; }; ENDCASE => { -- append sorted msgs to end of message set msInfo.last.next _ SortMsgsIntoMsgs[unsorted: msgH, sorted: NIL]; }; WHILE msInfo.last.next # NIL DO -- update pointer to last message msInfo.last _ msInfo.last.next; ENDLOOP; newMail _ FALSE; }; FilterMessages: BlackCherry.ProcessNewMailProc ~ { <> ENABLE TapFilter.Error => { BlackCherry.Report["Problem with filtering agent: %g - %g.\n", IO.atom[ec], IO.rope[explanation]]; CONTINUE; }; <<>> <> IF filteringAgent = NIL THEN { filterFeeder _ TapMsgQueue.Create[]; GetProfileInfo[]; filteringAgent _ TapFilter.CreateAgent[feeder: filterFeeder, filterDB: filterDBName, user: NIL, annotDB: annotationDBName]; IF filteringAgent = NIL THEN RETURN; TapFilter.MonitorAgent[agent: filteringAgent, proc: ReportProgress]; }; <<>> <> BlackCherry.Report["\nAnnotating messages: "]; FOR new: MsgHandle _ msgH, new.next WHILE new # NIL DO newData: MsgData _ NEW[MsgDataRec]; contents: ROPE _ BlackCherry.GetMsgContents[msInfo, new].contents; new.data _ newData; newData.parsedMsg _ TapFilter.ParseMsgIntoFields[contents]; IF new.gvID = NIL THEN new.gvID _ BlackCherry.GetMsgID[msInfo, new]; newData.parsedMsg _ CONS[[$MsgID, new.gvID], newData.parsedMsg]; TapMsgQueue.Put[newData.parsedMsg, filterFeeder]; ENDLOOP; <<>> <> TapFilter.WakeupAgent[filteringAgent]; [] _ TapFilter.IsAgentIdle[agent: filteringAgent, wait: TRUE]; BlackCherry.Report[" done.\n"]; newMail _ TRUE; }; <<>> ReportProgress: TapFilter.MonitorProc = { <<[msgID: ROPE, msg: TapMsgQueue.Msg, filterID: ROPE, annot: TapFilter.Annotation] RETURNS [doIt: BOOLEAN _ TRUE]>> BlackCherry.Report["@"]; }; GetMsgILevel: PROC [msgH: MsgHandle] RETURNS [ilevel: INT] ~ { <> ENABLE TapFilter.Error => { BlackCherry.Report["Problem with annotation database: %g - %g.\n", IO.atom[ec], IO.rope[explanation]]; CONTINUE; }; Max: PROC [values: LIST OF ROPE] RETURNS [max: INT] ~ { max _ 0; FOR rL: LIST OF ROPE _ values, rL.rest WHILE rL # NIL DO i: INT _ Convert.IntFromRope[rL.first]; IF i > max THEN max _ i; ENDLOOP; }; msgData: MsgData; IF msgH.data = NIL THEN msgH.data _ NEW[MsgDataRec]; msgData _ NARROW[msgH.data]; IF msgData.ilevel < 0 THEN { -- get ilevel from database and cache for future use annot: TapFilter.Annotation; IF msgH.gvID = NIL THEN msgH.gvID _ BlackCherry.GetMsgID[msgH.msInfo, msgH]; GetProfileInfo[]; annot _ TapFilter.GetAnnotations[annotDB: annotationDBName, msgID: msgH.gvID]; msgData.ilevel _ IF annot = NIL THEN defaultILevel ELSE Max[LoganBerryEntry.GetAllAttrs[entry: annot, type: $Level]]; }; ilevel _ msgData.ilevel; }; SortMsgsIntoMsgs: PROC [unsorted, sorted: MsgHandle] RETURNS [new: MsgHandle] ~ { <> WHILE unsorted # NIL DO <> msg: MsgHandle _ unsorted; unsorted _ unsorted.next; sorted _ InsertMsg[msg, sorted]; ENDLOOP; RETURN[sorted]; }; InsertMsg: PROC [msg: MsgHandle, sorted: MsgHandle] RETURNS [new: MsgHandle] ~ { <> prev: MsgHandle _ NIL; ilevel: INT _ GetMsgILevel[msg]; msg.next _ NIL; IF sorted = NIL THEN RETURN [msg]; new _ sorted; FOR each: MsgHandle _ sorted, each.next WHILE each # NIL DO IF ilevel > GetMsgILevel[each] THEN { -- found place for insertion msg.next _ each; IF prev = NIL THEN new _ msg ELSE prev.next _ msg; EXIT; }; prev _ each; ENDLOOP; IF msg.next = NIL THEN -- add to end of list prev.next _ msg; }; <> menuItems: LIST OF ROPE _ LIST["InterestLevel?", "DropConv", "BoostConv", "DropSim", "BoostSim"]; menuItemsDoc: LIST OF ROPE _ LIST[ "Explain why msg has given interest level", "Drop msg's conversation to a low interest level", "Raise msg's conversation to a high interest level", "Drop similar msgs to a low interest level", "Raise similar msgs to a high interest level" ]; subMenuItems: LIST OF ROPE _ LIST["5", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "95", "default", "original"]; subMenuItemsDoc: LIST OF ROPE _ LIST["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Set to default value?", "Set to original value?"]; dropLevel: ROPE _ "25"; boostLevel: ROPE _ "75"; defaultSimInterest: ROPE _ "50"; defaultSimThreshold: ROPE _ "50"; FiltersMenuProc: Menus.MenuProc ~ { <<[parent: ViewerClasses.Viewer, clientData: REF ANY _ NIL, mouseButton: ViewerClasses.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> ENABLE { UNWIND => NULL; TapFilter.Error => {BlackCherry.Report["Filters problem: %g - %g.\n", IO.atom[ec], IO.rope[explanation]]; CONTINUE;}; }; dropSimInterest: ROPE; dropSimThreshold: ROPE; msInfo: MsgSetInfo ~ NARROW[ViewerOps.FetchProp[NARROW[parent], $BlackCherry]]; which: INT _ PopUpSelection.Request[header: "Filters", choice: menuItems, headerDoc: NIL, choiceDoc: menuItemsDoc, default: 0, timeOut: 15]; IF which <= 0 THEN RETURN; -- no selection dropSimInterest _ dropLevel; dropSimThreshold _ dropLevel; SELECT which FROM 1 => ExplainILevel[msInfo]; 2 => DropConv[msInfo]; 3 => BoostConv[msInfo]; 4 => DropSim[msInfo]; 5 => BoostSim[msInfo] ENDCASE; }; AnnotationToRope: PROC [note: TapFilter.Annotation] RETURNS [rope: Rope.ROPE] = { rope _ NIL; FOR l: TapFilter.Annotation _ note, l.rest WHILE l # NIL DO rope _ IO.PutFR["%g %g: \"%g\"", IO.rope[rope], IO.rope[Atom.GetPName[l.first.type]], IO.rope[l.first.value]]; ENDLOOP; }; ExplainILevel: PROC [msInfo: MsgSetInfo] ~ { note: TapFilter.Annotation; IF msInfo.selected = NIL THEN { BlackCherry.Report["\nNo message selected.\n"]; RETURN; }; GetProfileInfo[]; BlackCherry.Report["\nAnnotations for message %g: ", IO.rope[msInfo.selected.gvID]]; note _ TapFilter.GetAnnotations[annotDB: annotationDBName, msgID: msInfo.selected.gvID]; BlackCherry.Report["%g\n", IO.rope[IF note # NIL THEN AnnotationToRope[note] ELSE "none"]]; }; DropConv: PROC [msInfo: MsgSetInfo] ~ { filterID: ROPE; data: MsgData; msg: MsgHandle; subject, filterName, user: ROPE; query: TapFilter.Query; annot: TapFilter.Annotation; whichSimInterest: INT; oldInterestLevel: ROPE; dropSimInterest: ROPE; msg _ msInfo.selected; data _ NARROW[msg.data]; IF data.parsedMsg = NIL THEN { contents: ROPE _ BlackCherry.GetMsgContents[msInfo, msg].contents; data.parsedMsg _ TapFilter.ParseMsgIntoFields[contents]; }; <> subject _ LoganBerryEntry.GetAttr[entry: TapMsgQueue.EntryFromMsg[data.parsedMsg], type: $subject]; IF msInfo.selected = NIL THEN { BlackCherry.Report["\nNo message selected.\n"]; RETURN; }; oldInterestLevel _ NIL; <> filterID _ Rope.Cat[userName, "$", "Subject=", subject]; [filterName, user, query, annot] _ TapFilter.LookupFilter[filterDB: filterDBName, filterID: filterID]; IF filterName # NIL THEN { <> FOR anno: TapFilter.Annotation _ annot, anno.rest WHILE anno # NIL DO IF anno.first.type = $Level THEN oldInterestLevel _ anno.first.value; ENDLOOP; }; <> whichSimInterest _ PopUpSelection.Request[header: "Interest", choice: subMenuItems, headerDoc: NIL, choiceDoc: subMenuItemsDoc, default: 0, timeOut: 15]; IF whichSimInterest > 0 THEN { SELECT whichSimInterest FROM IN [1..19] => dropSimInterest _ ListNth[list: subMenuItems, itemNum: whichSimInterest]; = 20 => dropSimInterest _ defaultSimInterest; -- set default value = 21 => IF oldInterestLevel # NIL THEN dropSimInterest _ oldInterestLevel ELSE { BlackCherry.Report["\nNo original value because filter does not already exist.\n"]; RETURN }; ENDCASE; } ELSE RETURN; --user picked nothing, so abort entire operation <> IF oldInterestLevel # NIL THEN { IF Convert.IntFromRope[dropSimInterest] >= Convert.IntFromRope[oldInterestLevel] THEN { BlackCherry.Report["\nCannot drop interest level from %g to new level %g.\n", IO.rope[oldInterestLevel], IO.rope[dropSimInterest]]; RETURN; }; }; filterID _ DeleteSubjectFilter[msInfo: msInfo, msg: msInfo.selected]; BlackCherry.Report["\nDeleted old filter %g.", IO.rope[filterID]]; filterID _ AddSubjectFilter[msInfo: msInfo, msg: msInfo.selected, note: LIST[[$Level, dropSimInterest]]]; BlackCherry.Report["\nAdded new filter %g to set conversation's interest level to %g.\n", IO.rope[filterID], IO.rope[dropSimInterest]]; }; BoostConv: PROC [msInfo: MsgSetInfo] ~ { filterID: ROPE; data: MsgData; msg: MsgHandle; subject, filterName, user: ROPE; query: TapFilter.Query; annot: TapFilter.Annotation; whichSimInterest: INT; oldInterestLevel: ROPE; boostSimInterest, dropSimInterest: ROPE; msg _ msInfo.selected; data _ NARROW[msg.data]; IF data.parsedMsg = NIL THEN { contents: ROPE _ BlackCherry.GetMsgContents[msInfo, msg].contents; data.parsedMsg _ TapFilter.ParseMsgIntoFields[contents]; }; subject _ LoganBerryEntry.GetAttr[entry: TapMsgQueue.EntryFromMsg[data.parsedMsg], type: $subject]; IF msInfo.selected = NIL THEN { BlackCherry.Report["\nNo message selected.\n"]; RETURN; }; oldInterestLevel _ NIL; <> filterID _ Rope.Cat[userName, "$", "Subject=", subject]; [filterName, user, query, annot] _ TapFilter.LookupFilter[filterDB: filterDBName, filterID: filterID]; IF filterName # NIL THEN { <> FOR anno: TapFilter.Annotation _ annot, anno.rest WHILE anno # NIL DO IF anno.first.type = $Level THEN oldInterestLevel _ anno.first.value; ENDLOOP; }; <> whichSimInterest _ PopUpSelection.Request[header: "Interest", choice: subMenuItems, headerDoc: NIL, choiceDoc: subMenuItemsDoc, default: 0, timeOut: 15]; IF whichSimInterest > 0 THEN { SELECT whichSimInterest FROM IN [1..19] => boostSimInterest _ ListNth[list: subMenuItems, itemNum: whichSimInterest]; = 20 => boostSimInterest _ defaultSimInterest; -- set default value = 21 => IF oldInterestLevel # NIL THEN boostSimInterest _ oldInterestLevel ELSE { BlackCherry.Report["\nNo original value because filter does not already exist.\n"]; RETURN }; ENDCASE; } ELSE RETURN; --user picked nothing, so abort entire operation <> IF oldInterestLevel # NIL THEN { IF Convert.IntFromRope[boostSimInterest] <= Convert.IntFromRope[oldInterestLevel] THEN { BlackCherry.Report["\nCannot boost interest level from %g to new level %g.\n", IO.rope[oldInterestLevel], IO.rope[boostSimInterest]]; RETURN; }; }; filterID _ AddSubjectFilter[msInfo: msInfo, msg: msInfo.selected, note: LIST[[$Level, boostSimInterest]]]; BlackCherry.Report["\nAdded filter %g to set conversation's interest level to %g.\n", IO.rope[filterID], IO.rope[boostSimInterest]]; }; DropSim: PROC [msInfo: MsgSetInfo] ~ { <> filterID: ROPE; name: ROPE; filterName, user: ROPE; query: TapFilter.Query; annot: TapFilter.Annotation; oldInterestLevel, oldThreshold: ROPE; whichSimThreshold: INT; dropSimThreshold, dropSimInterest: ROPE; whichSimInterest: INT; IF msInfo.selected = NIL THEN { BlackCherry.Report["\nNo message selected.\n"]; RETURN; }; <> oldInterestLevel _ NIL; name _ BlackCherry.GetMsgID[msInfo: msInfo, msgH: msInfo.selected]; <> filterID _ Rope.Cat[userName, "$", "SimTo:", name]; [filterName, user, query, annot] _ TapFilter.LookupFilter[filterDB: filterDBName, filterID: filterID]; IF filterName # NIL THEN { <> FOR anno: TapFilter.Annotation _ annot, anno.rest WHILE anno # NIL DO IF anno.first.type = $SimThreshold THEN oldThreshold _ anno.first.value ELSE IF anno.first.type = $Level THEN oldInterestLevel _ anno.first.value; ENDLOOP; }; <> whichSimThreshold _ PopUpSelection.Request[header: "Sim Threshold", choice: subMenuItems, headerDoc: NIL, choiceDoc: subMenuItemsDoc, default: 0, timeOut: 15]; IF whichSimThreshold <= 0 THEN RETURN; -- no selection SELECT whichSimThreshold FROM IN [1..19] => dropSimThreshold _ ListNth[list: subMenuItems, itemNum: whichSimThreshold]; = 20 => dropSimThreshold _ defaultSimThreshold; --set default value = 21 => IF oldThreshold # NIL THEN dropSimThreshold _ oldThreshold ELSE { BlackCherry.Report["\nNo original value because filter does not exist.\n"]; RETURN }; ENDCASE; <> IF oldThreshold # NIL THEN { IF Convert.IntFromRope[dropSimThreshold] >= Convert.IntFromRope[oldThreshold] THEN { BlackCherry.Report["\n Cannot drop old similarity threshold %g to new, higher threshold %g.\n", IO.rope[oldThreshold], IO.rope[dropSimThreshold]]; RETURN; }; }; <> whichSimInterest _ PopUpSelection.Request[header: "Interest", choice: subMenuItems, headerDoc: NIL, choiceDoc: subMenuItemsDoc, default: 0, timeOut: 15]; IF whichSimInterest > 0 THEN { SELECT whichSimInterest FROM IN [1..19] => dropSimInterest _ ListNth[list: subMenuItems, itemNum: whichSimInterest]; = 20 => dropSimInterest _ defaultSimInterest; -- set default value = 21 => IF oldInterestLevel # NIL THEN dropSimInterest _ oldInterestLevel ELSE { BlackCherry.Report["\nNo original value because filter does not exist.\n"]; RETURN }; ENDCASE; } ELSE RETURN; BlackCherry.Report["\nReplacing old filter %g.\n", IO.rope[filterID]]; filterID _ DeleteTextFilter[msInfo: msInfo, msg: msInfo.selected]; filterID _ AddTextFilter[msInfo: msInfo, msg: msInfo.selected, note: LIST[[$Level, dropSimInterest], [$SimThreshold, dropSimThreshold]]]; BlackCherry.Report["\nAdding new filter %g to drop interest level of similar msgs to %g. Similarity threshold dropped to %g.\n", IO.rope[filterID], IO.rope[dropSimInterest], IO.rope[dropSimThreshold]]; }; BoostSim: PROC [msInfo: MsgSetInfo] ~ { <> filterID: ROPE; whichSimInterest, whichSimThreshold: INT; boostSimInterest, boostSimThreshold: ROPE; name: ROPE; oldThreshold, oldInterestLevel: ROPE; filterName, user: ROPE; query: TapFilter.Query; annot: TapFilter.Annotation; IF msInfo.selected = NIL THEN { BlackCherry.Report["\nNo message selected.\n"]; RETURN; }; oldThreshold _ NIL; oldInterestLevel _ NIL; name _ BlackCherry.GetMsgID[msInfo: msInfo, msgH: msInfo.selected]; <> filterID _ Rope.Cat[userName, "$", "SimTo:", name]; [filterName, user, query, annot] _ TapFilter.LookupFilter[filterDB: filterDBName, filterID: filterID]; IF filterName # NIL THEN { <> FOR anno: TapFilter.Annotation _ annot, anno.rest WHILE anno # NIL DO IF anno.first.type = $SimThreshold THEN oldThreshold _ anno.first.value ELSE IF anno.first.type = $Level THEN oldInterestLevel _ anno.first.value; ENDLOOP; }; <> whichSimThreshold _ PopUpSelection.Request[header: "Threshold", choice: subMenuItems, headerDoc: NIL, choiceDoc: subMenuItemsDoc, default: 0, timeOut: 15]; IF whichSimThreshold > 0 THEN { SELECT whichSimThreshold FROM IN [1..19] => boostSimThreshold _ ListNth[list: subMenuItems, itemNum: whichSimThreshold]; = 20 => boostSimThreshold _ defaultSimThreshold; --set default value = 21 => IF oldThreshold # NIL THEN boostSimThreshold _ oldThreshold ELSE { BlackCherry.Report["\nNo original value because filter does not exist.\n"]; RETURN }; ENDCASE; } ELSE RETURN; --user picked nothing, so abort entire operation <> IF oldThreshold # NIL THEN { IF Convert.IntFromRope[boostSimThreshold] <= Convert.IntFromRope[oldThreshold] THEN { BlackCherry.Report["\nCannot boost old similarity threshold %g to new, lower threshold %g.\n", IO.rope[oldThreshold], IO.rope[boostSimThreshold]]; RETURN; }; }; <> whichSimInterest _ PopUpSelection.Request[header: "Interest", choice: subMenuItems, headerDoc: NIL, choiceDoc: subMenuItemsDoc, default: 0, timeOut: 15]; IF whichSimInterest > 0 THEN { SELECT whichSimInterest FROM IN [1..19] => boostSimInterest _ ListNth[list: subMenuItems, itemNum: whichSimInterest]; = 20 => boostSimInterest _ defaultSimInterest; -- set default value = 21 => IF oldInterestLevel # NIL THEN boostSimInterest _ oldInterestLevel ELSE { BlackCherry.Report["\nnNo original value because filter does not exist.\n"]; RETURN }; ENDCASE; } ELSE RETURN; filterID _ AddTextFilter[msInfo: msInfo, msg: msInfo.selected, note: LIST[[$Level, boostSimInterest], [$SimThreshold, boostSimThreshold]]]; BlackCherry.Report["\nAdded filter %g to boost interest level of similar msgs to %g. Similarity threshold boosted to %g.\n", IO.rope[filterID], IO.rope[boostSimInterest], IO.rope[boostSimThreshold]]; }; AddSubjectFilter: PROC [msInfo: MsgSetInfo, msg: MsgHandle, note: TapFilter.Annotation] RETURNS [filterID: ROPE] ~ { subject, query: ROPE; data: MsgData _ NARROW[msg.data]; IF data.parsedMsg = NIL THEN { contents: ROPE _ BlackCherry.GetMsgContents[msInfo, msg].contents; data.parsedMsg _ TapFilter.ParseMsgIntoFields[contents]; }; subject _ LoganBerryEntry.GetAttr[entry: TapMsgQueue.EntryFromMsg[data.parsedMsg], type: $subject]; IF Rope.Find[s1: subject, s2: "Re: ", pos1: 0, case: FALSE] = 0 THEN subject _ Rope.Substr[base: subject, start: 4]; -- strip off "re: " query _ IO.PutFR["subject(re): \"(Re\':| )*%g\"", IO.rope[subject]]; GetProfileInfo[]; filterID _ Rope.Cat[userName, "$", "Subject=", subject]; IF TapFilter.ExistsFilter[filterDB: filterDBName, filterID: filterID] THEN { [] _ TapFilter.DeleteFilter[filterDB: filterDBName, filterID: filterID]; }; filterID _ TapFilter.AddFilter[filterDB: filterDBName, user: userName, filterName: Rope.Concat["Subject=", subject], query: query, annot: note, agent: filteringAgent]; }; DeleteSubjectFilter: PROC [msInfo: MsgSetInfo, msg: MsgHandle] RETURNS [filterID: ROPE] ~ { subject, query: ROPE; data: MsgData _ NARROW[msg.data]; IF data.parsedMsg = NIL THEN { contents: ROPE _ BlackCherry.GetMsgContents[msInfo, msg].contents; data.parsedMsg _ TapFilter.ParseMsgIntoFields[contents]; }; subject _ LoganBerryEntry.GetAttr[entry: TapMsgQueue.EntryFromMsg[data.parsedMsg], type: $subject]; IF Rope.Find[s1: subject, s2: "Re: ", pos1: 0, case: FALSE] = 0 THEN subject _ Rope.Substr[base: subject, start: 4]; -- strip off "re: " query _ IO.PutFR["subject(re): \"(Re\':| )*%g\"", IO.rope[subject]]; GetProfileInfo[]; filterID _ Rope.Cat[userName, "$", "Subject=", subject]; TapFilter.DeleteFilter[filterDB: filterDBName, filterID: filterID]; }; <> AddTextFilter: PROC [msInfo: MsgSetInfo, msg: MsgHandle, note: TapFilter.Annotation] RETURNS [filterID: ROPE] ~ { name, query, text: ROPE; attrs: LIST OF ROPE; stream: IO.STREAM; threshold: ROPE; data: MsgData _ NARROW[msg.data]; aps: LoganQuery.AttributePatterns; ap: LoganQuery.AttributePattern _ NEW[LoganQuery.AttributePatternRec]; ap.attr.type _ $text; ap.ptype _ IO.PutFR["sim"]; IF data.parsedMsg = NIL THEN { contents: ROPE _ BlackCherry.GetMsgContents[msInfo, msg].contents; data.parsedMsg _ TapFilter.ParseMsgIntoFields[contents]; }; <> attrs _ LoganBerryEntry.GetAllAttrs[entry: TapMsgQueue.EntryFromMsg[data.parsedMsg], type: $text]; WHILE attrs # NIL DO text _ Rope.Concat[text, attrs.first]; attrs _ attrs.rest; ENDLOOP; <<>> <> stream _ IO.RIS[text]; stream _ SimMatch.Tokenize[stream]; text _ IO.RopeFromROS[stream]; <> <> FOR anno: TapFilter.Annotation _ note, anno.rest WHILE anno # NIL DO IF anno.first.type = $SimThreshold THEN threshold _ anno.first.value; ENDLOOP; text _ Rope.Concat[threshold, text]; -- Prepend the threshold ap.attr.value _ text; aps _ LIST[ap]; stream _ IO.ROS[]; LoganQuery.WriteAttributePatterns[s: stream, ap: aps]; query _ IO.RopeFromROS[stream]; <> name _ BlackCherry.GetMsgID[msInfo: msInfo, msgH: msg]; GetProfileInfo[]; IF TapFilter.ExistsFilter[filterDB: filterDBName, filterID: Rope.Cat[userName, "$", "SimTo:", name]] THEN { BlackCherry.Report["\Replacing old filter %g.\n", IO.rope[filterID]]; [] _ DeleteTextFilter[msInfo, msg]; }; filterID _ TapFilter.AddFilter[filterDB: filterDBName, user: userName, filterName: Rope.Concat["SimTo:", name], query: query, annot: note, agent: filteringAgent]; }; DeleteTextFilter: PROC [msInfo: MsgSetInfo, msg: MsgHandle] RETURNS [filterID: ROPE] ~ { name: ROPE; stream: IO.STREAM; name _ BlackCherry.GetMsgID[msInfo: msInfo, msgH: msg]; GetProfileInfo[]; filterID _ Rope.Cat[userName, "$", "SimTo:", name]; TapFilter.DeleteFilter[filterDB: filterDBName, filterID: filterID]; }; ListNth: PROC [list: LIST OF ROPE, itemNum: INT] RETURNS [nth: ROPE] ~ { <> item: ROPE; counter: INT _ 0; FOR element: LIST OF ROPE _ list, element.rest WHILE element # NIL DO counter _ counter + 1; item _ element.first; IF counter = itemNum THEN RETURN[item]; ENDLOOP; }; <> GetProfileInfo: PROC [] ~ { IF checkProfile THEN { userName _ UserProfile.Token[key: "Tapestry.UserName", default: userName]; filterDBName _ UserProfile.Token[key: "Tapestry.FilterDB", default: filterDBName]; annotationDBName _ UserProfile.Token[key: "Tapestry.AnnotationDB", default: annotationDBName]; checkProfile _ FALSE; }; }; ProfileChanged: UserProfile.ProfileChangedProc = { <<[reason: UserProfile.ProfileChangeReason]>> checkProfile _ TRUE; }; <> CustomizeBlackCherry: PROC ~ { BlackCherry.RegisterCustomProcs[procs: tapProcs]; BlackCherry.AddDisplayerProc[menuName: "Filters", proc: FiltersMenuProc]; UserProfile.CallWhenProfileChanges[proc: ProfileChanged]; }; CustomizeBlackCherry[]; END.