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