Merge branch 'next'
This commit is contained in:
commit
d653c6a404
@ -11,6 +11,22 @@ ChangeLog
|
|||||||
on releases. And because I'm lazy, it will also be used as a draft for the
|
on releases. And because I'm lazy, it will also be used as a draft for the
|
||||||
releases announces.
|
releases announces.
|
||||||
|
|
||||||
|
OfflineIMAP v6.5.0 (2012-01-06)
|
||||||
|
===============================
|
||||||
|
|
||||||
|
This is a CRITICAL bug fix release for everyone who is on the 6.4.x series. Please upgrade to avoid potential data loss! The version has been bumped to 6.5.0, please let everyone know that the 6.4.x series is problematic.
|
||||||
|
|
||||||
|
* Uploading multiple emails to an IMAP server would lead to wrong UIDs
|
||||||
|
being returned (ie the same for all), which confused offlineimap and
|
||||||
|
led to recurrent upload/download loops and inconsistencies in the
|
||||||
|
IMAP<->IMAP uid mapping.
|
||||||
|
|
||||||
|
* Uploading of Messages from Maildir and IMAP<->IMAP has been made more
|
||||||
|
efficient by renaming files/mapping entries, rather than actually
|
||||||
|
loading and saving the message under a new UID.
|
||||||
|
|
||||||
|
* Fix regression that broke MachineUI
|
||||||
|
|
||||||
OfflineIMAP v6.4.4 (2012-01-06)
|
OfflineIMAP v6.4.4 (2012-01-06)
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
|
@ -110,3 +110,23 @@ within the sample file.
|
|||||||
`OfflineIMAP`_ also ships a file named `offlineimap.conf.minimal` that you can
|
`OfflineIMAP`_ also ships a file named `offlineimap.conf.minimal` that you can
|
||||||
also try. It's useful if you want to get started with the most basic feature
|
also try. It's useful if you want to get started with the most basic feature
|
||||||
set, and you can read about other features later with `offlineimap.conf`.
|
set, and you can read about other features later with `offlineimap.conf`.
|
||||||
|
|
||||||
|
|
||||||
|
===============
|
||||||
|
Uninstall
|
||||||
|
===============
|
||||||
|
|
||||||
|
If you installed a system-wide installation via "python setup.py
|
||||||
|
install", there are a few files to purge to uninstall it again. I assume
|
||||||
|
that /usr/local is the standard prefix that your system and you use
|
||||||
|
python 2.7. Adapt to your system. In that case you need to:
|
||||||
|
|
||||||
|
1) Delete:
|
||||||
|
/usr/local/lib/python2.7/dist-packages/offlineimap-6.4.4.egg-info
|
||||||
|
/usr/local/lib/python2.7/dist-packages/offlineimap
|
||||||
|
|
||||||
|
2) Delete the cache at (default location) ~/.offlineimap
|
||||||
|
Delete your manually created (default loc) ~/.offlineimaprc
|
||||||
|
(It is possible that you created those in different spots)
|
||||||
|
|
||||||
|
That's it. Have fun without OfflineImap.
|
||||||
|
@ -305,6 +305,25 @@ achieve this.
|
|||||||
to hit the disk before continueing, you can set this to True. If you
|
to hit the disk before continueing, you can set this to True. If you
|
||||||
set it to False, you lose some of that safety, trading it for speed.
|
set it to False, you lose some of that safety, trading it for speed.
|
||||||
|
|
||||||
|
|
||||||
|
Upgrading from plain text cache to SQLITE based cache
|
||||||
|
=====================================================
|
||||||
|
|
||||||
|
OfflineImap uses a cache to store the last know status of mails (flags etc). Historically that has meant plain text files, but recently we introduced sqlite-based cache, which helps with performance and CPU usage on large folders. Here is how to upgrade existing plain text cache installations to sqlite based one:
|
||||||
|
|
||||||
|
1) Sync to make sure things are reasonably similar
|
||||||
|
3) Change the account section to status_backend = sqlite
|
||||||
|
4) A new sync will convert your plain text cache to an sqlite cache (but
|
||||||
|
leave the old plain text cache around for easy reverting)
|
||||||
|
This should be quick and not involve any mail up/downloading.
|
||||||
|
5) See if it works :-)
|
||||||
|
6a) If it does not work, go back to the old version or set
|
||||||
|
status_backend=plain
|
||||||
|
6b) Or once you are sure it works, you can delete the
|
||||||
|
.offlineimap/Account-foo/LocalStatus folder (the new cache will be in
|
||||||
|
the LocalStatus-sqlite folder)
|
||||||
|
|
||||||
|
|
||||||
Security and SSL
|
Security and SSL
|
||||||
================
|
================
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
__all__ = ['OfflineImap']
|
__all__ = ['OfflineImap']
|
||||||
|
|
||||||
__productname__ = 'OfflineIMAP'
|
__productname__ = 'OfflineIMAP'
|
||||||
__version__ = "6.4.4"
|
__version__ = "6.5.0"
|
||||||
__copyright__ = "Copyright 2002-2012 John Goerzen & contributors"
|
__copyright__ = "Copyright 2002-2012 John Goerzen & contributors"
|
||||||
__author__ = "John Goerzen"
|
__author__ = "John Goerzen"
|
||||||
__author_email__= "john@complete.org"
|
__author_email__= "john@complete.org"
|
||||||
|
@ -37,13 +37,14 @@ class BaseFolder(object):
|
|||||||
self.sync_this = True
|
self.sync_this = True
|
||||||
"""Should this folder be included in syncing?"""
|
"""Should this folder be included in syncing?"""
|
||||||
self.ui = getglobalui()
|
self.ui = getglobalui()
|
||||||
self.name = name
|
# Top level dir name is always ''
|
||||||
|
self.name = name if not name == self.getsep() else ''
|
||||||
self.repository = repository
|
self.repository = repository
|
||||||
self.visiblename = repository.nametrans(name)
|
self.visiblename = repository.nametrans(name)
|
||||||
# In case the visiblename becomes '.' (top-level) we use '' as
|
# In case the visiblename becomes '.' or '/' (top-level) we use
|
||||||
# that is the name that e.g. the Maildir scanning will return
|
# '' as that is the name that e.g. the Maildir scanning will
|
||||||
# for the top-level dir.
|
# return for the top-level dir.
|
||||||
if self.visiblename == '.':
|
if self.visiblename == self.getsep():
|
||||||
self.visiblename = ''
|
self.visiblename = ''
|
||||||
self.config = repository.getconfig()
|
self.config = repository.getconfig()
|
||||||
|
|
||||||
@ -234,6 +235,15 @@ class BaseFolder(object):
|
|||||||
for uid in uidlist:
|
for uid in uidlist:
|
||||||
self.deletemessageflags(uid, flags)
|
self.deletemessageflags(uid, flags)
|
||||||
|
|
||||||
|
def change_message_uid(self, uid, new_uid):
|
||||||
|
"""Change the message from existing uid to new_uid
|
||||||
|
|
||||||
|
If the backend supports it (IMAP does not).
|
||||||
|
:param new_uid: (optional) If given, the old UID will be changed
|
||||||
|
to a new UID. This allows backends efficient renaming of
|
||||||
|
messages if the UID has changed."""
|
||||||
|
raise NotImplementedException
|
||||||
|
|
||||||
def deletemessage(self, uid):
|
def deletemessage(self, uid):
|
||||||
raise NotImplementedException
|
raise NotImplementedException
|
||||||
|
|
||||||
@ -275,21 +285,16 @@ class BaseFolder(object):
|
|||||||
#remained negative, no server was willing to assign us an
|
#remained negative, no server was willing to assign us an
|
||||||
#UID. If newid is 0, saving succeeded, but we could not
|
#UID. If newid is 0, saving succeeded, but we could not
|
||||||
#retrieve the new UID. Ignore message in this case.
|
#retrieve the new UID. Ignore message in this case.
|
||||||
newuid = dstfolder.savemessage(uid, message, flags, rtime)
|
new_uid = dstfolder.savemessage(uid, message, flags, rtime)
|
||||||
|
if new_uid > 0:
|
||||||
if newuid > 0:
|
if new_uid != uid:
|
||||||
if newuid != 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.
|
# Got new UID, change the local uid.
|
||||||
#TODO: Maildir could do this with a rename rather than
|
|
||||||
#load/save/del operation, IMPLEMENT a changeuid()
|
|
||||||
#function or so.
|
|
||||||
self.savemessage(newuid, message, flags, rtime)
|
|
||||||
self.deletemessage(uid)
|
|
||||||
uid = newuid
|
|
||||||
# Save uploaded status in the statusfolder
|
# Save uploaded status in the statusfolder
|
||||||
statusfolder.savemessage(uid, message, flags, rtime)
|
statusfolder.savemessage(new_uid, message, flags, rtime)
|
||||||
|
elif new_uid == 0:
|
||||||
elif newuid == 0:
|
|
||||||
# Message was stored to dstfolder, but we can't find it's UID
|
# 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
|
# This means we can't link current message to the one created
|
||||||
# in IMAP. So we just delete local message and on next run
|
# in IMAP. So we just delete local message and on next run
|
||||||
@ -299,11 +304,11 @@ class BaseFolder(object):
|
|||||||
self.deletemessage(uid)
|
self.deletemessage(uid)
|
||||||
else:
|
else:
|
||||||
raise OfflineImapError("Trying to save msg (uid %d) on folder "
|
raise OfflineImapError("Trying to save msg (uid %d) on folder "
|
||||||
"%s returned invalid uid %d" % \
|
"%s returned invalid uid %d" % (uid,
|
||||||
(uid,
|
dstfolder.getvisiblename(), new_uid),
|
||||||
dstfolder.getvisiblename(),
|
|
||||||
newuid),
|
|
||||||
OfflineImapError.ERROR.MESSAGE)
|
OfflineImapError.ERROR.MESSAGE)
|
||||||
|
except (KeyboardInterrupt): # bubble up CTRL-C
|
||||||
|
raise
|
||||||
except OfflineImapError, e:
|
except OfflineImapError, e:
|
||||||
if e.severity > OfflineImapError.ERROR.MESSAGE:
|
if e.severity > OfflineImapError.ERROR.MESSAGE:
|
||||||
raise # buble severe errors up
|
raise # buble severe errors up
|
||||||
@ -311,10 +316,9 @@ class BaseFolder(object):
|
|||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.ui.error(e, "Copying message %s [acc: %s]:\n %s" %\
|
self.ui.error(e, "Copying message %s [acc: %s]:\n %s" %\
|
||||||
(uid, self.accountname,
|
(uid, self.accountname,
|
||||||
traceback.format_exc()))
|
exc_info()[2]))
|
||||||
raise #raise on unknown errors, so we can fix those
|
raise #raise on unknown errors, so we can fix those
|
||||||
|
|
||||||
|
|
||||||
def syncmessagesto_copy(self, dstfolder, statusfolder):
|
def syncmessagesto_copy(self, dstfolder, statusfolder):
|
||||||
"""Pass1: Copy locally existing messages not on the other side
|
"""Pass1: Copy locally existing messages not on the other side
|
||||||
|
|
||||||
|
@ -33,10 +33,10 @@ except NameError:
|
|||||||
class IMAPFolder(BaseFolder):
|
class IMAPFolder(BaseFolder):
|
||||||
def __init__(self, imapserver, name, repository):
|
def __init__(self, imapserver, name, repository):
|
||||||
name = imaputil.dequote(name)
|
name = imaputil.dequote(name)
|
||||||
|
self.sep = imapserver.delim
|
||||||
super(IMAPFolder, self).__init__(name, repository)
|
super(IMAPFolder, self).__init__(name, repository)
|
||||||
self.expunge = repository.getexpunge()
|
self.expunge = repository.getexpunge()
|
||||||
self.root = None # imapserver.root
|
self.root = None # imapserver.root
|
||||||
self.sep = imapserver.delim
|
|
||||||
self.imapserver = imapserver
|
self.imapserver = imapserver
|
||||||
self.messagelist = None
|
self.messagelist = None
|
||||||
self.randomgenerator = random.Random()
|
self.randomgenerator = random.Random()
|
||||||
@ -570,7 +570,7 @@ class IMAPFolder(BaseFolder):
|
|||||||
self.ui.warn("Server supports UIDPLUS but got no APPENDUID "
|
self.ui.warn("Server supports UIDPLUS but got no APPENDUID "
|
||||||
"appending a message.")
|
"appending a message.")
|
||||||
return 0
|
return 0
|
||||||
uid = long(imapobj._get_untagged_response('APPENDUID', True)[-1].split(' ')[1])
|
uid = long(imapobj._get_untagged_response('APPENDUID')[-1].split(' ')[1])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# we don't support UIDPLUS
|
# we don't support UIDPLUS
|
||||||
@ -597,8 +597,8 @@ class IMAPFolder(BaseFolder):
|
|||||||
self.ui.debug('imap', 'savemessage: returning new UID %d' % uid)
|
self.ui.debug('imap', 'savemessage: returning new UID %d' % uid)
|
||||||
return uid
|
return uid
|
||||||
|
|
||||||
|
|
||||||
def savemessageflags(self, uid, flags):
|
def savemessageflags(self, uid, flags):
|
||||||
|
"""Change a message's flags to `flags`."""
|
||||||
imapobj = self.imapserver.acquireconnection()
|
imapobj = self.imapserver.acquireconnection()
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@ -684,6 +684,14 @@ class IMAPFolder(BaseFolder):
|
|||||||
elif operation == '-':
|
elif operation == '-':
|
||||||
self.messagelist[uid]['flags'] -= flags
|
self.messagelist[uid]['flags'] -= flags
|
||||||
|
|
||||||
|
def change_message_uid(self, uid, new_uid):
|
||||||
|
"""Change the message from existing uid to new_uid
|
||||||
|
|
||||||
|
If the backend supports it. IMAP does not and will throw errors."""
|
||||||
|
raise OfflineImapError('IMAP backend cannot change a messages UID from '
|
||||||
|
'%d to %d' % (uid, new_uid),
|
||||||
|
OfflineImapError.ERROR.MESSAGE)
|
||||||
|
|
||||||
def deletemessage(self, uid):
|
def deletemessage(self, uid):
|
||||||
self.deletemessages_noconvert([uid])
|
self.deletemessages_noconvert([uid])
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
|
|||||||
|
|
||||||
class LocalStatusFolder(BaseFolder):
|
class LocalStatusFolder(BaseFolder):
|
||||||
def __init__(self, name, repository):
|
def __init__(self, name, repository):
|
||||||
|
self.sep = '.' #needs to be set before super.__init__()
|
||||||
super(LocalStatusFolder, self).__init__(name, repository)
|
super(LocalStatusFolder, self).__init__(name, repository)
|
||||||
self.sep = '.'
|
|
||||||
self.filename = os.path.join(self.getroot(), self.getfolderbasename())
|
self.filename = os.path.join(self.getroot(), self.getfolderbasename())
|
||||||
self.messagelist = {}
|
self.messagelist = {}
|
||||||
self.savelock = threading.Lock()
|
self.savelock = threading.Lock()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Maildir folder support
|
# Maildir folder support
|
||||||
# Copyright (C) 2002 - 2007 John Goerzen
|
# Copyright (C) 2002 - 2011 John Goerzen & contributors
|
||||||
# <jgoerzen@complete.org>
|
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -35,8 +34,10 @@ except NameError:
|
|||||||
|
|
||||||
from offlineimap import OfflineImapError
|
from offlineimap import OfflineImapError
|
||||||
|
|
||||||
uidmatchre = re.compile(',U=(\d+)')
|
# Find the UID in a message filename
|
||||||
timestampmatchre = re.compile('(\d+)');
|
re_uidmatch = re.compile(',U=(\d+)')
|
||||||
|
# Find a numeric timestamp in a string (filename prefix)
|
||||||
|
re_timestampmatch = re.compile('(\d+)');
|
||||||
|
|
||||||
timeseq = 0
|
timeseq = 0
|
||||||
lasttime = long(0)
|
lasttime = long(0)
|
||||||
@ -59,19 +60,22 @@ def gettimeseq():
|
|||||||
|
|
||||||
class MaildirFolder(BaseFolder):
|
class MaildirFolder(BaseFolder):
|
||||||
def __init__(self, root, name, sep, repository):
|
def __init__(self, root, name, sep, repository):
|
||||||
|
self.sep = sep # needs to be set before super().__init__
|
||||||
super(MaildirFolder, self).__init__(name, repository)
|
super(MaildirFolder, self).__init__(name, repository)
|
||||||
self.dofsync = self.config.getdefaultboolean("general", "fsync", True)
|
self.dofsync = self.config.getdefaultboolean("general", "fsync", True)
|
||||||
self.root = root
|
self.root = root
|
||||||
self.sep = sep
|
|
||||||
self.messagelist = None
|
self.messagelist = None
|
||||||
# check if we should use a different infosep to support Win file systems
|
# check if we should use a different infosep to support Win file systems
|
||||||
self.wincompatible = self.config.getdefaultboolean(
|
self.wincompatible = self.config.getdefaultboolean(
|
||||||
"Account "+self.accountname, "maildir-windows-compatible", False)
|
"Account "+self.accountname, "maildir-windows-compatible", False)
|
||||||
|
|
||||||
self.infosep = '!' if self.wincompatible else ':'
|
self.infosep = '!' if self.wincompatible else ':'
|
||||||
"""infosep is the separator between maildir name and flag appendix"""
|
"""infosep is the separator between maildir name and flag appendix"""
|
||||||
self.flagmatchre = re.compile('(%s2,)(\w*)' % self.infosep)
|
self.re_flagmatch = re.compile('%s2,(\w*)' % self.infosep)
|
||||||
#self.ui is set in BaseFolder.init()
|
#self.ui is set in BaseFolder.init()
|
||||||
|
# Everything up to the first comma or colon (or ! if Windows):
|
||||||
|
self.re_prefixmatch = re.compile('([^'+ self.infosep + ',]*)')
|
||||||
|
#folder's md, so we can match with recorded file md5 for validity
|
||||||
|
self._foldermd5 = md5(self.getvisiblename()).hexdigest()
|
||||||
# Cache the full folder path, as we use getfullname() very often
|
# Cache the full folder path, as we use getfullname() very often
|
||||||
self._fullname = os.path.join(self.getroot(), self.getname())
|
self._fullname = os.path.join(self.getroot(), self.getname())
|
||||||
|
|
||||||
@ -97,7 +101,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
+ oldest_time_struct[5])
|
+ oldest_time_struct[5])
|
||||||
oldest_time_utc -= oldest_time_today_seconds
|
oldest_time_utc -= oldest_time_today_seconds
|
||||||
|
|
||||||
timestampmatch = timestampmatchre.search(messagename)
|
timestampmatch = re_timestampmatch.search(messagename)
|
||||||
timestampstr = timestampmatch.group()
|
timestampstr = timestampmatch.group()
|
||||||
timestamplong = long(timestampstr)
|
timestamplong = long(timestampstr)
|
||||||
if(timestamplong < oldest_time_utc):
|
if(timestamplong < oldest_time_utc):
|
||||||
@ -105,68 +109,80 @@ class MaildirFolder(BaseFolder):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _parse_filename(self, filename):
|
||||||
|
"""Returns a messages file name components
|
||||||
|
|
||||||
|
Receives the file name (without path) of a msg. Usual format is
|
||||||
|
'<%d_%d.%d.%s>,U=<%d>,FMD5=<%s>:2,<FLAGS>' (pointy brackets
|
||||||
|
denoting the various components).
|
||||||
|
|
||||||
|
If FMD5 does not correspond with the current folder MD5, we will
|
||||||
|
return None for the UID & FMD5 (as it is not valid in this
|
||||||
|
folder). If UID or FMD5 can not be detected, we return `None`
|
||||||
|
for the respective element. If flags are empty or cannot be
|
||||||
|
detected, we return an empty flags list.
|
||||||
|
|
||||||
|
:returns: (prefix, UID, FMD5, flags). UID is a numeric "long"
|
||||||
|
type. flags is a set() of Maildir flags"""
|
||||||
|
prefix, uid, fmd5, flags = None, None, None, set()
|
||||||
|
prefixmatch = self.re_prefixmatch.match(filename)
|
||||||
|
if prefixmatch:
|
||||||
|
prefix = prefixmatch.group(1)
|
||||||
|
folderstr = ',FMD5=%s' % self._foldermd5
|
||||||
|
foldermatch = folderstr in filename
|
||||||
|
# If there was no folder MD5 specified, or if it mismatches,
|
||||||
|
# assume it is a foreign (new) message and ret: uid, fmd5 = None, None
|
||||||
|
if foldermatch:
|
||||||
|
uidmatch = re_uidmatch.search(filename)
|
||||||
|
if uidmatch:
|
||||||
|
uid = long(uidmatch.group(1))
|
||||||
|
flagmatch = self.re_flagmatch.search(filename)
|
||||||
|
if flagmatch:
|
||||||
|
flags = set(flagmatch.group(1))
|
||||||
|
return prefix, uid, fmd5, flags
|
||||||
|
|
||||||
def _scanfolder(self):
|
def _scanfolder(self):
|
||||||
"""Cache the message list. Maildir flags are:
|
"""Cache the message list from a Maildir.
|
||||||
R (replied)
|
|
||||||
S (seen)
|
Maildir flags are: R (replied) S (seen) T (trashed) D (draft) F
|
||||||
T (trashed)
|
(flagged).
|
||||||
D (draft)
|
:returns: dict that can be used as self.messagelist"""
|
||||||
F (flagged)
|
maxage = self.config.getdefaultint("Account " + self.accountname,
|
||||||
and must occur in ASCII order."""
|
"maxage", None)
|
||||||
|
maxsize = self.config.getdefaultint("Account " + self.accountname,
|
||||||
|
"maxsize", None)
|
||||||
retval = {}
|
retval = {}
|
||||||
files = []
|
files = []
|
||||||
nouidcounter = -1 # Messages without UIDs get
|
nouidcounter = -1 # Messages without UIDs get negative UIDs.
|
||||||
# negative UID numbers.
|
|
||||||
foldermd5 = md5(self.getvisiblename()).hexdigest()
|
|
||||||
folderstr = ',FMD5=' + foldermd5
|
|
||||||
for dirannex in ['new', 'cur']:
|
for dirannex in ['new', 'cur']:
|
||||||
fulldirname = os.path.join(self.getfullname(), dirannex)
|
fulldirname = os.path.join(self.getfullname(), dirannex)
|
||||||
files.extend(os.path.join(dirannex, filename) for
|
files.extend((dirannex, filename) for
|
||||||
filename in os.listdir(fulldirname))
|
filename in os.listdir(fulldirname))
|
||||||
for file in files:
|
|
||||||
messagename = os.path.basename(file)
|
|
||||||
|
|
||||||
#check if there is a parameter for maxage / maxsize - then see if this
|
for dirannex, filename in files:
|
||||||
#message should be considered or not
|
# We store just dirannex and filename, ie 'cur/123...'
|
||||||
maxage = self.config.getdefaultint("Account " + self.accountname, "maxage", -1)
|
filepath = os.path.join(dirannex, filename)
|
||||||
maxsize = self.config.getdefaultint("Account " + self.accountname, "maxsize", -1)
|
# 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
|
||||||
|
|
||||||
if(maxage != -1):
|
(prefix, uid, fmd5, flags) = self._parse_filename(filename)
|
||||||
isnewenough = self._iswithinmaxage(messagename, maxage)
|
if uid is None: # assign negative uid to upload it.
|
||||||
if(isnewenough != True):
|
|
||||||
#this message is older than we should consider....
|
|
||||||
continue
|
|
||||||
|
|
||||||
#Check and see if the message is too big if the maxsize for this account is set
|
|
||||||
if(maxsize != -1):
|
|
||||||
size = os.path.getsize(os.path.join(self.getfullname(), file))
|
|
||||||
if(size > maxsize):
|
|
||||||
continue
|
|
||||||
|
|
||||||
foldermatch = messagename.find(folderstr) != -1
|
|
||||||
if not foldermatch:
|
|
||||||
# If there is no folder MD5 specified, or if it mismatches,
|
|
||||||
# assume it is a foreign (new) message and generate a
|
|
||||||
# negative uid for it
|
|
||||||
uid = nouidcounter
|
uid = nouidcounter
|
||||||
nouidcounter -= 1
|
nouidcounter -= 1
|
||||||
else: # It comes from our folder.
|
else: # It comes from our folder.
|
||||||
uidmatch = uidmatchre.search(messagename)
|
uidmatch = re_uidmatch.search(filename)
|
||||||
uid = None
|
uid = None
|
||||||
if not uidmatch:
|
if not uidmatch:
|
||||||
uid = nouidcounter
|
uid = nouidcounter
|
||||||
nouidcounter -= 1
|
nouidcounter -= 1
|
||||||
else:
|
else:
|
||||||
uid = long(uidmatch.group(1))
|
uid = long(uidmatch.group(1))
|
||||||
#identify flags in the path name
|
|
||||||
flagmatch = self.flagmatchre.search(messagename)
|
|
||||||
if flagmatch:
|
|
||||||
flags = set(flagmatch.group(2))
|
|
||||||
else:
|
|
||||||
flags = set()
|
|
||||||
# 'filename' is 'dirannex/filename', e.g. cur/123,U=1,FMD5=1:2,S
|
# 'filename' is 'dirannex/filename', e.g. cur/123,U=1,FMD5=1:2,S
|
||||||
retval[uid] = {'flags': flags, 'filename': file}
|
retval[uid] = {'flags': flags, 'filename': filepath}
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def quickchanged(self, statusfolder):
|
def quickchanged(self, statusfolder):
|
||||||
@ -205,6 +221,17 @@ class MaildirFolder(BaseFolder):
|
|||||||
filepath = os.path.join(self.getfullname(), filename)
|
filepath = os.path.join(self.getfullname(), filename)
|
||||||
return os.path.getmtime(filepath)
|
return os.path.getmtime(filepath)
|
||||||
|
|
||||||
|
def new_message_filename(self, uid, flags=set()):
|
||||||
|
"""Creates a new unique Maildir filename
|
||||||
|
|
||||||
|
:param uid: The UID`None`, or a set of maildir flags
|
||||||
|
:param flags: A set of maildir flags
|
||||||
|
:returns: String containing unique message filename"""
|
||||||
|
timeval, timeseq = gettimeseq()
|
||||||
|
return '%d_%d.%d.%s,U=%d,FMD5=%s%s2,%s' % \
|
||||||
|
(timeval, timeseq, os.getpid(), socket.gethostname(),
|
||||||
|
uid, self._foldermd5, self.infosep, ''.join(sorted(flags)))
|
||||||
|
|
||||||
def savemessage(self, uid, content, flags, rtime):
|
def savemessage(self, uid, content, flags, rtime):
|
||||||
# This function only ever saves to tmp/,
|
# This function only ever saves to tmp/,
|
||||||
# but it calls savemessageflags() to actually save to cur/ or new/.
|
# but it calls savemessageflags() to actually save to cur/ or new/.
|
||||||
@ -221,14 +248,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
# Otherwise, save the message in tmp/ and then call savemessageflags()
|
# Otherwise, save the message in tmp/ and then call savemessageflags()
|
||||||
# to give it a permanent home.
|
# to give it a permanent home.
|
||||||
tmpdir = os.path.join(self.getfullname(), 'tmp')
|
tmpdir = os.path.join(self.getfullname(), 'tmp')
|
||||||
timeval, timeseq = gettimeseq()
|
messagename = self.new_message_filename(uid, flags)
|
||||||
messagename = '%d_%d.%d.%s,U=%d,FMD5=%s' % \
|
|
||||||
(timeval,
|
|
||||||
timeseq,
|
|
||||||
os.getpid(),
|
|
||||||
socket.gethostname(),
|
|
||||||
uid,
|
|
||||||
md5(self.getvisiblename()).hexdigest())
|
|
||||||
# open file and write it out
|
# open file and write it out
|
||||||
try:
|
try:
|
||||||
fd = os.open(os.path.join(tmpdir, messagename),
|
fd = os.open(os.path.join(tmpdir, messagename),
|
||||||
@ -253,7 +273,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
if rtime != None:
|
if rtime != None:
|
||||||
os.utime(os.path.join(tmpdir, messagename), (rtime, rtime))
|
os.utime(os.path.join(tmpdir, messagename), (rtime, rtime))
|
||||||
|
|
||||||
self.messagelist[uid] = {'flags': set(),
|
self.messagelist[uid] = {'flags': flags,
|
||||||
'filename': os.path.join('tmp', messagename)}
|
'filename': os.path.join('tmp', messagename)}
|
||||||
# savemessageflags moves msg to 'cur' or 'new' as appropriate
|
# savemessageflags moves msg to 'cur' or 'new' as appropriate
|
||||||
self.savemessageflags(uid, flags)
|
self.savemessageflags(uid, flags)
|
||||||
@ -264,25 +284,27 @@ class MaildirFolder(BaseFolder):
|
|||||||
return self.messagelist[uid]['flags']
|
return self.messagelist[uid]['flags']
|
||||||
|
|
||||||
def savemessageflags(self, uid, flags):
|
def savemessageflags(self, uid, flags):
|
||||||
|
"""Sets the specified message's flags to the given set.
|
||||||
|
|
||||||
|
This function moves the message to the cur or new subdir,
|
||||||
|
depending on the 'S'een flag."""
|
||||||
|
|
||||||
oldfilename = self.messagelist[uid]['filename']
|
oldfilename = self.messagelist[uid]['filename']
|
||||||
dir_prefix, newname = os.path.split(oldfilename)
|
dir_prefix, filename = os.path.split(oldfilename)
|
||||||
tmpdir = os.path.join(self.getfullname(), 'tmp')
|
# If a message has been seen, it goes into 'cur'
|
||||||
if 'S' in flags:
|
dir_prefix = 'cur' if 'S' in flags else 'new'
|
||||||
# If a message has been seen, it goes into the cur
|
|
||||||
# directory. CR debian#152482
|
|
||||||
dir_prefix = 'cur'
|
|
||||||
else:
|
|
||||||
dir_prefix = 'new'
|
|
||||||
|
|
||||||
# Strip off existing infostring (preserving small letter flags, that
|
if flags != self.messagelist[uid]['flags']:
|
||||||
# dovecot uses)
|
# Flags have actually changed, construct new filename
|
||||||
infomatch = self.flagmatchre.search(newname)
|
# Strip off existing infostring (preserving small letter flags that
|
||||||
if infomatch:
|
# dovecot uses)
|
||||||
newname = newname[:-len(infomatch.group())] #strip off
|
infomatch = self.flagmatchre.search(filename)
|
||||||
infostr = '%s2,%s' % (self.infosep, ''.join(sorted(flags)))
|
if infomatch:
|
||||||
newname += infostr
|
filename = filename[:-len(infomatch.group())] #strip off
|
||||||
|
infostr = '%s2,%s' % (self.infosep, ''.join(sorted(flags)))
|
||||||
|
filename += infostr
|
||||||
|
|
||||||
newfilename = os.path.join(dir_prefix, newname)
|
newfilename = os.path.join(dir_prefix, filename)
|
||||||
if (newfilename != oldfilename):
|
if (newfilename != oldfilename):
|
||||||
try:
|
try:
|
||||||
os.rename(os.path.join(self.getfullname(), oldfilename),
|
os.rename(os.path.join(self.getfullname(), oldfilename),
|
||||||
@ -295,10 +317,26 @@ class MaildirFolder(BaseFolder):
|
|||||||
self.messagelist[uid]['flags'] = flags
|
self.messagelist[uid]['flags'] = flags
|
||||||
self.messagelist[uid]['filename'] = newfilename
|
self.messagelist[uid]['filename'] = newfilename
|
||||||
|
|
||||||
# By now, the message had better not be in tmp/ land!
|
def change_message_uid(self, uid, new_uid):
|
||||||
final_dir, final_name = os.path.split(self.messagelist[uid]['filename'])
|
"""Change the message from existing uid to new_uid
|
||||||
assert final_dir != 'tmp'
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
filename = self.new_message_filename(new_uid, flags)
|
||||||
|
os.rename(os.path.join(self.getfullname(), oldfilename),
|
||||||
|
os.path.join(self.getfullname(), dir_prefix, filename))
|
||||||
|
self.messagelist[new_uid] = self.messagelist[uid]
|
||||||
|
del self.messagelist[uid]
|
||||||
|
|
||||||
def deletemessage(self, uid):
|
def deletemessage(self, uid):
|
||||||
"""Unlinks a message file from the Maildir.
|
"""Unlinks a message file from the Maildir.
|
||||||
|
|
||||||
|
@ -221,6 +221,31 @@ class MappedIMAPFolder(IMAPFolder):
|
|||||||
self._mb.addmessagesflags(self._uidlist(self.r2l, uidlist),
|
self._mb.addmessagesflags(self._uidlist(self.r2l, uidlist),
|
||||||
flags)
|
flags)
|
||||||
|
|
||||||
|
def change_message_uid(self, ruid, new_ruid):
|
||||||
|
"""Change the message from existing ruid to new_ruid
|
||||||
|
|
||||||
|
:param new_uid: The old remote UID will be changed to a new
|
||||||
|
UID. The UIDMaps case handles this efficiently by simply
|
||||||
|
changing the mappings file."""
|
||||||
|
if ruid not in self.r2l:
|
||||||
|
raise OfflineImapError("Cannot change unknown Maildir UID %s" % ruid,
|
||||||
|
OfflineImapError.ERROR.MESSAGE)
|
||||||
|
if ruid == new_ruid: return # sanity check shortcut
|
||||||
|
self.maplock.acquire()
|
||||||
|
try:
|
||||||
|
luid = self.r2l[ruid]
|
||||||
|
self.l2r[luid] = new_ruid
|
||||||
|
del self.r2l[ruid]
|
||||||
|
self.r2l[new_ruid] = luid
|
||||||
|
#TODO: diskl2r|r2l are a pain to sync and should be done away with
|
||||||
|
#diskl2r only contains positive UIDs, so wrap in ifs
|
||||||
|
if luid>0: self.diskl2r[luid] = new_ruid
|
||||||
|
if ruid>0: del self.diskr2l[ruid]
|
||||||
|
if new_ruid > 0: self.diskr2l[new_ruid] = luid
|
||||||
|
self._savemaps(dolock = 0)
|
||||||
|
finally:
|
||||||
|
self.maplock.release()
|
||||||
|
|
||||||
def _mapped_delete(self, uidlist):
|
def _mapped_delete(self, uidlist):
|
||||||
self.maplock.acquire()
|
self.maplock.acquire()
|
||||||
try:
|
try:
|
||||||
|
@ -324,6 +324,8 @@ class IMAPRepository(BaseRepository):
|
|||||||
:param foldername: Full path of the folder to be created."""
|
:param foldername: Full path of the folder to be created."""
|
||||||
if self.getreference():
|
if self.getreference():
|
||||||
foldername = self.getreference() + self.getsep() + foldername
|
foldername = self.getreference() + self.getsep() + foldername
|
||||||
|
if not foldername: # Create top level folder as folder separator
|
||||||
|
foldername = self.getsep()
|
||||||
|
|
||||||
imapobj = self.imapserver.acquireconnection()
|
imapobj = self.imapserver.acquireconnection()
|
||||||
try:
|
try:
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
import urllib
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
Loading…
Reference in New Issue
Block a user