From anadelonbrin at users.sourceforge.net Wed Dec 1 00:00:13 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 1 00:00:18 2004 Subject: [Spambayes-checkins] spambayes setup.py,1.30,1.31 Message-ID: Update of /cvsroot/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv29241 Modified Files: setup.py Log Message: Update PyPI details and an error message. For convenience, when doing an sdist, get the script to print out an MD5 checksum and the size of the created archive(s) for us. If only I could figure a way to get Inno to do this, too ;) Index: setup.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/setup.py,v retrieving revision 1.30 retrieving revision 1.31 diff -C2 -d -r1.30 -r1.31 *** setup.py 13 Apr 2004 13:24:11 -0000 1.30 --- setup.py 30 Nov 2004 23:00:11 -0000 1.31 *************** *** 15,19 **** if email.__version__ < '2.4.3': print "Error: email package version < 2.4.3 found - need newer version" ! print "See INTEGRATION.txt for download information for email package" sys.exit(0) --- 15,19 ---- if email.__version__ < '2.4.3': print "Error: email package version < 2.4.3 found - need newer version" ! print "See README.txt for download information for email package" sys.exit(0) *************** *** 74,77 **** --- 74,91 ---- return parent.run(self) + import distutils.command.sdist + parent = distutils.command.sdist.sdist + class sdist(parent): + """Like the standard sdist, but also prints out MD5 checksums and sizes + for the created files, for convenience.""" + def run(self): + import md5 + retval = parent.run(self) + for archive in self.get_archive_files(): + data = file(archive, "rb").read() + print '\n', archive, "\n\tMD5:", md5.md5(data).hexdigest() + print "\tLength:", len(data) + return retval + scripts=['scripts/sb_client.py', 'scripts/sb_dbexpimp.py', *************** *** 107,111 **** author_email = "spambayes@python.org", url = "http://spambayes.sourceforge.net", ! cmdclass = {'install_scripts': install_scripts}, scripts=scripts, packages = [ --- 121,127 ---- author_email = "spambayes@python.org", url = "http://spambayes.sourceforge.net", ! cmdclass = {'install_scripts': install_scripts, ! 'sdist': sdist, ! }, scripts=scripts, packages = [ *************** *** 114,119 **** ], classifiers = [ ! 'Development Status :: 4 - Beta', 'Environment :: Console', 'License :: OSI Approved :: Python Software Foundation License', 'Operating System :: POSIX', --- 130,137 ---- ], classifiers = [ ! 'Development Status :: 5 - Production/Stable', 'Environment :: Console', + 'Environment :: Plugins', + 'Environment :: Win32 (MS Windows)', 'License :: OSI Approved :: Python Software Foundation License', 'Operating System :: POSIX', *************** *** 121,128 **** --- 139,149 ---- 'Operating System :: Microsoft :: Windows :: Windows 95/98/2000', 'Operating System :: Microsoft :: Windows :: Windows NT/2000', + 'Natural Language :: English', 'Programming Language :: Python', + 'Programming Language :: C', 'Intended Audience :: End Users/Desktop', 'Topic :: Communications :: Email :: Filters', 'Topic :: Communications :: Email :: Post-Office :: POP3', + 'Topic :: Communications :: Email :: Post-Office :: IMAP', ], ) From anadelonbrin at users.sourceforge.net Wed Dec 1 00:49:40 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 1 00:49:44 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_imapfilter.py,1.44,1.45 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7418/scripts Modified Files: sb_imapfilter.py Log Message: There's a tricky situation where if use_ssl is False, but we try to connect to a IMAP over SSL server, we will just hang forever, waiting for a repsonse that will never come. To get past this, just for the welcome message, we install a timeout on the connection. Normal service is then returned. This only applies when we are not using SSL. Index: sb_imapfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_imapfilter.py,v retrieving revision 1.44 retrieving revision 1.45 diff -C2 -d -r1.44 -r1.45 *** sb_imapfilter.py 22 Nov 2004 00:02:28 -0000 1.44 --- sb_imapfilter.py 30 Nov 2004 23:49:23 -0000 1.45 *************** *** 127,138 **** --- 127,153 ---- '''A class extending the IMAP4 class, with a few optimizations''' + timeout = 60 # seconds def __init__(self, server, port, debug=0, do_expunge=False): + # There's a tricky situation where if use_ssl is False, but we + # try to connect to a IMAP over SSL server, we will just hang + # forever, waiting for a repsonse that will never come. To + # get past this, just for the welcome message, we install a + # timeout on the connection. Normal service is then returned. + # This only applies when we are not using SSL. + if not hasattr(self, "ssl"): + readline = self.readline + self.readline = self.readline_timeout try: BaseIMAP.__init__(self, server, port) except (BaseIMAP.error, socket.gaierror, socket.error): print "Cannot connect to server %s on port %s" % (server, port) + if not hasattr(self, "ssl"): + print "If you are connecting to an SSL server, please " \ + "ensure that you have the 'Use SSL' option enabled." self.connected = False else: self.connected = True + if not hasattr(self, "ssl"): + self.readline = readline self.debug = debug self.do_expunge = do_expunge *************** *** 145,148 **** --- 160,188 ---- self.current_folder = None + def readline_timeout(self): + """Read line from remote, possibly timing out.""" + st_time = time.time() + self.sock.setblocking(False) + buffer = [] + while True: + if (time.time() - st_time) > self.timeout: + if options["globals", "verbose"]: + print >> sys.stderr, "IMAP Timing out" + break + try: + data = self.sock.recv(1) + except socket.error, e: + if e[0] == 10035: + # Nothing to receive, keep going. + continue + raise + if not data: + break + if data == '\n': + break + buffer.append(data) + self.sock.setblocking(True) + return "".join(buffer) + def login(self, username, pwd): """Log in to the IMAP server, catching invalid username/password.""" From anadelonbrin at users.sourceforge.net Fri Dec 3 06:09:45 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Fri Dec 3 06:09:50 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_notesfilter.py,1.8,1.9 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv11624/scripts Modified Files: sb_notesfilter.py Log Message: Add improvements from Hugo Duncan: Add ability to log to a Notes log. Add ability to specify the name of the index file. Improve message headers. Add ability to specify password Save spam field Connect to database remotely, if possible Plus some misc cleanup. Index: sb_notesfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_notesfilter.py,v retrieving revision 1.8 retrieving revision 1.9 diff -C2 -d -r1.8 -r1.9 *** sb_notesfilter.py 3 May 2004 02:08:13 -0000 1.8 --- sb_notesfilter.py 3 Dec 2004 05:09:42 -0000 1.9 *************** *** 1,11 **** #! /usr/bin/env python ! '''sb_notesfilter.py - Lotus Notes Spambayes interface. ! ! Classes: ! ! Abstract: ! This module uses Spambayes as a filter against a Lotus Notes mail database. The Notes client must be running when this process is executed. --- 1,7 ---- #! /usr/bin/env python ! '''sb_notesfilter.py - Lotus Notes SpamBayes interface. ! This module uses SpamBayes as a filter against a Lotus Notes mail database. The Notes client must be running when this process is executed. *************** *** 45,49 **** folder for Ham that has mistakenly been classified as Spam. If there is any there, move it to the Train as Ham folder, so ! Spambayes will be less likely to make this mistake again. Mail that is classified as Ham or Unsure is left in the inbox. --- 41,45 ---- folder for Ham that has mistakenly been classified as Spam. If there is any there, move it to the Train as Ham folder, so ! SpamBayes will be less likely to make this mistake again. Mail that is classified as Ham or Unsure is left in the inbox. *************** *** 53,57 **** You should occasionally select some Ham and move it to the Train as Ham folder, so Spambayes can tell the difference between Spam ! and Ham. The goal is to maintain a relative balance between the number of Spam and the number of Ham that have been trained into the database. These numbers are reported every time this program --- 49,53 ---- You should occasionally select some Ham and move it to the Train as Ham folder, so Spambayes can tell the difference between Spam ! and Ham. The goal is to maintain an approximate balance between the number of Spam and the number of Ham that have been trained into the database. These numbers are reported every time this program *************** *** 59,67 **** the amount of Ham you receive, it may be very difficult to maintain this balance. This is not a matter of great concern. ! Spambayes will still make very few mistakes in this circumstance. But, if this is the case, you should review your Spam folder for falsely classified Ham, and retrain those that you find, on a regular basis. This will prevent statistical error accumulation, ! which if allowed to continue, would cause Spambayes to tend to classify everything as Spam. --- 55,63 ---- the amount of Ham you receive, it may be very difficult to maintain this balance. This is not a matter of great concern. ! SpamBayes will still make very few mistakes in this circumstance. But, if this is the case, you should review your Spam folder for falsely classified Ham, and retrain those that you find, on a regular basis. This will prevent statistical error accumulation, ! which if allowed to continue, would cause SpamBayes to tend to classify everything as Spam. *************** *** 70,74 **** it keeps a pickled dictionary of notes mail ids, so that once a mail has been classified, it will not be classified again. The ! non-existence of is index file, named .sbindex, indicates to the system that this is an initialization execution. Rather than classify the inbox in this case, the contents of the --- 66,70 ---- it keeps a pickled dictionary of notes mail ids, so that once a mail has been classified, it will not be classified again. The ! non-existence of this index file, named .sbindex, indicates to the system that this is an initialization execution. Rather than classify the inbox in this case, the contents of the *************** *** 91,95 **** e.g. d27ml602/27/M/IBM if specified, will initiate a replication ! -f folder : Name of spambayes folder must have subfolders: Spam Ham --- 87,91 ---- e.g. d27ml602/27/M/IBM if specified, will initiate a replication ! -f folder : Name of SpamBayes folder must have subfolders: Spam Ham *************** *** 103,106 **** --- 99,105 ---- statistics output would otherwise be lost when the window closes. + -i filename : index file name + -W : password + -L dbname : log to database (template alog4.ntf) -o section:option:value : set [section, option] in the options database *************** *** 124,131 **** o sb_server style training/configuration interface? o parameter to retrain? o Suggestions? ''' ! # This module is part of the spambayes project, which is Copyright 2002 # The Python Software Foundation and is covered by the Python Software # Foundation license. --- 123,131 ---- o sb_server style training/configuration interface? o parameter to retrain? + o Use spambayes.message MessageInfo db's rather than own database. o Suggestions? ''' ! # This module is part of the spambayes project, which is Copyright 2002-5 # The Python Software Foundation and is covered by the Python Software # Foundation license. *************** *** 154,158 **** ! def classifyInbox(v, vmoveto, bayes, ldbname, notesindex): # the notesindex hash ensures that a message is looked at only once --- 154,158 ---- ! def classifyInbox(v, vmoveto, bayes, ldbname, notesindex, log): # the notesindex hash ensures that a message is looked at only once *************** *** 176,200 **** else: if not notesindex.has_key(nid): - numdocs += 1 # Notes returns strings in unicode, and the Python ! # uni-decoder has trouble with these strings when # you try to print them. So don't... ! # The com interface returns basic data types as tuples ! # only, thus the subscript on GetItemValue ! ! try: ! subj = doc.GetItemValue('Subject')[0] ! except: ! subj = 'No Subject' ! ! try: ! body = doc.GetItemValue('Body')[0] ! except: ! body = 'No Body' ! ! message = "Subject: %s\r\n\r\n%s" % (subj, body) # generate_long_skips = True blows up on occasion, --- 176,186 ---- else: if not notesindex.has_key(nid): numdocs += 1 # Notes returns strings in unicode, and the Python ! # decoder has trouble with these strings when # you try to print them. So don't... ! message = getMessage(doc) # generate_long_skips = True blows up on occasion, *************** *** 218,261 **** try: print "%s spamprob is %s" % (subj[:30], prob) except UnicodeError: print " spamprob is %s" % (prob) doc = v.GetNextDocument(doc) # docstomove list is built because moving documents in the middle of ! # the classification loop looses the iterator position for doc in docstomove: doc.RemoveFromFolder(v.Name) doc.PutInFolder(vmoveto.Name) ! print "%s documents processed" % (numdocs) ! print " %s classified as spam" % (numspam) ! print " %s classified as ham" % (numham) ! print " %s classified as unsure" % (numuns) ! def processAndTrain(v, vmoveto, bayes, is_spam, notesindex): if is_spam: ! str = options["Headers", "header_spam_string"] else: ! str = options["Headers", "header_ham_string"] ! print "Training %s" % (str) docstomove = [] doc = v.GetFirstDocument() while doc: ! try: ! subj = doc.GetItemValue('Subject')[0] ! except: ! subj = 'No Subject' ! ! try: ! body = doc.GetItemValue('Body')[0] ! except: ! body = 'No Body' ! ! message = "Subject: %s\r\n%s" % (subj, body) options["Tokenizer", "generate_long_skips"] = False --- 204,273 ---- try: print "%s spamprob is %s" % (subj[:30], prob) + if log: + log.LogAction("%s spamprob is %s" % (subj[:30], + prob)) except UnicodeError: print " spamprob is %s" % (prob) + if log: + log.LogAction(" spamprob " \ + "is %s" % (prob,)) + + item = doc.ReplaceItemValue("Spam", prob) + item.IsSummary = True + doc.save(False, True, False) doc = v.GetNextDocument(doc) # docstomove list is built because moving documents in the middle of ! # the classification loop loses the iterator position for doc in docstomove: doc.RemoveFromFolder(v.Name) doc.PutInFolder(vmoveto.Name) ! print "%s documents processed" % (numdocs,) ! print " %s classified as spam" % (numspam,) ! print " %s classified as ham" % (numham,) ! print " %s classified as unsure" % (numuns,) ! if log: ! log.LogAction("%s documents processed" % (numdocs,)) ! log.LogAction(" %s classified as spam" % (numspam,)) ! log.LogAction(" %s classified as ham" % (numham,)) ! log.LogAction(" %s classified as unsure" % (numuns,)) + def getMessage(doc): + try: + subj = doc.GetItemValue('Subject')[0] + except: + subj = 'No Subject' ! try: ! body = doc.GetItemValue('Body')[0] ! except: ! body = 'No Body' ! ! hdrs = '' ! for item in doc.Items: ! if item.Name == "From" or item.Name == "Sender" or \ ! item.Name == "Received" or item.Name == "ReplyTo": ! try: ! hdrs = hdrs + ( "%s: %s\r\n" % (item.Name, item.Text) ) ! except: ! hdrs = '' ! ! message = "%sSubject: %s\r\n\r\n%s" % (hdrs, subj, body) ! return message + def processAndTrain(v, vmoveto, bayes, is_spam, notesindex, log): if is_spam: ! header_str = options["Headers", "header_spam_string"] else: ! header_str = options["Headers", "header_ham_string"] ! print "Training %s" % (header_str,) docstomove = [] doc = v.GetFirstDocument() while doc: ! message = getMessage(doc) options["Tokenizer", "generate_long_skips"] = False *************** *** 276,280 **** bayes.learn(tokens, is_spam) ! notesindex[nid] = str docstomove += [doc] doc = v.GetNextDocument(doc) --- 288,292 ---- bayes.learn(tokens, is_spam) ! notesindex[nid] = header_str docstomove += [doc] doc = v.GetNextDocument(doc) *************** *** 284,300 **** doc.PutInFolder(vmoveto.Name) ! print "%s documents trained" % (len(docstomove)) ! def run(bdbname, useDBM, ldbname, rdbname, foldname, doTrain, doClassify): bayes = storage.open_storage(bdbname, useDBM) try: ! fp = open("%s.sbindex" % (ldbname), 'rb') except IOError, e: ! if e.errno != errno.ENOENT: raise notesindex = {} ! print "%s.sbindex file not found, this is a first time run" \ ! % (ldbname) print "No classification will be performed" else: --- 296,315 ---- doc.PutInFolder(vmoveto.Name) ! print "%s documents trained" % (len(docstomove),) ! if log: ! log.LogAction("%s documents trained" % (len(docstomove),)) ! def run(bdbname, useDBM, ldbname, rdbname, foldname, doTrain, doClassify, ! pwd, idxname, logname): bayes = storage.open_storage(bdbname, useDBM) try: ! fp = open(idxname, 'rb') except IOError, e: ! if e.errno != errno.ENOENT: ! raise notesindex = {} ! print "%s file not found, this is a first time run" % (idxname,) print "No classification will be performed" else: *************** *** 302,347 **** fp.close() sess = win32com.client.Dispatch("Lotus.NotesSession") try: ! sess.initialize() except pywintypes.com_error: print "Session aborted" sys.exit() ! db = sess.GetDatabase("",ldbname) vinbox = db.getView('($Inbox)') ! vspam = db.getView("%s\Spam" % (foldname)) ! vham = db.getView("%s\Ham" % (foldname)) ! vtrainspam = db.getView("%s\Train as Spam" % (foldname)) ! vtrainham = db.getView("%s\Train as Ham" % (foldname)) if doTrain: ! processAndTrain(vtrainspam, vspam, bayes, True, notesindex) # for some reason, using inbox as a target here loses the mail ! processAndTrain(vtrainham, vham, bayes, False, notesindex) ! if rdbname: ! print "Replicating..." ! db.Replicate(rdbname) ! print "Done" if doClassify: ! classifyInbox(vinbox, vtrainspam, bayes, ldbname, notesindex) print "The Spambayes database currently has %s Spam and %s Ham" \ ! % (bayes.nspam, bayes.nham) bayes.store() ! fp = open("%s.sbindex" % (ldbname), 'wb') pickle.dump(notesindex, fp) fp.close() - if __name__ == '__main__': try: ! opts, args = getopt.getopt(sys.argv[1:], 'htcPd:p:l:r:f:o:') except getopt.error, msg: print >>sys.stderr, str(msg) + '\n\n' + __doc__ --- 317,393 ---- fp.close() + need_replicate = False + sess = win32com.client.Dispatch("Lotus.NotesSession") try: ! if pwd: ! sess.initialize(pwd) ! else: ! sess.initialize() except pywintypes.com_error: print "Session aborted" sys.exit() + try: + db = sess.GetDatabase(rdbname, ldbname) + except pywintypes.com_error: + if rdbname: + print "Could not open database remotely, trying locally" + try: + db = sess.GetDatabase("", ldbname) + need_replicate = True + except pywintypes.com_error: + print "Could not open database" + sys.exit() + else: + raise ! log = sess.CreateLog("SpambayesAgentLog") ! try: ! log.OpenNotesLog("", logname) ! except pywintypes.com_error: ! print "Could not open log" ! log = None ! ! if log: ! log.LogAction("Running spambayes") vinbox = db.getView('($Inbox)') ! vspam = db.getView("%s\Spam" % (foldname,)) ! vham = db.getView("%s\Ham" % (foldname,)) ! vtrainspam = db.getView("%s\Train as Spam" % (foldname,)) ! vtrainham = db.getView("%s\Train as Ham" % (foldname,)) if doTrain: ! processAndTrain(vtrainspam, vspam, bayes, True, notesindex, log) # for some reason, using inbox as a target here loses the mail ! processAndTrain(vtrainham, vham, bayes, False, notesindex, log) ! if need_replicate: ! try: ! print "Replicating..." ! db.Replicate(rdbname) ! print "Done" ! except pywintypes.com_error: ! print "Could not replicate" if doClassify: ! classifyInbox(vinbox, vtrainspam, bayes, ldbname, notesindex, log) print "The Spambayes database currently has %s Spam and %s Ham" \ ! % (bayes.nspam, bayes.nham) bayes.store() ! fp = open(idxname), 'wb') pickle.dump(notesindex, fp) fp.close() + if log: + log.LogAction("Finished running spambayes") + if __name__ == '__main__': try: ! opts, args = getopt.getopt(sys.argv[1:], 'htcPd:p:l:r:f:o:i:W:L:') except getopt.error, msg: print >>sys.stderr, str(msg) + '\n\n' + __doc__ *************** *** 351,354 **** --- 397,403 ---- rdbname = None # remote notes database location sbfname = None # spambayes folder name + idxname = None # index file name + logname = None # log database name + pwd = None # password doTrain = False doClassify = False *************** *** 371,381 **** elif opt == '-P': doPrompt = True elif opt == '-o': options.set_from_cmdline(arg, sys.stderr) bdbname, useDBM = storage.database_type(opts) if (bdbname and ldbname and sbfname and (doTrain or doClassify)): run(bdbname, useDBM, ldbname, rdbname, \ ! sbfname, doTrain, doClassify) if doPrompt: --- 420,439 ---- elif opt == '-P': doPrompt = True + elif opt == '-i': + idxname = arg + elif opt == '-L': + logname = arg + elif opt == '-W': + pwd = arg elif opt == '-o': options.set_from_cmdline(arg, sys.stderr) bdbname, useDBM = storage.database_type(opts) + if not idxname: + idxname = "%s.sbindex" % (ldbname) + if (bdbname and ldbname and sbfname and (doTrain or doClassify)): run(bdbname, useDBM, ldbname, rdbname, \ ! sbfname, doTrain, doClassify, pwd, idxname, logname) if doPrompt: From kpitt at users.sourceforge.net Fri Dec 3 22:43:23 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Fri Dec 3 22:43:26 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py, 1.140, 1.141 config.py, 1.33, 1.34 manager.py, 1.99, 1.100 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4934/Outlook2000 Modified Files: addin.py config.py manager.py Log Message: Add notification sound support as per patch #858925. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.140 retrieving revision 1.141 diff -C2 -d -r1.140 -r1.141 *** addin.py 26 Nov 2004 03:11:43 -0000 1.140 --- addin.py 3 Dec 2004 21:43:19 -0000 1.141 *************** *** 223,226 **** --- 223,228 ---- folder_name, disposition) + + manager.HandleNotification(disposition) else: print "Spam filtering is disabled - ignoring new message" Index: config.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/config.py,v retrieving revision 1.33 retrieving revision 1.34 diff -C2 -d -r1.33 -r1.34 *** config.py 2 Nov 2004 21:37:37 -0000 1.33 --- config.py 3 Dec 2004 21:43:19 -0000 1.34 *************** *** 28,32 **** from spambayes.OptionsClass import OptionsClass, Option from spambayes.OptionsClass import RESTORE, DO_NOT_RESTORE ! from spambayes.OptionsClass import BOOLEAN, INTEGER, REAL, PATH class FolderIDOption(Option): --- 28,32 ---- from spambayes.OptionsClass import OptionsClass, Option from spambayes.OptionsClass import RESTORE, DO_NOT_RESTORE ! from spambayes.OptionsClass import BOOLEAN, INTEGER, REAL, PATH, FILE_WITH_PATH class FolderIDOption(Option): *************** *** 269,272 **** --- 269,309 ---- BOOLEAN, RESTORE), ), + + # These options control how the user is notified of new messages. + "Notification": ( + ("notify_sound_enabled", _("Play a notification sound when new messages arrive?"), False, + _("""If enabled, SpamBayes will play a notification sound after a + batch of new messages is processed. A different sound can be + assigned to each of the three classifications of messages. The + good sound will be played if any good messages are received. The + possible spam sound will be played if unsure messages are received, + but no good messages. The spam sound will be played if all + received messages are spam."""), + BOOLEAN, RESTORE), + ("notify_ham_sound", _("Sound file to play for good messages"), "", + _("""Specifies the full path to a Windows sound file (WAV format) that + will be played as notification that a good message has been received."""), + FILE_WITH_PATH, DO_NOT_RESTORE), + ("notify_unsure_sound", _("Sound file to play for possible spam messages"), "", + _("""Specifies the full path to a Windows sound file (WAV format) that + will be played as notification that a possible spam message has been + received. The possible spam notification sound will only be played + if no good messages have been received."""), + FILE_WITH_PATH, DO_NOT_RESTORE), + ("notify_spam_sound", _("Sound file to play for spam messages"), "", + _("""Specifies the full path to a Windows sound file (WAV format) that + will be played as notification that a spam message has been + received. The spam notification sound will only be played if no + good or possible spam messages have been received."""), + FILE_WITH_PATH, DO_NOT_RESTORE), + ("notify_accumulate_delay", _("The delay time to wait for additional received messages (in seconds)"), 10.0, + _("""When SpamBayes classifies a new message, it sets a timer to wait + for additional new messages. If another new message is received + before the timer expires then the delay time is reset and SpamBayes + continues to wait. If no new messages arrive within the delay time + the SpamBayes will play the appropriate notification sound for the + received messages."""), + REAL, RESTORE), + ), } Index: manager.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/manager.py,v retrieving revision 1.99 retrieving revision 1.100 diff -C2 -d -r1.99 -r1.100 *** manager.py 25 Nov 2004 23:26:58 -0000 1.99 --- manager.py 3 Dec 2004 21:43:19 -0000 1.100 *************** *** 10,13 **** --- 10,15 ---- import win32api, win32con, win32gui + import timer, thread + import win32com.client import win32com.client.gencache *************** *** 331,334 **** --- 333,337 ---- class BayesManager: def __init__(self, config_base="default", outlook=None, verbose=0): + self.owner_thread_ident = thread.get_ident() # check we aren't multi-threaded self.never_configured = True self.reported_error_map = {} *************** *** 340,343 **** --- 343,348 ---- self.dialog_parser = None self.test_suite_running = False + self.received_ham = self.received_unsure = self.received_spam = 0 + self.notify_timer_id = None import_early_core_spambayes_stuff() *************** *** 786,789 **** --- 791,795 ---- def Close(self): global _mgr + self._KillNotifyTimer() self.classifier_data.Close() self.config = self.options = None *************** *** 909,912 **** --- 915,976 ---- SetWaitCursor(0) + def HandleNotification(self, disposition): + if self.config.notification.notify_sound_enabled: + if disposition == "Yes": + self.received_spam += 1 + elif disposition == "No": + self.received_ham += 1 + else: + self.received_unsure += 1 + self._StartNotifyTimer() + + def _StartNotifyTimer(self): + # First kill any existing timer + self._KillNotifyTimer() + # And start a new timer. + delay = self.config.notification.notify_accumulate_delay + self._DoStartNotifyTimer(delay) + pass + + def _DoStartNotifyTimer(self, delay): + assert thread.get_ident() == self.owner_thread_ident + assert self.notify_timer_id is None, "Shouldn't start a timer when already have one" + assert type(delay)==type(0.0), "Timer values are float seconds" + # And start a new timer. + assert delay, "No delay means no timer!" + delay = int(delay*1000) # convert to ms. + self.notify_timer_id = timer.set_timer(delay, self._NotifyTimerFunc) + self.LogDebug(1, "Notify timer started - id=%d, delay=%d" % (self.notify_timer_id, delay)) + + def _KillNotifyTimer(self): + assert thread.get_ident() == self.owner_thread_ident + if self.notify_timer_id is not None: + timer.kill_timer(self.notify_timer_id) + self.LogDebug(2, "The notify timer with id=%d was stopped" % self.notify_timer_id) + self.notify_timer_id = None + + def _NotifyTimerFunc(self, event, time): + # Kill the timer first + assert thread.get_ident() == self.owner_thread_ident + self.LogDebug(1, "The notify timer with id=%s fired" % self.notify_timer_id) + self._KillNotifyTimer() + + import winsound + config = self.config.notification + sound_opts = winsound.SND_FILENAME | winsound.SND_ASYNC | winsound.SND_NOSTOP | winsound.SND_NODEFAULT + self.LogDebug(3, "Notify received ham=%d, unsure=%d, spam=%d" % + (self.received_ham, self.received_unsure, self.received_spam)) + if self.received_ham > 0 and len(config.notify_ham_sound) > 0: + self.LogDebug(3, "Playing ham sound '%s'" % config.notify_ham_sound) + winsound.PlaySound(config.notify_ham_sound, sound_opts) + elif self.received_unsure > 0 and len(config.notify_unsure_sound) > 0: + self.LogDebug(3, "Playing unsure sound '%s'" % config.notify_unsure_sound) + winsound.PlaySound(config.notify_unsure_sound, sound_opts) + elif self.received_spam > 0 and len(config.notify_spam_sound) > 0: + self.LogDebug(3, "Playing spam sound '%s'" % config.notify_spam_sound) + winsound.PlaySound(config.notify_spam_sound, sound_opts) + # Reset received counts to zero after notify. + self.received_ham = self.received_unsure = self.received_spam = 0 + _mgr = None From anadelonbrin at users.sourceforge.net Mon Dec 6 00:28:53 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 6 00:28:55 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs dialog_map.py, 1.41, 1.42 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7228/Outlook2000/dialogs Modified Files: dialog_map.py Log Message: Fix [ 1078923 ] Unicode support incomplete (Need to encode the data_directory when asking the OS to show it). Index: dialog_map.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/dialog_map.py,v retrieving revision 1.41 retrieving revision 1.42 diff -C2 -d -r1.41 -r1.42 *** dialog_map.py 11 Nov 2004 21:55:40 -0000 1.41 --- dialog_map.py 5 Dec 2004 23:28:50 -0000 1.42 *************** *** 236,240 **** """ import os ! os.startfile(window.manager.data_directory) def ShowLog(window): """Opens the log file for the current SpamBayes session --- 236,242 ---- """ import os ! import sys ! filesystem_encoding = sys.getfilesystemencoding() ! os.startfile(window.manager.data_directory.encode(filesystem_encoding)) def ShowLog(window): """Opens the log file for the current SpamBayes session From anadelonbrin at users.sourceforge.net Mon Dec 6 01:11:35 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 6 01:11:37 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 msgstore.py,1.90,1.91 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv17895/Outlook2000 Modified Files: msgstore.py Log Message: I'm not sure that PR_DISPLAY_NAME_A is the right property to find the "From" name in. At least, I don't have any messages with that property, and I get a lot of Exchange-only mail. So if that property isn't found, use PR_SENDER_NAME_A if that can be found (which works for me). This will help those that need additional clues with Exchange-only messages. Index: msgstore.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v retrieving revision 1.90 retrieving revision 1.91 diff -C2 -d -r1.90 -r1.91 *** msgstore.py 26 Nov 2004 03:11:43 -0000 1.90 --- msgstore.py 6 Dec 2004 00:11:31 -0000 1.91 *************** *** 986,990 **** # on an exchange server that do not have such headers of their own prop_ids = PR_SUBJECT_A, PR_DISPLAY_NAME_A, PR_DISPLAY_TO_A, \ ! PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME hr, data = self.mapi_object.GetProps(prop_ids,0) subject = self._GetPotentiallyLargeStringProp(prop_ids[0], data[0]) --- 986,991 ---- # on an exchange server that do not have such headers of their own prop_ids = PR_SUBJECT_A, PR_DISPLAY_NAME_A, PR_DISPLAY_TO_A, \ ! PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME, \ ! PR_SENDER_NAME_A hr, data = self.mapi_object.GetProps(prop_ids,0) subject = self._GetPotentiallyLargeStringProp(prop_ids[0], data[0]) *************** *** 993,1001 **** cc = self._GetPotentiallyLargeStringProp(prop_ids[3], data[3]) delivery_time = data[4][1] headers = ["X-Exchange-Message: true"] ! if subject: headers.append("Subject: "+subject) ! if sender: headers.append("From: "+sender) ! if to: headers.append("To: "+to) ! if cc: headers.append("CC: "+cc) if delivery_time: from time import timezone --- 994,1010 ---- cc = self._GetPotentiallyLargeStringProp(prop_ids[3], data[3]) delivery_time = data[4][1] + alt_sender = self._GetPotentiallyLargeStringProp(prop_ids[5], + data[5]) headers = ["X-Exchange-Message: true"] ! if subject: ! headers.append("Subject: "+subject) ! if sender: ! headers.append("From: "+sender) ! elif alt_sender: ! headers.append("From: "+alt_sender) ! if to: ! headers.append("To: "+to) ! if cc: ! headers.append("CC: "+cc) if delivery_time: from time import timezone From anadelonbrin at users.sourceforge.net Mon Dec 6 02:37:51 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 6 02:37:58 2004 Subject: [Spambayes-checkins] spambayes/spambayes Dibbler.py,1.14,1.15 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv3159/spambayes Modified Files: Dibbler.py Log Message: Fix the regex that the auth digest used (+ was outside the group, so only the first letter of the key would be used). IE 6.0 and Firefox 1.0 appear to give back improper auth responses, based on my limited understanding of the RFC, so make allowances for that. Auth digest works for me now, at least. (If anyone would like to check this, that would be great). This needs to be backported, too (I'll do that later). Index: Dibbler.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Dibbler.py,v retrieving revision 1.14 retrieving revision 1.15 diff -C2 -d -r1.14 -r1.15 *** Dibbler.py 19 Jul 2004 02:10:12 -0000 1.14 --- Dibbler.py 6 Dec 2004 01:37:43 -0000 1.15 *************** *** 347,351 **** # RE to extract option="value" fields from # digest auth login field ! _login_splitter = re.compile('([a-zA-Z])+=(".*?"|.*?),?') def __init__(self, clientSocket, server, context): --- 347,351 ---- # RE to extract option="value" fields from # digest auth login field ! _login_splitter = re.compile('([a-zA-Z]+)=(".*?"|.*?),?') def __init__(self, clientSocket, server, context): *************** *** 631,634 **** --- 631,640 ---- unhashedDigest = "" if options.has_key("qop"): + # IE 6.0 doesn't give nc back correctly? + if not options["nc"]: + options["nc"] = "00000001" + # Firefox 1.0 doesn't give qop back correctly? + if not options["qop"]: + options["qop"] = "auth" unhashedDigest = "%s:%s:%s:%s:%s:%s" % \ (HA1, nonce, From anadelonbrin at users.sourceforge.net Mon Dec 6 04:04:19 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 6 04:04:22 2004 Subject: [Spambayes-checkins] spambayes setup.py,1.31,1.32 Message-ID: Update of /cvsroot/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv21289 Modified Files: setup.py Log Message: Fix bug found by Barry. Don't re-use global variable names! Index: setup.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/setup.py,v retrieving revision 1.31 retrieving revision 1.32 diff -C2 -d -r1.31 -r1.32 *** setup.py 30 Nov 2004 23:00:11 -0000 1.31 --- setup.py 6 Dec 2004 03:04:17 -0000 1.32 *************** *** 75,85 **** import distutils.command.sdist ! parent = distutils.command.sdist.sdist ! class sdist(parent): """Like the standard sdist, but also prints out MD5 checksums and sizes for the created files, for convenience.""" def run(self): import md5 ! retval = parent.run(self) for archive in self.get_archive_files(): data = file(archive, "rb").read() --- 75,85 ---- import distutils.command.sdist ! sdist_parent = distutils.command.sdist.sdist ! class sdist(sdist_parent): """Like the standard sdist, but also prints out MD5 checksums and sizes for the created files, for convenience.""" def run(self): import md5 ! retval = sdist_parent.run(self) for archive in self.get_archive_files(): data = file(archive, "rb").read() From kpitt at users.sourceforge.net Mon Dec 6 18:50:07 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Mon Dec 6 18:50:12 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 oastats.py,1.8,1.9 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv8330 Modified Files: oastats.py Log Message: More detailed statistics in SpamBayes Manager. These roughly match the updated statistics in the sb_server Web UI. Index: oastats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/oastats.py,v retrieving revision 1.8 retrieving revision 1.9 diff -C2 -d -r1.8 -r1.9 *** oastats.py 2 Nov 2004 21:33:46 -0000 1.8 --- oastats.py 6 Dec 2004 17:50:04 -0000 1.9 *************** *** 85,88 **** --- 85,90 ---- points. """ + chunks = [] + push = chunks.append num_seen = self.num_ham + self.num_spam + self.num_unsure if not session_only: *************** *** 90,97 **** num_seen += (totals["num_ham"] + totals["num_spam"] + totals["num_unsure"]) if num_seen==0: ! return [_("SpamBayes has processed zero messages")] ! chunks = [] ! push = chunks.append if session_only: num_ham = self.num_ham --- 92,98 ---- num_seen += (totals["num_ham"] + totals["num_spam"] + totals["num_unsure"]) + push(_("Messages classified: %d") % num_seen); if num_seen==0: ! return chunks if session_only: num_ham = self.num_ham *************** *** 117,120 **** --- 118,160 ---- perc_spam = 100.0 * num_spam / num_seen perc_unsure = 100.0 * num_unsure / num_seen + num_ham_correct = num_ham - num_deleted_spam_fn + num_spam_correct = num_spam - num_recovered_good_fp + num_correct = num_ham_correct + num_spam_correct + num_incorrect = num_deleted_spam_fn + num_recovered_good_fp + perc_correct = 100.0 * num_correct / num_seen + perc_incorrect = 100.0 * num_incorrect / num_seen + perc_fp = 100.0 * num_recovered_good_fp / num_seen + perc_fn = 100.0 * num_deleted_spam_fn / num_seen + num_unsure_trained_ham = num_recovered_good - num_recovered_good_fp + num_unsure_trained_spam = num_deleted_spam - num_deleted_spam_fn + num_unsure_not_trained = num_unsure - num_unsure_trained_ham - num_unsure_trained_spam + if num_unsure: + perc_unsure_trained_ham = 100.0 * num_unsure_trained_ham / num_unsure + perc_unsure_trained_spam = 100.0 * num_unsure_trained_spam / num_unsure + perc_unsure_not_trained = 100.0 * num_unsure_not_trained / num_unsure + else: + perc_unsure_trained_ham = 0.0 + perc_unsure_trained_spam = 0.0 + perc_unsure_not_trained = 0.0 + total_ham = num_ham_correct + num_recovered_good + total_spam = num_spam_correct + num_deleted_spam + if total_ham: + perc_ham_incorrect = 100.0 * num_recovered_good_fp / total_ham + perc_ham_unsure = 100.0 * num_unsure_trained_ham / total_ham + perc_ham_incorrect_or_unsure = \ + 100.0 * (num_recovered_good_fp + num_unsure_trained_ham) / total_ham + else: + perc_ham_incorrect = 0.0 + perc_ham_unsure = 0.0 + perc_ham_incorrect_or_unsure = 0.0 + if total_spam: + perc_spam_correct = 100.0 * num_spam_correct / total_spam + perc_spam_unsure = 100.0 * num_unsure_trained_spam / total_spam + perc_spam_correct_or_unsure = \ + 100.0 * (num_spam_correct + num_unsure_trained_spam) / total_spam + else: + perc_spam_correct = 100.0 + perc_spam_unsure = 0.0 + perc_spam_incorrect_or_unsure = 100.0 format_dict = locals().copy() del format_dict["self"] *************** *** 122,126 **** del format_dict["chunks"] format_dict.update(dict(perc_spam=perc_spam, perc_ham=perc_ham, ! perc_unsure=perc_unsure, num_seen=num_seen)) format_dict["perc_ham_s"] = "%%(perc_ham).%df%%(perc)s" \ % (decimal_points,) --- 162,181 ---- del format_dict["chunks"] format_dict.update(dict(perc_spam=perc_spam, perc_ham=perc_ham, ! perc_unsure=perc_unsure, num_seen=num_seen, ! num_correct=num_correct, num_incorrect=num_incorrect, ! perc_correct=perc_correct, perc_incorrect=perc_incorrect, ! perc_fp=perc_fp, perc_fn=perc_fn, ! num_unsure_trained_ham=num_unsure_trained_ham, ! num_unsure_trained_spam=num_unsure_trained_spam, ! num_unsure_not_trained=num_unsure_not_trained, ! perc_unsure_trained_ham=perc_unsure_trained_ham, ! perc_unsure_trained_spam=perc_unsure_trained_spam, ! perc_unsure_not_trained=perc_unsure_not_trained, ! perc_ham_incorrect=perc_ham_incorrect, ! perc_ham_unsure=perc_ham_unsure, ! perc_ham_incorrect_or_unsure=perc_ham_incorrect_or_unsure, ! perc_spam_correct=perc_spam_correct, ! perc_spam_unsure=perc_spam_unsure, ! perc_spam_correct_or_unsure=perc_spam_correct_or_unsure)) format_dict["perc_ham_s"] = "%%(perc_ham).%df%%(perc)s" \ % (decimal_points,) *************** *** 129,151 **** format_dict["perc_unsure_s"] = "%%(perc_unsure).%df%%(perc)s" \ % (decimal_points,) format_dict["perc"] = "%" ! push((_("SpamBayes has processed %(num_seen)d messages - " \ ! "%(num_ham)d (%(perc_ham_s)s) good, " \ ! "%(num_spam)d (%(perc_spam_s)s) spam " \ ! "and %(num_unsure)d (%(perc_unsure_s)s) unsure") \ % format_dict) % format_dict) - if num_recovered_good: - push(_("%(num_recovered_good)d message(s) were manually " \ - "classified as good (with %(num_recovered_good_fp)d " \ - "being false positives)") % format_dict) - else: - push(_("No messages were manually classified as good")) - if num_deleted_spam: - push(_("%(num_deleted_spam)d message(s) were manually " \ - "classified as spam (with %(num_deleted_spam_fn)d " \ - "being false negatives)") % format_dict) - else: - push(_("No messages were manually classified as spam")) return chunks --- 184,252 ---- format_dict["perc_unsure_s"] = "%%(perc_unsure).%df%%(perc)s" \ % (decimal_points,) + format_dict["perc_correct_s"] = "%%(perc_correct).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_incorrect_s"] = "%%(perc_incorrect).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_fp_s"] = "%%(perc_fp).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_fn_s"] = "%%(perc_fn).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_spam_correct_s"] = "%%(perc_spam_correct).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_spam_unsure_s"] = "%%(perc_spam_unsure).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_spam_correct_or_unsure_s"] = "%%(perc_spam_correct_or_unsure).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_ham_incorrect_s"] = "%%(perc_ham_incorrect).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_ham_unsure_s"] = "%%(perc_ham_unsure).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_ham_incorrect_or_unsure_s"] = "%%(perc_ham_incorrect_or_unsure).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_unsure_trained_ham_s"] = "%%(perc_unsure_trained_ham).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_unsure_trained_spam_s"] = "%%(perc_unsure_trained_spam).%df%%(perc)s" \ + % (decimal_points,) + format_dict["perc_unsure_not_trained_s"] = "%%(perc_unsure_not_trained).%df%%(perc)s" \ + % (decimal_points,) format_dict["perc"] = "%" ! ! push((_("\tGood:\t%(num_ham)d (%(perc_ham_s)s)") \ ! % format_dict) % format_dict) ! push((_("\tSpam:\t%(num_spam)d (%(perc_spam_s)s)") \ ! % format_dict) % format_dict) ! push((_("\tUnsure:\t%(num_unsure)d (%(perc_unsure_s)s)") \ ! % format_dict) % format_dict) ! push("") ! ! push((_("Classified correctly:\t%(num_correct)d (%(perc_correct_s)s of total)") \ ! % format_dict) % format_dict) ! push((_("Classified incorrectly:\t%(num_incorrect)d (%(perc_incorrect_s)s of total)") \ ! % format_dict) % format_dict) ! if num_incorrect: ! push((_("\tFalse positives:\t%(num_recovered_good_fp)d (%(perc_fp_s)s of total)") \ ! % format_dict) % format_dict) ! push((_("\tFalse negatives:\t%(num_deleted_spam_fn)d (%(perc_fn_s)s of total)") \ ! % format_dict) % format_dict) ! push("") ! ! push(_("Manually classified as good:\t%(num_recovered_good)d") % format_dict) ! push(_("Manually classified as spam:\t%(num_deleted_spam)d") % format_dict) ! push("") ! ! if num_unsure: ! push((_("Unsures trained as good:\t%(num_unsure_trained_ham)d (%(perc_unsure_trained_ham_s)s of unsures)") \ ! % format_dict) % format_dict) ! push((_("Unsures trained as spam:\t%(num_unsure_trained_spam)d (%(perc_unsure_trained_spam_s)s of unsures)") \ ! % format_dict) % format_dict) ! push((_("Unsures not trained:\t\t%(num_unsure_not_trained)d (%(perc_unsure_not_trained_s)s of unsures)") \ ! % format_dict) % format_dict) ! push("") ! ! push((_("Spam correctly identified:\t%(perc_spam_correct_s)s (+ %(perc_spam_unsure_s)s unsure)") \ ! % format_dict) % format_dict) ! push((_("Ham incorrectly identified:\t%(perc_ham_incorrect_s)s (+ %(perc_ham_unsure_s)s unsure)") \ % format_dict) % format_dict) return chunks From kpitt at users.sourceforge.net Mon Dec 6 19:04:38 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Mon Dec 6 19:04:42 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py, 1.141, 1.142 oastats.py, 1.9, 1.10 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv11566 Modified Files: addin.py oastats.py Log Message: If I logged out of Windows without closing Outlook first, updated stats were not saved. Saving the stats in the OnClose event of the explorer window seems to fix this. To be safe, I left the Store() call in the OnDisconnection event of the addin as well, and added a check in the Store() routine so that stats are not saved again if they haven't changed. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.141 retrieving revision 1.142 diff -C2 -d -r1.141 -r1.142 *** addin.py 3 Dec 2004 21:43:19 -0000 1.141 --- addin.py 6 Dec 2004 18:04:34 -0000 1.142 *************** *** 1137,1144 **** def OnClose(self): ! self.manager.LogDebug(3, "OnClose", self) self.explorers_collection._DoDeadExplorer(self) self.explorers_collection = None self.toolbar = None self.close() # disconnect events. --- 1137,1145 ---- def OnClose(self): ! self.manager.LogDebug(3, "Explorer window closing", self) self.explorers_collection._DoDeadExplorer(self) self.explorers_collection = None self.toolbar = None + self.manager.stats.Store() # save stats self.close() # disconnect events. Index: oastats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/oastats.py,v retrieving revision 1.9 retrieving revision 1.10 diff -C2 -d -r1.9 -r1.10 *** oastats.py 6 Dec 2004 17:50:04 -0000 1.9 --- oastats.py 6 Dec 2004 18:04:35 -0000 1.10 *************** *** 39,49 **** def Store(self): # Update totals, and save that. for stat in ["num_ham", "num_spam", "num_unsure", "num_deleted_spam", "num_deleted_spam_fn", "num_recovered_good", "num_recovered_good_fp",]: ! self.totals[stat] += getattr(self, stat) ! store = open(self.stored_statistics_fn, 'wb') ! pickle.dump(self.totals, store) ! store.close() # Reset, or the reporting for the remainder of this session will be # incorrect. --- 39,55 ---- def Store(self): # Update totals, and save that. + should_store = False for stat in ["num_ham", "num_spam", "num_unsure", "num_deleted_spam", "num_deleted_spam_fn", "num_recovered_good", "num_recovered_good_fp",]: ! count = getattr(self, stat) ! self.totals[stat] += count ! if count != 0: ! # One of the totals changed, so we need to store the updates. ! should_store = True ! if should_store: ! store = open(self.stored_statistics_fn, 'wb') ! pickle.dump(self.totals, store) ! store.close() # Reset, or the reporting for the remainder of this session will be # incorrect. From anadelonbrin at users.sourceforge.net Tue Dec 7 01:43:52 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Tue Dec 7 01:43:54 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 oastats.py,1.10,1.11 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7727/Outlook2000 Modified Files: oastats.py Log Message: For historical reasons we use "good" not "ham"; make consistent. Fix a typo that stopped the stats working if there was no spam trained. Index: oastats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/oastats.py,v retrieving revision 1.10 retrieving revision 1.11 diff -C2 -d -r1.10 -r1.11 *** oastats.py 6 Dec 2004 18:04:35 -0000 1.10 --- oastats.py 7 Dec 2004 00:43:49 -0000 1.11 *************** *** 162,166 **** perc_spam_correct = 100.0 perc_spam_unsure = 0.0 ! perc_spam_incorrect_or_unsure = 100.0 format_dict = locals().copy() del format_dict["self"] --- 162,166 ---- perc_spam_correct = 100.0 perc_spam_unsure = 0.0 ! perc_spam_correct_or_unsure = 100.0 format_dict = locals().copy() del format_dict["self"] *************** *** 252,256 **** push((_("Spam correctly identified:\t%(perc_spam_correct_s)s (+ %(perc_spam_unsure_s)s unsure)") \ % format_dict) % format_dict) ! push((_("Ham incorrectly identified:\t%(perc_ham_incorrect_s)s (+ %(perc_ham_unsure_s)s unsure)") \ % format_dict) % format_dict) --- 252,256 ---- push((_("Spam correctly identified:\t%(perc_spam_correct_s)s (+ %(perc_spam_unsure_s)s unsure)") \ % format_dict) % format_dict) ! push((_("Good incorrectly identified:\t%(perc_ham_incorrect_s)s (+ %(perc_ham_unsure_s)s unsure)") \ % format_dict) % format_dict) From anadelonbrin at users.sourceforge.net Wed Dec 8 03:02:35 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 8 03:02:38 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_storage.py, 1.6, 1.7 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv10490/spambayes/test Modified Files: test_storage.py Log Message: Add a simple store and load test. Add a test to check that nham and nspam are correctly adjusted. Simplify tearDown. Replace a convoluted sys.stderr redirection with assertRaises. Add cases for ZODBClassifier and CDBClassifier. Skip the dbm tests if no dbm modules are available, and skip the ZODB tests if ZODB is not available (informing the user in each case). Index: test_storage.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/test/test_storage.py,v retrieving revision 1.6 retrieving revision 1.7 diff -C2 -d -r1.6 -r1.7 *** test_storage.py 9 Jul 2004 03:24:48 -0000 1.6 --- test_storage.py 8 Dec 2004 02:02:32 -0000 1.7 *************** *** 8,11 **** --- 8,12 ---- sb_test_support.fix_sys_path() + from spambayes.storage import ZODBClassifier, CDBClassifier from spambayes.storage import DBDictClassifier, PickledClassifier *************** *** 19,26 **** --- 20,68 ---- def tearDown(self): + self.classifier.close() self.classifier = None if os.path.isfile(self.db_name): os.remove(self.db_name) + def testLoadAndStore(self): + # Simple test to verify that putting data in the db, storing and + # then loading gives back the same data. + c = self.classifier + c.learn(["some", "simple", "tokens"], True) + c.learn(["some", "other"], False) + c.learn(["ones"], False) + c.store() + c.close() + del self.classifier + self.classifier = self.StorageClass(self.db_name) + self._checkAllWordCounts((("some", 1, 1), + ("simple", 0, 1), + ("tokens", 0, 1), + ("other", 1, 0), + ("ones", 1, 0)), False) + self.assertEqual(self.classifier.nham, 2) + self.assertEqual(self.classifier.nspam, 1) + + def testCounts(self): + # Check that nham and nspam are correctedly adjusted. + c = self.classifier + count = 30 + for i in xrange(count): + c.learn(["tony"], True) + self.assertEqual(c.nspam, i+1) + self.assertEqual(c.nham, 0) + for i in xrange(count): + c.learn(["tony"], False) + self.assertEqual(c.nham, i+1) + self.assertEqual(c.nspam, count) + for i in xrange(count): + c.unlearn(["tony"], True) + self.assertEqual(c.nham, count) + self.assertEqual(c.nspam, count-i-1) + for i in xrange(count): + c.unlearn(["tony"], False) + self.assertEqual(c.nham, count-i-1) + self.assertEqual(c.nspam, 0) + def _checkWordCounts(self, word, expected_ham, expected_spam): assert word *************** *** 131,138 **** StorageClass = DBDictClassifier - def tearDown(self): - self.classifier.db.close() - _StorageTestBase.tearDown(self) - def _fail_open_best(self, *args): from spambayes import dbmstorage --- 173,176 ---- *************** *** 146,172 **** DBDictClassifier_load = DBDictClassifier.load DBDictClassifier.load = self._fail_open_best ! # Redirect sys.stderr, as open_storage() prints a msg to stderr. ! # Then it does sys.exit(), which we catch. ! sys_stderr = sys.stderr ! sys.stderr = StringIO.StringIO() try: ! try: ! open_storage(db_name, "dbm") ! except SystemExit: ! pass ! else: ! self.fail("expected SystemExit from open_storage() call") finally: DBDictClassifier.load = DBDictClassifier_load - sys.stderr = sys_stderr if os.path.isfile(db_name): os.remove(db_name) def suite(): suite = unittest.TestSuite() ! for cls in (PickleStorageTestCase, ! DBStorageTestCase, ! ): suite.addTest(unittest.makeSuite(cls)) return suite --- 184,236 ---- DBDictClassifier_load = DBDictClassifier.load DBDictClassifier.load = self._fail_open_best ! print "This test will print out an error, which can be ignored." try: ! self.assertRaises(SystemExit, open_storage, (db_name, "dbm")) finally: DBDictClassifier.load = DBDictClassifier_load if os.path.isfile(db_name): os.remove(db_name) + class CDBStorageTestCase(_StorageTestBase): + StorageClass = CDBClassifier + + class ZODBStorageTestCase(_StorageTestBase): + StorageClass = ZODBClassifier + def suite(): suite = unittest.TestSuite() ! clses = (PickleStorageTestCase, ! CDBStorageTestCase, ! ) ! try: ! import gdbm ! except ImportError: ! gdbm = None ! if sys.platform != "win32" or sys.version_info > (2,3): ! try: ! import bsddb ! except ImportError: ! bsddb = None ! else: ! bsddb = None ! try: ! import bsddb3 ! except ImportError: ! bsddb3 = None ! ! if gdbm or bsddb or bsddb3: ! clses += (DBStorageTestCase,) ! else: ! print "Skipping dbm tests, no dbm module available" ! ! try: ! import ZODB ! except ImportError: ! print "Skipping ZODB tests, ZODB not available" ! else: ! clses += (ZODBStorageTestCase,) ! ! for cls in clses: suite.addTest(unittest.makeSuite(cls)) return suite From anadelonbrin at users.sourceforge.net Wed Dec 8 03:06:28 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 8 03:06:30 2004 Subject: [Spambayes-checkins] spambayes/spambayes storage.py,1.44,1.45 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv11398/spambayes Modified Files: storage.py Log Message: Fix an import. Let ZODB handle nham and nspam for us. Avoid endless recursive AttributeErrors. Print out (to stderr) the same messages that the other storage types do. Ensure that we don't try to load a closed ZODB database. Do a store before closing to avoid problems. The unittests for ZODBClassifier now pass, and the database lasts more than one Outlook session, so I think it is somewhat usable :) Index: storage.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/storage.py,v retrieving revision 1.44 retrieving revision 1.45 diff -C2 -d -r1.44 -r1.45 *** storage.py 22 Nov 2004 00:26:44 -0000 1.44 --- storage.py 8 Dec 2004 02:06:25 -0000 1.45 *************** *** 662,667 **** # is ok. try: ! Persistent ! except NameError: Persistent = object class _PersistentClassifier(classifier.Classifier, Persistent): --- 662,667 ---- # is ok. try: ! from persistent import Persistent ! except ImportError: Persistent = object class _PersistentClassifier(classifier.Classifier, Persistent): *************** *** 675,685 **** class ZODBClassifier(object): def __init__(self, db_name): - self.statekey = STATE_KEY self.db_name = db_name self.load() def __getattr__(self, att): # We pretend that we are a classifier subclass. ! if hasattr(self.classifier, att): return getattr(self.classifier, att) raise AttributeError("ZODBClassifier object has no attribute '%s'" --- 675,685 ---- class ZODBClassifier(object): def __init__(self, db_name): self.db_name = db_name + self.closed = True self.load() def __getattr__(self, att): # We pretend that we are a classifier subclass. ! if hasattr(self, "classifier") and hasattr(self.classifier, att): return getattr(self.classifier, att) raise AttributeError("ZODBClassifier object has no attribute '%s'" *************** *** 699,706 **** def load(self): import ZODB self.create_storage() self.db = ZODB.DB(self.storage) ! root = self.db.open().root() self.classifier = root.get(self.db_name) if self.classifier is None: --- 699,717 ---- def load(self): + '''Load state from database''' import ZODB + + if options["globals", "verbose"]: + print >> sys.stderr, 'Loading state from', self.db_name, 'database' + + # If we are not closed, then we need to close first before we + # reload. + if not self.closed: + self.close() + self.create_storage() self.db = ZODB.DB(self.storage) ! self.conn = self.db.open() ! root = self.conn.root() self.classifier = root.get(self.db_name) if self.classifier is None: *************** *** 709,735 **** print >> sys.stderr, self.db_name, 'is a new ZODB' self.classifier = root[self.db_name] = _PersistentClassifier() - get_transaction().commit() else: - # It seems to me that the persistent classifier should store - # the nham and nspam values, but that doesn't appear to be the - # case, so work around that. This can be removed once I figure - # out the problem. - self.nham, self.nspam = self.classifier.wordinfo[self.statekey] if options["globals", "verbose"]: print >> sys.stderr, '%s is an existing ZODB, with %d ' \ 'ham and %d spam' % (self.db_name, self.nham, self.nspam) def store(self): ! # It seems to me that the persistent classifier should store ! # the nham and nspam values, but that doesn't appear to be the ! # case, so work around that. This can be removed once I figure ! # out the problem. ! self.classifier.wordinfo[self.statekey] = (self.nham, self.nspam) ! get_transaction().commit() def close(self): self.db.close() self.storage.close() --- 720,756 ---- print >> sys.stderr, self.db_name, 'is a new ZODB' self.classifier = root[self.db_name] = _PersistentClassifier() else: if options["globals", "verbose"]: print >> sys.stderr, '%s is an existing ZODB, with %d ' \ 'ham and %d spam' % (self.db_name, self.nham, self.nspam) + self.closed = False def store(self): ! '''Place state into persistent store''' ! import ZODB ! import transaction ! ! assert self.closed == False, "Can't store a closed database" ! ! if options["globals", "verbose"]: ! print >> sys.stderr, 'Persisting', self.db_name, 'state in database' ! ! transaction.commit() def close(self): + # Ensure that the db is saved before closing. Alternatively, we + # could abort any waiting transaction. We need to do *something* + # with it, though, or it will be still around after the db is + # closed and cause problems. For now, saving seems to make sense + # (and we can always add abort methods if they are ever needed). + self.store() + + # Do the closing. self.db.close() self.storage.close() + self.closed = True + if options["globals", "verbose"]: + print >> sys.stderr, 'Closed', self.db_name, 'database' From anadelonbrin at users.sourceforge.net Wed Dec 8 04:31:38 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 8 04:31:41 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_message.py, 1.1, 1.2 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv29996/spambayes/test Modified Files: test_message.py Log Message: Add Python 2.2 compatibility. Add tests for delNotations. Add a test for inserting an exception header in a message without a header/body separator. Index: test_message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/test/test_message.py,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** test_message.py 22 Nov 2004 00:22:55 -0000 1.1 --- test_message.py 8 Dec 2004 03:31:35 -0000 1.2 *************** *** 21,25 **** # Richie's hammer.py script has code for generating any number of # randomly composed email messages. ! from test_sb_server import good1, spam1 TEMP_PICKLE_NAME = os.path.join(os.path.dirname(__file__), "temp.pik") --- 21,31 ---- # Richie's hammer.py script has code for generating any number of # randomly composed email messages. ! from test_sb_server import good1, spam1, malformed1 ! ! try: ! __file__ ! except NameError: ! # Python 2.2 compatibility. ! __file__ = sys.argv[0] TEMP_PICKLE_NAME = os.path.join(os.path.dirname(__file__), "temp.pik") *************** *** 453,456 **** --- 459,522 ---- self.assert_(header not in self.msg.keys()) + def test_delNotations(self): + # Add each type of notation to each header and check that it + # is removed. + for headername in ["subject", "to"]: + for disp in (self.ham, self.spam, self.unsure): + # Add a notation to the header + header = self.msg[headername] + self.assert_(disp not in header) + options["Headers", "notate_%s" % (headername,)] = \ + (self.ham, self.unsure, self.spam) + prob = {self.ham:self.g_prob, self.spam:self.s_prob, + self.unsure:self.u_prob}[disp] + self.msg.addSBHeaders(prob, self.clues) + self.assert_(disp in self.msg[headername]) + # Remove it + self.msg.delNotations() + self.assertEqual(self.msg[headername], header) + + def test_delNotations_missing(self): + # Check that nothing is removed if the disposition is not + # there. + for headername in ["subject", "to"]: + for disp in (self.ham, self.spam, self.unsure): + # Add a notation to the header + header = self.msg[headername] + self.assert_(disp not in header) + options["Headers", "notate_%s" % (headername,)] = () + prob = {self.ham:self.g_prob, self.spam:self.s_prob, + self.unsure:self.u_prob}[disp] + self.msg.addSBHeaders(prob, self.clues) + self.assert_(disp not in self.msg[headername]) + # Remove it + self.msg.delNotations() + self.assertEqual(self.msg[headername], header) + + def test_delNotations_only_once(self): + # Check that only one disposition is removed, even if more than + # one is present. + for headername in ["subject", "to"]: + for disp in (self.ham, self.spam, self.unsure): + # Add a notation to the header + header = self.msg[headername] + self.assert_(disp not in header) + options["Headers", "notate_%s" % (headername,)] = \ + (self.ham, self.unsure, self.spam) + prob = {self.ham:self.g_prob, self.spam:self.s_prob, + self.unsure:self.u_prob}[disp] + self.msg.addSBHeaders(prob, self.clues) + self.assert_(disp in self.msg[headername]) + header2 = self.msg[headername] + # Add a second notation + self.msg.addSBHeaders(prob, self.clues) + self.assert_(disp in + self.msg[headername].replace(disp, "", 1)) + # Remove it + self.msg.delNotations() + self.assertEqual(self.msg[headername], header2) + # Restore for next time round the loop + self.msg.replace_header(headername, header) + class MessageInfoBaseTest(unittest.TestCase): *************** *** 607,612 **** headerName = 'X-Spambayes-Exception' header = email.Header.Header(details, header_name=headerName) ! self.assertEqual(msg[headerName].replace('\n', '\r\n'), ! str(header).replace('\n', '\r\n')) def test_insert_exception_header(self): --- 673,678 ---- headerName = 'X-Spambayes-Exception' header = email.Header.Header(details, header_name=headerName) ! self.assertEqual(msg[headerName].replace('\r\n', '\n'), ! str(header).replace('\r\n', '\n')) def test_insert_exception_header(self): *************** *** 635,638 **** --- 701,714 ---- header = email.Header.Header(id, header_name=headerName) self.assertEqual(msg[headerName], str(header).replace('\n', '\r\n')) + + def test_insert_exception_header_no_separator(self): + # Cause an exception to insert. + try: + raise Exception("Test") + except Exception: + pass + msg, details = insert_exception_header(malformed1) + self._verify_details(details) + self._verify_exception_header(msg, details) From anadelonbrin at users.sourceforge.net Wed Dec 8 04:34:32 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 8 04:34:35 2004 Subject: [Spambayes-checkins] spambayes/spambayes ProxyUI.py, 1.53, 1.54 message.py, 1.60, 1.61 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30428/spambayes Modified Files: ProxyUI.py message.py Log Message: Don't overwrite existing attributes even if the message isn't already in the database. Add a function to remove (if present) the subject/to notations. Do so when removing all SB headers, as this is symmetric with adding and is typically what we want (we usually remove in order to train, and don't want them present then). Remove notations before adding messages to the review page. Adds [ 848365 ] Remove subject annotations from message review page Index: ProxyUI.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/ProxyUI.py,v retrieving revision 1.53 retrieving revision 1.54 diff -C2 -d -r1.53 -r1.54 *** ProxyUI.py 28 Nov 2004 23:38:17 -0000 1.53 --- ProxyUI.py 8 Dec 2004 03:34:29 -0000 1.54 *************** *** 681,685 **** bodySummary and other header (as needed) attributes. These objects are passed into appendMessages by onReview - passing email.Message ! objects directly uses too much memory.""" subjectHeader = message["Subject"] or "(none)" headers = {"subject" : subjectHeader} --- 681,689 ---- bodySummary and other header (as needed) attributes. These objects are passed into appendMessages by onReview - passing email.Message ! objects directly uses too much memory. ! """ ! # Remove notations before displaying - see: ! # [ 848365 ] Remove subject annotations from message review page ! message.delNotations() subjectHeader = message["Subject"] or "(none)" headers = {"subject" : subjectHeader} Index: message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/message.py,v retrieving revision 1.60 retrieving revision 1.61 diff -C2 -d -r1.60 -r1.61 *** message.py 29 Nov 2004 00:17:59 -0000 1.60 --- message.py 8 Dec 2004 03:34:29 -0000 1.61 *************** *** 132,136 **** # Set to None, as it's not there. for att in msg.stored_attributes: ! setattr(msg, att, None) else: if not isinstance(attributes, types.ListType): --- 132,138 ---- # Set to None, as it's not there. for att in msg.stored_attributes: ! # Don't overwrite. ! if not hasattr(msg, att): ! setattr(msg, att, None) else: if not isinstance(attributes, types.ListType): *************** *** 452,458 **** self[headerName] = "".join(wrappedEvd) ! # These are pretty ugly, but no-one has a better idea about how to ! # allow filtering in 'stripped down' mailers like Outlook Express, ! # so for the moment, they stay in. # options["Headers", "notate_to"] (and notate_subject) can be # either a single string (like "spam") or a tuple (like --- 454,471 ---- self[headerName] = "".join(wrappedEvd) ! if options['Headers','add_unique_id']: ! self[options['Headers','mailid_header_name']] = self.id ! ! self.addNotations() ! ! def addNotations(self): ! """Add the appropriate string to the subject: and/or to: header. ! ! This is a reasonably ugly method of including the classification, ! but no-one has a better idea about how to allow filtering in ! 'stripped down' mailers (i.e. Outlook Express), so, for the moment, ! this is it. ! """ ! disposition = self.GetClassification() # options["Headers", "notate_to"] (and notate_subject) can be # either a single string (like "spam") or a tuple (like *************** *** 488,493 **** self["Subject"] = disposition ! if options['Headers','add_unique_id']: ! self[options['Headers','mailid_header_name']] = self.id def currentSBHeaders(self): --- 501,540 ---- self["Subject"] = disposition ! def delNotations(self): ! """If present, remove our notation from the subject: and/or to: ! header of the message. ! ! This is somewhat problematic, as we cannot be 100% positive that we ! added the notation. It's almost certain to be us with the to: ! header, but someone else might have played with the subject: ! header. However, as long as the user doesn't turn this option on ! and off, this will all work nicely. ! ! See also [ 848365 ] Remove subject annotations from message review ! page ! """ ! subject = self["Subject"] ! ham = options["Headers", "header_ham_string"] + ',' ! spam = options["Headers", "header_spam_string"] + ',' ! unsure = options["Headers", "header_unsure_string"] + ',' ! if options["Headers", "notate_subject"]: ! for disp in (ham, spam, unsure): ! if subject.startswith(disp): ! self.replace_header("Subject", subject[len(disp):]) ! # Only remove one, maximum. ! break ! to = self["To"] ! ham = "%s@spambayes.invalid;" % \ ! (options["Headers", "header_ham_string"],) ! spam = "%s@spambayes.invalid;" % \ ! (options["Headers", "header_spam_string"],) ! unsure = "%s@spambayes.invalid;" % \ ! (options["Headers", "header_unsure_string"],) ! if options["Headers", "notate_to"]: ! for disp in (ham, spam, unsure): ! if to.startswith(disp): ! self.replace_header("To", to[len(disp):]) ! # Only remove one, maximum. ! break def currentSBHeaders(self): *************** *** 517,520 **** --- 564,570 ---- del self[options['Headers','score_header_name']] del self[options['Headers','trained_header_name']] + # Also delete notations - typically this is called just before + # training, and we don't want them there for that. + self.delNotations() # Utility function to insert an exception header into the given RFC822 text. From anadelonbrin at users.sourceforge.net Wed Dec 8 05:28:01 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 8 05:28:04 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 config.py, 1.34, 1.35 filter.py, 1.40, 1.41 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7519/Outlook2000 Modified Files: config.py filter.py Log Message: Add [ 1036970 ] Allow Outlook plugin to move ham to a designated folder This can't be configured via the GUI at the moment, but does at least provide the capability. If we end up making more room in the Manager dialog before 1.1 goes out, then adding configuration for this would be reasonable to do, as long as it doesn't end up looking too complicated. Index: config.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/config.py,v retrieving revision 1.34 retrieving revision 1.35 diff -C2 -d -r1.34 -r1.35 *** config.py 3 Dec 2004 21:43:19 -0000 1.34 --- config.py 8 Dec 2004 04:27:59 -0000 1.35 *************** *** 188,192 **** will be considered spam, and processed accordingly."""), REAL, RESTORE), ! ("spam_action", _("The action to take for new spam"), "Moved", _("""The action that should be taken as Spam messages arrive."""), FILTER_ACTION, RESTORE), --- 188,192 ---- will be considered spam, and processed accordingly."""), REAL, RESTORE), ! ("spam_action", _("The action to take for new spam"), FILTER_ACTION[1], _("""The action that should be taken as Spam messages arrive."""), FILTER_ACTION, RESTORE), *************** *** 215,218 **** --- 215,229 ---- filtered. See 'spam_mark_as_read' for more details."""), BOOLEAN, RESTORE), + (FolderIDOption, + "ham_folder_id", _("The folder to which good messages are moved"), None, + _("""The folder SpamBayes moves or copies good messages to."""), + FOLDER_ID, DO_NOT_RESTORE), + ("ham_action", _("The action to take for new good messages"), FILTER_ACTION[0], + _("""The action that should be taken as good messages arrive."""), + FILTER_ACTION, RESTORE), + ("ham_mark_as_read", _("Should filtered good message also be marked as 'read'"), False, + _("""Determines if good messages are marked as 'Read' as they are + filtered. See 'spam_mark_as_read' for more details."""), + BOOLEAN, RESTORE), ("enabled", _("Is filtering enabled?"), False, _(""""""), Index: filter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/filter.py,v retrieving revision 1.40 retrieving revision 1.41 diff -C2 -d -r1.40 -r1.41 *** filter.py 26 Nov 2004 03:11:43 -0000 1.40 --- filter.py 8 Dec 2004 04:27:59 -0000 1.41 *************** *** 22,26 **** else: disposition = "No" ! attr_prefix = None ms = mgr.message_store --- 22,26 ---- else: disposition = "No" ! attr_prefix = "ham" ms = mgr.message_store From kpitt at users.sourceforge.net Wed Dec 15 20:46:33 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Wed Dec 15 20:46:36 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 msgstore.py,1.91,1.92 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv13348/Outlook2000 Modified Files: msgstore.py Log Message: Include Message-ID in fake Exchange headers. Index: msgstore.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v retrieving revision 1.91 retrieving revision 1.92 diff -C2 -d -r1.91 -r1.92 *** msgstore.py 6 Dec 2004 00:11:31 -0000 1.91 --- msgstore.py 15 Dec 2004 19:46:28 -0000 1.92 *************** *** 29,32 **** --- 29,33 ---- MYPR_BODY_HTML_A = 0x1013001e # magic MYPR_BODY_HTML_W = 0x1013001f # ditto + MYPR_MESSAGE_ID_A = 0x1035001E # more magic (message id field used for Exchange) CLEAR_READ_FLAG = 0x00000004 *************** *** 987,991 **** prop_ids = PR_SUBJECT_A, PR_DISPLAY_NAME_A, PR_DISPLAY_TO_A, \ PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME, \ ! PR_SENDER_NAME_A hr, data = self.mapi_object.GetProps(prop_ids,0) subject = self._GetPotentiallyLargeStringProp(prop_ids[0], data[0]) --- 988,992 ---- prop_ids = PR_SUBJECT_A, PR_DISPLAY_NAME_A, PR_DISPLAY_TO_A, \ PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME, \ ! PR_SENDER_NAME_A, MYPR_MESSAGE_ID_A hr, data = self.mapi_object.GetProps(prop_ids,0) subject = self._GetPotentiallyLargeStringProp(prop_ids[0], data[0]) *************** *** 996,999 **** --- 997,1002 ---- alt_sender = self._GetPotentiallyLargeStringProp(prop_ids[5], data[5]) + message_id = self._GetPotentiallyLargeStringProp(prop_ids[6], + data[6]) headers = ["X-Exchange-Message: true"] if subject: *************** *** 1007,1010 **** --- 1010,1015 ---- if cc: headers.append("CC: "+cc) + if message_id: + headers.append("Message-ID: "+message_id) if delivery_time: from time import timezone From anadelonbrin at users.sourceforge.net Thu Dec 16 04:23:19 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 16 04:23:22 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 msgstore.py,1.92,1.93 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv17493/Outlook2000 Modified Files: msgstore.py Log Message: Improve the faked up exchange headers that we generate if there are no Internet headers. Address headers are now in a standard format. Generate organisation, importance and date. Change X-Exchange-Delivery-Time to received. Index: msgstore.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v retrieving revision 1.92 retrieving revision 1.93 diff -C2 -d -r1.92 -r1.93 *** msgstore.py 15 Dec 2004 19:46:28 -0000 1.92 --- msgstore.py 16 Dec 2004 03:23:16 -0000 1.93 *************** *** 30,33 **** --- 30,35 ---- MYPR_BODY_HTML_W = 0x1013001f # ditto MYPR_MESSAGE_ID_A = 0x1035001E # more magic (message id field used for Exchange) + MYPR_VERSION_ID = 0x7E8FFFFD # magic that I think tells us the Outlook version + MYPR_ORGANISATION_A = 0x1037001E # I think this is the organisation magic CLEAR_READ_FLAG = 0x00000004 *************** *** 985,1022 **** def _GetFakeHeaders(self): # This is designed to fake up some SMTP headers for messages ! # on an exchange server that do not have such headers of their own ! prop_ids = PR_SUBJECT_A, PR_DISPLAY_NAME_A, PR_DISPLAY_TO_A, \ PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME, \ ! PR_SENDER_NAME_A, MYPR_MESSAGE_ID_A ! hr, data = self.mapi_object.GetProps(prop_ids,0) ! subject = self._GetPotentiallyLargeStringProp(prop_ids[0], data[0]) ! sender = self._GetPotentiallyLargeStringProp(prop_ids[1], data[1]) ! to = self._GetPotentiallyLargeStringProp(prop_ids[2], data[2]) ! cc = self._GetPotentiallyLargeStringProp(prop_ids[3], data[3]) ! delivery_time = data[4][1] ! alt_sender = self._GetPotentiallyLargeStringProp(prop_ids[5], ! data[5]) ! message_id = self._GetPotentiallyLargeStringProp(prop_ids[6], ! data[6]) headers = ["X-Exchange-Message: true"] ! if subject: ! headers.append("Subject: "+subject) ! if sender: ! headers.append("From: "+sender) ! elif alt_sender: ! headers.append("From: "+alt_sender) ! if to: ! headers.append("To: "+to) ! if cc: ! headers.append("CC: "+cc) ! if message_id: ! headers.append("Message-ID: "+message_id) ! if delivery_time: ! from time import timezone ! from email.Utils import formatdate ! headers.append("X-Exchange-Delivery-Time: "+\ ! formatdate(int(delivery_time)-timezone, True)) return "\n".join(headers) + "\n" def _EnsureObject(self): if self.mapi_object is None: --- 987,1050 ---- def _GetFakeHeaders(self): # This is designed to fake up some SMTP headers for messages ! # on an exchange server that do not have such headers of their own. ! prop_ids = PR_SUBJECT_A, PR_SENDER_NAME_A, PR_DISPLAY_TO_A, \ PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME, \ ! MYPR_MESSAGE_ID_A, PR_IMPORTANCE, PR_CLIENT_SUBMIT_TIME, \ ! MYPR_ORGANISATION_A, ! # This property gives a 'The parameter is incorrect' error, for some ! # reason, as does, 0x7E8EFFE2, which I think is the 'pretty' version ! # number. Until that's figured out, we'll have to not get this. ! # MYPR_VERSION_ID ! hr, data = self.mapi_object.GetProps(prop_ids, 0) headers = ["X-Exchange-Message: true"] ! for header, index, potentially_large, format_func in (\ ! ("Subject", 0, True, None), ! ("From", 1, True, self._format_address), ! ("To", 2, True, self._format_address), ! ("CC", 3, True, self._format_address), ! ("Received", 4, False, self._format_received), ! ("Message-ID", 5, True, None), ! ("Importance", 6, False, self._format_importance), ! ("Date", 7, False, self._format_time), ! ("Organisation", 8, True, None), ! # ("X-Mailer", 9, False, self._format_version), ! ): ! if potentially_large: ! value = self._GetPotentiallyLargeStringProp(prop_ids[index], ! data[index]) ! else: ! value = data[index][1] ! if value: ! if format_func: ! value = format_func(value) ! headers.append("%s: %s" % (header, value)) return "\n".join(headers) + "\n" + def _format_received(self, raw): + # Fake up a 'received' header. It's important that the date + # is right, so that sort+group.py will work. The rest is just more + # clues for the tokenizer to find. + return "(via local Exchange server); %s" % (self._format_time(raw),) + + def _format_time(self, raw): + from time import timezone + from email.Utils import formatdate + return formatdate(int(raw)-timezone, True) + + def _format_importance(self, raw): + # olImportanceHigh = 2, olImportanceLow = 0, olImportanceNormal = 1 + return {0 : "low", 1 : "normal", 2 : "high"}[raw] + + def _format_version(self, raw): + # Data is just a version string, so prepend something to it. + return "Exchange Client " + raw + + _address_re = re.compile(r"[()<>,:@!/=; ]") + def _format_address(self, raw): + # Fudge up something that's in the appropriate form. We don't + # have enough information available to get an actual working + # email address. + return "%s@invalid (%s)" % (self._address_re.sub('', raw), raw) + def _EnsureObject(self): if self.mapi_object is None: From anadelonbrin at users.sourceforge.net Thu Dec 16 06:31:22 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 16 06:31:25 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_imapfilter.py,1.45,1.46 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4711/scripts Modified Files: sb_imapfilter.py Log Message: Compile re's and do this only once. This should speed things up (previously we regenerated the re each time (eg) we retrieved a message. Safeguard getting the RFC822.Header data, as this is causing problems at the moment. Print out additional debugging material for the moment, and possibly handle this better later, when we know more about what is going wrong. Index: sb_imapfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_imapfilter.py,v retrieving revision 1.45 retrieving revision 1.46 diff -C2 -d -r1.45 -r1.46 *** sb_imapfilter.py 30 Nov 2004 23:49:23 -0000 1.45 --- sb_imapfilter.py 16 Dec 2004 05:31:02 -0000 1.46 *************** *** 131,135 **** # There's a tricky situation where if use_ssl is False, but we # try to connect to a IMAP over SSL server, we will just hang ! # forever, waiting for a repsonse that will never come. To # get past this, just for the welcome message, we install a # timeout on the connection. Normal service is then returned. --- 131,135 ---- # There's a tricky situation where if use_ssl is False, but we # try to connect to a IMAP over SSL server, we will just hang ! # forever, waiting for a response that will never come. To # get past this, just for the welcome message, we install a # timeout on the connection. Normal service is then returned. *************** *** 256,259 **** --- 256,261 ---- return data + number_re = re.compile(r"{\d+}") + folder_re = re.compile(r"\(([\w\\ ]*)\) ") def folder_list(self): """Return a alphabetical list of all folders available on the *************** *** 272,282 **** # literal, so we need to crunch this out. if isinstance(fol, types.TupleType): ! m = re.search(r"{\d+}", fol[0]) if not m: # Something is wrong here! Skip this folder. continue fol = '%s"%s"' % (fol[0][:m.start()], fol[1]) ! r = re.compile(r"\(([\w\\ ]*)\) ") ! m = r.search(fol) if not m: # Something is not good with this folder, so skip it. --- 274,283 ---- # literal, so we need to crunch this out. if isinstance(fol, types.TupleType): ! m = self.number_re.search(fol[0]) if not m: # Something is wrong here! Skip this folder. continue fol = '%s"%s"' % (fol[0][:m.start()], fol[1]) ! m = self.folder_re.search(fol) if not m: # Something is not good with this folder, so skip it. *************** *** 511,514 **** --- 512,516 ---- return message.SBHeaderMessage.as_string(self, unixfrom) + recent_re = re.compile(r"\\Recent ?| ?\\Recent") def Save(self): """Save message to IMAP server. *************** *** 537,541 **** # The \Recent flag can be fetched, but cannot be stored # We must remove it from the list if it is there. ! flags = re.sub(r"\\Recent ?| ?\\Recent", "", flags) else: flags = None --- 539,543 ---- # The \Recent flag can be fetched, but cannot be stored # We must remove it from the list if it is there. ! flags = self.recent_re.sub("", flags) else: flags = None *************** *** 676,679 **** --- 678,686 ---- return [] + custom_header_id_re = re.compile(re.escape(\ + options["Headers", "mailid_header_name"]) + "\:\s*(\d+(?:\-\d)?)", + re.IGNORECASE) + message_id_re = re.compile("Message-ID\: ?\<([^\n\>]+)\>", + re.IGNORECASE) def __getitem__(self, key): """Return message matching the given *uid*. *************** *** 688,694 **** # that not all servers accept it, even though it is in the RFC response = self.imap_server.uid("FETCH", key, "RFC822.HEADER") ! data = self.imap_server.check_response("fetch %s rfc822.header" \ ! % (key,), response) ! data = self.imap_server.extract_fetch_data(data[0]) # Create a new IMAPMessage object, which will be the return value. --- 695,701 ---- # that not all servers accept it, even though it is in the RFC response = self.imap_server.uid("FETCH", key, "RFC822.HEADER") ! response_data = self.imap_server.check_response(\ ! "fetch %s rfc822.header" % (key,), response) ! data = self.imap_server.extract_fetch_data(response_data[0]) # Create a new IMAPMessage object, which will be the return value. *************** *** 700,709 **** # We use the MessageID header as the ID for the message, as long # as it is available, and if not, we add our own. ! custom_header_id = re.escape(options["Headers", ! "mailid_header_name"]) + \ ! "\:\s*(\d+(?:\-\d)?)" # Search for our custom id first, for backwards compatibility. ! for id_header in [custom_header_id, "Message-ID\: ?\<([^\n\>]+)\>"]: ! mo = re.search(id_header, data["RFC822.HEADER"], re.IGNORECASE) if mo: msg.setId(mo.group(1)) --- 707,728 ---- # We use the MessageID header as the ID for the message, as long # as it is available, and if not, we add our own. ! try: ! headers = data["RFC822.HEADER"] ! except KeyError: ! # This is bad! We asked for this in the fetch, so either ! # our parsing is wrong or the response from the server is ! # wrong. For the moment, print out some debugging info ! # and don't do anything else (which means we will keep ! # coming back to this message). ! print >> sys.stderr, "Trouble parsing response:", \ ! response_data, data ! print >> sys.stderr, "Please report this to spambayes@python.org" ! if options["globals", "verbose"]: ! sys.stdout.write("?") ! return msg ! # Search for our custom id first, for backwards compatibility. ! for id_header_re in [self.custom_header_id_re, self.message_id_re]: ! mo = id_header_re.search(headers) if mo: msg.setId(mo.group(1)) From kpitt at users.sourceforge.net Thu Dec 16 17:20:25 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Thu Dec 16 17:20:29 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 msgstore.py,1.93,1.94 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4936 Modified Files: msgstore.py Log Message: Improve e-mail address formatting for fake Exchange headers so that it supports multiple addresses, some of which may be real Internet addresses. Also, RFC 822 uses the American spelling of "Organization". Index: msgstore.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v retrieving revision 1.93 retrieving revision 1.94 diff -C2 -d -r1.93 -r1.94 *** msgstore.py 16 Dec 2004 03:23:16 -0000 1.93 --- msgstore.py 16 Dec 2004 16:20:23 -0000 1.94 *************** *** 1007,1011 **** ("Importance", 6, False, self._format_importance), ("Date", 7, False, self._format_time), ! ("Organisation", 8, True, None), # ("X-Mailer", 9, False, self._format_version), ): --- 1007,1011 ---- ("Importance", 6, False, self._format_importance), ("Date", 7, False, self._format_time), ! ("Organization", 8, True, None), # ("X-Mailer", 9, False, self._format_version), ): *************** *** 1045,1049 **** # have enough information available to get an actual working # email address. ! return "%s@invalid (%s)" % (self._address_re.sub('', raw), raw) def _EnsureObject(self): --- 1045,1059 ---- # have enough information available to get an actual working # email address. ! addresses = raw.split(";") ! formattedAddresses = [] ! for address in addresses: ! address = address.strip() ! if address.find("@") >= 0: ! formattedAddress = address ! else: ! formattedAddress = "\"%s\" <%s>" % \ ! (address, self._address_re.sub('.', address)) ! formattedAddresses.append(formattedAddress) ! return "; ".join(formattedAddresses) def _EnsureObject(self): From kpitt at users.sourceforge.net Thu Dec 16 18:43:54 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Thu Dec 16 18:43:58 2004 Subject: [Spambayes-checkins] spambayes WHAT_IS_NEW.txt,1.35,1.36 Message-ID: Update of /cvsroot/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv24402 Modified Files: WHAT_IS_NEW.txt Log Message: The use_bigrams option is no longer experimental. Index: WHAT_IS_NEW.txt =================================================================== RCS file: /cvsroot/spambayes/spambayes/WHAT_IS_NEW.txt,v retrieving revision 1.35 retrieving revision 1.36 diff -C2 -d -r1.35 -r1.36 *** WHAT_IS_NEW.txt 5 May 2004 00:37:07 -0000 1.35 --- WHAT_IS_NEW.txt 16 Dec 2004 17:43:50 -0000 1.36 *************** *** 144,154 **** for more details). - o [Classifier] x-use_bigrams - By default, SpamBayes uses unigrams tokens that are basically - single words (split on whitespace). This option enables both unigrams - and bigrams (pairs of words), but uses a 'tiling' scheme, where only - the set of unigrams and bigrams that have the strongest effect on - the message are used. - o [URLRetriever] x-slurp_urls o [URLRetriever] x-cache_expiry_days --- 144,147 ---- From anadelonbrin at users.sourceforge.net Fri Dec 17 01:37:24 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Fri Dec 17 01:37:27 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_sb_imapfilter.py, 1.6, 1.7 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv20003/spambayes/test Modified Files: test_sb_imapfilter.py Log Message: Update to reflect the new API for extract_fetch_data. Add a new, more difficult test, that matches the problem outlined on spambayes@python.org. Index: test_sb_imapfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/test/test_sb_imapfilter.py,v retrieving revision 1.6 retrieving revision 1.7 diff -C2 -d -r1.6 -r1.7 *** test_sb_imapfilter.py 22 Nov 2004 00:22:55 -0000 1.6 --- test_sb_imapfilter.py 17 Dec 2004 00:37:21 -0000 1.7 *************** *** 400,438 **** message_number = "123" uid = "5432" ! response = "%s (UID %s)" % (message_number, uid) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data["message_number"], message_number) ! self.assertEqual(data["UID"], uid) # Check INTERNALDATE, FLAGS. flags = r"(\Seen \Deleted)" date = '"27-Jul-2004 13:11:56 +1200"' ! response = "%s (FLAGS %s INTERNALDATE %s)" % \ ! (message_number, flags, date) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data["FLAGS"], flags) ! self.assertEqual(data["INTERNALDATE"], date) # Check RFC822 and literals. rfc = "Subject: Test\r\n\r\nThis is a test message." ! response = ("%s (RFC822 {%s}" % (message_number, len(rfc)), rfc) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data["message_number"], message_number) ! self.assertEqual(data["RFC822"], rfc) # Check RFC822.HEADER. headers = "Subject: Foo\r\nX-SpamBayes-ID: 1231-1\r\n" ! response = ("%s (RFC822.HEADER {%s}" % (message_number, ! len(headers)), headers) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data["RFC822.HEADER"], headers) # Check BODY.PEEK. peek = "Subject: Test2\r\n\r\nThis is another test message." ! response = ("%s (BODY[] {%s}" % (message_number, len(peek)), ! peek) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data["BODY[]"], peek) class IMAPMessageTest(BaseIMAPFilterTest): --- 400,496 ---- message_number = "123" uid = "5432" ! response = ("%s (UID %s)" % (message_number, uid),) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data[message_number]["message_number"], ! message_number) ! self.assertEqual(data[message_number]["UID"], uid) # Check INTERNALDATE, FLAGS. flags = r"(\Seen \Deleted)" date = '"27-Jul-2004 13:11:56 +1200"' ! response = ("%s (FLAGS %s INTERNALDATE %s)" % \ ! (message_number, flags, date),) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data[message_number]["message_number"], ! message_number) ! self.assertEqual(data[message_number]["FLAGS"], flags) ! self.assertEqual(data[message_number]["INTERNALDATE"], date) # Check RFC822 and literals. rfc = "Subject: Test\r\n\r\nThis is a test message." ! response = (("%s (RFC822 {%s}" % (message_number, len(rfc)), rfc),) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data[message_number]["message_number"], ! message_number) ! self.assertEqual(data[message_number]["RFC822"], rfc) # Check RFC822.HEADER. headers = "Subject: Foo\r\nX-SpamBayes-ID: 1231-1\r\n" ! response = (("%s (RFC822.HEADER {%s}" % (message_number, ! len(headers)), headers),) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data[message_number]["RFC822.HEADER"], headers) # Check BODY.PEEK. peek = "Subject: Test2\r\n\r\nThis is another test message." ! response = (("%s (BODY[] {%s}" % (message_number, len(peek)), ! peek),) data = self.imap.extract_fetch_data(response) ! self.assertEqual(data[message_number]["BODY[]"], peek) + # A more complcated test with more than one message number. + uid = '3018' + flags = '(\\Seen \\Deleted)' + headers = "Return-Path: \r\nX-Original-To" \ + ": david@leinbach.name\r\nDelivered-To: dleinbac@ma" \ + "il2.majro.dhs.org\r\nReceived: from its-mail1.mass" \ + "ey.ac.nz (its-mail1.massey.ac.nz [130.123.128.11])" \ + "\r\n\tby mail2.majro.dhs.org (Postfix) with ESMTP " \ + "id 7BC5018FE22\r\n\tfor ; Mon" \ + ", 13 Dec 2004 22:46:05 -0800 (PST)\r\nReceived: fr" \ + "om its-mm1.massey.ac.nz (its-mm1 [130.123.128.45])" \ + "\r\n\tby its-mail1.massey.ac.nz (8.9.3/8.9.3) with" \ + "ESMTP id TAA12081;\r\n\tTue, 14 Dec 2004 19:45:56 " \ + "+1300 (NZDT)\r\nReceived: from its-campus2.massey." \ + "ac.nz (Not Verified[130.123.48.254]) by its-mm1.ma" \ + "ssey.ac.nz with NetIQ MailMarshal\r\n\tid ; Tue, 14 Dec 2004 19:45:56 +1300\r\nReceived:" \ + "from it029048 (it029048.massey.ac.nz [130.123.238." \ + "51])\r\n\tby its-campus2.massey.ac.nz (8.9.3/8.9.3" \ + ") with ESMTP id TAA05881;\r\n\tTue, 14 Dec 2004 19" \ + ':45:55 +1300 (NZDT)\r\nFrom: "Tony Meyer" \r\nTo: "\'David Leinbach\'" , \r\nSubject: " \ + "RE: [Spambayes] KeyError in sp_imapfilter\r\nDate:" \ + "Tue, 14 Dec 2004 19:45:25 +1300\r\nMessage-ID: \r\nMIME-Version: 1.0\r\nContent-Type: t" \ + 'ext/plain;\r\n\tcharset="us-ascii"\r\nContent-Tran' \ + "sfer-Encoding: quoted-printable\r\nX-Priority: 3 (" \ + "Normal)\r\nX-MSMail-Priority: Normal\r\nX-Mailer: " \ + "Microsoft Outlook, Build 10.0.4510\r\nIn-Reply-To:" \ + "\r\nX-Habeas-SWE-1: winter into spri" \ + "ng\r\nX-Habeas-SWE-2: brightly anticipated\r\nX-Ha" \ + "beas-SWE-3: like Habeas SWE (tm)\r\nX-Habeas-SWE-4" \ + ": Copyright 2002 Habeas (tm)\r\nX-Habeas-SWE-5: Se" \ + "nder Warranted Email (SWE) (tm). The sender of thi" \ + "s\r\nX-Habeas-SWE-6: email in exchange for a licen" \ + "se for this Habeas\r\nX-Habeas-SWE-7: warrant mark" \ + "warrants that this is a Habeas Compliant\r\nX-Habe" \ + "as-SWE-8: Message (HCM) and not spam. Please repo" \ + "rt use of this\r\nX-Habeas-SWE-9: mark in spam to " \ + ".\r\nX-MimeOLE: Pro" \ + "duced By Microsoft MimeOLE V6.00.2900.2180\r\nImpo" \ + "rtance: Normal\r\n\r\n" + response = ['1 (FLAGS %s)' % flags, + ('2 (UID %s RFC822.HEADER {%d}' % (uid, len(headers)), + headers), ')'] + data = self.imap.extract_fetch_data(response) + self.assertEqual(data['1']["message_number"], '1') + self.assertEqual(data['2']["message_number"], '2') + self.assertEqual(data['1']["FLAGS"], flags) + self.assertEqual(data['2']["UID"], uid) + self.assertEqual(data['2']["RFC822.HEADER"], headers) class IMAPMessageTest(BaseIMAPFilterTest): From anadelonbrin at users.sourceforge.net Fri Dec 17 01:38:47 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Fri Dec 17 01:38:50 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_imapfilter.py,1.46,1.47 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv20378/scripts Modified Files: sb_imapfilter.py Log Message: Update extract_fetch_data to handle any number of literals/message numbers. A dict of dicts is now returned. Index: sb_imapfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_imapfilter.py,v retrieving revision 1.46 retrieving revision 1.47 diff -C2 -d -r1.46 -r1.47 *** sb_imapfilter.py 16 Dec 2004 05:31:02 -0000 1.46 --- sb_imapfilter.py 17 Dec 2004 00:38:45 -0000 1.47 *************** *** 320,348 **** re.escape(FLAG_CHARS) + r"\"\{\}\(\)\\ ]*)\)?") LITERAL_RE = re.compile(r"^\{[\d]+\}$") ! def extract_fetch_data(self, response): ! """Extract data from the response given to an IMAP FETCH command. ! ! The data is put into a dictionary, which is returned, where the ! keys are the fetch items. """ - # The response might be a tuple containing literal data. - # At the moment, we only handle one literal per response. This - # may need to be improved if our code ever asks for something - # more complicated (like RFC822.Header and RFC822.Body). - if isinstance(response, types.TupleType): - literal = response[1] - response = response[0] - else: - literal = None - - # The first item will always be the message number. - mo = self.FETCH_RESPONSE_RE.match(response) - data = {} - if mo: - data["message_number"] = mo.group(1) - response = mo.group(2) - else: - raise BadIMAPResponseError("FETCH response", response) - # We support the following FETCH items: # FLAGS --- 320,327 ---- re.escape(FLAG_CHARS) + r"\"\{\}\(\)\\ ]*)\)?") LITERAL_RE = re.compile(r"^\{[\d]+\}$") ! def _extract_fetch_data(self, response): ! """This does the real work of extracting the data, for each message ! number. """ # We support the following FETCH items: # FLAGS *************** *** 353,364 **** # BODY.PEEK # All others are ignored. ! for r in [self.FLAGS_RE, self.INTERNALDATE_RE, self.RFC822_RE, ! self.UID_RE, self.RFC822_HEADER_RE, self.BODY_PEEK_RE]: ! mo = r.search(response) ! if mo is not None: ! if self.LITERAL_RE.match(mo.group(2)): ! data[mo.group(1)] = literal ! else: ! data[mo.group(1)] = mo.group(2) return data --- 332,391 ---- # BODY.PEEK # All others are ignored. ! ! if isinstance(response, types.StringTypes): ! response = (response,) ! ! data = {} ! expected_literal = None ! for part in response: ! # We ignore parentheses by themselves, for convenience. ! if part == ')': ! continue ! if expected_literal: ! # This should be a literal of a certain size. ! key, expected_size = expected_literal ! ## if len(part) != expected_size: ! ## raise BadIMAPResponseError(\ ! ## "FETCH response (wrong size literal %d != %d)" % \ ! ## (len(part), expected_size), response) ! data[key] = part ! expected_literal = None ! continue ! # The first item will always be the message number. ! mo = self.FETCH_RESPONSE_RE.match(part) ! if mo: ! data["message_number"] = mo.group(1) ! rest = mo.group(2) ! else: ! raise BadIMAPResponseError("FETCH response", response) ! ! for r in [self.FLAGS_RE, self.INTERNALDATE_RE, self.RFC822_RE, ! self.UID_RE, self.RFC822_HEADER_RE, self.BODY_PEEK_RE]: ! mo = r.search(rest) ! if mo is not None: ! if self.LITERAL_RE.match(mo.group(2)): ! # The next element will be a literal. ! expected_literal = (mo.group(1), ! int(mo.group(2)[1:-1])) ! else: ! data[mo.group(1)] = mo.group(2) ! return data ! ! def extract_fetch_data(self, response): ! """Extract data from the response given to an IMAP FETCH command. ! ! The data is put into a dictionary, which is returned, where the ! keys are the fetch items. ! """ ! # There may be more than one message number in the response, so ! # handle separately. ! if isinstance(response, types.StringTypes): ! response = (response,) ! ! data = {} ! for msg in response: ! msg_data = self._extract_fetch_data(msg) ! if msg_data: ! data[msg_data["message_number"]] = msg_data return data *************** *** 448,457 **** command = "uid fetch %s" % (self.uid,) ! data = self.imap_server.check_response(command, response) ! data = self.imap_server.extract_fetch_data(data[0]) try: ! new_msg = email.message_from_string(data[self.rfc822_key], ! IMAPMessage) # We use a general 'except' because the email package doesn't # always return email.Errors (it can return a TypeError, for --- 475,494 ---- command = "uid fetch %s" % (self.uid,) ! response_data = self.imap_server.check_response(command, response) ! data = self.imap_server.extract_fetch_data(response_data) ! # The data will be a dictionary - hopefully with only one element, ! # but maybe more than one. The key is the message number, which we ! # do not have (we use the UID instead). So we look through the ! # message and use the first data of the right type we find. ! rfc822_data = None ! for msg_data in data.itervalues(): ! if self.rfc822_key in msg_data: ! rfc822_data = msg_data[self.rfc822_key] ! break ! if rfc822_data is None: ! raise BadIMAPResponseError("FETCH response", response_data) try: ! new_msg = email.message_from_string(rfc822_data, IMAPMessage) # We use a general 'except' because the email package doesn't # always return email.Errors (it can return a TypeError, for *************** *** 471,475 **** self.invalid = True text, details = message.insert_exception_header( ! data[self.rfc822_key], self.id) self.invalid_content = text self.got_substance = True --- 508,512 ---- self.invalid = True text, details = message.insert_exception_header( ! rfc822_data, self.id) self.invalid_content = text self.got_substance = True *************** *** 527,546 **** "(FLAGS INTERNALDATE)") command = "fetch %s (flags internaldate)" % (self.uid,) ! data = self.imap_server.check_response(command, response) ! data = self.imap_server.extract_fetch_data(data[0]) ! ! if data.has_key("INTERNALDATE"): ! msg_time = data["INTERNALDATE"] ! else: ! msg_time = self.extractTime() ! ! if data.has_key("FLAGS"): ! flags = data["FLAGS"] ! # The \Recent flag can be fetched, but cannot be stored ! # We must remove it from the list if it is there. ! flags = self.recent_re.sub("", flags) ! else: ! flags = None ! # We try to save with flags and time, then with just the # time, then with the flags and the current time, then with just --- 564,584 ---- "(FLAGS INTERNALDATE)") command = "fetch %s (flags internaldate)" % (self.uid,) ! response_data = self.imap_server.check_response(command, response) ! data = self.imap_server.extract_fetch_data(response_data) ! # The data will be a dictionary - hopefully with only one element, ! # but maybe more than one. The key is the message number, which we ! # do not have (we use the UID instead). So we look through the ! # message and use the last data of the right type we find. ! msg_time = self.extractTime() ! flags = None ! for msg_data in data.itervalues(): ! if "INTERNALDATE" in msg_data: ! msg_time = msg_data["INTERNALDATE"] ! if "FLAGS" in msg_data: ! flags = msg_data["FLAGS"] ! # The \Recent flag can be fetched, but cannot be stored ! # We must remove it from the list if it is there. ! flags = self.recent_re.sub("", flags) ! # We try to save with flags and time, then with just the # time, then with the flags and the current time, then with just *************** *** 697,701 **** response_data = self.imap_server.check_response(\ "fetch %s rfc822.header" % (key,), response) ! data = self.imap_server.extract_fetch_data(response_data[0]) # Create a new IMAPMessage object, which will be the return value. --- 735,750 ---- response_data = self.imap_server.check_response(\ "fetch %s rfc822.header" % (key,), response) ! data = self.imap_server.extract_fetch_data(response_data) ! # The data will be a dictionary - hopefully with only one element, ! # but maybe more than one. The key is the message number, which we ! # do not have (we use the UID instead). So we look through the ! # message and use the first data of the right type we find. ! headers = None ! for msg_data in data.itervalues(): ! if "RFC822.HEADER" in msg_data: ! headers = msg_data["RFC822.HEADER"] ! break ! if headers is None: ! raise BadIMAPResponseError("FETCH response", response_data) # Create a new IMAPMessage object, which will be the return value. *************** *** 707,725 **** # We use the MessageID header as the ID for the message, as long # as it is available, and if not, we add our own. - try: - headers = data["RFC822.HEADER"] - except KeyError: - # This is bad! We asked for this in the fetch, so either - # our parsing is wrong or the response from the server is - # wrong. For the moment, print out some debugging info - # and don't do anything else (which means we will keep - # coming back to this message). - print >> sys.stderr, "Trouble parsing response:", \ - response_data, data - print >> sys.stderr, "Please report this to spambayes@python.org" - if options["globals", "verbose"]: - sys.stdout.write("?") - return msg - # Search for our custom id first, for backwards compatibility. for id_header_re in [self.custom_header_id_re, self.message_id_re]: --- 756,759 ---- From anadelonbrin at users.sourceforge.net Fri Dec 17 02:22:31 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Fri Dec 17 02:22:34 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_message.py, 1.2, 1.3 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30892/spambayes/test Modified Files: test_message.py Log Message: The correct address separator is ',' not ';', so get notate_to to use that. Index: test_message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/test/test_message.py,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** test_message.py 8 Dec 2004 03:31:35 -0000 1.2 --- test_message.py 17 Dec 2004 01:22:28 -0000 1.3 *************** *** 334,338 **** options["Headers", "notate_to"] = (self.ham,) self.msg.addSBHeaders(self.g_prob, self.clues) ! disp, orig = self.msg["To"].split(';', 1) self.assertEqual(orig, self.to) self.assertEqual(disp, "%s@spambayes.invalid" % (self.ham,)) --- 334,338 ---- options["Headers", "notate_to"] = (self.ham,) self.msg.addSBHeaders(self.g_prob, self.clues) ! disp, orig = self.msg["To"].split(',', 1) self.assertEqual(orig, self.to) self.assertEqual(disp, "%s@spambayes.invalid" % (self.ham,)) *************** *** 341,345 **** options["Headers", "notate_to"] = (self.ham, self.unsure) self.msg.addSBHeaders(self.u_prob, self.clues) ! disp, orig = self.msg["To"].split(';', 1) self.assertEqual(orig, self.to) self.assertEqual(disp, "%s@spambayes.invalid" % (self.unsure,)) --- 341,345 ---- options["Headers", "notate_to"] = (self.ham, self.unsure) self.msg.addSBHeaders(self.u_prob, self.clues) ! disp, orig = self.msg["To"].split(',', 1) self.assertEqual(orig, self.to) self.assertEqual(disp, "%s@spambayes.invalid" % (self.unsure,)) *************** *** 348,352 **** options["Headers", "notate_to"] = (self.ham, self.spam, self.unsure) self.msg.addSBHeaders(self.s_prob, self.clues) ! disp, orig = self.msg["To"].split(';', 1) self.assertEqual(orig, self.to) self.assertEqual(disp, "%s@spambayes.invalid" % (self.spam,)) --- 348,352 ---- options["Headers", "notate_to"] = (self.ham, self.spam, self.unsure) self.msg.addSBHeaders(self.s_prob, self.clues) ! disp, orig = self.msg["To"].split(',', 1) self.assertEqual(orig, self.to) self.assertEqual(disp, "%s@spambayes.invalid" % (self.spam,)) *************** *** 399,403 **** result = self.test_notate_to_ham() # Just be sure that it's using the new value. ! self.assertEqual(self.msg["To"].split(';', 1)[0], "bacon@spambayes.invalid") finally: --- 399,403 ---- result = self.test_notate_to_ham() # Just be sure that it's using the new value. ! self.assertEqual(self.msg["To"].split(',', 1)[0], "bacon@spambayes.invalid") finally: From anadelonbrin at users.sourceforge.net Fri Dec 17 02:23:39 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Fri Dec 17 02:23:42 2004 Subject: [Spambayes-checkins] spambayes/spambayes message.py,1.61,1.62 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv31195/spambayes Modified Files: message.py Log Message: The correct address separator is ',' not ';', so get notate_to to use that. Spit out a warning if the deprecated function is used. Change the deprecated function to work in Python 2.4 (this bit should be backported). Index: message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/message.py,v retrieving revision 1.61 retrieving revision 1.62 diff -C2 -d -r1.61 -r1.62 *** message.py 8 Dec 2004 03:34:29 -0000 1.61 --- message.py 17 Dec 2004 01:23:36 -0000 1.62 *************** *** 87,90 **** --- 87,91 ---- import errno import shelve + import warnings try: import cPickle as pickle *************** *** 290,302 **** This function does not work (as a result of using private methods in a hackish way) in Python 2.4, so is now deprecated. ! Use *_from_string as described above.""" ! prs = email.Parser.Parser() ! fp = StringIO.StringIO(payload) ! # this is kindof a hack, due to the fact that the parser creates a ! # new message object, and we already have the message object ! prs._parseheaders(self, fp) ! # we may want to do some header parsing error handling here ! # to try to extract important headers regardless of malformations ! prs._parsebody(self, fp) def setId(self, id): --- 291,307 ---- This function does not work (as a result of using private methods in a hackish way) in Python 2.4, so is now deprecated. ! Use *_from_string as described above. ! ! More: Python 2.4 has a new email package, and the private functions ! are gone. So this won't even work. We have to do something to ! get this to work, for the 1.0.x branch, so use a different ugly ! hack. ! """ ! warnings.warn("setPayload is deprecated. Use " \ ! "email.message_from_string(payload, _class=" \ ! "Message) instead.", ! DeprecationWarning, 2) ! new_me = email.message_from_string(payload, _class=Message) ! self.__dict__ = new_me.__dict__ def setId(self, id): *************** *** 387,390 **** --- 392,404 ---- '''Message class that is cognizant of SpamBayes headers. Adds routines to add/remove headers for SpamBayes''' + def setPayload(self, payload): + """DEPRECATED. + """ + warnings.warn("setPayload is deprecated. Use " \ + "email.message_from_string(payload, _class=" \ + "SBHeaderMessage) instead.", + DeprecationWarning, 2) + new_me = email.message_from_string(payload, _class=SBHeaderMessage) + self.__dict__ = new_me.__dict__ def setIdFromPayload(self): *************** *** 486,490 **** address = "%s@spambayes.invalid" % (disposition, ) try: ! self.replace_header("To", "%s;%s" % (address, self["To"])) except KeyError: self["To"] = address --- 500,504 ---- address = "%s@spambayes.invalid" % (disposition, ) try: ! self.replace_header("To", "%s,%s" % (address, self["To"])) except KeyError: self["To"] = address *************** *** 525,533 **** break to = self["To"] ! ham = "%s@spambayes.invalid;" % \ (options["Headers", "header_ham_string"],) ! spam = "%s@spambayes.invalid;" % \ (options["Headers", "header_spam_string"],) ! unsure = "%s@spambayes.invalid;" % \ (options["Headers", "header_unsure_string"],) if options["Headers", "notate_to"]: --- 539,547 ---- break to = self["To"] ! ham = "%s@spambayes.invalid," % \ (options["Headers", "header_ham_string"],) ! spam = "%s@spambayes.invalid," % \ (options["Headers", "header_spam_string"],) ! unsure = "%s@spambayes.invalid," % \ (options["Headers", "header_unsure_string"],) if options["Headers", "notate_to"]: From anadelonbrin at users.sourceforge.net Fri Dec 17 02:37:07 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Fri Dec 17 02:37:09 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 msgstore.py,1.94,1.95 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1913/Outlook2000 Modified Files: msgstore.py Log Message: The organisation clue I found doesn't seem to be standard, so remove it. Index: msgstore.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v retrieving revision 1.94 retrieving revision 1.95 diff -C2 -d -r1.94 -r1.95 *** msgstore.py 16 Dec 2004 16:20:23 -0000 1.94 --- msgstore.py 17 Dec 2004 01:37:04 -0000 1.95 *************** *** 31,35 **** MYPR_MESSAGE_ID_A = 0x1035001E # more magic (message id field used for Exchange) MYPR_VERSION_ID = 0x7E8FFFFD # magic that I think tells us the Outlook version - MYPR_ORGANISATION_A = 0x1037001E # I think this is the organisation magic CLEAR_READ_FLAG = 0x00000004 --- 31,34 ---- *************** *** 991,995 **** PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME, \ MYPR_MESSAGE_ID_A, PR_IMPORTANCE, PR_CLIENT_SUBMIT_TIME, \ - MYPR_ORGANISATION_A, # This property gives a 'The parameter is incorrect' error, for some # reason, as does, 0x7E8EFFE2, which I think is the 'pretty' version --- 990,993 ---- *************** *** 1007,1012 **** ("Importance", 6, False, self._format_importance), ("Date", 7, False, self._format_time), ! ("Organization", 8, True, None), ! # ("X-Mailer", 9, False, self._format_version), ): if potentially_large: --- 1005,1009 ---- ("Importance", 6, False, self._format_importance), ("Date", 7, False, self._format_time), ! # ("X-Mailer", 8, False, self._format_version), ): if potentially_large: From anadelonbrin at users.sourceforge.net Mon Dec 20 03:49:09 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 03:49:13 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_sb_imapfilter.py, 1.7, 1.8 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv25987/spambayes/test Modified Files: test_sb_imapfilter.py Log Message: Add another test case - maybe the fetch data has multiple parts, but the message numbers are the same. Index: test_sb_imapfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/test/test_sb_imapfilter.py,v retrieving revision 1.7 retrieving revision 1.8 diff -C2 -d -r1.7 -r1.8 *** test_sb_imapfilter.py 17 Dec 2004 00:37:21 -0000 1.7 --- test_sb_imapfilter.py 20 Dec 2004 02:49:06 -0000 1.8 *************** *** 494,497 **** --- 494,533 ---- self.assertEqual(data['2']["RFC822.HEADER"], headers) + # Another complicated one (like the previous, but the two message + # numbers are the same). + headers = 'Return-Path: \r\n' \ + 'X-Original-To: david@leinbach.name\r\n' \ + 'Delivered-To: dleinbac@mail2.majro.dhs.org\r\n' \ + 'Received: from cressida (unknown [70.70.206.137])' \ + '\r\n\tby mail2.majro.dhs.org (Postfix) with ESMTP' \ + 'id AAD451CD28E\r\n\tfor ; Mo' \ + 'n, 6 Dec 2004 11:49:41 -0800 (PST)\r\n' \ + 'From: "Erin Leinbach" \r\n' \ + 'To: "Dave" \r\n' \ + 'Subject: Goo goo dolls songs\r\n' \ + 'Date: Mon, 6 Dec 2004 11:51:07 -0800\r\n' \ + 'Message-ID: <000001c4dbcc$ed6188a0$6801a8c0@cre' \ + 'ssida>\r\nMIME-Version: 1.0\r\n' \ + 'Content-Type: text/plain;\r\n' \ + '\tcharset="Windows-1252"\r\n' \ + 'Content-Transfer-Encoding: 7bit\r\n' \ + 'X-Priority: 3 (Normal)\r\n' \ + 'X-MSMail-Priority: Normal\r\n' \ + 'X-Mailer: Microsoft Outlook, Build 10.0.2616\r\n' \ + 'Importance: Normal\r\n' \ + 'X-MimeOLE: Produced By Microsoft MimeOLE V6.00.29' \ + '00.2180\r\nX-Spambayes-Classification: ham\r\n' \ + 'X-Spambayes-MailId: 000001\r\n\r\n' + uid = '3086' + flags = '(\\Seen \\Deleted)' + response = [('5 (UID %s RFC822.HEADER {839}' % (uid,), headers), + ')', '5 (FLAGS %s)' % (flags,)] + data = self.imap.extract_fetch_data(response) + self.assertEqual(data['5']["message_number"], '5') + self.assertEqual(data['5']["FLAGS"], flags) + self.assertEqual(data['5']["UID"], uid) + self.assertEqual(data['5']["RFC822.HEADER"], headers) + + class IMAPMessageTest(BaseIMAPFilterTest): def setUp(self): From anadelonbrin at users.sourceforge.net Mon Dec 20 03:49:51 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 03:49:53 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_imapfilter.py,1.47,1.48 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv26085/scripts Modified Files: sb_imapfilter.py Log Message: Maybe the fetch data has multiple parts, but the message numbers are the same. Handle that correctly. Index: sb_imapfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_imapfilter.py,v retrieving revision 1.47 retrieving revision 1.48 diff -C2 -d -r1.47 -r1.48 *** sb_imapfilter.py 17 Dec 2004 00:38:45 -0000 1.47 --- sb_imapfilter.py 20 Dec 2004 02:49:47 -0000 1.48 *************** *** 387,391 **** msg_data = self._extract_fetch_data(msg) if msg_data: ! data[msg_data["message_number"]] = msg_data return data --- 387,396 ---- msg_data = self._extract_fetch_data(msg) if msg_data: ! # Maybe there are two about the same message number! ! num = msg_data["message_number"] ! if num in data: ! data[num].update(msg_data) ! else: ! data[num] = msg_data return data From anadelonbrin at users.sourceforge.net Mon Dec 20 04:33:12 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 04:33:15 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/docs troubleshooting.html, 1.24, 1.25 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/docs In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv675/Outlook2000/docs Modified Files: troubleshooting.html Log Message: Improve information about how to find the log. Index: troubleshooting.html =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/docs/troubleshooting.html,v retrieving revision 1.24 retrieving revision 1.25 diff -C2 -d -r1.24 -r1.25 *** troubleshooting.html 22 Oct 2004 00:06:44 -0000 1.24 --- troubleshooting.html 20 Dec 2004 03:33:08 -0000 1.25 *************** *** 287,292 **** If you are running the source code version, then please see README.txt in the Outlook2000 directory.
! If you are running the binary version, then the SpamBayes addin writes ! a log in your Windows temp directory.  This directory is generally \WINDOWS\TEMP for Windows 9x, or \Documents and --- 287,300 ---- If you are running the source code version, then please see README.txt in the Outlook2000 directory.
! If you are running the binary version, the simplest way to get hold of ! the most recent log is to: !
    !
  1. Open the SpamBayes Manager dialog (from the SpamBayes toolbar)
  2. !
  3. Click the Advanced tab.
  4. !
  5. Click the Diagnostics button.
  6. !
  7. Click the View log button.
  8. !
! To find the log manually, you'll need to find your Windows temp directory, ! into which the SpamBayes addin writes the log. This directory is generally \WINDOWS\TEMP for Windows 9x, or \Documents and From anadelonbrin at users.sourceforge.net Mon Dec 20 04:37:47 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 04:37:51 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 manager.py,1.100,1.101 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1418/Outlook2000 Modified Files: manager.py Log Message: Allow Outlook users to select their storage method in exactly the same way as other users can. This isn't exposed via the GUI, so few will notice, and just about everyone will continue on with the default (bsddb at the moment, still). Also add a wrapper for the ZODB (FileStorage) storage class. I've been using this for a while now, and it seems to work fine. Remove some checks that no longer need to be done (that the messageinfo db has the same length as the tokens db). This isn't the case now that we store info about classified messages, and was only to track down a (solved) problem, anyway. Index: manager.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/manager.py,v retrieving revision 1.100 retrieving revision 1.101 diff -C2 -d -r1.100 -r1.101 *** manager.py 3 Dec 2004 21:43:19 -0000 1.100 --- manager.py 20 Dec 2004 03:37:38 -0000 1.101 *************** *** 81,100 **** this_filename = os.path.abspath(sys.argv[0]) # See if we can use the new bsddb module. (The old one is unreliable # on Windows, so we don't use that) ! try: ! import bsddb3 as bsddb ! # bsddb3 is definitely not broken ! use_db = True ! except ImportError: ! # Not using the 3rd party bsddb3, so try the one in the std library try: import bsddb - use_db = hasattr(bsddb, "db") # This name is not in the old one. except ImportError: ! # No DB library at all! ! assert not hasattr(sys, "frozen"), \ ! "Don't build binary versions without bsddb!" ! use_db = False # This is a little bit of a hack . We are generally in a child --- 81,102 ---- this_filename = os.path.abspath(sys.argv[0]) + # Ensure that a bsddb module is available if we are frozen. # See if we can use the new bsddb module. (The old one is unreliable # on Windows, so we don't use that) ! if hasattr(sys, "frozen"): ! try: ! import bsddb3 ! except ImportError: ! bsddb3 = None try: import bsddb except ImportError: ! bsddb = None ! else: ! # This name is not in the old (bad) one. ! if not hasattr(bsddb, "db"): ! bsddb = None ! assert bsddb or bsddb3, \ ! "Don't build binary versions without bsddb!" # This is a little bit of a hack . We are generally in a child *************** *** 181,186 **** db_extension = None # for pychecker - overwritten by subclass def __init__(self, bayes_base_name, mdb_base_name): ! self.bayes_filename = bayes_base_name + self.db_extension ! self.mdb_filename = mdb_base_name + self.db_extension def new_bayes(self): # Just delete the file and do an "open" --- 183,190 ---- db_extension = None # for pychecker - overwritten by subclass def __init__(self, bayes_base_name, mdb_base_name): ! self.bayes_filename = bayes_base_name.encode(filesystem_encoding) + \ ! self.db_extension ! self.mdb_filename = mdb_base_name.encode(filesystem_encoding) + \ ! self.db_extension def new_bayes(self): # Just delete the file and do an "open" *************** *** 197,201 **** bayes.close() def open_mdb(self): ! return bayes_message.open_storage(self.mdb_filename, self.klass) def store_mdb(self, mdb): mdb.store() --- 201,209 ---- bayes.close() def open_mdb(self): ! # MessageInfo storage types may lag behind, so use pickle if the ! # matching type isn't available. ! if self.klass in bayes_message._storage_types.keys(): ! return bayes_message.open_storage(self.mdb_filename, self.klass) ! return bayes_message.open_storage(self.mdb_filename, "pickle") def store_mdb(self, mdb): mdb.store() *************** *** 214,222 **** db_extension = ".db" klass = "dbm" - def __init__(self, bayes_base_name, mdb_base_name): - self.bayes_filename = bayes_base_name.encode(filesystem_encoding) + \ - self.db_extension - self.mdb_filename = mdb_base_name.encode(filesystem_encoding) + \ - self.db_extension def new_mdb(self): try: --- 222,225 ---- *************** *** 228,231 **** --- 231,238 ---- return True # True means only changed records get actually written + class ZODBStorageManager(DBStorageManager): + db_extension = ".fs" + klass = "zodb" + # Encapsulates our entire classification database # This allows a couple of different "databases" to be open at once *************** *** 256,262 **** self.logger.LogDebug(0, "Bayes database initialized with " "%d spam and %d good messages" % (bayes.nspam, bayes.nham)) ! if len(message_db) != bayes.nham + bayes.nspam: ! print "*** - message database has %d messages - bayes has %d - something is screwey" % \ ! (len(message_db), bayes.nham + bayes.nspam) self.bayes = bayes self.message_db = message_db --- 263,272 ---- self.logger.LogDebug(0, "Bayes database initialized with " "%d spam and %d good messages" % (bayes.nspam, bayes.nham)) ! # Once, we checked that the message database was the same length ! # as the training database here. However, we now store information ! # about messages that are classified but not trained in the message ! # database, so the lengths will not be equal (unless all messages ! # are trained). That step doesn't really gain us anything, anyway, ! # since it no longer would tell us useful information, so remove it. self.bayes = bayes self.message_db = message_db *************** *** 288,296 **** start = time.clock() bayes = self.bayes - # Try and work out where this count sometimes goes wrong. - if bayes.nspam + bayes.nham != len(self.message_db): - print "WARNING: Bayes database has %d messages, " \ - "but training database has %d" % \ - (bayes.nspam + bayes.nham, len(self.message_db)) if self.logger.verbose: --- 298,301 ---- *************** *** 328,332 **** def GetStorageManagerClass(): ! return [PickleStorageManager, DBStorageManager][use_db] # Our main "bayes manager" --- 333,353 ---- def GetStorageManagerClass(): ! # We used to enforce this so that all binary users used bsddb, and ! # unless they modified the source, so would all source users. We ! # would like more flexibility now, so we match what the rest of the ! # applications do - this isn't exposed via the GUI, so Outlook users ! # still get bsddb by default, and have to fiddle with a text file ! # to change that. ! use_db = bayes_options["Storage", "persistent_use_database"] ! available = {"pickle" : PickleStorageManager, ! "dbm" : DBStorageManager, ! "zodb" : ZODBStorageManager, ! } ! if use_db not in available: ! # User is trying to use something fancy which isn't available. ! # Fall back on bsddb. ! print use_db, "storage type not available. Using bsddb." ! use_db = "dbm" ! return available[use_db] # Our main "bayes manager" From anadelonbrin at users.sourceforge.net Mon Dec 20 04:40:30 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 04:40:33 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py,1.142,1.143 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1870/Outlook2000 Modified Files: addin.py Log Message: Fix a bug that prevented the "show clues" message working with messages that hadn't been classified. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.142 retrieving revision 1.143 diff -C2 -d -r1.142 -r1.143 *** addin.py 6 Dec 2004 18:04:34 -0000 1.142 --- addin.py 20 Dec 2004 03:40:26 -0000 1.143 *************** *** 471,482 **** # people realise that it may not necessarily be the same, and will # help diagnosing any 'wrong' scoring reported. ! original_score = 100 * msgstore_message.GetField(\ mgr.config.general.field_score_name) ! if original_score >= mgr.config.filter.spam_threshold: ! original_class = "spam" ! elif original_score >= mgr.config.filter.unsure_threshold: ! original_class = "unsure" ! else: ! original_class = "good" push("
\n") if original_score is None: --- 471,484 ---- # people realise that it may not necessarily be the same, and will # help diagnosing any 'wrong' scoring reported. ! original_score = msgstore_message.GetField(\ mgr.config.general.field_score_name) ! if original_score is not None: ! original_score *= 100.0 ! if original_score >= mgr.config.filter.spam_threshold: ! original_class = "spam" ! elif original_score >= mgr.config.filter.unsure_threshold: ! original_class = "unsure" ! else: ! original_class = "good" push("
\n") if original_score is None: From anadelonbrin at users.sourceforge.net Mon Dec 20 04:45:42 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 04:45:45 2004 Subject: [Spambayes-checkins] spambayes/spambayes ProxyUI.py,1.45,1.45.4.1 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv2964/spambayes Modified Files: Tag: release_1_0-branch ProxyUI.py Log Message: Backport fix for cgi.escaping subject lines. Index: ProxyUI.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/ProxyUI.py,v retrieving revision 1.45 retrieving revision 1.45.4.1 diff -C2 -d -r1.45 -r1.45.4.1 *** ProxyUI.py 16 Mar 2004 05:08:31 -0000 1.45 --- ProxyUI.py 20 Dec 2004 03:45:39 -0000 1.45.4.1 *************** *** 329,333 **** else: h = self.html.reviewRow.headerValue.clone() ! h.text = text row.optionalHeadersValues += h --- 329,333 ---- else: h = self.html.reviewRow.headerValue.clone() ! h.text = cgi.escape(text) row.optionalHeadersValues += h From anadelonbrin at users.sourceforge.net Mon Dec 20 04:49:02 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 04:49:05 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs dialog_map.py, 1.39, 1.39.4.1 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv3466/Outlook2000/dialogs Modified Files: Tag: release_1_0-branch dialog_map.py Log Message: Backport fix for [ 1078923 ] Unicode support incomplete Index: dialog_map.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/dialog_map.py,v retrieving revision 1.39 retrieving revision 1.39.4.1 diff -C2 -d -r1.39 -r1.39.4.1 *** dialog_map.py 28 Apr 2004 22:30:13 -0000 1.39 --- dialog_map.py 20 Dec 2004 03:48:59 -0000 1.39.4.1 *************** *** 236,240 **** """ import os ! os.startfile(window.manager.data_directory) def ShowLog(window): """Opens the log file for the current SpamBayes session --- 236,242 ---- """ import os ! import sys ! filesystem_encoding = sys.getfilesystemencoding() ! os.startfile(window.manager.data_directory.encode(filesystem_encoding)) def ShowLog(window): """Opens the log file for the current SpamBayes session From anadelonbrin at users.sourceforge.net Mon Dec 20 04:53:58 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 04:54:00 2004 Subject: [Spambayes-checkins] spambayes/spambayes Dibbler.py, 1.13.4.1, 1.13.4.2 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4374/spambayes Modified Files: Tag: release_1_0-branch Dibbler.py Log Message: Backport fix for AUTH digest. Index: Dibbler.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Dibbler.py,v retrieving revision 1.13.4.1 retrieving revision 1.13.4.2 diff -C2 -d -r1.13.4.1 -r1.13.4.2 *** Dibbler.py 15 Oct 2004 06:05:33 -0000 1.13.4.1 --- Dibbler.py 20 Dec 2004 03:53:55 -0000 1.13.4.2 *************** *** 347,351 **** # RE to extract option="value" fields from # digest auth login field ! _login_splitter = re.compile('([a-zA-Z])+=(".*?"|.*?),?') def __init__(self, clientSocket, server, context): --- 347,351 ---- # RE to extract option="value" fields from # digest auth login field ! _login_splitter = re.compile('([a-zA-Z]+)=(".*?"|.*?),?') def __init__(self, clientSocket, server, context): *************** *** 631,634 **** --- 631,640 ---- unhashedDigest = "" if options.has_key("qop"): + # IE 6.0 doesn't give nc back correctly? + if not options["nc"]: + options["nc"] = "00000001" + # Firefox 1.0 doesn't give qop back correctly? + if not options["qop"]: + options["qop"] = "auth" unhashedDigest = "%s:%s:%s:%s:%s:%s" % \ (HA1, nonce, From anadelonbrin at users.sourceforge.net Mon Dec 20 05:09:33 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 05:09:35 2004 Subject: [Spambayes-checkins] spambayes/spambayes message.py, 1.49.4.5, 1.49.4.6 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv6978/spambayes Modified Files: Tag: release_1_0-branch message.py Log Message: Backport fix for setPaylod with Python 2.4. Index: message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/message.py,v retrieving revision 1.49.4.5 retrieving revision 1.49.4.6 diff -C2 -d -r1.49.4.5 -r1.49.4.6 *** message.py 22 Nov 2004 23:38:34 -0000 1.49.4.5 --- message.py 20 Dec 2004 04:09:28 -0000 1.49.4.6 *************** *** 243,254 **** # imapfilter has an example of this in action def setPayload(self, payload): ! prs = email.Parser.Parser() ! fp = StringIO.StringIO(payload) ! # this is kindof a hack, due to the fact that the parser creates a ! # new message object, and we already have the message object ! prs._parseheaders(self, fp) ! # we may want to do some header parsing error handling here ! # to try to extract important headers regardless of malformations ! prs._parsebody(self, fp) def setId(self, id): --- 243,252 ---- # imapfilter has an example of this in action def setPayload(self, payload): ! # Python 2.4 has a new email package, and the private functions ! # are gone. So the old method won't work. We do this much nicer ! # with 1.1, but we have to do something to get this to work for the ! # 1.0.x branch, so use a different ugly hack. ! new_me = email.message_from_string(payload, _class=Message) ! self.__dict__ = new_me.__dict__ def setId(self, id): *************** *** 343,348 **** Adds routines to add/remove headers for Spambayes''' ! def __init__(self): ! Message.__init__(self) def setIdFromPayload(self): --- 341,347 ---- Adds routines to add/remove headers for Spambayes''' ! def setPayload(self, payload): ! new_me = email.message_from_string(payload, _class=Message) ! self.__dict__ = new_me.__dict__ def setIdFromPayload(self): From anadelonbrin at users.sourceforge.net Mon Dec 20 05:12:09 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 05:12:13 2004 Subject: [Spambayes-checkins] spambayes CHANGELOG.txt,1.44.4.5,1.44.4.6 Message-ID: Update of /cvsroot/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7388 Modified Files: Tag: release_1_0-branch CHANGELOG.txt Log Message: Bring up to date. Index: CHANGELOG.txt =================================================================== RCS file: /cvsroot/spambayes/spambayes/CHANGELOG.txt,v retrieving revision 1.44.4.5 retrieving revision 1.44.4.6 diff -C2 -d -r1.44.4.5 -r1.44.4.6 *** CHANGELOG.txt 22 Nov 2004 23:49:32 -0000 1.44.4.5 --- CHANGELOG.txt 20 Dec 2004 04:12:05 -0000 1.44.4.6 *************** *** 1,4 **** --- 1,11 ---- [Note that all dates are in English, not American format - i.e. day/month/year] + Release 1.0.2 + ============= + Tony Meyer 17/12/2004 message.py: Change the deprecated function to work in Python 2.4. + Tony Meyer 06/12/2004 Fix [ 1078923 ] Unicode support incomplete + Tony Meyer 06/12/2004 Fix the regex that the auth digest used, and handle the auth digest responses tha IE 6.0 and Firefox 1.0 give. + Tony Meyer 29/11/2004 Subject lines are not cgi.escape()d in the web interface, which might cause errors - fixed. + Release 1.0.1 ============= From anadelonbrin at users.sourceforge.net Mon Dec 20 05:12:27 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 05:12:30 2004 Subject: [Spambayes-checkins] spambayes CHANGELOG.txt,1.49,1.50 Message-ID: Update of /cvsroot/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7410 Modified Files: CHANGELOG.txt Log Message: Bring up to date. Index: CHANGELOG.txt =================================================================== RCS file: /cvsroot/spambayes/spambayes/CHANGELOG.txt,v retrieving revision 1.49 retrieving revision 1.50 diff -C2 -d -r1.49 -r1.50 *** CHANGELOG.txt 22 Nov 2004 23:49:58 -0000 1.49 --- CHANGELOG.txt 20 Dec 2004 04:12:25 -0000 1.50 *************** *** 3,6 **** --- 3,38 ---- Release 1.1a1 ============= + Tony Meyer 20/12/2004 Allow Outlook users to select their storage method in exactly the same way as other users can. + Tony Meyer 20/12/2004 Outlook: add a wrapper for the ZODB (FileStorage) storage class. + Tony Meyer 20/12/2004 Outlook: Remove some checks that no longer need to be done (that the messageinfo db has the same length as the tokens db). + Tony Meyer 17/12/2004 message.py: Spit out a warning if the deprecated function is used. + Tony Meyer 17/12/2004 message.py: Change the deprecated function to work in Python 2.4. + Tony Meyer 17/12/2004 sb_imapfilter: Update extract_fetch_data to handle any number of literals/message numbers. + Kenny Pitt 17/12/2004 Outlook: Improve e-mail address formatting for fake Exchange headers so that it supports multiple addresses, some of which may be real Internet addresses. + Tony Meyer 16/12/2004 sb_imapfilter: compile re's and do this only once. + Tony Meyer 16/12/2004 sb_imapfilter: improve handing of extract_fetch_data. + Tony Meyer 16/12/2004 Outlook: Improve the faked up exchange headers that we generate if there are no Internet headers. + Kenny Pitt 16/12/2004 Outlook: Include Message-ID in fake Exchange headers. + Tony Meyer 08/12/2004 Improve test_storage.py and test_message.py. + Tony Meyer 08/12/2004 Add [ 848365 ] Remove subject annotations from message review page + Tony Meyer 08/12/2004 Add [ 1036970 ] Allow Outlook plugin to move ham to a designated folder + Tony Meyer 07/12/2004 Outlook: More detailed statistics in SpamBayes Manager. These roughly match the updated statistics in the sb_server Web UI. + Tony Meyer 06/12/2004 Fix the regex that the auth digest used, and handle the auth digest responses tha IE 6.0 and Firefox 1.0 give. + Tony Meyer 06/12/2004 Fix [ 1078923 ] Unicode support incomplete + Tony Meyer 06/12/2004 Outlook: Use PR_SENDER_NAME rather than PR_DISPLAY_NAME_A for the faked-up "From" header. + Kenny Pitt 04/12/2004 Add notification sound support as per patch #858925. + Tony Meyer 01/12/2004 When doing "setup.py sdist" an MD5 checksum and the size of the created archive(s) is printed to stdout. + Tony Meyer 29/11/2004 sb_server: Messages that did not have the required \r?\n\r?\n separator would just pass through spambayes unproxied. Now they are, as best as possible. + Tony Meyer 29/11/2004 Handle a message that does not have a proper separator in insert_exception_header. + Tony Meyer 29/11/2004 Change sb_server to use the centralised insert_exception_header code. + Tony Meyer 29/11/2004 Subject lines are not cgi.escape()d in the web interface, which might cause errors - fixed. + Tony Meyer 26/11/2004 Outlook: Stop using the deprecated access to the bayes database and use the manager.classifier_data directly. + Tony Meyer 26/11/2004 Outlook: Switch to using a spambayes.message.MessageInfo database rather than an Outlook specific one. + Tony Meyer 26/11/2004 Outlook: Save the current folder when doing a "delete as spam", because the message may not be in the folder it was when it was filtered, or it may not have been filtered, but we do really want to recover it to wherever it was last. + Tony Meyer 26/11/2004 Fix [ 1071319 ] Outlook plug in for IMAP boxes + Tony Meyer 26/11/2004 message.py: Handle loading an Outlook messageinfo database, and add a __len__ function to the messageinfo databases. + Tony Meyer 26/11/2004 message.py: The messageinfo db now needs messages to have a GetDBKey function to determine the key to store the message under. For non-Outlook message classes, this is just the same as getId(). + Tony Meyer 24/11/2004 Moved the cleanarch script to the utilities directory and added '.py' to the filename. + Tony Meyer 23/11/2004 Improve OE support code, mostly from [ 800671 ] Windows GUI for easy Outlook Express mailboxes training Tony Meyer 23/11/2004 message.py: Change MessageInfoBase's methods so that recording & retrieving a message are not private methods and are more clearly named. Tony Meyer 23/11/2004 message.py: Change so that the messageinfodb doesn't get created/opened on import, but rather through utility functions like those in spambayes.storage. From anadelonbrin at users.sourceforge.net Mon Dec 20 05:23:35 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 05:23:39 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 msgstore.py,1.95,1.96 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv9230/Outlook2000 Modified Files: msgstore.py Log Message: The version doesn't appear to be easily accessible in the message properties, so use a fixed one for the faked-up headers. Index: msgstore.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v retrieving revision 1.95 retrieving revision 1.96 diff -C2 -d -r1.95 -r1.96 *** msgstore.py 17 Dec 2004 01:37:04 -0000 1.95 --- msgstore.py 20 Dec 2004 04:23:33 -0000 1.96 *************** *** 30,34 **** MYPR_BODY_HTML_W = 0x1013001f # ditto MYPR_MESSAGE_ID_A = 0x1035001E # more magic (message id field used for Exchange) - MYPR_VERSION_ID = 0x7E8FFFFD # magic that I think tells us the Outlook version CLEAR_READ_FLAG = 0x00000004 --- 30,33 ---- *************** *** 989,997 **** prop_ids = PR_SUBJECT_A, PR_SENDER_NAME_A, PR_DISPLAY_TO_A, \ PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME, \ ! MYPR_MESSAGE_ID_A, PR_IMPORTANCE, PR_CLIENT_SUBMIT_TIME, \ ! # This property gives a 'The parameter is incorrect' error, for some ! # reason, as does, 0x7E8EFFE2, which I think is the 'pretty' version ! # number. Until that's figured out, we'll have to not get this. ! # MYPR_VERSION_ID hr, data = self.mapi_object.GetProps(prop_ids, 0) headers = ["X-Exchange-Message: true"] --- 988,992 ---- prop_ids = PR_SUBJECT_A, PR_SENDER_NAME_A, PR_DISPLAY_TO_A, \ PR_DISPLAY_CC_A, PR_MESSAGE_DELIVERY_TIME, \ ! MYPR_MESSAGE_ID_A, PR_IMPORTANCE, PR_CLIENT_SUBMIT_TIME, hr, data = self.mapi_object.GetProps(prop_ids, 0) headers = ["X-Exchange-Message: true"] *************** *** 1005,1009 **** ("Importance", 6, False, self._format_importance), ("Date", 7, False, self._format_time), ! # ("X-Mailer", 8, False, self._format_version), ): if potentially_large: --- 1000,1004 ---- ("Importance", 6, False, self._format_importance), ("Date", 7, False, self._format_time), ! ("X-Mailer", 7, False, self._format_version), ): if potentially_large: *************** *** 1033,1039 **** return {0 : "low", 1 : "normal", 2 : "high"}[raw] ! def _format_version(self, raw): ! # Data is just a version string, so prepend something to it. ! return "Exchange Client " + raw _address_re = re.compile(r"[()<>,:@!/=; ]") --- 1028,1033 ---- return {0 : "low", 1 : "normal", 2 : "high"}[raw] ! def _format_version(self, unused): ! return "Microsoft Exchange Client" _address_re = re.compile(r"[()<>,:@!/=; ]") From anadelonbrin at users.sourceforge.net Mon Dec 20 05:24:57 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 20 05:24:59 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py,1.143,1.144 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv9431/Outlook2000 Modified Files: addin.py Log Message: Round the original score, don't truncate - otherwise it looks like the scores don't match! Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.143 retrieving revision 1.144 diff -C2 -d -r1.143 -r1.144 *** addin.py 20 Dec 2004 03:40:26 -0000 1.143 --- addin.py 20 Dec 2004 04:24:54 -0000 1.144 *************** *** 485,488 **** --- 485,489 ---- push("This message has not been filtered.") else: + original_score = round(original_score) push("When this message was last filtered, it was classified " \ "as %s (it scored %d%%)." % (original_class, original_score)) From kpitt at users.sourceforge.net Mon Dec 20 16:42:34 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Mon Dec 20 16:42:38 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_server.py,1.31,1.32 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv10663 Modified Files: sb_server.py Log Message: On a RETR command, my POP3 server returns the message size following the "+OK" in the status line. That caused SpamBayes to think it was an error because the entire status line didn't exactly match "+OK". To prevent this, split off the first word of the status line and only compare that to "+OK". Index: sb_server.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_server.py,v retrieving revision 1.31 retrieving revision 1.32 diff -C2 -d -r1.31 -r1.32 *** sb_server.py 29 Nov 2004 00:17:58 -0000 1.31 --- sb_server.py 20 Dec 2004 15:42:17 -0000 1.32 *************** *** 474,478 **** # Break off the first line, which will be '+OK'. ! ok, messageText = response.split('\n', 1) if ok.strip().upper() != "+OK": # Must be an error response. Return unproxied. --- 474,479 ---- # Break off the first line, which will be '+OK'. ! statusLine, messageText = response.split('\n', 1) ! ok, statusRemainder = statusLine.split(None, 1) if ok.strip().upper() != "+OK": # Must be an error response. Return unproxied. From anadelonbrin at users.sourceforge.net Tue Dec 21 00:24:12 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Tue Dec 21 00:24:15 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_server.py,1.32,1.33 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv24135/scripts Modified Files: sb_server.py Log Message: Kenny's fix worked if there was more than +OK, but not if there was just +OK. Fix the fix so that both cases work. Index: sb_server.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_server.py,v retrieving revision 1.32 retrieving revision 1.33 diff -C2 -d -r1.32 -r1.33 *** sb_server.py 20 Dec 2004 15:42:17 -0000 1.32 --- sb_server.py 20 Dec 2004 23:24:03 -0000 1.33 *************** *** 457,461 **** """Adds the judgement header based on the raw headers and body of the message.""" ! # Previously, we used '\n\r?\n' to detect the end of the headers in # case of broken emails that don't use the proper line separators, # and if we couldn't find it, then we assumed that the response was --- 457,461 ---- """Adds the judgement header based on the raw headers and body of the message.""" ! # Previous, we used '\n\r?\n' to detect the end of the headers in # case of broken emails that don't use the proper line separators, # and if we couldn't find it, then we assumed that the response was *************** *** 475,479 **** # Break off the first line, which will be '+OK'. statusLine, messageText = response.split('\n', 1) ! ok, statusRemainder = statusLine.split(None, 1) if ok.strip().upper() != "+OK": # Must be an error response. Return unproxied. --- 475,480 ---- # Break off the first line, which will be '+OK'. statusLine, messageText = response.split('\n', 1) ! statusData = statusLine.split() ! ok = statusData[0] if ok.strip().upper() != "+OK": # Must be an error response. Return unproxied. From anadelonbrin at users.sourceforge.net Tue Dec 21 00:26:38 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Tue Dec 21 00:26:42 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_sb_server.py, 1.3, 1.4 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv24839/spambayes/test Modified Files: test_sb_server.py Log Message: If run with no options, run the test (like the other scripts - I always forget to use -z). Help is still available with -h. Modify the server a little to test that servers that return the size of messages with the +OK in a RETR will work. Index: test_sb_server.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/test/test_sb_server.py,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** test_sb_server.py 29 Nov 2004 00:11:47 -0000 1.3 --- test_sb_server.py 20 Dec 2004 23:26:33 -0000 1.4 *************** *** 3,7 **** """Test the POP3 proxy is working correctly. ! When using the -z command line option, carries out a test that the POP3 proxy can be connected to, that incoming mail is classified, that pipelining is removed from the CAPA[bility] query, and that the --- 3,7 ---- """Test the POP3 proxy is working correctly. ! Given no command line options, carries out a test that the POP3 proxy can be connected to, that incoming mail is classified, that pipelining is removed from the CAPA[bility] query, and that the *************** *** 17,26 **** options: - -z : Runs a self-test and exits. -t : Runs a fake POP3 server on port 8110 (for testing). -h : Displays this help message. """ ! # This module is part of the spambayes project, which is Copyright 2002 # The Python Software Foundation and is covered by the Python Software # Foundation license. --- 17,25 ---- options: -t : Runs a fake POP3 server on port 8110 (for testing). -h : Displays this help message. """ ! # This module is part of the spambayes project, which is Copyright 2002-5 # The Python Software Foundation and is covered by the Python Software # Foundation license. *************** *** 77,80 **** --- 76,83 ---- """ + # An example of a particularly nasty malformed message - where there is + # no body, and no separator, which would at one point slip through + # SpamBayes. This is an example that Tony made up. + malformed1 = """From: ta-meyer@ihug.co.nz Subject: No body, and no separator""" *************** *** 225,229 **** headers, body = message.split('\n\n', 1) except ValueError: ! return "+OK\r\n%s\r\n.\r\n" % message bodyLines = body.split('\n')[:maxLines] message = headers + '\r\n\r\n' + '\n'.join(bodyLines) --- 228,233 ---- headers, body = message.split('\n\n', 1) except ValueError: ! return "+OK %d octets\r\n%s\r\n.\r\n" % (len(message), ! message) bodyLines = body.split('\n')[:maxLines] message = headers + '\r\n\r\n' + '\n'.join(bodyLines) *************** *** 364,373 **** # Read the arguments. try: ! opts, args = getopt.getopt(sys.argv[1:], 'htz') except getopt.error, msg: print >>sys.stderr, str(msg) + '\n\n' + __doc__ sys.exit() ! runSelfTest = False for opt, arg in opts: if opt == '-h': --- 368,378 ---- # Read the arguments. try: ! opts, args = getopt.getopt(sys.argv[1:], 'ht') except getopt.error, msg: print >>sys.stderr, str(msg) + '\n\n' + __doc__ sys.exit() ! state.isTest = True ! runSelfTest = True for opt, arg in opts: if opt == '-h': *************** *** 377,383 **** state.isTest = True state.runTestServer = True ! elif opt == '-z': ! state.isTest = True ! runSelfTest = True state.createWorkers() --- 382,386 ---- state.isTest = True state.runTestServer = True ! runSelfTest = False state.createWorkers() *************** *** 394,399 **** asyncore.loop() - else: - print >>sys.stderr, __doc__ if __name__ == '__main__': --- 397,400 ---- From kpitt at users.sourceforge.net Tue Dec 21 17:22:03 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Tue Dec 21 17:22:06 2004 Subject: [Spambayes-checkins] spambayes/spambayes Stats.py,1.9,1.10 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv10875 Modified Files: Stats.py Log Message: A couple of recent changes to the definition of the MessageInfoBase class apparently didn't get propagated to the usages in the Stats class. Index: Stats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Stats.py,v retrieving revision 1.9 retrieving revision 1.10 diff -C2 -d -r1.9 -r1.10 *** Stats.py 22 Nov 2004 23:34:43 -0000 1.9 --- Stats.py 21 Dec 2004 16:21:59 -0000 1.10 *************** *** 44,47 **** --- 44,49 ---- class Stats(object): class __empty_msg: + def __init__(self): + self.getDBKey = self.getId def getId(self): return self.id *************** *** 70,74 **** m = self.__empty_msg() m.id = msg ! msginfoDB._getState(m) if m.c == 's': # Classified as spam. --- 72,76 ---- m = self.__empty_msg() m.id = msg ! msginfoDB.load_msg(m) if m.c == 's': # Classified as spam. From anadelonbrin at users.sourceforge.net Tue Dec 21 22:30:16 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Tue Dec 21 22:30:20 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_stats.py, NONE, 1.1 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv22111/spambayes/test Added Files: test_stats.py Log Message: Unittests for the way the stats class is now going to work. --- NEW FILE: test_stats.py --- # Test spambayes.Stats module. import os import sys import time import unittest import sb_test_support sb_test_support.fix_sys_path() from spambayes.Stats import Stats from spambayes.Options import options from spambayes.message import MessageInfoPickle, Message class StatsTest(unittest.TestCase): def setUp(self): self.s_cut = options["Categorization", "spam_cutoff"] self.h_cut = options["Categorization", "ham_cutoff"] self.h_string = options["Headers", "header_ham_string"] self.u_string = options["Headers", "header_unsure_string"] self.s_string = options["Headers", "header_spam_string"] self.messageinfo_db_name = "__unittest.pik" self.messageinfo_db = MessageInfoPickle(self.messageinfo_db_name) self.s = Stats(self.s_cut, self.h_cut, self.messageinfo_db, self.h_string, self.u_string, self.s_string) def tearDown(self): if os.path.exists(self.messageinfo_db_name): os.remove(self.messageinfo_db_name) def test_from_date_unset(self): self.assertEqual(None, self.s.from_date) def test_set_date(self): now = time.time() self.s.ResetTotal(permanently=True) self.assertEqual(now, self.s.from_date) for stat in ["num_ham", "num_spam", "num_unsure", "num_trained_spam", "num_trained_spam_fn", "num_trained_ham", "num_trained_ham_fp",]: self.assertEqual(self.s.totals[stat], 0) # Check that it was stored, too. self.messageinfo_db.close() self.messageinfo_db = MessageInfoPickle(self.messageinfo_db_name) self.s = Stats(self.s_cut, self.h_cut, self.messageinfo_db, self.h_string, self.u_string, self.s_string) self.assertEqual(now, self.s.from_date) def test_no_messages(self): self.assertEqual(self.s.GetStats(), ["Messages classified: 0"]) def test_reset_session(self): self.s.RecordClassification(.2) self.s.RecordClassification(.1) self.s.RecordClassification(.4) self.s.RecordClassification(.91) self.s.RecordTraining(True, 0.1) self.s.RecordTraining(True, 0.91) self.s.RecordTraining(False, 0.1) self.s.RecordTraining(False, 0.91) self.assertNotEqual(self.s.num_ham, 0) self.assertNotEqual(self.s.num_spam, 0) self.assertNotEqual(self.s.num_unsure, 0) self.assertNotEqual(self.s.num_trained_spam, 0) self.assertNotEqual(self.s.num_trained_spam_fn, 0) self.assertNotEqual(self.s.num_trained_ham, 0) self.assertNotEqual(self.s.num_trained_ham_fp, 0) self.s.Reset() self.assertEqual(self.s.num_ham, 0) self.assertEqual(self.s.num_spam, 0) self.assertEqual(self.s.num_unsure, 0) self.assertEqual(self.s.num_trained_spam, 0) self.assertEqual(self.s.num_trained_spam_fn, 0) self.assertEqual(self.s.num_trained_ham, 0) self.assertEqual(self.s.num_trained_ham_fp, 0) def test_record_ham(self): self.s.RecordClassification(0.0) self.assertEqual(self.s.num_ham, 1) self.s.RecordClassification(0.0) self.assertEqual(self.s.num_ham, 2) def test_record_spam(self): self.s.RecordClassification(1.0) self.assertEqual(self.s.num_spam, 1) self.s.RecordClassification(1.0) self.assertEqual(self.s.num_spam, 2) def test_record_unsure(self): self.s.RecordClassification(0.5) self.assertEqual(self.s.num_unsure, 1) self.s.RecordClassification(0.5) self.assertEqual(self.s.num_unsure, 2) def test_record_fp(self): self.s.RecordTraining(True, 1.0) self.assertEqual(self.s.num_trained_ham, 1) self.assertEqual(self.s.num_trained_ham_fp, 1) def test_record_fn(self): self.s.RecordTraining(False, 0.0) self.assertEqual(self.s.num_trained_spam, 1) self.assertEqual(self.s.num_trained_spam_fn, 1) def test_record_train_spam(self): self.s.RecordTraining(False, 1.0) self.assertEqual(self.s.num_trained_spam, 1) self.assertEqual(self.s.num_trained_spam_fn, 0) def test_record_train_ham(self): self.s.RecordTraining(True, 0.0) self.assertEqual(self.s.num_trained_ham, 1) self.assertEqual(self.s.num_trained_ham_fp, 0) def test_calculate_persistent_stats(self): # Make sure it is empty to start with. for stat in ["num_ham", "num_spam", "num_unsure", "num_trained_spam", "num_trained_spam_fn", "num_trained_ham", "num_trained_ham_fp",]: self.assertEqual(self.s.totals[stat], 0) # Stuff some things in to calculate. msg = Message('0', self.messageinfo_db) msg.RememberTrained(True) msg.RememberClassification(options['Headers','header_spam_string']) msg = Message('1', self.messageinfo_db) msg.RememberTrained(False) msg.RememberClassification(options['Headers','header_spam_string']) msg = Message('2', self.messageinfo_db) msg.RememberTrained(True) msg.RememberClassification(options['Headers','header_ham_string']) msg = Message('3', self.messageinfo_db) msg.RememberTrained(False) msg.RememberClassification(options['Headers','header_ham_string']) msg = Message('4', self.messageinfo_db) msg.RememberClassification(options['Headers','header_ham_string']) msg = Message('5', self.messageinfo_db) msg.RememberTrained(False) msg.RememberClassification(options['Headers','header_unsure_string']) msg = Message('6', self.messageinfo_db) msg.RememberTrained(True) msg.RememberClassification(options['Headers','header_unsure_string']) msg = Message('7', self.messageinfo_db) msg.RememberClassification(options['Headers','header_unsure_string']) msg = Message('8', self.messageinfo_db) msg.RememberClassification(options['Headers','header_unsure_string']) self.s.CalculatePersistentStats() self.assertEqual(self.s.totals["num_ham"], 3) self.assertEqual(self.s.totals["num_spam"], 2) self.assertEqual(self.s.totals["num_unsure"], 4) self.assertEqual(self.s.totals["num_trained_spam"], 1) self.assertEqual(self.s.totals["num_trained_spam_fn"], 1) self.assertEqual(self.s.totals["num_trained_ham"], 1) self.assertEqual(self.s.totals["num_trained_ham_fp"], 1) def test_CalculateAdditional(self): data = {} data["num_seen"] = 45 data["num_ham"] = 23 data["num_spam"] = 10 data["num_unsure"] = 12 data["num_trained_spam_fn"] = 4 data["num_trained_ham_fp"] = 3 data["num_trained_ham"] = 7 data["num_trained_spam"] = 5 data["num_unsure_trained_ham"] = 2 data["num_unsure_trained_spam"] = 1 new_data = self.s._CalculateAdditional(data) self.assertEqual(new_data["perc_ham"], 100.0 * data["num_ham"] / data["num_seen"]) self.assertEqual(new_data["perc_spam"], 100.0 * data["num_spam"] / data["num_seen"]) self.assertEqual(new_data["perc_unsure"], 100.0 * data["num_unsure"] / data["num_seen"]) self.assertEqual(new_data["num_ham_correct"], data["num_ham"] - data["num_trained_spam_fn"]) self.assertEqual(new_data["num_spam_correct"], data["num_spam"] - data["num_trained_ham_fp"]) self.assertEqual(new_data["num_correct"], new_data["num_ham_correct"] + new_data["num_spam_correct"]) self.assertEqual(new_data["num_incorrect"], data["num_trained_spam_fn"] + data["num_trained_ham_fp"]) self.assertEqual(new_data["perc_correct"], 100.0 * new_data["num_correct"] / data["num_seen"]) self.assertEqual(new_data["perc_incorrect"], 100.0 * new_data["num_incorrect"] / data["num_seen"]) self.assertEqual(new_data["perc_fp"], 100.0 * data["num_trained_ham_fp"] / data["num_seen"]) self.assertEqual(new_data["perc_fn"], 100.0 * data["num_trained_spam_fn"] / data["num_seen"]) self.assertEqual(new_data["num_unsure_trained_ham"], data["num_trained_ham"] - data["num_trained_ham_fp"]) self.assertEqual(new_data["num_unsure_trained_spam"], data["num_trained_spam"] - data["num_trained_spam_fn"]) self.assertEqual(new_data["num_unsure_not_trained"], data["num_unsure"] - data["num_unsure_trained_ham"] - data["num_unsure_trained_spam"]) self.assertEqual(new_data["perc_unsure_trained_ham"], 100.0 * data["num_unsure_trained_ham"] / data["num_unsure"]) self.assertEqual(new_data["perc_unsure_trained_spam"], 100.0 * data["num_unsure_trained_spam"] / data["num_unsure"]) self.assertEqual(new_data["perc_unsure_not_trained"], 100.0 * new_data["num_unsure_not_trained"] / data["num_unsure"]) self.assertEqual(new_data["total_ham"], new_data["num_ham_correct"] + data["num_trained_ham"]) self.assertEqual(new_data["total_spam"], new_data["num_spam_correct"] + data["num_trained_spam"]) self.assertEqual(new_data["perc_ham_incorrect"], 100.0 * data["num_trained_ham_fp"] / data["total_ham"]) self.assertEqual(new_data["perc_ham_unsure"], 100.0 * data["num_unsure_trained_ham"] / data["total_ham"]) self.assertEqual(new_data["perc_ham_incorrect_or_unsure"], 100.0 * (data["num_trained_ham_fp"] + data["num_unsure_trained_ham"]) / data["total_ham"]) self.assertEqual(new_data["perc_spam_correct"], 100.0 * data["num_spam_correct"] / data["total_spam"]) self.assertEqual(new_data["perc_spam_unsure"], 100.0 * data["num_unsure_trained_spam"] / data["total_spam"]) self.assertEqual(new_data["perc_spam_correct_or_unsure"], 100.0 * (data["num_spam_correct"] + data["num_unsure_trained_spam"]) / data["total_spam"]) def test_AddPercentStrings(self): for i in xrange(10): self._test_AddPercentStrings(i) def _test_AddPercentStrings(self, dp): data = self.s._AddPercentStrings({}, dp) self.assertEqual(data["perc_ham_s"], "%%(perc_ham).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_spam_s"], "%%(perc_spam).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_unsure_s"], "%%(perc_unsure).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_correct_s"], "%%(perc_correct).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_incorrect_s"], "%%(perc_incorrect).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_fp_s"], "%%(perc_fp).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_fn_s"], "%%(perc_fn).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_spam_correct_s"], "%%(perc_spam_correct).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_spam_unsure_s"], "%%(perc_spam_unsure).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_spam_correct_or_unsure_s"], "%%(perc_spam_correct_or_unsure).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_ham_incorrect_s"], "%%(perc_ham_incorrect).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_ham_unsure_s"], "%%(perc_ham_unsure).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_ham_incorrect_or_unsure_s"], "%%(perc_ham_incorrect_or_unsure).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_unsure_trained_ham_s"], "%%(perc_unsure_trained_ham).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_unsure_trained_spam_s"], "%%(perc_unsure_trained_spam).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc_unsure_not_trained_s"], "%%(perc_unsure_not_trained).%df%%(perc)s" % (dp,)) self.assertEqual(data["perc"], "%") def _test_no_recovery(self, score, one, two, three, five): self.s.RecordClassification(score) s = self.s.GetStats() self.assertEqual(s[0], "Messages classified: 1") self.assertEqual(s[1], one) self.assertEqual(s[2], two) self.assertEqual(s[3], three) self.assertEqual(s[4], "") self.assertEqual(s[5], five) self.assertEqual(s[6], "Classified incorrectly:\t0 (0.0% of total)") self.assertEqual(s[7], "") self.assertEqual(s[8], "Manually classified as good:\t0") self.assertEqual(s[9], "Manually classified as spam:\t0") self.assertEqual(s[10], "") if self.h_cut <= score < self.s_cut: self.assertEqual(s[11], "Unsures trained as good:\t0 (0.0% of unsures)") self.assertEqual(s[12], "Unsures trained as spam:\t0 (0.0% of unsures)") self.assertEqual(s[13], "Unsures not trained:\t\t1 (100.0% of unsures)") self.assertEqual(s[14], "") counter = 15 else: counter = 11 try: return s[counter] except IndexError: return def test_no_recovery_unsure(self): score = 0.2 one = "\tGood:\t0 (0.0%)" two = "\tSpam:\t0 (0.0%)" three = "\tUnsure:\t1 (100.0%)" five = "Classified correctly:\t0 (0.0% of total)" self._test_no_recovery(score, one, two, three, five) def test_no_recovery_ham(self): score = 0.0 one = "\tGood:\t1 (100.0%)" two = "\tSpam:\t0 (0.0%)" three = "\tUnsure:\t0 (0.0%)" five = "Classified correctly:\t1 (100.0% of total)" final = self._test_no_recovery(score, one, two, three, five) self.assertEqual(final, "Good incorrectly identified:\t0.0% (+ 0.0% unsure)") def test_no_recovery_spam(self): score = 1.0 one = "\tGood:\t0 (0.0%)" two = "\tSpam:\t1 (100.0%)" three = "\tUnsure:\t0 (0.0%)" five = "Classified correctly:\t1 (100.0% of total)" final = self._test_no_recovery(score, one, two, three, five) self.assertEqual(final, "Spam correctly identified:\t100.0% (+ 0.0% unsure)") def test_get_stats_session_only(self): # Record some session data. self.s.RecordClassification(0.0) self.s.RecordClassification(0.2) self.s.RecordClassification(0.1) self.s.RecordClassification(0.4) self.s.RecordClassification(0.5) self.s.RecordClassification(0.95) self.s.RecordClassification(1.0) self.s.RecordTraining(True, 0.1) self.s.RecordTraining(True, 1.0) self.s.RecordTraining(False, 0.1) self.s.RecordTraining(False, 1.0) # Put data into the totals, to ensure that we only get back the # session data. for stat in ["num_ham", "num_spam", "num_unsure", "num_trained_spam", "num_trained_spam_fn", "num_trained_ham", "num_trained_ham_fp",]: self.s.totals[stat] = 4 s = self.s.GetStats(session_only=True) self.assertEqual(s[0], "Messages classified: 7") self.assertEqual(s[1], "\tGood:\t2 (28.6%)") self.assertEqual(s[2], "\tSpam:\t2 (28.6%)") self.assertEqual(s[3], "\tUnsure:\t3 (42.9%)") self.assertEqual(s[4], "") self.assertEqual(s[5], "Classified correctly:\t2 (28.6% of total)") self.assertEqual(s[6], "Classified incorrectly:\t2 (28.6% of total)") self.assertEqual(s[7], "\tFalse positives:\t1 (14.3% of total)") self.assertEqual(s[8], "\tFalse negatives:\t1 (14.3% of total)") self.assertEqual(s[9], "") self.assertEqual(s[10], "Manually classified as good:\t2") self.assertEqual(s[11], "Manually classified as spam:\t2") self.assertEqual(s[12], "") self.assertEqual(s[13], "Unsures trained as good:\t1 (33.3% of unsures)") self.assertEqual(s[14], "Unsures trained as spam:\t1 (33.3% of unsures)") self.assertEqual(s[15], "Unsures not trained:\t\t1 (33.3% of unsures)") self.assertEqual(s[16], "") self.assertEqual(s[17], "Spam correctly identified:\t33.3% (+ 33.3% unsure)") self.assertEqual(s[18], "Good incorrectly identified:\t33.3% (+ 33.3% unsure)") self.assertEqual(len(s), 19) def test_get_all_stats(self): s = self._stuff_with_data() self.assertEqual(s[0], "Messages classified: 16") self.assertEqual(s[1], "\tGood:\t5 (31.3%)") self.assertEqual(s[2], "\tSpam:\t4 (25.0%)") self.assertEqual(s[3], "\tUnsure:\t7 (43.8%)") self.assertEqual(s[4], "") self.assertEqual(s[5], "Classified correctly:\t5 (31.3% of total)") self.assertEqual(s[6], "Classified incorrectly:\t4 (25.0% of total)") self.assertEqual(s[7], "\tFalse positives:\t2 (12.5% of total)") self.assertEqual(s[8], "\tFalse negatives:\t2 (12.5% of total)") self.assertEqual(s[9], "") self.assertEqual(s[10], "Manually classified as good:\t3") self.assertEqual(s[11], "Manually classified as spam:\t3") self.assertEqual(s[12], "") self.assertEqual(s[13], "Unsures trained as good:\t1 (14.3% of unsures)") self.assertEqual(s[14], "Unsures trained as spam:\t1 (14.3% of unsures)") self.assertEqual(s[15], "Unsures not trained:\t\t5 (71.4% of unsures)") self.assertEqual(s[16], "") self.assertEqual(s[17], "Spam correctly identified:\t40.0% (+ 20.0% unsure)") self.assertEqual(s[18], "Good incorrectly identified:\t33.3% (+ 16.7% unsure)") self.assertEqual(len(s), 19) def _stuff_with_data(self, use_html=False): # Record some session data. self.s.RecordClassification(0.0) self.s.RecordClassification(0.2) self.s.RecordClassification(0.1) self.s.RecordClassification(0.4) self.s.RecordClassification(0.5) self.s.RecordClassification(0.95) self.s.RecordClassification(1.0) self.s.RecordTraining(True, 0.1) self.s.RecordTraining(True, 1.0) self.s.RecordTraining(False, 0.1) self.s.RecordTraining(False, 1.0) # Put data into the totals. msg = Message('0', self.messageinfo_db) msg.RememberTrained(True) msg.RememberClassification(options['Headers','header_spam_string']) msg = Message('1', self.messageinfo_db) msg.RememberTrained(False) msg.RememberClassification(options['Headers','header_spam_string']) msg = Message('2', self.messageinfo_db) msg.RememberTrained(True) msg.RememberClassification(options['Headers','header_ham_string']) msg = Message('3', self.messageinfo_db) msg.RememberTrained(False) msg.RememberClassification(options['Headers','header_ham_string']) msg = Message('4', self.messageinfo_db) msg.RememberClassification(options['Headers','header_ham_string']) msg = Message('5', self.messageinfo_db) msg.RememberTrained(False) msg.RememberClassification(options['Headers','header_unsure_string']) msg = Message('6', self.messageinfo_db) msg.RememberTrained(True) msg.RememberClassification(options['Headers','header_unsure_string']) msg = Message('7', self.messageinfo_db) msg.RememberClassification(options['Headers','header_unsure_string']) msg = Message('8', self.messageinfo_db) msg.RememberClassification(options['Headers','header_unsure_string']) self.s.CalculatePersistentStats() return self.s.GetStats(use_html=use_html) def test_with_html(self): s = self._stuff_with_data(True) for line in s: self.assert_('\t' not in line) def test_without_html(self): s = self._stuff_with_data(False) for line in s: self.assert_(' ' not in line) def suite(): suite = unittest.TestSuite() for cls in (StatsTest, ): suite.addTest(unittest.makeSuite(cls)) return suite if __name__=='__main__': sb_test_support.unittest_main(argv=sys.argv + ['suite']) From anadelonbrin at users.sourceforge.net Tue Dec 21 22:37:10 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Tue Dec 21 22:37:14 2004 Subject: [Spambayes-checkins] spambayes/spambayes UserInterface.py, 1.48, 1.49 message.py, 1.62, 1.63 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv23974/spambayes Modified Files: UserInterface.py message.py Log Message: Store a stats start date in the message database, so that we can 'reset' the statistics without removing the whole message database. If the database grows too large now, we can also age out data from it if we want to. Allow message.Messages to have their id specified on creation. Also allow specification of the messageinfo_db, which means one object can be shared rather than each message creating it's own one. Only the test suite uses this at the moment, but other code should, too. Update a few comments in UserInterface.py and use keyword args when calling the Stats. Index: UserInterface.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/UserInterface.py,v retrieving revision 1.48 retrieving revision 1.49 diff -C2 -d -r1.48 -r1.49 *** UserInterface.py 10 Aug 2004 14:20:24 -0000 1.48 --- UserInterface.py 21 Dec 2004 21:37:06 -0000 1.49 *************** *** 503,506 **** --- 503,512 ---- # the database later. This is a temporary implementation - # it should keep a Corpus of trained messages. + # XXX Temporary, heh. One of the problems with this is that + # XXX these files get opened in whatever happens to be the cwd. + # XXX I don't think anyone uses these anyway, but we should fix + # XXX this for 1.1. I think that creating a new message in the + # XXX Ham/Spam corpus would work, and not interfere with anything. + # XXX We could later search for them, too, which would be a bonus. if isSpam: f = open("_pop3proxyspam.mbox", "a") *************** *** 512,515 **** --- 518,524 ---- self.flush() for message in messages: + # XXX Here, we should really use the message.Message class, + # XXX so that the messageinfo database is updated (and so + # XXX the stats are correct, and so on). tokens = tokenizer.tokenize(message) self.classifier.learn(tokens, isSpam) *************** *** 913,919 **** # rather than regenerating it every time. If people complain # about it being too slow, then do this! s = Stats.Stats() self._writePreamble("Statistics") ! stats = s.GetStats() stats = self._buildBox("Statistics", None, "

".join(stats)) self.write(stats) --- 922,930 ---- # rather than regenerating it every time. If people complain # about it being too slow, then do this! + # XXX The Stats object should be generated once, when we start up, + # XXX and then just called, here. s = Stats.Stats() self._writePreamble("Statistics") ! stats = s.GetStats(use_html=True) stats = self._buildBox("Statistics", None, "

".join(stats)) self.write(stats) Index: message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/message.py,v retrieving revision 1.62 retrieving revision 1.63 diff -C2 -d -r1.62 -r1.63 *** message.py 17 Dec 2004 01:23:36 -0000 1.62 --- message.py 21 Dec 2004 21:37:06 -0000 1.63 *************** *** 83,86 **** --- 83,87 ---- import sys import types + import time import math import re *************** *** 111,114 **** --- 112,117 ---- CRLF_RE = re.compile(r'\r\n|\r|\n') + STATS_START_KEY = "Statistics start date" + class MessageInfoBase(object): def __init__(self, db_name): *************** *** 118,121 **** --- 121,134 ---- return len(self.db) + def get_statistics_start_date(self): + if STATS_START_KEY in self.db: + return self.db[STATS_START_KEY] + else: + return None + + def set_statistics_start_date(self, date): + self.db[STATS_START_KEY] = date + self.store() + def load_msg(self, msg): if self.db is not None: *************** *** 150,154 **** return else: ! print >> sys.stderr, "Unknown message info type" sys.exit(1) for att, val in attributes: --- 163,168 ---- return else: ! print >> sys.stderr, "Unknown message info type", \ ! attributes sys.exit(1) for att, val in attributes: *************** *** 157,161 **** def store_msg(self, msg): if self.db is not None: ! attributes = [] for att in msg.stored_attributes: attributes.append((att, getattr(msg, att))) --- 171,175 ---- def store_msg(self, msg): if self.db is not None: ! attributes = [("date_modified", time.time())] for att in msg.stored_attributes: attributes.append((att, getattr(msg, att))) *************** *** 264,273 **** '''An email.Message.Message extended for SpamBayes''' ! def __init__(self): email.Message.Message.__init__(self) # persistent state ! nm, typ = database_type() ! self.message_info_db = open_storage(nm, typ) self.stored_attributes = ['c', 't',] self.getDBKey = self.getId --- 278,291 ---- '''An email.Message.Message extended for SpamBayes''' ! def __init__(self, id=None, message_info_db=None): email.Message.Message.__init__(self) # persistent state ! # (non-persistent state includes all of email.Message.Message state) ! if message_info_db is not None: ! self.message_info_db = message_info_db ! else: ! nm, typ = database_type() ! self.message_info_db = open_storage(nm, typ) self.stored_attributes = ['c', 't',] self.getDBKey = self.getId *************** *** 276,280 **** self.t = None ! # non-persistent state includes all of email.Message.Message state # This function (and it's hackishness) can be avoided by using --- 294,299 ---- self.t = None ! if id is not None: ! self.setId(id) # This function (and it's hackishness) can be avoided by using *************** *** 315,318 **** --- 334,340 ---- raise TypeError, "Id must be a string" + if id == STATS_START_KEY: + raise ValueError, "MsgId must not be" + STATS_START_KEY + self.id = id self.message_info_db.load_msg(self) From anadelonbrin at users.sourceforge.net Tue Dec 21 22:41:52 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Tue Dec 21 22:41:55 2004 Subject: [Spambayes-checkins] spambayes/spambayes Stats.py,1.10,1.11 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv25155/spambayes Modified Files: Stats.py Log Message: Merge the Outlook2000.oastats.Stats class and the spambayes.Stats.Stats class into one that all the applications can use. Hopefully this combines the best features of each, and allows us to make improvements to both much more easily. The stats are sourced from the messageinfo db (no more need for the pickle the Outlook plugin was using - this was only used by CVS users, so they can delete it manually). Resetting is done by only counting messages since a certain date, which can be changed to the current date/time. Per-session and persistent (total) stats are supported, in much the same way as the Outlook stats have always worked. The other applications can use these too, now, although they don't as yet. The code is moderately backwards compatible. The code that generates the stats strings is broken up into three functions now, to make it easier to test it (and, hopefully, modify it). Index: Stats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Stats.py,v retrieving revision 1.10 retrieving revision 1.11 diff -C2 -d -r1.10 -r1.11 *** Stats.py 21 Dec 2004 16:21:59 -0000 1.10 --- Stats.py 21 Dec 2004 21:41:49 -0000 1.11 *************** *** 1,5 **** #! /usr/bin/env python ! """Stats.py - Spambayes statistics class. Classes: --- 1,5 ---- #! /usr/bin/env python ! """Stats.py - SpamBayes statistics class. Classes: *************** *** 14,29 **** is . To Do: o People would like pretty graphs, so maybe that could be done. o People have requested time-based statistics - mail per hour, spam per hour, and so on. ! o The possible stats to show are pretty much endless. Some to ! consider would be: percentage of mail that is fp/fn/unsure, ! percentage of mail correctly classified. o Suggestions? - """ ! # This module is part of the spambayes project, which is Copyright 2002-4 # The Python Software Foundation and is covered by the Python Software # Foundation license. --- 14,31 ---- is . + This class provides information for both the web interface, the + Outlook plug-in, and sb_pop3dnd. + To Do: o People would like pretty graphs, so maybe that could be done. o People have requested time-based statistics - mail per hour, spam per hour, and so on. ! Discussion on spambayes-dev indicated that this would be a lot ! of work for not much gain; however, since we now have some ! time data stored, it wouldn't be too bad, so maybe it can go in. o Suggestions? """ ! # This module is part of the spambayes project, which is Copyright 2002-5 # The Python Software Foundation and is covered by the Python Software # Foundation license. *************** *** 38,246 **** True, False = 1, 0 import types ! from spambayes.message import database_type, open_storage ! class Stats(object): ! class __empty_msg: ! def __init__(self): ! self.getDBKey = self.getId ! def getId(self): ! return self.id ! def __init__(self): ! self.CalculateStats() def Reset(self): ! self.cls_spam = 0 ! self.cls_ham = 0 ! self.cls_unsure = 0 ! self.trn_spam = 0 ! self.trn_ham = 0 ! self.trn_unsure_ham = 0 ! self.trn_unsure_spam = 0 ! self.fp = 0 ! self.fn = 0 ! self.total = 0 ! def CalculateStats(self): ! self.Reset() ! nm, typ = database_type() ! msginfoDB = open_storage(nm, typ) ! for msg in msginfoDB.db.keys(): ! self.total += 1 ! m = self.__empty_msg() ! m.id = msg ! msginfoDB.load_msg(m) ! if m.c == 's': # Classified as spam. ! self.cls_spam += 1 ! if m.t == False: # False positive (classified as spam, trained as ham) ! self.fp += 1 ! elif m.c == 'h': # Classified as ham. ! self.cls_ham += 1 ! if m.t == True: # False negative (classified as ham, trained as spam) ! self.fn += 1 ! elif m.c == 'u': # Classified as unsure. ! self.cls_unsure += 1 ! if m.t == False: ! self.trn_unsure_ham += 1 ! elif m.t == True: ! self.trn_unsure_spam += 1 ! if m.t == True: ! self.trn_spam += 1 ! elif m.t == False: ! self.trn_ham += 1 ! def GetStats(self, use_html=True): ! if self.total == 0: ! return ["SpamBayes has processed zero messages"] chunks = [] push = chunks.append ! not_trn_unsure = self.cls_unsure - self.trn_unsure_ham - \ ! self.trn_unsure_spam ! if self.cls_unsure: ! unsure_ham_perc = 100.0 * self.trn_unsure_ham / self.cls_unsure ! unsure_spam_perc = 100.0 * self.trn_unsure_spam / self.cls_unsure ! unsure_not_perc = 100.0 * not_trn_unsure / self.cls_unsure ! else: ! unsure_ham_perc = 0.0 # Not correct, really! ! unsure_spam_perc = 0.0 # Not correct, really! ! unsure_not_perc = 0.0 # Not correct, really! ! if self.trn_ham: ! trn_perc_unsure_ham = 100.0 * self.trn_unsure_ham / \ ! self.trn_ham ! trn_perc_fp = 100.0 * self.fp / self.trn_ham ! trn_perc_ham = 100.0 - (trn_perc_unsure_ham + trn_perc_fp) ! else: ! trn_perc_ham = 0.0 # Not correct, really! ! trn_perc_unsure_ham = 0.0 # Not correct, really! ! trn_perc_fp = 0.0 # Not correct, really! ! if self.trn_spam: ! trn_perc_unsure_spam = 100.0 * self.trn_unsure_spam / \ ! self.trn_spam ! trn_perc_fn = 100.0 * self.fn / self.trn_spam ! trn_perc_spam = 100.0 - (trn_perc_unsure_spam + trn_perc_fn) else: ! trn_perc_spam = 0.0 # Not correct, really! ! trn_perc_unsure_spam = 0.0 # Not correct, really! ! trn_perc_fn = 0.0 # Not correct, really! ! format_dict = { ! 'num_seen' : self.total, ! 'correct' : self.total - (self.cls_unsure + self.fp + self.fn), ! 'incorrect' : self.cls_unsure + self.fp + self.fn, ! 'unsure_ham_perc' : unsure_ham_perc, ! 'unsure_spam_perc' : unsure_spam_perc, ! 'unsure_not_perc' : unsure_not_perc, ! 'not_trn_unsure' : not_trn_unsure, ! 'trn_total' : (self.trn_ham + self.trn_spam + \ ! self.trn_unsure_ham + self.trn_unsure_spam), ! 'trn_perc_ham' : trn_perc_ham, ! 'trn_perc_unsure_ham' : trn_perc_unsure_ham, ! 'trn_perc_fp' : trn_perc_fp, ! 'trn_perc_spam' : trn_perc_spam, ! 'trn_perc_unsure_spam' : trn_perc_unsure_spam, ! 'trn_perc_fn' : trn_perc_fn, ! } ! format_dict.update(self.__dict__) ! # Add percentages of everything. ! for key, val in format_dict.items(): ! perc_key = "perc_" + key ! if self.total and isinstance(val, types.IntType): ! format_dict[perc_key] = 100.0 * val / self.total ! else: ! format_dict[perc_key] = 0.0 # Not correct, really! ! # Figure out plurals ! for num, key in [("num_seen", "sp1"), ! ("correct", "sp2"), ! ("incorrect", "sp3"), ! ("fp", "sp4"), ! ("fn", "sp5"), ! ("trn_unsure_ham", "sp6"), ! ("trn_unsure_spam", "sp7"), ! ("not_trn_unsure", "sp8"), ! ("trn_total", "sp9"), ! ]: ! if format_dict[num] == 1: ! format_dict[key] = '' ! else: ! format_dict[key] = 's' ! for num, key in [("correct", "wp1"), ! ("incorrect", "wp2"), ! ("not_trn_unsure", "wp3"), ! ]: ! if format_dict[num] == 1: ! format_dict[key] = 'was' ! else: ! format_dict[key] = 'were' ! # Possibly use HTML for breaks/tabs. if use_html: - format_dict["br"] = "
" format_dict["tab"] = "    " else: - format_dict["br"] = "\r\n" format_dict["tab"] = "\t" ! ## Our result should look something like this: ! ## (devised by Mark Moraes and Kenny Pitt) ! ## ! ## SpamBayes has classified a total of 1223 messages: ! ## 827 ham (67.6% of total) ! ## 333 spam (27.2% of total) ! ## 63 unsure (5.2% of total) ! ## ! ## 1125 messages were classified correctly (92.0% of total) ! ## 35 messages were classified incorrectly (2.9% of total) ! ## 0 false positives (0.0% of total) ! ## 35 false negatives (2.9% of total) ! ## ! ## 6 unsures trained as ham (9.5% of unsures) ! ## 56 unsures trained as spam (88.9% of unsures) ! ## 1 unsure was not trained (1.6% of unsures) ! ## ! ## A total of 760 messages have been trained: ! ## 346 ham (98.3% ham, 1.7% unsure, 0.0% false positives) ! ## 414 spam (78.0% spam, 13.5% unsure, 8.5% false negatives) - push("SpamBayes has classified a total of " \ - "%(num_seen)d message%(sp1)s:" \ - "%(br)s%(tab)s%(cls_ham)d " \ - "(%(perc_cls_ham).0f%% of total) good" \ - "%(br)s%(tab)s%(cls_spam)d " \ - "(%(perc_cls_spam).0f%% of total) spam" \ - "%(br)s%(tab)s%(cls_unsure)d " \ - "(%(perc_cls_unsure).0f%% of total) unsure." % \ - format_dict) - push("%(correct)d message%(sp2)s %(wp1)s classified correctly " \ - "(%(perc_correct).0f%% of total)" \ - "%(br)s%(incorrect)d message%(sp3)s %(wp2)s classified " \ - "incorrectly " \ - "(%(perc_incorrect).0f%% of total)" \ - "%(br)s%(tab)s%(fp)d false positive%(sp4)s " \ - "(%(perc_fp).0f%% of total)" \ - "%(br)s%(tab)s%(fn)d false negative%(sp5)s " \ - "(%(perc_fn).0f%% of total)" % \ - format_dict) - push("%(trn_unsure_ham)d unsure%(sp6)s trained as good " \ - "(%(unsure_ham_perc).0f%% of unsures)" \ - "%(br)s%(trn_unsure_spam)d unsure%(sp7)s trained as spam " \ - "(%(unsure_spam_perc).0f%% of unsures)" \ - "%(br)s%(not_trn_unsure)d unsure%(sp8)s %(wp3)s not trained " \ - "(%(unsure_not_perc).0f%% of unsures)" % \ - format_dict) - push("A total of %(trn_total)d message%(sp9)s have been trained:" \ - "%(br)s%(tab)s%(trn_ham)d good " \ - "(%(trn_perc_ham)0.f%% good, %(trn_perc_unsure_ham)0.f%% " \ - "unsure, %(trn_perc_fp).0f%% false positives)" \ - "%(br)s%(tab)s%(trn_spam)d spam " \ - "(%(trn_perc_spam)0.f%% spam, %(trn_perc_unsure_spam)0.f%% " \ - "unsure, %(trn_perc_fn).0f%% false negatives)" % \ - format_dict) return chunks --- 40,349 ---- True, False = 1, 0 + import time import types ! from spambayes.message import STATS_START_KEY ! from spambayes.message import database_type, open_storage, Message ! try: ! _ ! except NameError: ! _ = lambda arg: arg ! class Stats(object): ! def __init__(self, spam_threshold, unsure_threshold, messageinfo_db, ! ham_string, unsure_string, spam_string): ! self.messageinfo_db = messageinfo_db ! self.spam_threshold = spam_threshold ! self.unsure_threshold = unsure_threshold ! self.ham_string = ham_string ! self.unsure_string = unsure_string ! self.spam_string = spam_string ! # Reset session stats. ! self.Reset() ! # Load persistent stats. ! self.from_date = self.messageinfo_db.get_statistics_start_date() ! self.CalculatePersistentStats() def Reset(self): ! self.num_ham = self.num_spam = self.num_unsure = 0 ! self.num_trained_spam = self.num_trained_spam_fn = 0 ! self.num_trained_ham = self.num_trained_ham_fp = 0 ! def ResetTotal(self, permanently=False): ! self.totals = {} ! for stat in ["num_ham", "num_spam", "num_unsure", ! "num_trained_spam", "num_trained_spam_fn", ! "num_trained_ham", "num_trained_ham_fp",]: ! self.totals[stat] = 0 ! if permanently: ! # Reset the date. ! self.from_date = time.time() ! self.messageinfo_db.set_statistics_start_date(self.from_date) ! ! def RecordClassification(self, score): ! if score >= self.spam_threshold: ! self.num_spam += 1 ! elif score >= self.unsure_threshold: ! self.num_unsure += 1 ! else: ! self.num_ham += 1 ! ! def RecordTraining(self, as_ham, old_score): ! if as_ham: ! self.num_trained_ham += 1 ! # If we are recovering an item that is in the "spam" threshold, ! # then record it as a "false positive" ! if old_score > self.spam_threshold: ! self.num_trained_ham_fp += 1 ! else: ! self.num_trained_spam += 1 ! # If we are deleting as Spam an item that was in our "good" ! # range, then record it as a false negative. ! if old_score < self.unsure_threshold: ! self.num_trained_spam_fn += 1 ! ! def CalculatePersistentStats(self): ! """Calculate the statistics totals (i.e. not this session). ! ! This is done by running through the messageinfo database and ! adding up the various information. This could get quite time ! consuming if the messageinfo database gets very large, so ! some consideration should perhaps be made about what to do ! then. ! """ ! self.ResetTotal() ! totals = self.totals ! for msg_id in self.messageinfo_db.db.keys(): ! # Skip the date key. ! if msg_id == STATS_START_KEY: ! continue ! m = Message(msg_id, self.messageinfo_db) ! self.messageinfo_db.load_msg(m) ! ! # Skip ones that are too old. ! if self.from_date and m.date_modified and \ ! m.date_modified > self.from_date: ! continue ! ! classification = m.GetClassification() ! trained = m.GetTrained() ! ! if classification == self.spam_string: # Classified as spam. ! totals["num_spam"] += 1 ! if trained == False: # False positive (classified as spam, trained as ham) ! totals["num_trained_ham_fp"] += 1 ! elif classification == self.ham_string: # Classified as ham. ! totals["num_ham"] += 1 ! if trained == True: # False negative (classified as ham, trained as spam) ! totals["num_trained_spam_fn"] += 1 ! elif classification == self.unsure_string: # Classified as unsure. ! totals["num_unsure"] += 1 ! if trained == False: ! totals["num_trained_ham"] += 1 ! elif trained == True: ! totals["num_trained_spam"] += 1 ! def _CombineSessionAndTotal(self): ! totals = self.totals ! num_seen = self.num_ham + self.num_spam + self.num_unsure + \ ! totals["num_ham"] + totals["num_spam"] + \ ! totals["num_unsure"] ! num_ham = self.num_ham + totals["num_ham"] ! num_spam = self.num_spam + totals["num_spam"] ! num_unsure = self.num_unsure + totals["num_unsure"] ! num_trained_ham = self.num_trained_ham + totals["num_trained_ham"] ! num_trained_ham_fp = self.num_trained_ham_fp + \ ! totals["num_trained_ham_fp"] ! num_trained_spam = self.num_trained_spam + \ ! totals["num_trained_spam"] ! num_trained_spam_fn = self.num_trained_spam_fn + \ ! totals["num_trained_spam_fn"] ! return locals() ! ! def _CalculateAdditional(self, data): ! data["perc_ham"] = 100.0 * data["num_ham"] / data["num_seen"] ! data["perc_spam"] = 100.0 * data["num_spam"] / data["num_seen"] ! data["perc_unsure"] = 100.0 * data["num_unsure"] / data["num_seen"] ! data["num_ham_correct"] = data["num_ham"] - \ ! data["num_trained_spam_fn"] ! data["num_spam_correct"] = data["num_spam"] - \ ! data["num_trained_ham_fp"] ! data["num_correct"] = data["num_ham_correct"] + \ ! data["num_spam_correct"] ! data["num_incorrect"] = data["num_trained_spam_fn"] + \ ! data["num_trained_ham_fp"] ! data["perc_correct"] = 100.0 * data["num_correct"] / \ ! data["num_seen"] ! data["perc_incorrect"] = 100.0 * data["num_incorrect"] / \ ! data["num_seen"] ! data["perc_fp"] = 100.0 * data["num_trained_ham_fp"] / \ ! data["num_seen"] ! data["perc_fn"] = 100.0 * data["num_trained_spam_fn"] / \ ! data["num_seen"] ! data["num_unsure_trained_ham"] = data["num_trained_ham"] - \ ! data["num_trained_ham_fp"] ! data["num_unsure_trained_spam"] = data["num_trained_spam"] - \ ! data["num_trained_spam_fn"] ! data["num_unsure_not_trained"] = data["num_unsure"] - \ ! data["num_unsure_trained_ham"] - \ ! data["num_unsure_trained_spam"] ! if data["num_unsure"]: ! data["perc_unsure_trained_ham"] = 100.0 * \ ! data["num_unsure_trained_ham"] / \ ! data["num_unsure"] ! data["perc_unsure_trained_spam"] = 100.0 * \ ! data["num_unsure_trained_spam"] / \ ! data["num_unsure"] ! data["perc_unsure_not_trained"] = 100.0 * \ ! data["num_unsure_not_trained"] / \ ! data["num_unsure"] ! data["total_ham"] = data["num_ham_correct"] + \ ! data["num_trained_ham"] ! data["total_spam"] = data["num_spam_correct"] + \ ! data["num_trained_spam"] ! if data["total_ham"]: ! data["perc_ham_incorrect"] = 100.0 * \ ! data["num_trained_ham_fp"] / \ ! data["total_ham"] ! data["perc_ham_unsure"] = 100.0 * \ ! data["num_unsure_trained_ham"] / \ ! data["total_ham"] ! data["perc_ham_incorrect_or_unsure"] = \ ! 100.0 * (data["num_trained_ham_fp"] + ! data["num_unsure_trained_ham"]) / \ ! data["total_ham"] ! if data["total_spam"]: ! data["perc_spam_correct"] = 100.0 * data["num_spam_correct"] / \ ! data["total_spam"] ! data["perc_spam_unsure"] = 100.0 * \ ! data["num_unsure_trained_spam"] / \ ! data["total_spam"] ! data["perc_spam_correct_or_unsure"] = \ ! 100.0 * (data["num_spam_correct"] + \ ! data["num_unsure_trained_spam"]) / \ ! data["total_spam"] ! return data ! ! def _AddPercentStrings(self, data, dp): ! data["perc_ham_s"] = "%%(perc_ham).%df%%(perc)s" % (dp,) ! data["perc_spam_s"] = "%%(perc_spam).%df%%(perc)s" % (dp,) ! data["perc_unsure_s"] = "%%(perc_unsure).%df%%(perc)s" % (dp,) ! data["perc_correct_s"] = "%%(perc_correct).%df%%(perc)s" % (dp,) ! data["perc_incorrect_s"] = "%%(perc_incorrect).%df%%(perc)s" % (dp,) ! data["perc_fp_s"] = "%%(perc_fp).%df%%(perc)s" % (dp,) ! data["perc_fn_s"] = "%%(perc_fn).%df%%(perc)s" % (dp,) ! data["perc_spam_correct_s"] = "%%(perc_spam_correct).%df%%(perc)s" \ ! % (dp,) ! data["perc_spam_unsure_s"] = "%%(perc_spam_unsure).%df%%(perc)s" \ ! % (dp,) ! data["perc_spam_correct_or_unsure_s"] = \ ! "%%(perc_spam_correct_or_unsure).%df%%(perc)s" % (dp,) ! data["perc_ham_incorrect_s"] = "%%(perc_ham_incorrect).%df%%(perc)s" \ ! % (dp,) ! data["perc_ham_unsure_s"] = "%%(perc_ham_unsure).%df%%(perc)s" \ ! % (dp,) ! data["perc_ham_incorrect_or_unsure_s"] = \ ! "%%(perc_ham_incorrect_or_unsure).%df%%(perc)s" % (dp,) ! data["perc_unsure_trained_ham_s"] = \ ! "%%(perc_unsure_trained_ham).%df%%(perc)s" % (dp,) ! data["perc_unsure_trained_spam_s"] = "%%(perc_unsure_trained_spam).%df%%(perc)s" \ ! % (dp,) ! data["perc_unsure_not_trained_s"] = "%%(perc_unsure_not_trained).%df%%(perc)s" \ ! % (dp,) ! data["perc"] = "%" ! return data ! ! def GetStats(self, use_html=False, session_only=False, decimal_points=1): ! """Return a description of the statistics. ! ! If session_only is True, then only a description of the statistics ! since we were last reset. Otherwise, lifetime statistics (i.e. ! those including the ones loaded). ! ! Users probably care most about persistent statistics, so present ! those by default. If session-only stats are desired, then a ! special call to here can be made. ! ! The percentages will be accurate to the given number of decimal ! points. ! ! If use_html is True, then the returned data is marked up with ! appropriate HTML, otherwise it is plain text. ! """ chunks = [] push = chunks.append ! ! if session_only: ! data = {} ! data["num_seen"] = self.num_ham + self.num_spam + \ ! self.num_unsure ! data["num_ham"] = self.num_ham ! data["num_spam"] = self.num_spam ! data["num_unsure"] = self.num_unsure ! data["num_trained_ham"] = self.num_trained_ham ! data["num_trained_ham_fp"] = self.num_trained_ham_fp ! data["num_trained_spam"] = self.num_trained_spam ! data["num_trained_spam_fn"] = self.num_trained_spam_fn else: ! data = self._CombineSessionAndTotal() ! push(_("Messages classified: %d" % (data["num_seen"],))) ! if data["num_seen"] == 0: ! return chunks ! data = self._CalculateAdditional(data) ! format_dict = self._AddPercentStrings(data, decimal_points) ! ! # Possibly use HTML for tabs. if use_html: format_dict["tab"] = "    " else: format_dict["tab"] = "\t" ! push((_("%(tab)sGood:%(tab)s%(num_ham)d (%(perc_ham_s)s)") \ ! % format_dict) % format_dict) ! push((_("%(tab)sSpam:%(tab)s%(num_spam)d (%(perc_spam_s)s)") \ ! % format_dict) % format_dict) ! push((_("%(tab)sUnsure:%(tab)s%(num_unsure)d (%(perc_unsure_s)s)") \ ! % format_dict) % format_dict) ! push("") ! ! push((_("Classified correctly:%(tab)s%(num_correct)d (%(perc_correct_s)s of total)") \ ! % format_dict) % format_dict) ! push((_("Classified incorrectly:%(tab)s%(num_incorrect)d (%(perc_incorrect_s)s of total)") \ ! % format_dict) % format_dict) ! if format_dict["num_incorrect"]: ! push((_("%(tab)sFalse positives:%(tab)s%(num_trained_ham_fp)d (%(perc_fp_s)s of total)") \ ! % format_dict) % format_dict) ! push((_("%(tab)sFalse negatives:%(tab)s%(num_trained_spam_fn)d (%(perc_fn_s)s of total)") \ ! % format_dict) % format_dict) ! push("") ! ! push(_("Manually classified as good:%(tab)s%(num_trained_ham)d") % format_dict) ! push(_("Manually classified as spam:%(tab)s%(num_trained_spam)d") % format_dict) ! push("") ! ! if format_dict["num_unsure"]: ! push((_("Unsures trained as good:%(tab)s%(num_unsure_trained_ham)d (%(perc_unsure_trained_ham_s)s of unsures)") \ ! % format_dict) % format_dict) ! push((_("Unsures trained as spam:%(tab)s%(num_unsure_trained_spam)d (%(perc_unsure_trained_spam_s)s of unsures)") \ ! % format_dict) % format_dict) ! push((_("Unsures not trained:%(tab)s%(tab)s%(num_unsure_not_trained)d (%(perc_unsure_not_trained_s)s of unsures)") \ ! % format_dict) % format_dict) ! push("") ! ! if format_dict["total_spam"]: ! push((_("Spam correctly identified:%(tab)s%(perc_spam_correct_s)s (+ %(perc_spam_unsure_s)s unsure)") \ ! % format_dict) % format_dict) ! if format_dict["total_ham"]: ! push((_("Good incorrectly identified:%(tab)s%(perc_ham_incorrect_s)s (+ %(perc_ham_unsure_s)s unsure)") \ ! % format_dict) % format_dict) return chunks From anadelonbrin at users.sourceforge.net Tue Dec 21 22:48:41 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Tue Dec 21 22:48:44 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py, 1.144, 1.145 filter.py, 1.41, 1.42 manager.py, 1.101, 1.102 msgstore.py, 1.96, 1.97 train.py, 1.40, 1.41 oastats.py, 1.11, NONE Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv26759/Outlook2000 Modified Files: addin.py filter.py manager.py msgstore.py train.py Removed Files: oastats.py Log Message: Use spambayes.Stats instead of out own oastats module. The central one has changed to be much like the oastats one was, except that it uses the messageinfo database and not another pickle. We also now stored the 'classified' (c) attribute in the message info database for Outlook. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.144 retrieving revision 1.145 diff -C2 -d -r1.144 -r1.145 *** addin.py 20 Dec 2004 04:24:54 -0000 1.144 --- addin.py 21 Dec 2004 21:48:30 -0000 1.145 *************** *** 695,700 **** for msgstore_message in msgstore_messages: # Record this recovery in our stats. ! self.manager.stats.RecordManualClassification(False, ! self.manager.score(msgstore_message)) # Record the original folder, in case this message is not where # it was after filtering, or has never been filtered. --- 695,700 ---- for msgstore_message in msgstore_messages: # Record this recovery in our stats. ! self.manager.stats.RecordTraining(False, ! self.manager.score(msgstore_message)) # Record the original folder, in case this message is not where # it was after filtering, or has never been filtered. *************** *** 763,767 **** # Record this recovery in our stats. ! self.manager.stats.RecordManualClassification(True, self.manager.score(msgstore_message)) # Must train before moving, else we lose the message! --- 763,767 ---- # Record this recovery in our stats. ! self.manager.stats.RecordTraining(True, self.manager.score(msgstore_message)) # Must train before moving, else we lose the message! *************** *** 1144,1148 **** self.explorers_collection = None self.toolbar = None - self.manager.stats.Store() # save stats self.close() # disconnect events. --- 1144,1147 ---- *************** *** 1505,1513 **** # Report some simple stats, for session, and for total. print "Session:" ! print "\r\n".join(self.manager.stats.GetStats(True)) print "Total:" print "\r\n".join(self.manager.stats.GetStats()) - # Save stats. - self.manager.stats.Store() self.manager.Close() self.manager = None --- 1504,1510 ---- # Report some simple stats, for session, and for total. print "Session:" ! print "\r\n".join(self.manager.stats.GetStats(session_only=True)) print "Total:" print "\r\n".join(self.manager.stats.GetStats()) self.manager.Close() self.manager = None Index: filter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/filter.py,v retrieving revision 1.41 retrieving revision 1.42 diff -C2 -d -r1.41 -r1.42 *** filter.py 8 Dec 2004 04:27:59 -0000 1.41 --- filter.py 21 Dec 2004 21:48:37 -0000 1.42 *************** *** 17,26 **** --- 17,29 ---- disposition = "Yes" attr_prefix = "spam" + msg.c = "spam" elif prob_perc >= config.unsure_threshold: disposition = "Unsure" attr_prefix = "unsure" + msg.c = "unsure" else: disposition = "No" attr_prefix = "ham" + msg.c = "ham" ms = mgr.message_store Index: manager.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/manager.py,v retrieving revision 1.101 retrieving revision 1.102 diff -C2 -d -r1.101 -r1.102 *** manager.py 20 Dec 2004 03:37:38 -0000 1.101 --- manager.py 21 Dec 2004 21:48:37 -0000 1.102 *************** *** 17,21 **** import msgstore - import oastats try: --- 17,20 ---- *************** *** 123,127 **** def import_core_spambayes_stuff(ini_filenames): global bayes_classifier, bayes_tokenize, bayes_storage, bayes_options, \ ! bayes_message if "spambayes.Options" in sys.modules: # The only thing we are worried about here is spambayes.Options --- 122,126 ---- def import_core_spambayes_stuff(ini_filenames): global bayes_classifier, bayes_tokenize, bayes_storage, bayes_options, \ ! bayes_message, bayes_stats if "spambayes.Options" in sys.modules: # The only thing we are worried about here is spambayes.Options *************** *** 150,157 **** --- 149,158 ---- from spambayes import storage from spambayes import message + from spambayes import Stats bayes_classifier = classifier bayes_tokenize = tokenize bayes_storage = storage bayes_message = message + bayes_stats = Stats assert "spambayes.Options" in sys.modules, \ "Expected 'spambayes.Options' to be loaded here" *************** *** 454,458 **** self.ReportFatalStartupError("Failed to load bayes database") self.classifier_data.InitNew() ! self.stats = oastats.Stats(self.config, self.data_directory) # Logging - this should be somewhere else. --- 455,463 ---- self.ReportFatalStartupError("Failed to load bayes database") self.classifier_data.InitNew() ! s_thres = self.config.filter.spam_threshold ! u_thres = self.config.filter.unsure_threshold ! mdb = self.classifier_data.message_db ! self.stats = bayes_stats.Stats(s_thres, u_thres, mdb, "ham", ! "unsure", "spam") # Logging - this should be somewhere else. Index: msgstore.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v retrieving revision 1.96 retrieving revision 1.97 diff -C2 -d -r1.96 -r1.97 *** msgstore.py 20 Dec 2004 04:23:33 -0000 1.96 --- msgstore.py 21 Dec 2004 21:48:37 -0000 1.97 *************** *** 809,814 **** # For use with the spambayes.message messageinfo database. ! self.stored_attributes = ['t', 'original_folder'] self.t = None self.original_folder = None --- 809,815 ---- # For use with the spambayes.message messageinfo database. ! self.stored_attributes = ['c', 't', 'original_folder'] self.t = None + self.c = None self.original_folder = None Index: train.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/train.py,v retrieving revision 1.40 retrieving revision 1.41 diff -C2 -d -r1.40 -r1.41 *** train.py 25 Nov 2004 23:26:58 -0000 1.40 --- train.py 21 Dec 2004 21:48:38 -0000 1.41 *************** *** 172,179 **** # If we are rebuilding, then we reset the statistics, too. # (But output them to the log for reference). ! mgr.LogDebug(1, "Session:" + "\r\n".join(mgr.stats.GetStats(False))) mgr.LogDebug(1, "Total:" + "\r\n".join(mgr.stats.GetStats())) mgr.stats.Reset() ! mgr.stats.ResetTotal(True) progress.tick() --- 172,180 ---- # If we are rebuilding, then we reset the statistics, too. # (But output them to the log for reference). ! mgr.LogDebug(1, "Session:" + "\r\n".join(\ ! mgr.stats.GetStats(session_only=True))) mgr.LogDebug(1, "Total:" + "\r\n".join(mgr.stats.GetStats())) mgr.stats.Reset() ! mgr.stats.ResetTotal(permanently=True) progress.tick() --- oastats.py DELETED --- From anadelonbrin at users.sourceforge.net Wed Dec 22 00:05:26 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:05:29 2004 Subject: [Spambayes-checkins] spambayes/spambayes/resources ui.html, 1.34, 1.35 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/resources In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv11911/spambayes/resources Modified Files: ui.html Log Message: Add notes about which sections do not need to be translated. Index: ui.html =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/resources/ui.html,v retrieving revision 1.34 retrieving revision 1.35 diff -C2 -d -r1.34 -r1.35 *** ui.html 10 Aug 2004 14:20:25 -0000 1.34 --- ui.html 21 Dec 2004 23:05:23 -0000 1.35 *************** *** 65,68 **** --- 65,72 ---- id tag, and becomes a Python object at runtime.

+

This "Introduction" section serves as an introduction to this file. + It does not require translation and is never used in the run-time + interface.

+

As an example of how this works, here is an editbox with an id of examplebox: *************** *** 107,110 **** --- 111,115 ---- presented in one of these. The pieces aren't presented in these boxes here in ui.html to avoid duplication of HTML. + As such, this section does not need translation.  
*************** *** 327,331 **** ! Warning: please insert warning message here! --- 332,338 ---- ! Warning: please insert warning message here! The warnings are ! all dynamically inserted, and so translating this text is not ! necessary. From anadelonbrin at users.sourceforge.net Wed Dec 22 00:07:20 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:07:23 2004 Subject: [Spambayes-checkins] spambayes/spambayes i18n.py,1.1,1.2 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv12321/spambayes Modified Files: i18n.py Log Message: Add a function to load the ui.html file appropriate for the language. Index: i18n.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/i18n.py,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** i18n.py 2 Nov 2004 21:27:43 -0000 1.1 --- i18n.py 21 Dec 2004 23:07:17 -0000 1.2 *************** *** 47,50 **** --- 47,51 ---- ## ..etc.. + class LanguageManager: def __init__(self, directory=os.path.dirname(__file__)): *************** *** 90,93 **** --- 91,112 ---- lang.install() + def import_ui_html(self): + """Load and return the appropriate ui_html.py module for the + current language.""" + for language in self.current_langs_codes: + moduleName = 'languages.%s.i18n_ui_html' % (language, ) + try: + module = __import__(moduleName, {}, {}, ('languages', + language)) + except ImportError: + # That language isn't available - fall back to the + # next one. + pass + else: + return module + # Nothing available - use the default. + from spambayes.resources import ui_html + return ui_html + def _install_gettext(self): """Set the gettext specific environment.""" From anadelonbrin at users.sourceforge.net Wed Dec 22 00:08:54 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:08:56 2004 Subject: [Spambayes-checkins] spambayes/spambayes ImapUI.py, 1.39, 1.40 UserInterface.py, 1.49, 1.50 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv12699/spambayes Modified Files: ImapUI.py UserInterface.py Log Message: Add support for i18n. Index: ImapUI.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/ImapUI.py,v retrieving revision 1.39 retrieving revision 1.40 diff -C2 -d -r1.39 -r1.40 *** ImapUI.py 13 Oct 2004 02:42:04 -0000 1.39 --- ImapUI.py 21 Dec 2004 23:08:51 -0000 1.40 *************** *** 110,114 **** class IMAPUserInterface(UserInterface.UserInterface): """Serves the HTML user interface for the proxies.""" ! def __init__(self, cls, imap, pwd, imap_session_class): global parm_map # Only offer SSL if it is available --- 110,115 ---- class IMAPUserInterface(UserInterface.UserInterface): """Serves the HTML user interface for the proxies.""" ! def __init__(self, cls, imap, pwd, imap_session_class, ! lang_manager=None): global parm_map # Only offer SSL if it is available *************** *** 121,125 **** else: del IMAP4_SSL ! UserInterface.UserInterface.__init__(self, cls, parm_map, adv_map) self.classifier = cls self.imap = imap --- 122,127 ---- else: del IMAP4_SSL ! UserInterface.UserInterface.__init__(self, cls, parm_map, adv_map, ! lang_manager) self.classifier = cls self.imap = imap Index: UserInterface.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/UserInterface.py,v retrieving revision 1.49 retrieving revision 1.50 diff -C2 -d -r1.49 -r1.50 *** UserInterface.py 21 Dec 2004 21:37:06 -0000 1.49 --- UserInterface.py 21 Dec 2004 23:08:51 -0000 1.50 *************** *** 133,138 **** class BaseUserInterface(Dibbler.HTTPPlugin): ! def __init__(self): Dibbler.HTTPPlugin.__init__(self) htmlSource, self._images = self.readUIResources() self.html = PyMeldLite.Meld(htmlSource, readonly=True) --- 133,139 ---- class BaseUserInterface(Dibbler.HTTPPlugin): ! def __init__(self, lang_manager=None): Dibbler.HTTPPlugin.__init__(self) + self.lang_manager = lang_manager htmlSource, self._images = self.readUIResources() self.html = PyMeldLite.Meld(htmlSource, readonly=True) *************** *** 250,257 **** def readUIResources(self): """Returns ui.html and a dictionary of Gifs.""" ! ! # Using `exec` is nasty, but I couldn't figure out a way of making ! # `getattr` or `__import__` work with ResourcePackage. ! from spambayes.resources import ui_html images = {} for baseName in IMAGES: --- 251,258 ---- def readUIResources(self): """Returns ui.html and a dictionary of Gifs.""" ! if self.lang_manager: ! ui_html = self.lang_manager.import_ui_html() ! else: ! from spambayes.resources import ui_html images = {} for baseName in IMAGES: *************** *** 265,271 **** """Serves the HTML user interface.""" ! def __init__(self, bayes, config_parms=(), adv_parms=()): """Load up the necessary resources: ui.html and helmet.gif.""" ! BaseUserInterface.__init__(self) self.classifier = bayes self.parm_ini_map = config_parms --- 266,273 ---- """Serves the HTML user interface.""" ! def __init__(self, bayes, config_parms=(), adv_parms=(), ! lang_manager=None): """Load up the necessary resources: ui.html and helmet.gif.""" ! BaseUserInterface.__init__(self, lang_manager) self.classifier = bayes self.parm_ini_map = config_parms From anadelonbrin at users.sourceforge.net Wed Dec 22 00:10:24 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:10:30 2004 Subject: [Spambayes-checkins] spambayes/spambayes FileCorpus.py,1.16,1.17 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv13193/spambayes Modified Files: FileCorpus.py Log Message: Don't use the strict keyword arg as it is deprecated (we are passing the default value, anyway). Index: FileCorpus.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/FileCorpus.py,v retrieving revision 1.16 retrieving revision 1.17 diff -C2 -d -r1.16 -r1.17 *** FileCorpus.py 19 Jul 2004 09:55:21 -0000 1.16 --- FileCorpus.py 21 Dec 2004 23:10:21 -0000 1.17 *************** *** 273,277 **** # We parse the content into a generic email.Message object. ! msg = email.message_from_string(payload, strict=False) # And then we set ourselves to be equal to it. --- 273,277 ---- # We parse the content into a generic email.Message object. ! msg = email.message_from_string(payload) # And then we set ourselves to be equal to it. *************** *** 347,352 **** '''Create a message object from a filename in a directory''' if content: ! msg = email.message_from_string(content, _class=FileMessage, ! strict=False) msg.file_name = key msg.directory = directory --- 347,351 ---- '''Create a message object from a filename in a directory''' if content: ! msg = email.message_from_string(content, _class=FileMessage) msg.file_name = key msg.directory = directory *************** *** 380,385 **** if content: msg = email.message_from_string(content, ! _class=GzipFileMessage, ! strict=False) msg.file_name = key msg.directory = directory --- 379,383 ---- if content: msg = email.message_from_string(content, ! _class=GzipFileMessage) msg.file_name = key msg.directory = directory From anadelonbrin at users.sourceforge.net Wed Dec 22 00:12:14 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:12:17 2004 Subject: [Spambayes-checkins] spambayes/spambayes message.py,1.63,1.64 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv13741/spambayes Modified Files: message.py Log Message: My patch for Python 2.4 compatibility for the setPayload function wouldn't work. This version should. (Need to update the __dict__, note replace it). Index: message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/message.py,v retrieving revision 1.63 retrieving revision 1.64 diff -C2 -d -r1.63 -r1.64 *** message.py 21 Dec 2004 21:37:06 -0000 1.63 --- message.py 21 Dec 2004 23:12:11 -0000 1.64 *************** *** 322,326 **** DeprecationWarning, 2) new_me = email.message_from_string(payload, _class=Message) ! self.__dict__ = new_me.__dict__ def setId(self, id): --- 322,326 ---- DeprecationWarning, 2) new_me = email.message_from_string(payload, _class=Message) ! self.__dict__.update(new_me.__dict__) def setId(self, id): *************** *** 422,426 **** DeprecationWarning, 2) new_me = email.message_from_string(payload, _class=SBHeaderMessage) ! self.__dict__ = new_me.__dict__ def setIdFromPayload(self): --- 422,426 ---- DeprecationWarning, 2) new_me = email.message_from_string(payload, _class=SBHeaderMessage) ! self.__dict__.update(new_me.__dict__) def setIdFromPayload(self): From anadelonbrin at users.sourceforge.net Wed Dec 22 00:12:59 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:13:00 2004 Subject: [Spambayes-checkins] spambayes/spambayes message.py, 1.49.4.6, 1.49.4.7 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv13913/spambayes Modified Files: Tag: release_1_0-branch message.py Log Message: Backport: My patch for Python 2.4 compatibility for the setPayload function wouldn't work. This version should. (Need to update the __dict__, note replace it). Index: message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/message.py,v retrieving revision 1.49.4.6 retrieving revision 1.49.4.7 diff -C2 -d -r1.49.4.6 -r1.49.4.7 *** message.py 20 Dec 2004 04:09:28 -0000 1.49.4.6 --- message.py 21 Dec 2004 23:12:55 -0000 1.49.4.7 *************** *** 248,252 **** # 1.0.x branch, so use a different ugly hack. new_me = email.message_from_string(payload, _class=Message) ! self.__dict__ = new_me.__dict__ def setId(self, id): --- 248,252 ---- # 1.0.x branch, so use a different ugly hack. new_me = email.message_from_string(payload, _class=Message) ! self.__dict__.update(new_me.__dict__) def setId(self, id): *************** *** 342,347 **** def setPayload(self, payload): ! new_me = email.message_from_string(payload, _class=Message) ! self.__dict__ = new_me.__dict__ def setIdFromPayload(self): --- 342,347 ---- def setPayload(self, payload): ! new_me = email.message_from_string(payload, _class=SBHeaderMessage) ! self.__dict__.update(new_me.__dict__) def setIdFromPayload(self): From anadelonbrin at users.sourceforge.net Wed Dec 22 00:13:50 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:13:52 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_server.py,1.33,1.34 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv14133/scripts Modified Files: sb_server.py Log Message: Add i18n support. Index: sb_server.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_server.py,v retrieving revision 1.33 retrieving revision 1.34 diff -C2 -d -r1.33 -r1.34 *** sb_server.py 20 Dec 2004 23:24:03 -0000 1.33 --- sb_server.py 21 Dec 2004 23:13:47 -0000 1.34 *************** *** 55,58 **** --- 55,66 ---- True, False = 1, 0 + try: + reversed + except NameError: + # Maintain compatibility with Python 2.2 and 2.3 + def reversed(seq): + seq = list(seq[:]) + seq.reverse() + return iter(seq) todo = """ *************** *** 102,105 **** --- 110,114 ---- import spambayes.message + from spambayes import i18n from spambayes import Dibbler from spambayes import storage *************** *** 111,114 **** --- 120,124 ---- from spambayes.Version import get_version_string + # Increase the stack size on MacOS X. Stolen from Lib/test/regrtest.py if sys.platform == 'darwin': *************** *** 652,655 **** --- 662,681 ---- def init(self): assert not self.prepared, "init after prepare, but before close" + # Load the environment for translation. + self.lang_manager = i18n.LanguageManager() + # Set the system user default language. + self.lang_manager.set_language(\ + self.lang_manager.locale_default_lang()) + # Set interface to use the user language in the configuration file. + for language in reversed(options["globals", "language"]): + # We leave the default in there as the last option, to fall + # back on if necessary. + self.lang_manager.add_language(language) + if options["globals", "verbose"]: + print "Asked to add languages: " + \ + ", ".join(options["globals", "language"]) + print "Set language to " + \ + str(self.lang_manager.current_langs_codes) + # Open the log file. if options["globals", "verbose"]: From anadelonbrin at users.sourceforge.net Wed Dec 22 00:16:40 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:16:44 2004 Subject: [Spambayes-checkins] spambayes/spambayes ProxyUI.py,1.54,1.55 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv14826/spambayes Modified Files: ProxyUI.py Log Message: Add i18n support. Index: ProxyUI.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/ProxyUI.py,v retrieving revision 1.54 retrieving revision 1.55 diff -C2 -d -r1.54 -r1.55 *** ProxyUI.py 8 Dec 2004 03:34:29 -0000 1.54 --- ProxyUI.py 21 Dec 2004 23:16:28 -0000 1.55 *************** *** 164,168 **** global state UserInterface.UserInterface.__init__(self, proxy_state.bayes, ! parm_ini_map, adv_map) state = proxy_state self.state_recreator = state_recreator # ugly --- 164,169 ---- global state UserInterface.UserInterface.__init__(self, proxy_state.bayes, ! parm_ini_map, adv_map, ! proxy_state.lang_manager) state = proxy_state self.state_recreator = state_recreator # ugly From anadelonbrin at users.sourceforge.net Wed Dec 22 00:18:35 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:18:37 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 manager.py,1.102,1.103 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv15251/Outlook2000 Modified Files: manager.py Log Message: Pass more information to the stats manager. Index: manager.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/manager.py,v retrieving revision 1.102 retrieving revision 1.103 diff -C2 -d -r1.102 -r1.103 *** manager.py 21 Dec 2004 21:48:37 -0000 1.102 --- manager.py 21 Dec 2004 23:18:32 -0000 1.103 *************** *** 457,463 **** s_thres = self.config.filter.spam_threshold u_thres = self.config.filter.unsure_threshold mdb = self.classifier_data.message_db self.stats = bayes_stats.Stats(s_thres, u_thres, mdb, "ham", ! "unsure", "spam") # Logging - this should be somewhere else. --- 457,468 ---- s_thres = self.config.filter.spam_threshold u_thres = self.config.filter.unsure_threshold + fp_cost = bayes_options["TestDriver", "best_cutoff_fp_weight"] + fn_cost = bayes_options["TestDriver", "best_cutoff_fn_weight"] + unsure_cost = bayes_options["TestDriver", + "best_cutoff_unsure_weight"] mdb = self.classifier_data.message_db self.stats = bayes_stats.Stats(s_thres, u_thres, mdb, "ham", ! "unsure", "spam", fp_cost, fn_cost, ! unsure_cost) # Logging - this should be somewhere else. From anadelonbrin at users.sourceforge.net Wed Dec 22 00:19:47 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:19:48 2004 Subject: [Spambayes-checkins] spambayes/spambayes Stats.py,1.11,1.12 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv15500/spambayes Modified Files: Stats.py Log Message: Include the cost figure (and a 'savings' figure, which is sure to be popular and meaningless ) in the stats. Index: Stats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Stats.py,v retrieving revision 1.11 retrieving revision 1.12 diff -C2 -d -r1.11 -r1.12 *** Stats.py 21 Dec 2004 21:41:49 -0000 1.11 --- Stats.py 21 Dec 2004 23:19:41 -0000 1.12 *************** *** 53,57 **** class Stats(object): def __init__(self, spam_threshold, unsure_threshold, messageinfo_db, ! ham_string, unsure_string, spam_string): self.messageinfo_db = messageinfo_db self.spam_threshold = spam_threshold --- 53,58 ---- class Stats(object): def __init__(self, spam_threshold, unsure_threshold, messageinfo_db, ! ham_string, unsure_string, spam_string, fp_cost, fn_cost, ! unsure_cost): self.messageinfo_db = messageinfo_db self.spam_threshold = spam_threshold *************** *** 60,63 **** --- 61,67 ---- self.unsure_string = unsure_string self.spam_string = spam_string + self.fp_cost = fp_cost + self.fn_cost = fn_cost + self.unsure_cost = unsure_cost # Reset session stats. self.Reset() *************** *** 229,232 **** --- 233,245 ---- data["num_unsure_trained_spam"]) / \ data["total_spam"] + + data["total_cost"] = data["num_trained_ham_fp"] * self.fp_cost + \ + data["num_trained_spam_fn"] * self.fn_cost + \ + data["num_unsure"] * self.unsure_cost + # If there was no filtering done, what would the cost have been? + # (Assuming that any spam in the inbox earns the cost of a fn) + no_filter_cost = data["num_spam"] * self.fn_cost + data["cost_savings"] = no_filter_cost - data["total_cost"] + return data *************** *** 345,348 **** --- 358,366 ---- push((_("Good incorrectly identified:%(tab)s%(perc_ham_incorrect_s)s (+ %(perc_ham_unsure_s)s unsure)") \ % format_dict) % format_dict) + if format_dict["total_spam"] or format_dict["total_ham"]: + push("") + + push(_("Total cost of spam:%(tab)s$%(total_cost).2f") % format_dict) + push(_("SpamBayes savings:%(tab)s$%(cost_savings).2f") % format_dict) return chunks From anadelonbrin at users.sourceforge.net Wed Dec 22 00:21:25 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:21:27 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_pop3dnd.py,1.13,1.14 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv15878/scripts Modified Files: sb_pop3dnd.py Log Message: Pass an existing message info db to message objects where possible. Correctly initialise the (new and improved) stats manager. Index: sb_pop3dnd.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_pop3dnd.py,v retrieving revision 1.13 retrieving revision 1.14 diff -C2 -d -r1.13 -r1.14 *** sb_pop3dnd.py 22 Nov 2004 00:16:39 -0000 1.13 --- sb_pop3dnd.py 21 Dec 2004 23:21:22 -0000 1.14 *************** *** 120,125 **** __implements__ = (IMessage,) ! def __init__(self, date=None): ! message.Message.__init__(self) # We want to persist more information than the generic # Message class. --- 120,125 ---- __implements__ = (IMessage,) ! def __init__(self, date=None, message_db=None): ! message.Message.__init__(self, message_info_db=message_db) # We want to persist more information than the generic # Message class. *************** *** 289,295 **** class DynamicIMAPMessage(IMAPMessage): """An IMAP Message that may change each time it is loaded.""" ! def __init__(self, func): date = imaplib.Time2Internaldate(time.time())[1:-1] ! IMAPMessage.__init__(self, date) self.func = func self.load() --- 289,295 ---- class DynamicIMAPMessage(IMAPMessage): """An IMAP Message that may change each time it is loaded.""" ! def __init__(self, func, mdb): date = imaplib.Time2Internaldate(time.time())[1:-1] ! IMAPMessage.__init__(self, date, mdb) self.func = func self.load() *************** *** 303,313 **** ! class IMAPFileMessage(IMAPMessage, FileCorpus.FileMessage): '''IMAP Message that persists as a file system artifact.''' ! def __init__(self, file_name=None, directory=None): """Constructor(message file name, corpus directory name).""" date = imaplib.Time2Internaldate(time.time())[1:-1] ! IMAPMessage.__init__(self, date) FileCorpus.FileMessage.__init__(self, file_name, directory) self.id = file_name --- 303,313 ---- ! class IM.APFileMessage(IMAPMessage, FileCorpus.FileMessage): '''IMAP Message that persists as a file system artifact.''' ! def __init__(self, file_name=None, directory=None, mdb=None): """Constructor(message file name, corpus directory name).""" date = imaplib.Time2Internaldate(time.time())[1:-1] ! IMAPMessage.__init__(self, date, mdb) FileCorpus.FileMessage.__init__(self, file_name, directory) self.id = file_name *************** *** 538,543 **** class SpambayesInbox(SpambayesMailbox): """A special mailbox that holds status messages from SpamBayes.""" ! def __init__(self, id): IMAPMailbox.__init__(self, "INBOX", "spambayes", id) self.UID_validity = id self.nextUID = 1 --- 538,544 ---- class SpambayesInbox(SpambayesMailbox): """A special mailbox that holds status messages from SpamBayes.""" ! def __init__(self, id, message_db): IMAPMailbox.__init__(self, "INBOX", "spambayes", id) + self.mdb = message_db self.UID_validity = id self.nextUID = 1 *************** *** 546,549 **** --- 547,560 ---- self.storage = {} self.createMessages() + s_thres = options["Categorization", "spam_cutoff"] + u_thres = options["Categorization", "ham_cutoff"] + fp_cost = options["TestDriver", "best_cutoff_fp_weight"] + fn_cost = options["TestDriver", "best_cutoff_fn_weight"] + unsure_cost = options["TestDriver", "best_cutoff_unsure_weight"] + h_string = options["Headers", "header_ham_string"] + s_string = options["Headers", "header_spam_string"] + u_string = options["Headers", "header_unsure_string"] + self.stats = Stats(s_thres, u_thres, message_db, h_string, u_string, + s_string, fp_cost, fn_cost, unsure_cost) def buildStatusMessage(self, body=False, headers=False): *************** *** 596,601 **** msg.append('\r\n') if body: - s = Stats() - s.CalculateStats() msg.extend(s.GetStats(use_html=False)) return "\r\n".join(msg) --- 607,610 ---- *************** *** 612,618 **** msg.date = date self.addMessage(msg) ! msg = DynamicIMAPMessage(self.buildStatusMessage) self.addMessage(msg) ! msg = DynamicIMAPMessage(self.buildStatisticsMessage) self.addMessage(msg) # XXX Add other messages here, for example --- 621,627 ---- msg.date = date self.addMessage(msg) ! msg = DynamicIMAPMessage(self.buildStatusMessage, self.mdb) self.addMessage(msg) ! msg = DynamicIMAPMessage(self.buildStatisticsMessage, self.mdb) self.addMessage(msg) # XXX Add other messages here, for example *************** *** 912,915 **** --- 921,927 ---- self.DBName, self.useDB = storage.database_type([]) self.bayes = storage.open_storage(self.DBName, self.useDB) + if not hasattr(self, "MBDName"): + self.MDBName, self.useMDB = message.database_type() + self.mdb = message.open_storage(self.MDBName, self.useMDB) self.buildStatusStrings() *************** *** 943,947 **** "spam_to_train") spam_train_box = SpambayesMailbox("TrainAsSpam", 3, spam_train_cache) ! inbox = SpambayesInbox(4) spam_trainer = Trainer(spam_train_box, True) --- 955,959 ---- "spam_to_train") spam_train_box = SpambayesMailbox("TrainAsSpam", 3, spam_train_cache) ! inbox = SpambayesInbox(4, state.mdb) spam_trainer = Trainer(spam_train_box, True) From anadelonbrin at users.sourceforge.net Wed Dec 22 00:06:23 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 00:53:50 2004 Subject: [Spambayes-checkins] spambayes/spambayes/resources ui_html.py, 1.33, 1.34 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/resources In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv12120/spambayes/resources Modified Files: ui_html.py Log Message: 'Compiled' version of new ui.html which just has more comments, no new functionality. Index: ui_html.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/resources/ui_html.py,v retrieving revision 1.33 retrieving revision 1.34 diff -C2 -d -r1.33 -r1.34 *** ui_html.py 10 Aug 2004 14:20:25 -0000 1.33 --- ui_html.py 21 Dec 2004 23:06:19 -0000 1.34 *************** *** 6,155 **** import zlib ! data = zlib.decompress("xÚÝ=ksÛF’ßS•ÿ0Á–\015©–\")Év\022=¸kËNœ;;öÙÊ¥¶R)Õ\020\030’X\000‚‡(nnÿûõcf0xð!\ ! ;»WuJÊ\"\001LOwO¿§\007ºøê廫뿽%^_¿}#ÞÿôâÍ\017WÂ;\032~>½\032^^¿ä\033O†ãcqˤ\ ! ˆÊ(Md<\032½úÑ›|ùÅÅ¢\\Æô[É\020—Q\031+\021…—\036}ò&\0373¹|!ת\020?\025*\027?$¥Êg2P\027#z\000Ç,U\ ! )Å¢,³#õ[\025Ý]zW2X(O\004)<œ”—^’\036\005|iÔûü«û,ÊUጸ^T\003\0018ÿ‡LÄñ·_Åx|Fÿ‹ï\ ! ß^o\002CÓ\036]\001Œ<{¦\037ˆeU”G¹º“q\024Ê\022.À­¢LsƒYQ®‰¤i\032®Åïb\006\020ÎÄ·ãGBæ‘Œ\007¢XE\ ! E1\020\013\025ß©2\012ä¹XÊ|\036%gb,þùå\027¥œ\002óö\030‡\017ÏÒ|\011Ï6\000|ù…<‹£ä\026®\007iœægâOcø\011è¦<\ »‹`ýT¸áî\"½ƒ\005ªï=\033?\031Ïfxo¨Y!¤Fîh¥¢ù\002pœ¦qHOÜEjE3w\036I\000O\0313véL\022šd*ƒ\ ! ÛyžVI\0103\005c\0053‹L†a”Ì/ŸÚG±š\001Œã§ç_~!ìÏ4ÍC•\037•i\006·²{Q¤°\036b\032\003̾ç¦iY¦\ ! ËΣ„7Ê-!D8\027Ñ?\024ïAÄåÈNΘÉû\006õ¯¼FK‹¼ž\036@Ž³{½è 0 \035ÌéBô\ ! Jè˜d°ód¿Àê‡k‘Û\002Ñyh\0330¤£Ì£Lݤ\011/hƒ¹*ÀÿôƒUô\032ŒÞë>Á9\031?:·Ð¿!èçýl»\030\031\ ! [q1Ò¶\023>\022\023Ñxâ\007®!ã/ÂèN\004±,ŠKŸ5ÈŸè;E\006\026\016G€m\000ƒæM.¢å\\\024ypéó•á<šù\002\014Ö\ ! <¹ôå´XFa\030+4yœL‹ì\034ð\000\010\015hz\"^\0053Qs²,OÃ*(”ˆ…ÿYM]\003OÏœñ’1M—\012—Ä\023‹\ ! \\Í`\"øîO^ÿ\027#Ù?\035èZBs=ž—碊†èvš¨7¿\000Ø£È9\004±”QrÅêìi\012=­ÞÌç‹Å“É\017hù\ ! ‘0gX–'õ\002d“ëETˆY\024ƒÑ¿\010ÒPM,\026ôm B5‹\022`B¹P\"NÓÛ#™„G3¥b\006‘ÎèN…N02<2W\ ! k\026~T9Xá¡\020×pù\016ÌZ\025\014 ‹@\006\013\034A.™§\013Á'ä\012Ü„\022ê¾Ìe€\002\016\023ƒÁJ¢¬Š%}/\031B^%e´\004\ ! ƒ•Šp\015ü\004G\022ÇkÁ‹©\010\021\002].dÙD‹Ç3n¢À_…¨2q\004Oi®\010˜\0324\026þ\007\023\020«²T\006S\036\033¤Ë,M€Û\005\ ! ÐÆ\014|¿~«âð\015è¤æ¡ˆ˜{KÀ\007í*¢\001ÈÝE¡ÁÀ \010¦ÿ(þ\035,\017\020šeh\016cÀ3ç1Ky\013ø\001mŒ^–\026\ ! E\004ÆvÈ0^+gfZ^Ò¨D©°@ÞLUƒ{\013$*Ñr@xF¡A¸”ó\001±{ª€@œT¼_—\013°!\032;€«¹>¼\030e\ ! ®<=G¨°hr™Å„É\"]1¾«4¿%g\017‹\032ñSaTNÓ{p\010å‚\001ÀÅ6.\000‚/i˜0@ßÒ*x\021%YU’6Ôx\ ! ¢\\g\012¢5\020\037\017\004.®”½í\0114k—ÞñØ\033iͪ×LÀ\"\027bV\016³j\021‚e›çr¹”%\013ÙYƒzTJ2`κîï\ ! Ù\030ÿ³\026.\0036˜‡û\034ž\015€Î]çc‡£õe“|é‘Á¿\032{\023ø¹\030á\0359˼t\010Ûs\034꾸\024i¦’\003OÛ\002o \ ! ¼¼ô\016ÁÉðàpOHa\032\000 \032!Ú)0W*?@ û‚Ér0+\010lX¯®YøÉã¸<ï_~»ÄÛ\004a„v÷b\024Mö'\ ! ÉÁbHЀFïj!“¹\012½ÿC’\014\012m’F g\035÷¡uõcJ’­U\033uŠìD\025/·\"ß²ìè³\012À]\025‰¯í/Z\030\034\ ! 8“\020VËi\012¨g²(Ñzað‘ÌÑü@T\011š?\020iÎ\023à]‚_[P†6ËÓ¥(‚\\–`Ï\"F\013lëó$ìº ´#\001ç5\ ! 1à\000Š¦ÀðO×ƪÂm\000Íó5?\022\001ò\011'G`¤ÅB©r JL\003AÛñ\036›j÷†Xƒ\021Næ\025\022\016V‘T\021\014b¢\ ! Vµ¹m1鈬4;ÁBYl\023èÒÀÛ¬…’źm<_¨\030¬%>ÂV¾ãWkfÁ\023)à¦\023\037Œò^K^Ø\0310\022È)™\ ! ËLœà\006|Þ„â¢ð\005ZW\007''\016Ó·=#M\034éë¨Êþ}\021¨8†‘\001&=~m³Ê|r\001A¨\001\007w½Öx}ÙGíÁ©\ ! /ý\023\010¡hjA†¿\014A‹\000N\007¢\037 \\Á¼>j\004E…Gù-ø\030úœ€\000è±\033\005²úAi€ù\034n\026¥,«‚ÃM=\ ! ðÉØ\0077†/}61§\023â!žÎWM4PЊÕ\032x9#(*C')\034ƃ\026¼M‹²?ä*L \005ÒSÃ\001å/`:à\036h\022ª\ ! ‚‘I\035‰Õc@››O;¢Ë™Õ=<ÈÎ;é\015\025Ñ\004È»\024\\]Xe1øG$ÌÄ\"Cc‚Ì\002Â?(+šf\035´OóQ;îÝ)\ ! ·H\006¤\004\031Y&#»6>Æ;xãó¥v· Ú\011\027\032µ:ü}\015W7\011ï\003ÄUäéŠg;ÝGt\021—†àžœÔ‚{z²¿à\" \ ! ö6ÛÅöcšçë\001Š\016Åx(S2Y‹\"SA4‹\002^'y'£˜Va–êÈVƒ\033ö\"ÒbY\023;-\022\016>*.r1šì\013\001é\ ! #§5­æ¹Â i\027•?Ì(>œB¶§î”Ž\025×\020Pß!I\020ÅÚ\0020Ô\023+\001äø\032ª)±ì\024C\014Ù\005¨\016ÆÉ\010*\000ƒ\ ! \013af\000Nu`/Èâ¶/sÁ\037È\000cJ\024\"0\015ÈQ@\035>Ê$@%ÿ1-Uø,ÕrŠ\025\010²\002`^pŒƒ\021º¦4†¸^áC\ ! 4\012|â\012ÌÌo•*\025…¶\014Q\016~Z\001dÈ\000\006àwm&ÁA<\001š\000ýz¬X¢àAnq«ò¨ÄŠêjn\031-G–)‰\ ! \011â\017³\032\022’XýVad\036¨\034ãGF\014oµ9N’—à-¿i\004Ó)È\033\004\006˜¿å@ýL\024Õt\011\030p \000œ¸‹$q\010ryÊ\ ! Þ=, žF\005P\020(`ë\\\015\023UŽ0\027½Uùè/\030¯g7 <ÏŽ¿\036Ÿx\015%À!ßá\020AS ´.JµÄBÀ\000×»€¬\ ! \012yW˜°\000Â#\024\0060î\016#)Þ%\032€mË!ã€d$\0072€\005ÓªX\013\006\000+¾BªéaHV\000Æ]”Wè>jH3\010›B\023õ\ ! \000\035\0347LÓ9<§‹~\020*LQo¬)\026£†CÒi›Y„(ÁB-ÛxXDÌ=UŠ)Ÿ‰\006o\023\020hô:˜\010\011\023OáOQÍçˆ\ ! ¦‘x–‹YtOÆ\002W+VĶ¯S¶\030˜ø¨\\Ù¥¯‘\014ØœØÁjàT\037P…ìÊÖz>\011 ¡)]Ôó1GpµH£\ ! `¡Uû`‘¦\005‰¬BÕ³‹D,\0053RC«\012#^°€1Ä{J¨¤A†‹=8c`m„ÁK†c\023\017Þ¡a]E…bÅ\002o\015\ ! Á`AË\022G·\030úršïéÙJph€…\030\016‡¼m6®µ/v*LÛ½.Å1÷å\021Ä\013¸wrçø`7¬#^á*•8œÃoXJƒ\037Z‘aÃ_±±§\000¶Êp\027^DeA\014˜JÖiˆ^b$‘ø7ÚÔµƒ}cø\ ! °Ò\014\031y‰{\033XÚês58¬…OZÅ!\004.Rµ#1\015ªgTLWÑmäæàø\030eÂ:´ˆUQè*Eí‡U°H\"ÌZ\ ! ÀÊ™±\012¡'&(¸Ðé-„?\006û&Ûk\021?À«+\010ƒMòÔP¸,®8\035 Ê\"w¡\000{\0200ì\011˜«Då\030á\037j§Áâ\ ! v\025W\034­8ˆÔƒ ¦!W¤—{m\"\015ƒæ\001z\034\022ožÓºÖÆ0-%‡À ­\011'ÇÈf\010;.V¸O\013³¢b¥rPËã\ ! JF”xá\032plÇnÓH)Ç8è\035ñ÷Ú8Tg10P3ä¿G\020)0ñ¥\\\023\007\020\030@Æ{?¢½2×µ2\026F\033Ë4sµ‘\014$\ ! ŽYâ\012¼/ygRQ\010ü).`<\012“³¢õã\020VSN›&:ÃZ9ä‚^»Ž\014\030^ûR\010zdU¦vC\011¥€}9±ÀÚ\032ô\ ! öç”'‡¡qeÎ,m\023ôAAz^,úŒP‹ì!KNÖ\012þ;Q;–A\013P'ø\005¨DA±Gà®Çt‚È[¥²‚scDE§”\ ! ø˜ë¸8°nz`*IänÀ\013Rñ\035{8]\014…Ìr`½\"­Œæb\001·A~\023pò\013\030|9S°+\013M,†žn&ó¶*\\±E\ ! Œ×µDÙ‰{ÉÂqI…\005\031dÂn40mÀ¨i@±\021\"ĹË\000ÃX\004¶$\017ª\013¼b…\011û\014Ì\026\012Ê\\–$ºÀ©,å„­à\ ! „\002C`\032h\020@H4¶93Ï\005\021\034ŒYi•rcâ;‰‹,\016·{'ò\027\\\0004ë\004K‡[âÆJ>þ­JËsc§…|…œ°\ ! u‹\035k§é‚EJ\023X\014í4`\035’†ÀšqŠÎ¹{ÀxU‚\013\004\010®\016„Ã=´\003{=n °¼_{Ôñ±‡r¸C4‡®À\ ! \012\033;˜-Ì$ÕrÎjZ×þµeì‰&ɱt¤Ï\030\020ãÄy‹\003EÇÍž‰hkgiŒ›RØÙRp\024B6Ï\025\027\014Þb¬¬‘\ ! \026‚ã›Eó*çòEK\010t\021…ä»^\021l5$w#Å\022\033\002Ð9OïÅÁ»ªÄ\024Z¼ºGºÀïá&\036c¯\013(&Vó§U|ë³\ ! ŠRþЊém\000!Í´\0150¸¡*\"'˜7™…ÉmÈT\012qe¬,Žfq|5yÅŸ©=@=\003Éb/AQ3\002\007Á‹+*Ãé\ ! V0N³´3OúÂHT½×˜ø !×^ÍÞø˜é;\010…§nóû\015ä²X1\032\030†p)£ŽèÛŒ4qÂÀ”Ù\034n\000\003^qöV\ ! \023Wë§öYD-,\"­\002Q´WHvœ\006ÒåÒ,Š»\021¬Ž&ùY\024\023»\020\034þ0ÂÄ\010\026[¦‡‹\035XnNÈqR´EfY\025A\ ! \036M7Ô\\È€b·M\006¡\005x…Ù\011îyG\030ÓWe‰—\024ô\031kVûž®ý‘¬õ \027yÙRÄu+èaöý\014°\021ÒU*\ ! _kŽ‚-ˆ €Zã:¡m0s\023\036•.`.È(gœ “º[¦^c\004cRí¬¸}‡\003õº\024ª¸hDùSx‡;\016„\022ñä\ ! 7ÄkÐÌ'õŒ\\\027…\005\0121ø'§”«9\026·…bEÇT¬/_nC¢íŽz{©–E^X,¹Ðzš\"\025'%YÄe±M.\027r\ ! dãMÎ[\014ÓŒ]àÌ\033©\003'\021GE»¦‹…/òŒ\030\017踡Ö:d¶­ÙðsÆ&›4ĉv5±N–;0\006„”Ì@Å}*S\ ! oÑNÃñ\030?¼\024\007\\¨¬‹5\035©\035f4ª Ì\017ʆÞv\"1Þ~1ÚÖÞAF±OBÉ¢êDË),¨ÖóS‰Lq\033H\ ! ½Š\007rVr=ks)í°\026·$¥¤\003T€„\021à,{ÜøÖ\012÷s$æ\010\014R¬Â# †vSýzo µÑìì*Ø. |©ë\012—\ ! \036ï7™ýÅ¥*\027)\014úþÕµ»stñÕÑ‘xË›\012Ü6ƒìYa.‰©x:›a\007Í\024ËÍXº\033ØU^‚›Ç6vgó\001w\000\ ! ʳö\0361õ\025aËé¥\007¸Äë£25}E§cÛKTÊ£¥Z«ü¯Ñ¢šC6=Lþáé^5O7oŸ<{ša“œbÍöþÒ\ ! {æ5ö‹:ÓMƒ g¦O\002[(*ƒ0Ü,Ò\034lž7é¶v_¤\031Ùc=\031wì{\020ˆâï‹\021ßÝ=®ÊçÔNû\023ýÞ0\016\ ! R:B˹|tÔØj~hãÁÚ\017&\037±º„\021§QÒv¿Áfx¼Áý\035hÜ\031\017ÚðDïòº½h¼$¨º7§æî‚£Ž8\ ! 7>CÒÆM‘x(™\037Ù„~\002¥L6Á†¶gš6{YSöž·1u\030>½¡îâüLü¢÷7Á”/—¸o‚\033«¿¶‰~òt\ ! Ü$úôóˆÖ\"ñ ¢qIÁÂC\030“Ë\014\024m±.°FaVY\033B¦^¹¡\006\014\022Kæ\014Æ{ܲ¸ƒ¾'\036’£gü\014BŸ—%\ ! –ÉߤsŠ%?Yžq°¡T\022Ìö‚ó\0235\005_;Fm¥2Bƒ^!]y›plô\027i\036ÒÆ+•ÈÏ\002t¶¹·\022žÃ\"‡f\ ! ÂÛ‰_£9\013ÛVóåC|''”FB6D4ݼÆïž>°CJíZAóÝœœ‚ïÍ®©Þó\024\014ÙÛt´ã~ýR•à]‹\ ! 6¿Þ¿{JQÎ\032÷\017ÍžÖÅtÒ\032þ\036œwñ‘ZZ½ÉñññXw©]Œ¦“A\023$=O\031FÚ\004ÄvÀBÑý½X²v€\015\ ! [ëY÷Å™Ÿç\001mº\023æp#HÊ=Š³\036wæÎ/iàG\035d{\023—†aÏDÝ\0318\000.\030®éÊ´”ñ~³5ŽºÐ‰\006¬\ ! º\026n\035jËÌY“j‰\021lkB[ˆƒíÃ^wG-v\016ú‰jc­a\\0k\023yüàΑÂ$\006-J\020ó³Ö\034ˆy\023~sÌ\ ! ëîE{D›ÝfóDWiߦ¹›¥\016‡ÃÖ±¥æI¤ºƒ\004\014—>Ç û3uïë6\"÷ßÖ\030·íÑÆžóâ:Ò¶Æ[\ ! sü\013Ï\004ú£^µo\024£Þàé,ç±.ŽÝM\022f\000éÞ\033› µ¸º\017Hî\005³íAª%\\z]4ý>¶ÚÔ$è.W9\031vV\ ! fcãñ\033õ1\022µôÚ}”ÆÍœú (ĺ•Ì\023²QMü~æËg¦¥)JÀ¤AF—m2ŠaÍW.ŽÓM\010Z_³Ó­p\ ! \002yí´\027qß<¡[ß4\030_7ò_¶ïTj,º{û\0216¡ªBsÕ,:W\015Ý\"‘ÂÎ\004Ü\033£Â`êô—ðH»¨õ:2b¾é\ ! w2ÏÓ6¤Ý1{0ûs\001™ÖߪÏ\017\020W­×Äâ2î\012Љ:m}LÁÂ\026—Òš`}ºÍR%·…ÒÕI·‡BW¢Á£Íö é0Õa˜éœŸ§=órÂAÙNPàÑ++[\007˜\003ؾÙÖŽ*ïvšÌ]\037ÛÆM\ ! Y]\034APº\\†ýd\005÷¡q‹JaëMXä¼W»\013\003½¥ûùó!×÷#š¶Ï\037Hð&§×>YÐ=\003bÓŒÚÖ¶-È£Ì\ ! …ùÏèïòNòU2\032]|õËÕËç×ÏÑgȪ„u7MØj\036àHzßD\031,®S}öð÷\032Ÿh†ýpA…u´!×ã\037\ ! ?\026Í+ÃX%sÈæ'—âä°\036ú{“‹¤e—­¡¿\034ÿzÞyL\034Dðàø\\D₆é\011àŸÿ|Ø|ü÷îRQ³\004\036U\ ! \007 8ú—è×!®ôn\034øgþaÏë\016P;ÒPty)N‘^{\003ðÅ‹´-öøq¿˜˜9õ)ÈKËÝa™¾IW*¿\002G\ ! ypxØ\035ýûv€džÁÞÃüy¥zhøgó’óUÔ¿F£_­Ï\023‘°ìi\015ß¿ûxíOÚ©«oêuœ§»oËØ×@\ ! Ùz1Ïø±š\032·¾á|\012Dc\023[uonÉã\013nζ\004VMP&<ð¹|)u¶\020Ñxm\005\033\017þòºó\016…NV¡›\ ! ¶A\030}÷\015áŒÊy\020\005ÐÖ麹\016“);õ×–}i%“nxÄýÛ7‹\006çwÒ?iÑö\027tÍ—\006š‡\013õAÑ«\"';\ ! ±ÚÈì$¥²\033Ø[\004ÜŸ\"xµ\021<«Mrá\017 Äâp\010Ì€WwÚAÐ9ú4h\024°\021,n\007þdH\020”\021\034Ú×þd(\030\ ! Ô\021\030Þ\005ïÊÃt²ù\\[]Ž\011 \037øc$@‘\030|ÄO{É@#\017ââš[\037dð\037ÒU}$ͼͥÿ\005&-Ýþo4ÍE\037\ ! ]öHoNÏôîžX‹\016w“æ~ˆ‚E¤ÄëˆúçñðzNWþê4câ‘ô>\035n)CÏB¹èb\007Š\012_;H›üȼ\014Ç\ ! ë7HT‡¢ý\011^´ú4I\0179êÌÉ\031qŸ–_` ò4¸ýJˆoŽ\016ÿ\020k¤i²¶\010ÄX–\003q|,>ªLœŒÇ§âøÛ³\ ! ñéÙñqÏ™L]÷àê¯ß9ÅÙ\011Às\031F©‰\003M‡Ç\031ùÿ|õ·þ0\020ÑÓg\006LñØ×ßm\010Øý÷_\004š\033¯ŽJ\ ! éû¿\027\005¬¥¹1|ÿ÷\"@õ?\007\001üî&;\017î²yóÚ\0023\036ŽÇz†h§ãñoo¢•Ç j\024È~çÞc2âÿÓy\ ! \037\0227\024wǘ®i¹¥bE;ê±Ln?è½\0217çØñµ\013‰\012\010{@ÚCÉz*ä[R.]ôЋÆß6¬ZoE¬“\0269Çâw\ ! Ö‰ª,NeØ­\000 Gøžgã_âPCü\032‘°PO\021}Ë*.#}ÔÜÖ°K€À<Ãiø\\\013´Sßðy·Ù?\0357\015Fk\017§Ïô\030IèoásV±¶=õî nã\ ! km\017ô[<2Ò²ˆ\002C\015±è\006±ñ›þNgô`Lùƒ/ºžN£M\000}Ðb*µ‚÷7u\024»\006Ã¥¼¿1\007Uë”ùä{Ý\ ! †!C\036\001¿‰åTÅÞä\005~\021Z.úBѦžVQ·/öÑ®I14™Ñ¥üá´n£ÑÌjÈüYÿdJs5W÷[Ȥû\ ! ÿ^\032iJCà\007Ý;úÊöŽ~2©àð1^\012daš?|ç’¥¸qé&Á\006§æoFÚ\031ePÿ.\011,”í­…ç°ÍÀ{\ ! +ï£eµ4ǸûaÖä:\020\014¹Î%Kîñ¸a™z*‘dªž¸eb«£í$5\" M‰q3Êv›}Ÿo›½šM»ï<\ ! àí³+²ÅäëíïÖ‹qñ}=ì\014¼ã§ã†EN>rOo”Ð>ût§‰×Â\030òK†¢ÐkË]×Æöoˆ7L Û\035ÜÍ\ ! Q· ¢»ÝüfëÛ\026UØlŸlï\001Ó»%IOƱ.à\024ŸˆÂ[»gMP\036„\012wiÙwÛî‡@Ã~½uÚ·÷h²èJÙ\ ! \006ÔšÖ¬cÞöBô6«Í¨Ï±U\017·S'ã¾\010êɦ\000ªÉÞþ™ÑHx6ùH¶Çj'­XmKpÆêß\033‹í4|{\006¼\ ! xR³èo\020°·QúÑ\036CÄ´Åî\\·ZŠð^VØ\012}|rº¡“«\006µØ\014ia\001=yúl\003 ÷ôÖ&:Ï¢O\033´V\ ! Û—7ñ9›‚7)d\033ql»õ&ãá×ß|»ažWF“ðaÆÛà–ÓËH7¯\033\026ºæ@w^wêÊu†àA†àMÌ¡\ ! àŸSçÕ\034=Å\010ëH\036uŠ9\006ÄŸôy°Ï\004òú³a8+×\007©§@C-h»ËÜúM”ÆÐû@¦Ôµ«tCƒ¬ðú,\ ! ¼ûŒYØ!ONŸí?\015Šš?aIÛG¯÷/阴ùƒ±™}ÚÝzÈi\001¢>˜¬^3½ïæ9×PGˆA¶\013Õ-àDó\033\ ! V¤wð1ÂW\025ô³ÏÝ$Õ’\001>em3š—MºXWªp^/J/¼Â²\002+\033\036\023Ç'Ä<Õ¯¡\034˜÷ôàû_è\000\036½¾\ ! K¿,óð¬sôÂÎÒÐ×vK6=ñºw?h»#Æw3ƒÕaEžNz626ˆÍVp\015]úà^Ó›ã€côÚ\007€m\014æ\ ! 7òôŽnõ“w\030LÅØþ\011Í3Þ\026\022^é׋ó™Ä¸·E»[\006ïš‚öxê£m\017<Þ5ˆ«­Q'»\031µÁ4hýý \ ! Ê*¯ßd×ú\013\003|Þ–u_È„:ÓÎúU\037zÎÏx\\¾3e>]À3jvØÿç\010ö1VÔ·ú\035@Û`§ì}¯S”ã|ì\ ! &s“3›ÞÒ\014£@s‚\016ËÓž[ŠtbWç¯\036x“úÝ]\022ϳ\027æHúeOÔË—™þv|A\031¿VÚ¼[A4;I\003ˆ\ ! D¸ý.çŽGcÚ¨w4· è¥&æt{72ѽ\030Å{Y.\022êz\030é\003\001£L_j\004+;øxUÿá\007zäúù‹7¯œgúÏ\ ! \\ôœ±¸þàŽ‚\020\027ôõ¸\021Æ\\¿ÔÒí‘Zzn¿Â¦‚…\016üßà/óW4º/ ½~Ùœg÷©3>—sòL'9x¿q\024\ ! -“ùò\006ÂÅ›¥ÌZG_Ú“é.”8ƒÕ\016ê]ŸÍMm\033\001ô¼ŠÚ¾¼÷¤Ã£lr@G÷è\020¼Õ=SâîÁ\027>~è]­\ ! «éCÖjS\027þ\037±p¦\022~Œõ£=§4ÝQ¼ˆ}\026Ö]~“¼:\013C_õY§;î\022ØG\024ºDÃ`“ªR·pÉïÝèu\ ! \0328þ¿J\027ÈÖI[¶œ¥Õø˜µ\026Z¿\036G\020/ÏzH3`lÕÌ?~:n\001Ñ|©M\037\003Õ-,\007´Þ‡Öbî&G›¶\025\ ! Iÿ\"ÜqxÌ‘•žggi\014¶¾\025nÕÊé`\022ºÅÂ6UŒ¡½Ø¨bt›ÿzÒ&[â>ó\035}\026Ž›kk\024¡²ŸNm\ ! S‰ãñxüÇjň|k7*â¿Á@\001Ÿ\005âÔ“XìnÌ™Ìæ\011Mfâ\002Ó`sXSÞ)¯®OX¸ýû°6zÂw`aÙ\016/\ ! Ôᓾ\032ª™äJZ;†Ú»\036¿\033ë\017ü \036Âà™z(؈¿ÁÖìD2Çjd³´(=³WyÃg6v ß{\016ö¹†/\032Ç\ ! «\0302ýM®OAZÝg*p{KÆ›\021‡§>\003ñWÎ\034ŸŒüCcùbQ•aºJúvTøøUë\011îƒÔ×(x\025ߧi8]×\ ! 0JOé\036+†ÏõþLšâÑf»5ƒo\036î6hëÜÞxŒñøQ_çus»¦÷/εœ¿Ê\013J\033þ›?\010ì˜2ƹõjt\ ! nwò\031_LÆüFz¶ñÏÈI÷Ì°sb\026Óë\022’\000<’™€\022\005ât,ŽŸœŸœž`kà‰FdØ=Ìéë7a®V«¡\ ! -\015Ó|>bDì÷vûU‡-æÐ Ÿ3\007sêõ\035EÃëÙ÷šõõ`~Êߥhý\021­$Mð\017û¡±vír‡€¦«n‘ã\ ! Ê(åAè/¼]åz*ééQú̌قJWÝPSÝÇ7\000=†%<\027f°oÿ …ÎøÝw®[‹\002Š’†kþã‰ü\007h¿ü\ ! â\001Žè\013b") ### end --- 6,156 ---- import zlib ! data = zlib.decompress("xÚÝ=ksÛF’ßS•ÿ0Á–\015©–\")Év\022=¸kËNì;;ñÙÊ¥¶R)Õ\020\030’X\000‚‡(nnÿûõcf0xð!\ ! ;»WuNJ\"\001LOwO¿§\007ºøêåW×{ÿJ¼¾~÷V¼ÿéÅÛ7WÂ;\032~>½\032^^¿ä\033O†ãcqˤ\ ! ˆÊ(Md<\032½úÁ›|ùÅÅ¢\\Æô[É\020—Q\031+\021…—\036}ò&\0373¹|!ת\020?\025*\027o’Rå3\031¨‹\021=€c–ª\ ! ”bQ–Ù‘ú­Šî.½+\031,”'‚\024\036NÊK/I\002¾4ê}þÕ}\026åªpF\\/ª\000œÿC&âøÛ¯Çb<>£ÿÅ÷\ ! ï®7¡i®\000FžÆ=Ó\017IJ*Ê£\\ÝÉ8\012e\011\027àVQ¦¹Á¬(×DÒ4\015×âw1\003\010gâÛñ#!óHÆ\003Q¬¢\ ! ¢\030ˆ…ŠïT\031\005ò\\,e>’31\026ÿüò‹RNy{ŒÃ‡gi¾„g\033\000¾üBžÅQr\013׃4Nó3ñ§1ü\013è¦<\ »‹`ýT¸áî\"½ƒ\005ªï=\033?\031Ïfxo¨Y!¤Fîh¥¢ù\002pœ¦qHOÜEjE3w\036I\000O\0313véL\022šd*ƒ\ ! ÛyžVI\0103\005c\0053‹L†a”Ì/ŸÚG±š\001Œã§ç_~!ì¿iš‡*?*Ó\014ne÷¢Ha=Ä4\006˜}ÏMÓ²L\ ! —G\011o”[Bˆp.¢(xîôôQƒìšËÇOéÆR\025…œ+^¯\022\031ÚÂWÝ×$äÌ\007¸Fc\013\025 \016™µf\034]äþô\ ! Í\030ÿ;\027«(,\027 \007O\037¹\003\021c\000Ûæál6“j|ÞƒˆË‘œ1“÷\015ê_y–\026y==€\034g÷zÑAa@:˜Ó…è\ ! •Ð1É`çÉ~Õ\017×\"·\005¢óÐ6`HG™G™ºI\023^Ð\006sU€ÿé\007«è5\030½×}‚s2~tn¡CÐÏûÙv12\ ! ¶âb¤m'|$&¢ñÄ\017\036]CÆ_„Ñ\010bY\024—>k?ÑwŠ\014,\034Ž\000Û\000\006Í›\\D˹(òàÒç+Ãy4ó\005\030¬\ ! yréËi±ŒÂ0Vþhò8™\026Ù9à\001\020\032ÐôD¼\012f¢ædYž†UPþ —\015\013ÿ³šº\006ž Ÿ9ã%cš.\025.‰'\026\ ! ¹šÁDðÝŸ¼†Ÿ\027#Ù?\035èZBs=ž—碊†èvš¨7¿\000Ø£È9\004±”QrÅêìi\012=­ÞÌç‹Å“É\033´ü\ ! H\030È3,Ë“z\001²Éõ\"*Ä,ŠÁè_\004i¨&\026\013ú6\020¡šE\0110¡\\(\021§éí‘L£™R1ƒHgt§B'\030\031\036™«\ ! 5\013?ª\034¬ðPˆk¸|\007æ?­\012\006E ƒ\005Ž —ÌÓ…à\023r\005nB\011u_æ2@\001‡‰Á`%QVÅ’¾—\014!¯’2Z‚\ ! ÁJE¸\006~‚#‰ãµàÅT„\010.\027²l¢Åã\0317Qà¯BT™8‚§4W\004L\015\032\013ÿƒ\011ˆUY*ƒ)\015Òe–&Àí\002\ ! hc\006¾_¿Sqø\026tRóPD̽%àƒv\025Ñ\000äî¢Ð``P\004Ó”Nÿ\016–\007\010Í24‡1à™ó˜¥¼\005ü€6F/K‹\ ! \"\002c;d\030¯À•33-/iT¢TX o¦ªÁ½\005\022•h9 <£Ð \\Êù€Ø=U@ N*Þ¯Ë\005Ø\020\035ÀÕ\\\037^Œ²\ ! Ž‚èY–kzÞ”\"Láé$…¹0’\001Y(1T\003ôq\010¢\007#\022…k\007â\007ß\022b$ v„˜1\ ! \030+“mDŸ\023\026ê^.³˜X¶HWŒÄ*Ío)*\031#~*ŒÊiz\017ž«\\0\000¸Øf\032€àK\032&\014з´­¸ˆ’¬*Im\ ! ëGÏl#µs×KÚáè&Øw\\zä™®ÆÞ\004þ]Œð\016(\007(P^:„í9\016”¸\024i¦’\003O\ ! \033-o ¼¼ô\016ÁáÊðàpOHa\032\000 \032!\032T°«*?@ û‚Ér5\0046¬W×,üäq\\ž÷/¿]âm‚0B\007q1\ ! Š&û“ä`1$h@£wµÉ\\…Þÿ!I\006…6I#³ŽŸÓºú1%ÉÖ6\010uŠ\014Z\025AÐaE¾å‚й\026dB\022_;\012\ ! 4…8p&!þ—Ó\024PÏdQ¢™Å()™£„ð\0274 Òœ'À»\004¿6õ\014m–§KQ\004¹,ÁðFŒ\0268çIØõ•hG\ ! \002NÀbÀ\001\024M‡š®ù‡Û\000šç!·s$\002ä\023NŽÀHÿŠ…Rå 6‚p\017ÈMµ\037F¬Á[$ó\012\011\007óMª\010–;Q\ ! «Ú/´˜tDu¡,6È\011ô½`Z×BÉbÝ6ž/T\014Ö\022\037awÔ\011\000jfÁ\023)à¦\023\037Œò^—SØ\0310dÉ)ë\ ! ÌL@ãF¦Þ„\002¸ð\005ZW\007''`Ô·=#Mœ’èðÏMS|\021¨8†‘\001fg~m³Ê|r\001Ѳ\001\007w½Öx}ÙGíÁ©\ ! /ý\023ˆõhjA†¿\014A‹\000N\007¢\037 \\Á¼>j\004…¯ðù-ø\030(ûœ)\001è±\033®²úA4l€ù\034\027\027¥,«‚ãb=\ ! ðÉØ\0077†\021:}6Á±\023‹\"žÎWM4PÐ\012*\033x9#ÈÝ£“\024\016ãA\013Þ¥EÙ\037\033\026&â\003é©á€ò\0270\035{qT\005#\ ! “:d¬Ç€67ŸvD—SÀ{xwÒ\033Ó¢\011w)¸º°Êbð\024Oè iXC‚\010¡¨‚Å€õÓ\00416(akRG$C\ ! c¼ÌÒÃ\017”2Í-—LóQ;´ß)ñÈ\000Èz2²iFêm\012€wðÆçËûn\021·\023.4ju„ÿ\032®n\022û\007\010ºÈÓ\025Ï\ ! vºÐ#.\015‘?9©Eþôd‘G@짶\013üÇ4Ï×(\022\034\035¢4Êd-ŠL\005Ñ,\012xäŒbZ…Yªƒw\015nØ‹H\ ! ‹eMì´H8ø@6¼ÈÅh²/\004¤ÜÝ´šç\012í]T¾™Qd9…„\026âk\035e®!g¸C’ þ\003…\007`¨aV\002Èe6\ ! ”Zbe-†4\005C~P:Œ°\021T\000¦\032\002Ô\000\024h`/Èâ¶/9ÃäÆ”\013E`T£€:|”I€æᇴTun·TË)\ ! \026YÈ~€Úâ\030\007#tji\014©‹Â‡h\024(ð\012\014Ôo•*\025…¶)Q\016\036^\001dH%\006à±m²Äá?\001š\000ýz¬X¢àA\ ! út«ò¨Ä¢ñj\016\035mN–)‰9ð›Y\015\011ÉG¬ Á\0012\002•cäɈá­6ÇIò\022¼å7Íg:\005yƒ\002SÔ\034¨Ÿ\ ! íš.\001\003\016!€\023w‘$\016]H.PxX#>\033\012  PÀÖ¹\032&ª\034aº}«òÑ_0ÒÏn@xž\035=>ñ\032J€C¾Ã\ ! !‚\036§\020j]”j‰µŽ\001®w\001‰#ò®0\001\005\004V(\014à\026\034FR¤\004K4\000Û–C®\002iL\016d\000\013¦U±\026\014\000V|…TÓÃ\ ! æ\000Œ»(¯ÐñÔf\020p…&^\002:8☦sxN×5!Ș¢ÞXS,F\015W¦\023>³\010Q‚µhö\016°ˆ˜^«\024“E\023G\ ! Þ& Ðè¯0…\022&\022ÃE5Ÿ#šFâY.fÑ=\031\013\\­X-±dp²ÅÀÜ\036FåÊ.}\015ŒdÀ¦ý\016V\003§À‚*dW\ ! ¶ÖóI\000©Péꤞ9‚«E\032\005\023\016­Ú\007‹4-Hd\025ªž]$b)˜‘\032ZU\030ñ‚\005Œ!RTB%\0152\\ìÁ\003k#\014\ ! \013]2\034›xð#\032ÖUT(V,ðó\020F\026´,qt‹A3W2<=[\011\016\015°\020Ãá\020Ò¾ÍƵöÅN\021m»×¥\010è¾<‚H\003\ ! ·‡î\034\037,ð†uÄ‹§-·xÃõ^oò~‹÷8„ýãâé¤åº\021”\035€\030Cxý³Öq˜\034Ø\016¦\0162dâ5>àX\\ä\ ! +1ŠÖi\025Áשvýà°FV\010¿¢\025ñ±˜#!\026kZX ée\"áTà^\003\005\004£çPX­¡Ti…+:\007%£ÔH†<'\ ! ¦9¼6Ä}Œ™R¬Îpi‚1\007\023\021%F5\015r:ì\"œ£%ÖÔ®9­ñ9\004FU=4uw\015šÀ#%âdlµ\032m½Âê\027>\ ! Ê\034(”ÂùȨ\"$P‚\"MÈ#ƒHUqiò\015!4ËkM*$\025© ¥€t-™OÀ¨*IÈ„‚4[c›.ã¬Èþ(\\\ ! \007'Cv °\"eáo\021Š\012:Ř4T™JB™P|ŒL¥\012\02613\000w›”Æ' 0Zô¡ø\033\\1V'¯Ð\022‚mž/8ö­\031\ ! ì#¨©¡ª\026Y&È'ÃXéUƒõb,uÑlV• #CŽçIÄQákËÉ\017£ (™ç,\036Æ&ƒ>: ÍÄ\033…›–ë\ ! ›P®‹CÈ¿á\027£eÆSì¼\006û½$aT\0042\017ë%:\010S”m\ ! šA`™\021©ÓÐ\017\007¡j¦rg X—;U\013nk¬ÀÚgL¾(?ĸÇÒ!\016@ò\0313Ö•#;ÅB.í\004\003£CG.\022\020÷Ö\ ! \034òª‡º2\012ZQ€š€\001\004!a‡«¹\037Dy€{ÓÌL¸\000qFžfy„\005AˆÝ«er.\"¶ì`Y\027ÌE,ÂX³\\¯2\027\ ! Z\012ÉåS\031°›11š‹\001ÏMà…N\025Ú:õ7\035ÛÔéš©\002`pÂ…d\020\015¸\027RÌÒXý\001D\000\020\026D3&‹ÒL\\L•ä\ ! »ÎY;0Ä~É¡W\010ÑxAY]â\022HKn\003Î\0304•ì³ó\004Yvq çãz²\\ÑU\012\002õ`\037«bÄ!°˜¡\\âÈhÖ\000\024\ ! p\021\021-\011\007+\030·ÂÓ\\¨YRF„F ”¥dÏ\005±€^íA“Óš\010ó´1Q›é?‚í®\035/¨K¡#'^Ì¢!\007ài¿\ ! \035-6Ë\002ù„¨Èpß!´‚1pÖüB-'×(ë\027#ø„«Tâp\016¿a)\015~hE†\015ÅÆž\002Ø*ÃF\003\021•\0051`*Y§\ ! !z‰‘Dâ?ÞhS×\016öáÃ\0325äò%nß`Q¬ÏÕà°\026>i\025‡\020¸H½MdÖŽÄ4¨ò\034­±5\002ˆ8ˆfF\032\021\ ! V\000é ‹¨…E¤U Š6ð\012ÉŽÓ@º\\šEq7‚ÕÑ$?‹bb\027‚Ã\037F˜\030ÁbËôp\ ! ±\003ËÍÔB#)Ú\"³¬Š ¦\033j.d@±¡¨?ƒÐ\002¼Âì\004wË#Œé«²ÇÄK\012úŒ5«}O×~áHÖz‹¼l)\ ! âº\025ô0û~\006Ø\010é¿*•¯5GÁ\026D\020@­qÐ6˜¹\011ÎJ\0270\027d”3NÐIÝ-S¯±N‚1©vVÜøÃz]\012\ ! U\\4¢ü)¼Ã\035\007B‰xò\033â5hæ“zF®‹Â\002…\030ü“SÊÕ\034‹ÛB±¢c*Ö—/·!ÑvG½½TË\"/,–\\h=\ ! M‘Š“’,â²Ø&—‹F9²ñ&ç-†iÆ.pæÔ“ˆ£¢]ÓÅÂ\027yFŒ\007tÜPk\0352ÛÖlø9c“M\032âD»š\ ! X'Ë\035\030\003BJf â>•©·h§áxŒ7/Å\001\027ê#ëbMÓm‡\031*(󃲡wHŒ·_Œ¶µ÷žQì“P²¨:Ñ\ ! rcg\012\013ªõüT\"SÜ@R¯âœ•\\ÏÚ\\J;¬Å-I)é\000\025 a\0048Ë\0367¾µÂý\034‰9\002ƒ\024«ð\010¨¡ÝT¿Þ\033\ ! hm4;»\012¶(_êºÂ¥ÇûMfq©ÊE\012ƒ¾uíî\034]|ut$Þñ¦\0027Ü {V˜Kb*žÎfØ{3År3–î\ ! \006v•—àæ±SßÙ|À\035€ò¬½GL\035IØU{é\001.ñú¨LMGÒéØv!•òh©Ö*ÿk´¨æM\017“xºËÍÓý\ ! é'ÏžfØž'§X³½¿ôžyý¢ÎtÓ è™é“À\026ŠÊ \0147Ë£4\007›çMºÝë\027iFöXOƇ\022<\010Dñ÷ň\ ! ïî\036Wåsê\030þ‰~o\030\007)\035¡å\\>:jl5?´ñàaÍ\007“X]ˆÓ(i»ß`3<Þàþ\0164îŒ\007mx¢wyÝ.\ ! 6^\022TÝ\033ÈSswÁQGœ\033Ÿ!iã¦H<”ÌlB?R¦N›`CÛ3M›½¬){ÏÛ˜:\014ŸÞP{o~&~Ñû›`\ ! Ê—KÜ7ÁÕ_ÛD?y:n\022}úyDk‘x\020Ѹ¤`á!ŒÉe\006Š¶X\027X£0«¬\015!S¯¿ÜP\003\006‰%s\006ã=nv\ ! ÜAß\023\017ÉÑ3~\006¡ÏË\022ËäoÓ9Å’Ÿ,Ï8ØP*\011f{Áù‰š‚¯\035£¶ÇR\031¡A¯®¼M86ú‹4\017iã•J\ ! äg\001:ÛÜÛF\011Ïa‘C3áíįќ…\015¯ùò!¾“\023ÊF\013\"›?\"šn^ãwOŸI\"¥v­ ùn\016‡Á÷f×Tï‘\ ! \021†ìm:½r¿~©Jð®E›_ï|JQÎ\032÷\017ÍžÖÅtÒ\032þ\036œwñ‘ša½ÉñññXw©]Œ¦“A\023$=O\031F\ ! Ú\004ÄvÀBÑÁX²v€\015[ëY÷ÅÙö»€6Ý\011sH¸\021$å\036ÅY;sç—4ð£\016²½‰Kðg¢î\014¦ã ìš\ ! ®LK\031ï7[ã4\017\035ÚÀªkáÖ¡¶Ìܘ5©–\030Á¶&\024¸…8Ø>ìuwÔbç Ÿ¨6Ö\032Æ\005³6‘×È\017î\034)Lb\ ! Т\0041?k͘7á7Ǽî\016Y´G´Ùm6Ot•ö]š»Yêp8lÌj\036¶ª;HÀpé\023\020Ú¸?S÷¾n#r¶Æ\ ! ¸mî4ö(\033ב¶µìš\023nxìÑ\037õª}£\030õ\026\017 9uqìþ4I˜\001¤{ol‚Ôâê> ¹\027̶\007©–péuÑ\ ! ôûØjS“ »\\ådØY™\015ÆoÔ\007PÔÒk÷Q\0327sꃠ\020ëV2OÈF5ñû™/Ÿ™–¦(\001“\006\031\011_¶É(†\ ! 5_q{²¾Õjj¦~+È\032ÝSi\014\013»\032t\011×mç'åçZ\032õ\0307A%Ø\002]@@Õ`Ìt\023W¬ƒÛéË8k½vzš¸Í\ ! ŸxTß4lºn$ÝìT¨¾Yt\033\012\"ì|U…ÆØH\032—*ÝÊ”Âv\010Ü£jdê4µðH+Iµð0b¾i²2ÏÓÞ§Ý\ ! ö1\033?ûs\001™Ö²€\037 ®ZW\025mÜŠ “ŠÚä™*‰­h¥5ÁúÔ ¥\032ë|\013¥K¢nã†.³ÎLO\014\001\037Øã‚\ ! ¦’êS¥\024{»ât\005Àü—ØÈâ\013ê_)ܵà¡(³\003ÝCgŠ60\015u²ØšcÝσô\036¶\030\011‚\016¿Á\030ÃO\024\002[`\ ! ¡®\022¤¤>Q£͞¤ik5ôºÑžöÌË]\022\006e;A'Ŭl\035`âa›u[Û¸¼ÅjÊ\005ú8<î\004ëŠ\014‚Ò5\ ! :lb+¸ùûb\012[äâÑKÉ\007ŽÌ®×aó8Œ[\0051Riê\037þ÷¯®\035Ûþ\"\012C\005kG!¸OY>5Ø›\034Îú\ ! c×îo\001€ŠòxþÔ;œcT\035’½F\010j¾÷&ê&*m`À‘·Á`ž:ø¿ e÷û\016Üã?ÝÚ\016%d\002aT ~\ ! \000âØ9ÉÝ\010…Ù˜ïƒEÎ\033Ä»0ÐûÈŸ?\037r}?¢iÏþ\004oò´íã\014݃'6·©wmÛ‚<Ê\014YèxF—w\ ! ’¯ú“Ñèâ«_®^>¿~þ‹>òV%¬»iÂVó\000GÒ{<Ê`q꣒¿×øD3lÂ\013*,Þ\015y\023àñcѼ2ŒU\ ! 2/\027br)N\016ë¡¿7¹HZvÙ\032úËñ¯çÇÄA\004\017ŽÏE$.h˜ž\000.üùχÍÇï.\025uhà+\000\000\010Žþ%ú\ ! uˆ+=¤\033\007þ™Øó\032\011$ÔŽ4\024]^ŠS¤×Þ\000|ñ\"íÅ=~Ü/&fN}hóÒrwX¦oӕʯÀQ\036\034\036vGÿ\ ! ¾\035 ™g°÷0^©\036\032þÙ¼ä|Õ\037õ¯Ñè×_ëCL$,{ZÃ÷?~¼ö'í|Ù7EB.\016¸o!Ù×@Ù\"5Ïø±\ ! š\032·¾áP\014„€\023[êoö\001à‹ƒÎ¶\004VMP&<ð¹f*u\035n)CÏB¹èbÛ‹\012_;H›üȼdÈë7HTü¢\ ! M\021^´ú\010K\0179êÌÉ\0311õå÷-ˆ<\015n!‰þæèð\017±Fš&k‹@Œe9\020ÇÇâ£ÊÄÉx|*Ž¿=\033Ÿž\035\037÷\034\ ! \004ÕÅ\026.9û££\000<—a”š8д•œ‘ÿøÏWë\017\003\021=}PÁT¬}ý݆€ÝŸÿ\012$ÐÜxuTJßÿ½(`\001\ ! Ï\015Œáû¿\027\001*::\010àw4ÙyZ˜Í›×\026˜ñp<~Ô3D;\035{\023­<\006Q£@ö;7<“\021ÿŸÎ{¦¸‹¹;Æ\ ! ´jË-e2ÚÆerûAoȸ9ÇŽ¯]HT@Ø\003Ò\036JÖS–ß’r颇^4þ¶aÕz+b´È9‹¿³NTeq*Ã\ ! n\005\0009Â÷<\033ÿ\022‡\032â׈„…‚|Šè[Vq\031á\021XÂê\010›§ŒgÐýh\014ºî°qœS~ƒ-…Þdà¶\026j’x°ñ\ ! \002\"ž\021¯\033~ògÍNळ\032?šN8,ά\026iìôÕw›ý¨¾tæ¾äÀn±òLäòxÏÔ?å­¿K\037_6älºÃë\ ! \027Ì°\004Ü°Æö\011\005uþ4…Ât\020ú›á-\036\010îõVhµFï\006iºújpF\"mmñ\000K­záIÆæøÎ':¸€yî4ÅÓ\ ! TæP\007··ÕG ŽœÂ{”¸µÅúe\032\"Wx t_èu¸wÅ\024ûå¨9Ïô±¹ª`oÖÚ€—¨iN3ij\027šï®è$\ ! Zh6ôþÈÓ§š{)v\011\020˜g8\015Ÿkvê\033>oqû§ã¦Áhm\034õ™\036#\011ý}ƒÎ*Ö¶§Þ’Ô½ƒ­=‰~‹G\ ! FZ\026Q`¨!\026Ý 6~ÓßéŒ\036Œ)ðE×Ói´\011 \017ZL¥Vðþ¦Žb×`¸”÷7ætl]˜2Ÿ|¯Û¥dÈ#à7\ ! ±œªØ›¼À/BËE_(ºÑÔÓ*êžÉ>Ú5)æ‘&3º”?œÖm4šY\015™?ëïŸLi®æê~\013™tÿßK#Mi\010\ ! ü \033V_Ù†ÕO&\025\034>ÆK,Lljï\\²\0247®1Ý$ØàÔüÍH;£\014êoè’ÀBÙÞØZx\016Û\014¼wò>ZVKs\ ! v¼\037fM®\003Áë\\²ä\036\033–©§\022I¦ê‰[&¶Ú8ÚNR#\002Ú”\030÷wÀl·ùØlú®Ù Ú´ûÎ\003Þ>»\"[L\ ! ¾Þso½p\030_\022ÄÎÀ;~:nXÔéä#7\022G\011mîOwšx-Œ!¿Ù(\012½¶Üumlÿ.|Ã\004º-ÉÝ\034u\013\"ºÅÎ\ ! oöÛmQ…ÍöÉÖù\0360½[’ôd\034ë\002Nñ‰(¼³\033å\004åA¨pk˜}gð~\0104ì×;§g|ÎŽ®”m@­iÍ:æ\ ! m/D\037h³ÚŒú\034[õp;u2î‹ žl\012 šìíŸ\031„g“d{¬vÒŠÕ¶\004g¬þ½±ØN÷gÀ‹ÇC‹þ\006\001\ ! {Û\030¥\037ìÙGL[ìÎu«\011ï\005i…ý×Ç'§\033ÚÇjP‹Í\026\026Г§Ï6\000zO¯Š¢C4úˆCû<·íDáÃ=\005\ ! !oRÈ6âØëëMÆï¿ùvÄ=¯â&áÃŒ·Á-§’n^7:-tÍî¼îÔ•ë\014Áƒ\014Á›˜“È?§Îû@z\ ! Š\021Ö‘<ê\024s\014ˆ?éChŸ\011äõgÃpV®\017RO†úÞv—¹õ‹))¡—L©íkWé†\006YáõYx÷\031³°Cž\ ! œ>Û\032\0245Â’¶^ï_Ò1ió\007c3û´»õÓ\002D}0Y½:gzßÍs®¡Ž\020ƒlë«[À‰æ7¬H?ÂÇ\010ß\ ! Ð\007Î>w“TK\006ø”µÍh^6éb]©Ây\033*½e\013Ë\012¬lx6\035Ÿ\020óT¿5s`^\016„/¡SôÎ0ýnÏóÎ\ ! y\017;KC_Û}àôÄëÞý íŽ\030_%\015V‡\025y:éÙÈØ 6[Á5té\017ƒzM¯«\003ŽÑ»~\036\000¶1˜_\003Ô;ºÕÄ\ ! Þa0\025cû'4Ïx[Hx¥ß†Î\007!7âÞ\026ín\031¼k\012Úã©y·=ðx× ®þµFìfÔ\006Ó õ÷ƒ*«¼~}^ë\ ! /7ð!_Ö}!\023êL;ëW5~è9?ãqùΔùt\001ϨÙaÿŸyØÇXQ³ìw\000mƒ²÷½NQŽó±›ÌMÎ^ÕPƒ\036¹~þâí+ç™þƒ\036=\007;®?¸£\ ! Ä\005}=n„1×/µt{¤–žÛ¯°©`¡\003ÿ·øËüu’î[o¯_6çÙ}Ô\017\003<ÓI\016ÞoœËd¾¼pñf\ ! )³Öy›ödº\013%Î`µƒz×gsSÛF\000=oζo\014>éð(›\034ÐyAꩶºgJÜ=øÂÇ\017½«u5}ÈZmjýÿ\ ! #\026ÎT±~´ç”¦;Š\027±ÏºËo’Wgáoè«>`uÇ]\002ûˆB—h\030lRUê6\020.ù½\033½N\003ÇÿWé\002Ù:\ ! iË–³´\032\037\022³ÖBëwò\010âåY\017i\006Œ­šùÇOÇ- š/µéc º…å€ÖûÐZÌÝähÓö¯\"é_„»³1\016\ ! 9²Òóì,Áַ­Z9½\023LB·XئŠ1´\027\033UŒnó_¥ÚdKÜg¾£ÏÂqsm\"TöÓ©m*q<\036ÿX­\ ! \030‘oíFEü'#(à³@œz\022‹Ý9\010Ú<\026ÊL\\`\032lNˆÊ;åÕõ\011\013·\037ÖFOøâ-,Ûá…:|ÒWC5“\\\ ! IkÇP;p×ãwcý\037ÄC\030×ðEãL\027C¦¿uö)H\ ! «ûLå\021noÉx3âðÔg þʙ㓑h,_,ª2LWIߎ\012\037¿j=Á}ú\032\005¯âû4\015§ëúï[é)ݳÌð\ ! ¹ÞŸISã‹É˜ß\ ! HÏ6þy>é\036TvŽébz]B\022€ç@\023P¢@œŽÅñ“³ñ“³Ó\023l\015<ш\014»'H}ýúÍÕj5´\005²ašÏGŒˆ\ ! ýÞn¿ê°ÅœTäÃí`N½¾\003«hx=û2µ¾\036ÌOùc\030­¿ù•¤\011þÁD4Ö®]î\020ÐtÕ-r\\\031¥<\010ý…·«\ ! \\O%==JŸ™1[P骻\023jªûøÚ¡Ç°„çÂ\014öí_ÁÐ\031¿û¢wkQ@QÒpÍ”’ÿ°ï—_ü/ÿ_z¾") ### end From anadelonbrin at users.sourceforge.net Wed Dec 22 01:22:29 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 01:22:32 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_stats.py, 1.1, 1.2 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv31740/spambayes/test Modified Files: test_stats.py Log Message: Update tests to reflect new Stats() constructor. Add checks for the cost calculations. Check that getting stats from a certain date works correctly. Index: test_stats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/test/test_stats.py,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** test_stats.py 21 Dec 2004 21:30:13 -0000 1.1 --- test_stats.py 22 Dec 2004 00:22:26 -0000 1.2 *************** *** 15,27 **** class StatsTest(unittest.TestCase): def setUp(self): - self.s_cut = options["Categorization", "spam_cutoff"] - self.h_cut = options["Categorization", "ham_cutoff"] - self.h_string = options["Headers", "header_ham_string"] - self.u_string = options["Headers", "header_unsure_string"] - self.s_string = options["Headers", "header_spam_string"] self.messageinfo_db_name = "__unittest.pik" self.messageinfo_db = MessageInfoPickle(self.messageinfo_db_name) ! self.s = Stats(self.s_cut, self.h_cut, self.messageinfo_db, ! self.h_string, self.u_string, self.s_string) def tearDown(self): --- 15,21 ---- class StatsTest(unittest.TestCase): def setUp(self): self.messageinfo_db_name = "__unittest.pik" self.messageinfo_db = MessageInfoPickle(self.messageinfo_db_name) ! self.s = Stats(options, self.messageinfo_db) def tearDown(self): *************** *** 43,48 **** self.messageinfo_db.close() self.messageinfo_db = MessageInfoPickle(self.messageinfo_db_name) ! self.s = Stats(self.s_cut, self.h_cut, self.messageinfo_db, ! self.h_string, self.u_string, self.s_string) self.assertEqual(now, self.s.from_date) --- 37,41 ---- self.messageinfo_db.close() self.messageinfo_db = MessageInfoPickle(self.messageinfo_db_name) ! self.s = Stats(options, self.messageinfo_db) self.assertEqual(now, self.s.from_date) *************** *** 235,238 **** --- 228,241 ---- data["num_unsure_trained_spam"]) / data["total_spam"]) + self.assertEqual(new_data["total_cost"], + data["num_trained_ham_fp"] * + options["TestDriver", "best_cutoff_fp_weight"] + \ + data["num_trained_spam_fn"] * + options["TestDriver", "best_cutoff_fn_weight"] + \ + data["num_unsure"] * + options["TestDriver", "best_cutoff_unsure_weight"]) + self.assertEqual(new_data["cost_savings"], data["num_spam"] * + options["TestDriver", "best_cutoff_fn_weight"] - + data["total_cost"]) def test_AddPercentStrings(self): *************** *** 293,297 **** self.assertEqual(s[9], "Manually classified as spam:\t0") self.assertEqual(s[10], "") ! if self.h_cut <= score < self.s_cut: self.assertEqual(s[11], "Unsures trained as good:\t0 (0.0% of unsures)") self.assertEqual(s[12], "Unsures trained as spam:\t0 (0.0% of unsures)") --- 296,301 ---- self.assertEqual(s[9], "Manually classified as spam:\t0") self.assertEqual(s[10], "") ! if options["Categorization", "ham_cutoff"] <= score < \ ! options["Categorization", "spam_cutoff"]: self.assertEqual(s[11], "Unsures trained as good:\t0 (0.0% of unsures)") self.assertEqual(s[12], "Unsures trained as spam:\t0 (0.0% of unsures)") *************** *** 372,376 **** self.assertEqual(s[17], "Spam correctly identified:\t33.3% (+ 33.3% unsure)") self.assertEqual(s[18], "Good incorrectly identified:\t33.3% (+ 33.3% unsure)") ! self.assertEqual(len(s), 19) def test_get_all_stats(self): --- 376,383 ---- self.assertEqual(s[17], "Spam correctly identified:\t33.3% (+ 33.3% unsure)") self.assertEqual(s[18], "Good incorrectly identified:\t33.3% (+ 33.3% unsure)") ! self.assertEqual(s[19], "") ! self.assertEqual(s[20], "Total cost of spam:\t$11.60") ! self.assertEqual(s[21], "SpamBayes savings:\t$-9.60") ! self.assertEqual(len(s), 22) def test_get_all_stats(self): *************** *** 395,401 **** self.assertEqual(s[17], "Spam correctly identified:\t40.0% (+ 20.0% unsure)") self.assertEqual(s[18], "Good incorrectly identified:\t33.3% (+ 16.7% unsure)") ! self.assertEqual(len(s), 19) def _stuff_with_data(self, use_html=False): # Record some session data. self.s.RecordClassification(0.0) --- 402,417 ---- self.assertEqual(s[17], "Spam correctly identified:\t40.0% (+ 20.0% unsure)") self.assertEqual(s[18], "Good incorrectly identified:\t33.3% (+ 16.7% unsure)") ! self.assertEqual(s[19], "") ! self.assertEqual(s[20], "Total cost of spam:\t$23.40") ! self.assertEqual(s[21], "SpamBayes savings:\t$-19.40") ! self.assertEqual(len(s), 22) def _stuff_with_data(self, use_html=False): + self._stuff_with_session_data() + self._stuff_with_persistent_data() + self.s.CalculatePersistentStats() + return self.s.GetStats(use_html=use_html) + + def _stuff_with_session_data(self): # Record some session data. self.s.RecordClassification(0.0) *************** *** 411,414 **** --- 427,431 ---- self.s.RecordTraining(False, 1.0) + def _stuff_with_persistent_data(self): # Put data into the totals. msg = Message('0', self.messageinfo_db) *************** *** 436,441 **** msg = Message('8', self.messageinfo_db) msg.RememberClassification(options['Headers','header_unsure_string']) - self.s.CalculatePersistentStats() - return self.s.GetStats(use_html=use_html) def test_with_html(self): --- 453,456 ---- *************** *** 449,452 **** --- 464,504 ---- self.assert_(' ' not in line) + def test_from_date_empty(self): + # Put persistent data in, but no session data. + self._stuff_with_persistent_data() + # Wait for a bit to make sure the time is later. + time.sleep(0.1) + # Set the date to now. + self.s.ResetTotal(permanently=True) + # Recalculate. + self.s.CalculatePersistentStats() + # Check. + self.assertEqual(self.s.GetStats(), ["Messages classified: 0"]) + + def test_from_specified_date(self): + # Put persistent data in, but no session data. + self._stuff_with_persistent_data() + # Wait for a bit to make sure the time is later. + time.sleep(0.1) + # Set the date to now. + self.s.from_date = time.time() + # Wait for a bit to make sure the time is later. + time.sleep(0.1) + # Put more data in. + msg = Message('0', self.messageinfo_db) + msg.RememberTrained(True) + msg.RememberClassification(options['Headers','header_spam_string']) + msg = Message('7', self.messageinfo_db) + msg.RememberTrained(False) + msg.RememberClassification(options['Headers','header_spam_string']) + msg = Message('2', self.messageinfo_db) + msg.RememberTrained(True) + msg.RememberClassification(options['Headers','header_ham_string']) + # Recalculate. + self.s.CalculatePersistentStats() + # Check that there are the right number of messages (assume that + # the rest is right - if not it should be caught by other tests). + self.assertEqual(self.s.GetStats()[0], "Messages classified: 3") + def suite(): From anadelonbrin at users.sourceforge.net Wed Dec 22 01:24:01 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 01:24:04 2004 Subject: [Spambayes-checkins] spambayes/spambayes ImapUI.py, 1.40, 1.41 ProxyUI.py, 1.55, 1.56 UserInterface.py, 1.50, 1.51 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32095/spambayes Modified Files: ImapUI.py ProxyUI.py UserInterface.py Log Message: Use new stats manager properly. Index: ImapUI.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/ImapUI.py,v retrieving revision 1.40 retrieving revision 1.41 diff -C2 -d -r1.40 -r1.41 *** ImapUI.py 21 Dec 2004 23:08:51 -0000 1.40 --- ImapUI.py 22 Dec 2004 00:23:58 -0000 1.41 *************** *** 111,115 **** """Serves the HTML user interface for the proxies.""" def __init__(self, cls, imap, pwd, imap_session_class, ! lang_manager=None): global parm_map # Only offer SSL if it is available --- 111,115 ---- """Serves the HTML user interface for the proxies.""" def __init__(self, cls, imap, pwd, imap_session_class, ! lang_manager=None, stats=None): global parm_map # Only offer SSL if it is available *************** *** 123,127 **** del IMAP4_SSL UserInterface.UserInterface.__init__(self, cls, parm_map, adv_map, ! lang_manager) self.classifier = cls self.imap = imap --- 123,127 ---- del IMAP4_SSL UserInterface.UserInterface.__init__(self, cls, parm_map, adv_map, ! lang_manager, stats) self.classifier = cls self.imap = imap Index: ProxyUI.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/ProxyUI.py,v retrieving revision 1.55 retrieving revision 1.56 diff -C2 -d -r1.55 -r1.56 *** ProxyUI.py 21 Dec 2004 23:16:28 -0000 1.55 --- ProxyUI.py 22 Dec 2004 00:23:58 -0000 1.56 *************** *** 165,169 **** UserInterface.UserInterface.__init__(self, proxy_state.bayes, parm_ini_map, adv_map, ! proxy_state.lang_manager) state = proxy_state self.state_recreator = state_recreator # ugly --- 165,170 ---- UserInterface.UserInterface.__init__(self, proxy_state.bayes, parm_ini_map, adv_map, ! proxy_state.lang_manager, ! proxy_state.stats) state = proxy_state self.state_recreator = state_recreator # ugly Index: UserInterface.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/UserInterface.py,v retrieving revision 1.50 retrieving revision 1.51 diff -C2 -d -r1.50 -r1.51 *** UserInterface.py 21 Dec 2004 23:08:51 -0000 1.50 --- UserInterface.py 22 Dec 2004 00:23:58 -0000 1.51 *************** *** 267,271 **** def __init__(self, bayes, config_parms=(), adv_parms=(), ! lang_manager=None): """Load up the necessary resources: ui.html and helmet.gif.""" BaseUserInterface.__init__(self, lang_manager) --- 267,271 ---- def __init__(self, bayes, config_parms=(), adv_parms=(), ! lang_manager=None, stats=None): """Load up the necessary resources: ui.html and helmet.gif.""" BaseUserInterface.__init__(self, lang_manager) *************** *** 273,276 **** --- 273,277 ---- self.parm_ini_map = config_parms self.advanced_options_map = adv_parms + self.stats = stats self.app_for_version = None # subclasses must fill this in *************** *** 921,933 **** def onStats(self): """Provide statistics about previous SpamBayes activity.""" - # Caching this information somewhere would be a good idea, - # rather than regenerating it every time. If people complain - # about it being too slow, then do this! - # XXX The Stats object should be generated once, when we start up, - # XXX and then just called, here. - s = Stats.Stats() self._writePreamble("Statistics") ! stats = s.GetStats(use_html=True) ! stats = self._buildBox("Statistics", None, "

".join(stats)) self.write(stats) self._writePostamble(help_topic="stats") --- 922,933 ---- def onStats(self): """Provide statistics about previous SpamBayes activity.""" self._writePreamble("Statistics") ! if self.stats: ! stats = self.stats.GetStats(use_html=True) ! stats = self._buildBox("Statistics", None, ! "

".join(stats)) ! else: ! stats = self._buildBox("Statistics", None, ! "Statistics not available") self.write(stats) self._writePostamble(help_topic="stats") From anadelonbrin at users.sourceforge.net Wed Dec 22 01:25:48 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 01:25:50 2004 Subject: [Spambayes-checkins] spambayes/spambayes Stats.py,1.12,1.13 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32506/spambayes Modified Files: Stats.py Log Message: Simplify constructor to take just the messageinfo db and an optionsclass object. We then have access to all the options we want, and (more importantly) the values will be updated correctly if the user changes options in mid run. Index: Stats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Stats.py,v retrieving revision 1.12 retrieving revision 1.13 diff -C2 -d -r1.12 -r1.13 *** Stats.py 21 Dec 2004 23:19:41 -0000 1.12 --- Stats.py 22 Dec 2004 00:25:45 -0000 1.13 *************** *** 52,67 **** class Stats(object): ! def __init__(self, spam_threshold, unsure_threshold, messageinfo_db, ! ham_string, unsure_string, spam_string, fp_cost, fn_cost, ! unsure_cost): self.messageinfo_db = messageinfo_db ! self.spam_threshold = spam_threshold ! self.unsure_threshold = unsure_threshold ! self.ham_string = ham_string ! self.unsure_string = unsure_string ! self.spam_string = spam_string ! self.fp_cost = fp_cost ! self.fn_cost = fn_cost ! self.unsure_cost = unsure_cost # Reset session stats. self.Reset() --- 52,58 ---- class Stats(object): ! def __init__(self, options, messageinfo_db): self.messageinfo_db = messageinfo_db ! self.options = options # Reset session stats. self.Reset() *************** *** 87,93 **** def RecordClassification(self, score): ! if score >= self.spam_threshold: self.num_spam += 1 ! elif score >= self.unsure_threshold: self.num_unsure += 1 else: --- 78,84 ---- def RecordClassification(self, score): ! if score >= self.options["Categorization", "spam_cutoff"]: self.num_spam += 1 ! elif score >= self.options["Categorization", "ham_cutoff"]: self.num_unsure += 1 else: *************** *** 99,103 **** # If we are recovering an item that is in the "spam" threshold, # then record it as a "false positive" ! if old_score > self.spam_threshold: self.num_trained_ham_fp += 1 else: --- 90,94 ---- # If we are recovering an item that is in the "spam" threshold, # then record it as a "false positive" ! if old_score > self.options["Categorization", "spam_cutoff"]: self.num_trained_ham_fp += 1 else: *************** *** 105,109 **** # If we are deleting as Spam an item that was in our "good" # range, then record it as a false negative. ! if old_score < self.unsure_threshold: self.num_trained_spam_fn += 1 --- 96,100 ---- # If we are deleting as Spam an item that was in our "good" # range, then record it as a false negative. ! if old_score < self.options["Categorization", "ham_cutoff"]: self.num_trained_spam_fn += 1 *************** *** 128,132 **** # Skip ones that are too old. if self.from_date and m.date_modified and \ ! m.date_modified > self.from_date: continue --- 119,123 ---- # Skip ones that are too old. if self.from_date and m.date_modified and \ ! m.date_modified < self.from_date: continue *************** *** 134,138 **** trained = m.GetTrained() ! if classification == self.spam_string: # Classified as spam. totals["num_spam"] += 1 --- 125,130 ---- trained = m.GetTrained() ! if classification == self.options["Headers", ! "header_spam_string"]: # Classified as spam. totals["num_spam"] += 1 *************** *** 140,144 **** # False positive (classified as spam, trained as ham) totals["num_trained_ham_fp"] += 1 ! elif classification == self.ham_string: # Classified as ham. totals["num_ham"] += 1 --- 132,137 ---- # False positive (classified as spam, trained as ham) totals["num_trained_ham_fp"] += 1 ! elif classification == self.options["Headers", ! "header_ham_string"]: # Classified as ham. totals["num_ham"] += 1 *************** *** 146,150 **** # False negative (classified as ham, trained as spam) totals["num_trained_spam_fn"] += 1 ! elif classification == self.unsure_string: # Classified as unsure. totals["num_unsure"] += 1 --- 139,144 ---- # False negative (classified as ham, trained as spam) totals["num_trained_spam_fn"] += 1 ! elif classification == self.options["Headers", ! "header_unsure_string"]: # Classified as unsure. totals["num_unsure"] += 1 *************** *** 234,243 **** data["total_spam"] ! data["total_cost"] = data["num_trained_ham_fp"] * self.fp_cost + \ ! data["num_trained_spam_fn"] * self.fn_cost + \ ! data["num_unsure"] * self.unsure_cost # If there was no filtering done, what would the cost have been? # (Assuming that any spam in the inbox earns the cost of a fn) ! no_filter_cost = data["num_spam"] * self.fn_cost data["cost_savings"] = no_filter_cost - data["total_cost"] --- 228,241 ---- data["total_spam"] ! fp_cost = self.options["TestDriver", "best_cutoff_fp_weight"] ! fn_cost = self.options["TestDriver", "best_cutoff_fn_weight"] ! unsure_cost = self.options["TestDriver", ! "best_cutoff_unsure_weight"] ! data["total_cost"] = data["num_trained_ham_fp"] * fp_cost + \ ! data["num_trained_spam_fn"] * fn_cost + \ ! data["num_unsure"] * unsure_cost # If there was no filtering done, what would the cost have been? # (Assuming that any spam in the inbox earns the cost of a fn) ! no_filter_cost = data["num_spam"] * fn_cost data["cost_savings"] = no_filter_cost - data["total_cost"] From anadelonbrin at users.sourceforge.net Wed Dec 22 01:27:19 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 01:27:22 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_imapfilter.py, 1.48, 1.49 sb_pop3dnd.py, 1.14, 1.15 sb_server.py, 1.34, 1.35 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv507/scripts Modified Files: sb_imapfilter.py sb_pop3dnd.py sb_server.py Log Message: Use new stats manager properly. Index: sb_imapfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_imapfilter.py,v retrieving revision 1.48 retrieving revision 1.49 diff -C2 -d -r1.48 -r1.49 *** sb_imapfilter.py 20 Dec 2004 02:49:47 -0000 1.48 --- sb_imapfilter.py 22 Dec 2004 00:27:17 -0000 1.49 *************** *** 98,101 **** --- 98,103 ---- import StringIO + from spambayes import Stats + from spambayes import message from spambayes.Options import options, get_pathname_option from spambayes import tokenizer, storage, message, Dibbler *************** *** 1028,1031 **** --- 1030,1034 ---- classifier = storage.open_storage(bdbname, useDBM) + message_db = message.open_storage(*message.database_type()) if options["globals", "verbose"]: *************** *** 1083,1089 **** else: imap = IMAPSession(server, port, imapDebug, doExpunge) httpServer = UserInterfaceServer(options["html_ui", "port"]) httpServer.register(IMAPUserInterface(classifier, imap, pwd, ! IMAPSession)) launchBrowser=launchUI or options["html_ui", "launch_browser"] if sleepTime: --- 1086,1096 ---- else: imap = IMAPSession(server, port, imapDebug, doExpunge) + + # Load stats manager. + stats = Stats(options, message_db) + httpServer = UserInterfaceServer(options["html_ui", "port"]) httpServer.register(IMAPUserInterface(classifier, imap, pwd, ! IMAPSession, stats=stats)) launchBrowser=launchUI or options["html_ui", "launch_browser"] if sleepTime: Index: sb_pop3dnd.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_pop3dnd.py,v retrieving revision 1.14 retrieving revision 1.15 diff -C2 -d -r1.14 -r1.15 *** sb_pop3dnd.py 21 Dec 2004 23:21:22 -0000 1.14 --- sb_pop3dnd.py 22 Dec 2004 00:27:17 -0000 1.15 *************** *** 538,544 **** class SpambayesInbox(SpambayesMailbox): """A special mailbox that holds status messages from SpamBayes.""" ! def __init__(self, id, message_db): IMAPMailbox.__init__(self, "INBOX", "spambayes", id) ! self.mdb = message_db self.UID_validity = id self.nextUID = 1 --- 538,544 ---- class SpambayesInbox(SpambayesMailbox): """A special mailbox that holds status messages from SpamBayes.""" ! def __init__(self, id, state): IMAPMailbox.__init__(self, "INBOX", "spambayes", id) ! self.mdb = state.mdb self.UID_validity = id self.nextUID = 1 *************** *** 547,560 **** self.storage = {} self.createMessages() ! s_thres = options["Categorization", "spam_cutoff"] ! u_thres = options["Categorization", "ham_cutoff"] ! fp_cost = options["TestDriver", "best_cutoff_fp_weight"] ! fn_cost = options["TestDriver", "best_cutoff_fn_weight"] ! unsure_cost = options["TestDriver", "best_cutoff_unsure_weight"] ! h_string = options["Headers", "header_ham_string"] ! s_string = options["Headers", "header_spam_string"] ! u_string = options["Headers", "header_unsure_string"] ! self.stats = Stats(s_thres, u_thres, message_db, h_string, u_string, ! s_string, fp_cost, fn_cost, unsure_cost) def buildStatusMessage(self, body=False, headers=False): --- 547,551 ---- self.storage = {} self.createMessages() ! self.stats = state.stats def buildStatusMessage(self, body=False, headers=False): *************** *** 607,611 **** msg.append('\r\n') if body: ! msg.extend(s.GetStats(use_html=False)) return "\r\n".join(msg) --- 598,602 ---- msg.append('\r\n') if body: ! msg.extend(self.stats.GetStats(use_html=False)) return "\r\n".join(msg) *************** *** 918,921 **** --- 909,913 ---- work is done elsewhere. We do need to load the classifier, though, and build the status strings.""" + # Load token and message databases. if not hasattr(self, "DBName"): self.DBName, self.useDB = storage.database_type([]) *************** *** 924,927 **** --- 916,924 ---- self.MDBName, self.useMDB = message.database_type() self.mdb = message.open_storage(self.MDBName, self.useMDB) + + # Load stats manager. + self.stats = Stats(options, self.mdb) + + # Build status strings. self.buildStatusStrings() *************** *** 955,959 **** "spam_to_train") spam_train_box = SpambayesMailbox("TrainAsSpam", 3, spam_train_cache) ! inbox = SpambayesInbox(4, state.mdb) spam_trainer = Trainer(spam_train_box, True) --- 952,956 ---- "spam_to_train") spam_train_box = SpambayesMailbox("TrainAsSpam", 3, spam_train_cache) ! inbox = SpambayesInbox(4, state) spam_trainer = Trainer(spam_train_box, True) Index: sb_server.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_server.py,v retrieving revision 1.34 retrieving revision 1.35 diff -C2 -d -r1.34 -r1.35 *** sb_server.py 21 Dec 2004 23:13:47 -0000 1.34 --- sb_server.py 22 Dec 2004 00:27:17 -0000 1.35 *************** *** 111,114 **** --- 111,115 ---- import spambayes.message from spambayes import i18n + from spambayes import Stats from spambayes import Dibbler from spambayes import storage *************** *** 804,808 **** self.DBName, self.useDB = storage.database_type([]) self.bayes = storage.open_storage(self.DBName, self.useDB) ! self.buildStatusStrings() --- 805,815 ---- self.DBName, self.useDB = storage.database_type([]) self.bayes = storage.open_storage(self.DBName, self.useDB) ! if not hasattr(self, "MBDName"): ! self.MDBName, self.useMDB = message.database_type() ! self.mdb = message.open_storage(self.MDBName, self.useMDB) ! ! # Load stats manager. ! self.stats = Stats(options, self.mdb) ! self.buildStatusStrings() From anadelonbrin at users.sourceforge.net Wed Dec 22 01:30:29 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 01:30:32 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 filter.py, 1.42, 1.43 manager.py, 1.103, 1.104 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1325/Outlook2000 Modified Files: filter.py manager.py Log Message: Just pass the bayes_options object to the stats manager rather than lots of individual options. This has two consequences: we need to use the same values for the classification attribute of messages (in filter.py) and we need to ensure that the bayes_options cutoff values are always the same as the outlook config threshold values. Index: filter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/filter.py,v retrieving revision 1.42 retrieving revision 1.43 diff -C2 -d -r1.42 -r1.43 *** filter.py 21 Dec 2004 21:48:37 -0000 1.42 --- filter.py 22 Dec 2004 00:30:26 -0000 1.43 *************** *** 17,29 **** disposition = "Yes" attr_prefix = "spam" ! msg.c = "spam" elif prob_perc >= config.unsure_threshold: disposition = "Unsure" attr_prefix = "unsure" ! msg.c = "unsure" else: disposition = "No" attr_prefix = "ham" ! msg.c = "ham" ms = mgr.message_store --- 17,29 ---- disposition = "Yes" attr_prefix = "spam" ! msg.c = mgr.bayes_options["Headers", "header_spam_string"] elif prob_perc >= config.unsure_threshold: disposition = "Unsure" attr_prefix = "unsure" ! msg.c = mgr.bayes_options["Headers", "header_unsure_string"] else: disposition = "No" attr_prefix = "ham" ! msg.c = mgr.bayes_options["Headers", "header_ham_string"] ms = mgr.message_store Index: manager.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/manager.py,v retrieving revision 1.103 retrieving revision 1.104 diff -C2 -d -r1.103 -r1.104 *** manager.py 21 Dec 2004 23:18:32 -0000 1.103 --- manager.py 22 Dec 2004 00:30:26 -0000 1.104 *************** *** 455,468 **** self.ReportFatalStartupError("Failed to load bayes database") self.classifier_data.InitNew() ! s_thres = self.config.filter.spam_threshold ! u_thres = self.config.filter.unsure_threshold ! fp_cost = bayes_options["TestDriver", "best_cutoff_fp_weight"] ! fn_cost = bayes_options["TestDriver", "best_cutoff_fn_weight"] ! unsure_cost = bayes_options["TestDriver", ! "best_cutoff_unsure_weight"] ! mdb = self.classifier_data.message_db ! self.stats = bayes_stats.Stats(s_thres, u_thres, mdb, "ham", ! "unsure", "spam", fp_cost, fn_cost, ! unsure_cost) # Logging - this should be somewhere else. --- 455,465 ---- self.ReportFatalStartupError("Failed to load bayes database") self.classifier_data.InitNew() ! self.bayes_options = bayes_options ! bayes_options["Categorization", "spam_cutoff"] = \ ! self.config.filter.spam_threshold ! bayes_options["Categorization", "ham_cutoff"] = \ ! self.config.filter.unsure_threshold ! self.stats = bayes_stats.Stats(bayes_options, ! self.classifier_data.message_db) # Logging - this should be somewhere else. *************** *** 908,911 **** --- 905,914 ---- # And re-save now, just incase Outlook dies on the way down. self.SaveConfig() + # And update the cutoff values in bayes_options (which the + # stats use) to our thresholds. + bayes_options["Categorization", "spam_cutoff"] = \ + self.config.filter.spam_threshold + bayes_options["Categorization", "ham_cutoff"] = \ + self.config.filter.unsure_threshold # And tell the addin that our filters may have changed. if self.addin is not None: From anadelonbrin at users.sourceforge.net Wed Dec 22 02:22:03 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 02:22:06 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 filter.py, 1.43, 1.44 manager.py, 1.104, 1.105 msgstore.py, 1.97, 1.98 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv11996/Outlook2000 Modified Files: filter.py manager.py msgstore.py Log Message: It makes life much simpler if the classification strings match the non-Outlook ones. Thresholds are 0-100, cutoffs are 0.0-1.0 - need to convert between them, or everything is spam! Index: filter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/filter.py,v retrieving revision 1.43 retrieving revision 1.44 diff -C2 -d -r1.43 -r1.44 *** filter.py 22 Dec 2004 00:30:26 -0000 1.43 --- filter.py 22 Dec 2004 01:22:00 -0000 1.44 *************** *** 17,29 **** disposition = "Yes" attr_prefix = "spam" ! msg.c = mgr.bayes_options["Headers", "header_spam_string"] elif prob_perc >= config.unsure_threshold: disposition = "Unsure" attr_prefix = "unsure" ! msg.c = mgr.bayes_options["Headers", "header_unsure_string"] else: disposition = "No" attr_prefix = "ham" ! msg.c = mgr.bayes_options["Headers", "header_ham_string"] ms = mgr.message_store --- 17,30 ---- disposition = "Yes" attr_prefix = "spam" ! msg.c = mgr.bayes_options["Headers", "header_spam_string"][0] elif prob_perc >= config.unsure_threshold: disposition = "Unsure" attr_prefix = "unsure" ! msg.c = mgr.bayes_options["Headers", "header_unsure_string"][0] else: disposition = "No" attr_prefix = "ham" ! msg.c = mgr.bayes_options["Headers", "header_ham_string"][0] ! mgr.classifier_data.message_db.store_msg(msg) ms = mgr.message_store Index: manager.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/manager.py,v retrieving revision 1.104 retrieving revision 1.105 diff -C2 -d -r1.104 -r1.105 *** manager.py 22 Dec 2004 00:30:26 -0000 1.104 --- manager.py 22 Dec 2004 01:22:00 -0000 1.105 *************** *** 457,463 **** self.bayes_options = bayes_options bayes_options["Categorization", "spam_cutoff"] = \ ! self.config.filter.spam_threshold bayes_options["Categorization", "ham_cutoff"] = \ ! self.config.filter.unsure_threshold self.stats = bayes_stats.Stats(bayes_options, self.classifier_data.message_db) --- 457,465 ---- self.bayes_options = bayes_options bayes_options["Categorization", "spam_cutoff"] = \ ! self.config.filter.spam_threshold \ ! / 100.0 bayes_options["Categorization", "ham_cutoff"] = \ ! self.config.filter.unsure_threshold \ ! / 100.0 self.stats = bayes_stats.Stats(bayes_options, self.classifier_data.message_db) *************** *** 908,914 **** # stats use) to our thresholds. bayes_options["Categorization", "spam_cutoff"] = \ ! self.config.filter.spam_threshold bayes_options["Categorization", "ham_cutoff"] = \ ! self.config.filter.unsure_threshold # And tell the addin that our filters may have changed. if self.addin is not None: --- 910,918 ---- # stats use) to our thresholds. bayes_options["Categorization", "spam_cutoff"] = \ ! self.config.filter.spam_threshold \ ! / 100.0 bayes_options["Categorization", "ham_cutoff"] = \ ! self.config.filter.unsure_threshold \ ! / 100.0 # And tell the addin that our filters may have changed. if self.addin is not None: Index: msgstore.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v retrieving revision 1.97 retrieving revision 1.98 diff -C2 -d -r1.97 -r1.98 *** msgstore.py 21 Dec 2004 21:48:37 -0000 1.97 --- msgstore.py 22 Dec 2004 01:22:00 -0000 1.98 *************** *** 812,815 **** --- 812,816 ---- self.t = None self.c = None + self.date_modified = None self.original_folder = None From anadelonbrin at users.sourceforge.net Wed Dec 22 02:29:16 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 02:29:19 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_server.py,1.35,1.36 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv13185/scripts Modified Files: sb_server.py Log Message: Fix bad import found by Sean Darcy. Index: sb_server.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_server.py,v retrieving revision 1.35 retrieving revision 1.36 diff -C2 -d -r1.35 -r1.36 *** sb_server.py 22 Dec 2004 00:27:17 -0000 1.35 --- sb_server.py 22 Dec 2004 01:29:14 -0000 1.36 *************** *** 109,117 **** from email.Header import Header - import spambayes.message from spambayes import i18n from spambayes import Stats from spambayes import Dibbler from spambayes import storage from spambayes.FileCorpus import FileCorpus, ExpiryFileCorpus from spambayes.FileCorpus import FileMessageFactory, GzipFileMessageFactory --- 109,117 ---- from email.Header import Header from spambayes import i18n from spambayes import Stats from spambayes import Dibbler from spambayes import storage + from spambayes import message from spambayes.FileCorpus import FileCorpus, ExpiryFileCorpus from spambayes.FileCorpus import FileMessageFactory, GzipFileMessageFactory *************** *** 494,498 **** try: msg = email.message_from_string(messageText, ! _class=spambayes.message.SBHeaderMessage) msg.setId(state.getNewMessageName()) # Now find the spam disposition and add the header. --- 494,498 ---- try: msg = email.message_from_string(messageText, ! _class=message.SBHeaderMessage) msg.setId(state.getNewMessageName()) # Now find the spam disposition and add the header. *************** *** 562,566 **** # This is one case where an unqualified 'except' is OK, 'cos # anything's better than destroying people's email... ! messageText, details = spambayes.message.\ insert_exception_header(messageText) --- 562,566 ---- # This is one case where an unqualified 'except' is OK, 'cos # anything's better than destroying people's email... ! messageText, details = message.\ insert_exception_header(messageText) From kennypitt at hotmail.com Tue Dec 21 16:05:33 2004 From: kennypitt at hotmail.com (Kenny Pitt) Date: Wed Dec 22 02:32:05 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_server.py,1.32,1.33 In-Reply-To: Message-ID: Tony Meyer wrote: > Kenny's fix worked if there was more than +OK, but not if there was > just +OK. Fix the fix so that both cases work. Oops, sorry, that was my limited Python experience showing through. I assumed that split() would just return None for the second value if there was no separator. Thanks for covering me! BTW, any idea what might have happened to the stats page in sb_server? I'm looking for the problem right now, but every time I try to view stats I get a "500 Server error" with the following traceback: """ Traceback (most recent call last): File "C:\src\python\spambayes\spambayes\Dibbler.py", line 461, in found_terminator getattr(plugin, name)(**params) File "C:\src\python\spambayes\spambayes\UserInterface.py", line 915, in onStats s = Stats.Stats() File "C:\src\python\spambayes\spambayes\Stats.py", line 50, in __init__ self.CalculateStats() File "C:\src\python\spambayes\spambayes\Stats.py", line 72, in CalculateStats msginfoDB._getState(m) AttributeError: 'MessageInfoDB' object has no attribute '_getState' """ I just upgraded to Python 2.4 Final yesterday, so don't know if that might have something to do with it. -- Kenny Pitt From anadelonbrin at users.sourceforge.net Wed Dec 22 02:50:15 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 02:50:17 2004 Subject: [Spambayes-checkins] spambayes/spambayes/test test_message.py, 1.3, 1.4 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes/test In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv16968/spambayes/test Modified Files: test_message.py Log Message: Update to reflect modified message info db. Index: test_message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/test/test_message.py,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** test_message.py 17 Dec 2004 01:22:28 -0000 1.3 --- test_message.py 22 Dec 2004 01:50:12 -0000 1.4 *************** *** 4,7 **** --- 4,8 ---- import sys import math + import time import email import unittest *************** *** 566,570 **** correct = [(att, getattr(msg, att)) \ for att in msg.stored_attributes] ! self.assertEqual(self.db.db[msg.id], correct) def _fake_store(self): --- 567,575 ---- correct = [(att, getattr(msg, att)) \ for att in msg.stored_attributes] ! db_version = dict(self.db.db[msg.id]) ! correct_version = dict(correct) ! self.assertEqual(db_version["date_modified"], time.time()) ! del db_version["date_modified"] ! self.assertEqual(db_version, correct_version) def _fake_store(self): From anadelonbrin at users.sourceforge.net Wed Dec 22 02:52:05 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 02:52:08 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_pop3dnd.py, 1.15, 1.16 sb_server.py, 1.36, 1.37 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv17316/scripts Modified Files: sb_pop3dnd.py sb_server.py Log Message: Sorry - I forgot to run the tests before the last checkins, so there are obvious failures. Fix a random dot in sb_pop3nd.py and correct the importing in sb_server. Index: sb_pop3dnd.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_pop3dnd.py,v retrieving revision 1.15 retrieving revision 1.16 diff -C2 -d -r1.15 -r1.16 *** sb_pop3dnd.py 22 Dec 2004 00:27:17 -0000 1.15 --- sb_pop3dnd.py 22 Dec 2004 01:51:49 -0000 1.16 *************** *** 303,307 **** ! class IM.APFileMessage(IMAPMessage, FileCorpus.FileMessage): '''IMAP Message that persists as a file system artifact.''' --- 303,307 ---- ! class IMAPFileMessage(IMAPMessage, FileCorpus.FileMessage): '''IMAP Message that persists as a file system artifact.''' Index: sb_server.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_server.py,v retrieving revision 1.36 retrieving revision 1.37 diff -C2 -d -r1.36 -r1.37 *** sb_server.py 22 Dec 2004 01:29:14 -0000 1.36 --- sb_server.py 22 Dec 2004 01:51:49 -0000 1.37 *************** *** 109,117 **** from email.Header import Header from spambayes import i18n from spambayes import Stats from spambayes import Dibbler from spambayes import storage - from spambayes import message from spambayes.FileCorpus import FileCorpus, ExpiryFileCorpus from spambayes.FileCorpus import FileMessageFactory, GzipFileMessageFactory --- 109,117 ---- from email.Header import Header + import spambayes.message from spambayes import i18n from spambayes import Stats from spambayes import Dibbler from spambayes import storage from spambayes.FileCorpus import FileCorpus, ExpiryFileCorpus from spambayes.FileCorpus import FileMessageFactory, GzipFileMessageFactory *************** *** 494,498 **** try: msg = email.message_from_string(messageText, ! _class=message.SBHeaderMessage) msg.setId(state.getNewMessageName()) # Now find the spam disposition and add the header. --- 494,498 ---- try: msg = email.message_from_string(messageText, ! _class=spambayes.message.SBHeaderMessage) msg.setId(state.getNewMessageName()) # Now find the spam disposition and add the header. *************** *** 562,566 **** # This is one case where an unqualified 'except' is OK, 'cos # anything's better than destroying people's email... ! messageText, details = message.\ insert_exception_header(messageText) --- 562,566 ---- # This is one case where an unqualified 'except' is OK, 'cos # anything's better than destroying people's email... ! messageText, details = spambayes.message.\ insert_exception_header(messageText) *************** *** 806,814 **** self.bayes = storage.open_storage(self.DBName, self.useDB) if not hasattr(self, "MBDName"): ! self.MDBName, self.useMDB = message.database_type() ! self.mdb = message.open_storage(self.MDBName, self.useMDB) # Load stats manager. ! self.stats = Stats(options, self.mdb) self.buildStatusStrings() --- 806,814 ---- self.bayes = storage.open_storage(self.DBName, self.useDB) if not hasattr(self, "MBDName"): ! self.MDBName, self.useMDB = spambayes.message.database_type() ! self.mdb = spambayes.message.open_storage(self.MDBName, self.useMDB) # Load stats manager. ! self.stats = Stats.Stats(options, self.mdb) self.buildStatusStrings() From anadelonbrin at users.sourceforge.net Wed Dec 22 04:32:22 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 04:32:27 2004 Subject: [Spambayes-checkins] spambayes/spambayes Dibbler.py,1.15,1.16 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv644/spambayes Modified Files: Dibbler.py Log Message: rstrip(chars) isn't available pre 2.2.2, and we say we support 2.2, so add a function to do the work if rstrip(chars) isn't available. This addresses the concern at http://entrian.com/sbwiki/Python22Compatibility Index: Dibbler.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Dibbler.py,v retrieving revision 1.15 retrieving revision 1.16 diff -C2 -d -r1.15 -r1.16 *** Dibbler.py 6 Dec 2004 01:37:43 -0000 1.15 --- Dibbler.py 22 Dec 2004 03:32:19 -0000 1.16 *************** *** 179,182 **** --- 179,191 ---- True, False = 1, 0 + try: + "".rstrip("abc") + except TypeError: + # rstrip(chars) requires Python 2.2.2 or higher. Apart from that + # we probably work with Python 2.2 (and say we do), so provide the + # ability to do this for that case. + RSTRIP_CHARS_AVAILABLE = False + else: + RSTRIP_CHARS_AVAILABLE = True class BrighterAsyncChat(asynchat.async_chat): *************** *** 594,598 **** minutes from now.""" timeString = time.asctime(time.localtime(time.time() + 20*60)) ! return base64.encodestring(timeString).rstrip('\n=') def _isValidNonce(self, nonce): --- 603,618 ---- minutes from now.""" timeString = time.asctime(time.localtime(time.time() + 20*60)) ! if RSTRIP_CHARS_AVAILABLE: ! return base64.encodestring(timeString).rstrip('\n=') ! else: ! # Python pre 2.2.2, so can't do a rstrip(chars). Do it ! # manually instead. ! def rstrip(s, chars): ! if not s: ! return s ! if s[-1] in chars: ! return rstrip(s[:-1]) ! return s ! return rstrip(base64.encodestring(timeString), '\n=') def _isValidNonce(self, nonce): From anadelonbrin at users.sourceforge.net Wed Dec 22 04:34:58 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 22 04:35:00 2004 Subject: [Spambayes-checkins] spambayes/spambayes Dibbler.py, 1.13.4.2, 1.13.4.3 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv987/spambayes Modified Files: Tag: release_1_0-branch Dibbler.py Log Message: Backport 2.2.1 compatibility. Index: Dibbler.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Dibbler.py,v retrieving revision 1.13.4.2 retrieving revision 1.13.4.3 diff -C2 -d -r1.13.4.2 -r1.13.4.3 *** Dibbler.py 20 Dec 2004 03:53:55 -0000 1.13.4.2 --- Dibbler.py 22 Dec 2004 03:34:55 -0000 1.13.4.3 *************** *** 179,182 **** --- 179,191 ---- True, False = 1, 0 + try: + "".rstrip("abc") + except TypeError: + # rstrip(chars) requires Python 2.2.2 or higher. Apart from that + # we probably work with Python 2.2 (and say we do), so provide the + # ability to do this for that case. + RSTRIP_CHARS_AVAILABLE = False + else: + RSTRIP_CHARS_AVAILABLE = True class BrighterAsyncChat(asynchat.async_chat): *************** *** 594,598 **** minutes from now.""" timeString = time.asctime(time.localtime(time.time() + 20*60)) ! return base64.encodestring(timeString).rstrip('\n=') def _isValidNonce(self, nonce): --- 603,618 ---- minutes from now.""" timeString = time.asctime(time.localtime(time.time() + 20*60)) ! if RSTRIP_CHARS_AVAILABLE: ! return base64.encodestring(timeString).rstrip('\n=') ! else: ! # Python pre 2.2.2, so can't do a rstrip(chars). Do it ! # manually instead. ! def rstrip(s, chars): ! if not s: ! return s ! if s[-1] in chars: ! return rstrip(s[:-1]) ! return s ! return rstrip(base64.encodestring(timeString), '\n=') def _isValidNonce(self, nonce): From kpitt at users.sourceforge.net Wed Dec 22 19:37:39 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Wed Dec 22 19:37:42 2004 Subject: [Spambayes-checkins] spambayes/spambayes message.py,1.64,1.65 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv31065 Modified Files: message.py Log Message: Stats.CalculatePersistentStats tests for old-format messages in the message info database by checking if the date_modified attribute of the message is None. Unfortunately, old-format messages didn't even have a date_modified attribute, which caused an error as soon as the from_date of the Stats object was set to a non-None value. Correct this by always initializing date_modified to None in the Message.__init__ function. Index: message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/message.py,v retrieving revision 1.64 retrieving revision 1.65 diff -C2 -d -r1.64 -r1.65 *** message.py 21 Dec 2004 23:12:11 -0000 1.64 --- message.py 22 Dec 2004 18:37:36 -0000 1.65 *************** *** 171,175 **** def store_msg(self, msg): if self.db is not None: ! attributes = [("date_modified", time.time())] for att in msg.stored_attributes: attributes.append((att, getattr(msg, att))) --- 171,176 ---- def store_msg(self, msg): if self.db is not None: ! msg.date_modified = time.time() ! attributes = [] for att in msg.stored_attributes: attributes.append((att, getattr(msg, att))) *************** *** 288,296 **** nm, typ = database_type() self.message_info_db = open_storage(nm, typ) ! self.stored_attributes = ['c', 't',] self.getDBKey = self.getId self.id = None self.c = None self.t = None if id is not None: --- 289,298 ---- nm, typ = database_type() self.message_info_db = open_storage(nm, typ) ! self.stored_attributes = ['c', 't', 'date_modified', ] self.getDBKey = self.getId self.id = None self.c = None self.t = None + self.date_modified = None if id is not None: From kpitt at users.sourceforge.net Wed Dec 22 19:55:57 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Wed Dec 22 19:56:11 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs dialog_map.py, 1.42, 1.43 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4639/Outlook2000/dialogs Modified Files: dialog_map.py Log Message: Add a button on the Statistics tab to reset the Outlook statistics. Also display the date when the statistics were last reset. Index: dialog_map.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/dialog_map.py,v retrieving revision 1.42 retrieving revision 1.43 diff -C2 -d -r1.42 -r1.43 *** dialog_map.py 5 Dec 2004 23:28:50 -0000 1.42 --- dialog_map.py 22 Dec 2004 18:55:54 -0000 1.43 *************** *** 17,26 **** # "dialog specific" processors: class StatsProcessor(ControlProcessor): def Init(self): ! text = "\n".join(self.window.manager.stats.GetStats()) win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT, 0, text) ! def GetPopupHelpText(self, cid): ! return "Displays statistics on mail processed by SpamBayes" class VersionStringProcessor(ControlProcessor): --- 17,61 ---- # "dialog specific" processors: class StatsProcessor(ControlProcessor): + def __init__(self, window, control_ids): + self.button_id = control_ids[1] + self.reset_date_id = control_ids[2] + ControlProcessor.__init__(self, window, control_ids) + self.stats = self.window.manager.stats + def Init(self): ! text = "\n".join(self.stats.GetStats()) win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT, 0, text) ! date_label = self.GetControl(self.reset_date_id) ! if self.stats.from_date: ! from time import localtime, strftime ! reset_date = localtime(self.stats.from_date) ! date_string = strftime("%a, %d %b %Y %I:%M:%S %p", reset_date) ! else: ! date_string = "None" ! win32gui.SendMessage(date_label, win32con.WM_SETTEXT, 0, date_string) ! ! def OnCommand(self, wparam, lparam): ! id = win32api.LOWORD(wparam) ! if id == self.button_id: ! self.ResetStatistics() ! ! def GetPopupHelpText(self, idFrom): ! if idFrom == self.control_id: ! return "Displays statistics on mail processed by SpamBayes" ! elif idFrom == self.button_id: ! return "Resets all SpamBayes statistics to zero" ! elif idFrom == self.reset_date_id: ! return "The date and time when the SpamBayes statistics were last reset" ! ! def ResetStatistics(self): ! question = "This will reset all your saved statistics to zero.\r\n\r\n" \ ! "Are you sure you wish to reset the statistics?" ! flags = win32con.MB_ICONQUESTION | win32con.MB_YESNO | win32con.MB_DEFBUTTON2 ! if win32gui.MessageBox(self.window.hwnd, ! question, "SpamBayes", flags) == win32con.IDYES: ! self.stats.Reset() ! self.stats.ResetTotal(True) ! self.Init() # update the statistics display class VersionStringProcessor(ControlProcessor): *************** *** 476,480 **** ), "IDD_STATISTICS" : ( ! (StatsProcessor, "IDC_STATISTICS"), ), "IDD_ADVANCED" : ( --- 511,516 ---- ), "IDD_STATISTICS" : ( ! (StatsProcessor, "IDC_STATISTICS IDC_BUT_RESET_STATS " \ ! "IDC_LAST_RESET_DATE"), ), "IDD_ADVANCED" : ( From kpitt at users.sourceforge.net Wed Dec 22 19:55:57 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Wed Dec 22 19:56:11 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs/resources dialogs.h, 1.22, 1.23 dialogs.rc, 1.48, 1.49 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs/resources In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4639/Outlook2000/dialogs/resources Modified Files: dialogs.h dialogs.rc Log Message: Add a button on the Statistics tab to reset the Outlook statistics. Also display the date when the statistics were last reset. Index: dialogs.h =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/resources/dialogs.h,v retrieving revision 1.22 retrieving revision 1.23 diff -C2 -d -r1.22 -r1.23 *** dialogs.h 11 Nov 2004 21:55:46 -0000 1.22 --- dialogs.h 22 Dec 2004 18:55:54 -0000 1.23 *************** *** 1,4 **** //{{NO_DEPENDENCIES}} ! // Microsoft Visual C++ generated include file. // Used by dialogs.rc // --- 1,4 ---- //{{NO_DEPENDENCIES}} ! // Microsoft Developer Studio generated include file. // Used by dialogs.rc // *************** *** 100,103 **** --- 100,105 ---- #define IDC_EDIT1 1094 #define IDC_STATISTICS 1095 + #define IDC_BUT_RESET_STATS 1096 + #define IDC_LAST_RESET_DATE 1097 // Next default values for new objects *************** *** 107,111 **** #define _APS_NEXT_RESOURCE_VALUE 128 #define _APS_NEXT_COMMAND_VALUE 40001 ! #define _APS_NEXT_CONTROL_VALUE 1096 #define _APS_NEXT_SYMED_VALUE 101 #endif --- 109,113 ---- #define _APS_NEXT_RESOURCE_VALUE 128 #define _APS_NEXT_COMMAND_VALUE 40001 ! #define _APS_NEXT_CONTROL_VALUE 1098 #define _APS_NEXT_SYMED_VALUE 101 #endif Index: dialogs.rc =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/resources/dialogs.rc,v retrieving revision 1.48 retrieving revision 1.49 diff -C2 -d -r1.48 -r1.49 *** dialogs.rc 11 Nov 2004 21:55:46 -0000 1.48 --- dialogs.rc 22 Dec 2004 18:55:55 -0000 1.49 *************** *** 1,3 **** ! // Microsoft Visual C++ generated resource script. // #include "dialogs.h" --- 1,3 ---- ! //Microsoft Developer Studio generated resource script. // #include "dialogs.h" *************** *** 29,36 **** IDD_ADVANCED DIALOGEX 0, 0, 248, 209 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Advanced" ! FONT 8, "Tahoma", 400, 0, 0x0 BEGIN GROUPBOX "Filter timer",IDC_STATIC,7,3,234,117 --- 29,36 ---- IDD_ADVANCED DIALOGEX 0, 0, 248, 209 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Advanced" ! FONT 8, "Tahoma", 400 BEGIN GROUPBOX "Filter timer",IDC_STATIC,7,3,234,117 *************** *** 54,83 **** END ! IDD_STATISTICS DIALOGEX 0, 0, 248, 209 ! STYLE DS_SETFONT | WS_CHILD CAPTION "Statistics" ! FONT 8, "Tahoma", 400, 0, 0x0 BEGIN ! GROUPBOX "Statistics",IDC_STATIC,7,3,234,201 LTEXT "some stats\nand some more\nline 3\nline 4\nline 5", ! IDC_STATISTICS,12,12,223,186 END IDD_MANAGER DIALOGEX 0, 0, 275, 260 ! STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP ! CAPTION "SpamBayes" ! FONT 8, "Tahoma", 400, 0, 0x0 BEGIN DEFPUSHBUTTON "Close",IDOK,216,239,50,14 PUSHBUTTON "Cancel",IDCANCEL,155,239,50,14,NOT WS_VISIBLE CONTROL "",IDC_TAB,"SysTabControl32",0x0,8,7,258,228 ! PUSHBUTTON "About",IDC_ABOUT_BTN,9,239,50,14 END IDD_FILTER_SPAM DIALOGEX 0, 0, 251, 147 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU CAPTION "Spam" ! FONT 8, "Tahoma", 400, 0, 0x0 BEGIN LTEXT "Filter the following folders as messages arrive", --- 54,86 ---- END ! IDD_STATISTICS DIALOG DISCARDABLE 0, 0, 248, 209 ! STYLE WS_CHILD | WS_CAPTION CAPTION "Statistics" ! FONT 8, "Tahoma" BEGIN ! GROUPBOX "Statistics",IDC_STATIC,7,3,241,181 LTEXT "some stats\nand some more\nline 3\nline 4\nline 5", ! IDC_STATISTICS,12,12,230,166 ! PUSHBUTTON "Reset Statistics",IDC_BUT_RESET_STATS,178,190,70,14 ! LTEXT "Last reset:",IDC_STATIC,7,193,36,8 ! LTEXT "<<>>",IDC_LAST_RESET_DATE,47,193,107,8 END IDD_MANAGER DIALOGEX 0, 0, 275, 260 ! STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP ! CAPTION "SpamBayes Manager" ! FONT 8, "Tahoma", 400 BEGIN DEFPUSHBUTTON "Close",IDOK,216,239,50,14 PUSHBUTTON "Cancel",IDCANCEL,155,239,50,14,NOT WS_VISIBLE CONTROL "",IDC_TAB,"SysTabControl32",0x0,8,7,258,228 ! PUSHBUTTON "About",IDC_ABOUT_BTN,8,239,50,14 END IDD_FILTER_SPAM DIALOGEX 0, 0, 251, 147 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU CAPTION "Spam" ! FONT 8, "Tahoma", 400 BEGIN LTEXT "Filter the following folders as messages arrive", *************** *** 106,112 **** IDD_FILTER_UNSURE DIALOGEX 0, 0, 249, 124 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU CAPTION "Possible Spam" ! FONT 8, "Tahoma", 400, 0, 0x0 BEGIN LTEXT "To be considered uncertain, a message must score at least", --- 109,115 ---- IDD_FILTER_UNSURE DIALOGEX 0, 0, 249, 124 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU CAPTION "Possible Spam" ! FONT 8, "Tahoma", 400 BEGIN LTEXT "To be considered uncertain, a message must score at least", *************** *** 128,135 **** IDD_DIAGNOSTIC DIALOGEX 0, 0, 183, 98 ! STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Diagnostics" ! FONT 8, "Tahoma", 400, 0, 0x0 BEGIN LTEXT "These advanced options are for diagnostic or debugging purposes only. You should only change these options if specifically asked to, or you know exactly what they mean.", --- 131,138 ---- IDD_DIAGNOSTIC DIALOGEX 0, 0, 183, 98 ! STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Diagnostics" ! FONT 8, "Tahoma", 400 BEGIN LTEXT "These advanced options are for diagnostic or debugging purposes only. You should only change these options if specifically asked to, or you know exactly what they mean.", *************** *** 145,152 **** IDD_WIZARD DIALOGEX 0, 0, 384, 190 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "SpamBayes Configuration Wizard" ! FONT 8, "Tahoma", 400, 0, 0x0 BEGIN PUSHBUTTON "Cancel",IDCANCEL,328,173,50,14 --- 148,155 ---- IDD_WIZARD DIALOGEX 0, 0, 384, 190 ! STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "SpamBayes Configuration Wizard" ! FONT 8, "Tahoma", 400 BEGIN PUSHBUTTON "Cancel",IDCANCEL,328,173,50,14 *************** *** 159,165 **** IDD_WIZARD_WELCOME DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN LTEXT "Welcome to the SpamBayes configuration wizard", --- 162,168 ---- IDD_WIZARD_WELCOME DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Welcome to the SpamBayes configuration wizard", *************** *** 184,190 **** IDD_WIZARD_FINISHED_UNTRAINED DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN LTEXT "Congratulations",IDC_STATIC,20,4,247,14 --- 187,193 ---- IDD_WIZARD_FINISHED_UNTRAINED DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Congratulations",IDC_STATIC,20,4,247,14 *************** *** 202,208 **** IDD_WIZARD_FOLDERS_REST DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_SPAM,208,85,60,15 --- 205,211 ---- IDD_WIZARD_FOLDERS_REST DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_SPAM,208,85,60,15 *************** *** 222,228 **** IDD_WIZARD_FOLDERS_WATCH DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_WATCH,225,134,50,14 --- 225,231 ---- IDD_WIZARD_FOLDERS_WATCH DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_WATCH,225,134,50,14 *************** *** 240,246 **** IDD_WIZARD_FINISHED_UNCONFIGURED DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN LTEXT "Configuration cancelled",IDC_STATIC,20,4,247,14 --- 243,249 ---- IDD_WIZARD_FINISHED_UNCONFIGURED DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Configuration cancelled",IDC_STATIC,20,4,247,14 *************** *** 252,258 **** IDD_WIZARD_FOLDERS_TRAIN DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_HAM,208,49,60,15 --- 255,261 ---- IDD_WIZARD_FOLDERS_TRAIN DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_HAM,208,49,60,15 *************** *** 276,282 **** IDD_WIZARD_TRAIN DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN LTEXT "Training",-1,20,4,247,14 --- 279,285 ---- IDD_WIZARD_TRAIN DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Training",-1,20,4,247,14 *************** *** 289,295 **** IDD_WIZARD_FINISHED_TRAINED DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN LTEXT "Congratulations",IDC_STATIC,20,4,247,14 --- 292,298 ---- IDD_WIZARD_FINISHED_TRAINED DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Congratulations",IDC_STATIC,20,4,247,14 *************** *** 303,309 **** IDD_WIZARD_TRAINING_IS_IMPORTANT DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN LTEXT "SpamBayes will not be effective until it is trained.", --- 306,312 ---- IDD_WIZARD_TRAINING_IS_IMPORTANT DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "SpamBayes will not be effective until it is trained.", *************** *** 326,332 **** IDD_WIZARD_FINISHED_TRAIN_LATER DIALOGEX 0, 0, 284, 162 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN LTEXT "Configuration suspended",IDC_STATIC,20,4,247,14 --- 329,335 ---- IDD_WIZARD_FINISHED_TRAIN_LATER DIALOGEX 0, 0, 284, 162 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Configuration suspended",IDC_STATIC,20,4,247,14 *************** *** 352,356 **** #ifdef APSTUDIO_INVOKED ! GUIDELINES DESIGNINFO BEGIN IDD_ADVANCED, DIALOG --- 355,359 ---- #ifdef APSTUDIO_INVOKED ! GUIDELINES DESIGNINFO MOVEABLE PURE BEGIN IDD_ADVANCED, DIALOG *************** *** 472,477 **** // ! IDB_SBLOGO BITMAP "sblogo.bmp" ! IDB_SBWIZLOGO BITMAP "sbwizlogo.bmp" #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// --- 475,480 ---- // ! IDB_SBLOGO BITMAP MOVEABLE PURE "sblogo.bmp" ! IDB_SBWIZLOGO BITMAP MOVEABLE PURE "sbwizlogo.bmp" #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// *************** *** 493,498 **** IDD_GENERAL DIALOGEX 0, 0, 253, 210 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_VISIBLE | WS_CAPTION | ! WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "General" --- 496,500 ---- IDD_GENERAL DIALOGEX 0, 0, 253, 210 ! STYLE DS_MODALFRAME | WS_CHILD | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "General" *************** *** 517,521 **** IDD_TRAINING DIALOGEX 0, 0, 252, 210 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Training" --- 519,523 ---- IDD_TRAINING DIALOGEX 0, 0, 252, 210 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Training" *************** *** 553,558 **** IDC_BUT_TRAIN_TO_SPAM_FOLDER,"Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,11,163,204,16 ! LTEXT "Clicking 'Spam' button should",IDC_STATIC,10,183,104, ! 10 COMBOBOX IDC_DEL_SPAM_RS,127,180,114,54,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP --- 555,559 ---- IDC_BUT_TRAIN_TO_SPAM_FOLDER,"Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,11,163,204,16 ! LTEXT "Clicking 'Spam' button should",IDC_STATIC,10,183,104,10 COMBOBOX IDC_DEL_SPAM_RS,127,180,114,54,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP *************** *** 560,564 **** IDD_FILTER_NOW DIALOGEX 0, 0, 244, 185 ! STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Filter Now" --- 561,565 ---- IDD_FILTER_NOW DIALOGEX 0, 0, 244, 185 ! STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Filter Now" *************** *** 589,593 **** IDD_FILTER DIALOGEX 0, 0, 249, 209 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Filtering" --- 590,594 ---- IDD_FILTER DIALOGEX 0, 0, 249, 209 ! STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Filtering" *************** *** 635,641 **** IDD_FOLDER_SELECTOR DIALOGEX 0, 0, 247, 215 ! STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" ! FONT 8, "Tahoma", 0, 0, 0x0 BEGIN LTEXT "&Folders:",IDC_STATIC,7,7,47,9 --- 636,642 ---- IDD_FOLDER_SELECTOR DIALOGEX 0, 0, 247, 215 ! STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" ! FONT 8, "Tahoma" BEGIN LTEXT "&Folders:",IDC_STATIC,7,7,47,9 *************** *** 661,665 **** #ifdef APSTUDIO_INVOKED ! GUIDELINES DESIGNINFO BEGIN IDD_GENERAL, DIALOG --- 662,666 ---- #ifdef APSTUDIO_INVOKED ! GUIDELINES DESIGNINFO MOVEABLE PURE BEGIN IDD_GENERAL, DIALOG *************** *** 700,704 **** // ! IDB_FOLDERS BITMAP "folders.bmp" #ifdef APSTUDIO_INVOKED --- 701,705 ---- // ! IDB_FOLDERS BITMAP MOVEABLE PURE "folders.bmp" #ifdef APSTUDIO_INVOKED *************** *** 708,717 **** // ! 1 TEXTINCLUDE BEGIN "dialogs.h\0" END ! 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" --- 709,718 ---- // ! 1 TEXTINCLUDE MOVEABLE PURE BEGIN "dialogs.h\0" END ! 2 TEXTINCLUDE MOVEABLE PURE BEGIN "#include ""winres.h""\r\n" *************** *** 720,724 **** END ! 3 TEXTINCLUDE BEGIN "\r\n" --- 721,725 ---- END ! 3 TEXTINCLUDE MOVEABLE PURE BEGIN "\r\n" From anadelonbrin at users.sourceforge.net Thu Dec 23 03:05:18 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 03:05:22 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 filter.py, 1.45, 1.46 manager.py, 1.105, 1.106 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv15578/Outlook2000 Modified Files: filter.py manager.py Log Message: Only store the classification of the message in the messageinfo db if 'all_actions' is true. This does not include using the spam/not spam buttons, which is necessary because otherwise our statistics will always reflect corrected results, not the initial ones. Use constants for the classification strings to avoid problems with people changing option values when they shouldn't. Index: filter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/filter.py,v retrieving revision 1.45 retrieving revision 1.46 diff -C2 -d -r1.45 -r1.46 *** filter.py 23 Dec 2004 01:50:47 -0000 1.45 --- filter.py 23 Dec 2004 02:05:14 -0000 1.46 *************** *** 17,31 **** disposition = "Yes" attr_prefix = "spam" ! msg.c = mgr.bayes_options["Headers", "header_spam_string"][0] elif prob_perc >= config.unsure_threshold: disposition = "Unsure" attr_prefix = "unsure" ! msg.c = mgr.bayes_options["Headers", "header_unsure_string"][0] else: disposition = "No" attr_prefix = "ham" ! msg.c = mgr.bayes_options["Headers", "header_ham_string"][0] ! mgr.classifier_data.message_db.store_msg(msg) ! mgr.classifier_data.dirty = True ms = mgr.message_store --- 17,32 ---- disposition = "Yes" attr_prefix = "spam" ! if all_actions: ! msg.c = mgr.bayes_message.PERSISTENT_SPAM_STRING elif prob_perc >= config.unsure_threshold: disposition = "Unsure" attr_prefix = "unsure" ! if all_actions: ! msg.c = mgr.bayes_message.PERSISTENT_UNSURE_STRING else: disposition = "No" attr_prefix = "ham" ! if all_actions: ! msg.c = mgr.bayes_message.PERSISTENT_HAM_STRING ms = mgr.message_store *************** *** 49,53 **** if all_actions: msg.RememberMessageCurrentFolder() - mgr.classifier_data.message_db.store_msg(msg) msg.Save() break --- 50,53 ---- *************** *** 111,114 **** --- 111,116 ---- if all_actions: mgr.stats.RecordClassification(prob) + mgr.classifier_data.message_db.store_msg(msg) + mgr.classifier_data.dirty = True return disposition except: Index: manager.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/manager.py,v retrieving revision 1.105 retrieving revision 1.106 diff -C2 -d -r1.105 -r1.106 *** manager.py 22 Dec 2004 01:22:00 -0000 1.105 --- manager.py 23 Dec 2004 02:05:14 -0000 1.106 *************** *** 456,459 **** --- 456,460 ---- self.classifier_data.InitNew() self.bayes_options = bayes_options + self.bayes_message = bayes_message bayes_options["Categorization", "spam_cutoff"] = \ self.config.filter.spam_threshold \ From anadelonbrin at users.sourceforge.net Thu Dec 23 02:50:50 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 03:05:24 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 filter.py,1.44,1.45 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv12322/Outlook2000 Modified Files: filter.py Log Message: Note that the messageinfo db needs saving after making changes. Index: filter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/filter.py,v retrieving revision 1.44 retrieving revision 1.45 diff -C2 -d -r1.44 -r1.45 *** filter.py 22 Dec 2004 01:22:00 -0000 1.44 --- filter.py 23 Dec 2004 01:50:47 -0000 1.45 *************** *** 27,30 **** --- 27,31 ---- msg.c = mgr.bayes_options["Headers", "header_ham_string"][0] mgr.classifier_data.message_db.store_msg(msg) + mgr.classifier_data.dirty = True ms = mgr.message_store From anadelonbrin at users.sourceforge.net Thu Dec 23 02:48:29 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 03:05:25 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py,1.145,1.146 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv11779/Outlook2000 Modified Files: addin.py Log Message: Need to store training status when using the spam/not spam buttons. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.145 retrieving revision 1.146 diff -C2 -d -r1.145 -r1.146 *** addin.py 21 Dec 2004 21:48:30 -0000 1.145 --- addin.py 23 Dec 2004 01:48:26 -0000 1.146 *************** *** 697,700 **** --- 697,703 ---- self.manager.stats.RecordTraining(False, self.manager.score(msgstore_message)) + msgstore_message.t = True + self.manager.classifier_data.message_db.store_msg(msg) + self.manager.classifier_data.dirty = True # Record the original folder, in case this message is not where # it was after filtering, or has never been filtered. *************** *** 765,768 **** --- 768,774 ---- self.manager.stats.RecordTraining(True, self.manager.score(msgstore_message)) + msgstore_message.t = False + self.manager.classifier_data.message_db.store_msg(msg) + self.manager.classifier_data.dirty = True # Must train before moving, else we lose the message! print "Recovering to folder '%s' and ham training message '%s' - " % (restore_folder.name, subject), From anadelonbrin at users.sourceforge.net Thu Dec 23 02:42:43 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 03:05:25 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs dialog_map.py, 1.43, 1.44 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv10561/Outlook2000/dialogs Modified Files: dialog_map.py Log Message: I like "last reset: never" more than "last reset: none". Index: dialog_map.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/dialog_map.py,v retrieving revision 1.43 retrieving revision 1.44 diff -C2 -d -r1.43 -r1.44 *** dialog_map.py 22 Dec 2004 18:55:54 -0000 1.43 --- dialog_map.py 23 Dec 2004 01:42:38 -0000 1.44 *************** *** 33,37 **** date_string = strftime("%a, %d %b %Y %I:%M:%S %p", reset_date) else: ! date_string = "None" win32gui.SendMessage(date_label, win32con.WM_SETTEXT, 0, date_string) --- 33,37 ---- date_string = strftime("%a, %d %b %Y %I:%M:%S %p", reset_date) else: ! date_string = "Never" win32gui.SendMessage(date_label, win32con.WM_SETTEXT, 0, date_string) From anadelonbrin at users.sourceforge.net Thu Dec 23 03:06:27 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 03:06:30 2004 Subject: [Spambayes-checkins] spambayes/spambayes message.py,1.65,1.66 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv15825/spambayes Modified Files: message.py Log Message: Provide constants for the persistent ham/spam/unsure stings for the messageinfo db. Index: message.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/message.py,v retrieving revision 1.65 retrieving revision 1.66 diff -C2 -d -r1.65 -r1.66 *** message.py 22 Dec 2004 18:37:36 -0000 1.65 --- message.py 23 Dec 2004 02:06:21 -0000 1.66 *************** *** 113,116 **** --- 113,119 ---- STATS_START_KEY = "Statistics start date" + PERSISTENT_HAM_STRING = 'h' + PERSISTENT_SPAM_STRING = 's' + PERSISTENT_UNSURE_STRING = 'u' class MessageInfoBase(object): *************** *** 372,380 **** def GetClassification(self): ! if self.c == 's': return options['Headers','header_spam_string'] ! elif self.c == 'h': return options['Headers','header_ham_string'] ! elif self.c == 'u': return options['Headers','header_unsure_string'] return None --- 375,383 ---- def GetClassification(self): ! if self.c == PERSISTENT_SPAM_STRING: return options['Headers','header_spam_string'] ! elif self.c == PERSISTENT_HAM_STRING: return options['Headers','header_ham_string'] ! elif self.c == PERSISTENT_UNSURE_STRING: return options['Headers','header_unsure_string'] return None *************** *** 385,393 **** if cls == options['Headers','header_spam_string']: ! self.c = 's' elif cls == options['Headers','header_ham_string']: ! self.c = 'h' elif cls == options['Headers','header_unsure_string']: ! self.c = 'u' else: raise ValueError, \ --- 388,396 ---- if cls == options['Headers','header_spam_string']: ! self.c = PERSISTENT_SPAM_STRING elif cls == options['Headers','header_ham_string']: ! self.c = PERSISTENT_HAM_STRING elif cls == options['Headers','header_unsure_string']: ! self.c = PERSISTENT_UNSURE_STRING else: raise ValueError, \ From anadelonbrin at users.sourceforge.net Thu Dec 23 05:33:00 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 05:33:02 2004 Subject: [Spambayes-checkins] spambayes/spambayes storage.py,1.45,1.46 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv15585/spambayes Modified Files: storage.py Log Message: Remove unused import. Index: storage.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/storage.py,v retrieving revision 1.45 retrieving revision 1.46 diff -C2 -d -r1.45 -r1.46 *** storage.py 8 Dec 2004 02:06:25 -0000 1.45 --- storage.py 23 Dec 2004 04:32:57 -0000 1.46 *************** *** 67,71 **** import os import sys - import types from spambayes import classifier from spambayes.Options import options, get_pathname_option --- 67,70 ---- From anadelonbrin at users.sourceforge.net Thu Dec 23 05:34:15 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 05:34:19 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py,1.146,1.147 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv15970/Outlook2000 Modified Files: addin.py Log Message: Fix bug with last checkin. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.146 retrieving revision 1.147 diff -C2 -d -r1.146 -r1.147 *** addin.py 23 Dec 2004 01:48:26 -0000 1.146 --- addin.py 23 Dec 2004 04:34:13 -0000 1.147 *************** *** 769,773 **** self.manager.score(msgstore_message)) msgstore_message.t = False ! self.manager.classifier_data.message_db.store_msg(msg) self.manager.classifier_data.dirty = True # Must train before moving, else we lose the message! --- 769,773 ---- self.manager.score(msgstore_message)) msgstore_message.t = False ! self.manager.classifier_data.message_db.store_msg(msgstore_message) self.manager.classifier_data.dirty = True # Must train before moving, else we lose the message! From anadelonbrin at users.sourceforge.net Thu Dec 23 06:18:24 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 06:18:27 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs/resources dialogs.h, 1.23, 1.24 dialogs.rc, 1.49, 1.50 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs/resources In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv24915/Outlook2000/dialogs/resources Modified Files: dialogs.h dialogs.rc Log Message: Enlarge the Manager dialog. This makes room for all the statistics that we want to show (plus one or two more later), and a control to set the ham folder. The general tab has more room to show the folders being filtered (it never fit well before). There's room on the training tab for some sort of option controlling the training regime, and room on the advanced tab for something new, too. Is this too large now? I don't think we'd want to go larger than this. Index: dialogs.h =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/resources/dialogs.h,v retrieving revision 1.23 retrieving revision 1.24 diff -C2 -d -r1.23 -r1.24 *** dialogs.h 22 Dec 2004 18:55:54 -0000 1.23 --- dialogs.h 23 Dec 2004 05:18:21 -0000 1.24 *************** *** 56,63 **** --- 56,66 ---- #define IDC_EDIT_UNSURE 1030 #define IDC_ACTION_UNSURE 1031 + #define IDC_ACTION_HAM 1032 #define IDC_FOLDER_UNSURE 1033 #define IDC_BROWSE_UNSURE 1034 #define IDC_TRAINING_STATUS 1035 + #define IDC_FOLDER_HAM 1098 #define IDC_FOLDER_NAMES 1036 + #define IDC_BROWSE_HAM 1099 #define IDC_BROWSE 1037 #define IDC_FOLDER_WATCH 1038 Index: dialogs.rc =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/resources/dialogs.rc,v retrieving revision 1.49 retrieving revision 1.50 diff -C2 -d -r1.49 -r1.50 *** dialogs.rc 22 Dec 2004 18:55:55 -0000 1.49 --- dialogs.rc 23 Dec 2004 05:18:21 -0000 1.50 *************** *** 28,36 **** // ! IDD_ADVANCED DIALOGEX 0, 0, 248, 209 STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Advanced" ! FONT 8, "Tahoma", 400 BEGIN GROUPBOX "Filter timer",IDC_STATIC,7,3,234,117 --- 28,36 ---- // ! IDD_ADVANCED DIALOGEX 0, 0, 248, 257 STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Advanced" ! FONT 8, "Tahoma", 400, 0, 0x1 BEGIN GROUPBOX "Filter timer",IDC_STATIC,7,3,234,117 *************** *** 48,80 **** IDC_INBOX_TIMER_ONLY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,100,217,10 ! PUSHBUTTON "Show Data Folder",IDC_SHOW_DATA_FOLDER,7,190,70,14 CONTROL "Enable background filtering",IDC_BUT_TIMER_ENABLED, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,12,162,10 ! PUSHBUTTON "Diagnostics...",IDC_BUT_SHOW_DIAGNOSTICS,171,190,70,14 END ! IDD_STATISTICS DIALOG DISCARDABLE 0, 0, 248, 209 STYLE WS_CHILD | WS_CAPTION CAPTION "Statistics" FONT 8, "Tahoma" BEGIN ! GROUPBOX "Statistics",IDC_STATIC,7,3,241,181 LTEXT "some stats\nand some more\nline 3\nline 4\nline 5", ! IDC_STATISTICS,12,12,230,166 ! PUSHBUTTON "Reset Statistics",IDC_BUT_RESET_STATS,178,190,70,14 ! LTEXT "Last reset:",IDC_STATIC,7,193,36,8 ! LTEXT "<<>>",IDC_LAST_RESET_DATE,47,193,107,8 END ! IDD_MANAGER DIALOGEX 0, 0, 275, 260 STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "SpamBayes Manager" ! FONT 8, "Tahoma", 400 BEGIN ! DEFPUSHBUTTON "Close",IDOK,216,239,50,14 ! PUSHBUTTON "Cancel",IDCANCEL,155,239,50,14,NOT WS_VISIBLE ! CONTROL "",IDC_TAB,"SysTabControl32",0x0,8,7,258,228 ! PUSHBUTTON "About",IDC_ABOUT_BTN,8,239,50,14 END --- 48,80 ---- IDC_INBOX_TIMER_ONLY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,100,217,10 ! PUSHBUTTON "Show Data Folder",IDC_SHOW_DATA_FOLDER,7,238,70,14 CONTROL "Enable background filtering",IDC_BUT_TIMER_ENABLED, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,12,162,10 ! PUSHBUTTON "Diagnostics...",IDC_BUT_SHOW_DIAGNOSTICS,171,238,70,14 END ! IDD_STATISTICS DIALOG DISCARDABLE 0, 0, 248, 257 STYLE WS_CHILD | WS_CAPTION CAPTION "Statistics" FONT 8, "Tahoma" BEGIN ! GROUPBOX "Statistics",IDC_STATIC,7,3,241,229 LTEXT "some stats\nand some more\nline 3\nline 4\nline 5", ! IDC_STATISTICS,12,12,230,204 ! PUSHBUTTON "Reset Statistics",IDC_BUT_RESET_STATS,178,238,70,14 ! LTEXT "Last reset:",IDC_STATIC,7,241,36,8 ! LTEXT "<<>>",IDC_LAST_RESET_DATE,47,241,107,8 END ! IDD_MANAGER DIALOGEX 0, 0, 275, 308 STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "SpamBayes Manager" ! FONT 8, "Tahoma" BEGIN ! DEFPUSHBUTTON "Close",IDOK,216,287,50,14 ! PUSHBUTTON "Cancel",IDCANCEL,155,287,50,14,NOT WS_VISIBLE ! CONTROL "",IDC_TAB,"SysTabControl32",0x0,8,7,258,276 ! PUSHBUTTON "About",IDC_ABOUT_BTN,8,287,50,14 END *************** *** 82,86 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU CAPTION "Spam" ! FONT 8, "Tahoma", 400 BEGIN LTEXT "Filter the following folders as messages arrive", --- 82,86 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU CAPTION "Spam" ! FONT 8, "Tahoma", 400, 0, 0x1 BEGIN LTEXT "Filter the following folders as messages arrive", *************** *** 111,115 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU CAPTION "Possible Spam" ! FONT 8, "Tahoma", 400 BEGIN LTEXT "To be considered uncertain, a message must score at least", --- 111,115 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU CAPTION "Possible Spam" ! FONT 8, "Tahoma", 400, 0, 0x1 BEGIN LTEXT "To be considered uncertain, a message must score at least", *************** *** 134,138 **** EXSTYLE WS_EX_CONTEXTHELP CAPTION "Diagnostics" ! FONT 8, "Tahoma", 400 BEGIN LTEXT "These advanced options are for diagnostic or debugging purposes only. You should only change these options if specifically asked to, or you know exactly what they mean.", --- 134,138 ---- EXSTYLE WS_EX_CONTEXTHELP CAPTION "Diagnostics" ! FONT 8, "Tahoma", 400, 0, 0x1 BEGIN LTEXT "These advanced options are for diagnostic or debugging purposes only. You should only change these options if specifically asked to, or you know exactly what they mean.", *************** *** 151,155 **** EXSTYLE WS_EX_CONTEXTHELP CAPTION "SpamBayes Configuration Wizard" ! FONT 8, "Tahoma", 400 BEGIN PUSHBUTTON "Cancel",IDCANCEL,328,173,50,14 --- 151,155 ---- EXSTYLE WS_EX_CONTEXTHELP CAPTION "SpamBayes Configuration Wizard" ! FONT 8, "Tahoma", 400, 0, 0x1 BEGIN PUSHBUTTON "Cancel",IDCANCEL,328,173,50,14 *************** *** 164,168 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Welcome to the SpamBayes configuration wizard", --- 164,168 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "Welcome to the SpamBayes configuration wizard", *************** *** 189,193 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Congratulations",IDC_STATIC,20,4,247,14 --- 189,193 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "Congratulations",IDC_STATIC,20,4,247,14 *************** *** 207,211 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_SPAM,208,85,60,15 --- 207,211 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_SPAM,208,85,60,15 *************** *** 227,231 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_WATCH,225,134,50,14 --- 227,231 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_WATCH,225,134,50,14 *************** *** 245,249 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Configuration cancelled",IDC_STATIC,20,4,247,14 --- 245,249 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "Configuration cancelled",IDC_STATIC,20,4,247,14 *************** *** 257,261 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_HAM,208,49,60,15 --- 257,261 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN PUSHBUTTON "Browse...",IDC_BROWSE_HAM,208,49,60,15 *************** *** 281,285 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Training",-1,20,4,247,14 --- 281,285 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "Training",-1,20,4,247,14 *************** *** 294,298 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Congratulations",IDC_STATIC,20,4,247,14 --- 294,298 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "Congratulations",IDC_STATIC,20,4,247,14 *************** *** 308,312 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "SpamBayes will not be effective until it is trained.", --- 308,312 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "SpamBayes will not be effective until it is trained.", *************** *** 331,335 **** STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma" BEGIN LTEXT "Configuration suspended",IDC_STATIC,20,4,247,14 --- 331,335 ---- STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION EXSTYLE WS_EX_CONTEXTHELP ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "Configuration suspended",IDC_STATIC,20,4,247,14 *************** *** 495,499 **** // ! IDD_GENERAL DIALOGEX 0, 0, 253, 210 STYLE DS_MODALFRAME | WS_CHILD | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP --- 495,499 ---- // ! IDD_GENERAL DIALOGEX 0, 0, 253, 257 STYLE DS_MODALFRAME | WS_CHILD | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP *************** *** 508,516 **** IDC_TRAINING_STATUS,6,101,242,27,SS_SUNKEN CONTROL "Enable SpamBayes",IDC_BUT_FILTER_ENABLE,"Button", ! BS_AUTOCHECKBOX | WS_TABSTOP,6,173,97,11 LTEXT "Certain spam is moved to Folder1\nPossible spam is moved too", ! IDC_FILTER_STATUS,6,146,242,19,SS_SUNKEN ! PUSHBUTTON "Reset Configuration...",IDC_BUT_RESET,6,190,84,15 ! PUSHBUTTON "Configuration Wizard...",IDC_BUT_WIZARD,164,190,84,15 LTEXT "Filter status:",IDC_STATIC,6,135,222,8 CONTROL 1062,IDC_LOGO_GRAPHIC,"Static",SS_BITMAP | --- 508,516 ---- IDC_TRAINING_STATUS,6,101,242,27,SS_SUNKEN CONTROL "Enable SpamBayes",IDC_BUT_FILTER_ENABLE,"Button", ! BS_AUTOCHECKBOX | WS_TABSTOP,6,221,97,11 LTEXT "Certain spam is moved to Folder1\nPossible spam is moved too", ! IDC_FILTER_STATUS,6,146,242,67,SS_SUNKEN ! PUSHBUTTON "Reset Configuration...",IDC_BUT_RESET,6,238,84,15 ! PUSHBUTTON "Configuration Wizard...",IDC_BUT_WIZARD,164,238,84,15 LTEXT "Filter status:",IDC_STATIC,6,135,222,8 CONTROL 1062,IDC_LOGO_GRAPHIC,"Static",SS_BITMAP | *************** *** 518,522 **** END ! IDD_TRAINING DIALOGEX 0, 0, 252, 210 STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP --- 518,522 ---- END ! IDD_TRAINING DIALOGEX 0, 0, 252, 257 STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP *************** *** 589,597 **** END ! IDD_FILTER DIALOGEX 0, 0, 249, 209 STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Filtering" ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "Filter the following folders as messages arrive", --- 589,597 ---- END ! IDD_FILTER DIALOGEX 0, 0, 249, 257 STYLE DS_MODALFRAME | WS_CHILD | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTEXTHELP CAPTION "Filtering" ! FONT 8, "Tahoma" BEGIN LTEXT "Filter the following folders as messages arrive", *************** *** 628,636 **** SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_SUNKEN | WS_GROUP,102,166,77,14 ! PUSHBUTTON "&Browse",IDC_BROWSE_UNSURE,183,166,50,14 CONTROL "Mark spam as read",IDC_MARK_SPAM_AS_READ,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,13,100,81,10 CONTROL "Mark possible spam as read",IDC_MARK_UNSURE_AS_READ, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,186,101,10 END --- 628,645 ---- SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_SUNKEN | WS_GROUP,102,166,77,14 ! PUSHBUTTON "&Browse",IDC_BROWSE_UNSURE,184,166,50,14 CONTROL "Mark spam as read",IDC_MARK_SPAM_AS_READ,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,13,100,81,10 CONTROL "Mark possible spam as read",IDC_MARK_UNSURE_AS_READ, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,186,101,10 + GROUPBOX "Certain Good",IDC_STATIC,6,203,235,48 + LTEXT "These messages should be:",IDC_STATIC,12,215,107,10 + COMBOBOX IDC_ACTION_HAM,12,228,55,40,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "to folder",IDC_STATIC,71,230,27,10 + CONTROL "(folder name)",IDC_FOLDER_HAM,"Static", + SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_SUNKEN | + WS_GROUP,102,228,77,14 + PUSHBUTTON "&Browse",IDC_BROWSE_HAM,184,228,50,14 END *************** *** 638,642 **** STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" ! FONT 8, "Tahoma" BEGIN LTEXT "&Folders:",IDC_STATIC,7,7,47,9 --- 647,651 ---- STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" ! FONT 8, "Tahoma", 0, 0, 0x1 BEGIN LTEXT "&Folders:",IDC_STATIC,7,7,47,9 *************** *** 689,693 **** IDD_FILTER, DIALOG BEGIN ! BOTTOMMARGIN, 206 HORZGUIDE, 127 END --- 698,702 ---- IDD_FILTER, DIALOG BEGIN ! BOTTOMMARGIN, 254 HORZGUIDE, 127 END From anadelonbrin at users.sourceforge.net Thu Dec 23 06:19:42 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Thu Dec 23 06:19:45 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs dialog_map.py, 1.44, 1.45 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv25096/Outlook2000/dialogs Modified Files: dialog_map.py Log Message: Attach the ham folder options. Index: dialog_map.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/dialog_map.py,v retrieving revision 1.44 retrieving revision 1.45 diff -C2 -d -r1.44 -r1.45 *** dialog_map.py 23 Dec 2004 01:42:38 -0000 1.44 --- dialog_map.py 23 Dec 2004 05:19:39 -0000 1.45 *************** *** 483,489 **** (EditNumberProcessor, "IDC_EDIT_UNSURE IDC_SLIDER_UNSURE", "Filter.unsure_threshold"), - (ComboProcessor, "IDC_ACTION_UNSURE", "Filter.unsure_action"), (BoolButtonProcessor, "IDC_MARK_UNSURE_AS_READ", "Filter.unsure_mark_as_read"), ), "IDD_TRAINING" : ( --- 483,491 ---- (EditNumberProcessor, "IDC_EDIT_UNSURE IDC_SLIDER_UNSURE", "Filter.unsure_threshold"), (ComboProcessor, "IDC_ACTION_UNSURE", "Filter.unsure_action"), (BoolButtonProcessor, "IDC_MARK_UNSURE_AS_READ", "Filter.unsure_mark_as_read"), + (FolderIDProcessor, "IDC_FOLDER_HAM IDC_BROWSE_HAM", + "Filter.ham_folder_id"), + (ComboProcessor, "IDC_ACTION_HAM", "Filter.ham_action"), ), "IDD_TRAINING" : ( From kpitt at users.sourceforge.net Thu Dec 23 18:13:40 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Thu Dec 23 18:13:43 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs/resources dialogs.h, 1.24, 1.25 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs/resources In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv19932 Modified Files: dialogs.h Log Message: Update the _APS_NEXT_CONTROL_VALUE so that we don't get an id conflict the next time someone adds a control from the Visual Studio resource editor. Index: dialogs.h =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/resources/dialogs.h,v retrieving revision 1.24 retrieving revision 1.25 diff -C2 -d -r1.24 -r1.25 *** dialogs.h 23 Dec 2004 05:18:21 -0000 1.24 --- dialogs.h 23 Dec 2004 17:13:37 -0000 1.25 *************** *** 112,116 **** #define _APS_NEXT_RESOURCE_VALUE 128 #define _APS_NEXT_COMMAND_VALUE 40001 ! #define _APS_NEXT_CONTROL_VALUE 1098 #define _APS_NEXT_SYMED_VALUE 101 #endif --- 112,116 ---- #define _APS_NEXT_RESOURCE_VALUE 128 #define _APS_NEXT_COMMAND_VALUE 40001 ! #define _APS_NEXT_CONTROL_VALUE 1100 #define _APS_NEXT_SYMED_VALUE 101 #endif From kpitt at users.sourceforge.net Thu Dec 23 19:14:35 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Thu Dec 23 19:14:39 2004 Subject: [Spambayes-checkins] spambayes/scripts sb_filter.py, 1.15, 1.16 sb_imapfilter.py, 1.49, 1.50 sb_pop3dnd.py, 1.16, 1.17 sb_server.py, 1.37, 1.38 Message-ID: Update of /cvsroot/spambayes/spambayes/scripts In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1047/scripts Modified Files: sb_filter.py sb_imapfilter.py sb_pop3dnd.py sb_server.py Log Message: New version numbering scheme. All apps now use the same version number, and the current version information is read from the __init__.py file in the "spambayes" module. Float version numbers are no longer used for update checks. Instead, our standard string version number format is parsed into a tuple in the format of the sys.version_info field. Versions can then be compared based on that tuple. Index: sb_filter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_filter.py,v retrieving revision 1.15 retrieving revision 1.16 diff -C2 -d -r1.15 -r1.16 *** sb_filter.py 22 Nov 2004 00:13:43 -0000 1.15 --- sb_filter.py 23 Dec 2004 18:14:18 -0000 1.16 *************** *** 77,81 **** import getopt from spambayes import hammie, Options, mboxutils, storage ! from spambayes.Version import get_version_string try: --- 77,81 ---- import getopt from spambayes import hammie, Options, mboxutils, storage ! from spambayes.Version import get_current_version try: *************** *** 126,131 **** """Print usage message and sys.exit(code).""" # Include version info in usage ! print >> sys.stderr, get_version_string("sb_filter") ! print >> sys.stderr, " with engine %s" % get_version_string() print >> sys.stderr --- 126,131 ---- """Print usage message and sys.exit(code).""" # Include version info in usage ! v = get_current_version() ! print >> sys.stderr, v.get_long_version("SpamBayes Command Line Filter") print >> sys.stderr Index: sb_imapfilter.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_imapfilter.py,v retrieving revision 1.49 retrieving revision 1.50 diff -C2 -d -r1.49 -r1.50 *** sb_imapfilter.py 22 Dec 2004 00:27:17 -0000 1.49 --- sb_imapfilter.py 23 Dec 2004 18:14:32 -0000 1.50 *************** *** 104,108 **** from spambayes.UserInterface import UserInterfaceServer from spambayes.ImapUI import IMAPUserInterface ! from spambayes.Version import get_version_string from imaplib import IMAP4 --- 104,108 ---- from spambayes.UserInterface import UserInterfaceServer from spambayes.ImapUI import IMAPUserInterface ! from spambayes.Version import get_current_version from imaplib import IMAP4 *************** *** 1023,1028 **** # Let the user know what they are using... ! print get_version_string("IMAP Filter") ! print "and engine %s.\n" % (get_version_string(),) if options["globals", "verbose"]: --- 1023,1028 ---- # Let the user know what they are using... ! v = get_current_version(); ! print "%s.\n" % (v.get_long_version("SpamBayes IMAP Filter"),) if options["globals", "verbose"]: Index: sb_pop3dnd.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_pop3dnd.py,v retrieving revision 1.16 retrieving revision 1.17 diff -C2 -d -r1.16 -r1.17 *** sb_pop3dnd.py 22 Dec 2004 01:51:49 -0000 1.16 --- sb_pop3dnd.py 23 Dec 2004 18:14:32 -0000 1.17 *************** *** 101,105 **** from spambayes.tokenizer import tokenize from spambayes import FileCorpus, Dibbler ! from spambayes.Version import get_version_string from sb_server import POP3ProxyBase, State, _addressPortStr, _recreateState --- 101,105 ---- from spambayes.tokenizer import tokenize from spambayes import FileCorpus, Dibbler ! from spambayes.Version import get_current_version from sb_server import POP3ProxyBase, State, _addressPortStr, _recreateState *************** *** 1021,1027 **** # Let the user know what they are using... ! print get_version_string("IMAP Server") ! print get_version_string("POP3 Proxy") ! print get_version_string() from twisted.copyright import version as twisted_version print "Twisted version %s.\n" % (twisted_version,) --- 1021,1026 ---- # Let the user know what they are using... ! v = get_current_version() ! print v.get_long_version() from twisted.copyright import version as twisted_version print "Twisted version %s.\n" % (twisted_version,) Index: sb_server.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/scripts/sb_server.py,v retrieving revision 1.37 retrieving revision 1.38 diff -C2 -d -r1.37 -r1.38 *** sb_server.py 22 Dec 2004 01:51:49 -0000 1.37 --- sb_server.py 23 Dec 2004 18:14:32 -0000 1.38 *************** *** 119,123 **** from spambayes.UserInterface import UserInterfaceServer from spambayes.ProxyUI import ProxyUserInterface ! from spambayes.Version import get_version_string --- 119,123 ---- from spambayes.UserInterface import UserInterfaceServer from spambayes.ProxyUI import ProxyUserInterface ! from spambayes.Version import get_current_version *************** *** 990,995 **** # Let the user know what they are using... ! print get_version_string("POP3 Proxy") ! print "and engine %s.\n" % (get_version_string(),) if 0 <= len(args) <= 2: --- 990,995 ---- # Let the user know what they are using... ! v = get_current_version() ! print "%s\n" % (v.get_long_version("SpamBayes POP3 Proxy"),) if 0 <= len(args) <= 2: From kpitt at users.sourceforge.net Thu Dec 23 19:14:37 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Thu Dec 23 19:14:40 2004 Subject: [Spambayes-checkins] spambayes/windows pop3proxy_tray.py,1.22,1.23 Message-ID: Update of /cvsroot/spambayes/spambayes/windows In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1047/windows Modified Files: pop3proxy_tray.py Log Message: New version numbering scheme. All apps now use the same version number, and the current version information is read from the __init__.py file in the "spambayes" module. Float version numbers are no longer used for update checks. Instead, our standard string version number format is parsed into a tuple in the format of the sys.version_info field. Versions can then be compared based on that tuple. Index: pop3proxy_tray.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/windows/pop3proxy_tray.py,v retrieving revision 1.22 retrieving revision 1.23 diff -C2 -d -r1.22 -r1.23 *** pop3proxy_tray.py 22 Jul 2004 05:56:56 -0000 1.22 --- pop3proxy_tray.py 23 Dec 2004 18:14:33 -0000 1.23 *************** *** 507,522 **** def CheckVersion(self): # Stolen, with few modifications, from addin.py ! from spambayes.Version import get_version_string, \ ! get_version_number, fetch_latest_dict ! if hasattr(sys, "frozen"): ! version_number_key = "BinaryVersion" ! version_string_key = "Full Description Binary" ! else: ! version_number_key = "Version" ! version_string_key = "Full Description" app_name = "POP3 Proxy" ! cur_ver_string = get_version_string(app_name, version_string_key) ! cur_ver_num = get_version_number(app_name, version_number_key) try: --- 507,517 ---- def CheckVersion(self): # Stolen, with few modifications, from addin.py ! from spambayes.Version import get_current_version, get_version, \ ! get_download_page, fetch_latest_dict app_name = "POP3 Proxy" ! app_display_name = "SpamBayes POP3 Proxy" ! ver_current = get_current_version() ! cur_ver_string = ver_current.get_long_version(app_display_name) try: *************** *** 524,536 **** latest = fetch_latest_dict() SetWaitCursor(0) ! try: ! latest_ver_string = get_version_string(app_name, version_string_key, ! version_dict=latest) ! latest_ver_num = get_version_number(app_name, version_number_key, ! version_dict=latest) ! except KeyError: ! # "Full Description Binary" not in the version currently on the web ! latest_ver_string = "0.1" ! latest_ver_num = 0.1 except: self.ShowMessage("Error checking the latest version") --- 519,524 ---- latest = fetch_latest_dict() SetWaitCursor(0) ! ver_latest = get_version(app_name, version_dict=latest) ! latest_ver_string = ver_latest.get_long_version(app_display_name) except: self.ShowMessage("Error checking the latest version") *************** *** 538,547 **** return ! self.ShowMessage("Current version is %s, latest is %s." % \ ! (cur_ver_string, latest_ver_string)) ! if latest_ver_num > cur_ver_num: ! url = get_version_string(app_name, "Download Page", version_dict=latest) ! # Offer to open up the url ! ## os.startfile(url) def ShowMessage(self, msg): --- 526,546 ---- return ! ver_message = "Current version is %s.\r\n" \ ! "Latest version is %s.\r\n\r\n" % \ ! (cur_ver_string, latest_ver_string) ! if ver_latest == ver_current: ! ver_message += "Your are running the latest downloadable version." ! elif ver_current > ver_latest: ! ver_message += "Your current version is newer than the latest " \ ! "downloadable version." ! else: ! ver_message += "There is a newer version available. You may " \ ! "download the updated version from:\r\n" ! url = get_download_page(app_name, version_dict=latest) ! ver_message += url ! self.ShowMessage(ver_message) ! # It would be nice to offer to open up the url if there is a ! # newer version, but we don't do that yet. ! ## os.startfile(url) def ShowMessage(self, msg): From kpitt at users.sourceforge.net Thu Dec 23 19:14:38 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Thu Dec 23 19:14:44 2004 Subject: [Spambayes-checkins] spambayes/spambayes ImapUI.py, 1.41, 1.42 ProxyUI.py, 1.56, 1.57 UserInterface.py, 1.51, 1.52 Version.py, 1.33, 1.34 __init__.py, 1.13, 1.14 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1047/spambayes Modified Files: ImapUI.py ProxyUI.py UserInterface.py Version.py __init__.py Log Message: New version numbering scheme. All apps now use the same version number, and the current version information is read from the __init__.py file in the "spambayes" module. Float version numbers are no longer used for update checks. Instead, our standard string version number format is parsed into a tuple in the format of the sys.version_info field. Versions can then be compared based on that tuple. Index: ImapUI.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/ImapUI.py,v retrieving revision 1.41 retrieving revision 1.42 diff -C2 -d -r1.41 -r1.42 *** ImapUI.py 22 Dec 2004 00:23:58 -0000 1.41 --- ImapUI.py 23 Dec 2004 18:14:32 -0000 1.42 *************** *** 128,132 **** self.imap_pwd = pwd self.imap_logged_in = False ! self.app_for_version = "IMAP Filter" self.imap_session_class = imap_session_class --- 128,132 ---- self.imap_pwd = pwd self.imap_logged_in = False ! self.app_for_version = "SpamBayes IMAP Filter" self.imap_session_class = imap_session_class Index: ProxyUI.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/ProxyUI.py,v retrieving revision 1.56 retrieving revision 1.57 diff -C2 -d -r1.56 -r1.57 *** ProxyUI.py 22 Dec 2004 00:23:58 -0000 1.56 --- ProxyUI.py 23 Dec 2004 18:14:32 -0000 1.57 *************** *** 169,173 **** state = proxy_state self.state_recreator = state_recreator # ugly ! self.app_for_version = "POP3 Proxy" self.previous_sort = None if not proxy_state.can_stop: --- 169,173 ---- state = proxy_state self.state_recreator = state_recreator # ugly ! self.app_for_version = "SpamBayes POP3 Proxy" self.previous_sort = None if not proxy_state.can_stop: Index: UserInterface.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/UserInterface.py,v retrieving revision 1.51 retrieving revision 1.52 diff -C2 -d -r1.51 -r1.52 *** UserInterface.py 22 Dec 2004 00:23:58 -0000 1.51 --- UserInterface.py 23 Dec 2004 18:14:32 -0000 1.52 *************** *** 162,166 **** timestamp = time.strftime('%H:%M on %A %B %d %Y', time.localtime()) clone.footer.timestamp = timestamp ! clone.footer.version = Version.get_version_string(self.app_for_version) if help_topic: clone.helplink.href = "help?topic=%s" % (help_topic,) --- 162,167 ---- timestamp = time.strftime('%H:%M on %A %B %d %Y', time.localtime()) clone.footer.timestamp = timestamp ! v = Version.get_current_version() ! clone.footer.version = v.get_long_version(self.app_for_version) if help_topic: clone.helplink.href = "help?topic=%s" % (help_topic,) *************** *** 940,944 **** report = self.html.bugreport.clone() # Prefill the report ! sb_ver = Version.get_version_string(self.app_for_version) if hasattr(sys, "frozen"): sb_type = "binary" --- 941,946 ---- report = self.html.bugreport.clone() # Prefill the report ! v = Version.get_current_version() ! sb_ver = v.get_long_version(self.app_for_version) if hasattr(sys, "frozen"): sb_type = "binary" *************** *** 1042,1046 **** outer['CC'] = from_addr outer['From'] = from_addr ! outer['X-Mailer'] = Version.get_version_string(self.app_for_version) outer.preamble = self._wrap(message) # To guarantee the message ends with a newline --- 1044,1049 ---- outer['CC'] = from_addr outer['From'] = from_addr ! v = Version.get_current_version() ! outer['X-Mailer'] = v.get_long_version(self.app_for_version) outer.preamble = self._wrap(message) # To guarantee the message ends with a newline Index: Version.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Version.py,v retrieving revision 1.33 retrieving revision 1.34 diff -C2 -d -r1.33 -r1.34 *** Version.py 22 Jul 2004 23:25:43 -0000 1.33 --- Version.py 23 Dec 2004 18:14:33 -0000 1.34 *************** *** 11,14 **** --- 11,22 ---- """ + import string, re + from types import StringType + + try: + _ + except NameError: + _ = lambda arg: arg + # See bug 806238: urllib2 fails in Outlook new-version chk. # A reason for why the spambayes.org URL fails is given in a comment there. *************** *** 16,116 **** # The SF URL instead works for Tim and xenogeist. LATEST_VERSION_HOME="http://spambayes.sourceforge.net/download/Version.cfg" ! # This module is part of the spambayes project, which is Copyright 2002-4 # The Python Software Foundation and is covered by the Python Software # Foundation license. versions = { ! # Non app specific - changed when "spambayes\*" changes significantly ! "Version": 0.3, ! "Description": "SpamBayes Engine", ! "Date": "January 2004", ! "Full Description": "%(Description)s Version %(Version)s (%(Date)s)", ! # Sub-dict for application specific version strings. "Apps": { - "sb_filter" : { - "Version": 0.3, - "Description": "SpamBayes Command Line Filter", - "Date": "April 2004", - "Full Description": "%(Description)s Version %(Version)s (%(Date)s)", - }, "Outlook" : { - # Note these version numbers currently don't appear in the - # "description" strings below - they just need to increment - # so automated version checking works. - # 0.99 indicates '1.0b/rc/' so will go 0.992 etc, until a real - # 1.0, which can get 1.0 :) - "Version": 0.992, - "BinaryVersion": 0.992, "Description": "SpamBayes Outlook Addin", - "Date": "May 2004", - "Full Description": "%(Description)s Version 1.0rc1 (%(Date)s)", - "Full Description Binary": - "%(Description)s Binary Version 1.0rc1 (%(Date)s)", - # Note this means we can change the download page later, and old - # versions will still go to the new page. - # We may also like to have a "Release Notes Page" item later? - "Download Page": "http://spambayes.sourceforge.net/windows.html" }, "POP3 Proxy" : { - # Note these version numbers also currently don't appear in the - # "description" strings below - see above - "Version": 0.6, - "BinaryVersion": 0.6, "Description": "SpamBayes POP3 Proxy", - "Date": "May 2004", - "Full Description": """%(Description)s Version 1.0rc1 (%(Date)s)""", - "Full Description Binary": - """%(Description)s Binary Version 1.0rc1 (%(Date)s)""", - # Note this means we can change the download page later, and old - # versions will still go to the new page. - # We may also like to have a "Release Notes Page" item later? - "Download Page": "http://spambayes.sourceforge.net/windows.html" - }, - "Lotus Notes Filter" : { - "Version": 0.02, - "Description": "SpamBayes Lotus Notes Filter", - "Date": "February 2004", - "Full Description": "%(Description)s Version %(Version)s (%(Date)s)", - }, - "IMAP Filter" : { - "Version": 0.4, - "Description": "SpamBayes IMAP Filter", - "Date": "May 2004", - "Full Description": """%(Description)s Version %(Version)s (%(Date)s)""", - }, - "IMAP Server" : { - "Version": 0.02, - "Description": "SpamBayes IMAP Server", - "Date": "January 2004", - "Full Description": """%(Description)s Version %(Version)s (%(Date)s)""", }, }, } ! def get_version_string(app = None, ! description_key = "Full Description", ! version_dict = None): ! """Get a pretty version string, generally just to log or show in a UI""" if version_dict is None: version_dict = versions ! if app is None: ! dict = version_dict ! else: ! dict = version_dict["Apps"][app] ! return dict[description_key] % dict ! def get_version_number(app = None, ! version_key = "Version", ! version_dict = None): ! """Get a version number, as a float. This would primarily be used so some ! app or extension can determine if we are later than a specific version ! of either the engine or a specific app. ! Maybe YAGNI. """ ! if version_dict is None: version_dict = versions ! if app is None: ! dict = version_dict ! else: ! dict = version_dict["Apps"][app] ! return dict[version_key] # Utilities to check the "latest" version of an app. --- 24,228 ---- # The SF URL instead works for Tim and xenogeist. LATEST_VERSION_HOME="http://spambayes.sourceforge.net/download/Version.cfg" + DEFAULT_DOWNLOAD_PAGE="http://spambayes.sourceforge.net/windows.html" ! # This module is part of the spambayes project, which is Copyright 2002-5 # The Python Software Foundation and is covered by the Python Software # Foundation license. + versions = { ! # Note this means we can change the download page later, and old ! # versions will still go to the new page. ! # We may also like to have a "Release Notes Page" item later? ! "Download Page": DEFAULT_DOWNLOAD_PAGE, ! ! # Sub-dict for generating sub-sections in the cfg file that are compatible ! # with the update checking in older versions of SpamBayes. "Apps": { "Outlook" : { "Description": "SpamBayes Outlook Addin", }, "POP3 Proxy" : { "Description": "SpamBayes POP3 Proxy", }, }, } ! def get_version(app = None, ! version_dict = None): ! """Get SBVersion object based on the version info in the supplied dict.""" ! ver = SBVersion() # get default version ! if version_dict is not None: ! dict = version_dict # default to top level dictionary ! if app is not None: ! # attempt to get a sub-dict for the specific app ! try: ! dict = version_dict["Apps"][app] ! except KeyError: ! pass ! try: ! version = dict["Version"] ! # KLUDGE: Perform some bizarre magic to try to figure out if we ! # have an old-format float version instead of a new-format string ! # and massage it into a string format that will compare properly ! # in update checks. ! try: ! ver_num = float(version) ! # Version converted successfully to a float, which means it ! # may be an old-format version number. Old convention was to ! # use 1.01 to represent "1.0.1", so check to see if there is ! # more than one digit following the decimal. ! dot = version.find('.') ! ver_frac_part = version[dot+1:] ! if len(ver_frac_part) > 1: ! # Use the first digit of the fractional part as the minor ! # version and the rest as the patch version. ! version = version[0:dot] + '.' + ver_frac_part[0] + '.' + ver_frac_part[1:] ! except ValueError: ! pass ! ver = SBVersion(version, version_dict["Date"]) ! except KeyError: ! pass ! return ver ! ! def get_download_page(app = None, ! version_dict = None): if version_dict is None: version_dict = versions ! dict = version_dict # default to top level dictionary ! if app is not None: ! # attempt to get a sub-dict for the specific app ! try: ! dict = version_dict["Apps"][app] ! except KeyError: ! pass ! try: ! return dict["Download Page"] ! except KeyError: ! # "Download Page" key not found so it may be an old-format dictionary. ! # Just use the default download page. ! return DEFAULT_DOWNLOAD_PAGE ! def get_current_version(): ! return SBVersion() ! ! #============================================================================ ! ! # The SBVersion class is a modified version of the StrictVersion class from ! # the "distutils" module. It has been adapted to handle an "rc" pre-release ! # designation for release candidates, and to store the version data in the ! # format of the sys.version_info tuple. A date string may also be provided ! # that will be included in the long format of the version string. The ! # default version and date info is read from the metadata in the "spambayes" ! # module __init__.py file. ! ! class SBVersion: ! ! """Version numbering for SpamBayes releases. ! A version number consists of two or three dot-separated numeric ! components, with an optional "pre-release" tag on the end. The ! pre-release tag consists of the designations 'a' (for alpha), ! 'b' (for beta), or 'rc' (for release candidate) followed by a number. ! If the numeric components of two version numbers are equal, then one ! with a pre-release tag will always be deemed earlier (lesser) than ! one without. ! ! The following are valid version numbers (shown in the order that ! would be obtained by sorting according to the supplied cmp function): ! ! 0.4 0.4.0 (these two are equivalent) ! 0.4.1 ! 0.5a1 ! 0.5b3 ! 0.5 ! 0.9.6 ! 1.0 ! 1.0.4a3 ! 1.0.4b1 ! 1.0.4rc2 ! 1.0.4 ! ! The following are examples of invalid version numbers: ! ! 1 ! 2.7.2.2 ! 1.3.a4 ! 1.3pl1 ! 1.3c4 ! ! A date may also be associated with the version, typically to track the ! date when the release was made public. The date is specified as a string, ! and is only used in formatting the long version of the version string. """ ! ! def __init__(self, vstring=None, date=None): ! import spambayes ! if vstring: ! self.parse(vstring) ! else: ! self.parse(spambayes.__version__) ! if date: ! self.date = date ! else: ! self.date = spambayes.__date__ ! ! def __repr__ (self): ! return "%s('%s', '%s')" % (self.__class__.__name__, str(self), self.date) ! ! version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? (([ab]|rc)(\d+))?$', ! re.VERBOSE) ! ! def parse(self, vstring): ! match = self.version_re.match(vstring) ! if not match: ! raise ValueError, "invalid version number '%s'" % vstring ! ! (major, minor, patch, prerelease, prerelease_num) = \ ! match.group(1, 2, 4, 6, 7) ! ! if not patch: ! patch = "0" ! ! if not prerelease: ! releaselevel = "final" ! serial = 0 ! else: ! serial = string.atoi(prerelease_num) ! if prerelease == "a": ! releaselevel = "alpha" ! elif prerelease == "b": ! releaselevel = "beta" ! elif prerelease == "rc": ! releaselevel = "candidate" ! self.version_info = tuple(map(string.atoi, [major, minor, patch]) + \ ! [releaselevel, serial]) ! ! def __str__(self): ! if self.version_info[2] == 0: ! vstring = string.join(map(str, self.version_info[0:2]), '.') ! else: ! vstring = string.join(map(str, self.version_info[0:3]), '.') ! ! releaselevel = self.version_info[3][0] ! if releaselevel != 'f': ! if releaselevel == 'a': ! prerelease = "a" ! elif releaselevel == 'b': ! prerelease = "b" ! elif releaselevel == 'c': ! prerelease = "rc" ! vstring = vstring + prerelease + str(self.version_info[4]) ! ! return vstring ! ! def __cmp__(self, other): ! if isinstance(other, StringType): ! other = SBVersion(other) ! ! return cmp(self.version_info, other.version_info) ! ! def get_long_version(self, app_name = None): ! if app_name is None: app_name = "SpamBayes" ! return _("%s Version %s (%s)") % (app_name, str(self), self.date) ! ! #============================================================================ # Utilities to check the "latest" version of an app. *************** *** 128,132 **** if MySafeConfigParser is None: raise RuntimeError, \ ! "Sorry, but only Python 2.3 can trust remote config files" import urllib2 --- 240,244 ---- if MySafeConfigParser is None: raise RuntimeError, \ ! "Sorry, but only Python 2.3+ can trust remote config files" import urllib2 *************** *** 163,171 **** for opt in cfg.options(sect): val = cfg.get(sect, opt) - # some butchering - try: - val = float(val) - except ValueError: - pass target_dict[opt] = val return ret_dict --- 275,278 ---- *************** *** 173,178 **** # Utilities for generating a 'config' version of this file. # The output of this should exist at the URL above. ! def _make_cfg_section(stream, key, this_dict): ! stream.write("[%s]\n" % key) for name, val in this_dict.items(): if type(val)==type(''): --- 280,298 ---- # Utilities for generating a 'config' version of this file. # The output of this should exist at the URL above. ! compat_apps = { ! "Outlook" : { ! "Description": "SpamBayes Outlook Addin", ! }, ! "POP3 Proxy" : { ! "Description": "SpamBayes POP3 Proxy", ! }, ! } ! shared_cfg_opts = { ! # Note this means we can change the download page later, and old ! # versions will still go to the new page. ! # We may also like to have a "Release Notes Page" item later? ! "Download Page": "http://spambayes.sourceforge.net/windows.html", ! } ! def _write_cfg_opts(stream, this_dict): for name, val in this_dict.items(): if type(val)==type(''): *************** *** 187,190 **** --- 307,341 ---- if val_str is not None: stream.write("%s:%s\n" % (name, val_str)) + def _make_compatible_cfg_section(stream, key, ver, this_dict): + stream.write("[%s]\n" % key) + # We need to create a float representation of the current version that + # sort correctly in older versions that used a float version number. + ver_num = float(ver.version_info[0]) + ver_num += float(ver.version_info[1] * 0.1) + ver_num += float(ver.version_info[2] * 0.01) + releaselevel = ver.version_info[3][0] + if releaselevel == 'a': + prerelease_offset = 0.001 - (float(ver.version_info[4]) * 0.00001) + elif releaselevel == 'b': + prerelease_offset = 0.0005 - (float(ver.version_info[4]) * 0.00001) + elif releaselevel == 'c': + prerelease_offset = 0.0001 - (float(ver.version_info[4]) * 0.00001) + else: + prerelease_offset = 0.0 + ver_num -= prerelease_offset + stream.write("Version:%s\n" % str(ver_num)) + stream.write("BinaryVersion:%s\n" % str(ver_num)) + stream.write("Date:%s\n" % ver.date) + _write_cfg_opts(stream, this_dict) + desc_str = "%%(Description)s Version %s (%%(Date)s)" % str(ver) + stream.write("Full Description:%s\n" % desc_str) + stream.write("Full Description Binary:%s\n" % desc_str) + _write_cfg_opts(stream, versions) + stream.write("\n") + def _make_cfg_section(stream, ver): + stream.write("[SpamBayes]\n") + stream.write("Version:%s\n" % str(ver)) + stream.write("Date:%s\n" % ver.date) + _write_cfg_opts(stream, versions) stream.write("\n") *************** *** 192,198 **** stream.write("# This file is generated from spambayes/Version.py" \ " - do not edit\n") ! _make_cfg_section(stream, "SpamBayes", versions) ! for appname in versions["Apps"]: ! _make_cfg_section(stream, appname, versions["Apps"][appname]) def main(args): --- 343,350 ---- stream.write("# This file is generated from spambayes/Version.py" \ " - do not edit\n") ! ver = get_current_version() ! _make_cfg_section(stream, ver) ! for appname in compat_apps: ! _make_compatible_cfg_section(stream, appname, ver, versions["Apps"][appname]) def main(args): *************** *** 201,210 **** make_cfg(sys.stdout) sys.exit(0) ! print "SpamBayes engine version:", get_version_string() ! # Enumerate applications ! print ! print "Application versions:" ! for app in versions["Apps"]: ! print "\n%s: %s" % (app, get_version_string(app)) print --- 353,359 ---- make_cfg(sys.stdout) sys.exit(0) ! ! v_this = get_current_version() ! print "Current version:", v_this.get_long_version() print *************** *** 218,228 **** sys.exit(1) print ! print "SpamBayes engine version:", get_version_string(version_dict=latest_dict) ! # Enumerate applications ! print ! print "Application versions:" ! for app in latest_dict["Apps"]: ! print "\n%s: %s" % (app, get_version_string(app, version_dict=latest_dict)) if __name__=='__main__': --- 367,373 ---- sys.exit(1) + v_latest = get_version(version_dict=latest_dict) print ! print "Latest version:", v_latest.get_long_version() if __name__=='__main__': Index: __init__.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/__init__.py,v retrieving revision 1.13 retrieving revision 1.14 diff -C2 -d -r1.13 -r1.14 *** __init__.py 30 Nov 2004 21:49:19 -0000 1.13 --- __init__.py 23 Dec 2004 18:14:33 -0000 1.14 *************** *** 1,3 **** # package marker. ! __version__ = '1.1a0' --- 1,4 ---- # package marker. ! __version__ = "1.1a0" ! __date__ = "January 2005" From kpitt at users.sourceforge.net Thu Dec 23 19:14:50 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Thu Dec 23 19:14:53 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000/dialogs dialog_map.py, 1.45, 1.46 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1047/Outlook2000/dialogs Modified Files: dialog_map.py Log Message: New version numbering scheme. All apps now use the same version number, and the current version information is read from the __init__.py file in the "spambayes" module. Float version numbers are no longer used for update checks. Instead, our standard string version number format is parsed into a tuple in the format of the sys.version_info field. Versions can then be compared based on that tuple. Index: dialog_map.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/dialogs/dialog_map.py,v retrieving revision 1.45 retrieving revision 1.46 diff -C2 -d -r1.45 -r1.46 *** dialog_map.py 23 Dec 2004 05:19:39 -0000 1.45 --- dialog_map.py 23 Dec 2004 18:14:17 -0000 1.46 *************** *** 61,72 **** class VersionStringProcessor(ControlProcessor): def Init(self): ! from spambayes.Version import get_version_string import sys ! version_key = "Full Description" ! if hasattr(sys, "frozen"): ! version_key += " Binary" ! version_string = get_version_string("Outlook", version_key) ! win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT, ! 0, version_string) def GetPopupHelpText(self, cid): --- 61,71 ---- class VersionStringProcessor(ControlProcessor): def Init(self): ! from spambayes.Version import get_current_version import sys ! v = get_current_version() ! vstring = v.get_long_version("SpamBayes Outlook Addin") ! if not hasattr(sys, "frozen"): ! vstring += " from source" ! win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT, 0, vstring) def GetPopupHelpText(self, cid): From kpitt at users.sourceforge.net Thu Dec 23 19:14:50 2004 From: kpitt at users.sourceforge.net (Kenny Pitt) Date: Thu Dec 23 19:14:53 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py,1.147,1.148 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1047/Outlook2000 Modified Files: addin.py Log Message: New version numbering scheme. All apps now use the same version number, and the current version information is read from the __init__.py file in the "spambayes" module. Float version numbers are no longer used for update checks. Instead, our standard string version number format is parsed into a tuple in the format of the sys.version_info field. Versions can then be compared based on that tuple. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.147 retrieving revision 1.148 diff -C2 -d -r1.147 -r1.148 *** addin.py 23 Dec 2004 04:34:13 -0000 1.147 --- addin.py 23 Dec 2004 18:14:16 -0000 1.148 *************** *** 45,48 **** --- 45,49 ---- toolbar_name = "SpamBayes" + ADDIN_DISPLAY_NAME = "SpamBayes Outlook Addin" # If we are not running in a console, redirect all print statements to the *************** *** 592,606 **** def CheckLatestVersion(manager): ! from spambayes.Version import get_version_string, get_version_number, fetch_latest_dict ! if hasattr(sys, "frozen"): ! version_number_key = "BinaryVersion" ! version_string_key = "Full Description Binary" ! else: ! version_number_key = "Version" ! version_string_key = "Full Description" app_name = "Outlook" ! cur_ver_string = get_version_string(app_name, version_string_key) ! cur_ver_num = get_version_number(app_name, version_number_key) try: --- 593,602 ---- def CheckLatestVersion(manager): ! from spambayes.Version import get_current_version, get_version, \ ! get_download_page, fetch_latest_dict app_name = "Outlook" ! ver_current = get_current_version() ! cur_ver_string = ver_current.get_long_version(ADDIN_DISPLAY_NAME) try: *************** *** 608,615 **** latest = fetch_latest_dict() SetWaitCursor(0) ! latest_ver_string = get_version_string(app_name, version_string_key, ! version_dict=latest) ! latest_ver_num = get_version_number(app_name, version_number_key, ! version_dict=latest) except: print "Error checking the latest version" --- 604,609 ---- latest = fetch_latest_dict() SetWaitCursor(0) ! ver_latest = get_version(app_name, version_dict=latest) ! latest_ver_string = ver_latest.get_long_version(ADDIN_DISPLAY_NAME) except: print "Error checking the latest version" *************** *** 622,628 **** return ! print "Current version is %s, latest is %s." % (cur_ver_num, latest_ver_num) ! if latest_ver_num > cur_ver_num: ! url = get_version_string(app_name, "Download Page", version_dict=latest) msg = _("You are running %s\r\n\r\nThe latest available version is %s" \ "\r\n\r\nThe download page for the latest version is\r\n%s" \ --- 616,622 ---- return ! print "Current version is %s, latest is %s." % (str(ver_current), str(ver_latest)) ! if ver_latest > ver_current: ! url = get_download_page(app_name, version_dict=latest) msg = _("You are running %s\r\n\r\nThe latest available version is %s" \ "\r\n\r\nThe download page for the latest version is\r\n%s" \ *************** *** 1282,1291 **** # Only now will the import of "spambayes.Version" work, as the # manager is what munges sys.path for us. ! from spambayes.Version import get_version_string ! version_key = "Full Description" ! if hasattr(sys, "frozen"): version_key += " Binary" ! print "%s starting (with engine %s)" % \ ! (get_version_string("Outlook", version_key), ! get_version_string()) major, minor, spack, platform, ver_str = win32api.GetVersionEx() print "on Windows %d.%d.%d (%s)" % \ --- 1276,1284 ---- # Only now will the import of "spambayes.Version" work, as the # manager is what munges sys.path for us. ! from spambayes.Version import get_current_version ! v = get_current_version() ! vstring = v.get_long_version(ADDIN_DISPLAY_NAME) ! if not hasattr(sys, "frozen"): vstring += " from source" ! print vstring major, minor, spack, platform, ver_str = win32api.GetVersionEx() print "on Windows %d.%d.%d (%s)" % \ From anadelonbrin at users.sourceforge.net Mon Dec 27 03:26:36 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Mon Dec 27 03:26:40 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py,1.148,1.149 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv3753/Outlook2000 Modified Files: addin.py Log Message: Fix typo. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.148 retrieving revision 1.149 diff -C2 -d -r1.148 -r1.149 *** addin.py 23 Dec 2004 18:14:16 -0000 1.148 --- addin.py 27 Dec 2004 02:26:28 -0000 1.149 *************** *** 692,696 **** self.manager.score(msgstore_message)) msgstore_message.t = True ! self.manager.classifier_data.message_db.store_msg(msg) self.manager.classifier_data.dirty = True # Record the original folder, in case this message is not where --- 692,696 ---- self.manager.score(msgstore_message)) msgstore_message.t = True ! self.manager.classifier_data.message_db.store_msg(msgstore_message) self.manager.classifier_data.dirty = True # Record the original folder, in case this message is not where From anadelonbrin at users.sourceforge.net Wed Dec 29 07:19:41 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 29 07:19:45 2004 Subject: [Spambayes-checkins] spambayes/spambayes Stats.py,1.13,1.14 Message-ID: Update of /cvsroot/spambayes/spambayes/spambayes In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1717/spambayes Modified Files: Stats.py Log Message: Skip messages that don't have a modified date - for Outlook, this will make no difference for anyone except people using code from CVS for the last few weeks. For others, this means that the stats will be refreshed when starting to use 1.1, but that's ok, really. Clean up another function. Index: Stats.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/spambayes/Stats.py,v retrieving revision 1.13 retrieving revision 1.14 diff -C2 -d -r1.13 -r1.14 *** Stats.py 22 Dec 2004 00:25:45 -0000 1.13 --- Stats.py 29 Dec 2004 06:19:38 -0000 1.14 *************** *** 117,123 **** self.messageinfo_db.load_msg(m) # Skip ones that are too old. ! if self.from_date and m.date_modified and \ ! m.date_modified < self.from_date: continue --- 117,126 ---- self.messageinfo_db.load_msg(m) + # Skip all old messages that don't have a date. + if m.date_modified is None: + continue + # Skip ones that are too old. ! if self.from_date and m.date_modified < self.from_date: continue *************** *** 150,167 **** def _CombineSessionAndTotal(self): totals = self.totals ! num_seen = self.num_ham + self.num_spam + self.num_unsure + \ ! totals["num_ham"] + totals["num_spam"] + \ ! totals["num_unsure"] ! num_ham = self.num_ham + totals["num_ham"] ! num_spam = self.num_spam + totals["num_spam"] ! num_unsure = self.num_unsure + totals["num_unsure"] ! num_trained_ham = self.num_trained_ham + totals["num_trained_ham"] ! num_trained_ham_fp = self.num_trained_ham_fp + \ ! totals["num_trained_ham_fp"] ! num_trained_spam = self.num_trained_spam + \ ! totals["num_trained_spam"] ! num_trained_spam_fn = self.num_trained_spam_fn + \ ! totals["num_trained_spam_fn"] ! return locals() def _CalculateAdditional(self, data): --- 153,171 ---- def _CombineSessionAndTotal(self): totals = self.totals ! data = {} ! data["num_ham"] = self.num_ham + totals["num_ham"] ! data["num_spam"] = self.num_spam + totals["num_spam"] ! data["num_unsure"] = self.num_unsure + totals["num_unsure"] ! data["num_seen"] = data["num_ham"] + data["num_spam"] + \ ! data["num_unsure"] ! data["num_trained_ham"] = self.num_trained_ham + \ ! totals["num_trained_ham"] ! data["num_trained_ham_fp"] = self.num_trained_ham_fp + \ ! totals["num_trained_ham_fp"] ! data["num_trained_spam"] = self.num_trained_spam + \ ! totals["num_trained_spam"] ! data["num_trained_spam_fn"] = self.num_trained_spam_fn + \ ! totals["num_trained_spam_fn"] ! return data def _CalculateAdditional(self, data): From anadelonbrin at users.sourceforge.net Wed Dec 29 07:22:34 2004 From: anadelonbrin at users.sourceforge.net (Tony Meyer) Date: Wed Dec 29 07:22:38 2004 Subject: [Spambayes-checkins] spambayes/Outlook2000 addin.py, 1.149, 1.150 filter.py, 1.46, 1.47 msgstore.py, 1.98, 1.99 Message-ID: Update of /cvsroot/spambayes/spambayes/Outlook2000 In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv2198/Outlook2000 Modified Files: addin.py filter.py msgstore.py Log Message: Must save message after remembering the folder. Report the last modified date in the "show clues" message. I'm not sure this is the best way to put it, but I'll think more about it and change it if I come up with something better. Feel free to think of something yourself! Save classifier_data if it is dirty. Add modified_date to the persistent attributes now that it's not automatically stored. Index: addin.py =================================================================== RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v retrieving revision 1.149 retrieving revision 1.150 diff -C2 -d -r1.149 -r1.150 *** addin.py 27 Dec 2004 02:26:28 -0000 1.149 --- addin.py 29 Dec 2004 06:22:24 -0000 1.150 *************** *** 469,472 **** --- 469,484 ---- push("# ham trained on: %d
\n" % c.nham) push("# spam trained on: %d
\n" % c.nspam) + push("
\n") + + # Report last modified date. + modified_date = msgstore_message.date_modified + if modified_date: + from time import localtime, strftime + modified_date = localtime(modified_date) + date_string = strftime("%a, %d %b %Y %I:%M:%S %p", modified_date) + push("As at %s:
\n" % (date_string,)) + else: + push("The last time this message was classified or trained:
\n") + # Score when the message was classified - this will hopefully help # people realise that it may not necessarily be the same, and will *************** *** 482,497 **** else: original_class = "good" - push("
\n") if original_score is None: ! push("This message has not been filtered.") else: original_score = round(original_score) ! push("When this message was last filtered, it was classified " \ ! "as %s (it scored %d%%)." % (original_class, original_score)) # Report whether this message has been trained or not. push("
\n") ! push("This message has %sbeen trained%s." % \ {False : ("", " as ham"), True : ("", " as spam"), None : ("not ", "")}[msgstore_message.t]) # Format the clues. push("

%s Significant Tokens

\n
" % len(clues))
--- 494,509 ----
          else:
              original_class = "good"
      if original_score is None:
!         push("This message had not been filtered.")
      else:
          original_score = round(original_score)
!         push("This message was classified as %s (it scored %d%%)." % \
!              (original_class, original_score))
      # Report whether this message has been trained or not.
      push("
\n") ! push("This message had %sbeen trained%s." % \ {False : ("", " as ham"), True : ("", " as spam"), None : ("not ", "")}[msgstore_message.t]) + # Format the clues. push("

%s Significant Tokens

\n
" % len(clues))
***************
*** 697,700 ****
--- 709,713 ----
              # it was after filtering, or has never been filtered.
              msgstore_message.RememberMessageCurrentFolder()
+             msgstore_message.Save()
              # Must train before moving, else we lose the message!
              subject = msgstore_message.GetSubject()

Index: filter.py
===================================================================
RCS file: /cvsroot/spambayes/spambayes/Outlook2000/filter.py,v
retrieving revision 1.46
retrieving revision 1.47
diff -C2 -d -r1.46 -r1.47
*** filter.py	23 Dec 2004 02:05:14 -0000	1.46
--- filter.py	29 Dec 2004 06:22:31 -0000	1.47
***************
*** 113,116 ****
--- 113,117 ----
              mgr.classifier_data.message_db.store_msg(msg)
              mgr.classifier_data.dirty = True
+             mgr.classifier_data.SavePostIncrementalTrain()
          return disposition
      except:

Index: msgstore.py
===================================================================
RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v
retrieving revision 1.98
retrieving revision 1.99
diff -C2 -d -r1.98 -r1.99
*** msgstore.py	22 Dec 2004 01:22:00 -0000	1.98
--- msgstore.py	29 Dec 2004 06:22:31 -0000	1.99
***************
*** 809,813 ****
  
          # For use with the spambayes.message messageinfo database.
!         self.stored_attributes = ['c', 't', 'original_folder']
          self.t = None
          self.c = None
--- 809,814 ----
  
          # For use with the spambayes.message messageinfo database.
!         self.stored_attributes = ['c', 't', 'original_folder',
!                                   'date_modified']
          self.t = None
          self.c = None