📄 manager.py
字号:
# 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"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 = {} self.reported_startup_error = False self.config = self.options = None self.addin = None self.verbose = verbose self.outlook = outlook 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() self.application_directory = os.path.dirname(this_filename) # Load the environment for translation. lang_manager = bayes_i18n.LanguageManager() # Set the system user default language. lang_manager.set_language(lang_manager.locale_default_lang()) # 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: # until I know otherwise, config files are ASCII - but our # file system is unicode to some degree. # (do config files support encodings at all?) # Assume the file system encoding for file names! try: value = value.decode(filesystem_encoding) except AttributeError: # May already be Unicode pass assert isinstance(value, types.UnicodeType), "%r should be a unicode" % value try: if not os.path.isdir(value): os.makedirs(value) assert os.path.isdir(value), "just made the *ucker" value = os.path.abspath(value) except os.error: print "The configuration files have specified a data " \ "directory of", repr(value), "but it is not valid. " \ "Using default." value = None if value: self.data_directory = value else: self.data_directory = self.windows_data_directory # Get the message store before loading config, as we use the profile # name. self.message_store = msgstore.MAPIMsgStore(outlook) self.LoadConfig() # Load the options for the classifier. We support # default_bayes_customize.ini in the app directory and user data # directory (version 0.8 and earlier, we copied the app one to the # user dir - that was a mistake - but supporting a version in that # directory wasn't). We also look for a # {outlook-profile-name}_bayes_customize.ini file in the data # directory, to allow per-profile customisations. bayes_option_filenames = [] # data dir last so options there win. for look_dir in [self.application_directory, self.data_directory]: look_file = os.path.join(look_dir, "default_bayes_customize.ini") if os.path.isfile(look_file): bayes_option_filenames.append(look_file) look_file = os.path.join(self.data_directory, self.GetProfileName() + \ "_bayes_customize.ini") if os.path.isfile(look_file): bayes_option_filenames.append(look_file) import_core_spambayes_stuff(bayes_option_filenames) # Set interface to use the user language in configuration file. for language in bayes_options["globals", "language"][::-1]: # We leave the default in there as the last option, to fall # back on if necessary. lang_manager.add_language(language) self.LogDebug(1, "Asked to add languages: " + \ ", ".join(bayes_options["globals", "language"])) self.LogDebug(1, "Set language to " + \ str(lang_manager.current_langs_codes)) 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 = GetStorageManagerClass() db_manager = ManagerClass(bayes_base, mdb_base) self.classifier_data = ClassifierData(db_manager, self) try: self.classifier_data.Load() except: self.ReportFatalStartupError("Failed to load bayes database") self.classifier_data.InitNew() self.bayes_options = bayes_options self.bayes_message = bayes_message 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) # Logging - this should be somewhere else. def LogDebug(self, level, *args): if self.verbose >= level: for arg in args[:-1]: print arg, print args[-1] def ReportError(self, message, title = None): if self.test_suite_running: print "ReportError:", repr(message) print "(but test suite running - not reported)" return ReportError(message, title) def ReportInformation(self, message, title=None): if self.test_suite_running: print "ReportInformation:", repr(message) print "(but test suite running - not reported)" return ReportInformation(message, title) def AskQuestion(self, message, title=None): return AskQuestion(message, title) # Report a super-serious startup error to the user. # This should only be used when SpamBayes was previously working, but a # critical error means we are probably not working now. # We just report the first such error - subsequent ones are likely a result of # the first - hence, this must only be used for startup errors. def ReportFatalStartupError(self, message): if not self.reported_startup_error: self.reported_startup_error = True full_message = _(\ "There was an error initializing the Spam plugin.\r\n\r\n" \ "Spam filtering has been disabled. Please re-configure\r\n" \ "and re-enable this plugin\r\n\r\n" \ "Error details:\r\n") + message # Disable the plugin if self.config is not None: self.config.filter.enabled = False self.ReportError(full_message) else: # We have reported the error, but for the sake of the log, we # still want it logged there. print "ERROR:", repr(message) traceback.print_exc() def ReportErrorOnce(self, msg, title = None, key = None): if key is None: key = msg # Always print the message and traceback. if self.test_suite_running: print "ReportErrorOnce:", repr(msg) print "(but test suite running - not reported)" return print "ERROR:", repr(msg) if key in self.reported_error_map: print "(this error has already been reported - not displaying it again)" else: traceback.print_exc() self.reported_error_map[key] = True ReportError(msg, title) # Outlook used to give us thread grief - now we avoid Outlook # from threads, but this remains a worthwhile abstraction. def WorkerThreadStarting(self): pythoncom.CoInitialize() def WorkerThreadEnding(self): pythoncom.CoUninitialize() def LocateDataDirectory(self): # Locate the best directory for our data files. from win32com.shell import shell, shellcon try: appdata = shell.SHGetFolderPath(0,shellcon.CSIDL_APPDATA,0,0) path = os.path.join(appdata, "SpamBayes") if not os.path.isdir(path): os.makedirs(path) return path except pythoncom.com_error: # Function doesn't exist on early win95, # and it may just fail anyway! return self.application_directory except EnvironmentError: # Can't make the directory. return self.application_directory def FormatFolderNames(self, folder_ids, include_sub): names = [] for eid in folder_ids: try: folder = self.message_store.GetFolder(eid) name = folder.name except self.message_store.MsgStoreException: name = "<unknown folder>" names.append(name) ret = '; '.join(names) if include_sub: ret += " (incl. Sub-folders)" return ret def EnsureOutlookFieldsForFolder(self, folder_id, include_sub=False): # Should be called at least once once per folder you are # watching/filtering etc # Ensure that our fields exist on the Outlook *folder* # Setting properties via our msgstore (via Ext Mapi) sets the props # on the message OK, but Outlook doesn't see it as a "UserProperty". # Using MAPI to set them directly on the folder also has no effect. # Later: We have since discovered that Outlook stores user property # information in the 'associated contents' folder - see # msgstore.MAPIMsgStoreFolder.DoesFolderHaveOutlookField() for more # details. We can reverse engineer this well enough to determine # if a property exists, but not well enough to actually add a # property. Thus, we resort to the Outlook object model to actually # add it. # Note that this means we need an object in the folder to modify. # We could go searching for an existing item then modify and save it # (indeed, we did once), but this could be bad-form, as the message # we randomly choose to modify will then have a meaningless 'Spam' # field. If we are going to go to the effort of creating a temp # item when no item exists, we may as well do it all the time, # especially now we know how to check if the folder has the field # without opening an Outlook item. # Regarding the property type: # We originally wanted to use the "Integer" Outlook field, # but it seems this property type alone is not exposed via the Object # model. So we resort to olPercent, and live with the % sign # (which really is OK!) assert self.outlook is not None, "I need outlook :(" field_name = self.config.general.field_score_name for msgstore_folder in self.message_store.GetFolderGenerator( [folder_id], include_sub): folder_name = msgstore_folder.GetFQName() if msgstore_folder.DoesFolderHaveOutlookField(field_name): self.LogDebug(1, "Folder '%s' already has field '%s'" \ % (folder_name, field_name)) continue self.LogDebug(0, "Folder '%s' has no field named '%s' - creating" \ % (folder_name, field_name)) # Creating the item via the Outlook model does some strange # things (such as moving it to "Drafts" on save), so we create # it using extended MAPI (via our msgstore) message = msgstore_folder.CreateTemporaryMessage(msg_flags=1) outlook_message = message.GetOutlookItem() ups = outlook_message.UserProperties try: # Display format is documented as being the 1-based index in # the combo box in the outlook UI for the given data type. # 1 is the first - "Rounded", which seems fine. format = 1 ups.Add(field_name, win32com.client.constants.olPercent, True, # Add to folder format) outlook_message.Save() except pythoncom.com_error, details: if msgstore.IsReadOnlyCOMException(details): self.LogDebug(1, "The folder '%s' is read-only - user " "property can't be added" % (folder_name,)) else: print "Warning: failed to create the Outlook " \ "user-property in folder '%s'" \ % (folder_name,) print "", details msgstore_folder.DeleteMessages((message,)) # Check our DoesFolderHaveOutlookField logic holds up. if not msgstore_folder.DoesFolderHaveOutlookField(field_name): self.LogDebug(0, "WARNING: We just created the user field in folder " "%s, but it appears to not exist. Something is " "probably wrong with DoesFolderHaveOutlookField()" % \ folder_name) 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 GetProfileName(self): profile_name = self.message_store.GetProfileName() # The profile name may include characters invalid in file names. if profile_name is not None: profile_name = "".join([c for c in profile_name if ord(c)>127 or c in filename_chars]) if profile_name is None: # should only happen in source-code versions - older win32alls can't # determine this. profile_name = "unknown_profile" print "*** NOTE: It appears you are running the source-code version of"
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -