📄 msgstore.py
字号:
from __future__ import generatorsimport sys, os, reimport localetry: True, Falseexcept NameError: # Maintain compatibility with Python 2.2 True, False = 1, 0# MAPI imports etc.from win32com.client import Dispatch, constantsfrom win32com.mapi import mapi, mapiutilfrom win32com.mapi.mapitags import *import pythoncomimport winerrortry: PR_USERFIELDS # only in new win32allexcept NameError: PR_USERFIELDS = 0x36E30102 # PROP_TAG(PT_BINARY, 0x36e3)# Additional MAPI constants we dont have in PythonMESSAGE_MOVE = 0x1 # from MAPIdefs.hMSGFLAG_READ = 0x1 # from MAPIdefs.hMSGFLAG_UNSENT = 0x00000008MYPR_BODY_HTML_A = 0x1013001e # magic <wink>MYPR_BODY_HTML_W = 0x1013001f # dittoMYPR_MESSAGE_ID_A = 0x1035001E # more magic (message id field used for Exchange)CLEAR_READ_FLAG = 0x00000004CLEAR_RN_PENDING = 0x00000020CLEAR_NRN_PENDING = 0x00000040SUPPRESS_RECEIPT = 0x1FOLDER_DIALOG = 0x00000002USE_DEFERRED_ERRORS = mapi.MAPI_DEFERRED_ERRORS # or set to zero to see what changes <wink>#import warnings#if sys.version_info >= (2, 3):# # sick off the new hex() warnings!# warnings.filterwarnings("ignore", category=FutureWarning, append=1)# Nod to our automated test suite. Currently supports a hack so our test# message is filtered, and also for raising exceptions at key times.# see tester.py for more details.test_suite_running = Falsetest_suite_failure_request = Nonetest_suite_failure = None# Set to the number of times we should fail, or None for all times.test_suite_failure_count = None# Sometimes the test suite will request that we simulate MAPI errors.def help_test_suite(checkpoint_name): global test_suite_failure_request, test_suite_failure_count if test_suite_running and \ test_suite_failure_request == checkpoint_name: if test_suite_failure_count: test_suite_failure_count -= 1 if test_suite_failure_count==0: test_suite_failure_request = None raise test_suite_failure[0], test_suite_failure[1]# Exceptions raised by this module. Raw MAPI exceptions should never# be raised to the caller.class MsgStoreException(Exception): def __init__(self, mapi_exception, extra_msg = None): self.mapi_exception = mapi_exception self.extra_msg = extra_msg Exception.__init__(self, mapi_exception, extra_msg) def __str__(self): try: if self.mapi_exception is not None: err_str = GetCOMExceptionString(self.mapi_exception) else: err_str = self.extra_msg or '' return "%s: %s" % (self.__class__.__name__, err_str) # Python silently consumes exceptions here, and uses # <unprintable object> except: print "FAILED to str() a MsgStore exception!" import traceback traceback.print_exc()# Exception raised when you attempt to get a message or folder that doesn't# exist. Usually means you are querying an ID that *was* valid, but has# since been moved or deleted.# Note you may get this exception "getting" objects (such as messages or# folders), or accessing properties once the object was created (the message# may be moved under us at any time)class NotFoundException(MsgStoreException): pass# Exception raised when you try and modify a "read only" object.# Only currently examples are Hotmail and IMAP folders.class ReadOnlyException(MsgStoreException): pass# The object has changed since it was opened.class ObjectChangedException(MsgStoreException): pass# Utility functions for exceptions. Convert a COM exception to the best# manager exception.def MsgStoreExceptionFromCOMException(com_exc): if IsNotFoundCOMException(com_exc): return NotFoundException(com_exc) if IsReadOnlyCOMException(com_exc): return ReadOnlyException(com_exc) scode = NormalizeCOMException(com_exc)[0] # And simple scode based ones. if scode == mapi.MAPI_E_OBJECT_CHANGED: return ObjectChangedException(com_exc) return MsgStoreException(com_exc)def NormalizeCOMException(exc_val): hr, msg, exc, arg_err = exc_val if hr == winerror.DISP_E_EXCEPTION and exc: # 'client' exception - unpack 'exception object' wcode, source, msg, help1, help2, hr = exc return hr, msg, exc, arg_err# Build a reasonable string from a COM exception tupledef GetCOMExceptionString(exc_val): hr, msg, exc, arg_err = NormalizeCOMException(exc_val) err_string = mapiutil.GetScodeString(hr) return "Exception 0x%x (%s): %s" % (hr, err_string, msg)# Does this exception probably mean "object not found"?def IsNotFoundCOMException(exc_val): hr, msg, exc, arg_err = NormalizeCOMException(exc_val) return hr in [mapi.MAPI_E_OBJECT_DELETED, mapi.MAPI_E_NOT_FOUND]# Does this exception probably mean "object not available 'cos you ain't logged# in, or 'cos the server is down"?def IsNotAvailableCOMException(exc_val): hr, msg, exc, arg_err = NormalizeCOMException(exc_val) return hr == mapi.MAPI_E_FAILONEPROVIDERdef IsReadOnlyCOMException(exc_val): # This seems to happen for IMAP mails (0x800cccd3) # and also for hotmail messages (0x8004dff7) known_failure_codes = -2146644781, -2147164169 exc_val = NormalizeCOMException(exc_val) return exc_val[0] in known_failure_codesdef ReportMAPIError(manager, what, exc_val): hr, exc_msg, exc, arg_err = exc_val if hr == mapi.MAPI_E_TABLE_TOO_BIG: err_msg = what + _(" failed as one of your\r\n" \ "Outlook folders is full. Futher operations are\r\n" \ "likely to fail until you clean up this folder.\r\n\r\n" \ "This message will not be reported again until SpamBayes\r\n"\ "is restarted.") else: err_msg = what + _(" failed due to an unexpected Outlook error.\r\n") \ + GetCOMExceptionString(exc_val) + "\r\n\r\n" + \ _("It is recommended you restart Outlook at the earliest opportunity\r\n\r\n" \ "This message will not be reported again until SpamBayes\r\n"\ "is restarted.") manager.ReportErrorOnce(err_msg)# Our objects.class MAPIMsgStore: # Stash exceptions in the class for ease of use by consumers. MsgStoreException = MsgStoreException NotFoundException = NotFoundException ReadOnlyException = ReadOnlyException ObjectChangedException = ObjectChangedException def __init__(self, outlook = None): self.outlook = outlook cwd = os.getcwd() # remember the cwd - mapi changes it under us! mapi.MAPIInitialize(None) logonFlags = (mapi.MAPI_NO_MAIL | mapi.MAPI_EXTENDED | mapi.MAPI_USE_DEFAULT) self.session = mapi.MAPILogonEx(0, None, None, logonFlags) # Note that if the CRT still has a default "C" locale, MAPILogonEx() # will change it. See locale comments in addin.py locale.setlocale(locale.LC_NUMERIC, "C") self.mapi_msg_stores = {} self.default_store_bin_eid = None os.chdir(cwd) def Close(self): self.mapi_msg_stores = None self.session.Logoff(0, 0, 0) self.session = None 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] # I can't convince MAPI to give me the Unicode name, so we assume # encoded as MBCS. return val.decode("mbcs", "ignore") def _GetMessageStore(self, store_eid): # bin eid. try: # Will usually be pre-fetched, so fast-path out return self.mapi_msg_stores[store_eid] except KeyError: pass given_store_eid = store_eid if store_eid is None: # Find the EID for the default store. tab = self.session.GetMsgStoresTable(0) # Restriction for the table: get rows where PR_DEFAULT_STORE is true. # There should be only one. restriction = (mapi.RES_PROPERTY, # a property restriction (mapi.RELOP_EQ, # check for equality PR_DEFAULT_STORE, # of the PR_DEFAULT_STORE prop (PR_DEFAULT_STORE, True))) # with True rows = mapi.HrQueryAllRows(tab, (PR_ENTRYID,), # columns to retrieve restriction, # only these rows None, # any sort order is fine 0) # any # of results is fine # get first entry, a (property_tag, value) pair, for PR_ENTRYID row = rows[0] eid_tag, store_eid = row[0] self.default_store_bin_eid = store_eid # Open it. store = self.session.OpenMsgStore( 0, # no parent window store_eid, # msg store to open None, # IID; accept default IMsgStore # need write access to add score fields mapi.MDB_WRITE | # we won't send or receive email mapi.MDB_NO_MAIL | USE_DEFERRED_ERRORS) # cache it self.mapi_msg_stores[store_eid] = store if given_store_eid is None: # The default store self.mapi_msg_stores[None] = store return store def GetRootFolder(self, store_id = None): # if storeID is None, gets the root folder from the default store. store = self._GetMessageStore(store_id) hr, data = store.GetProps((PR_ENTRYID, PR_IPM_SUBTREE_ENTRYID), 0) store_eid = data[0][1] subtree_eid = data[1][1] eid = mapi.HexFromBin(store_eid), mapi.HexFromBin(subtree_eid) return self.GetFolder(eid) def _OpenEntry(self, id, iid = None, flags = None): # id is already normalized. store_id, item_id = id
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -