]> code.delx.au - offlineimap/blob - offlineimap/folder/Gmail.py
make the trash and spam folder names in Gmail accounts configurable
[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 def __init__(self, imapserver, name, visiblename, accountname, repository):
40 self.realdelete = repository.getrealdelete(name)
41 self.trash_folder = repository.gettrashfolder(name)
42 #: Gmail will really delete messages upon EXPUNGE in these folders
43 self.real_delete_folders = [ self.trash_folder, repository.getspamfolder() ]
44 IMAPFolder.__init__(self, imapserver, name, visiblename, \
45 accountname, repository)
46
47 def deletemessages_noconvert(self, uidlist):
48 uidlist = [uid for uid in uidlist if uid in self.messagelist]
49 if not len(uidlist):
50 return
51
52 if self.realdelete and not (self.getname() in self.real_delete_folders):
53 # IMAP expunge is just "remove label" in this folder,
54 # so map the request into a "move into Trash"
55
56 imapobj = self.imapserver.acquireconnection()
57 try:
58 imapobj.select(self.getfullname())
59 result = imapobj.uid('copy',
60 imaputil.listjoin(uidlist),
61 self.trash_folder)
62 assert result[0] == 'OK', \
63 "Bad IMAPlib result: %s" % result[0]
64 finally:
65 self.imapserver.releaseconnection(imapobj)
66 for uid in uidlist:
67 del self.messagelist[uid]
68 else:
69 IMAPFolder.deletemessages_noconvert(self, uidlist)
70
71 def processmessagesflags(self, operation, uidlist, flags):
72 # XXX: the imapobj.myrights(...) calls dies with an error
73 # report from Gmail server stating that IMAP command
74 # 'MYRIGHTS' is not implemented. So, this
75 # `processmessagesflags` is just a copy from `IMAPFolder`,
76 # with the references to `imapobj.myrights()` deleted This
77 # shouldn't hurt, however, Gmail users always have full
78 # control over all their mailboxes (apparently).
79 if len(uidlist) > 101:
80 # Hack for those IMAP ervers with a limited line length
81 self.processmessagesflags(operation, uidlist[:100], flags)
82 self.processmessagesflags(operation, uidlist[100:], flags)
83 return
84
85 imapobj = self.imapserver.acquireconnection()
86 try:
87 imapobj.select(self.getfullname())
88 r = imapobj.uid('store',
89 imaputil.listjoin(uidlist),
90 operation + 'FLAGS',
91 imaputil.flagsmaildir2imap(flags))
92 assert r[0] == 'OK', 'Error with store: ' + '. '.join(r[1])
93 r = r[1]
94 finally:
95 self.imapserver.releaseconnection(imapobj)
96
97 needupdate = copy(uidlist)
98 for result in r:
99 if result == None:
100 # Compensate for servers that don't return anything from
101 # STORE.
102 continue
103 attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1])
104 if not ('UID' in attributehash and 'FLAGS' in attributehash):
105 # Compensate for servers that don't return a UID attribute.
106 continue
107 flags = attributehash['FLAGS']
108 uid = long(attributehash['UID'])
109 self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
110 try:
111 needupdate.remove(uid)
112 except ValueError: # Let it slide if it's not in the list
113 pass
114 for uid in needupdate:
115 if operation == '+':
116 for flag in flags:
117 if not flag in self.messagelist[uid]['flags']:
118 self.messagelist[uid]['flags'].append(flag)
119 self.messagelist[uid]['flags'].sort()
120 elif operation == '-':
121 for flag in flags:
122 if flag in self.messagelist[uid]['flags']:
123 self.messagelist[uid]['flags'].remove(flag)