'From Squeak3.7alpha of ''11 September 2003'' [latest update: #5526] on 7 November 2003 at 2:14:56 pm'! "Change Set: BugFixArchive-Model Date: 21 August 2003 Author: Brent Vukmer This framework provides the model for the Bug Fixes Archive Viewer. See http://minnow.cc.gatech.edu/squeak/3214 for the BugFixArchiveViewer changelog and other details about the project.."! Object subclass: #ArchivePost instanceVariableNames: 'archive id title authorName authorEmail dateSent typeMask reviewStepsMask statusMask flags updateStreamNumbers displayLabel ' classVariableNames: 'TagQaMap TagStatusMap TagTypeMap ' poolDictionaries: '' category: 'BFAV-Model'! !ArchivePost commentStamp: 'bkv 9/2/2003 21:10' prior: 0! A local copy of a post to the Bug Fixes Archive. Currently the Bug Fixes Archive is populated by a script on the swiki.gsug.org server that parses the squeak-dev.mbox file; so for the time being, an ArchivePost class is pretty closely related to the MailMessage class. See the BugFixArchive class for more details about how ArchivePost objects are used. Or just explore the BugFixArchive instances in memory: BugFixArchive allSubInstances explore. ! Object subclass: #ArchivePostGroup instanceVariableNames: 'posts aggregatedPost ' classVariableNames: '' poolDictionaries: '' category: 'BFAV-Model'! Object subclass: #BugFixArchive instanceVariableNames: 'repository archivePosts topicGroups ' classVariableNames: 'Registry ' poolDictionaries: '' category: 'BFAV-Model'! !BugFixArchive commentStamp: 'bkv 6/30/2003 11:25' prior: 0! A local cache of the Squeak community's Bug Fixes Archive. instance variables: loadFilterSelectors updater repository archivePosts topics archivePostGroups class variables: Registry! Object subclass: #HttpEmailFileRepository instanceVariableNames: 'repositoryDir serverUrl ' classVariableNames: '' poolDictionaries: '' category: 'BFAV-Model'! Object subclass: #MailUtil instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'BFAV-Model'! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 6/2/2003 10:04'! archive ^ archive! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 5/18/2003 21:56'! archive: aBugFixArchive archive _ aBugFixArchive! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 20:33'! authorEmail ^ authorEmail! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 22:20'! authorEmail: aString authorEmail _ aString! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 20:33'! authorName ^ authorName ! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 22:19'! authorName: aString authorName _ aString ! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 21:20'! dateSent ^ dateSent! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 22:29'! dateSent: aDate dateSent _ aDate! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/15/2003 21:21'! displayLabel ^ displayLabel! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/15/2003 21:21'! displayLabel: aString displayLabel _ aString! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/6/2003 12:52'! flags flags ifNil: [ flags _ self parseFlagsFromTitle ]. ^ flags! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/4/2003 17:51'! id ^ id! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/4/2003 18:06'! id: aNumberOrString id _ aNumberOrString! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/6/2003 13:33'! qaFlags ^ self flags intersection: self canonicalQaFlags! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 21:09'! reviewStepsMask ^ reviewStepsMask copy! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/6/2003 13:34'! statusFlags ^ self flags intersection: self canonicalStatusFlags! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 21:09'! statusMask ^ statusMask copy! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 22:16'! statusMask: aNumber statusMask _ aNumber.! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 22:12'! title ^ title! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 22:55'! title: aString title _ aString.! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/6/2003 19:39'! topic | source | source _ self title withBlanksTrimmed. ^ self topicContentFrom: source ! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/6/2003 14:23'! typeFlags ^ self flags intersection: self canonicalTypeFlags! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 21:09'! typeMask ^ typeMask copy! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 11/4/2003 22:16'! typeMask: aNumber typeMask _ aNumber.! ! !ArchivePost methodsFor: 'accessing' stamp: 'bkv 9/6/2003 14:22'! updateStreamNumbers ^ updateStreamNumbers! ! !ArchivePost methodsFor: 'comparing' stamp: 'bkv 9/6/2003 14:24'! = otherPost self == otherPost ifTrue: [ ^true ]. self species == otherPost species ifFalse: [ ^false ]. self archive = otherPost archive ifFalse: [ ^false ]. self id = otherPost id ifFalse: [ ^false ]. self flags = otherPost flags ifFalse: [ ^false ]. self updateStreamNumbers = otherPost updateStreamNumbers ifFalse: [ ^false ]. ^ true! ! !ArchivePost methodsFor: 'comparing' stamp: 'bkv 9/6/2003 14:23'! hash | hash | hash _ self species hash. hash _ hash bitXor: self archive hash. hash _ hash bitXor: self id hash. hash _ hash bitXor: self flags hash. hash _ hash bitXor: self updateStreamNumbers hash. ^ hash! ! !ArchivePost methodsFor: 'files' stamp: 'bkv 9/16/2003 22:04'! attachments "Pre-parsed and saved as files, we assume. Returns filenames." ^ self archive attachmentsForId: self id! ! !ArchivePost methodsFor: 'files' stamp: 'bkv 9/16/2003 22:02'! attachmentsDirectory ^ self archive attachmentsDirectoryForId: self id! ! !ArchivePost methodsFor: 'files' stamp: 'bkv 5/30/2003 18:06'! hasAttachments ^ self attachments isEmptyOrNil not! ! !ArchivePost methodsFor: 'initialization' stamp: 'bkv 9/6/2003 14:21'! initialize | tags | tags _ self parseTagsFromTitle. flags _ self flagsFromTags: tags. updateStreamNumbers _ self updateStreamNumbersFromTags: tags.! ! !ArchivePost methodsFor: 'mail headers' stamp: 'bkv 11/6/2003 08:26'! authorEmailFromMailMessage | mailMsg fromLine | mailMsg _ self asMailMessage. mailMsg ifNil: [ ^ nil ]. fromLine _ mailMsg from. ^ (MailUtil nameAndEmailAddressFromLine: fromLine) second ! ! !ArchivePost methodsFor: 'mail headers' stamp: 'bkv 11/6/2003 08:26'! authorNameFromMailMessage | mailMsg fromLine | mailMsg _ self asMailMessage. mailMsg ifNil: [ ^ nil ]. fromLine _ mailMsg from. ^ (MailUtil nameAndEmailAddressFromLine: fromLine) first ! ! !ArchivePost methodsFor: 'mail headers' stamp: 'bkv 9/3/2003 21:38'! body "We should probably pre-parse the body instead, save to a file, then load from that file here.." | mailMsg formattedBody | mailMsg _ self asMailMessage. mailMsg ifNil: [ ^ nil ]. (mailMsg body isMultipart) ifTrue: [ | formattedParts textParts | "Attachments exist and have not yet been parsed from body of email and saved to filesystem." formattedParts _ MailUtil parsePartsFor: mailMsg. (formattedParts isEmptyOrNil) ifFalse: [ textParts _ MailUtil textPartsFrom: formattedParts. ]. (textParts isEmptyOrNil) ifFalse: [ formattedBody _ MailUtil streamContentsForTextParts: textParts. ]] ifFalse: [ formattedBody _ mailMsg format body content. ]. ^ formattedBody! ! !ArchivePost methodsFor: 'mail headers' stamp: 'bkv 9/3/2003 08:24'! comments ^ self parseCommentsFromTitle! ! !ArchivePost methodsFor: 'mail headers' stamp: 'bkv 11/4/2003 21:19'! dateSentFromMailMessage | mailMsg | mailMsg _ self asMailMessage. mailMsg ifNil: [ ^ nil ]. ^ mailMsg date asDate! ! !ArchivePost methodsFor: 'mail headers' stamp: 'bkv 11/4/2003 22:10'! titleFromMailMessage ^ self asMailMessage subject! ! !ArchivePost methodsFor: 'printing' stamp: 'bkv 5/9/2003 18:01'! describe: string withBoldLabel: label on: stream "Copied blatantly from SMCard" stream withAttribute: (TextEmphasis bold) do: [ stream nextPutAll: label ]. stream nextPutAll: string; cr.! ! !ArchivePost methodsFor: 'printing' stamp: 'bkv 9/3/2003 21:50'! fullDescription "Return a full textual description of the package." | s tab bodyText | s _ TextStream on: (Text new: 400). tab _ String with: Character tab. self describe: self title withBoldLabel: 'Subject:' , tab , tab on: s. self authorName isEmptyOrNil ifFalse: [s withAttribute: TextEmphasis bold do: [s nextPutAll: 'Author:']; tab; tab. s withAttribute: (PluggableTextAttribute evalBlock: [ CommentNotePad openOn: self ]) do: [s nextPutAll: self authorName ]; cr]. self dateSent isNil ifFalse: [self describe: self dateSent asString withBoldLabel: 'Date Posted: ' on: s]. self body isEmptyOrNil ifFalse: [ bodyText _ self text ] ifTrue: [ bodyText _ '']. self describe: bodyText withBoldLabel: 'Comments:' , tab on: s. "It would be cool to have a list of links to comments on this post, maybe." "The downloadUrl is supplied via the right-click menu in the archivePostsList pane" ^ s contents isoToSqueak! ! !ArchivePost methodsFor: 'printing' stamp: 'bkv 9/3/2003 08:26'! groupDisplayLabel ^ String streamContents: [ :stream | self printGroupDisplayLabelOn: stream ]! ! !ArchivePost methodsFor: 'printing' stamp: 'bkv 9/3/2003 21:47'! printGroupDisplayLabelOn: aStream | authorLabel | authorLabel _ self authorName. authorLabel isEmptyOrNil ifTrue: [ authorLabel _ self authorEmail ]. authorLabel isEmptyOrNil ifTrue: [ authorLabel _ '' ]. aStream nextPut: Character space. aStream nextPut: Character space. aStream nextPut: Character space. aStream nextPut: Character space. aStream nextPutAll: authorLabel. aStream nextPut: Character space. aStream nextPut: $(. aStream nextPutAll: self dateSent asString. aStream nextPut: $). aStream nextPut: Character space. self comments isEmptyOrNil ifFalse: [ aStream nextPutAll: self comments ]. ^ aStream ! ! !ArchivePost methodsFor: 'printing' stamp: 'bkv 6/2/2003 10:07'! printOn: aStream ^ aStream nextPutAll: self title! ! !ArchivePost methodsFor: 'printing' stamp: 'bkv 6/2/2003 10:07'! printString ^ self title! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/4/2003 16:21'! authorEmailMatches: aString "Be inclusive by default. If there is no author email, return true." ^ aString isEmptyOrNil not and: [ self authorEmail isNil or: [ aString match: self authorEmail ]]! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/4/2003 16:21'! authorNameMatches: aString "Be inclusive by default. If there is no author name, return true." ^ aString isEmptyOrNil not and: [ self authorName isNil or: [ aString match: self authorName ]]! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/2/2003 10:15'! authorNameOrEmailMatches: aString ^ aString isEmptyOrNil not and: [ (self authorNameMatches: aString) or: [ self authorEmailMatches: aString ]]! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/4/2003 16:22'! bodyMatches: aString "Be inclusive by default. If there is no body text, return true." ^ aString isEmptyOrNil not and: [ self body isNil or: [ aString match: self body ]]! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 9/6/2003 14:23'! byteSize "Answer a (somewhat low) estimate of how much space I take up" " ArchivePost allSubInstances detectSum: [ :ea | ea byteSize ] " | dataSize | dataSize _ self class instSize * 4. dataSize _ dataSize + self id size * 4. dataSize _ dataSize + self flags size * 4. dataSize _ dataSize + self updateStreamNumbers size * 4. ^ dataSize! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/3/2003 14:49'! isBefore: beforeDate andAfter: afterDate | beforeCheck afterCheck result | self dateSent isNil ifTrue: [ ^ false ]. beforeCheck _ beforeDate isNil or: [ self dateSent < beforeDate ]. afterCheck _ afterDate isNil or: [ self dateSent > afterDate ]. result _ beforeCheck and: [ afterCheck ]. ^ result ! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/2/2003 10:10'! monthMatches: aMonth ^ aMonth isNil not and: [ self dateSent month = aMonth]! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/2/2003 10:10'! monthMatches: aMonth andYearMatches: aYear ^ (self monthMatches: aMonth) and: [ self yearMatches: aYear ]! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 9/2/2003 20:51'! size "Bogus query for heterogenous collections" ^ 1! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 9/6/2003 19:38'! titleMatches: aString "Be inclusive by default. If there is no title text, return true." | title | title _ self title. ^ aString isEmptyOrNil not and: [ title isNil or: [ aString match: title ]]! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/2/2003 10:13'! titleOrBodyMatches: aString ^ (self titleMatches: aString) or: [ self bodyMatches: aString ]! ! !ArchivePost methodsFor: 'queries' stamp: 'bkv 6/2/2003 08:16'! yearMatches: aYear ^ aYear isNil not and: [ self dateSent year == aYear ]! ! !ArchivePost methodsFor: 'review-step testing' stamp: 'bkv 9/6/2003 12:51'! isMarkedAsHasBeenDocumented ^ self flags includes: self class symbolForHasBeenDocumented! ! !ArchivePost methodsFor: 'review-step testing' stamp: 'bkv 9/6/2003 12:50'! isMarkedAsHasBeenReviewed ^ self flags includes: self class symbolForHasBeenReviewed! ! !ArchivePost methodsFor: 'review-step testing' stamp: 'bkv 9/6/2003 12:50'! isMarkedAsHasBeenTested ^ self flags includes: self class symbolForHasBeenTested! ! !ArchivePost methodsFor: 'review-step testing' stamp: 'bkv 9/6/2003 12:49'! isMarkedAsHasSUnitTests ^ self flags includes: self class symbolForHasSUnitTests! ! !ArchivePost methodsFor: 'review-step testing' stamp: 'bkv 9/6/2003 12:49'! isMarkedAsPassesSLint ^ self flags includes: self class symbolForPassesSLint! ! !ArchivePost methodsFor: 'review-step testing' stamp: 'bkv 9/6/2003 12:48'! isMarkedAsSmall ^ self flags includes: self class symbolForIsSmall! ! !ArchivePost methodsFor: 'status testing' stamp: 'bkv 9/6/2003 19:37'! hasNoStatus | strictStatusTests relaxedStatusTests title | strictStatusTests _ (self isMarkedAsApproved not and: [ self isMarkedAsClosed not ]) and: [ self isMarkedAsUpdate not ]. title _ self title. relaxedStatusTests _ (('*approve*' match: title) not and: [ ('*close*' match: title) not ]) and: [ ('*update -*' match: title) not ]. ^ strictStatusTests and: [ relaxedStatusTests ]! ! !ArchivePost methodsFor: 'status testing' stamp: 'bkv 9/6/2003 14:05'! isMarkedAsApproved ^ self flags includes: (self class symbolForHasBeenApproved)! ! !ArchivePost methodsFor: 'status testing' stamp: 'bkv 9/6/2003 14:05'! isMarkedAsClosed ^ self flags includes: (self class symbolForHasBeenClosed)! ! !ArchivePost methodsFor: 'status testing' stamp: 'bkv 9/6/2003 14:06'! isMarkedAsUpdate ^ self flags includes: (self class symbolForHasBecomeAnUpdate)! ! !ArchivePost methodsFor: 'services' stamp: 'bkv 9/6/2003 12:51'! addQaTag: aString self error: 'Obsolete and rudely deprecated!!'! ! !ArchivePost methodsFor: 'services' stamp: 'bkv 9/15/2003 21:39'! asMailMessage self archive ifNil: [ ^ nil ]. ^ self archive mailMessageForId: self id! ! !ArchivePost methodsFor: 'services' stamp: 'bkv 9/13/2003 19:20'! rawAttachments "The attachments should already be parsed and saved as files. Here we force a re-parsing from the source file." | mailMsg atts | mailMsg _ self asMailMessage. mailMsg ifNil: [ ^ nil ]. atts _ #(). (mailMsg body isMultipart) ifTrue: [ | formattedParts | "Attachments exist and have not yet been parsed from body of email and saved to filesystem." formattedParts _ MailUtil parsePartsFor: mailMsg. (formattedParts isEmptyOrNil) ifFalse: [ atts _ MailUtil attachmentPartsFrom: formattedParts. ] ]. ^ atts ! ! !ArchivePost methodsFor: 'services' stamp: 'bkv 9/6/2003 12:51'! removeQaTag: aString self error: 'Obsolete and rudely deprecated!!'! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/6/2003 13:38'! flagsFromTags: aListOfStrings | titleFlags | aListOfStrings isNil ifTrue: [ ^ nil ]. aListOfStrings isEmpty ifTrue: [ ^ flags ]. titleFlags _ OrderedCollection withAll: (self typeFlagsFromTags: aListOfStrings). titleFlags addAll: (self qaFlagsFromTags: aListOfStrings). titleFlags addAll: (self statusFlagsFromTags: aListOfStrings). ^ titleFlags! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/13/2003 20:19'! flagsFromTags: aListOfStrings usingSelector: aSymbol aListOfStrings isEmptyOrNil ifTrue: [ ^ #() ]. ^ aListOfStrings collect: [ :tag | self class perform: aSymbol with: tag ] thenSelect: [ :flag | flag notNil ] ! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 6/9/2003 22:03'! parseCommentsFromTitle | txt startIdx endIdx | self title ifNil: [ ^ nil ]. startIdx _ self title lastIndexOf: $(. "We require an opening paren to denote comments start." (startIdx > 0) ifTrue: [ startIdx _ startIdx + 1 ] ifFalse: [ ^ nil ]. endIdx _ self title lastIndexOf: $). "Parse at all costs!! Be a little more forgiving than the web page constructor, which requires a close paren." (endIdx > startIdx) ifTrue: [ endIdx _ endIdx - 1 ] ifFalse: [ endIdx _ self title withBlanksTrimmed size ]. ((startIdx > 0) and: [ endIdx > 0 ]) ifTrue: [ txt _ self title copyFrom: startIdx to: endIdx. txt ifNotNil: [ txt _ txt withBlanksTrimmed. ]]. ^ txt ! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/6/2003 13:39'! parseFlagsFromTitle ^ self flagsFromTags: self parseTagsFromTitle! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 11/4/2003 21:08'! parseReviewStepsMaskFromTitle ^ self class parseReviewStepsMaskFromTitle: self title ! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/14/2003 13:26'! parseTagsFromTitle ^ self class parseTagsFromString: self title! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/6/2003 13:55'! parseUpdateStreamNumbersFromTitle ^ self updateStreamNumbersFromTags: self parseTagsFromTitle! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/6/2003 13:29'! qaFlagsFromTags: aListOfStrings ^ self flagsFromTags: aListOfStrings usingSelector: #flagForQaTag: ! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/6/2003 13:29'! statusFlagsFromTags: aListOfStrings ^ self flagsFromTags: aListOfStrings usingSelector: #flagForStatusTag: ! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/6/2003 19:39'! topicContentFrom: aString | content parenIndex | ((parenIndex _ aString indexOf: $() > 0) ifTrue: [ content _ aString copyFrom: 1 to: (parenIndex - 1) ] ifFalse: [ content _ aString ]. ^ content withBlanksTrimmed! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/6/2003 13:27'! typeFlagsFromTags: aListOfStrings ^ self flagsFromTags: aListOfStrings usingSelector: #flagForTypeTag: ! ! !ArchivePost methodsFor: 'title parsing' stamp: 'bkv 9/6/2003 13:54'! updateStreamNumbersFromTags: aListOfStrings | updateTags updateTag source startIdx endIdx updateNumbers | updateNumbers _ OrderedCollection new. aListOfStrings ifNil: [ ^ nil ]. aListOfStrings isEmpty ifTrue: [ ^ updateNumbers ]. updateTags _ aListOfStrings select: [ :tag | '[update -*]' match: tag ]. updateTags isEmpty ifTrue: [ ^ updateNumbers ]. updateTag _ updateTags first. startIdx _ (updateTag indexOf: $-) + 1. endIdx _ (updateTag indexOf: $]) - 1. source _ (updateTag copyFrom: startIdx to: endIdx) withBlanksTrimmed. (source findTokens: { Character space. $-. }) do: [ :token | updateNumbers add: token asNumber ]. ^ updateNumbers ! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 9/6/2003 13:34'! canonicalQaFlags ^ self class canonicalQaFlags ! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/2/2003 10:05'! canonicalQaTags ^ self class canonicalQaTags ! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 9/6/2003 13:34'! canonicalStatusFlags ^ self class canonicalStatusFlags ! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/7/2003 16:35'! canonicalStatusTags ^ self class canonicalStatusTags ! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 9/6/2003 13:32'! canonicalTypeFlags ^ self class canonicalTypeFlags ! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/2/2003 10:05'! canonicalTypeTags ^ self class canonicalTypeTags ! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/2/2003 08:56'! canonicalTypes ^ self class canonicalTypes ! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/2/2003 10:43'! flagForQaTag: aString ^ self class flagForQaTag: aString! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/9/2003 11:25'! flagForStatusTag: aString ^ self class flagForStatusTag: aString! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/2/2003 11:01'! qaTags ^ self qaFlags collect: [ :flag | self tagForQaFlag: flag ]! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/7/2003 16:41'! statusTags self statusFlags ifNil: [ ^ nil ]. ^ self statusFlags collect: [ :e | self class tagForStatusFlag: e ]! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 6/2/2003 10:59'! tagForQaFlag: aString ^ self class tagForQaFlag: aString! ! !ArchivePost methodsFor: 'title tags' stamp: 'bkv 9/6/2003 13:56'! tagForType: aSymbol ^ self class tagTypeMap keyAtValue: aSymbol ! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 11/6/2003 19:23'! isAnnouncement ^ self typeMask allMask: self class maskForAnnouncement! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 11/6/2003 19:24'! isBug ^ self typeMask allMask: self class maskForBug! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 9/6/2003 12:32'! isBugAndFix ^ self isBug and: [ self isFix ]! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 9/6/2003 12:32'! isBugAndNotFix ^ self isBug and: [ self isFix not ]! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 9/6/2003 12:46'! isBugOnly ^ (self isBug) and: [ self flags size = 1 ]! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 11/6/2003 20:02'! isCanonicalType "Returns whether this post has at least one of the canonical type tags." ^ self typeMask anyMask: self class maskForAllTypesCombined! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 11/6/2003 19:24'! isEnhancement ^ self typeMask allMask: self class maskForEnhancement! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 11/6/2003 19:24'! isFix ^ self typeMask allMask: self class maskForFix! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 9/6/2003 12:32'! isFixAndNotBug ^ self isFix and: [ self isBug not ]! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 11/6/2003 19:24'! isGoodie ^ self typeMask allMask: self class maskForGoodie! ! !ArchivePost methodsFor: 'types testing' stamp: 'bkv 9/6/2003 14:23'! typeTags ^ self typeFlags collect: [ :type | self tagForType: type ]! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 9/6/2003 13:32'! canonicalQaFlags ^ self tagQaMap values! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/2/2003 10:31'! canonicalQaTags ^ self tagQaMap keys! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 9/6/2003 13:32'! canonicalStatusFlags ^ self tagStatusMap values! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/7/2003 16:31'! canonicalStatusTags ^ self tagStatusMap keys! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 9/6/2003 13:31'! canonicalTypeFlags ^self tagTypeMap values! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/2/2003 10:32'! canonicalTypeTags ^ self tagTypeMap keys! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 5/16/2003 16:45'! canonicalTypes ^self tagTypeMap values! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/2/2003 10:43'! flagForQaTag: aString ^ self tagQaMap at: aString ifAbsent: [ nil ]! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/7/2003 16:33'! flagForStatusTag: aString ^ self tagStatusMap at: aString ifAbsent: [ ^ nil ]! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 9/6/2003 13:17'! flagForTypeTag: aString ^self tagTypeMap at: aString ifAbsent: [ nil ] ! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/2/2003 12:58'! tagForQaFlag: aStringOrSymbol ^ self tagQaMap keyAtValue: aStringOrSymbol asSymbol ! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/7/2003 16:32'! tagForStatusFlag: aStringOrSymbol ^ self tagStatusMap keyAtValue: aStringOrSymbol asSymbol ! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/2/2003 08:55'! tagQaMap TagQaMap isNil ifTrue: [ ^ self initializeTagQaMap ]. ^ TagQaMap! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/9/2003 11:26'! tagStatusMap TagStatusMap ifNil: [ self initializeTagStatusMap ]. ^ TagStatusMap! ! !ArchivePost class methodsFor: 'accessing' stamp: 'bkv 6/2/2003 10:32'! tagTypeMap TagTypeMap isNil ifTrue: [ self initializeTagTypeMap ]. ^ TagTypeMap! ! !ArchivePost class methodsFor: 'parsing' stamp: 'bkv 11/6/2003 19:57'! findTokensInListingRow: aString | indexes prevIdx tokens | indexes _ self indexesForUnEscapedListingRow: aString. tokens _ OrderedCollection new. prevIdx _ 1. indexes do: [ :i | tokens add: (aString copyFrom: prevIdx to: i -1). prevIdx _ i + 1. ]. tokens add: (aString copyFrom: prevIdx to: aString size). ^ tokens! ! !ArchivePost class methodsFor: 'parsing' stamp: 'bkv 11/6/2003 19:56'! indexesForUnEscapedListingRow: aString | idx char last indexes | indexes _ OrderedCollection new. idx _ 1. last _ aString lastIndexOf: $:. [ idx < last ] whileTrue: [ char _ aString at: idx. (char = $:) ifTrue: [ indexes add: idx. idx _ idx + 1 ] ifFalse: [ (char = $\) ifTrue: [ "Skip backslash-escaped characters" idx _ idx + 2. ] ifFalse: [ idx _ idx + 1. ]]]. indexes add: last. "Colons within dates are not escaped currently. So just ignore all colons after the fifth un-escaped colon." indexes _ indexes copyFrom: 1 to: 5. ^ indexes ! ! !ArchivePost class methodsFor: 'parsing' stamp: 'bkv 11/4/2003 21:08'! parseReviewStepsMaskFromTitle: aString | rsMask | rsMask _ 0. (('*', self tagForHasBeenDocumented, '*') match: aString) ifTrue: [ rsMask _ rsMask bitOr: self maskForDocumented ]. (('*', self tagForHasBeenReviewed, '*') match: aString) ifTrue: [ rsMask _ rsMask bitOr: self maskForReviewed ]. (('*', self tagForHasSUnitTests, '*') match: aString) ifTrue: [ rsMask _ rsMask bitOr: self maskForSUnitTests ]. (('*', self tagForPassesSLint, '*') match: aString) ifTrue: [ rsMask _ rsMask bitOr: self maskForSLint ]. (('*', self tagForHasBeenTested, '*') match: aString) ifTrue: [ rsMask _ rsMask bitOr: self maskForTested ]. ^ rsMask! ! !ArchivePost class methodsFor: 'parsing' stamp: 'bkv 11/4/2003 22:54'! unEscapeListingRow: aString | unEscapedRow | unEscapedRow _ aString copyReplaceAll: '\\' with: '\' asTokens: false. unEscapedRow _ unEscapedRow copyReplaceAll: '\:' with: ':' asTokens: false. ^ unEscapedRow! ! !ArchivePost class methodsFor: 'bit masks for review steps' stamp: 'bkv 11/4/2003 20:47'! maskForDocumented ^ 1! ! !ArchivePost class methodsFor: 'bit masks for review steps' stamp: 'bkv 11/4/2003 20:48'! maskForReviewed ^ 2! ! !ArchivePost class methodsFor: 'bit masks for review steps' stamp: 'bkv 11/4/2003 20:48'! maskForSLint ^ 4! ! !ArchivePost class methodsFor: 'bit masks for review steps' stamp: 'bkv 11/4/2003 20:50'! maskForSUnitTests ^ 16! ! !ArchivePost class methodsFor: 'bit masks for review steps' stamp: 'bkv 11/4/2003 20:50'! maskForSmall ^ 8! ! !ArchivePost class methodsFor: 'bit masks for review steps' stamp: 'bkv 11/4/2003 20:50'! maskForTested ^ 32! ! !ArchivePost class methodsFor: 'bit masks for status tags' stamp: 'bkv 11/4/2003 20:46'! maskForApproved ^ 4! ! !ArchivePost class methodsFor: 'bit masks for status tags' stamp: 'bkv 11/4/2003 20:45'! maskForClosed ^ 1! ! !ArchivePost class methodsFor: 'bit masks for status tags' stamp: 'bkv 11/4/2003 20:45'! maskForUpdate ^ 2! ! !ArchivePost class methodsFor: 'bit masks for type tags' stamp: 'bkv 11/6/2003 19:27'! maskForAllTypesCombined ^ ((((self maskForAnnouncement bitOr: self maskForBug) bitOr: self maskForEnhancement) bitOr: self maskForFix) bitOr: self maskForGoodie)! ! !ArchivePost class methodsFor: 'bit masks for type tags' stamp: 'bkv 11/4/2003 20:44'! maskForAnnouncement ^ 1! ! !ArchivePost class methodsFor: 'bit masks for type tags' stamp: 'bkv 11/4/2003 20:45'! maskForBug ^ 2! ! !ArchivePost class methodsFor: 'bit masks for type tags' stamp: 'bkv 11/4/2003 20:45'! maskForEnhancement ^ 4! ! !ArchivePost class methodsFor: 'bit masks for type tags' stamp: 'bkv 11/4/2003 20:45'! maskForFix ^ 8! ! !ArchivePost class methodsFor: 'bit masks for type tags' stamp: 'bkv 11/4/2003 20:45'! maskForGoodie ^ 16! ! !ArchivePost class methodsFor: 'class initialization' stamp: 'bkv 9/6/2003 12:28'! initialize "ArchivePost initialize" self initializeTagQaMap. self initializeTagStatusMap. self initializeTagTypeMap.! ! !ArchivePost class methodsFor: 'class initialization' stamp: 'bkv 6/2/2003 11:43'! initializeTagQaMap TagQaMap _ Dictionary new. TagQaMap at: self tagForHasBeenDocumented put: self symbolForHasBeenDocumented. TagQaMap at: self tagForHasBeenReviewed put: self symbolForHasBeenReviewed. TagQaMap at: self tagForHasBeenTested put: self symbolForHasBeenTested. TagQaMap at: self tagForPassesSLint put: self symbolForPassesSLint. TagQaMap at: self tagForIsSmall put: self symbolForIsSmall. TagQaMap at: self tagForHasSUnitTests put: self symbolForHasSUnitTests. ^ TagQaMap! ! !ArchivePost class methodsFor: 'class initialization' stamp: 'bkv 6/7/2003 16:28'! initializeTagStatusMap TagStatusMap _ Dictionary new. TagStatusMap at: self tagForHasBeenClosed put: self symbolForHasBeenClosed. TagStatusMap at: self tagForHasBeenApproved put: self symbolForHasBeenApproved. TagStatusMap at: self tagForHasBecomeAnUpdate put: self symbolForHasBecomeAnUpdate. ^ TagStatusMap! ! !ArchivePost class methodsFor: 'class initialization' stamp: 'bkv 9/6/2003 14:08'! initializeTagTypeMap TagTypeMap _ Dictionary new. TagTypeMap at: self tagForAnnouncement put: self symbolForIsAnnouncement. TagTypeMap at: self tagForBug put: self symbolForIsBug. TagTypeMap at: self tagForEnhancement put: self symbolForIsEnhancement. TagTypeMap at: self tagForFix put: self symbolForIsFix. TagTypeMap at: self tagForGoodie put: self symbolForIsGoodie. ^ TagTypeMap! ! !ArchivePost class methodsFor: 'instance creation' stamp: 'bkv 9/4/2003 17:49'! archive: aBugFixArchive id: aNumberOrString ^ self new archive: aBugFixArchive; id: aNumberOrString; yourself! ! !ArchivePost class methodsFor: 'instance creation' stamp: 'bkv 11/6/2003 08:28'! archive: aBugFixArchive listingRow: aString | ivarValues id post title typeMask statusMask fromLine nameAndEmail authorName authorEmail dateProcessor rfc822Time dateSent | aString isEmptyOrNil ifTrue: [ ^ nil ]. ivarValues _ self findTokensInListingRow: aString. ivarValues isEmptyOrNil ifTrue: [ ^ nil ]. id _ ivarValues first asNumber. post _ self archive: aBugFixArchive id: id. title _ self unEscapeListingRow: (ivarValues at: 2). post title: title. typeMask _ (ivarValues at: 3) asNumber. post typeMask: typeMask. statusMask _ (ivarValues at: 4) asNumber. post statusMask: statusMask. fromLine _ ivarValues at: 5. nameAndEmail _ MailUtil nameAndEmailAddressFromLine: fromLine. authorName _ nameAndEmail first. post authorName: authorName. authorEmail _ nameAndEmail second. post authorEmail: authorEmail. dateProcessor _ MailMessage new. [ rfc822Time _ dateProcessor timeFrom: (ivarValues at: 6). dateSent _ (dateProcessor dateStringFrom: rfc822Time) asDate. post dateSent: dateSent ] on: Error do: [ "Nothing right now." ]. ^ post! ! !ArchivePost class methodsFor: 'type tags' stamp: 'bkv 9/6/2003 14:07'! tagForAnnouncement ^ '[ANN]'! ! !ArchivePost class methodsFor: 'type tags' stamp: 'bkv 9/6/2003 14:07'! tagForBug ^ '[BUG]'! ! !ArchivePost class methodsFor: 'type tags' stamp: 'bkv 9/6/2003 14:07'! tagForEnhancement ^ '[ENH]'! ! !ArchivePost class methodsFor: 'type tags' stamp: 'bkv 9/6/2003 14:07'! tagForFix ^ '[FIX]'! ! !ArchivePost class methodsFor: 'type tags' stamp: 'bkv 9/6/2003 14:07'! tagForGoodie ^ '[GOODIE]'! ! !ArchivePost class methodsFor: 'review step tags' stamp: 'bkv 6/2/2003 11:24'! tagForHasBeenDocumented ^ '[cd]' ! ! !ArchivePost class methodsFor: 'review step tags' stamp: 'bkv 6/2/2003 11:24'! tagForHasBeenReviewed ^ '[er]' ! ! !ArchivePost class methodsFor: 'review step tags' stamp: 'bkv 6/2/2003 11:24'! tagForHasBeenTested ^ '[et]' ! ! !ArchivePost class methodsFor: 'review step tags' stamp: 'bkv 6/2/2003 11:24'! tagForHasSUnitTests ^ '[su]' ! ! !ArchivePost class methodsFor: 'review step tags' stamp: 'bkv 6/2/2003 11:25'! tagForIsSmall ^ '[sm]' ! ! !ArchivePost class methodsFor: 'review step tags' stamp: 'bkv 6/2/2003 11:25'! tagForPassesSLint ^ '[sl]' ! ! !ArchivePost class methodsFor: 'status flags' stamp: 'bkv 6/7/2003 16:25'! tagForHasBecomeAnUpdate ^ '[update]' ! ! !ArchivePost class methodsFor: 'status flags' stamp: 'bkv 6/7/2003 16:24'! tagForHasBeenApproved ^ '[approved]' ! ! !ArchivePost class methodsFor: 'status flags' stamp: 'bkv 6/7/2003 16:24'! tagForHasBeenClosed ^ '[closed]' ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/7/2003 16:26'! symbolForHasBecomeAnUpdate ^ #hasBecomeAnUpdate ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/7/2003 16:26'! symbolForHasBeenApproved ^ #hasBeenApproved ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/7/2003 16:26'! symbolForHasBeenClosed ^ #hasBeenClosed ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/2/2003 11:39'! symbolForHasBeenDocumented ^ #hasBeenDocumented ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/2/2003 11:39'! symbolForHasBeenReviewed ^ #hasBeenReviewed ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/2/2003 11:40'! symbolForHasBeenTested ^ #hasBeenTested ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/2/2003 11:41'! symbolForHasSUnitTests ^ #hasSUnitTests ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 9/6/2003 12:26'! symbolForIsAnnouncement ^ #isAnnouncement ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 9/6/2003 12:26'! symbolForIsBug ^ #isBug ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 9/6/2003 12:26'! symbolForIsEnhancement ^ #isEnhancement ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 9/6/2003 12:26'! symbolForIsFix ^ #isFix ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 9/6/2003 12:27'! symbolForIsGoodie ^ #isGoodie ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/2/2003 11:40'! symbolForIsSmall ^ #isSmall ! ! !ArchivePost class methodsFor: 'flags' stamp: 'bkv 6/2/2003 11:40'! symbolForPassesSLint ^ #passesSLint ! ! !ArchivePost class methodsFor: 'utilities' stamp: 'bkv 6/2/2003 12:54'! harvestingTagSpecs "ArchivePost harvestingTagSpecs" ^ { { 'Documented'. self symbolForHasBeenDocumented. 'Changes have been documented; reasoning is given that explains every change made.' }. { 'Reviewed'. self symbolForHasBeenReviewed. 'Externally reviewed, design + code, by someone quite knowledgeable about the package, other than the author.' }. { 'Tested'. self symbolForHasBeenTested. 'Externally tested; including at minimum: import into a fresh image; generally making sure it does not break anything that uses it; run relevant existing SUnit tests.' }. { 'SLint-Approved'. self symbolForPassesSLint. 'SLint approved. You do not have to do what SLint says -- sometimes it is wrong -- but have a good reason why not.' }. { 'Small'. self symbolForIsSmall. 'Small changeset (10KB or less).' }. { 'SUnit'. self symbolForHasSUnitTests. 'SUnit tests have been created for this code.' }. } ! ! !ArchivePost class methodsFor: 'utilities' stamp: 'bkv 9/15/2003 20:06'! parseTagsFromString: aString "We allow ${ and $}, but convert them to $[ and $]. We allow $|, but convert it to $[. We treat mis-matching counts for $[ and $] as an error." | source startIdxs endIdxs tags tag start end | aString isEmptyOrNil ifTrue: [ ^ nil ]. source _ aString copyReplaceAll: '{' with: '['. source _ source copyReplaceAll: '}' with: ']'. source _ source copyReplaceAll: '|' with: '['. tags _ OrderedCollection new. startIdxs _ SortedCollection new. endIdxs _ SortedCollection new. 1 to: source size do: [ :idx | | char | char _ source at: idx. char == $[ ifTrue: [ startIdxs add: idx ] ifFalse: [ char == $] ifTrue: [ endIdxs add: idx ]]]. startIdxs size = endIdxs size ifFalse: [ self error: 'Tag open/close not matched evenly.' ]. 1 to: startIdxs size do: [ :idx | start _ startIdxs at: idx. end _ endIdxs at: idx. tag _ source copyFrom: start to: end. tags add: tag. ]. ^ tags ! ! !ArchivePostGroup methodsFor: 'initialization' stamp: 'bkv 6/2/2003 17:15'! initialize posts _ SortedCollection sortBlock: [ :a :b | a dateSent < b dateSent ]. ! ! !ArchivePostGroup methodsFor: 'initialization' stamp: 'bkv 9/6/2003 19:39'! printTopicOn: aStream | content | self firstPost ifNil: [ aStream nextPutAll: ''. ^ aStream ]. content _ self firstPost topic. aStream nextPutAll: content. aStream nextPut: Character space. aStream nextPut: $(. aStream nextPutAll: self firstPost dateSent asString. aStream nextPut: $). ! ! !ArchivePostGroup methodsFor: 'initialization' stamp: 'bkv 9/6/2003 19:40'! topicContentMatchesPost: anArchivePost self firstPost ifNil: [ ^ true ]. ^ self firstPost topic = anArchivePost topic! ! !ArchivePostGroup methodsFor: 'modifying' stamp: 'bkv 9/4/2003 19:21'! addPost: anArchivePost "Not sure about raising an error here. For now just do nothing if anArchivePost's title doesn't match this group's topic." (self topicContentMatchesPost: anArchivePost) ifFalse: [^ nil]. (posts includes: anArchivePost) ifTrue: [^ nil]. posts add: anArchivePost. ! ! !ArchivePostGroup methodsFor: 'modifying' stamp: 'bkv 6/3/2003 23:18'! firstPost: anArchivePost "If the topic is already set, don't set a first post that doesn't match the topic." posts notEmpty ifTrue: [ ^ nil ]. self addPost: anArchivePost. ! ! !ArchivePostGroup methodsFor: 'modifying' stamp: 'bkv 6/4/2003 17:25'! removePost: anArchivePost "Not sure about raising an error here. For now just do nothing if anArchivePost's title doesn't match this group's topic." posts remove: anArchivePost ifAbsent: []. self updateAggregatedPost. ! ! !ArchivePostGroup methodsFor: 'modifying' stamp: 'bkv 9/4/2003 18:08'! updateAggregatedPost self firstPost ifNil: [ ^ nil ]. aggregatedPost ifNil: [ aggregatedPost _ ArchivePost new. ]. self error: 'No more ArchivePost#title:'. self error: 'No more ArchivePost#groupDisplayLabel:'. posts do: [ :post | post qaTags do: [ :qaTag | aggregatedPost addQaTag: qaTag ]]. aggregatedPost initialize. ! ! !ArchivePostGroup methodsFor: 'accessing' stamp: 'bkv 6/1/2003 18:16'! aggregatedPost "Return an ArchivePost object that aggregates the body-text from all of the posts in this group." ^ aggregatedPost! ! !ArchivePostGroup methodsFor: 'accessing' stamp: 'bkv 6/2/2003 16:54'! aggregatedQaTags "Returns the list of QA tags from all of the posts in this group." aggregatedPost ifNil: [ ^ #() ]. ^ aggregatedPost qaTags! ! !ArchivePostGroup methodsFor: 'accessing' stamp: 'bkv 6/2/2003 16:16'! allPosts "Return a list of all the posts in this group, with a rollup post at the head of the list." | daPosts | daPosts _ OrderedCollection new. (self firstPost notNil) ifTrue: [ daPosts add: self aggregatedPost. daPosts addAll: self posts. ]. ^ daPosts! ! !ArchivePostGroup methodsFor: 'accessing' stamp: 'bkv 6/3/2003 22:28'! archive self firstPost ifNil: [ ^ nil ]. ^ self firstPost archive! ! !ArchivePostGroup methodsFor: 'accessing' stamp: 'bkv 6/2/2003 14:36'! firstPost posts isEmptyOrNil ifTrue: [ ^ nil ]. ^ posts first! ! !ArchivePostGroup methodsFor: 'accessing' stamp: 'bkv 6/2/2003 16:16'! posts ^ posts! ! !ArchivePostGroup methodsFor: 'accessing' stamp: 'bkv 6/2/2003 08:04'! topic ^ String streamContents: [ :stream | self printTopicOn: stream ].! ! !ArchivePostGroup methodsFor: 'printing' stamp: 'bkv 6/1/2003 16:25'! printGroupDisplayLabelOn: aStream "Returns the label for the aggregatedPost that heads up the group display list" aStream nextPutAll: self topic. ^ aStream ! ! !ArchivePostGroup methodsFor: 'printing' stamp: 'bkv 6/2/2003 15:01'! printOn: aStream ^ aStream nextPutAll: self topic! ! !ArchivePostGroup methodsFor: 'printing' stamp: 'bkv 5/29/2003 22:51'! printRepliesTextOn: aStream self replies do: [ :replyPost | aStream nextPutAll: replyPost text; nextPut: Character cr; nextPutAll: '-------------------'; nextPut: Character cr; yourself ]. ! ! !ArchivePostGroup methodsFor: 'printing' stamp: 'bkv 6/2/2003 15:01'! printString ^ self topic! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/4/2003 15:42'! authorEmailMatches: aString ^ (posts select: [ :any | any authorEmailMatches: aString ]) notEmpty! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/4/2003 15:41'! authorNameMatches: aString ^ (posts select: [ :any | any authorNameMatches: aString ]) notEmpty! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:41'! authorNameOrEmailMatches: aString ^ (posts select: [ :any | any authorNameOrEmailMatches: aString ]) notEmpty! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:30'! hasReviews ^ self numberOfReviews > 1 ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:50'! isBefore: beforeDate andAfter: afterDate posts isEmptyOrNil ifTrue: [ ^ false ]. ^ (posts select: [ :post | post isBefore: beforeDate andAfter: afterDate ]) size == posts size! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:34'! leastRecentDate | leastRecentPost | leastRecentPost _ self leastRecentPost. leastRecentPost ifNil: [ ^ nil ]. ^ leastRecentPost dateSent ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:33'! leastRecentPost posts isEmptyOrNil ifTrue: [ ^ nil ]. ^ posts first! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 9/4/2003 17:23'! maxId self posts isEmptyOrNil ifTrue: [ ^ nil ]. ^ (self posts asSortedCollection: [ :a :b | a id > b id ]) first id ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:32'! mostRecentDate | mostRecentPost | mostRecentPost _ self mostRecentPost. mostRecentPost ifNil: [ ^ nil ]. ^ mostRecentPost dateSent ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/2/2003 14:59'! mostRecentPost posts isEmptyOrNil ifTrue: [ ^ nil ]. ^ posts last! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:37'! numberOfReviews "Returns a guess at the number of reviews. This may not be accurate if the local BugFixArchive has not loaded some posts that belong in this group. That is, the first post in a group may have been posted to the archive in 2001 but the local BugFixArchive has only loaded posts from 2003. In such a case, this number will be inaccurate. The mostRecentDate and leastRecentDate methods are helpful sanity checks." posts isEmptyOrNil ifTrue: [ ^ 0 ]. ^ posts size - 1 ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:35'! postDates posts ifNil: [ ^ nil ]. ^ posts collect: [ :post | post dateSent ] ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 22:13'! size posts isNil ifTrue: [ ^ nil ]. ^ posts size ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 22:14'! sizeMatches: aNumber "Returns whether this group meets the minimum size requirement." posts ifNil: [ ^ false ]. ^ self size >= aNumber ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 7/29/2003 20:17'! titleMatches: aString self aggregatedPost ifNil: [ ^ false ]. ^ self aggregatedPost titleMatches: aString ! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:42'! titleOrBodyMatches: aString ^ (posts select: [ :any | any titleOrBodyMatches: aString ]) notEmpty! ! !ArchivePostGroup methodsFor: 'misc. queries' stamp: 'bkv 6/3/2003 14:42'! yearMatches: aString ^ (posts select: [ :any | any yearMatches: aString ]) notEmpty! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:56'! isAnnouncement ^ self firstPost notNil and: [ self firstPost isAnnouncement ]! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:55'! isBug ^ self firstPost notNil and: [ self firstPost isBug ]! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:57'! isBugAndFix ^ self isBug and: [ self isFix ]! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:57'! isBugAndNotFix ^self isBug and: [ self isFix not ]! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:58'! isBugOnly ^ self firstPost notNil and: [ self firstPost isBugOnly ]! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:56'! isEnhancement ^ self firstPost notNil and: [ self firstPost isEnhancement ]! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:55'! isFix ^ self firstPost notNil and: [ self firstPost isFix ]! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:58'! isFixAndNotBug ^self isFix and: [ self isBug not ]! ! !ArchivePostGroup methodsFor: 'queries about type' stamp: 'bkv 5/29/2003 22:56'! isGoodie ^ self firstPost notNil and: [ self firstPost isGoodie ]! ! !ArchivePostGroup methodsFor: 'queries about status' stamp: 'bkv 6/30/2003 19:06'! hasNoStatus | slackers | slackers _ self posts select: [ :post | post hasNoStatus ]. ^ slackers size == self size ! ! !ArchivePostGroup methodsFor: 'queries about status' stamp: 'bkv 6/9/2003 22:57'! isMarkedAsApproved self firstPost ifNil: [ ^ false ]. ^ (self posts select: [ :any | any isMarkedAsApproved ]) notEmpty! ! !ArchivePostGroup methodsFor: 'queries about status' stamp: 'bkv 6/9/2003 22:58'! isMarkedAsClosed self firstPost ifNil: [ ^ false ]. ^ (self posts select: [ :any | any isMarkedAsClosed ]) notEmpty! ! !ArchivePostGroup methodsFor: 'queries about status' stamp: 'bkv 6/9/2003 22:58'! isMarkedAsUpdate self firstPost ifNil: [ ^ false ]. ^ (self posts select: [ :any | any isMarkedAsUpdate ]) notEmpty! ! !ArchivePostGroup class methodsFor: 'instance creation' stamp: 'bkv 6/2/2003 14:35'! withFirstPost: anArchivePost ^ self new initialize firstPost: anArchivePost; yourself! ! !BugFixArchive methodsFor: 'accessing' stamp: 'bkv 11/6/2003 21:10'! archivePostGroups "Returns this BugFixArchive's list of ArchivePosts, grouped by topic and sorted in descending order by most-recent-post in each group." | sortedGroups | sortedGroups _ topicGroups values asSortedCollection: [ :groupA :groupB | groupA mostRecentPost id > groupB mostRecentPost id ]. ^ sortedGroups ! ! !BugFixArchive methodsFor: 'accessing' stamp: 'bkv 6/3/2003 15:00'! archivePosts ^ archivePosts ! ! !BugFixArchive methodsFor: 'accessing' stamp: 'bkv 9/4/2003 19:13'! name ^ (self class registry keyForIdentity: self) ifNil: [ 'unnamed' ]! ! !BugFixArchive methodsFor: 'accessing' stamp: 'bkv 5/29/2003 22:15'! repository ^ repository! ! !BugFixArchive methodsFor: 'accessing' stamp: 'bkv 5/29/2003 22:17'! repository: aRepository self repository ifNotNil: [ self error: 'This archive already has a repository.' ]. repository _ aRepository.! ! !BugFixArchive methodsFor: 'accessing' stamp: 'bkv 9/4/2003 18:45'! serverUrl self repository ifNil: [ ^ nil ]. ^ self repository serverUrl! ! !BugFixArchive methodsFor: 'accessing' stamp: 'bkv 11/6/2003 20:57'! topics ^ topicGroups keys copy! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 5/28/2003 21:36'! announcementPosts ^ self archivePosts select: [ :post | post isAnnouncement ] ! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/9/2003 13:00'! approvedArchivePostGroups self archivePostGroups ifNil: [ ^ nil ]. ^ (self archivePostGroups select: [ :every | every isMarkedAsApproved ]) ! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 9/13/2003 09:49'! archivePostsSortedByDate "Archive posts grouped by topic and then sorted in descending order by date." ^ self archivePosts asSortedCollection: self dateDescendingSortBlock! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 5/28/2003 21:37'! bugPosts ^ self archivePosts select: [ :post | post isBug ] ! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/9/2003 12:59'! closedArchivePostGroups self archivePostGroups ifNil: [ ^ nil ]. ^ (self archivePostGroups select: [ :every | every isMarkedAsClosed ]) ! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 5/28/2003 21:37'! enhancementPosts ^ self archivePosts select: [ :post | post isEnhancement ] ! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 5/28/2003 21:37'! fixPosts ^ self archivePosts select: [ :post | post isFix ] ! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 5/28/2003 21:37'! goodiePosts ^ self archivePosts select: [ :post | post isGoodie ] ! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/7/2003 16:49'! openArchivePostGroups | closed | closed _ self closedArchivePostGroups. self archivePostGroups ifNil: [ ^ nil ]. ^ self archivePostGroups reject: [ :any | closed includes: any ] ! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:11'! postsForMonth: aMonth "This assumes a generic Month object ( which in Squeak, specifies the year as well )." ^ self archivePosts select: [ :post | post monthMatches: aMonth ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:12'! postsForMonth: aMonth andYear: aNumber ^ self archivePosts select: [ :post | post monthMatches: aMonth andYearMatches: aNumber ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:11'! postsForYear: aNumber ^ self archivePosts select: [ :post | post yearMatches: aNumber ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:14'! postsWithAuthorEmail: aString ^ self archivePosts select: [ :post | post authorEmailMatches: aString ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:14'! postsWithAuthorName: aString ^ self archivePosts select: [ :post | post authorNameMatches: aString ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:16'! postsWithAuthorNameOrEmail: aString ^ self archivePosts select: [ :post | post authorNameOrEmailMatches: aString ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:13'! postsWithBodyMatching: aString ^ self archivePosts select: [ :post | post bodyMatches: aString ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:13'! postsWithTitleMatching: aString ^ self archivePosts select: [ :post | post titleMatches: aString ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/2/2003 10:14'! postsWithTitleOrBodyMatching: aString ^ self archivePosts select: [ :post | post titleOrBodyMatches: aString ]! ! !BugFixArchive methodsFor: 'enumerating' stamp: 'bkv 6/9/2003 12:58'! updateStreamArchivePostGroups self archivePostGroups ifNil: [ ^ nil ]. ^ (self archivePostGroups select: [ :every | every isMarkedAsUpdate ]) ! ! !BugFixArchive methodsFor: 'initialization' stamp: 'bkv 11/7/2003 14:01'! initialize archivePosts _ SortedCollection sortBlock: self idDescendingSortBlock. topicGroups _ Dictionary new. ! ! !BugFixArchive methodsFor: 'modifying' stamp: 'bkv 11/6/2003 21:04'! addArchivePost: aPost | groupTopic group | aPost ifNil: [ ^ nil ]. "We only archive aPost if it is a [ANN],[BUG],[ENH],[FIX] or [GOODIE] post" aPost isCanonicalType ifFalse: [ ^ nil ]. groupTopic _ aPost topic. (groupTopic isEmptyOrNil) ifFalse: [ self archivePosts add: aPost ]. group _ topicGroups at: groupTopic ifAbsent: [ nil ]. group isNil ifTrue: [ topicGroups at: groupTopic put: (ArchivePostGroup withFirstPost: aPost) ] ifFalse: [ group addPost: aPost. ]. ^ aPost ! ! !BugFixArchive methodsFor: 'modifying' stamp: 'bkv 9/13/2003 09:36'! removeArchivePost: aPost archivePosts remove: aPost. ! ! !BugFixArchive methodsFor: 'printing' stamp: 'nk 6/28/2003 13:23'! printOn: aStream super printOn: aStream. aStream nextPut: $(; nextPutAll: self name; nextPut: $).! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 9/16/2003 22:03'! attachmentsDirectoryForId: aNumberOrString self repository ifNil: [ ^ nil ]. ^ self repository attachmentsDirectoryForId: aNumberOrString! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 9/16/2003 22:04'! attachmentsForId: aNumberOrString self repository ifNil: [ ^ nil ]. ^ self repository attachmentsForId: aNumberOrString! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 9/14/2003 12:27'! byteSize "Answer a (somewhat low) estimate of how much space I take up" " BugFixArchive allSubInstances detectSum: [ :ea | ea byteSize ] " | dataSize | dataSize _ self class instSize * 4. dataSize _ dataSize + self repository byteSize. dataSize _ dataSize + (self archivePosts detectSum: [ :post | post byteSize ]). dataSize _ dataSize + self archivePostGroups size * 4. ^ dataSize! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 9/4/2003 17:54'! fullFileNameForId: aNumberOrString self repository ifNil: [ ^ nil ]. ^ self repository fullFileNameForId: aNumberOrString! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 9/15/2003 21:40'! mailMessageForId: aNumberOrString self repository ifNil: [ ^ nil ]. ^ self repository mailMessageForId: aNumberOrString! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 5/28/2003 08:59'! monthsRepresentedForYear: aNumber ^((self postsForYear: aNumber) collect: [ :ea | ea dateSent month ]) asSet asSortedCollection! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 9/5/2003 08:11'! size ^ self archivePosts size! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 9/4/2003 17:52'! sourceFileForId: aNumberOrString self repository ifNil: [ ^ nil ]. ^ self repository sourceFileForId: aNumberOrString! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 9/13/2003 17:36'! titleForPostWithId: aStringOrNumber self repository ifNil: [ ^ nil ]. ^ self repository titleForPostWithId: aStringOrNumber! ! !BugFixArchive methodsFor: 'services' stamp: 'bkv 5/28/2003 08:59'! yearsRepresented "Return descending sort of the set of years spanned by this archive's posts." ^ ((self archivePosts) collect: [ :ea | ea dateSent year ]) asSet asSortedCollection: [ :a :b | a > b ]! ! !BugFixArchive methodsFor: 'sorting' stamp: 'bkv 11/7/2003 14:02'! dateAscendingSortBlock ^[ :a :b | (a dateSent < b dateSent) or: [ b dateSent isNil ]] copy fixTemps! ! !BugFixArchive methodsFor: 'sorting' stamp: 'bkv 11/7/2003 14:02'! dateDescendingSortBlock ^[ :a :b | (a dateSent > b dateSent) or: [ b dateSent isNil ]] copy fixTemps! ! !BugFixArchive methodsFor: 'sorting' stamp: 'bkv 9/4/2003 17:25'! defaultSortBlock ^self idDescendingSortBlock! ! !BugFixArchive methodsFor: 'sorting' stamp: 'bkv 9/4/2003 17:24'! idAscendingSortBlock ^[ :a :b | a id < b id ] copy fixTemps! ! !BugFixArchive methodsFor: 'sorting' stamp: 'bkv 9/4/2003 17:24'! idDescendingSortBlock ^[ :a :b | a id > b id ] copy fixTemps! ! !BugFixArchive methodsFor: 'sorting' stamp: 'nk 6/28/2003 10:25'! sortByDateAscending archivePosts sortBlock: self dateAscendingSortBlock! ! !BugFixArchive methodsFor: 'sorting' stamp: 'nk 6/28/2003 10:26'! sortByDateDescending archivePosts sortBlock: self dateDescendingSortBlock! ! !BugFixArchive methodsFor: 'sorting' stamp: 'bkv 9/4/2003 17:25'! sortByIdAscending archivePosts sortBlock: self idAscendingSortBlock! ! !BugFixArchive methodsFor: 'sorting' stamp: 'bkv 9/4/2003 17:25'! sortByIdDescending archivePosts sortBlock: self idDescendingSortBlock! ! !BugFixArchive methodsFor: 'testing' stamp: 'bkv 9/4/2003 19:04'! isUpdatable ^ self repository notNil! ! !BugFixArchive methodsFor: 'updates' stamp: 'nk 6/28/2003 13:15'! listChanged "Notify my dependents that my contents have changed in some important way" self changed: #listChanged! ! !BugFixArchive methodsFor: 'updates' stamp: 'bkv 11/6/2003 20:37'! loadEverything "Returns the list of all ArchivePost objects loaded into this BugFixArchive after its repository updates itself." | listingRows posts | self repository ifNil: [ ^ nil ]. listingRows _ self repository loadEverything. posts _ listingRows collect: [ :listingRow | ArchivePost archive: self listingRow: listingRow ]. posts do: [ :post | self addArchivePost: post ]. self listChanged. ^ posts! ! !BugFixArchive methodsFor: 'updates' stamp: 'bkv 11/4/2003 22:38'! loadUpdates "Returns the list of new ArchivePost objects loaded into this BugFixArchive after its repository updates itself." | listingRows posts | self repository ifNil: [ ^ nil ]. listingRows _ self repository loadUpdates. posts _ listingRows collect: [ :listingRow | ArchivePost archive: self listingRow: listingRow ]. posts do: [ :post | self addArchivePost: post ]. self listChanged. ^ posts! ! !BugFixArchive methodsFor: 'validation' stamp: 'bkv 8/20/2003 20:07'! validate self error: 'Implement me!!' ! ! !BugFixArchive class methodsFor: 'instance creation' stamp: 'bkv 9/21/2003 18:30'! defaultArchive "BugFixArchive defaultArchive" ^ self sqFoundationArchive! ! !BugFixArchive class methodsFor: 'instance creation' stamp: 'bkv 9/4/2003 19:09'! new ^ super new initialize! ! !BugFixArchive class methodsFor: 'instance creation' stamp: 'bkv 9/6/2003 19:13'! sqFoundationArchive "BugFixArchive sqFoundationArchive" ^ self named: #sqFoundationArchive ifAbsentPut: [ self withRepository: HttpEmailFileRepository sqFoundationRepository. ]! ! !BugFixArchive class methodsFor: 'instance creation' stamp: 'bkv 11/6/2003 21:06'! withRepository: anHttpEmailFileRepository | archive | archive _ self new initialize repository: anHttpEmailFileRepository. ^ archive! ! !BugFixArchive class methodsFor: 'load filters' stamp: 'bkv 9/6/2003 19:16'! defaultLoadFilterSelectors ^ #( isBug isEnhancement isFix isGoodie )! ! !BugFixArchive class methodsFor: 'registry' stamp: 'bkv 9/5/2003 07:32'! clearRegistry "BugFixArchive clearRegistry" "Initialize my registry, thus forgetting all the registered BugFixArchives" ^ Registry _ WeakValueDictionary new! ! !BugFixArchive class methodsFor: 'registry' stamp: 'bkv 9/4/2003 19:15'! forgetArchiveNamed: aSymbol "Remove the BugFixArchive named aSymbol from my registry, if there is such an entry." ^ self registry removeKey: aSymbol ifAbsent: []! ! !BugFixArchive class methodsFor: 'registry' stamp: 'bkv 9/4/2003 19:15'! named: aSymbol ifAbsentPut: aBlock "Answer the BugFixArchive named aSymbol. If it doesn't exist, or has been garbage collected, register the value of aBlock under aSymbol and answer that." | retval | retval _ self registry at: aSymbol ifAbsentPut: [ nil ]. ^ retval ifNil: [ self registry at: aSymbol put: aBlock value ]. ! ! !BugFixArchive class methodsFor: 'registry' stamp: 'bkv 9/4/2003 19:15'! registry "Answer my registry, which is a Dictionary of name->BugFixArchive" ^ Registry ifNil: [ self clearRegistry ].! ! !HttpEmailFileRepository methodsFor: 'accessing' stamp: 'bkv 10/30/2003 19:14'! listing ^ self listingFromFile! ! !HttpEmailFileRepository methodsFor: 'accessing' stamp: 'bkv 9/13/2003 19:25'! listingIds ^ self listing keys asSortedCollection! ! !HttpEmailFileRepository methodsFor: 'accessing' stamp: 'bkv 9/6/2003 17:46'! listingTitles ^ self listing values! ! !HttpEmailFileRepository methodsFor: 'accessing' stamp: 'bkv 9/16/2003 21:54'! mailMessageForId: aNumberOrString | id fileName | id _ aNumberOrString asNumber. fileName _ self fullFileNameForId: id. (self emailsDir fileExists: fileName) ifFalse: [ | zipInterval zipMember | "Check to see if an already downloaded ZIP contains the file." zipInterval _ self zipIntervalForId: id. zipInterval isNil ifTrue: [ | zipList downloads | "Download ZIP filename". zipList _ self downloadZipsListForIds: { id }. zipList isEmptyOrNil ifFalse: [ "Download actual ZIP file." downloads _ self downloadZips: zipList. downloads isEmptyOrNil ifFalse: [ self error: 'Failed to download ZIP containing id: ', id asString. ]. ]. ]. "Try to extract member from already downloaded ZIP". zipMember _ self zipMemberForId: id. zipMember ifNotNil: [ "If the ZIP exists and contains the file, extract the file." zipMember extractToFileNamed: fileName. ]. ]. ^ MailUtil mailMessageFromFile: fileName ! ! !HttpEmailFileRepository methodsFor: 'accessing' stamp: 'bkv 9/16/2003 20:54'! postIds ^ self listingIds! ! !HttpEmailFileRepository methodsFor: 'accessing' stamp: 'bkv 8/18/2003 20:42'! serverUrl ^ serverUrl! ! !HttpEmailFileRepository methodsFor: 'accessing' stamp: 'bkv 8/20/2003 23:26'! serverUrl: aUrlOrString | newDirName | serverUrl _ aUrlOrString asUrl. newDirName _ self createDirsForUrl: serverUrl. repositoryDir _ repositoryDir directoryNamed: newDirName. ! ! !HttpEmailFileRepository methodsFor: 'accessing' stamp: 'bkv 9/13/2003 17:34'! titleForPostWithId: aStringOrNumber ^ self listing at: aStringOrNumber asNumber ifAbsent: [ nil ]! ! !HttpEmailFileRepository methodsFor: 'downloads' stamp: 'bkv 11/6/2003 19:46'! appendListingBytes: aByteArray | listingFile | listingFile _ self repositoryDir fileNamed: self listingFileName. listingFile binary. listingFile setToEnd. listingFile nextPutAll: aByteArray. listingFile close. ^ aByteArray ! ! !HttpEmailFileRepository methodsFor: 'downloads' stamp: 'bkv 11/4/2003 21:34'! downloadListing "Download the BFAV listing from the server over HTTP. The download request uses If-Changed-Since and Range headers to only download the bytes added to the listing on the server since this client's last download of the listing file." | bytes numBytes | self repositoryDir ifNil: [self error: 'No repository directory has been defined.']. numBytes _ 0. "If we have never downloaded the listing before, then download the zipped listing first." (self listingFileExists) ifFalse: [(self zippedListingFileExists) ifFalse: [self downloadZippedListing. bytes _ self extractZippedListing. numBytes _ bytes size]]. "Download and append the difference between the server listing and the local listing." (bytes isEmptyOrNil not and: [self listingFileExists]) ifTrue: [ bytes _ bytes, self downloadListingDelta. bytes ifNotNil: [ numBytes _ bytes size. ]. (numBytes > 0) ifTrue: [ self appendListingBytes: bytes. ]]. ^ bytes! ! !HttpEmailFileRepository methodsFor: 'downloads' stamp: 'bkv 10/30/2003 19:42'! downloadListingDelta "Download the BFAV listing from the server over HTTP. The download request uses the Range HTTP header to only download the bytes added to the listing on the server since this client's last download of the listing file." | numBytes response bytes | self listingFileExists ifFalse: [ ^ nil ]. numBytes _ self listingFile size. response _ self httpGet: self listingUrl withRange: numBytes asString, '-'. (response notNil and: [response isSuccessful]) ifTrue: [ bytes _ response bodyBytes. ]. ^ bytes! ! !HttpEmailFileRepository methodsFor: 'downloads' stamp: 'bkv 10/30/2003 19:40'! downloadZippedListing "Download the BFAV listing from the server over HTTP. The download request uses If-Changed-Since and Range headers to only download the bytes added to the listing on the server since this client's last download of the listing file." | response zipBytes zipFile | self zippedListingFileExists ifTrue: [ self error: 'ZIP for BFAV listing already exists locally, so I''m not going to download it again.' ]. response _ self httpGet: self zippedListingUrl. (response notNil and: [response isSuccessful]) ifTrue: [ zipBytes _ response bodyBytes. (zipBytes isNil or: [zipBytes size = 1]) ifTrue: [ self error: 'No bytes available for BFAV listing ZIP.'. ]. zipFile _ self repositoryDir fileNamed: self zippedListingFileName. zipFile binary. zipFile nextPutAll: zipBytes. zipFile close. ]. ^ zipFile! ! !HttpEmailFileRepository methodsFor: 'downloads' stamp: 'bkv 10/30/2003 19:44'! downloadZips: aListOfStrings "Downloads ZIP files from the server." | url response fn file downloads alreadyHere diff | aListOfStrings isEmptyOrNil ifTrue: [ ^ #() ]. downloads _ OrderedCollection new. alreadyHere _ self repositoryDir fileNames. aListOfStrings do: [ :name | fn _ name withBlanksTrimmed. ((alreadyHere includes: fn) not or: [ fn = 'latest.zip' ]) ifTrue: [ file _ self repositoryDir fileNamed: fn. url _ (self serverUrl asString, '/', fn) asUrl. response _ self httpGet: url. (response notNil and: [response isSuccessful]) ifTrue: [ file binary nextPutAll: response bodyBytes. file close. downloads add: fn. ]. ]. ]. diff _ (aListOfStrings difference: downloads) difference: alreadyHere. diff size > 0 ifTrue: [ Transcript cr; show: 'Failed to download ', diff asString. self downloadZips: diff. ]. ^ downloads ! ! !HttpEmailFileRepository methodsFor: 'downloads' stamp: 'bkv 11/4/2003 20:20'! downloadZipsListForIds: aListOfIds "Returns the server's list of ZIP files that span aListOfIds." | url response bytes file zipsList | aListOfIds ifNil: [ ^ nil ]. aListOfIds isEmpty ifTrue: [ ^ #() ]. url _ self emailFileZipsListUrlFor: aListOfIds. response _ self httpGet: url. (response notNil and: [response isSuccessful]) ifTrue: [ bytes _ response bodyBytes. ] ifFalse: [ self error: 'Failed to download list of zipped email files.' ]. bytes ifNil: [ ^ nil ]. self repositoryDir deleteFileNamed: 'zips.list' ifAbsent: []. file _ self repositoryDir fileNamed: 'zips.list'. file binary nextPutAll: bytes. file close. zipsList _ response body withBlanksTrimmed findTokens: Character lf. ^ zipsList ! ! !HttpEmailFileRepository methodsFor: 'downloads' stamp: 'bkv 10/30/2003 19:38'! httpGet: aUrlOrString ^ self httpGet: aUrlOrString withRange: nil! ! !HttpEmailFileRepository methodsFor: 'downloads' stamp: 'bkv 10/30/2003 19:37'! httpGet: aUrlOrString withRange: aRangeString | request response | [ request _ SptHTTPRequest new. request openGetTo: aUrlOrString asString; addHeader: 'Accept' value: '*/*'; isFollowRedirects: false. aRangeString isEmptyOrNil ifFalse: [ request addHeader: 'Range' value: 'bytes=', aRangeString. ]. request send; waitOnReady. response _ request lastResponse. ] on: Error do: [ :err | self inform: 'A error has occurred: ', err messageText, '. This has prevented a successful HTTP response.' ]. ^ response! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/4/2003 08:47'! attachmentsDirectoryForId: aNumberOrString ^ self repositoryDir directoryNamed: aNumberOrString asString, '-attachments'! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/16/2003 22:08'! attachmentsForId: aNumberOrString | attachmentDir attachmentFileNames | attachmentDir _ self attachmentsDirectoryForId: aNumberOrString. attachmentDir ifNil: [ ^ nil ]. attachmentFileNames _ attachmentDir fileNames. attachmentFileNames isEmptyOrNil ifTrue: [ attachmentFileNames _ self extractAndSaveAttachmentsForId: aNumberOrString ]. ^ attachmentFileNames! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 8/20/2003 22:16'! createDirsForUrl: aUrlOrString | url subDirNames currentDir | aUrlOrString asString isEmptyOrNil ifTrue: [ ^nil ]. url _ aUrlOrString asUrl. subDirNames _ { url authority }, url path. subDirNames _ subDirNames reject: [ :any | (any endsWith: 'txt') or: [ repositoryDir pathParts includes: any ]]. currentDir _ self repositoryDir. subDirNames do: [ :dName | currentDir assureExistenceOfPath: dName. currentDir _ currentDir directoryNamed: dName ]. ^ currentDir pathName! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/13/2003 11:53'! emailFileNames ^ self emailsDir fileNames collect: [ :fn | self repositoryDir fullNameFor: fn ]! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/6/2003 17:35'! emailsDir | dir | dir _ self repositoryDir directoryNamed: 'emails'. dir assureExistence. ^ dir! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/16/2003 21:12'! extractAndSaveAttachmentsForId: aNumberOrString | id mailMsg formattedParts atts attFileNames | id _ aNumberOrString asNumber. mailMsg _ self mailMessageForId: id. mailMsg ifNil: [ ^ nil ]. mailMsg fields isEmpty ifTrue: [ ^ #() ]. mailMsg body isMultipart ifFalse: [ ^ #() ]. formattedParts _ MailUtil parsePartsFor: mailMsg. (formattedParts isEmptyOrNil) ifTrue: [ ^ #() ]. atts _ MailUtil attachmentPartsFrom: formattedParts. atts isEmptyOrNil ifTrue: [ ^ #() ]. self emailsDir assureExistence. attFileNames _ OrderedCollection new. atts do: [ :part | | stream streamSize file fileSize sizeDiff | (part isKindOf: MailMessage) ifTrue: [ stream _ RWBinaryOrTextStream with: part body content. file _ self emailsDir fileNamed: (self emailsDir localNameFor: part name). ]. streamSize _ stream contents size. fileSize _ file contents size. "Don't unnecessarily append to an existing file" sizeDiff _ streamSize - fileSize. (sizeDiff >= 0) ifTrue: [ file nextPutAll: (stream contents copyFrom: (streamSize - sizeDiff) + 1 to: streamSize ). ]. stream close. file close. attFileNames add: file name. ]. ^ attFileNames ! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/6/2003 17:36'! fileExtension ^ '.eml'! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 8/20/2003 22:04'! fileNames ^ self repositoryDir fileNames collect: [ :fn | self repositoryDir fullNameFor: fn ]! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/6/2003 17:36'! fullFileNameForId: aNumberOrString ^ self emailsDir fullNameFor: (self localFileNameForId: aNumberOrString)! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/13/2003 22:23'! listingFile ^ (self repositoryDir fileExists: self listingFileName) ifTrue: [ self repositoryDir readOnlyFileNamed: self listingFileName ] ifFalse: [ nil ]! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 10/29/2003 21:58'! listingFileExists ^ self repositoryDir fileExists: self listingFileName ! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 10/29/2003 21:32'! listingFileName ^ self repositoryDir fullNameFor: 'listing'! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/13/2003 22:25'! listingFromFile | file | file _ self listingFile. ^ file notNil ifTrue: [ self listingFromStream: file ] ifFalse: [ nil ]! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/6/2003 19:04'! listingFromStream: aStream | lines map id title | aStream ifNil: [ ^ nil ]. lines _ aStream contents asString findTokens: Character cr. "Extract id->title map" map _ Dictionary new. lines do: [ :line | id _ (line copyUpTo: $:) withBlanksTrimmed asNumber. title _ (line copyAfter: $:) withBlanksTrimmed. map at: id put: title. ]. ^ map! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 9/4/2003 08:33'! localFileNameForId: aNumberOrString ^ aNumberOrString asString, self fileExtension! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 8/18/2003 20:28'! repositoryDir ^ repositoryDir! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 10/29/2003 21:33'! zippedListingFile | fn | fn _ self zippedListingFileName. ^ (self repositoryDir fileExists: fn) ifTrue: [ self repositoryDir readOnlyFileNamed: fn ] ifFalse: [ nil ]! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 10/29/2003 21:58'! zippedListingFileExists ^ self repositoryDir fileExists: self zippedListingFileName! ! !HttpEmailFileRepository methodsFor: 'files' stamp: 'bkv 10/29/2003 21:32'! zippedListingFileName ^ self repositoryDir fullNameFor: 'listing.zip'! ! !HttpEmailFileRepository methodsFor: 'initialization' stamp: 'bkv 8/18/2003 20:28'! baseDirName ^ 'email-file-repository'! ! !HttpEmailFileRepository methodsFor: 'initialization' stamp: 'bkv 9/8/2003 21:04'! onDirectory: aDirectory "Set aDirectory as this repository's base directory." repositoryDir _ aDirectory directoryNamed: self baseDirName. repositoryDir assureExistence. ! ! !HttpEmailFileRepository methodsFor: 'testing' stamp: 'bkv 9/5/2003 08:17'! byteSize "Answer a (somewhat low) estimate of how much space I take up" " HttpEmailFileRepository allSubInstances detectSum: [ :ea | ea byteSize ] " | dataSize | dataSize _ self class instSize * 4. dataSize _ dataSize + 4. "repositoryDir" dataSize _ dataSize + self serverUrl asString size. ^ dataSize! ! !HttpEmailFileRepository methodsFor: 'testing' stamp: 'bkv 9/13/2003 11:56'! size | fns | fns _ self fileNames. fns ifNil: [ ^ 0 ]. fns addAll: self emailFileNames. ^ fns size! ! !HttpEmailFileRepository methodsFor: 'updates' stamp: 'bkv 11/7/2003 14:14'! listingRows self listingFileExists ifFalse: [ self downloadListing ]. ^ self rowsFromListingBytes: self listingFile binary contents ! ! !HttpEmailFileRepository methodsFor: 'updates' stamp: 'bkv 11/6/2003 20:34'! loadEverything "Load all BFAV posts stored locally. Then load updates, if there are any." | listingRows | listingRows _ self listingRows. listingRows add: self loadUpdates. ^ listingRows ! ! !HttpEmailFileRepository methodsFor: 'updates' stamp: 'bkv 11/4/2003 22:53'! loadUpdates | listingDelta | listingDelta _ self updateListing. ^ (listingDelta isEmptyOrNil) ifTrue: [ {} ] ifFalse: [ self rowsFromListingBytes: listingDelta ] ! ! !HttpEmailFileRepository methodsFor: 'updates' stamp: 'bkv 11/4/2003 22:47'! rowsFromListingBytes: aByteArray | rows | rows _ aByteArray asString findTokens: Character cr. ^ rows! ! !HttpEmailFileRepository methodsFor: 'updates' stamp: 'bkv 11/4/2003 21:28'! updateListing "Updates the local copy of the BFAV listing. Returns the downloaded bytes." | downloadedBytes | downloadedBytes _ self downloadListing. ^ downloadedBytes! ! !HttpEmailFileRepository methodsFor: 'urls' stamp: 'bkv 11/4/2003 20:17'! emailFileZipsListUrl ^ (self serverUrl asString, '/', 'getPosts.cgi') asUrl! ! !HttpEmailFileRepository methodsFor: 'urls' stamp: 'bkv 11/4/2003 20:18'! emailFileZipsListUrlFor: aListOfIds | sortedIds url | sortedIds _ aListOfIds asSortedCollection: [ :a :b | a < b ]. url _ (self emailFileZipsListUrl asString, '?', 'from=', sortedIds first asString, '&', 'to=', sortedIds last asString) asUrl. ^ url! ! !HttpEmailFileRepository methodsFor: 'urls' stamp: 'bkv 9/6/2003 17:17'! listingUrl ^ (self serverUrl asString, '/', 'listing') asUrl! ! !HttpEmailFileRepository methodsFor: 'urls' stamp: 'bkv 10/29/2003 21:28'! zippedListingUrl ^ (self listingUrl asString, '.zip') asUrl! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 11/4/2003 21:31'! extractZippedListing self listingFileExists ifTrue: [ self error: 'Can''t extract BFAV listing file from this ZIP because the BFAV listing file already exists locally.' ]. self zipArchiveForListing extractMember: 'listing' toFileNamed: self listingFileName. ^ self listingFile binary contents! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/16/2003 21:22'! fileNameForZipInterval: anInterval ^ anInterval first asString, '-', anInterval last asString, '.zip' ! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/15/2003 22:41'! latestPostIds ^ self postIdsFromZip: self latestZip ! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/16/2003 21:07'! latestZip ^ self zipArchiveNamed: 'latest.zip' ! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/13/2003 18:26'! postIdsFromZip: aZipArchive ^ aZipArchive members collect: [ :zipMem | zipMem fileName asNumber. ]! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/6/2003 14:32'! startId "1.txt is a placeholder file." ^ 2 ! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 10/29/2003 21:36'! zipArchiveForListing ^ self zipArchiveNamed: self zippedListingFileName! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/16/2003 21:06'! zipArchiveNamed: aLocalFileName ^ ZipArchive new readFrom: (self repositoryDir fullNameFor: aLocalFileName) ! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/13/2003 11:56'! zipFileNames ^ self fileNames select: [ :e | e endsWith: '.zip' ]! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/16/2003 21:05'! zipIntervalForId: aNumberOrString | id interval | id _ aNumberOrString asNumber. interval _ self zipIntervals detect: [ :any | any includes: id ] ifNone: [ nil ]. ^ interval! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/16/2003 21:01'! zipIntervals "Returns the list of Intervals covered by the downloaded ZIP files, sorted by last value." | intervals | intervals _ OrderedCollection new. self zipFileNames do: [ :e | | source | source _ e copyFrom: ((e lastIndexOf: $/) + 1) to: ((e lastIndexOf: $.)-1). (source = 'latest') ifTrue: [ | postIds | postIds _ self latestPostIds asSortedCollection. intervals add: (Interval from: postIds first to: postIds last). ] ifFalse: [ intervals add: source asInterval. ]. ]. ^ intervals asSortedCollection: [ :a : b | a last < b last ]! ! !HttpEmailFileRepository methodsFor: 'zips' stamp: 'bkv 9/16/2003 21:23'! zipMemberForId: aNumberOrString | id zipIntervals interval fileName zip memberName member | id _ aNumberOrString asNumber. zipIntervals _ self zipIntervals. interval _ zipIntervals detect: [ :any | any includes: id ] ifNone: [ nil ]. interval ifNil: [ ^ nil ]. interval = zipIntervals last ifTrue: [ fileName _ 'latest.zip' ] ifFalse: [ fileName _ self fileNameForZipInterval: interval. ]. zip _ self zipArchiveNamed: fileName. memberName _ id asString, '.eml'. member _ zip members detect: [ :zipMember | zipMember fileName = memberName ] ifNone: [ nil ]. ^ member ! ! !HttpEmailFileRepository class methodsFor: 'instance creation' stamp: 'bkv 10/30/2003 07:07'! sqFoundationRepository "HttpEmailFileRepository sqFoundationRepository" ^ self withServerUrl: 'http://bfav.squeakfoundation.org' asUrl ! ! !HttpEmailFileRepository class methodsFor: 'instance creation' stamp: 'bkv 9/4/2003 17:38'! withServerUrl: aUrlOrString ^ self withServerUrl: aUrlOrString asUrl onDirectory: FileDirectory default. ! ! !HttpEmailFileRepository class methodsFor: 'instance creation' stamp: 'bkv 9/4/2003 17:37'! withServerUrl: aUrlOrString onDirectory: aDirectory ^ self new onDirectory: aDirectory; serverUrl: aUrlOrString asUrl; yourself! ! !MailUtil class methodsFor: 'mail-message-utilities' stamp: 'bkv 9/4/2003 08:28'! attachmentPartsFrom: aMailMessageAtomicParts aMailMessageAtomicParts isEmptyOrNil ifTrue: [ ^ #() ]. ^ (aMailMessageAtomicParts select: [ :mailMsg | mailMsg notNil and: [ 'application/*' match: mailMsg body contentType ]]) reject: [ :attachPart | attachPart name isEmptyOrNil ]! ! !MailUtil class methodsFor: 'mail-message-utilities' stamp: 'bkv 5/28/2003 07:35'! collectPartsFrom: aMailMessage | parts | parts _ OrderedCollection new. ( aMailMessage body isMultipart ) ifTrue: [ parts addAll: aMailMessage atomicParts. ( parts isNil or: [ parts isEmpty ] ) ifTrue: [ ^ #() ]. parts do: [ :mailMsg | mailMsg format ]]. ^ parts ! ! !MailUtil class methodsFor: 'mail-message-utilities' stamp: 'bkv 9/6/2003 19:11'! mailMessageFromFile: aFileName | file mailMsg | file _ (CrLfFileStream readOnlyFileNamed: aFileName) text. mailMsg _ MailMessage from: file contents. ^ mailMsg! ! !MailUtil class methodsFor: 'mail-message-utilities' stamp: 'bkv 11/6/2003 19:18'! nameAndEmailAddressFromLine: aString | fromLine emailList email nameTokens fromName | aString ifNil: [ ^ nil ]. fromLine _ aString. [ emailList _ MailAddressParser addressesIn: aString. ] on: Error do: [ "A hack to handle the case where the from line is the email address followed by a sentence." ( fromLine beginsWith: '"' ) ifTrue: [ fromLine _ fromLine copyFrom: 2 to: fromLine size. ]. emailList _ fromLine findTokens: Character space. [ emailList _ MailAddressParser addressesIn: emailList first. ] on: Error do: [ emailList _ #() ]]. (emailList isEmptyOrNil) ifFalse: [ email _ emailList first ]. email ifNotNil: [ fromName _ (aString copyUpTo: $<) withBlanksTrimmed. ]. name ifNotNil: [ ((fromName beginsWith: '"') and: [fromName endsWith: '"']) ifTrue: [ nameTokens _ fromName findTokens: $". nameTokens isEmptyOrNil ifFalse: [ fromName _ nameTokens first. ]]]. ^ { fromName. email. } ! ! !MailUtil class methodsFor: 'mail-message-utilities' stamp: 'bkv 6/1/2003 15:15'! parsePartsFor: aMailMessage ^ self collectPartsFrom: aMailMessage. ! ! !MailUtil class methodsFor: 'mail-message-utilities' stamp: 'bkv 6/2/2003 18:03'! streamContentsForTextParts: aListOfTextParts ^ String streamContents: [ :stream | aListOfTextParts do: [ :textPart | stream nextPutAll: textPart text. stream nextPut: Character cr ]]! ! !MailUtil class methodsFor: 'mail-message-utilities' stamp: 'bkv 5/28/2003 07:36'! textPartsFrom: aMailMessageAtomicParts ^ aMailMessageAtomicParts select: [ :mailMsg | mailMsg notNil and: [ (mailMsg body contentType = 'text/plain') or: [ mailMsg body contentType = 'text/html' ]]].! ! !MailUtil reorganize! ('as yet unclassified') ! !HttpEmailFileRepository reorganize! ('accessing' listing listingIds listingTitles mailMessageForId: postIds serverUrl serverUrl: titleForPostWithId:) ('downloads' appendListingBytes: downloadListing downloadListingDelta downloadZippedListing downloadZips: downloadZipsListForIds: httpGet: httpGet:withRange:) ('files' attachmentsDirectoryForId: attachmentsForId: createDirsForUrl: emailFileNames emailsDir extractAndSaveAttachmentsForId: fileExtension fileNames fullFileNameForId: listingFile listingFileExists listingFileName listingFromFile listingFromStream: localFileNameForId: repositoryDir zippedListingFile zippedListingFileExists zippedListingFileName) ('initialization' baseDirName onDirectory:) ('testing' byteSize size) ('updates' listingRows loadEverything loadUpdates rowsFromListingBytes: updateListing) ('urls' emailFileZipsListUrl emailFileZipsListUrlFor: listingUrl zippedListingUrl) ('zips' extractZippedListing fileNameForZipInterval: latestPostIds latestZip postIdsFromZip: startId zipArchiveForListing zipArchiveNamed: zipFileNames zipIntervalForId: zipIntervals zipMemberForId:) ! !BugFixArchive reorganize! ('accessing' archivePostGroups archivePosts name repository repository: serverUrl topics) ('enumerating' announcementPosts approvedArchivePostGroups archivePostsSortedByDate bugPosts closedArchivePostGroups enhancementPosts fixPosts goodiePosts openArchivePostGroups postsForMonth: postsForMonth:andYear: postsForYear: postsWithAuthorEmail: postsWithAuthorName: postsWithAuthorNameOrEmail: postsWithBodyMatching: postsWithTitleMatching: postsWithTitleOrBodyMatching: updateStreamArchivePostGroups) ('initialization' initialize) ('modifying' addArchivePost: removeArchivePost:) ('printing' printOn:) ('services' attachmentsDirectoryForId: attachmentsForId: byteSize fullFileNameForId: mailMessageForId: monthsRepresentedForYear: size sourceFileForId: titleForPostWithId: yearsRepresented) ('sorting' dateAscendingSortBlock dateDescendingSortBlock defaultSortBlock idAscendingSortBlock idDescendingSortBlock sortByDateAscending sortByDateDescending sortByIdAscending sortByIdDescending) ('testing' isUpdatable) ('updates' listChanged loadEverything loadUpdates) ('validation' validate) ! !ArchivePostGroup reorganize! ('initialization' initialize printTopicOn: topicContentMatchesPost:) ('modifying' addPost: firstPost: removePost: updateAggregatedPost) ('accessing' aggregatedPost aggregatedQaTags allPosts archive firstPost posts topic) ('printing' printGroupDisplayLabelOn: printOn: printRepliesTextOn: printString) ('misc. queries' authorEmailMatches: authorNameMatches: authorNameOrEmailMatches: hasReviews isBefore:andAfter: leastRecentDate leastRecentPost maxId mostRecentDate mostRecentPost numberOfReviews postDates size sizeMatches: titleMatches: titleOrBodyMatches: yearMatches:) ('queries about type' isAnnouncement isBug isBugAndFix isBugAndNotFix isBugOnly isEnhancement isFix isFixAndNotBug isGoodie) ('queries about status' hasNoStatus isMarkedAsApproved isMarkedAsClosed isMarkedAsUpdate) ! ArchivePost initialize! !ArchivePost class reorganize! ('accessing' canonicalQaFlags canonicalQaTags canonicalStatusFlags canonicalStatusTags canonicalTypeFlags canonicalTypeTags canonicalTypes flagForQaTag: flagForStatusTag: flagForTypeTag: tagForQaFlag: tagForStatusFlag: tagQaMap tagStatusMap tagTypeMap) ('parsing' findTokensInListingRow: indexesForUnEscapedListingRow: parseReviewStepsMaskFromTitle: unEscapeListingRow:) ('bit masks for review steps' maskForDocumented maskForReviewed maskForSLint maskForSUnitTests maskForSmall maskForTested) ('bit masks for status tags' maskForApproved maskForClosed maskForUpdate) ('bit masks for type tags' maskForAllTypesCombined maskForAnnouncement maskForBug maskForEnhancement maskForFix maskForGoodie) ('class initialization' initialize initializeTagQaMap initializeTagStatusMap initializeTagTypeMap) ('instance creation' archive:id: archive:listingRow:) ('type tags' tagForAnnouncement tagForBug tagForEnhancement tagForFix tagForGoodie) ('review step tags' tagForHasBeenDocumented tagForHasBeenReviewed tagForHasBeenTested tagForHasSUnitTests tagForIsSmall tagForPassesSLint) ('status flags' tagForHasBecomeAnUpdate tagForHasBeenApproved tagForHasBeenClosed) ('flags' symbolForHasBecomeAnUpdate symbolForHasBeenApproved symbolForHasBeenClosed symbolForHasBeenDocumented symbolForHasBeenReviewed symbolForHasBeenTested symbolForHasSUnitTests symbolForIsAnnouncement symbolForIsBug symbolForIsEnhancement symbolForIsFix symbolForIsGoodie symbolForIsSmall symbolForPassesSLint) ('utilities' harvestingTagSpecs parseTagsFromString:) ! !ArchivePost reorganize! ('accessing' archive archive: authorEmail authorEmail: authorName authorName: dateSent dateSent: displayLabel displayLabel: flags id id: qaFlags reviewStepsMask statusFlags statusMask statusMask: title title: topic typeFlags typeMask typeMask: updateStreamNumbers) ('comparing' = hash) ('files' attachments attachmentsDirectory hasAttachments) ('initialization' initialize) ('mail headers' authorEmailFromMailMessage authorNameFromMailMessage body comments dateSentFromMailMessage titleFromMailMessage) ('printing' describe:withBoldLabel:on: fullDescription groupDisplayLabel printGroupDisplayLabelOn: printOn: printString) ('queries' authorEmailMatches: authorNameMatches: authorNameOrEmailMatches: bodyMatches: byteSize isBefore:andAfter: monthMatches: monthMatches:andYearMatches: size titleMatches: titleOrBodyMatches: yearMatches:) ('review-step testing' isMarkedAsHasBeenDocumented isMarkedAsHasBeenReviewed isMarkedAsHasBeenTested isMarkedAsHasSUnitTests isMarkedAsPassesSLint isMarkedAsSmall) ('status testing' hasNoStatus isMarkedAsApproved isMarkedAsClosed isMarkedAsUpdate) ('services' addQaTag: asMailMessage rawAttachments removeQaTag:) ('title parsing' flagsFromTags: flagsFromTags:usingSelector: parseCommentsFromTitle parseFlagsFromTitle parseReviewStepsMaskFromTitle parseTagsFromTitle parseUpdateStreamNumbersFromTitle qaFlagsFromTags: statusFlagsFromTags: topicContentFrom: typeFlagsFromTags: updateStreamNumbersFromTags:) ('title tags' canonicalQaFlags canonicalQaTags canonicalStatusFlags canonicalStatusTags canonicalTypeFlags canonicalTypeTags canonicalTypes flagForQaTag: flagForStatusTag: qaTags statusTags tagForQaFlag: tagForType:) ('types testing' isAnnouncement isBug isBugAndFix isBugAndNotFix isBugOnly isCanonicalType isEnhancement isFix isFixAndNotBug isGoodie typeTags) !