]> code.delx.au - offlineimap/blob - offlineimap/folder/Gmail.py
Attempt to fix a crashing bug in Gmail driver
[offlineimap] / offlineimap / folder / Gmail.py
1 # Gmail IMAP folder support
2 # Copyright (C) 2008 Riccardo Murri <riccardo.murri@gmail.com>
3 # Copyright (C) 2002-2007 John Goerzen <jgoerzen@complete.org>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 """Folder implementation to support features of the Gmail IMAP server.
20 """
21
22 from IMAP import IMAPFolder
23 import imaplib
24 from offlineimap import imaputil, imaplibutil
25 from offlineimap.ui import UIBase
26 from copy import copy
27
28
29 class GmailFolder(IMAPFolder):
30 """Folder implementation to support features of the Gmail IMAP server.
31 Specifically, deleted messages are moved to folder `Gmail.TRASH_FOLDER`
32 (by default: ``[Gmail]/Trash``) prior to expunging them, since
33 Gmail maps to IMAP ``EXPUNGE`` command to "remove label".
34
35 For more information on the Gmail IMAP server:
36 http://mail.google.com/support/bin/answer.py?answer=77657&topic=12815
37 """
38
39 #: Where deleted mail should be moved
40 TRASH_FOLDER='[Gmail]/Trash'
41
42 #: Gmail will really delete messages upon EXPUNGE in these folders
43 REAL_DELETE_FOLDERS = [ TRASH_FOLDER, '[Gmail]/Spam' ]
44
45 def __init__(self, imapserver, name, visiblename, accountname, repository):
46 self.realdelete = repository.getrealdelete(name)
47 IMAPFolder.__init__(self, imapserver, name, visiblename, \
48 accountname, repository)
49
50 def deletemessages_noconvert(self, uidlist):
51 uidlist = [uid for uid in uidlist if uid in self.messagelist]
52 if not len(uidlist):
53 return
54
55 if self.realdelete and not (self.getname() in self.REAL_DELETE_FOLDERS):
56 # IMAP expunge is just "remove label" in this folder,
57 # so map the request into a "move into Trash"
58
59 imapobj = self.imapserver.acquireconnection()
60 try:
61 imapobj.select(self.getfullname())
62 result = imapobj.uid('copy',
63 imaputil.listjoin(uidlist),
64 self.TRASH_FOLDER)
65 assert result[0] == 'OK', \
66 "Bad IMAPlib result: %s" % result[0]
67 finally:
68 self.imapserver.releaseconnection(imapobj)
69 for uid in uidlist:
70 del self.messagelist[uid]
71 else:
72 IMAPFolder.deletemessages_noconvert(self, uidlist)
73
74 def processmessagesflags(self, operation, uidlist, flags):
75 # XXX: the imapobj.myrights(...) calls dies with an error
76 # report from Gmail server stating that IMAP command
77 # 'MYRIGHTS' is not implemented. So, this
78 # `processmessagesflags` is just a copy from `IMAPFolder`,
79 # with the references to `imapobj.myrights()` deleted This
80 # shouldn't hurt, however, Gmail users always have full
81 # control over all their mailboxes (apparently).
82 if len(uidlist) > 101:
83 # Hack for those IMAP ervers with a limited line length
84 self.processmessagesflags(operation, uidlist[:100], flags)
85 self.processmessagesflags(operation, uidlist[100:], flags)
86 return
87
88 imapobj = self.imapserver.acquireconnection()
89 try:
90 imapobj.select(self.getfullname())
91 r = imapobj.uid('store',
92 imaputil.listjoin(uidlist),
93 operation + 'FLAGS',
94 imaputil.flagsmaildir2imap(flags))
95 assert r[0] == 'OK', 'Error with store: ' + '. '.join(r[1])
96 r = r[1]
97 finally:
98 self.imapserver.releaseconnection(imapobj)
99
100 needupdate = copy(uidlist)
101 for result in r:
102 if result == None:
103 # Compensate for servers that don't return anything from
104 # STORE.
105 continue
106 attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1])
107 if not ('UID' in attributehash and 'FLAGS' in attributehash):
108 # Compensate for servers that don't return a UID attribute.
109 continue
110 flags = attributehash['FLAGS']
111 uid = long(attributehash['UID'])
112 self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
113 try:
114 needupdate.remove(uid)
115 except ValueError: # Let it slide if it's not in the list
116 pass
117 for uid in needupdate:
118 if operation == '+':
119 for flag in flags:
120 if not flag in self.messagelist[uid]['flags']:
121 self.messagelist[uid]['flags'].append(flag)
122 self.messagelist[uid]['flags'].sort()
123 elif operation == '-':
124 for flag in flags:
125 if flag in self.messagelist[uid]['flags']:
126 self.messagelist[uid]['flags'].remove(flag)