📄 msgstore.py
字号:
self.msgstore.session.CompareEntryIDs(parent_eid, self.id[1]): # No parent EID, or EID same as ours. return None parent = self.msgstore._OpenEntry(parent_id) # Finally get the item itself return self._FolderFromMAPIFolder(parent) except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def OpenEntry(self, iid = None, flags = None): return self.msgstore._OpenEntry(self.id, iid, flags) def GetOutlookItem(self): try: hex_item_id = mapi.HexFromBin(self.id[1]) hex_store_id = mapi.HexFromBin(self.id[0]) return self.msgstore.outlook.Session.GetFolderFromID(hex_item_id, hex_store_id) except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def GetMessageGenerator(self, only_filter_candidates = True): folder = self.OpenEntry() table = folder.GetContentsTable(0) table.SetColumns(MAPIMsgStoreMsg.message_init_props, 0) if only_filter_candidates: # Limit ourselves to IPM.* objects - ie, messages. restriction = (mapi.RES_PROPERTY, # a property restriction (mapi.RELOP_GE, # >= PR_MESSAGE_CLASS_A, # of the this prop (PR_MESSAGE_CLASS_A, "IPM."))) # with this value table.Restrict(restriction, 0) while 1: # Getting 70 at a time was the random number that gave best # perf for me ;) rows = table.QueryRows(70, 0) if len(rows) == 0: break for row in rows: # Our restriction helped, but may not have filtered # every message we don't want to touch. # Note no exception will be raised below if the message is # moved under us, as we don't need to access any properties. msg = MAPIMsgStoreMsg(self.msgstore, row) if not only_filter_candidates or msg.IsFilterCandidate(): yield msg def GetNewUnscoredMessageGenerator(self, scoreFieldName): folder = self.msgstore._OpenEntry(self.id) table = folder.GetContentsTable(0) # Resolve the field name resolve_props = ( (mapi.PS_PUBLIC_STRINGS, scoreFieldName), ) resolve_ids = folder.GetIDsFromNames(resolve_props, 0) field_id = PROP_TAG( PT_DOUBLE, PROP_ID(resolve_ids[0])) # Setup the properties we want to read. table.SetColumns(MAPIMsgStoreMsg.message_init_props, 0) # Set up the restriction # Need to check message-flags # (PR_CONTENT_UNREAD is optional, and somewhat unreliable # PR_MESSAGE_FLAGS & MSGFLAG_READ is the official way) prop_restriction = (mapi.RES_BITMASK, # a bitmask restriction (mapi.BMR_EQZ, # when bit is clear PR_MESSAGE_FLAGS, MSGFLAG_READ)) exist_restriction = mapi.RES_EXIST, (field_id,) not_exist_restriction = mapi.RES_NOT, (exist_restriction,) # A restriction for the message class class_restriction = (mapi.RES_PROPERTY, # a property restriction (mapi.RELOP_GE, # >= PR_MESSAGE_CLASS_A, # of the this prop (PR_MESSAGE_CLASS_A, "IPM."))) # with this value # Put the final restriction together restriction = (mapi.RES_AND, (prop_restriction, not_exist_restriction, class_restriction)) table.Restrict(restriction, 0) while 1: rows = table.QueryRows(70, 0) if len(rows) == 0: break for row in rows: # Note no exception will be raised below if the message is # moved under us, as we don't need to access any properties. msg = MAPIMsgStoreMsg(self.msgstore, row) if msg.IsFilterCandidate(): yield msg def IsReceiveFolder(self, msg_class = "IPM.Note"): # Is this folder the nominated "receive folder" for its store? try: mapi_store = self.msgstore._GetMessageStore(self.id[0]) eid, ret_class = mapi_store.GetReceiveFolder(msg_class, 0) return mapi_store.CompareEntryIDs(eid, self.id[1]) except pythoncom.com_error: # Error getting the receive folder from the store (or maybe our # store - but that would be insane!). Either way, we can't be it! return False def CreateFolder(self, name, comments = None, type = None, open_if_exists = False, flags = None): if type is None: type = mapi.FOLDER_GENERIC if flags is None: flags = 0 if open_if_exists: flags |= mapi.OPEN_IF_EXISTS folder = self.OpenEntry() ret = folder.CreateFolder(type, name, comments, None, flags) return self._FolderFromMAPIFolder(ret) def GetItemCount(self): try: folder = self.OpenEntry() return folder.GetContentsTable(0).GetRowCount(0) except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) # EmptyFolder() *permanently* deletes ALL messages and subfolders from # this folder without deleting the folder itself. # # WORD OF WARNING: This is a *very dangerous* function that has the # potential to destroy a user's mail. Don't even *think* about calling # this function on anything but the Certain Spam folder! def EmptyFolder(self, parentWindow): try: folder = self.OpenEntry() folder.EmptyFolder(parentWindow, None, FOLDER_DIALOG) except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def DoesFolderHaveOutlookField(self, field_name): # Returns True if the specified folder has an *Outlook* field with # the given name, False if the folder does not have it, or None # if we can't tell, or there was an error, etc. # We have discovered that Outlook stores 'Fields' for a folder as a # PR_USERFIELDS field in the hidden, 'associated' message with # message class IPC.MS.REN.USERFIELDS. This is a binary property # which is undocumented, but probably could be reverse-engineered # with a little effort (see 'dump_props --dump-folder-user-props' for # an example of the raw data. For now, the simplest thing appears # to be to check for a \0 character, followed by the property name # as an ascii string. try: folder = self.msgstore._OpenEntry(self.id) table = folder.GetContentsTable(mapi.MAPI_ASSOCIATED) restriction = (mapi.RES_PROPERTY, (mapi.RELOP_EQ, PR_MESSAGE_CLASS_A, (PR_MESSAGE_CLASS_A, 'IPC.MS.REN.USERFIELDS'))) cols = (PR_USERFIELDS,) table.SetColumns(cols, 0) rows = mapi.HrQueryAllRows(table, cols, restriction, None, 0) if len(rows)>1: print "Eeek - only expecting one row from IPC.MS.REN.USERFIELDS" print "got", repr(rows) return None if len(rows)==0: # New folders with no userdefined fields do not have such a row, # but this is a clear indication it does not exist. return False row = rows[0] val = GetPotentiallyLargeStringProp(folder, cols[0], row[0]) except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) if type(val) != type(''): print "Value type incorrect - expected string, got", repr(val) return None return val.find("\0" + field_name) >= 0 def DeleteMessages(self, message_things): # A *permanent* delete - MAPI has no concept of 'Deleted Items', # only Outlook does. If you want a "soft" delete, you must locate # deleted item (via a special ID) and move it to there yourself # message_things may be ID tuples, or MAPIMsgStoreMsg instances. real_ids = [] for thing in message_things: if isinstance(thing, MAPIMsgStoreMsg): real_ids.append( thing.id[1] ) thing.mapi_object = thing.id = thing.folder_id = None else: real_ids.append(self.msgstore.NormalizeID(thing)[1]) try: folder = self.msgstore._OpenEntry(self.id) # Nuke my MAPI reference, and set my ID to None rc = folder.DeleteMessages(real_ids, 0, None, 0) except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) def CreateTemporaryMessage(self, msg_flags = None): # Create a message designed to be used temporarily. It is your # responsibility to delete when you are done with it. # If msg_flags is not None, it should be an integer for the # PR_MESSAGE_FLAGS property. Note that Outlook appears to refuse # to set user properties on a message marked as 'unsent', which # is the default. Setting to, eg, 1 marks it as a "not unsent, read" # message, which works fine with user properties. try: folder = self.msgstore._OpenEntry(self.id) imsg = folder.CreateMessage(None, 0) if msg_flags is not None: props = (PR_MESSAGE_FLAGS,msg_flags), imsg.SetProps(props) imsg.SaveChanges(0) hr, data = imsg.GetProps((PR_ENTRYID, PR_STORE_ENTRYID), 0) eid = data[0][1] storeid = data[1][1] msg_id = mapi.HexFromBin(storeid), mapi.HexFromBin(eid) except pythoncom.com_error, details: raise MsgStoreExceptionFromCOMException(details) return self.msgstore.GetMessage(msg_id)class MAPIMsgStoreMsg: # All the properties we must initialize a message with. # These include all the IDs we need, parent IDs, any properties needed # to determine if this is a "filterable" message, etc message_init_props = (PR_ENTRYID, PR_STORE_ENTRYID, PR_SEARCH_KEY, PR_PARENT_ENTRYID, # folder ID PR_MESSAGE_CLASS_A, # 'IPM.Note' etc PR_RECEIVED_BY_ENTRYID, # who received it PR_SUBJECT_A, PR_TRANSPORT_MESSAGE_HEADERS_A, ) def __init__(self, msgstore, prop_row): self.msgstore = msgstore self.mapi_object = None # prop_row is a single mapi property row, with fields as above. # NOTE: We can't trust these properties for "large" values # (ie, strings, PT_BINARY, objects etc.), as they sometimes come # from the IMAPITable (which has a 255 limit on property values) # and sometimes from the object itself (which has no restriction). # This limitation is documented by MAPI. # Thus, we don't trust "PR_TRANSPORT_MESSAGE_HEADERS_A" more than # to ask "does the property exist?" tag, eid = prop_row[0] # ID tag, store_eid = prop_row[1] tag, searchkey = prop_row[2] tag, parent_eid = prop_row[3] tag, msgclass = prop_row[4] recby_tag, recby = prop_row[5] tag, subject = prop_row[6] headers_tag, headers = prop_row[7] self.id = store_eid, eid self.folder_id = store_eid, parent_eid self.msgclass = msgclass self.subject = subject has_headers = PROP_TYPE(headers_tag)==PT_STRING8 # Search key is the only reliable thing after a move/copy operation # only problem is that it can potentially be changed - however, the # Outlook client provides no such (easy/obvious) way # (ie, someone would need to really want to change it <wink>) # Thus, searchkey is our long-lived message key. self.searchkey = searchkey # To check if a message has ever been received, we check the # PR_RECEIVED_BY_ENTRYID flag. Tim wrote in an old comment that # An article on the web said the distinction can't be made with 100% # certainty, but that a good heuristic is to believe that a # msg has been received iff at least one of these properties # has a sensible value: RECEIVED_BY_EMAIL_ADDRESS, RECEIVED_BY_NAME, # RECEIVED_BY_ENTRYID PR_TRANSPORT_MESSAGE_HEADERS # But MarkH can't find it, and believes and tests that # PR_RECEIVED_BY_ENTRYID is all we need (but has since discovered a # couple of messages without any PR_RECEIVED_BY properties - but *with* # PR_TRANSPORT_MESSAGE_HEADERS - *sigh*) self.was_received = PROP_TYPE(recby_tag) == PT_BINARY or has_headers self.dirty = False # For use with the spambayes.message messageinfo database. self.stored_attributes = ['c', 't', 'original_folder', 'date_modified'] self.t = None self.c = None self.date_modified = None self.original_folder = None
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -