def test_06_flagsmaildir2imap(self): """Test imaputil.flagsmaildir2imap()""" res = imaputil.flagsmaildir2imap(set(b'DR')) self.assertEqual(res, b'(\\Answered \\Draft)') # test all possible flags res = imaputil.flagsmaildir2imap(set(b'SRFTD')) self.assertEqual(res, b'(\\Answered \\Deleted \\Draft \\Flagged \\Seen)')
def savemessageflags(self, uid, flags): """Change a message's flags to `flags`. Note that this function does not check against dryrun settings, so you need to ensure that it is never called in a dryrun mode.""" imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: self.ui.flagstoreadonly(self, [uid], flags) return result = imapobj.uid('store', '%d' % uid, 'FLAGS', imaputil.flagsmaildir2imap(flags)) assert result[0] == 'OK', 'Error with store: ' + '. '.join( result[1]) finally: self.imapserver.releaseconnection(imapobj) result = result[1][0] if not result: self.messagelist[uid]['flags'] = flags else: flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS'] self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
def processmessagesflags(self, operation, uidlist, flags): # XXX: the imapobj.myrights(...) calls dies with an error # report from Gmail server stating that IMAP command # 'MYRIGHTS' is not implemented. So, this # `processmessagesflags` is just a copy from `IMAPFolder`, # with the references to `imapobj.myrights()` deleted This # shouldn't hurt, however, Gmail users always have full # control over all their mailboxes (apparently). if len(uidlist) > 101: # Hack for those IMAP ervers with a limited line length self.processmessagesflags(operation, uidlist[:100], flags) self.processmessagesflags(operation, uidlist[100:], flags) return imapobj = self.imapserver.acquireconnection() try: imapobj.select(self.getfullname()) r = imapobj.uid('store', imaputil.listjoin(uidlist), operation + 'FLAGS', imaputil.flagsmaildir2imap(flags)) assert r[0] == 'OK', 'Error with store: ' + '. '.join(r[1]) r = r[1] finally: self.imapserver.releaseconnection(imapobj) needupdate = copy(uidlist) for result in r: if result == None: # Compensate for servers that don't return anything from # STORE. continue attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1]) if not ('UID' in attributehash and 'FLAGS' in attributehash): # Compensate for servers that don't return a UID attribute. continue flags = attributehash['FLAGS'] uid = long(attributehash['UID']) self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags) try: needupdate.remove(uid) except ValueError: # Let it slide if it's not in the list pass for uid in needupdate: if operation == '+': for flag in flags: if not flag in self.messagelist[uid]['flags']: self.messagelist[uid]['flags'].append(flag) self.messagelist[uid]['flags'].sort() elif operation == '-': for flag in flags: if flag in self.messagelist[uid]['flags']: self.messagelist[uid]['flags'].remove(flag)
def processmessagesflags(self, operation, uidlist, flags): if len(uidlist) > 101: # Hack for those IMAP ervers with a limited line length self.processmessagesflags(operation, uidlist[:100], flags) self.processmessagesflags(operation, uidlist[100:], flags) return imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: UIBase.getglobalui().flagstoreadonly(self, uidlist, flags) return r = imapobj.uid('store', imaputil.listjoin(uidlist), operation + 'FLAGS', imaputil.flagsmaildir2imap(flags)) assert r[0] == 'OK', 'Error with store: ' + '. '.join(r[1]) r = r[1] finally: self.imapserver.releaseconnection(imapobj) # Some IMAP servers do not always return a result. Therefore, # only update the ones that it talks about, and manually fix # the others. needupdate = copy(uidlist) for result in r: if result == None: # Compensate for servers that don't return anything from # STORE. continue attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1]) if not ('UID' in attributehash and 'FLAGS' in attributehash): # Compensate for servers that don't return a UID attribute. continue lflags = attributehash['FLAGS'] uid = long(attributehash['UID']) self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(lflags) try: needupdate.remove(uid) except ValueError: # Let it slide if it's not in the list pass for uid in needupdate: if operation == '+': for flag in flags: if not flag in self.messagelist[uid]['flags']: self.messagelist[uid]['flags'].append(flag) self.messagelist[uid]['flags'].sort() elif operation == '-': for flag in flags: if flag in self.messagelist[uid]['flags']: self.messagelist[uid]['flags'].remove(flag)
def _scanfolder(self): """Cache the message list from a Maildir. Maildir flags are: R (replied) S (seen) T (trashed) D (draft) F (flagged). :returns: dict that can be used as self.messagelist""" maxage = self.config.getdefaultint("Account " + self.accountname, "maxage", None) maxsize = self.config.getdefaultint("Account " + self.accountname, "maxsize", None) retval = {} files = [] nouidcounter = -1 # Messages without UIDs get negative UIDs. for dirannex in ['new', 'cur']: fulldirname = os.path.join(self.getfullname(), dirannex) files.extend( (dirannex, filename) for filename in os.listdir(fulldirname)) for dirannex, filename in files: # We store just dirannex and filename, ie 'cur/123...' filepath = os.path.join(dirannex, filename) # check maxage/maxsize if this message should be considered if maxage and not self._iswithinmaxage(filename, maxage): continue if maxsize and (os.path.getsize( os.path.join(self.getfullname(), filepath)) > maxsize): continue (prefix, uid, fmd5, maildirflags) = self._parse_filename(filename) if uid is None: # assign negative uid to upload it. uid = nouidcounter nouidcounter -= 1 else: # It comes from our folder. uidmatch = re_uidmatch.search(filename) uid = None if not uidmatch: uid = nouidcounter nouidcounter -= 1 else: uid = long(uidmatch.group(1)) # 'filename' is 'dirannex/filename', e.g. cur/123,U=1,FMD5=1:2,S retval[uid] = { 'flags': imaputil.flagsmaildir2imap(maildirflags), 'filename': filepath } return retval
def processmessagesflags(self, operation, uidlist, flags): if len(uidlist) > 101: # Hack for those IMAP ervers with a limited line length self.processmessagesflags(operation, uidlist[:100], flags) self.processmessagesflags(operation, uidlist[100:], flags) return imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: self.ui.flagstoreadonly(self, uidlist, flags) return r = imapobj.uid( "store", imaputil.uid_sequence(uidlist), operation + "FLAGS", imaputil.flagsmaildir2imap(flags) ) assert r[0] == "OK", "Error with store: " + ". ".join(r[1]) r = r[1] finally: self.imapserver.releaseconnection(imapobj) # Some IMAP servers do not always return a result. Therefore, # only update the ones that it talks about, and manually fix # the others. needupdate = list(uidlist) for result in r: if result == None: # Compensate for servers that don't return anything from # STORE. continue attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1]) if not ("UID" in attributehash and "FLAGS" in attributehash): # Compensate for servers that don't return a UID attribute. continue flagstr = attributehash["FLAGS"] uid = long(attributehash["UID"]) self.messagelist[uid]["flags"] = imaputil.flagsimap2maildir(flagstr) try: needupdate.remove(uid) except ValueError: # Let it slide if it's not in the list pass for uid in needupdate: if operation == "+": self.messagelist[uid]["flags"] |= flags elif operation == "-": self.messagelist[uid]["flags"] -= flags
def savemessageflags(self, uid, flags): imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: self.ui.flagstoreadonly(self, [uid], flags) return result = imapobj.uid("store", "%d" % uid, "FLAGS", imaputil.flagsmaildir2imap(flags)) assert result[0] == "OK", "Error with store: " + ". ".join(result[1]) finally: self.imapserver.releaseconnection(imapobj) result = result[1][0] if not result: self.messagelist[uid]["flags"] = flags else: flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])["FLAGS"] self.messagelist[uid]["flags"] = imaputil.flagsimap2maildir(flags)
def _scanfolder(self): """Cache the message list from a Maildir. Maildir flags are: R (replied) S (seen) T (trashed) D (draft) F (flagged). :returns: dict that can be used as self.messagelist""" maxage = self.config.getdefaultint("Account " + self.accountname, "maxage", None) maxsize = self.config.getdefaultint("Account " + self.accountname, "maxsize", None) retval = {} files = [] nouidcounter = -1 # Messages without UIDs get negative UIDs. for dirannex in ['new', 'cur']: fulldirname = os.path.join(self.getfullname(), dirannex) files.extend((dirannex, filename) for filename in os.listdir(fulldirname)) for dirannex, filename in files: # We store just dirannex and filename, ie 'cur/123...' filepath = os.path.join(dirannex, filename) # check maxage/maxsize if this message should be considered if maxage and not self._iswithinmaxage(filename, maxage): continue if maxsize and (os.path.getsize(os.path.join( self.getfullname(), filepath)) > maxsize): continue (prefix, uid, fmd5, maildirflags) = self._parse_filename(filename) if uid is None: # assign negative uid to upload it. uid = nouidcounter nouidcounter -= 1 else: # It comes from our folder. uidmatch = re_uidmatch.search(filename) uid = None if not uidmatch: uid = nouidcounter nouidcounter -= 1 else: uid = long(uidmatch.group(1)) # 'filename' is 'dirannex/filename', e.g. cur/123,U=1,FMD5=1:2,S retval[uid] = {'flags': imaputil.flagsmaildir2imap(maildirflags), 'filename': filepath} return retval
def __processmessagesflags_real(self, operation, uidlist, flags): imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: self.ui.flagstoreadonly(self, uidlist, flags) return response = imapobj.uid('store', imaputil.uid_sequence(uidlist), operation + 'FLAGS', imaputil.flagsmaildir2imap(flags)) if response[0] != 'OK': raise OfflineImapError( 'Error with store: %s' % '. '.join(response[1]), OfflineImapError.ERROR.MESSAGE) response = response[1] finally: self.imapserver.releaseconnection(imapobj) # Some IMAP servers do not always return a result. Therefore, # only update the ones that it talks about, and manually fix # the others. needupdate = list(uidlist) for result in response: if result is None: # Compensate for servers that don't return anything from # STORE. continue attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1]) if not ('UID' in attributehash and 'FLAGS' in attributehash): # Compensate for servers that don't return a UID attribute. continue flagstr = attributehash['FLAGS'] uid = int(attributehash['UID']) self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir( flagstr) try: needupdate.remove(uid) except ValueError: # Let it slide if it's not in the list. pass for uid in needupdate: if operation == '+': self.messagelist[uid]['flags'] |= flags elif operation == '-': self.messagelist[uid]['flags'] -= flags
def __processmessagesflags_real(self, operation, uidlist, flags): imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: self.ui.flagstoreadonly(self, uidlist, flags) return response = imapobj.uid('store', imaputil.uid_sequence(uidlist), operation + 'FLAGS', imaputil.flagsmaildir2imap(flags)) if response[0] != 'OK': raise OfflineImapError( 'Error with store: %s'% '. '.join(response[1]), OfflineImapError.ERROR.MESSAGE) response = response[1] finally: self.imapserver.releaseconnection(imapobj) # Some IMAP servers do not always return a result. Therefore, # only update the ones that it talks about, and manually fix # the others. needupdate = list(uidlist) for result in response: if result is None: # Compensate for servers that don't return anything from # STORE. continue attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1]) if not ('UID' in attributehash and 'FLAGS' in attributehash): # Compensate for servers that don't return a UID attribute. continue flagstr = attributehash['FLAGS'] uid = int(attributehash['UID']) self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flagstr) try: needupdate.remove(uid) except ValueError: # Let it slide if it's not in the list. pass for uid in needupdate: if operation == '+': self.messagelist[uid]['flags'] |= flags elif operation == '-': self.messagelist[uid]['flags'] -= flags
def savemessageflags(self, uid, flags): imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: self.ui.flagstoreadonly(self, [uid], flags) return result = imapobj.uid('store', '%d' % uid, 'FLAGS', imaputil.flagsmaildir2imap(flags)) assert result[0] == 'OK', 'Error with store: ' + '. '.join(result[1]) finally: self.imapserver.releaseconnection(imapobj) result = result[1][0] if not result: self.messagelist[uid]['flags'] = flags else: flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS'] self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
def savemessageflags(self, uid, flags): imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: self.ui.flagstoreadonly(self, [uid], flags) return result = imapobj.uid('store', '%d' % uid, 'FLAGS', imaputil.flagsmaildir2imap(flags)) assert result[0] == 'OK', 'Error with store: ' + '. '.join(r[1]) finally: self.imapserver.releaseconnection(imapobj) result = result[1][0] if not result: self.messagelist[uid]['flags'] = flags else: flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS'] self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
def savemessageflags(self, uid, flags): """Change a message's flags to `flags`. Note that this function does not check against dryrun settings, so you need to ensure that it is never called in a dryrun mode.""" imapobj = self.imapserver.acquireconnection() try: result = self._store_to_imap(imapobj, str(uid), 'FLAGS', imaputil.flagsmaildir2imap(flags)) except imapobj.readonly: self.ui.flagstoreadonly(self, [uid], flags) return finally: self.imapserver.releaseconnection(imapobj) if not result: self.messagelist[uid]['flags'] = flags else: flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS'] self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
def savemessageflags(self, uid, flags): """Change a message's flags to `flags`. Note that this function does not check against dryrun settings, so you need to ensure that it is never called in a dryrun mode.""" imapobj = self.imapserver.acquireconnection() try: try: imapobj.select(self.getfullname()) except imapobj.readonly: self.ui.flagstoreadonly(self, [uid], flags) return result = imapobj.uid('store', '%d' % uid, 'FLAGS', imaputil.flagsmaildir2imap(flags)) assert result[0] == 'OK', 'Error with store: ' + '. '.join(result[1]) finally: self.imapserver.releaseconnection(imapobj) result = result[1][0] if not result: self.messagelist[uid]['flags'] = flags else: flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS'] self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. See folder/Base for details. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the message is saved, but it's UID can not be found, it will return 0. If the message can't be written (folder is read-only for example) it will return -1.""" self.ui.savemessage('imap', uid, flags, self) # Already have it, just save modified flags. if uid > 0 and self.uidexists(uid): self.savemessageflags(uid, flags) return uid content = self.deletemessageheaders(content, self.filterheaders) # Use proper CRLF all over the message. content = re.sub("(?<!\r)\n", CRLF, content) # Get the date of the message, so we can pass it to the server. date = self.__getmessageinternaldate(content, rtime) # Message-ID is handy for debugging messages. msg_id = self.getmessageheader(content, "message-id") if not msg_id: msg_id = '[unknown message-id]' retry_left = 2 # succeeded in APPENDING? imapobj = self.imapserver.acquireconnection() # NB: in the finally clause for this try we will release # NB: the acquired imapobj, so don't do that twice unless # NB: you will put another connection to imapobj. If you # NB: really do need to release connection manually, set # NB: imapobj to None. try: while retry_left: # XXX: we can mangle message only once, out of the loop # UIDPLUS extension provides us with an APPENDUID response. use_uidplus = 'UIDPLUS' in imapobj.capabilities if not use_uidplus: # Insert a random unique header that we can fetch later. (headername, headervalue) = self.__generate_randomheader(content) self.ui.debug( 'imap', 'savemessage: header is: %s: %s' % (headername, headervalue)) content = self.addmessageheader(content, CRLF, headername, headervalue) if len(content) > 200: dbg_output = "%s...%s" % (content[:150], content[-50:]) else: dbg_output = content self.ui.debug( 'imap', "savemessage: date: %s, content: '%s'" % (date, dbg_output)) try: # Select folder for append and make the box READ-WRITE. imapobj.select(self.getfullname()) except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid # Do the APPEND. try: (typ, dat) = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) # This should only catch 'NO' responses since append() # will raise an exception for 'BAD' responses: if typ != 'OK': # For example, Groupwise IMAP server can return something like: # # NO APPEND The 1500 MB storage limit has been exceeded. # # In this case, we should immediately abort the repository sync # and continue with the next account. msg = \ "Saving msg (%s) in folder '%s', repository '%s' failed (abort). " \ "Server responded: %s %s\n"% \ (msg_id, self, self.getrepository(), typ, dat) raise OfflineImapError(msg, OfflineImapError.ERROR.REPO) retry_left = 0 # Mark as success. except imapobj.abort as e: # Connection has been reset, release connection and retry. retry_left -= 1 self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() if not retry_left: six.reraise( OfflineImapError, OfflineImapError( "Saving msg (%s) in folder '%s', " "repository '%s' failed (abort). Server responded: %s\n" "Message content was: %s" % (msg_id, self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE), exc_info()[2]) # XXX: is this still needed? self.ui.error(e, exc_info()[2]) except imapobj.error as e: # APPEND failed # If the server responds with 'BAD', append() # raise()s directly. So we catch that too. # drop conn, it might be bad. self.imapserver.releaseconnection(imapobj, True) imapobj = None six.reraise( OfflineImapError, OfflineImapError( "Saving msg (%s) folder '%s', repo '%s'" "failed (error). Server responded: %s\nMessage content was: " "%s" % (msg_id, self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE), exc_info()[2]) # Checkpoint. Let it write out stuff, etc. Eg searches for # just uploaded messages won't work if we don't do this. (typ, dat) = imapobj.check() assert (typ == 'OK') # Get the new UID, do we use UIDPLUS? if use_uidplus: # Get new UID from the APPENDUID response, it could look # like OK [APPENDUID 38505 3955] APPEND completed with # 38505 bein folder UIDvalidity and 3955 the new UID. # note: we would want to use .response() here but that # often seems to return [None], even though we have # data. TODO resp = imapobj._get_untagged_response('APPENDUID') if resp == [None] or resp is None: self.ui.warn( "Server supports UIDPLUS but got no APPENDUID " "appending a message.") return 0 uid = int(resp[-1].split(' ')[1]) if uid == 0: self.ui.warn( "savemessage: Server supports UIDPLUS, but" " we got no usable uid back. APPENDUID reponse was " "'%s'" % str(resp)) else: # We don't support UIDPLUS. uid = self.__savemessage_searchforheader( imapobj, headername, headervalue) # See docs for savemessage in Base.py for explanation # of this and other return values. if uid == 0: self.ui.debug( 'imap', 'savemessage: attempt to get new UID ' 'UID failed. Search headers manually.') uid = self.__savemessage_fetchheaders( imapobj, headername, headervalue) self.ui.warn( 'imap', "savemessage: Searching mails for new " "Message-ID failed. Could not determine new UID.") finally: if imapobj: self.imapserver.releaseconnection(imapobj) if uid: # Avoid UID FETCH 0 crash happening later on self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid]['flags'] = flags self.ui.debug('imap', 'savemessage: returning new UID %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the folder is read-only it will return 0.""" self.ui.debug('imap', 'savemessage: called') # already have it, just save modified flags if uid > 0 and uid in self.messagelist: self.savemessageflags(uid, flags) return uid try: imapobj = self.imapserver.acquireconnection() try: imapobj.select(self.getfullname( )) # Needed for search and making the box READ-WRITE except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid # UIDPLUS extension provides us with an APPENDUID response to our append() use_uidplus = 'UIDPLUS' in imapobj.capabilities # get the date of the message file, so we can pass it to the server. date = self.getmessageinternaldate(content, rtime) self.ui.debug('imap', 'savemessage: using date %s' % date) content = re.sub("(?<!\r)\n", "\r\n", content) if not use_uidplus: # insert a random unique header that we can fetch later (headername, headervalue) = self.generate_randomheader(content) self.ui.debug('imap', 'savemessage: new headers are: %s: %s' % \ (headername, headervalue)) content = self.savemessage_addheader(content, headername, headervalue) self.ui.debug('imap', 'savemessage: content is: ' + repr(content)) # TODO: - append could raise a ValueError if the date is not in # valid format...? (typ, dat) = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) assert (typ == 'OK') # Checkpoint. Let it write out the messages, etc. (typ, dat) = imapobj.check() assert (typ == 'OK') # get the new UID. Test for APPENDUID response even if the # server claims to not support it, as e.g. Gmail does :-( if use_uidplus or imapobj._get_untagged_response( 'APPENDUID', True): # get the new UID from the APPENDUID response, it could look like # OK [APPENDUID 38505 3955] APPEND completed # with 38505 bein folder UIDvalidity and 3955 the new UID if not imapobj._get_untagged_response('APPENDUID', True): self.ui.warn( "Server supports UIDPLUS but got no APPENDUID " "appending a message.") return 0 uid = long( imapobj._get_untagged_response('APPENDUID', True)[-1].split(' ')[1]) else: # we don't support UIDPLUS uid = self.savemessage_searchforheader(imapobj, headername, headervalue) # See docs for savemessage in Base.py for explanation of this and other return values if uid == 0: self.ui.debug( 'imap', 'savemessage: first attempt to get new UID failed. Going to run a NOOP and try again.' ) assert (imapobj.noop()[0] == 'OK') uid = self.savemessage_searchforheader( imapobj, headername, headervalue) finally: self.imapserver.releaseconnection(imapobj) if uid: # avoid UID FETCH 0 crash happening later on self.messagelist[uid] = {'uid': uid, 'flags': flags} self.ui.debug('imap', 'savemessage: returning new UID %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. See folder/Base for details. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the message is saved, but it's UID can not be found, it will return 0. If the message can't be written (folder is read-only for example) it will return -1.""" self.ui.savemessage('imap', uid, flags, self) # already have it, just save modified flags if uid > 0 and self.uidexists(uid): self.savemessageflags(uid, flags) return uid # Remove headers in filterheaders if len(self.filterheaders) > 0: insertionpoint = content.find("\n\n") leader = content[0:insertionpoint] trailer = content[insertionpoint:] for header in self.filterheaders: leader = re.sub('^%s:.*$\n?' % header, '', leader, flags = re.MULTILINE) content = leader + trailer retry_left = 2 # succeeded in APPENDING? imapobj = self.imapserver.acquireconnection() try: while retry_left: # UIDPLUS extension provides us with an APPENDUID response. use_uidplus = 'UIDPLUS' in imapobj.capabilities # get the date of the message, so we can pass it to the server. date = self.getmessageinternaldate(content, rtime) if not use_uidplus: # insert a random unique header that we can fetch later (headername, headervalue) = self.generate_randomheader( content) self.ui.debug('imap', 'savemessage: header is: %s: %s' %\ (headername, headervalue)) content = self.savemessage_addheader(content, headername, headervalue) content = re.sub("(?<!\r)\n", "\r\n", content) if len(content)>200: dbg_output = "%s...%s" % (content[:150], content[-50:]) else: dbg_output = content self.ui.debug('imap', "savemessage: date: %s, content: '%s'" % (date, dbg_output)) try: # Select folder for append and make the box READ-WRITE imapobj.select(self.getfullname()) except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid #Do the APPEND try: (typ, dat) = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) retry_left = 0 # Mark as success except imapobj.abort as e: # connection has been reset, release connection and retry. retry_left -= 1 self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() if not retry_left: raise OfflineImapError("Saving msg in folder '%s', " "repository '%s' failed (abort). Server reponded: %s\n" "Message content was: %s" % (self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE) self.ui.error(e, exc_info()[2]) except imapobj.error as e: # APPEND failed # If the server responds with 'BAD', append() # raise()s directly. So we catch that too. # drop conn, it might be bad. self.imapserver.releaseconnection(imapobj, True) imapobj = None raise OfflineImapError("Saving msg folder '%s', repo '%s'" "failed (error). Server reponded: %s\nMessage content was: " "%s" % (self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE) # Checkpoint. Let it write out stuff, etc. Eg searches for # just uploaded messages won't work if we don't do this. (typ,dat) = imapobj.check() assert(typ == 'OK') # get the new UID, do we use UIDPLUS? if use_uidplus: # get new UID from the APPENDUID response, it could look # like OK [APPENDUID 38505 3955] APPEND completed with # 38505 bein folder UIDvalidity and 3955 the new UID. # note: we would want to use .response() here but that # often seems to return [None], even though we have # data. TODO resp = imapobj._get_untagged_response('APPENDUID') if resp == [None] or resp is None: self.ui.warn("Server supports UIDPLUS but got no APPENDUID " "appending a message.") return 0 uid = long(resp[-1].split(' ')[1]) if uid == 0: self.ui.warn("savemessage: Server supports UIDPLUS, but" " we got no usable uid back. APPENDUID reponse was " "'%s'" % str(resp)) else: # we don't support UIDPLUS uid = self.savemessage_searchforheader(imapobj, headername, headervalue) # See docs for savemessage in Base.py for explanation # of this and other return values if uid == 0: self.ui.debug('imap', 'savemessage: attempt to get new UID ' 'UID failed. Search headers manually.') uid = self.savemessage_fetchheaders(imapobj, headername, headervalue) self.ui.warn('imap', "savemessage: Searching mails for new " "Message-ID failed. Could not determine new UID.") finally: self.imapserver.releaseconnection(imapobj) if uid: # avoid UID FETCH 0 crash happening later on self.messagelist[uid] = {'uid': uid, 'flags': flags} self.ui.debug('imap', 'savemessage: returning new UID %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the message is saved, but it's UID can not be found, it will return 0. If the message can't be written (folder is read-only for example) it will return -1.""" self.ui.debug("imap", "savemessage: called") # already have it, just save modified flags if uid > 0 and self.uidexists(uid): self.savemessageflags(uid, flags) return uid imapobj = self.imapserver.acquireconnection() try: success = False # succeeded in APPENDING? while not success: # UIDPLUS extension provides us with an APPENDUID response. use_uidplus = "UIDPLUS" in imapobj.capabilities # get the date of the message, so we can pass it to the server. date = self.getmessageinternaldate(content, rtime) content = re.sub("(?<!\r)\n", "\r\n", content) if not use_uidplus: # insert a random unique header that we can fetch later (headername, headervalue) = self.generate_randomheader(content) self.ui.debug("imap", "savemessage: header is: %s: %s" % (headername, headervalue)) content = self.savemessage_addheader(content, headername, headervalue) if len(content) > 200: dbg_output = "%s...%s" % (content[:150], content[-50:]) else: dbg_output = content self.ui.debug("imap", "savemessage: date: %s, content: '%s'" % (date, dbg_output)) try: # Select folder for append and make the box READ-WRITE imapobj.select(self.getfullname()) except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid # Do the APPEND try: (typ, dat) = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) success = True except imapobj.abort, e: # connection has been reset, release connection and retry. self.ui.error(e, exc_info()[2]) self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() except imapobj.error, e: # If the server responds with 'BAD', append() raise()s directly. # So we need to prepare a response ourselves. typ, dat = "BAD", str(e) if typ != "OK": # APPEND failed raise OfflineImapError( "Saving msg in folder '%s', repository " "'%s' failed. Server reponded; %s %s\nMessage content was:" " %s" % (self, self.getrepository(), typ, dat, dbg_output), OfflineImapError.ERROR.MESSAGE, )
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the message is saved, but it's UID can not be found, it will return 0. If the message can't be written (folder is read-only for example) it will return -1.""" self.ui.debug('imap', 'savemessage: called') # already have it, just save modified flags if uid > 0 and self.uidexists(uid): self.savemessageflags(uid, flags) return uid retry_left = 2 # succeeded in APPENDING? imapobj = self.imapserver.acquireconnection() try: while retry_left: # UIDPLUS extension provides us with an APPENDUID response. use_uidplus = 'UIDPLUS' in imapobj.capabilities # get the date of the message, so we can pass it to the server. date = self.getmessageinternaldate(content, rtime) content = re.sub("(?<!\r)\n", "\r\n", content) if not use_uidplus: # insert a random unique header that we can fetch later (headername, headervalue) = self.generate_randomheader(content) self.ui.debug('imap', 'savemessage: header is: %s: %s' %\ (headername, headervalue)) content = self.savemessage_addheader( content, headername, headervalue) if len(content) > 200: dbg_output = "%s...%s" % (content[:150], content[-50:]) else: dbg_output = content self.ui.debug( 'imap', "savemessage: date: %s, content: '%s'" % (date, dbg_output)) try: # Select folder for append and make the box READ-WRITE imapobj.select(self.getfullname()) except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid #Do the APPEND try: (typ, dat) = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) retry_left = 0 # Mark as success except imapobj.abort, e: # connection has been reset, release connection and retry. retry_left -= 1 self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() if not retry_left: raise OfflineImapError( "Saving msg in folder '%s', " "repository '%s' failed (abort). Server reponded: %s\n" "Message content was: %s" % (self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE) self.ui.error(e, exc_info()[2]) except imapobj.error, e: # APPEND failed # If the server responds with 'BAD', append() # raise()s directly. So we catch that too. # drop conn, it might be bad. self.imapserver.releaseconnection(imapobj, True) imapobj = None raise OfflineImapError( "Saving msg folder '%s', repo '%s'" "failed (error). Server reponded: %s\nMessage content was: " "%s" % (self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE)
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the folder is read-only it will return 0.""" self.ui.debug('imap', 'savemessage: called') # already have it, just save modified flags if uid > 0 and self.uidexists(uid): self.savemessageflags(uid, flags) return uid try: imapobj = self.imapserver.acquireconnection() try: imapobj.select(self.getfullname()) # Needed for search and making the box READ-WRITE except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid # UIDPLUS extension provides us with an APPENDUID response to our append() use_uidplus = 'UIDPLUS' in imapobj.capabilities # get the date of the message file, so we can pass it to the server. date = self.getmessageinternaldate(content, rtime) self.ui.debug('imap', 'savemessage: using date %s' % date) content = re.sub("(?<!\r)\n", "\r\n", content) if not use_uidplus: # insert a random unique header that we can fetch later (headername, headervalue) = self.generate_randomheader(content) self.ui.debug('imap', 'savemessage: new headers are: %s: %s' % \ (headername, headervalue)) content = self.savemessage_addheader(content, headername, headervalue) self.ui.debug('imap', 'savemessage: content is: ' + repr(content)) # TODO: - append could raise a ValueError if the date is not in # valid format...? (typ,dat) = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) assert(typ == 'OK') # Checkpoint. Let it write out the messages, etc. (typ,dat) = imapobj.check() assert(typ == 'OK') # get the new UID. Test for APPENDUID response even if the # server claims to not support it, as e.g. Gmail does :-( if use_uidplus or imapobj._get_untagged_response('APPENDUID', True): # get the new UID from the APPENDUID response, it could look like # OK [APPENDUID 38505 3955] APPEND completed # with 38505 bein folder UIDvalidity and 3955 the new UID if not imapobj._get_untagged_response('APPENDUID', True): self.ui.warn("Server supports UIDPLUS but got no APPENDUID " "appending a message.") return 0 uid = long(imapobj._get_untagged_response('APPENDUID', True)[-1].split(' ')[1]) else: # we don't support UIDPLUS uid = self.savemessage_searchforheader(imapobj, headername, headervalue) # See docs for savemessage in Base.py for explanation of this and other return values if uid == 0: self.ui.debug('imap', 'savemessage: first attempt to get new UID failed. Going to run a NOOP and try again.') assert(imapobj.noop()[0] == 'OK') uid = self.savemessage_searchforheader(imapobj, headername, headervalue) finally: self.imapserver.releaseconnection(imapobj) if uid: # avoid UID FETCH 0 crash happening later on self.messagelist[uid] = {'uid': uid, 'flags': flags} self.ui.debug('imap', 'savemessage: returning new UID %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. See folder/Base for details. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the message is saved, but it's UID can not be found, it will return 0. If the message can't be written (folder is read-only for example) it will return -1.""" self.ui.savemessage('imap', uid, flags, self) # Already have it, just save modified flags. if uid > 0 and self.uidexists(uid): self.savemessageflags(uid, flags) return uid content = self.deletemessageheaders(content, self.filterheaders) # Use proper CRLF all over the message. content = re.sub("(?<!\r)\n", CRLF, content) # Get the date of the message, so we can pass it to the server. date = self.__getmessageinternaldate(content, rtime) # Message-ID is handy for debugging messages. msg_id = self.getmessageheader(content, "message-id") if not msg_id: msg_id = '[unknown message-id]' retry_left = 2 # succeeded in APPENDING? imapobj = self.imapserver.acquireconnection() # NB: in the finally clause for this try we will release # NB: the acquired imapobj, so don't do that twice unless # NB: you will put another connection to imapobj. If you # NB: really do need to release connection manually, set # NB: imapobj to None. try: while retry_left: # XXX: we can mangle message only once, out of the loop # UIDPLUS extension provides us with an APPENDUID response. use_uidplus = 'UIDPLUS' in imapobj.capabilities if not use_uidplus: # Insert a random unique header that we can fetch later. (headername, headervalue) = self.__generate_randomheader( content) self.ui.debug('imap', 'savemessage: header is: %s: %s'% (headername, headervalue)) content = self.addmessageheader(content, CRLF, headername, headervalue) if len(content)>200: dbg_output = "%s...%s"% (content[:150], content[-50:]) else: dbg_output = content self.ui.debug('imap', "savemessage: date: %s, content: '%s'"% (date, dbg_output)) try: # Select folder for append and make the box READ-WRITE. imapobj.select(self.getfullname()) except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid # Do the APPEND. try: (typ, dat) = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) # This should only catch 'NO' responses since append() # will raise an exception for 'BAD' responses: if typ != 'OK': # For example, Groupwise IMAP server can return something like: # # NO APPEND The 1500 MB storage limit has been exceeded. # # In this case, we should immediately abort the repository sync # and continue with the next account. msg = \ "Saving msg (%s) in folder '%s', repository '%s' failed (abort). " \ "Server responded: %s %s\n"% \ (msg_id, self, self.getrepository(), typ, dat) raise OfflineImapError(msg, OfflineImapError.ERROR.REPO) retry_left = 0 # Mark as success. except imapobj.abort as e: # Connection has been reset, release connection and retry. retry_left -= 1 self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() if not retry_left: six.reraise(OfflineImapError, OfflineImapError("Saving msg (%s) in folder '%s', " "repository '%s' failed (abort). Server responded: %s\n" "Message content was: %s"% (msg_id, self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE), exc_info()[2]) # XXX: is this still needed? self.ui.error(e, exc_info()[2]) except imapobj.error as e: # APPEND failed # If the server responds with 'BAD', append() # raise()s directly. So we catch that too. # drop conn, it might be bad. self.imapserver.releaseconnection(imapobj, True) imapobj = None six.reraise(OfflineImapError, OfflineImapError("Saving msg (%s) folder '%s', repo '%s'" "failed (error). Server responded: %s\nMessage content was: " "%s"% (msg_id, self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE), exc_info()[2]) # Checkpoint. Let it write out stuff, etc. Eg searches for # just uploaded messages won't work if we don't do this. (typ,dat) = imapobj.check() assert(typ == 'OK') # Get the new UID, do we use UIDPLUS? if use_uidplus: # Get new UID from the APPENDUID response, it could look # like OK [APPENDUID 38505 3955] APPEND completed with # 38505 bein folder UIDvalidity and 3955 the new UID. # note: we would want to use .response() here but that # often seems to return [None], even though we have # data. TODO resp = imapobj._get_untagged_response('APPENDUID') if resp == [None] or resp is None: self.ui.warn("Server supports UIDPLUS but got no APPENDUID " "appending a message.") return 0 uid = int(resp[-1].split(' ')[1]) if uid == 0: self.ui.warn("savemessage: Server supports UIDPLUS, but" " we got no usable uid back. APPENDUID reponse was " "'%s'"% str(resp)) else: # We don't support UIDPLUS. uid = self.__savemessage_searchforheader(imapobj, headername, headervalue) # See docs for savemessage in Base.py for explanation # of this and other return values. if uid == 0: self.ui.debug('imap', 'savemessage: attempt to get new UID ' 'UID failed. Search headers manually.') uid = self.__savemessage_fetchheaders(imapobj, headername, headervalue) self.ui.warn('imap', "savemessage: Searching mails for new " "Message-ID failed. Could not determine new UID.") finally: if imapobj: self.imapserver.releaseconnection(imapobj) if uid: # Avoid UID FETCH 0 crash happening later on self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid]['flags'] = flags self.ui.debug('imap', 'savemessage: returning new UID %d'% uid) return uid
def savemessage(self, uid, content, flags, rtime): imapobj = self.imapserver.acquireconnection() ui = UIBase.getglobalui() ui.debug('imap', 'savemessage: called') try: try: imapobj.select(self.getfullname()) # Needed for search except imapobj.readonly: ui.msgtoreadonly(self, uid, content, flags) # Return indicating message taken, but no UID assigned. # Fudge it. return 0 # This backend always assigns a new uid, so the uid arg is ignored. # In order to get the new uid, we need to save off the message ID. message = rfc822.Message(StringIO(content)) datetuple_msg = rfc822.parsedate(message.getheader('Date')) # Will be None if missing or not in a valid format. # If time isn't known if rtime == None and datetuple_msg == None: datetuple = time.localtime() elif rtime == None: datetuple = datetuple_msg else: datetuple = time.localtime(rtime) try: if datetuple[0] < 1981: raise ValueError # Check for invalid date datetuple_check = time.localtime(time.mktime(datetuple)) if datetuple[:2] != datetuple_check[:2]: raise ValueError # This could raise a value error if it's not a valid format. date = imaplib.Time2Internaldate(datetuple) except (ValueError, OverflowError): # Argh, sometimes it's a valid format but year is 0102 # or something. Argh. It seems that Time2Internaldate # will rause a ValueError if the year is 0102 but not 1902, # but some IMAP servers nonetheless choke on 1902. date = imaplib.Time2Internaldate(time.localtime()) ui.debug('imap', 'savemessage: using date ' + str(date)) content = re.sub("(?<!\r)\n", "\r\n", content) ui.debug('imap', 'savemessage: initial content is: ' + repr(content)) (headername, headervalue) = self.savemessage_getnewheader(content) ui.debug('imap', 'savemessage: new headers are: %s: %s' % \ (headername, headervalue)) content = self.savemessage_addheader(content, headername, headervalue) ui.debug('imap', 'savemessage: new content is: ' + repr(content)) ui.debug('imap', 'savemessage: new content length is ' + \ str(len(content))) assert(imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content)[0] == 'OK') # Checkpoint. Let it write out the messages, etc. assert(imapobj.check()[0] == 'OK') # Keep trying until we get the UID. ui.debug('imap', 'savemessage: first attempt to get new UID') uid = self.savemessage_searchforheader(imapobj, headername, headervalue) # See docs for savemessage in Base.py for explanation of this and other return values if uid <= 0: ui.debug('imap', 'savemessage: first attempt to get new UID failed. Going to run a NOOP and try again.') assert(imapobj.noop()[0] == 'OK') uid = self.savemessage_searchforheader(imapobj, headername, headervalue) finally: self.imapserver.releaseconnection(imapobj) if uid: # avoid UID FETCH 0 crash happening later on self.messagelist[uid] = {'uid': uid, 'flags': flags} ui.debug('imap', 'savemessage: returning %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. See folder/Base for details. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the message is saved, but it's UID can not be found, it will return 0. If the message can't be written (folder is read-only for example) it will return -1.""" self.ui.savemessage('imap', uid, flags, self) # already have it, just save modified flags if uid > 0 and self.uidexists(uid): self.savemessageflags(uid, flags) return uid retry_left = 2 # succeeded in APPENDING? imapobj = self.imapserver.acquireconnection() try: while retry_left: # UIDPLUS extension provides us with an APPENDUID response. use_uidplus = 'UIDPLUS' in imapobj.capabilities # get the date of the message, so we can pass it to the server. date = self.getmessageinternaldate(content, rtime) content = re.sub("(?<!\r)\n", "\r\n", content) if not use_uidplus: # insert a random unique header that we can fetch later (headername, headervalue) = self.generate_randomheader( content) self.ui.debug('imap', 'savemessage: header is: %s: %s' %\ (headername, headervalue)) content = self.savemessage_addheader(content, headername, headervalue) if len(content)>200: dbg_output = "%s...%s" % (content[:150], content[-50:]) else: dbg_output = content self.ui.debug('imap', "savemessage: date: %s, content: '%s'" % (date, dbg_output)) try: # Select folder for append and make the box READ-WRITE imapobj.select(self.getfullname()) except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid #Do the APPEND try: (typ, dat) = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) retry_left = 0 # Mark as success except imapobj.abort as e: # connection has been reset, release connection and retry. retry_left -= 1 self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() if not retry_left: raise OfflineImapError("Saving msg in folder '%s', " "repository '%s' failed (abort). Server reponded: %s\n" "Message content was: %s" % (self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE) self.ui.error(e, exc_info()[2]) except imapobj.error as e: # APPEND failed # If the server responds with 'BAD', append() # raise()s directly. So we catch that too. # drop conn, it might be bad. self.imapserver.releaseconnection(imapobj, True) imapobj = None raise OfflineImapError("Saving msg folder '%s', repo '%s'" "failed (error). Server reponded: %s\nMessage content was: " "%s" % (self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE) # Checkpoint. Let it write out stuff, etc. Eg searches for # just uploaded messages won't work if we don't do this. (typ,dat) = imapobj.check() assert(typ == 'OK') # get the new UID, do we use UIDPLUS? if use_uidplus: # get new UID from the APPENDUID response, it could look # like OK [APPENDUID 38505 3955] APPEND completed with # 38505 bein folder UIDvalidity and 3955 the new UID. # note: we would want to use .response() here but that # often seems to return [None], even though we have # data. TODO resp = imapobj._get_untagged_response('APPENDUID') if resp == [None] or resp is None: self.ui.warn("Server supports UIDPLUS but got no APPENDUID " "appending a message.") return 0 uid = long(resp[-1].split(' ')[1]) if uid == 0: self.ui.warn("savemessage: Server supports UIDPLUS, but" " we got no usable uid back. APPENDUID reponse was " "'%s'" % str(resp)) else: # we don't support UIDPLUS uid = self.savemessage_searchforheader(imapobj, headername, headervalue) # See docs for savemessage in Base.py for explanation # of this and other return values if uid == 0: self.ui.debug('imap', 'savemessage: attempt to get new UID ' 'UID failed. Search headers manually.') uid = self.savemessage_fetchheaders(imapobj, headername, headervalue) self.ui.warn('imap', "savemessage: Searching mails for new " "Message-ID failed. Could not determine new UID.") finally: self.imapserver.releaseconnection(imapobj) if uid: # avoid UID FETCH 0 crash happening later on self.messagelist[uid] = {'uid': uid, 'flags': flags} self.ui.debug('imap', 'savemessage: returning new UID %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Save the message on the Server This backend always assigns a new uid, so the uid arg is ignored. This function will update the self.messagelist dict to contain the new message after sucessfully saving it. :param rtime: A timestamp to be used as the mail date :returns: the UID of the new message as assigned by the server. If the message is saved, but it's UID can not be found, it will return 0. If the message can't be written (folder is read-only for example) it will return -1.""" self.ui.debug('imap', 'savemessage: called') # already have it, just save modified flags if uid > 0 and self.uidexists(uid): self.savemessageflags(uid, flags) return uid retry_left = 2 # succeeded in APPENDING? imapobj = self.imapserver.acquireconnection() try: while retry_left: # UIDPLUS extension provides us with an APPENDUID response. use_uidplus = 'UIDPLUS' in imapobj.capabilities # get the date of the message, so we can pass it to the server. date = self.getmessageinternaldate(content, rtime) content = re.sub("(?<!\r)\n", "\r\n", content) if not use_uidplus: # insert a random unique header that we can fetch later (headername, headervalue) = self.generate_randomheader( content) self.ui.debug('imap', 'savemessage: header is: %s: %s' %\ (headername, headervalue)) content = self.savemessage_addheader(content, headername, headervalue) if len(content)>200: dbg_output = "%s...%s" % (content[:150], content[-50:]) else: dbg_output = content self.ui.debug('imap', "savemessage: date: %s, content: '%s'" % (date, dbg_output)) try: # Select folder for append and make the box READ-WRITE imapobj.select(self.getfullname()) except imapobj.readonly: # readonly exception. Return original uid to notify that # we did not save the message. (see savemessage in Base.py) self.ui.msgtoreadonly(self, uid, content, flags) return uid # Clean out existing APPENDUID responses and do APPEND try: imapobj.response('APPENDUID') # flush APPENDUID responses typ, dat = imapobj.append(self.getfullname(), imaputil.flagsmaildir2imap(flags), date, content) retry_left = 0 # Mark as success except imapobj.abort, e: # connection has been reset, release connection and retry. retry_left -= 1 self.imapserver.releaseconnection(imapobj, True) imapobj = self.imapserver.acquireconnection() if not retry_left: raise OfflineImapError("Saving msg in folder '%s', " "repository '%s' failed (abort). Server reponded: %s\n" "Message content was: %s" % (self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE) self.ui.error(e, exc_info()[2]) except imapobj.error, e: # APPEND failed # If the server responds with 'BAD', append() # raise()s directly. So we catch that too. # drop conn, it might be bad. self.imapserver.releaseconnection(imapobj, True) imapobj = None raise OfflineImapError("Saving msg folder '%s', repo '%s'" "failed (error). Server reponded: %s\nMessage content was: " "%s" % (self, self.getrepository(), str(e), dbg_output), OfflineImapError.ERROR.MESSAGE)