Merge branch 'next'
This commit is contained in:
commit
2009952ec9
@ -8,6 +8,10 @@ ChangeLog
|
|||||||
OfflineIMAP v6.5.6.1 (YYYY-MM-DD)
|
OfflineIMAP v6.5.6.1 (YYYY-MM-DD)
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
|
* Create SQLite database directory if it doesn't exist
|
||||||
|
yet; warn if path is not a directory (Nick Farrell,
|
||||||
|
GutHub pull #102)
|
||||||
|
|
||||||
* Fix mangled message headers for servers without UIDPLUS:
|
* Fix mangled message headers for servers without UIDPLUS:
|
||||||
X-OfflineIMAP was added with preceeding '\n' instead of
|
X-OfflineIMAP was added with preceeding '\n' instead of
|
||||||
'\r\n' just before message was uploaded to the IMAP server.
|
'\r\n' just before message was uploaded to the IMAP server.
|
||||||
@ -17,6 +21,8 @@ OfflineIMAP v6.5.6.1 (YYYY-MM-DD)
|
|||||||
|
|
||||||
* Various fixes in documentation.
|
* Various fixes in documentation.
|
||||||
|
|
||||||
|
* Fix unbounded recursion during flag update (Josh Berry).
|
||||||
|
|
||||||
|
|
||||||
OfflineIMAP v6.5.6 (2014-05-14)
|
OfflineIMAP v6.5.6 (2014-05-14)
|
||||||
===============================
|
===============================
|
||||||
|
@ -236,6 +236,17 @@ class BaseFolder(object):
|
|||||||
You must call cachemessagelist() before calling this function!"""
|
You must call cachemessagelist() before calling this function!"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def msglist_item_initializer(self, uid):
|
||||||
|
"""
|
||||||
|
Returns value for empty messagelist element with given UID.
|
||||||
|
|
||||||
|
This function must initialize all fields of messagelist item
|
||||||
|
and must be called every time when one creates new messagelist
|
||||||
|
entry to ensure that all fields that must be present are present.
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def uidexists(self, uid):
|
def uidexists(self, uid):
|
||||||
"""Returns True if uid exists"""
|
"""Returns True if uid exists"""
|
||||||
return uid in self.getmessagelist()
|
return uid in self.getmessagelist()
|
||||||
|
@ -110,6 +110,11 @@ class GmailFolder(IMAPFolder):
|
|||||||
else:
|
else:
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
# Interface from BaseFolder
|
||||||
|
def msglist_item_initializer(self, uid):
|
||||||
|
return {'uid': uid, 'flags': set(), 'labels': set(), 'time': 0}
|
||||||
|
|
||||||
|
|
||||||
# TODO: merge this code with the parent's cachemessagelist:
|
# TODO: merge this code with the parent's cachemessagelist:
|
||||||
# TODO: they have too much common logics.
|
# TODO: they have too much common logics.
|
||||||
def cachemessagelist(self):
|
def cachemessagelist(self):
|
||||||
@ -152,6 +157,7 @@ class GmailFolder(IMAPFolder):
|
|||||||
minor = 1)
|
minor = 1)
|
||||||
else:
|
else:
|
||||||
uid = long(options['UID'])
|
uid = long(options['UID'])
|
||||||
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
||||||
flags = imaputil.flagsimap2maildir(options['FLAGS'])
|
flags = imaputil.flagsimap2maildir(options['FLAGS'])
|
||||||
m = re.search('\(([^\)]*)\)', options['X-GM-LABELS'])
|
m = re.search('\(([^\)]*)\)', options['X-GM-LABELS'])
|
||||||
if m:
|
if m:
|
||||||
|
@ -56,6 +56,13 @@ class GmailMaildirFolder(MaildirFolder):
|
|||||||
return True
|
return True
|
||||||
return False #Nope, nothing changed
|
return False #Nope, nothing changed
|
||||||
|
|
||||||
|
|
||||||
|
# Interface from BaseFolder
|
||||||
|
def msglist_item_initializer(self, uid):
|
||||||
|
return {'flags': set(), 'labels': set(), 'labels_cached': False,
|
||||||
|
'filename': '/no-dir/no-such-file/', 'mtime': 0}
|
||||||
|
|
||||||
|
|
||||||
def cachemessagelist(self):
|
def cachemessagelist(self):
|
||||||
if self.messagelist is None:
|
if self.messagelist is None:
|
||||||
self.messagelist = self._scanfolder()
|
self.messagelist = self._scanfolder()
|
||||||
@ -66,9 +73,10 @@ class GmailMaildirFolder(MaildirFolder):
|
|||||||
filepath = os.path.join(self.getfullname(), msg['filename'])
|
filepath = os.path.join(self.getfullname(), msg['filename'])
|
||||||
msg['mtime'] = long(os.stat(filepath).st_mtime)
|
msg['mtime'] = long(os.stat(filepath).st_mtime)
|
||||||
|
|
||||||
|
|
||||||
def getmessagelabels(self, uid):
|
def getmessagelabels(self, uid):
|
||||||
# Labels are not cached in cachemessagelist because it is too slow.
|
# Labels are not cached in cachemessagelist because it is too slow.
|
||||||
if not 'labels' in self.messagelist[uid]:
|
if not self.messagelist[uid]['labels_cached']:
|
||||||
filename = self.messagelist[uid]['filename']
|
filename = self.messagelist[uid]['filename']
|
||||||
filepath = os.path.join(self.getfullname(), filename)
|
filepath = os.path.join(self.getfullname(), filename)
|
||||||
|
|
||||||
@ -82,10 +90,11 @@ class GmailMaildirFolder(MaildirFolder):
|
|||||||
self.messagelist[uid]['labels'] = \
|
self.messagelist[uid]['labels'] = \
|
||||||
imaputil.labels_from_header(self.labelsheader,
|
imaputil.labels_from_header(self.labelsheader,
|
||||||
self.getmessageheader(content, self.labelsheader))
|
self.getmessageheader(content, self.labelsheader))
|
||||||
|
self.messagelist[uid]['labels_cached'] = True
|
||||||
|
|
||||||
return self.messagelist[uid]['labels']
|
return self.messagelist[uid]['labels']
|
||||||
|
|
||||||
|
|
||||||
def getmessagemtime(self, uid):
|
def getmessagemtime(self, uid):
|
||||||
if not 'mtime' in self.messagelist[uid]:
|
if not 'mtime' in self.messagelist[uid]:
|
||||||
return 0
|
return 0
|
||||||
|
@ -202,6 +202,10 @@ class IMAPFolder(BaseFolder):
|
|||||||
|
|
||||||
return msgsToFetch
|
return msgsToFetch
|
||||||
|
|
||||||
|
# Interface from BaseFolder
|
||||||
|
def msglist_item_initializer(self, uid):
|
||||||
|
return {'uid': uid, 'flags': set(), 'time': 0}
|
||||||
|
|
||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
def cachemessagelist(self):
|
def cachemessagelist(self):
|
||||||
@ -239,6 +243,7 @@ class IMAPFolder(BaseFolder):
|
|||||||
minor = 1)
|
minor = 1)
|
||||||
else:
|
else:
|
||||||
uid = long(options['UID'])
|
uid = long(options['UID'])
|
||||||
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
||||||
flags = imaputil.flagsimap2maildir(options['FLAGS'])
|
flags = imaputil.flagsimap2maildir(options['FLAGS'])
|
||||||
rtime = imaplibutil.Internaldate2epoch(messagestr)
|
rtime = imaplibutil.Internaldate2epoch(messagestr)
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
|
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
|
||||||
@ -641,7 +646,8 @@ class IMAPFolder(BaseFolder):
|
|||||||
if imapobj: self.imapserver.releaseconnection(imapobj)
|
if imapobj: self.imapserver.releaseconnection(imapobj)
|
||||||
|
|
||||||
if uid: # avoid UID FETCH 0 crash happening later on
|
if uid: # avoid UID FETCH 0 crash happening later on
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
||||||
|
self.messagelist[uid]['flags'] = flags
|
||||||
|
|
||||||
self.ui.debug('imap', 'savemessage: returning new UID %d' % uid)
|
self.ui.debug('imap', 'savemessage: returning new UID %d' % uid)
|
||||||
return uid
|
return uid
|
||||||
@ -755,14 +761,7 @@ class IMAPFolder(BaseFolder):
|
|||||||
def deletemessagesflags(self, uidlist, flags):
|
def deletemessagesflags(self, uidlist, flags):
|
||||||
self.__processmessagesflags('-', uidlist, flags)
|
self.__processmessagesflags('-', uidlist, flags)
|
||||||
|
|
||||||
def __processmessagesflags(self, operation, uidlist, flags):
|
def __processmessagesflags_real(self, operation, uidlist, flags):
|
||||||
# XXX: should really iterate over batches of 100 UIDs
|
|
||||||
if len(uidlist) > 101:
|
|
||||||
# Hack for those IMAP servers with a limited line length
|
|
||||||
self.__processmessagesflags(operation, uidlist[:100], flags)
|
|
||||||
self.__processmessagesflags(operation, uidlist[100:], flags)
|
|
||||||
return
|
|
||||||
|
|
||||||
imapobj = self.imapserver.acquireconnection()
|
imapobj = self.imapserver.acquireconnection()
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@ -804,6 +803,16 @@ class IMAPFolder(BaseFolder):
|
|||||||
elif operation == '-':
|
elif operation == '-':
|
||||||
self.messagelist[uid]['flags'] -= flags
|
self.messagelist[uid]['flags'] -= flags
|
||||||
|
|
||||||
|
|
||||||
|
def __processmessagesflags(self, operation, uidlist, flags):
|
||||||
|
# Hack for those IMAP servers with a limited line length
|
||||||
|
batch_size = 100
|
||||||
|
for i in xrange(0, len(uidlist), batch_size):
|
||||||
|
self.__processmessagesflags_real(operation,
|
||||||
|
uidlist[i:i + batch_size], flags)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
def change_message_uid(self, uid, new_uid):
|
def change_message_uid(self, uid, new_uid):
|
||||||
"""Change the message from existing uid to new_uid
|
"""Change the message from existing uid to new_uid
|
||||||
|
@ -57,6 +57,11 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
os.unlink(self.filename)
|
os.unlink(self.filename)
|
||||||
|
|
||||||
|
|
||||||
|
# Interface from BaseFolder
|
||||||
|
def msglist_item_initializer(self, uid):
|
||||||
|
return {'uid': uid, 'flags': set(), 'labels': set(), 'time': 0, 'mtime': 0}
|
||||||
|
|
||||||
|
|
||||||
def readstatus_v1(self, fp):
|
def readstatus_v1(self, fp):
|
||||||
"""
|
"""
|
||||||
Read status folder in format version 1.
|
Read status folder in format version 1.
|
||||||
@ -76,7 +81,8 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
(line, self.filename)
|
(line, self.filename)
|
||||||
self.ui.warn(errstr)
|
self.ui.warn(errstr)
|
||||||
raise ValueError(errstr)
|
raise ValueError(errstr)
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'mtime': 0, 'labels': set()}
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
||||||
|
self.messagelist[uid]['flags'] = flags
|
||||||
|
|
||||||
|
|
||||||
def readstatus(self, fp):
|
def readstatus(self, fp):
|
||||||
@ -100,7 +106,10 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
(line, self.filename)
|
(line, self.filename)
|
||||||
self.ui.warn(errstr)
|
self.ui.warn(errstr)
|
||||||
raise ValueError(errstr)
|
raise ValueError(errstr)
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'mtime': mtime, 'labels': labels}
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
||||||
|
self.messagelist[uid]['flags'] = flags
|
||||||
|
self.messagelist[uid]['mtime'] = mtime
|
||||||
|
self.messagelist[uid]['labels'] = labels
|
||||||
|
|
||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
@ -197,7 +206,11 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
self.savemessageflags(uid, flags)
|
self.savemessageflags(uid, flags)
|
||||||
return uid
|
return uid
|
||||||
|
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime, 'mtime': mtime, 'labels': labels}
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
||||||
|
self.messagelist[uid]['flags'] = flags
|
||||||
|
self.messagelist[uid]['time'] = rtime
|
||||||
|
self.messagelist[uid]['mtime'] = mtime
|
||||||
|
self.messagelist[uid]['labels'] = labels
|
||||||
self.save()
|
self.save()
|
||||||
return uid
|
return uid
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# 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 os.path
|
import os
|
||||||
import re
|
import re
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from .Base import BaseFolder
|
from .Base import BaseFolder
|
||||||
@ -51,6 +51,12 @@ class LocalStatusSQLiteFolder(BaseFolder):
|
|||||||
|
|
||||||
self._newfolder = False # flag if the folder is new
|
self._newfolder = False # flag if the folder is new
|
||||||
|
|
||||||
|
dirname = os.path.dirname(self.filename)
|
||||||
|
if not os.path.exists(dirname):
|
||||||
|
os.makedirs(dirname)
|
||||||
|
if not os.path.isdir(dirname):
|
||||||
|
raise UserWarning("SQLite database path '%s' is not a directory." % dirname)
|
||||||
|
|
||||||
# dblock protects against concurrent writes in same connection
|
# dblock protects against concurrent writes in same connection
|
||||||
self._dblock = Lock()
|
self._dblock = Lock()
|
||||||
|
|
||||||
@ -178,14 +184,23 @@ class LocalStatusSQLiteFolder(BaseFolder):
|
|||||||
self._newfolder = True
|
self._newfolder = True
|
||||||
|
|
||||||
|
|
||||||
|
# Interface from BaseFolder
|
||||||
|
def msglist_item_initializer(self, uid):
|
||||||
|
return {'uid': uid, 'flags': set(), 'labels': set(), 'time': 0, 'mtime': 0}
|
||||||
|
|
||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
def cachemessagelist(self):
|
def cachemessagelist(self):
|
||||||
self.messagelist = {}
|
self.messagelist = {}
|
||||||
cursor = self.connection.execute('SELECT id,flags,mtime,labels from status')
|
cursor = self.connection.execute('SELECT id,flags,mtime,labels from status')
|
||||||
for row in cursor:
|
for row in cursor:
|
||||||
|
uid = row[0]
|
||||||
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
||||||
flags = set(row[1])
|
flags = set(row[1])
|
||||||
labels = set([lb.strip() for lb in row[3].split(',') if len(lb.strip()) > 0])
|
labels = set([lb.strip() for lb in row[3].split(',') if len(lb.strip()) > 0])
|
||||||
self.messagelist[row[0]] = {'uid': row[0], 'flags': flags, 'mtime': row[2], 'labels': labels}
|
self.messagelist[uid]['flags'] = flags
|
||||||
|
self.messagelist[uid]['labels'] = labels
|
||||||
|
self.messagelist[uid]['mtime'] = row[2]
|
||||||
|
|
||||||
# Interface from LocalStatusFolder
|
# Interface from LocalStatusFolder
|
||||||
def save(self):
|
def save(self):
|
||||||
@ -265,6 +280,7 @@ class LocalStatusSQLiteFolder(BaseFolder):
|
|||||||
self.savemessageflags(uid, flags)
|
self.savemessageflags(uid, flags)
|
||||||
return uid
|
return uid
|
||||||
|
|
||||||
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime, 'mtime': mtime, 'labels': labels}
|
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime, 'mtime': mtime, 'labels': labels}
|
||||||
flags = ''.join(sorted(flags))
|
flags = ''.join(sorted(flags))
|
||||||
labels = ', '.join(sorted(labels))
|
labels = ', '.join(sorted(labels))
|
||||||
@ -272,9 +288,11 @@ class LocalStatusSQLiteFolder(BaseFolder):
|
|||||||
(uid,flags,mtime,labels))
|
(uid,flags,mtime,labels))
|
||||||
return uid
|
return uid
|
||||||
|
|
||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
def savemessageflags(self, uid, flags):
|
def savemessageflags(self, uid, flags):
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
assert self.uidexists(uid)
|
||||||
|
self.messagelist[uid]['flags'] = flags
|
||||||
flags = ''.join(sorted(flags))
|
flags = ''.join(sorted(flags))
|
||||||
self.__sql_write('UPDATE status SET flags=? WHERE id=?',(flags,uid))
|
self.__sql_write('UPDATE status SET flags=? WHERE id=?',(flags,uid))
|
||||||
|
|
||||||
|
@ -191,7 +191,9 @@ class MaildirFolder(BaseFolder):
|
|||||||
else:
|
else:
|
||||||
uid = long(uidmatch.group(1))
|
uid = long(uidmatch.group(1))
|
||||||
# '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': filepath}
|
retval[uid] = self.msglist_item_initializer(uid)
|
||||||
|
retval[uid]['flags'] = flags
|
||||||
|
retval[uid]['filename'] = filepath
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
@ -208,6 +210,12 @@ class MaildirFolder(BaseFolder):
|
|||||||
return True
|
return True
|
||||||
return False #Nope, nothing changed
|
return False #Nope, nothing changed
|
||||||
|
|
||||||
|
|
||||||
|
# Interface from BaseFolder
|
||||||
|
def msglist_item_initializer(self, uid):
|
||||||
|
return {'flags': set(), 'filename': '/no-dir/no-such-file/'}
|
||||||
|
|
||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
def cachemessagelist(self):
|
def cachemessagelist(self):
|
||||||
if self.messagelist is None:
|
if self.messagelist is None:
|
||||||
@ -319,7 +327,9 @@ class MaildirFolder(BaseFolder):
|
|||||||
if rtime != None:
|
if rtime != None:
|
||||||
os.utime(os.path.join(self.getfullname(), tmpname), (rtime, rtime))
|
os.utime(os.path.join(self.getfullname(), tmpname), (rtime, rtime))
|
||||||
|
|
||||||
self.messagelist[uid] = {'flags': flags, 'filename': tmpname}
|
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
|
# savemessageflags moves msg to 'cur' or 'new' as appropriate
|
||||||
self.savemessageflags(uid, flags)
|
self.savemessageflags(uid, flags)
|
||||||
self.ui.debug('maildir', 'savemessage: returning uid %d' % uid)
|
self.ui.debug('maildir', 'savemessage: returning uid %d' % uid)
|
||||||
@ -339,6 +349,9 @@ class MaildirFolder(BaseFolder):
|
|||||||
Note that this function does not check against dryrun settings,
|
Note that this function does not check against dryrun settings,
|
||||||
so you need to ensure that it is never called in a
|
so you need to ensure that it is never called in a
|
||||||
dryrun mode."""
|
dryrun mode."""
|
||||||
|
|
||||||
|
assert uid in self.messagelist
|
||||||
|
|
||||||
oldfilename = self.messagelist[uid]['filename']
|
oldfilename = self.messagelist[uid]['filename']
|
||||||
dir_prefix, filename = os.path.split(oldfilename)
|
dir_prefix, filename = os.path.split(oldfilename)
|
||||||
# If a message has been seen, it goes into 'cur'
|
# If a message has been seen, it goes into 'cur'
|
||||||
|
Loading…
Reference in New Issue
Block a user