2008-01-03 04:56:55 +01:00
|
|
|
# Gmail IMAP folder support
|
|
|
|
# Copyright (C) 2008 Riccardo Murri <riccardo.murri@gmail.com>
|
|
|
|
# Copyright (C) 2002-2007 John Goerzen <jgoerzen@complete.org>
|
|
|
|
#
|
|
|
|
# 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
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
|
|
|
"""Folder implementation to support features of the Gmail IMAP server.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from IMAP import IMAPFolder
|
|
|
|
import imaplib
|
|
|
|
from offlineimap import imaputil, imaplibutil
|
|
|
|
from offlineimap.ui import UIBase
|
|
|
|
from copy import copy
|
|
|
|
|
|
|
|
|
|
|
|
class GmailFolder(IMAPFolder):
|
|
|
|
"""Folder implementation to support features of the Gmail IMAP server.
|
|
|
|
Specifically, deleted messages are moved to folder `Gmail.TRASH_FOLDER`
|
|
|
|
(by default: ``[Gmail]/Trash``) prior to expunging them, since
|
|
|
|
Gmail maps to IMAP ``EXPUNGE`` command to "remove label".
|
|
|
|
|
|
|
|
For more information on the Gmail IMAP server:
|
|
|
|
http://mail.google.com/support/bin/answer.py?answer=77657&topic=12815
|
|
|
|
"""
|
|
|
|
|
|
|
|
#: Where deleted mail should be moved
|
|
|
|
TRASH_FOLDER='[Gmail]/Trash'
|
|
|
|
|
|
|
|
#: Gmail will really delete messages upon EXPUNGE in these folders
|
|
|
|
REAL_DELETE_FOLDERS = [ TRASH_FOLDER, '[Gmail]/Spam' ]
|
|
|
|
|
|
|
|
def __init__(self, imapserver, name, visiblename, accountname, repository):
|
|
|
|
self.realdelete = repository.getrealdelete(name)
|
|
|
|
IMAPFolder.__init__(self, imapserver, name, visiblename, \
|
|
|
|
accountname, repository)
|
|
|
|
|
|
|
|
def deletemessages_noconvert(self, uidlist):
|
|
|
|
uidlist = [uid for uid in uidlist if uid in self.messagelist]
|
|
|
|
if not len(uidlist):
|
|
|
|
return
|
|
|
|
|
|
|
|
if self.realdelete and not (self.getname() in self.REAL_DELETE_FOLDERS):
|
|
|
|
# IMAP expunge is just "remove label" in this folder,
|
|
|
|
# so map the request into a "move into Trash"
|
|
|
|
|
|
|
|
imapobj = self.imapserver.acquireconnection()
|
|
|
|
try:
|
|
|
|
imapobj.select(self.getfullname())
|
|
|
|
result = imapobj.uid('copy',
|
|
|
|
imaputil.listjoin(uidlist),
|
|
|
|
self.TRASH_FOLDER)
|
|
|
|
assert result[0] == 'OK', \
|
|
|
|
"Bad IMAPlib result: %s" % result[0]
|
|
|
|
finally:
|
|
|
|
self.imapserver.releaseconnection(imapobj)
|
|
|
|
for uid in uidlist:
|
|
|
|
del self.messagelist[uid]
|
|
|
|
else:
|
|
|
|
IMAPFolder.deletemessages_noconvert(self, uidlist)
|
|
|
|
|
|
|
|
def processmessagesflags(self, operation, uidlist, flags):
|
|
|
|
# XXX: the imapobj.myrights(...) calls dies with an error
|
|
|
|
# report from Gmail server stating that IMAP command
|
|
|
|
# 'MYRIGHTS' is not implemented. So, this
|
|
|
|
# `processmessagesflags` is just a copy from `IMAPFolder`,
|
|
|
|
# with the references to `imapobj.myrights()` deleted This
|
|
|
|
# shouldn't hurt, however, Gmail users always have full
|
|
|
|
# control over all their mailboxes (apparently).
|
|
|
|
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:
|
|
|
|
imapobj.select(self.getfullname())
|
|
|
|
r = imapobj.uid('store',
|
|
|
|
imaputil.listjoin(uidlist),
|
|
|
|
operation + 'FLAGS',
|
|
|
|
imaputil.flagsmaildir2imap(flags))
|
|
|
|
assert r[0] == 'OK', 'Error with store: ' + '. '.join(r[1])
|
|
|
|
r = r[1]
|
|
|
|
finally:
|
|
|
|
self.imapserver.releaseconnection(imapobj)
|
|
|
|
|
|
|
|
needupdate = copy(uidlist)
|
|
|
|
for result in r:
|
Attempt to fix a crashing bug in Gmail driver
refs deb#469598
Bug report received in Debian. User is having occasional trouble with Gmail
driver crashing, indicates problem may have started after switching to Gmail.
Gmail driver merged to tree in 81b86fb74 on 2008-01-03 and recently released
in beta tarballs.
BT shows:
File "/var/lib/python-support/python2.4/offlineimap/folder/Gmail.py", line 102, in processmessagesflags
attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1])
File "/var/lib/python-support/python2.4/offlineimap/imaputil.py", line 88, in imapsplit
for i in range(len(imapstring)):
TypeError: len() of unsized object
imap debug log shows:
imap: 29:58.16 > BEAL75 UID STORE 4887 +FLAGS (\Deleted)
imap: 29:58.47 < BEAL75 OK Success
imap: 29:58.47 matched r'(?P<tag>BEAL\d+) (?P<type>[A-Z]+) (?P<data>.*)' => ('BEAL75', 'OK', 'Success')
imap: imapsplit() called with input: None
imap: imapsplit() got a non-string input; working around.
looking at code for Gmail.py processmessagesflags, comments from Ricardo
indicate it was copied from the same function in folder/IMAP.py.
However, folder/IMAP.py has checks for this, added 2002-07-12 in
5342dacc & 817a10ce. Suspect that Gmail author believed those checks
superfluous for Gmail.
Copied checks from folder/IMAP.py to folder/Gmail.py.
2008-03-08 14:47:53 +01:00
|
|
|
if result == None:
|
|
|
|
# Compensate for servers that don't return anything from
|
|
|
|
# STORE.
|
|
|
|
continue
|
2008-01-03 04:56:55 +01:00
|
|
|
attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1])
|
Attempt to fix a crashing bug in Gmail driver
refs deb#469598
Bug report received in Debian. User is having occasional trouble with Gmail
driver crashing, indicates problem may have started after switching to Gmail.
Gmail driver merged to tree in 81b86fb74 on 2008-01-03 and recently released
in beta tarballs.
BT shows:
File "/var/lib/python-support/python2.4/offlineimap/folder/Gmail.py", line 102, in processmessagesflags
attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1])
File "/var/lib/python-support/python2.4/offlineimap/imaputil.py", line 88, in imapsplit
for i in range(len(imapstring)):
TypeError: len() of unsized object
imap debug log shows:
imap: 29:58.16 > BEAL75 UID STORE 4887 +FLAGS (\Deleted)
imap: 29:58.47 < BEAL75 OK Success
imap: 29:58.47 matched r'(?P<tag>BEAL\d+) (?P<type>[A-Z]+) (?P<data>.*)' => ('BEAL75', 'OK', 'Success')
imap: imapsplit() called with input: None
imap: imapsplit() got a non-string input; working around.
looking at code for Gmail.py processmessagesflags, comments from Ricardo
indicate it was copied from the same function in folder/IMAP.py.
However, folder/IMAP.py has checks for this, added 2002-07-12 in
5342dacc & 817a10ce. Suspect that Gmail author believed those checks
superfluous for Gmail.
Copied checks from folder/IMAP.py to folder/Gmail.py.
2008-03-08 14:47:53 +01:00
|
|
|
if not ('UID' in attributehash and 'FLAGS' in attributehash):
|
|
|
|
# Compensate for servers that don't return a UID attribute.
|
|
|
|
continue
|
2008-01-03 04:56:55 +01:00
|
|
|
flags = attributehash['FLAGS']
|
|
|
|
uid = long(attributehash['UID'])
|
|
|
|
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
|
|
|
|
try:
|
|
|
|
needupdate.remove(uid)
|
|
|
|
except ValueError: # Let it slide if it's not in the list
|
|
|
|
pass
|
|
|
|
for uid in needupdate:
|
|
|
|
if operation == '+':
|
|
|
|
for flag in flags:
|
|
|
|
if not flag in self.messagelist[uid]['flags']:
|
|
|
|
self.messagelist[uid]['flags'].append(flag)
|
|
|
|
self.messagelist[uid]['flags'].sort()
|
|
|
|
elif operation == '-':
|
|
|
|
for flag in flags:
|
|
|
|
if flag in self.messagelist[uid]['flags']:
|
|
|
|
self.messagelist[uid]['flags'].remove(flag)
|