Package ProcImap :: Module ImapServer
[hide private]
[frames] | no frames]

Source Code for Module ProcImap.ImapServer

  1  ############################################################################ 
  2  #    Copyright (C) 2008 by Michael Goerz                                   # 
  3  #    http://www.physik.fu-berlin.de/~goerz                                 # 
  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 3 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                         # 
 17  #    Free Software Foundation, Inc.,                                       # 
 18  #    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             # 
 19  ############################################################################ 
 20   
 21  """ This module contains a wrapper around an IMAP Server. It only implements 
 22      the minimum methods needed to work with UID. You'll have to use the 
 23      uid command for almost everything, the commands for the "regular" IDs 
 24      are not supported. 
 25  """ 
 26   
 27  STANDARD_IMAPLIB = True    # this module can use the multithreaded imaplib2 
 28                              # replacement of the standard imaplib module. 
 29                              # If you find imaplib2 to cause problems, you can 
 30                              # switch to to the standard library module 
 31                              # As a consequence, the idle command will not 
 32                              # be available (it will be replace by a dummy). 
 33   
 34  IDLE_TIMEOUT = 120 # max time to wait in the idle command (this must be less 
 35                     # than 29 minutes) 
 36   
 37  if STANDARD_IMAPLIB: 
 38      import imaplib 
 39      IDLE_TIMEOUT = 5 # that's how long we sleep in the bogus idle 
 40  else: 
 41      # imaplib2 from http://www.cs.usyd.edu.au/~piers/python/imaplib2 
 42      # enables idle command 
 43      import imaplib2 as imaplib 
 44   
 45  import time 
 46  import re 
 47   
 48   
49 -class ClosedMailboxError(Exception):
50 """ Raised if a method is called on a closed mailbox """ 51 pass
52
53 -class NoSuchMailboxError(Exception):
54 """ Raised if a non-existing mailbox is opened """ 55 pass
56
57 -class ImapServer:
58 """ A small lowlevel representation of an imap server 59 60 Public attributes are: 61 servername address of the server 62 username authentication username 63 password authentication password 64 port server port 65 mailboxname currently active mailbox on the server 66 """ 67
68 - def __init__(self, servername, username, password, ssl=True, port=None):
69 """ Initialize the IMAP Server, connect and log in. 70 If you leave the port unspecified, the default port will be used. 71 This is port 143 is ssl is disabled, and port 933 if ssl is 72 enabled. 73 """ 74 self.servername = servername 75 self.username = username 76 self.password = password 77 self.port = port # if None, connect() will set this 78 self.ssl = ssl 79 self._server = None 80 self._flags = { 81 'connected' : False, # connected? connect/disconnect 82 'logged_in' : False, # authenticated? login/logout 83 'open' : False # opened a mailbox? select/close 84 } 85 self.mailboxname = None 86 self.connect() 87 self.login()
88
89 - def clone(self):
90 """ Return a new instance of ImapServer that points to the same server, 91 in a connected and authenticated state. 92 93 >>> server1 = ImapServer('localhost','user','secret') 94 >>> server2 = ImapServer('localhost','user','secret') 95 is equivalant to 96 >>> server1 = ImapServer('localhost','user','secret') 97 >>> server2 = server1.clone() 98 """ 99 return ImapServer(self.servername, self.username, 100 self.password, self.ssl, self.port)
101
102 - def connect(self):
103 """ Connect to servername """ 104 if self.ssl: 105 if self.port is None: 106 self.port = 993 107 self._server = imaplib.IMAP4_SSL(self.servername, self.port) 108 else: 109 if self.port is None: 110 self.port = 143 111 self._server = imaplib.IMAP4(self.servername, self.port) 112 self._flags['connected'] = True
113
114 - def disconnect(self):
115 """ Disconnect from the server 116 If looged in, log out 117 """ 118 if self._flags['logged_in']: 119 self.logout() 120 self._server = None 121 self._flags['connected'] = False
122 123
124 - def login(self):
125 """ Identify the client using a plaintext password. 126 The password will be quoted. 127 Connect to the server is not connected already. If any problems 128 occur during a login attempt, this method may cause a reconnect 129 to the server. 130 """ 131 if not self._flags['connected']: 132 self.connect() 133 if not self._flags['logged_in']: 134 try: 135 result = self._server.login(self.username, self.password) 136 except: 137 self.reconnect() 138 result = self._server.login(self.username, self.password) 139 self._flags['logged_in'] = True 140 return result
141
142 - def reconnect(self):
143 """ Close and then reopen the connection to the server """ 144 try: 145 self.disconnect() 146 except: 147 pass 148 self.connect()
149
150 - def idle(self, timeout=IDLE_TIMEOUT):
151 """ Put server into IDLE mode until server notifies some change, 152 or 'timeout' (secs) occurs (default: 29 minutes), 153 or another IMAP4 command is scheduled. 154 """ 155 if STANDARD_IMAPLIB: 156 time.sleep(IDLE_TIMEOUT) 157 else: 158 return self._server.idle(timeout)
159
160 - def create(self, name):
161 """ Create new mailbox """ 162 return self._server.create(name)
163
164 - def delete(self, name):
165 """ Delete old mailbox """ 166 return self._server.delete(name)
167
168 - def subscribe(self, name):
169 """ Subscribe to new mailbox """ 170 return self._server.subscribe(name)
171
172 - def unsubscribe(self, name):
173 """ Unsubscribe from old mailbox """ 174 return self._server.unsubscribe(name)
175
176 - def logout(self):
177 """ Shutdown connection to server. Returns server "BYE" 178 response. 179 """ 180 if self._flags['open']: 181 self.close() 182 self._flags['logged_in'] = False 183 return self._server.logout()
184
185 - def append(self, mailbox, flags, date_time, messagestr):
186 """ Append message to named mailbox. All parameters are strings which 187 need to be in the appropriate format as described in RFC3501""" 188 if not self._flags['open']: 189 raise ClosedMailboxError, "called append on closed mailbox" 190 flags = flags.replace("\\Recent", '') 191 return self._server.append(mailbox, flags, date_time, messagestr)
192
193 - def uid(self, command, *args):
194 """ uid(command, arg[, ...]) 195 Execute command with messages identified by UID. 196 Returns response appropriate to command. 197 """ 198 if not self._flags['open']: 199 raise ClosedMailboxError, "called uid on closed mailbox" 200 return self._server.uid(command, *args)
201
202 - def expunge(self):
203 """ Permanently remove deleted items from selected mailbox. 204 Generates an "EXPUNGE" response for each deleted message. 205 Returned data contains a list of "EXPUNGE" message numbers 206 in order received. 207 """ 208 if not self._flags['open']: 209 raise ClosedMailboxError, "called expunge on closed mailbox" 210 return self._server.expunge()
211
212 - def close(self):
213 """ Close currently selected mailbox. Deleted messages are 214 removed from writable mailbox. This is the recommended 215 command before "LOGOUT".""" 216 self._flags['open'] = False 217 self.mailboxname = None 218 return self._server.close()
219
220 - def select(self, mailbox = 'INBOX', create=False):
221 """ Select a mailbox. Log in if not logged in already. 222 Return number of messages in mailbox if successful. 223 If the mailbox does not exist, create it if 'create' is True, 224 else raise NoSuchMailboxError. 225 The name of the mailbox will be stored in the mailboxname 226 attribute if selection was successful 227 """ 228 if not self._flags['logged_in']: 229 self.login() 230 data = self._server.select(mailbox) 231 code = data[0] 232 count = data[1][0] 233 if code == 'OK': 234 self._flags['open'] = True 235 self.mailboxname = mailbox 236 return int(count) 237 else: 238 if create: 239 self.create(mailbox) 240 self.select(mailbox, create=False) 241 else: 242 raise NoSuchMailboxError, "mailbox %s does not exist." \ 243 % mailbox
244
245 - def list(self):
246 """ Return list mailbox names, or None if the server does 247 not send an 'OK' reply. 248 """ 249 if not self._flags['logged_in']: 250 raise ClosedMailboxError, "called list before logging in" 251 mailbox_pattern = re.compile( 252 r'\(\\HasNoChildren\) "/" "(?P<mailboxname>.+)"') 253 code, mailboxlist = self._server.list() 254 result = [] 255 if code == 'OK': 256 for raw_mailbox in mailboxlist: 257 mailbox_match = mailbox_pattern.match(raw_mailbox) 258 if mailbox_match: 259 result.append(mailbox_match.group('mailboxname')) 260 else: 261 return None 262 return result
263
264 - def lsub(self):
265 """ List subscribed mailbox names """ 266 if not self._flags['logged_in']: 267 raise ClosedMailboxError, "called lsub before logging in" 268 return self._server.lsub()
269
270 - def __eq__(self, other):
271 """ Equality test: 272 servers are equal if they are equal in servername, username, 273 password, port, and ssl. 274 """ 275 return ( (self.servername == other.servername) \ 276 and (self.username == other.username) \ 277 and (self.password == other.password) \ 278 and (self.port == other.port) \ 279 and (self.ssl == other.ssl) \ 280 )
281
282 - def __ne__(self, other):
283 """ Inequality test: 284 servers are unequal if they are not equal 285 """ 286 return (not (self == other))
287