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.flagset2flagstring(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.flagstring2flagset(flags)
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.flagset2flagstring(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.flagstring2flagset( 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(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.flagset2flagstring(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.flagstring2flagset(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): """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.flagset2flagstring(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.flagstring2flagset(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 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.flagset2flagstring(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 in folder '%s', repository '%s' failed (abort). " \ "Server responded: %s %s\n" % \ (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: raise OfflineImapError( "Saving msg in folder '%s', " "repository '%s' failed (abort). Server responded: %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 responded: %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. 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.flagset2flagstring(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 in folder '%s', repository '%s' failed (abort). " \ "Server responded: %s %s\n" % \ (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: raise OfflineImapError("Saving msg in folder '%s', " "repository '%s' failed (abort). Server responded: %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 responded: %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