def change_message_uid(self, uid, new_uid): """Change the message from existing uid to new_uid This will not update the statusfolder UID, you need to do that yourself. :param new_uid: (optional) If given, the old UID will be changed to a new UID. The Maildir backend can implement this as an efficient rename. """ if not uid in self.messagelist: raise OfflineImapError("Cannot change unknown Maildir UID %s" % uid) if uid == new_uid: return oldfilename = self.messagelist[uid]['filename'] dir_prefix, filename = os.path.split(oldfilename) flags = self.getmessageflags(uid) content = self.getmessage(uid) rtime = emailutil.get_message_date(content) newfilename = os.path.join( dir_prefix, self.new_message_filename(new_uid, flags, rtime=rtime)) os.rename(os.path.join(self.getfullname(), oldfilename), os.path.join(self.getfullname(), newfilename)) self.messagelist[new_uid] = self.messagelist[uid] self.messagelist[new_uid]['filename'] = newfilename del self.messagelist[uid]
def change_message_uid(self, uid, new_uid): """Change the message from existing uid to new_uid This will not update the statusfolder UID, you need to do that yourself. :param new_uid: (optional) If given, the old UID will be changed to a new UID. The Maildir backend can implement this as an efficient rename. """ if not uid in self.messagelist: raise OfflineImapError("Cannot change unknown Maildir UID %s"% uid) if uid == new_uid: return oldfilename = self.messagelist[uid]['filename'] dir_prefix, filename = os.path.split(oldfilename) flags = self.getmessageflags(uid) content = self.getmessage(uid) rtime = emailutil.get_message_date(content) newfilename = os.path.join(dir_prefix, self.new_message_filename(new_uid, flags, rtime=rtime)) os.rename(os.path.join(self.getfullname(), oldfilename), os.path.join(self.getfullname(), newfilename)) self.messagelist[new_uid] = self.messagelist[uid] self.messagelist[new_uid]['filename'] = newfilename del self.messagelist[uid]
def savemessage(self, uid, content, flags, rtime): """Writes a new message, with the specified uid. See folder/Base for detail. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode.""" # This function only ever saves to tmp/, # but it calls savemessageflags() to actually save to cur/ or new/. self.ui.savemessage('maildir', uid, flags, self) if uid < 0: # We cannot assign a new uid. return uid if uid in self.messagelist: # We already have it, just update flags. self.savemessageflags(uid, flags) return uid # Otherwise, save the message in tmp/ and then call savemessageflags() # to give it a permanent home. tmpdir = os.path.join(self.getfullname(), 'tmp') messagename = self.new_message_filename(uid, flags) tmpname = self.save_to_tmp_file(messagename, content) if self.utime_from_header: try: date = emailutil.get_message_date(content, 'Date') if date is not None: os.utime(os.path.join(self.getfullname(), tmpname), (date, date)) # In case date is wrongly so far into the future as to be > max int32 except Exception as e: from email.Parser import Parser from offlineimap.ui import getglobalui datestr = Parser().parsestr(content, True).get("Date") ui = getglobalui() ui.warn("UID %d has invalid date %s: %s\n" "Not changing file modification time" % (uid, datestr, e)) self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid]['flags'] = flags self.messagelist[uid]['filename'] = tmpname # savemessageflags moves msg to 'cur' or 'new' as appropriate self.savemessageflags(uid, flags) self.ui.debug('maildir', 'savemessage: returning uid %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Writes a new message, with the specified uid. See folder/Base for detail. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode.""" # This function only ever saves to tmp/, # but it calls savemessageflags() to actually save to cur/ or new/. self.ui.savemessage("maildir", uid, flags, self) if uid < 0: # We cannot assign a new uid. return uid if uid in self.messagelist: # We already have it, just update flags. self.savemessageflags(uid, flags) return uid # Otherwise, save the message in tmp/ and then call savemessageflags() # to give it a permanent home. tmpdir = os.path.join(self.getfullname(), "tmp") messagename = self.new_message_filename(uid, flags) tmpname = self.save_to_tmp_file(messagename, content) if self.utime_from_header: try: date = emailutil.get_message_date(content, "Date") if date is not None: os.utime(os.path.join(self.getfullname(), tmpname), (date, date)) # In case date is wrongly so far into the future as to be > max int32 except Exception as e: from email.Parser import Parser from offlineimap.ui import getglobalui datestr = Parser().parsestr(content, True).get("Date") ui = getglobalui() ui.warn("UID %d has invalid date %s: %s\n" "Not changing file modification time" % (uid, datestr, e)) self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid]["flags"] = flags self.messagelist[uid]["filename"] = tmpname # savemessageflags moves msg to 'cur' or 'new' as appropriate self.savemessageflags(uid, flags) self.ui.debug("maildir", "savemessage: returning uid %d" % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Writes a new message, with the specified uid. See folder/Base for detail. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode.""" # This function only ever saves to tmp/, # but it calls savemessageflags() to actually save to cur/ or new/. self.ui.savemessage('maildir', uid, flags, self) if uid < 0: # We cannot assign a new uid. return uid if uid in self.messagelist: # We already have it, just update flags. self.savemessageflags(uid, flags) return uid # Otherwise, save the message in tmp/ and then call savemessageflags() # to give it a permanent home. tmpdir = os.path.join(self.getfullname(), 'tmp') # Make sure rtime is the internal date, so it can be put in the filename if rtime is None: rtime = emailutil.get_message_date(content) messagename = self.new_message_filename(uid, flags, rtime=rtime) tmpname = self.save_to_tmp_file(messagename, content) if rtime != None: os.utime(os.path.join(self.getfullname(), tmpname), (rtime, rtime)) self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid]['flags'] = flags self.messagelist[uid]['filename'] = tmpname # savemessageflags moves msg to 'cur' or 'new' as appropriate self.savemessageflags(uid, flags) self.ui.debug('maildir', 'savemessage: returning uid %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Writes a new message, with the specified uid. See folder/Base for detail. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode.""" # This function only ever saves to tmp/, # but it calls savemessageflags() to actually save to cur/ or new/. self.ui.savemessage('maildir', uid, flags, self) if uid < 0: # We cannot assign a new uid. return uid if uid in self.messagelist: # We already have it, just update flags. self.savemessageflags(uid, flags) return uid # Otherwise, save the message in tmp/ and then call savemessageflags() # to give it a permanent home. tmpdir = os.path.join(self.getfullname(), 'tmp') messagename = self.new_message_filename(uid, flags) tmpname = self.save_to_tmp_file(messagename, content) if self.utime_from_header: date = emailutil.get_message_date(content, 'Date') if date != None: os.utime(os.path.join(self.getfullname(), tmpname), (date, date)) self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid]['flags'] = flags self.messagelist[uid]['filename'] = tmpname # savemessageflags moves msg to 'cur' or 'new' as appropriate self.savemessageflags(uid, flags) self.ui.debug('maildir', 'savemessage: returning uid %d' % uid) return uid
def savemessage(self, uid, content, flags, rtime): """Writes a new message, with the specified uid. See folder/Base for detail. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode.""" # This function only ever saves to tmp/, # but it calls savemessageflags() to actually save to cur/ or new/. self.ui.savemessage('maildir', uid, flags, self) if uid < 0: # We cannot assign a new uid. return uid if uid in self.messagelist: # We already have it, just update flags. self.savemessageflags(uid, flags) return uid # Use the mail timestamp given by either Date or Delivery-date mail # headers. message_timestamp = None if self._filename_use_mail_timestamp is not False: try: message_timestamp = emailutil.get_message_date(content, 'Date') if message_timestamp is None: # Give a try with Delivery-date message_timestamp = emailutil.get_message_date( content, 'Delivery-date') except Exception as e: # This should never happen. from offlineimap.ui import getglobalui datestr = emailutil.get_message_date(content) ui = getglobalui() ui.warn("UID %d has invalid date %s: %s\n" "Not using message timestamp as file prefix" % (uid, datestr, e)) # No need to check if message_timestamp is None here since it # would be overridden by _gettimeseq. messagename = self.new_message_filename(uid, flags, date=message_timestamp) tmpname = self.save_to_tmp_file(messagename, content) if self._utime_from_header is True: try: date = emailutil.get_message_date(content, 'Date') if date is not None: os.utime(os.path.join(self.getfullname(), tmpname), (date, date)) # In case date is wrongly so far into the future as to be > max # int32. except Exception as e: from offlineimap.ui import getglobalui datestr = emailutil.get_message_date(content) ui = getglobalui() ui.warn("UID %d has invalid date %s: %s\n" "Not changing file modification time" % (uid, datestr, e)) self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid]['flags'] = flags self.messagelist[uid]['filename'] = tmpname # savemessageflags moves msg to 'cur' or 'new' as appropriate. self.savemessageflags(uid, flags) self.ui.debug('maildir', 'savemessage: returning uid %d' % uid) return uid
def copymessageto(self, uid, dstfolder, statusfolder, register = 1): """Copies a message from self to dst if needed, updating the status Note that this function does not check against dryrun settings, so you need to ensure that it is never called in a dryrun mode. :param uid: uid of the message to be copied. :param dstfolder: A BaseFolder-derived instance :param statusfolder: A LocalStatusFolder instance :param register: whether we should register a new thread." :returns: Nothing on success, or raises an Exception.""" # Sometimes, it could be the case that if a sync takes awhile, # a message might be deleted from the maildir before it can be # synced to the status cache. This is only a problem with # self.getmessage(). So, don't call self.getmessage unless # really needed. if register: # output that we start a new thread self.ui.registerthread(self.repository.account) try: message = None flags = self.getmessageflags(uid) rtime = self.getmessagetime(uid) if dstfolder.utime_from_message: content = self.getmessage(uid) rtime = emailutil.get_message_date(content, 'Date') if uid > 0 and dstfolder.uidexists(uid): # dst has message with that UID already, only update status statusfolder.savemessage(uid, None, flags, rtime) return # If any of the destinations actually stores the message body, # load it up. if dstfolder.storesmessages(): message = self.getmessage(uid) #Succeeded? -> IMAP actually assigned a UID. If newid #remained negative, no server was willing to assign us an #UID. If newid is 0, saving succeeded, but we could not #retrieve the new UID. Ignore message in this case. new_uid = dstfolder.savemessage(uid, message, flags, rtime) if new_uid > 0: if new_uid != uid: # Got new UID, change the local uid to match the new one. self.change_message_uid(uid, new_uid) statusfolder.deletemessage(uid) # Got new UID, change the local uid. # Save uploaded status in the statusfolder statusfolder.savemessage(new_uid, message, flags, rtime) elif new_uid == 0: # Message was stored to dstfolder, but we can't find it's UID # This means we can't link current message to the one created # in IMAP. So we just delete local message and on next run # we'll sync it back # XXX This could cause infinite loop on syncing between two # IMAP servers ... self.deletemessage(uid) else: raise OfflineImapError("Trying to save msg (uid %d) on folder " "%s returned invalid uid %d" % (uid, dstfolder.getvisiblename(), new_uid), OfflineImapError.ERROR.MESSAGE) except (KeyboardInterrupt): # bubble up CTRL-C raise except OfflineImapError as e: if e.severity > OfflineImapError.ERROR.MESSAGE: raise # bubble severe errors up self.ui.error(e, exc_info()[2]) except Exception as e: self.ui.error(e, exc_info()[2], msg="Copying message %s [acc: %s]" %\ (uid, self.accountname)) raise #raise on unknown errors, so we can fix those
def __getmessageinternaldate(self, content, rtime=None): """Parses mail and returns an INTERNALDATE string It will use information in the following order, falling back as an attempt fails: - rtime parameter - Date header of email We return None, if we couldn't find a valid date. In this case the IMAP server will use the server local time when appening (per RFC). Note, that imaplib's Time2Internaldate is inherently broken as it returns localized date strings which are invalid for IMAP servers. However, that function is called for *every* append() internally. So we need to either pass in `None` or the correct string (in which case Time2Internaldate() will do nothing) to append(). The output of this function is designed to work as input to the imapobj.append() function. TODO: We should probably be returning a bytearray rather than a string here, because the IMAP server will expect plain ASCII. However, imaplib.Time2INternaldate currently returns a string so we go with the same for now. :param rtime: epoch timestamp to be used rather than analyzing the email. :returns: string in the form of "DD-Mmm-YYYY HH:MM:SS +HHMM" (including double quotes) or `None` in case of failure (which is fine as value for append).""" if rtime is None: rtime = emailutil.get_message_date(content) if rtime == None: return None datetuple = time.localtime(rtime) try: # Check for invalid dates. if datetuple[0] < 1981: raise ValueError # Check for invalid dates. datetuple_check = time.localtime(time.mktime(datetuple)) if datetuple[:2] != datetuple_check[:2]: raise ValueError 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. self.ui.debug('imap', "Message with invalid date %s. " "Server will use local time."% datetuple) return None # Produce a string representation of datetuple that works as # INTERNALDATE. num2mon = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul', 8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'} # tm_isdst coming from email.parsedate is not usable, we still use it # here, mhh. if datetuple.tm_isdst == 1: zone = -time.altzone else: zone = -time.timezone offset_h, offset_m = divmod(zone//60, 60) internaldate = '"%02d-%s-%04d %02d:%02d:%02d %+03d%02d"'% \ (datetuple.tm_mday, num2mon[datetuple.tm_mon], datetuple.tm_year, \ datetuple.tm_hour, datetuple.tm_min, datetuple.tm_sec, offset_h, offset_m) return internaldate
def __getmessageinternaldate(self, content, rtime=None): """Parses mail and returns an INTERNALDATE string It will use information in the following order, falling back as an attempt fails: - rtime parameter - Date header of email We return None, if we couldn't find a valid date. In this case the IMAP server will use the server local time when appening (per RFC). Note, that imaplib's Time2Internaldate is inherently broken as it returns localized date strings which are invalid for IMAP servers. However, that function is called for *every* append() internally. So we need to either pass in `None` or the correct string (in which case Time2Internaldate() will do nothing) to append(). The output of this function is designed to work as input to the imapobj.append() function. TODO: We should probably be returning a bytearray rather than a string here, because the IMAP server will expect plain ASCII. However, imaplib.Time2INternaldate currently returns a string so we go with the same for now. :param rtime: epoch timestamp to be used rather than analyzing the email. :returns: string in the form of "DD-Mmm-YYYY HH:MM:SS +HHMM" (including double quotes) or `None` in case of failure (which is fine as value for append).""" if rtime is None: rtime = emailutil.get_message_date(content) if rtime == None: return None datetuple = time.localtime(rtime) try: # Check for invalid dates. if datetuple[0] < 1981: raise ValueError # Check for invalid dates. datetuple_check = time.localtime(time.mktime(datetuple)) if datetuple[:2] != datetuple_check[:2]: raise ValueError 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. self.ui.debug( 'imap', "Message with invalid date %s. " "Server will use local time." % datetuple) return None # Produce a string representation of datetuple that works as # INTERNALDATE. num2mon = { 1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' } # tm_isdst coming from email.parsedate is not usable, we still use it # here, mhh. if datetuple.tm_isdst == 1: zone = -time.altzone else: zone = -time.timezone offset_h, offset_m = divmod(zone // 60, 60) internaldate = '"%02d-%s-%04d %02d:%02d:%02d %+03d%02d"'% \ (datetuple.tm_mday, num2mon[datetuple.tm_mon], datetuple.tm_year, \ datetuple.tm_hour, datetuple.tm_min, datetuple.tm_sec, offset_h, offset_m) return internaldate
def copymessageto(self, uid, dstfolder, statusfolder, register = 1): """Copies a message from self to dst if needed, updating the status Note that this function does not check against dryrun settings, so you need to ensure that it is never called in a dryrun mode. :param uid: uid of the message to be copied. :param dstfolder: A BaseFolder-derived instance :param statusfolder: A LocalStatusFolder instance :param register: whether we should register a new thread." :returns: Nothing on success, or raises an Exception.""" # Sometimes, it could be the case that if a sync takes awhile, # a message might be deleted from the maildir before it can be # synced to the status cache. This is only a problem with # self.getmessage(). So, don't call self.getmessage unless # really needed. if register: # output that we start a new thread self.ui.registerthread(self.repository.account) try: message = None flags = self.getmessageflags(uid) rtime = self.getmessagetime(uid) if dstfolder.utime_from_message: content = self.getmessage(uid) rtime = emailutil.get_message_date(content, 'Date') if uid > 0 and dstfolder.uidexists(uid): # dst has message with that UID already, only update status statusfolder.savemessage(uid, None, flags, rtime) return # If any of the destinations actually stores the message body, # load it up. if dstfolder.storesmessages(): message = self.getmessage(uid) # Succeeded? -> IMAP actually assigned a UID. If newid # remained negative, no server was willing to assign us an # UID. If newid is 0, saving succeeded, but we could not # retrieve the new UID. Ignore message in this case. new_uid = dstfolder.savemessage(uid, message, flags, rtime) if new_uid > 0: if new_uid != uid: # Got new UID, change the local uid to match the new one. self.change_message_uid(uid, new_uid) statusfolder.deletemessage(uid) # Got new UID, change the local uid. # Save uploaded status in the statusfolder statusfolder.savemessage(new_uid, message, flags, rtime) elif new_uid == 0: # Message was stored to dstfolder, but we can't find it's UID # This means we can't link current message to the one created # in IMAP. So we just delete local message and on next run # we'll sync it back # XXX This could cause infinite loop on syncing between two # IMAP servers ... self.deletemessage(uid) else: raise OfflineImapError("Trying to save msg (uid %d) on folder " "%s returned invalid uid %d"% (uid, dstfolder.getvisiblename(), new_uid), OfflineImapError.ERROR.MESSAGE) except (KeyboardInterrupt): # bubble up CTRL-C raise except OfflineImapError as e: if e.severity > OfflineImapError.ERROR.MESSAGE: raise # bubble severe errors up self.ui.error(e, exc_info()[2]) except Exception as e: self.ui.error(e, exc_info()[2], msg = "Copying message %s [acc: %s]"% (uid, self.accountname)) raise #raise on unknown errors, so we can fix those
def savemessage(self, uid, content, flags, rtime): """Writes a new message, with the specified uid. See folder/Base for detail. Note that savemessage() does not check against dryrun settings, so you need to ensure that savemessage is never called in a dryrun mode.""" # This function only ever saves to tmp/, # but it calls savemessageflags() to actually save to cur/ or new/. self.ui.savemessage('maildir', uid, flags, self) if uid < 0: # We cannot assign a new uid. return uid if uid in self.messagelist: # We already have it, just update flags. self.savemessageflags(uid, flags) return uid # Otherwise, save the message in tmp/ and then call savemessageflags() # to give it a permanent home. tmpdir = os.path.join(self.getfullname(), 'tmp') # use the mail timestamp given by either Date or Delivery-date mail # headers. message_timestamp = None if self._filename_use_mail_timestamp: try: message_timestamp = emailutil.get_message_date(content, 'Date') if message_timestamp is None: # Give a try with Delivery-date date = emailutil.get_message_date(content, 'Delivery-date') except: # This should never happen from email.Parser import Parser from offlineimap.ui import getglobalui datestr = Parser().parsestr(content, True).get("Date") ui = getglobalui() ui.warn("UID %d has invalid date %s: %s\n" "Not using message timestamp as file prefix" % (uid, datestr, e)) # No need to check if date is None here since it would # be overridden by _gettimeseq. messagename = self.new_message_filename(uid, flags, date=message_timestamp) tmpname = self.save_to_tmp_file(messagename, content) if self.utime_from_header: try: date = emailutil.get_message_date(content, 'Date') if date is not None: os.utime(os.path.join(self.getfullname(), tmpname), (date, date)) # In case date is wrongly so far into the future as to be > max int32 except Exception as e: from email.Parser import Parser from offlineimap.ui import getglobalui datestr = Parser().parsestr(content, True).get("Date") ui = getglobalui() ui.warn("UID %d has invalid date %s: %s\n" "Not changing file modification time" % (uid, datestr, e)) self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid]['flags'] = flags self.messagelist[uid]['filename'] = tmpname # savemessageflags moves msg to 'cur' or 'new' as appropriate self.savemessageflags(uid, flags) self.ui.debug('maildir', 'savemessage: returning uid %d' % uid) return uid