Example #1
0
    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)
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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)
Example #5
0
    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
Example #6
0
    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