[Spambayes-checkins] spambayes/Outlook2000 addin.py, 1.59, 1.60 config.py, 1.8, 1.9 filter.py, 1.22, 1.23 manager.py, 1.59, 1.60 msgstore.py, 1.45, 1.46

Mark Hammond mhammond at users.sourceforge.net
Sun Jun 15 23:00:26 EDT 2003


Update of /cvsroot/spambayes/spambayes/Outlook2000
In directory sc8-pr-cvs1:/tmp/cvs-serv17842

Modified Files:
	addin.py config.py filter.py manager.py msgstore.py 
Log Message:
Change "Anti-Spam" on the dropdown to "SpamBayes"

Huge changes to configuration.  No longer use a pickle, but instead a series
of .INI files (in the format of the default bayes file - but *not* using 
the bayes ini file).  We automatically migrate the old pickle file to the
new INI file.

This is very cool as it allows us to support options before we have a UI
for it.  A first such option is a "data_directory", which would allow
site administrators to specify a different per-user directory.  Some
other options are likely to appear soon.

Source-code users will note the creation of "unknown_profile.ini" - this is
because released win32alls currently can not determine the Outlook profile 
name. When it can, this file will magically be renamed to the correct profile 
name. The next binary release will know the profile name immediately, so will
never see "unknown_profile.ini".  (This is used so SpamBayes can
eventually be used with multiple profiles - currently it fails pretty 
badly)



Index: addin.py
===================================================================
RCS file: /cvsroot/spambayes/spambayes/Outlook2000/addin.py,v
retrieving revision 1.59
retrieving revision 1.60
diff -C2 -d -r1.59 -r1.60
*** addin.py	6 Jun 2003 04:01:51 -0000	1.59
--- addin.py	16 Jun 2003 05:00:22 -0000	1.60
***************
*** 154,158 ****
  # outlook one
  def ProcessMessage(msgstore_message, manager):
!     if msgstore_message.GetField(manager.config.field_score_name) is not None:
          # Already seem this message - user probably moving it back
          # after incorrect classification.
--- 154,158 ----
  # outlook one
  def ProcessMessage(msgstore_message, manager):
!     if msgstore_message.GetField(manager.config.general.field_score_name) is not None:
          # Already seem this message - user probably moving it back
          # after incorrect classification.
***************
*** 224,228 ****
              return
          msgstore_message = self.manager.message_store.GetMessage(item)
!         prop = msgstore_message.GetField(self.manager.config.field_score_name)
          if prop is not None:
              import train
--- 224,228 ----
              return
          msgstore_message = self.manager.message_store.GetMessage(item)
!         prop = msgstore_message.GetField(self.manager.config.general.field_score_name)
          if prop is not None:
              import train
***************
*** 480,485 ****
                          constants.msoControlPopup,
                          None, None,
!                         Caption="Anti-Spam",
!                         TooltipText = "Anti-Spam filters and functions",
                          Enabled = True,
                          Tag = "SpamBayesCommand.Popup")
--- 480,485 ----
                          constants.msoControlPopup,
                          None, None,
!                         Caption="SpamBayes",
!                         TooltipText = "SpamBayes anti-spam filters and functions",
                          Enabled = True,
                          Tag = "SpamBayesCommand.Popup")
***************
*** 601,606 ****
      def OnActivate(self):
          # See comments for OnNewExplorer below.
!         # *sigh* - OnActivate seems too early too :(
!         pass
  
      def OnSelectionChange(self):
--- 601,610 ----
      def OnActivate(self):
          # See comments for OnNewExplorer below.
!         # *sigh* - OnActivate seems too early too for Outlook 2000,
!         # but Outlook 2003 seems to work here, and *not* the folder switch etc
!         # This also appears to solve some issues relating to multi-profiles
!         # on 2000 (where secondary profiles appears to behave closer to 2003)
!         if not self.have_setup_ui:
!             self.SetupUI() 
  
      def OnSelectionChange(self):
***************
*** 730,733 ****
--- 734,751 ----
      
              if self.manager.config.filter.enabled:
+                 # A little "sanity test" to help the user.  If our status is
+                 # 'enabled', then it means we have previously managed to
+                 # convince the manager dialog we have enough ham/spam and
+                 # valid folders.  If for some reason, we have zero ham or spam,
+                 # or no folder definitions but are 'enabled', then it is likely
+                 # something got hosed and the user doesn't know.
+                 if self.manager.bayes.nham==0 or \
+                    self.manager.bayes.nspam==0 or \
+                    not self.manager.config.filter.spam_folder_id or \
+                    not self.manager.config.filter.watch_folder_ids:
+                     msg = "It appears there was an error loading your configuration\r\n\r\n" \
+                           "Please re-configure SpamBayes via the SpamBayes dropdown"
+                     self.manager.ReportError(msg)
+                 # But continue on regardless.
                  self.FiltersChanged()
                  try:
***************
*** 748,752 ****
          config = self.manager.config.filter
          manager = self.manager
!         field_name = manager.config.field_score_name
          for folder in manager.message_store.GetFolderGenerator(
                                      config.watch_folder_ids,
--- 766,770 ----
          config = self.manager.config.filter
          manager = self.manager
!         field_name = manager.config.general.field_score_name
          for folder in manager.message_store.GetFolderGenerator(
                                      config.watch_folder_ids,

Index: config.py
===================================================================
RCS file: /cvsroot/spambayes/spambayes/Outlook2000/config.py,v
retrieving revision 1.8
retrieving revision 1.9
diff -C2 -d -r1.8 -r1.9
*** config.py	18 Mar 2003 02:50:02 -0000	1.8
--- config.py	16 Jun 2003 05:00:23 -0000	1.9
***************
*** 1,5 ****
! # configuration stuff we persist via a pickle
! # Can't be defined in any module that may be used as "__main__"
! # or as a module.
  
  try:
--- 1,15 ----
! # configuration classes for the plugin.
! # We used to use a little pickle, but have since moved to a "spambayes.Options"
! # class.
! 
! # Hack for testing - setup sys.path
! if __name__=='__main__':
!     try:
!         import spambayes.Options
!     except ImportError:
!         import sys, os
!         sys.path.append(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..")))
! 
! import sys, types
  
  try:
***************
*** 7,36 ****
  except NameError:
      # Maintain compatibility with Python 2.2
!     True, False = 1, 0
  
  
! class _ConfigurationContainer:
!     def __init__(self, **kw):
!         self.__dict__.update(kw)
  
!     def __setattr__(self, attr, val):
!         if not self.__dict__.has_key(attr):
!             raise AttributeError, attr
!         self.__dict__[attr] = val
!     # Crap state-loading code so when we load an early version of the pickle
!     # any attributes in the new version are considered defaults.
!     # XXX - I really really want a better scheme than pickles etc here :(
!     def _update_from(self, dict):
!         for name, val in dict.items():
!             updater = getattr(val, "_update_from", None)
!             if updater is not None and self.__dict__.has_key(name):
!                 self.__dict__[name]._update_from(val.__dict__)
              else:
!                 self.__dict__[name] = val
  
!     def __setstate__(self, state):
!         self.__init__() # ensure any new/default values setup
!         self._update_from(state)
  
      def _dump(self, thisname="<root>", level=0):
          import pprint
--- 17,219 ----
  except NameError:
      # Maintain compatibility with Python 2.2
!     True, False = 0==0, 1==0
  
  
! FOLDER_ID = r"\(\'[a-fA-F0-9]+\', \'[a-fA-F0-9]+\'\)"
! FIELD_NAME = r"[a-zA-Z0-9 ]+"
! FILTER_ACTION = "Untouched", "Moved", "Copied"
  
! from spambayes.OptionsClass import OptionsClass, Option
! from spambayes.OptionsClass import RESTORE, DO_NOT_RESTORE
! from spambayes.OptionsClass import BOOLEAN, REAL, PATH
! 
! class FolderIDOption(Option):
!     def convert(self, value):
!         #print "Convert called on", repr(value)
!         error = None
!         is_multi = self.multiple_values_allowed()
!         # empty string means nothing to single value.
!         if not is_multi and not value:
!             return None
!         # Now sure why we get non-strings here for multis
!         if type(value) == types.ListType:
!             return value
!         # If we really care here, it would be fairly easy to use a regex
!         # etc to pull these IDs apart.  eval is easier for now :)
!         try:
!             items = eval(value)
!         except:
!             error = "Invalid value (%s:%s)" % (sys.exc_type, sys.exc_value)
!         check_items = []
!         if error is None:
!             if is_multi:
!                 if type(items) != types.ListType:
!                     error = "Multi-valued ID must yield a list"
!                 check_items = items
              else:
!                 check_items = [items]
!         if error is None:
!             for item in check_items:
!                 if item is None:
!                     error = "None isn't valid here (how did it get here anyway?"
!                     break
!                 if not self.is_valid_single(item):
!                     error = "Each ID must be a tuple of 2 strings"
!                     break
!         if error is not None:
!             print "Failed to convert FolderID value '%r', is_multi=%d" % \
!                   (value, is_multi)
!             print error
!             if is_multi:
!                 return []
!             else:
!                 return None
!         return items
  
!     def unconvert(self):
!         #print "unconvert called with", repr(self.value)
!         if self.value is None:
!             return ""
!         return str(self.value)
!     
!     def is_valid_single(self, value):
!         return value is None or \
!                (type(value)==types.TupleType and \
!                len(value)==2 and \
!                type(value[0])==type(value[1])==types.StringType)
! 
! defaults = {
!     "General" : (
!     ("field_score_name", "The name of the field used to store the spam score", "Spam",
!         """SpamBayes stores the spam score for each message in a custom field.
!         This option specifies the name of the field""",
!         FIELD_NAME, RESTORE),
!     ("data_directory", "The directory to store the data files.", "",
!         """""",
!         PATH, RESTORE),
!     ),
!     "Training" : (
!     (FolderIDOption,
!         "ham_folder_ids", "Folders containing known good messages", [],
!         """A list of folders known to contain good (ham) messages.  When SpamBayes
!         is trained, these messages will be used as examples of good messages.""",
!         FOLDER_ID, DO_NOT_RESTORE),
!     ("ham_include_sub", "Does the nominated ham folders include sub-folders?", False,
!         """""",
!         BOOLEAN, DO_NOT_RESTORE),
!     (FolderIDOption,
!         "spam_folder_ids", "Folders containing known bad or spam messages", [],
!         """A list of folders known to contain bad (spam) messages.  When SpamBayes
!         is trained, these messages will be used as examples of messages to filter.""",
!         FOLDER_ID, DO_NOT_RESTORE),
!     ("spam_include_sub", "Does the nominated spam folders include sub-folders?", False,
!         """""",
!         BOOLEAN, DO_NOT_RESTORE),
!     ("train_recovered_spam", "Train as good as items are recovered?", True,
!         """SpamBayes can detect when a message previously classified as spam
!         (or unsure) is moved back to the folder from which it was filtered.
!         If this option is enabled, SpamBayes will automatically train on
!         such messages""",
!         BOOLEAN, RESTORE),
!     ("train_manual_spam", "Train as spam items are manually moved?", True,
!         """SpamBayes can detect when a message previously classified as good
!         (or unsure) is manually moved to the Spam folder.  If this option is
!         enabled, SpamBayes will automatically train on such messages""",
!         BOOLEAN, RESTORE),
!     ("rescore", "Rescore message after training?", True,
!         """State of the 'rescore' button""",
!         BOOLEAN, RESTORE),
!     ),
  
+     # These options control how a message is categorized
+     "Filter" : (
+     ("filter_now", "State of 'Filter Now' checkbox", False,
+         """Something useful.""",
+         BOOLEAN, RESTORE),
+     (FolderIDOption,
+        "watch_folder_ids", "Folders to watch for new messages", [],
+         """The list of folders SpamBayes will watch for new messages,
+         processing messages as defined by the filters.""",
+         FOLDER_ID, DO_NOT_RESTORE),
+     ("watch_include_sub", "Does the nominated watch folders include sub-folders?", False,
+         """""",
+         BOOLEAN, DO_NOT_RESTORE),
+     (FolderIDOption,
+         "spam_folder_id", "The folder used to track spam", None,
+         """The folder SpamBayes moves or copies spam to.""",
+         FOLDER_ID, DO_NOT_RESTORE),
+     ("spam_threshold", "The score necessary to be considered 'certain' spam", 90.0,
+         """""",
+         REAL, RESTORE),
+     ("spam_action", "The action to take for new spam", "Untouched",
+         """""",
+         FILTER_ACTION, RESTORE),
+     (FolderIDOption,
+         "unsure_folder_id", "The folder used to track uncertain messages", None,
+         """The folder SpamBayes moves or copies uncertain messages to.""",
+         FOLDER_ID, DO_NOT_RESTORE),
+     ("unsure_threshold", "The score necessary to be considered 'unsure'", 15.0,
+         """""",
+         REAL, RESTORE),
+     ("unsure_action", "The action to take for new uncertain messages", "Untouched",
+         """""",
+         FILTER_ACTION, RESTORE),
+     ("enabled", "Is filtering enabled?", False,
+         """""",
+         BOOLEAN, RESTORE),
+     ),
+     "Filter_Now": (
+     (FolderIDOption, "folder_ids", "Folders to filter in a 'Filter Now' operation", [],
+         """""",
+         FOLDER_ID, DO_NOT_RESTORE),
+     ("include_sub", "Does the nominated folders include sub-folders?", False,
+         """""",
+         BOOLEAN, DO_NOT_RESTORE),
+     ("only_unread", "Only filter unread messages?", False,
+         """""",
+         BOOLEAN, RESTORE),
+     ("only_unseen", "Only filter previously unseen ?", False,
+         """""",
+         BOOLEAN, RESTORE),
+     ("action_all", "Perform all filter actions?", True,
+         """""",
+         BOOLEAN, RESTORE),
+     ),
+ }
+ 
+ # A simple container that provides "." access to items
+ class SectionContainer:
+     def __init__(self, options, section):
+         self.__dict__['_options'] = options
+         self.__dict__['_section'] = section
+     def __getattr__(self, attr):
+         return self._options.get(self._section, attr)
+     def __setattr__(self, attr, val):
+         return self._options.set(self._section, attr, val)
+ 
+ class OptionsContainer:
+     def __init__(self, options):
+         self.__dict__['_options'] = options
+     def __getattr__(self, attr):
+         attr = attr.lower()
+         for key in self._options.sections():
+             if attr == key.lower():
+                 container = SectionContainer(self._options, key)
+                 self.__dict__[attr] = container
+                 return container
+         raise AttributeError, "Options has no section '%s'" % attr
+ 
+ def CreateConfig():
+     options = OptionsClass()
+     options.load_defaults(defaults)
+     return options
+ 
+ # Old code when we used a pickle.  Still needed so old pickles can be
+ # loaded, and moved to the new options file format.
+ class _ConfigurationContainer:
+     def __init__(self, **kw):
+         self.__dict__.update(kw)
+     def __setstate__(self, state):
+         self.__dict__.update(state)
      def _dump(self, thisname="<root>", level=0):
          import pprint
***************
*** 46,86 ****
  class ConfigurationRoot(_ConfigurationContainer):
      def __init__(self):
!         training = _ConfigurationContainer(
!             ham_folder_ids = [],
!             ham_include_sub = False,
!             spam_folder_ids = [],
!             spam_include_sub = False,
!             # Train as unsure/spam is moved back to Inbox
!             train_recovered_spam = True,
!             # Train as stuff dragged into spam folders.
!             train_manual_spam = True,
!             # Rescore all messages after training?
!             rescore = True,
!             )
!         filter = _ConfigurationContainer(
!             watch_folder_ids = [],
!             watch_include_sub = False,
!             spam_folder_id = None,
!             spam_threshold = 90,
!             spam_action = "Untouched",
!             unsure_folder_id = None,
!             unsure_threshold = 15,
!             unsure_action = "Untouched",
!             enabled = False,
!             )
!         filter_now = _ConfigurationContainer(
!             folder_ids = [],
!             include_sub = False,
!             only_unread = False,
!             only_unseen = True,
!             action_all = True,
!             )
!         field_score_name = "Spam"
!         _ConfigurationContainer.__init__(self,
!                                          training=training,
!                                          filter=filter,
!                                          filter_now = filter_now,
!                                          field_score_name = field_score_name)
! 
  if __name__=='__main__':
!     print "Please run 'manager.py'"
--- 229,269 ----
  class ConfigurationRoot(_ConfigurationContainer):
      def __init__(self):
!         pass
! # End of old pickle code.
!     
  if __name__=='__main__':
!     options = CreateConfig()
!     options.merge_files(['delme.cfg'])
!     c = OptionsContainer(options)
!     f = options.get("Training", "ham_folder_ids")
!     print "Folders before set are", f
!     for i in f:
!         print i, type(i)
!     new_folder_ids = [('000123','456789'), ('ABCDEF', 'FEDCBA')]
!     options.set("Training", "ham_folder_ids", new_folder_ids)
!     f = options.get("Training", "ham_folder_ids")
!     print "Folders after set are", f
!     for i in f:
!         print i, type(i)
!     
!     # Test single ID folders.
!     if c.filter.unsure_folder_id is not None:
!         print "It appears we loaded a folder ID - resetting"
!         c.filter.unsure_folder_id = None
!     unsure_id = c.filter.unsure_folder_id
!     if unsure_id is not None: raise ValueError, "unsure_id wrong (%r)" % (c.filter.unsure_folder_id,)
!     unsure_id = c.filter.unsure_folder_id = ('12345', 'abcdef')
!     if unsure_id != c.filter.unsure_folder_id: raise ValueError, "unsure_id wrong (%r)" % (c.filter.unsure_folder_id,)
!     c.filter.unsure_folder_id = None
!     if c.filter.unsure_folder_id is not None: raise ValueError, "unsure_id wrong (%r)" % (c.filter.unsure_folder_id,)
!     
!     options.set("Filter", "filter_now", True)
!     print "Filter_now from container is", c.filter.filter_now
!     options.set("Filter", "filter_now", False)
!     print "Filter_now from container is now", c.filter.filter_now
!     c.filter.filter_now = True
!     print "Filter_now from container is finally", c.filter.filter_now
!     print "Only unread is", c.filter_now.only_unread
!     options.update_file("delme.cfg")
!     print "Created 'delme.cfg'"
! 

Index: filter.py
===================================================================
RCS file: /cvsroot/spambayes/spambayes/Outlook2000/filter.py,v
retrieving revision 1.22
retrieving revision 1.23
diff -C2 -d -r1.22 -r1.23
*** filter.py	5 Jun 2003 00:50:10 -0000	1.22
--- filter.py	16 Jun 2003 05:00:23 -0000	1.23
***************
*** 33,37 ****
              # Catch this exception, as failing to save the score need not
              # be fatal - it may still be possible to perform the move.
!             msg.SetField(mgr.config.field_score_name, prob)
              # and the ID of the folder we were in when scored.
              # (but only if we want to perform all actions)
--- 33,37 ----
              # Catch this exception, as failing to save the score need not
              # be fatal - it may still be possible to perform the move.
!             msg.SetField(mgr.config.general.field_score_name, prob)
              # and the ID of the folder we were in when scored.
              # (but only if we want to perform all actions)
***************
*** 42,45 ****
--- 42,49 ----
              msg.Save()
          except:
+             # XXX - unfortunately, for the case I added this code, a failing
+             # Save *did* imply a failing Move :(
+             # I also heard a rumour hotmail works if we do 2 saves.
+             # This should be revisited.
              print "Failed to save the Spam score for message ", msg
              import traceback
***************
*** 82,85 ****
--- 86,90 ----
      all_actions = config.action_all
      dispositions = {}
+     field_name = mgr.config.general.field_score_name
      for message in f.GetMessageGenerator():
          if progress.stop_requested():
***************
*** 87,91 ****
          progress.tick()
          if only_unread and not message.unread or \
!            only_unseen and message.GetField(mgr.config.field_score_name) is not None:
              continue
          try:
--- 92,96 ----
          progress.tick()
          if only_unread and not message.unread or \
!            only_unseen and message.GetField(field_name) is not None:
              continue
          try:

Index: manager.py
===================================================================
RCS file: /cvsroot/spambayes/spambayes/Outlook2000/manager.py,v
retrieving revision 1.59
retrieving revision 1.60
diff -C2 -d -r1.59 -r1.60
*** manager.py	4 Jun 2003 02:46:47 -0000	1.59
--- manager.py	16 Jun 2003 05:00:23 -0000	1.60
***************
*** 12,16 ****
  import pythoncom
  
- import config
  import msgstore
  
--- 12,15 ----
***************
*** 57,66 ****
  # first (if we import anything from the core spambayes code before
  # setting that envar, our .ini file may have no effect).
! def import_core_spambayes_stuff(ini_filename):
!     global bayes_classifier, bayes_tokenize, bayes_storage
! 
!     os.environ["BAYESCUSTOMIZE"] = ini_filename
      try:
!         from spambayes import classifier
      except ImportError:
          parent = os.path.abspath(os.path.join(os.path.dirname(this_filename),
--- 56,66 ----
  # first (if we import anything from the core spambayes code before
  # setting that envar, our .ini file may have no effect).
! # However, we want *some* Spambayes code before the options are processed
! # so this is now 2 steps - get the "early" spambayes core stuff (which
! # must not import spambayes.Options) and sets up sys.path, and "later" core
! # stuff, which can include spambayes.Options, and assume sys.path in place.
! def import_early_core_spambayes_stuff():
      try:
!         from spambayes import OptionsClass
      except ImportError:
          parent = os.path.abspath(os.path.join(os.path.dirname(this_filename),
***************
*** 68,71 ****
--- 68,76 ----
          sys.path.insert(0, parent)
  
+ def import_core_spambayes_stuff(ini_filename):
+     assert "spambayes.Options" not in sys.modules, \
+         "'spambayes.Options' was imported too early"
+     global bayes_classifier, bayes_tokenize, bayes_storage
+     os.environ["BAYESCUSTOMIZE"] = ini_filename
      from spambayes import classifier
      from spambayes.tokenizer import tokenize
***************
*** 74,77 ****
--- 79,84 ----
      bayes_tokenize = tokenize
      bayes_storage = storage
+     assert "spambayes.Options" in sys.modules, \
+         "Expected 'spambayes.Options' to be loaded here"
  
  class ManagerError(Exception):
***************
*** 172,199 ****
      def __init__(self, config_base="default", outlook=None, verbose=1):
          self.reported_startup_error = False
!         self.config = None
          self.addin = None
          self.verbose = verbose
          self.stats = Stats()
!         self.application_directory = os.path.dirname(this_filename)
!         self.data_directory = self.LocateDataDirectory()
!         self.MigrateDataDirectory()
!         if not os.path.isabs(config_base):
!             config_base = os.path.join(self.data_directory,
!                                        config_base)
!         config_base = os.path.abspath(config_base)
  
!         self.ini_filename = config_base + "_bayes_customize.ini"
!         self.config_filename = config_base + "_configuration.pck"
  
!         # Read the configuration file.
!         self.config = self.LoadConfig()
  
!         self.outlook = outlook
  
!         import_core_spambayes_stuff(self.ini_filename)
  
!         bayes_base = config_base + "_bayes_database"
!         mdb_base = config_base + "_message_database"
          # determine which db manager to use, and create it.
          ManagerClass = [PickleStorageManager, DBStorageManager][use_db]
--- 179,232 ----
      def __init__(self, config_base="default", outlook=None, verbose=1):
          self.reported_startup_error = False
!         self.config = self.options = None
          self.addin = None
          self.verbose = verbose
          self.stats = Stats()
!         self.outlook = outlook
  
!         import_early_core_spambayes_stuff()
  
!         self.application_directory = os.path.dirname(this_filename)
!         # where windows would like our data stored (and where
!         # we do, unless overwritten via a config file)
!         self.windows_data_directory = self.LocateDataDirectory()
!         # Read the primary configuration files
!         self.PrepareConfig()
  
!         # See if the initial config files specify a
!         # "data directory".  If so, use it, otherwise
!         # use the default Windows data directory for our app.
!         value = self.config.general.data_directory
!         if value:
!             try:
!                 if not os.path.isdir(value):
!                     os.makedirs(value)
!                 if not os.path.isdir(value):
!                     raise os.error
!                 value = os.path.abspath(value)
!             except os.error:
!                 print "The configuration files have specified a data directory of"
!                 print repr(value)
!                 print "but it is not valid.  Using default"
!                 value = None
!         if value:
!             self.data_directory = value
!         else:
!             self.data_directory = self.windows_data_directory
!             
!         # Now we have the data directory, migrate anything needed, and load
!         # any config from it.
!         self.MigrateDataDirectory()
  
!         # Get the message store before loading config, as we use the profile
!         # name.
!         self.message_store = msgstore.MAPIMsgStore(outlook)
!         self.LoadConfig()
  
!         bayes_options_filename = os.path.join(self.data_directory, "default_bayes_customize.ini")
!         import_core_spambayes_stuff(bayes_options_filename)
! 
!         bayes_base = os.path.join(self.data_directory, "default_bayes_database")
!         mdb_base = os.path.join(self.data_directory, "default_message_database")
          # determine which db manager to use, and create it.
          ManagerClass = [PickleStorageManager, DBStorageManager][use_db]
***************
*** 202,206 ****
          self.bayes = self.message_db = None
          self.LoadBayes()
-         self.message_store = msgstore.MAPIMsgStore(outlook)
  
      def ReportError(self, message, title = None):
--- 235,238 ----
***************
*** 319,323 ****
          if self.verbose > 1:
              print "Checking folder '%s' for our field '%s'" \
!                   % (self.config.field_score_name,folder.Name.encode("mbcs", "replace"))
          items = folder.Items
          item = items.GetFirst()
--- 351,355 ----
          if self.verbose > 1:
              print "Checking folder '%s' for our field '%s'" \
!                   % (self.config.general.field_score_name,folder.Name.encode("mbcs", "replace"))
          items = folder.Items
          item = items.GetFirst()
***************
*** 333,337 ****
              for i in range(ups.Count):
                  up = ups[i+1]
!                 if up.Name == self.config.field_score_name:
                      break
              else: # for not broken
--- 365,369 ----
              for i in range(ups.Count):
                  up = ups[i+1]
!                 if up.Name == self.config.general.field_score_name:
                      break
              else: # for not broken
***************
*** 341,345 ****
                      # 1 is the first - "Rounded", which seems fine.
                      format = 1
!                     ups.Add(self.config.field_score_name,
                             win32com.client.constants.olPercent,
                             True, # Add to folder
--- 373,377 ----
                      # 1 is the first - "Rounded", which seems fine.
                      format = 1
!                     ups.Add(self.config.general.field_score_name,
                             win32com.client.constants.olPercent,
                             True, # Add to folder
***************
*** 366,373 ****
          import time
          start = time.clock()
-         if not os.path.exists(self.ini_filename):
-             raise ManagerError("The file '%s' must exist before the "
-                                "database '%s' can be opened or created" % (
-                                self.ini_filename, self.db_manager.bayes_filename))
          bayes = message_db = None
          try:
--- 398,401 ----
***************
*** 403,435 ****
              print "Loaded databases in %gms" % ((time.clock()-start)*1000)
  
      def LoadConfig(self):
!         # Our 'config' file always uses a pickle
          try:
!             f = open(self.config_filename, 'rb')
          except IOError:
              if self.verbose:
!                 print ("Created new configuration file '%s'" %
!                        self.config_filename)
!             return config.ConfigurationRoot()
! 
          try:
!             ret = cPickle.load(f)
              f.close()
!             if self.verbose > 1:
!                 print "Loaded configuration from '%s':" % self.config_filename
!                 ret._dump()
!         except IOError, details:
!             # File-not-found - less serious.
!             ret = config.ConfigurationRoot()
!             if self.verbose > 1:
!                 # filename included in exception!
!                 print "IOError loading configuration (%s) - using default:" % (details)
!         except:
!             # Any other error loading configuration is nasty, but should not
!             # cause a fatal error.
!             msg = "FAILED to load configuration from '%s'" % (self.config_filename,)
!             self.ReportFatalStartupError(msg)
!             ret = config.ConfigurationRoot()
!         return ret
  
      def InitNewBayes(self):
--- 431,537 ----
              print "Loaded databases in %gms" % ((time.clock()-start)*1000)
  
+     def PrepareConfig(self):
+         # Load our Outlook specific configuration.  This is done before
+         # SpamBayes is imported, and thus we are able to change the INI
+         # file used for the engine.  It is also done before the primary
+         # options are loaded - this means we can change the directory
+         # from which these options are loaded.
+         import config
+         self.options = config.CreateConfig()
+         # Note that self.options really *is* self.config - but self.config
+         # allows a "." notation to access the values.  Changing one is reflected
+         # immediately in the other.
+         self.config = config.OptionsContainer(self.options)
+ 
+         filename = os.path.join(self.application_directory, "default_configuration.ini")
+         self._MergeConfigFile(filename)
+ 
+         filename = os.path.join(self.windows_data_directory, "default_configuration.ini")
+         self._MergeConfigFile(filename)
+ 
+     def _MergeConfigFile(self, filename):
+         try:
+             self.options.merge_file(filename)
+         except:
+             msg = "The configuration file named below is invalid.\r\n" \
+                     "Please either correct or remove this file\r\n\r\n" \
+                     "Filename: " + filename
+             self.ReportError(msg)
+ 
      def LoadConfig(self):
!         profile_name = self.message_store.GetProfileName()
!         if profile_name is None:
!             # should only happen in source-code versions - older win32alls can't
!             # determine this.
!             profile_name = "unknown_profile"
!         else:
!             # xxx - remove me sometime - win32all grew this post 154(ish)
!             # binary never released with this, so we can be a little more brutal
!             # Try and rename to current profile, silent failure
!             try:
!                 os.rename(os.path.join(self.data_directory, "unknown_profile.ini"),
!                           os.path.join(self.data_directory, profile_name + ".ini"))
!             except os.error:
!                 pass
! 
!         self.config_filename = os.path.join(self.data_directory, profile_name + ".ini")
!         # Now load it up
!         self._MergeConfigFile(self.config_filename)
!         self.MigrateOldPickle()
! 
!     def MigrateOldPickle(self):
!         assert self.config is not None, "Must have a config"
!         pickle_filename = os.path.join(self.data_directory,
!                                        "default_configuration.pck")
          try:
!             f = open(pickle_filename, 'rb')
          except IOError:
              if self.verbose:
!                 print "No old pickle file to migrate"
!             return
!         print "Migrating old pickle '%s'" % pickle_filename
          try:
!             try:
!                 old_config = cPickle.load(f)
!             except IOError, details:
!                 print "FAILED to load old pickle"
!                 print details
!                 msg = "There was an error loading your old\r\n" \
!                       "SpamBayes configuration file.\r\n\r\n" \
!                       "It is likely that you will need to re-configure\r\n" \
!                       "SpamBayes before it will function correctly."
!                 self.ReportError(msg)
!                 # But we can't abort yet - we really should still try and
!                 # delete it, as we aren't gunna work next time in this case!
!                 old_config = None
!         finally:
              f.close()
!         if old_config is not None:
!             for section, items in old_config.__dict__.items():
!                 print " migrating section '%s'" % (section,)
!                 # exactly one value wasn't in a section - now in "general"
!                 dict = getattr(items, "__dict__", None)
!                 if dict is None:
!                     dict = {section: items}
!                     section = "general"
!                 for name, value in dict.items():
!                     sect = getattr(self.config, section)
!                     setattr(sect, name, value)
!         # Save the config, then delete the pickle so future attempts to
!         # migrate will fail.  We save first, so failure here means next
!         # attempt should still find the pickle.
!         if self.verbose:
!             print "pickle migration doing initial configuration save"
!         self.SaveConfig()
!         try:
!             if self.verbose:
!                 print "pickle migration removing '%s'" % pickle_filename
!             os.remove(pickle_filename)
!         except os.error:
!             msg = "There was an error migrating and removing your old\r\n" \
!                   "SpamBayes configuration file.  Configuration changes\r\n" \
!                   "you make are unlikely to be reflected next\r\n" \
!                   "time you start Outlook.  Please try rebooting."
!             self.ReportError(msg)
  
      def InitNewBayes(self):
***************
*** 481,490 ****
  
      def SaveConfig(self):
          if self.verbose > 1:
!             print "Saving configuration:"
!             self.config._dump()
!             print " ->", self.config_filename
!         assert self.config, "Have no config to save!"
!         SavePickle(self.config, self.config_filename)
  
      def Save(self):
--- 583,591 ----
  
      def SaveConfig(self):
+         print "Saving configuration ->", self.config_filename
          if self.verbose > 1:
!             self.options.display()
!         assert self.config and self.options, "Have no config to save!"
!         self.options.update_file(self.config_filename)
  
      def Save(self):

Index: msgstore.py
===================================================================
RCS file: /cvsroot/spambayes/spambayes/Outlook2000/msgstore.py,v
retrieving revision 1.45
retrieving revision 1.46
diff -C2 -d -r1.45 -r1.46
*** msgstore.py	4 Jun 2003 01:47:13 -0000	1.45
--- msgstore.py	16 Jun 2003 05:00:23 -0000	1.46
***************
*** 136,139 ****
--- 136,162 ----
          mapi.MAPIUninitialize()
  
+     def GetProfileName(self):
+         # Return the name of the MAPI profile currently in use.
+         # XXX - note - early win32all versions are missing
+         # GetStatusTable :(
+         try:
+             self.session.GetStatusTable
+         except AttributeError:
+             # We try and recover from this when win32all is updated, so no need to whinge.
+             return None
+ 
+         MAPI_SUBSYSTEM = 39
+         restriction = mapi.RES_PROPERTY, (mapi.RELOP_EQ, PR_RESOURCE_TYPE,
+                                           (PR_RESOURCE_TYPE, MAPI_SUBSYSTEM))
+         table = self.session.GetStatusTable(0)
+         rows = mapi.HrQueryAllRows(table,
+                                     (PR_DISPLAY_NAME_A,),   # columns to retrieve
+                                     restriction,     # only these rows
+                                     None,            # any sort order is fine
+                                     0)               # any # of results is fine
+         assert len(rows)==1, "Should be exactly one row"
+         (tag, val), = rows[0]
+         return val
+ 
      def _GetMessageStore(self, store_eid): # bin eid.
          try:





More information about the Spambayes-checkins mailing list