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

Source Code for Module ProcImap.ImapMailbox

  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 provides the ImapMailbox class, which is a wrapper around 
 22      the imaplib module of the standard library, and a full implementation 
 23      of the mailbox.Mailbox interface. 
 24  """ 
 25   
 26  import imaplib 
 27  from email.generator import Generator 
 28  from cStringIO import StringIO 
 29  from mailbox import Mailbox 
 30  from mailbox import Message 
 31   
 32  from ProcImap.ImapServer import ImapServer 
 33  from ProcImap.ImapMessage import ImapMessage 
 34   
 35   
 36  FIX_BUGGY_IMAP_FROMLINE = False # I used this for the standard IMAP server 
 37                                  # on SuSe Linux, which seems to be extremely 
 38                                  # buggy, and responds to requests for messages 
 39                                  # with an escaped(!) envelope-header. Don't 
 40                                  # use that server! 
 41   
 42   
 43   
44 -class ImapNotOkError(Exception):
45 """ Raised if the imap server returns a non-OK status on any request """ 46 pass
47
48 -class NoSuchUIDError(Exception):
49 """ Raised if a message is requested with a non-existing UID """ 50 pass
51
52 -class ReadOnlyError(Exception):
53 """ Raised if you try to make a change to a mailbox that was opened 54 read-only """ 55 pass
56
57 -class NotSupportedError(Exception):
58 """ Raised if a method is called that the Mailbox interface demands, 59 but that cannot be surported in IMAP 60 """ 61 pass
62
63 -class ServerNotAvailableError(Exception):
64 """ Raised if you try to open a ImapMailbox using an instance of ImapServer 65 that is already used for another ImapMailbox """ 66 pass
67 68 69
70 -class ImapMailbox(object, Mailbox):
71 """ An abstract representation of a mailbox on an IMAP Server. 72 This class implements the mailbox.Mailbox interface, insofar 73 possible. Methods for changing a message in-place are not 74 available for IMAP; the 'update' and '__setitem__' methods 75 will raise a NotSupportedError. 76 By default deleting messages (discard/remove) just adds the 77 \\Deleted flag to the message. Optionally, you can define a 78 trash folder for any ImapMailbox. If set, "deleted" messages 79 will be moved to the trash folder. Note that you must set the 80 trash folder to '[Gmail]/Trash' if you are using Gmail, as 81 the Gmail IMAP server has a different interpretation of what 82 deletion means. 83 84 The class specific attributes are: 85 86 name name of the mailbox (readonly, see below) 87 server ImapServer object (readonly, see below) 88 trash Trash folder 89 readonly True if mailbox is readonly, false otherwise 90 91 The 'trash' attribute may a string, another instance 92 of ImapMailbox, or an instance of mailbox.Mailbox. 93 If not set, it is None. 94 95 If the 'readonly' attribute is set, all subsequent calls that would 96 change the mailbox will raise a ReadOnlyError. Note that setting the 97 readonly attribute does not prevent you from making changes through 98 the methods of the server attribute. 99 """
100 - def __init__(self, path, factory=ImapMessage, readonly=False, create=True):
101 """ Initialize an ImapMailbox 102 path is a tuple with two elements, consisting of 103 1) an instance of ImapServer in any state 104 2) the name of a mailbox on the server as a string 105 If the mailbox does not exist, it is created unless 106 create is set to False, in which case NoSuchMailboxError 107 is raised. 108 The 'factory' parameter determines to which type the 109 messages in the mailbox should be converted. 110 111 Note that two instances of ImapMailbox can never share the 112 same instance of server. If you try to create an ImapMailbox 113 with an instance of ImapServer that you already used for 114 another mailbox, a ServerNotAvailableError will be thrown. 115 """ 116 # not calling Mailbox.__init__(self) is on purpose: 117 # my definition of 'path' is incompatibel 118 self._factory = factory 119 try: 120 (server, name) = path 121 except: 122 raise TypeError, "path must be a tuple, consisting of an "\ 123 + " instance of ImapServer and a string" 124 if isinstance(server, ImapServer): 125 if hasattr(server, 'locked') and server.locked: 126 raise ServerNotAvailableError, "This instance of ImapServer"\ 127 + " is already in use for another mailbox" 128 self._server = server 129 else: 130 raise TypeError, "path must be a tuple, consisting of an "\ 131 + " instance of ImapServer and a string" 132 if not isinstance(name, str): 133 raise TypeError("path must be a tuple, consisting of an "\ 134 + " instance of ImapServer and a string") 135 self._server.select(name, create) 136 self._cached_uid = None 137 self._cached_mailbox = None 138 self._cached_text = None 139 self.trash = None 140 self.readonly = readonly 141 server.locked = True
142 143 name = property(lambda self: self._server.mailboxname, None, 144 doc="Name of the mailbox on the server") 145 146 server = property(lambda self: self._server, None, 147 doc="Instance of the ImapServer that is being used as a backend") 148
149 - def reconnect(self):
150 """ Renew the connection to the mailbox """ 151 name = self.name 152 self._server.reconnect() 153 self._server.login() 154 try: 155 self._server.select(name) 156 except: 157 # for some reason I have to do the whole thing twice if the 158 # connection was really broken. I'm getting an exception the 159 # first time. Not completely sure what's going on. 160 self._server.reconnect() 161 self._server.login() 162 self._server.select(name)
163 164
165 - def switch(self, name, readonly=False, create=False):
166 """ Switch to a different Mailbox on the same server """ 167 self.flush() 168 if not isinstance(name, str): 169 raise TypeError("name must be the name of a mailbox " \ 170 + "as a string") 171 self._server.select(name, create) 172 self._cached_uid = None 173 self._cached_text = None 174 self.readonly = readonly
175
176 - def search(self, criteria='ALL', charset=None ):
177 """ Return a list of all the UIDs in the mailbox (as integers) 178 that match the search criteria. See documentation 179 of imaplib and/or RFC3501 for details. 180 Raise ImapNotOkError if a non-OK response is received from 181 the server or if the response cannot be parsed into a list 182 of integers. 183 184 charset indicates the charset 185 of the strings that appear in the search criteria. 186 187 In all search keys that use strings, a message matches the key if 188 the string is a substring of the field. The matching is 189 case-insensitive. 190 191 The defined search keys are as follows. Refer to RFC 3501 for detailed definitions of the 192 arguments. 193 194 <sequence set> 195 ALL 196 ANSWERED 197 BCC <string> 198 BEFORE <date> 199 BODY <string> 200 CC <string> 201 DELETED 202 DRAFT 203 FLAGGED 204 FROM <string> 205 HEADER <field-name> <string> 206 KEYWORD <flag> 207 LARGER <n> 208 NEW 209 NOT <search-key> 210 OLD 211 ON <date> 212 OR <search-key1> <search-key2> 213 RECENT 214 SEEN 215 SENTBEFORE <date> 216 SENTON <date> 217 SENTSINCE <date> 218 SINCE <date> 219 SMALLER <n> 220 SUBJECT <string> 221 TEXT <string> 222 TO <string> 223 UID <sequence set> 224 UNANSWERED 225 UNDELETED 226 UNDRAFT 227 UNFLAGGED 228 UNKEYWORD <flag> 229 UNSEEN 230 231 Example: search('FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith"') 232 search('TEXT "string not in mailbox"') 233 """ 234 (code, data) = self._server.uid('search', charset, "(%s)" % criteria) 235 uidlist = data[0].split() 236 if code != 'OK': 237 raise ImapNotOkError, "%s in search" % code 238 try: 239 return [int(uid) for uid in uidlist] 240 except ValueError: 241 raise ImapNotOkError, "received unparsable response."
242
243 - def get_unseen_uids(self):
244 """ Get a list of all the unseen UIDs in the mailbox 245 Equivalent to search(None, "UNSEEN UNDELETED") 246 """ 247 return(self.search("UNSEEN UNDELETED"))
248
249 - def get_all_uids(self):
250 """ Get a list of all the undeleted UIDs in the mailbox 251 (as integers). 252 Equivalent to search(None, "UNDELETED") 253 """ 254 return(self.search("UNDELETED"))
255
256 - def _cache_message(self, uid):
257 """ Download the RFC822 text of the message with UID and put 258 in in the cache. Return the RFC822 text of the message. If the 259 message is already in the cache, it is returned directly. 260 Raise KeyError if there if there is no message with that UID. 261 """ 262 if (self._cached_uid != uid) or (self._cached_mailbox != self.name): 263 try: 264 (code, data) = self._server.uid('fetch', uid, "(RFC822)") 265 if code != 'OK': 266 raise ImapNotOkError, "%s in fetch_message(%s)" \ 267 % (code, uid) 268 try: 269 rfc822string = data[0][1] 270 except TypeError: 271 raise KeyError, "No message %s in _cache_message" % uid 272 except MemoryError: 273 # this happens sometimes for unknown reasons. Try do download 274 # in chunks instead 275 self.reconnect() 276 size = self.get_size(uid) 277 octets_read = 0 278 chunksize = 204800 279 chunks = [] 280 while octets_read < size: 281 attempts = 0 282 while True: 283 try: 284 (code, data) = self._server.uid('fetch', uid, 285 "(BODY[]<%s.%s>)" % (octets_read, chunksize)) 286 if code != 'OK': 287 raise ImapNotOkError, "%s in fetch_message(%s)"\ 288 % (code, uid) 289 break 290 except: 291 self.reconnect() 292 attempts += 1 293 continue 294 if attempts > 10: 295 break 296 chunksize = chunksize / (attempts + 1) 297 try: 298 chunks.append(data[0][1]) 299 except TypeError: 300 raise KeyError, "No message %s in _cache_message" % uid 301 octets_read += chunksize 302 rfc822string = ''.join(chunks) 303 if FIX_BUGGY_IMAP_FROMLINE: 304 if rfc822string.startswith(">From "): 305 rfc822string = rfc822string[rfc822string.find("\n")+1:] 306 self._cached_uid = uid 307 self._cached_mailbox = self.name 308 self._cached_text = rfc822string 309 return self._cached_text
310
311 - def get_message(self, uid):
312 """ Return an ImapMessage object created from the message with UID. 313 Raise KeyError if there if there is no message with that UID. 314 """ 315 rfc822string = self._cache_message(uid) 316 result = ImapMessage(rfc822string) 317 result.set_imapflags(self.get_imapflags(uid)) 318 result.internaldate = self.get_internaldate(uid) 319 result.size = self.get_size(uid) 320 if self._factory is ImapMessage: 321 return result 322 return self._factory(result)
323
324 - def __getitem__(self, uid):
325 """ Return an ImapMessage object created from the message with UID. 326 Raise KeyError if there if there is no message with that UID. 327 """ 328 return self.get_message(uid)
329
330 - def get(self, uid, default=None):
331 """ Return an ImapMessage object created from the message with UID. 332 Return default if there is no message with that UID. 333 """ 334 try: 335 return self[uid] 336 except KeyError: 337 return default
338
339 - def get_string(self, uid):
340 """ Return a RFC822 string representation of the message 341 corresponding to key, or raise a KeyError exception if no 342 such message exists. 343 """ 344 return self._cache_message(uid)
345
346 - def get_file(self, uid):
347 """ Return a cStringIO.StringIO of the message corresponding 348 to key, or raise a KeyError exception if no such message 349 exists. 350 """ 351 return StringIO(self._cache_message(uid))
352
353 - def has_key(self, uid):
354 """ Return True if key corresponds to a message, False otherwise. 355 """ 356 return (uid in self.search('ALL'))
357
358 - def __contains__(self, uid):
359 """ Return True if key corresponds to a message, False otherwise. 360 """ 361 return self.has_key(uid)
362
363 - def __len__(self):
364 """ Return a count of messages in the mailbox. """ 365 return len(self.search('ALL'))
366
367 - def clear(self):
368 """ Delete all messages from the mailbox and expunge""" 369 if self.readonly: 370 raise ReadOnlyError, "Tried to clear read-only mailbox" 371 for uid in self.get_all_uids(): 372 self.discard(uid) 373 self.expunge()
374
375 - def pop(self, uid, default=None):
376 """ Return a representation of the message corresponding to key, 377 delete and expunge the message. If no such message exists, 378 return default if it was supplied (i.e. is not None) or else 379 raise a KeyError exception. The message is represented as an 380 instance of ImapMessage unless a custom message factory was 381 specified when the Mailbox instance was initialized. 382 """ 383 if self.readonly: 384 raise ReadOnlyError, "Tried to pop read-only mailbox" 385 try: 386 message = self[uid] 387 del self[uid] 388 self.expunge() 389 return message 390 except KeyError: 391 if default is not None: 392 return default 393 else: 394 raise KeyError, "No such UID"
395
396 - def popitem(self):
397 """ Return an arbitrary (key, message) pair, where key is a key 398 and message is a message representation, delete and expunge 399 the corresponding message. If the mailbox is empty, raise a 400 KeyError exception. The message is represented as an instance 401 of ImapMessage unless a custom message factory was specified 402 when the Mailbox instance was initialized. 403 """ 404 if self.readonly: 405 raise ReadOnlyError, "Tried to pop item from read-only mailbox" 406 self.expunge() 407 uids = self.search("ALL") 408 if len(uids) > 0: 409 uid = uids[0] 410 result = (uid, self[uid]) 411 del self[uid] 412 self.expunge() 413 return result 414 else: 415 raise KeyError, "Mailbox is empty"
416
417 - def update(self, arg=None):
418 """ Parameter arg should be a key-to-message mapping or an iterable 419 of (key, message) pairs. Updates the mailbox so that, for each 420 given key and message, the message corresponding to key is set 421 to message as if by using __setitem__(). 422 This operation is not supported for IMAP mailboxes and will 423 raise NotSupportedError 424 """ 425 raise NotSupportedError, "Updating items in IMAP not supported"
426 427
428 - def flush(self):
429 """ Equivalent to expunge() """ 430 if not self.readonly: 431 self.expunge()
432
433 - def lock(self):
434 """ Do nothing """ 435 pass
436
437 - def unlock(self):
438 """ Do nothing """ 439 pass
440
441 - def get_header(self, uid):
442 """ Return an ImapMessage object containing only the Header 443 of the message with UID. 444 Raise KeyError if there if there is no message with that UID. 445 """ 446 (code, data) = self._server.uid('fetch', uid, "(BODY.PEEK[HEADER])") 447 if code != 'OK': 448 raise ImapNotOkError, "%s in fetch_header(%s)" % (code, uid) 449 try: 450 rfc822string = data[0][1] 451 except TypeError: 452 raise KeyError, "No UID %s in get_header" % uid 453 result = ImapMessage(rfc822string) 454 result.set_imapflags(self.get_imapflags(uid)) 455 result.internaldate = self.get_internaldate(uid) 456 result.size = self.get_size(uid) 457 if self._factory is ImapMessage: 458 return result 459 return self._factory(result)
460
461 - def get_fields(self, uid, fields):
462 """ Return an mailbox.Message object containing only the requested 463 header fields of the message with UID. 464 The fields parameter is a string ofheader fields seperated by 465 spaces, e.g. 'From SUBJECT date' 466 Raise KeyError if there if there is no message with that UID. 467 """ 468 (code, data) = self._server.uid('fetch', uid, 469 "(BODY.PEEK[HEADER.FIELDS (%s)])" 470 % fields) 471 if code != 'OK': 472 raise ImapNotOkError, "%s in fetch_header(%s)" % (code, uid) 473 try: 474 rfc822string = data[0][1] 475 except TypeError: 476 raise KeyError, "No UID %s in get_fields" % uid 477 result = Message(rfc822string) 478 return result
479 480
481 - def get_size(self, uid):
482 """ Get the number of bytes contained in the message with UID """ 483 try: 484 (code, data) = self._server.uid('fetch', uid, '(RFC822.SIZE)') 485 sizeresult = data[0] 486 if code != 'OK': 487 raise ImapNotOkError, "%s in get_imapflags(%s)" % (code, uid) 488 if sizeresult is None: 489 raise NoSuchUIDError, "No message %s in get_size" % uid 490 startindex = sizeresult.find('SIZE') + 5 491 stopindex = sizeresult.find(' ', startindex) 492 return int(sizeresult[startindex:stopindex]) 493 except (TypeError, ValueError): 494 raise ValueError, "Unexpected results while fetching flags " \ 495 + "from server for message %s" % uid
496
497 - def get_imapflags(self, uid):
498 """ Return a list of imap flags for the message with UID 499 Raise exception if there if there is no message with that UID. 500 """ 501 try: 502 (code, data) = self._server.uid('fetch', uid, '(FLAGS)') 503 flagresult = data[0] 504 if code != 'OK': 505 raise ImapNotOkError, "%s in get_imapflags(%s)" % (code, uid) 506 return list(imaplib.ParseFlags(flagresult)) 507 except (TypeError, ValueError): 508 raise ValueError, "Unexpected results while fetching flags " \ 509 + "from server for message %s; response was (%s, %s)" \ 510 % (uid, code, data)
511
512 - def get_internaldate(self, uid):
513 """ Return a time tuple representing the internal date for the 514 message with UID 515 Raise exception if there if there is no message with that UID. 516 """ 517 try: 518 (code, data) = self._server.uid('fetch', uid, '(INTERNALDATE)') 519 dateresult = data[0] 520 if code != 'OK': 521 raise ImapNotOkError, "%s in get_internaldate(%s)" % (code, uid) 522 if dateresult is None: 523 raise NoSuchUIDError, "No message %s in get_internaldate" % uid 524 return imaplib.Internaldate2tuple(dateresult) 525 except (TypeError, ValueError): 526 raise ValueError, "Unexpected results while fetching flags " \ 527 + "from server for message %s" % uid
528 529
530 - def __eq__(self, other):
531 """ Equality test: 532 mailboxes are equal if they are equal in server and name 533 """ 534 if not isinstance(other, ImapMailbox): 535 return False 536 return ( (self._server == other.server) \ 537 and (self.name == other.name) \ 538 )
539
540 - def __ne__(self, other):
541 """ Inequality test: 542 mailboxes are unequal if they are not equal 543 """ 544 return (not (self == other))
545
546 - def copy(self, uid, targetmailbox, exact=False):
547 """ Copy the message with UID to the targetmailbox and try to return 548 the key that was assigned to the copied message in the 549 targetmailbox. If targetmailbox is an ImapMailbox, this is 550 the target-UID. 551 targetmailbox can be a string (the name of a mailbox on the 552 same imap server), any of mailbox.Mailbox. Note that not all 553 imap flags will be preserved if the targetmailbox is not on 554 an ImapMailbox. Copying is efficient (i.e. the message is not 555 downloaded) if the targetmailbox is on the same server. 556 Do nothing and return None if there if there is no message with 557 that UID. 558 Unless 'exact' is set to True, the return value will be None if 559 the targetmailbox is an ImapMailbox. This is because finding out 560 the new UID of the copied message on an IMAP server is non-trivial. 561 Giving 'exact' as True means that additional work will be done to 562 find the accurate result. This operation can be relatively 563 expensive. If targetmailbox is not an ImapMailbox, the value of 564 'exact' is irrelevant, and the return value will always be 565 accurate. 566 """ 567 result = None 568 if isinstance(targetmailbox, ImapMailbox): 569 if targetmailbox.server == self._server: 570 targetmailbox = targetmailbox.name # set as string 571 if isinstance(targetmailbox, Mailbox): 572 if self != targetmailbox: 573 targetmailbox.lock() 574 result = targetmailbox.add(self[uid]) 575 if isinstance(targetmailbox, ImapMailbox): 576 result = None 577 targetmailbox.flush() 578 if exact: 579 pass 580 # TODO: get more exact result 581 targetmailbox.unlock() 582 elif isinstance(targetmailbox, str): 583 if targetmailbox != self.name: 584 (code, data) = self._server.uid('copy', uid, targetmailbox) 585 if code != 'OK': 586 raise ImapNotOkError, "%s in copy: %s" % (code, data) 587 if exact: 588 pass 589 # TODO: get more exact result 590 591 else: 592 return uid 593 else: 594 raise TypeError, "targetmailbox in copy is of unknown type." 595 return result
596 597 598
599 - def move(self, uid, targetmailbox, exact=False):
600 """ Copy the message with UID to the targetmailbox, delete it in the 601 original mailbox, and try to return the key that was assigned to 602 the copied message in the targetmailbox. 603 The discussions of the copy method concerning 'targetmailbox' and 604 'exact' apply here as well. 605 Do nothing and return None if there if there is no message with that UID. 606 """ 607 result = None 608 if self.readonly: 609 raise ReadOnlyError, "Tried to move message from read-only mailbox" 610 if (targetmailbox != self) and (targetmailbox != self.name): 611 result = self.copy(uid, targetmailbox, exact) 612 (code, data) = self._server.uid('store', uid, \ 613 '+FLAGS', "(\\Deleted)") 614 if code != 'OK': 615 raise ImapNotOkError, "%s in move: %s" % (code, data) 616 else: 617 return uid 618 return result
619 620
621 - def discard(self, uid, exact=False):
622 """ If trash folder is defined, move the message with UID to 623 trash and try to return the key assigned to the message in the 624 trash; else, just add the \Deleted flag to the message with UID and 625 return None. 626 If a trash folder is defined, this method is equivalent to 627 self.move(uid, self.trash). The discussions of the move/copy method 628 apply. 629 Do nothing and return None if there if there is no message with 630 that UID. 631 """ 632 result = None 633 if self.readonly: 634 raise ReadOnlyError, "Tried to discard from read-only mailbox" 635 if self.trash is None: 636 self.add_imapflag(uid, "\\Deleted") 637 else: 638 print "Moving to %s" % self.trash 639 return self.move(uid, self.trash, exact) 640 return result
641
642 - def remove(self, uid, exact=False):
643 """ Discard the message with UID. 644 If there is no message with that UID, raise a KeyError 645 This is exactly equivalent to self.discard(uid), except for 646 the KeyError exception. 647 """ 648 if self.readonly: 649 raise ReadOnlyError, "Tried to remove from read-only mailbox" 650 if uid not in self.search("ALL"): 651 raise KeyError, "No UID %s" % uid 652 return self.discard(uid, exact)
653
654 - def __delitem__(self, uid):
655 """ Discard the message with UID. 656 If there is no message with that UID, raise a KeyError 657 """ 658 self.remove(uid)
659
660 - def __setitem__(self, uid, message):
661 """ Replace the message corresponding to key with message. 662 This operation is not supported for IMAP mailboxes 663 and will raise NotSupportedError 664 """ 665 raise NotSupportedError, "Setting items in IMAP not supported"
666
667 - def iterkeys(self):
668 """ Return an iterator over all UIDs 669 This is an iterator over the list of UIDs at the time iterkeys() 670 is a called. 671 """ 672 return iter(self.search("ALL"))
673
674 - def keys(self):
675 """ Return a list of all UIDs """ 676 return self.search("ALL")
677
678 - def itervalues(self):
679 """ Return an iterator over all messages. The messages are 680 represented as instances of ImapMessage unless a custom message 681 factory was specified when the Mailbox instance was initialized. 682 """ 683 for uid in self.search("ALL"): 684 yield self[uid]
685
686 - def __iter__(self):
687 """ Return an iterator over all messages. 688 Identical to itervalues 689 """ 690 return self.itervalues()
691
692 - def values(self):
693 """ Return a list of all messages 694 The messages are represented as instances of ImapMessage unless 695 a custom message factory was specified when the Mailbox instance 696 was initialized. 697 Beware that this method can be extremely expensive in terms 698 of time, bandwidth, and memory. 699 """ 700 messagelist = [] 701 for message in self: 702 messagelist.append(message) 703 return messagelist
704
705 - def iteritems(self):
706 """ Return an iterator over (uid, message) pairs, 707 where uid is a key and message is a message representation. 708 """ 709 for uid in self.keys(): 710 yield((uid, self[uid]))
711
712 - def items(self):
713 """ Return a list (uid, message) pairs, 714 where uid is a key and message is a message representation. 715 Beware that this method can be extremely expensive in terms 716 of time, bandwidth, and memory. 717 """ 718 result = [] 719 for uid in self.keys(): 720 result.append((uid, self[uid])) 721 return result
722
723 - def add(self, message):
724 """ Add the message to mailbox. 725 Message can be an instance of email.Message.Message 726 (including instaces of mailbox.Message and its subclasses ); 727 or an open file handle or a string containing an RFC822 message. 728 Return the highest UID in the mailbox, which should be, but 729 is not guaranteed to be, the UID of the message that was added. 730 Raise ImapNotOkError if a non-OK response is received from 731 the server 732 """ 733 if self.readonly: 734 raise ReadOnlyError, "Tried to add to a read-only mailbox" 735 message = ImapMessage(message) 736 flags = message.flagstring() 737 date_time = message.internaldatestring() 738 memoryfile = StringIO() 739 generator = Generator(memoryfile, mangle_from_=False) 740 generator.flatten(message) 741 message_str = memoryfile.getvalue() 742 (code, data) = self._server.append(self.name, flags, \ 743 date_time, message_str) 744 if code != 'OK': 745 raise ImapNotOkError, "%s in add: %s" % (code, data) 746 try: 747 return self.get_all_uids()[-1] 748 except IndexError: 749 return 0
750 751
752 - def add_imapflag(self, uid, *flags):
753 """ Add imap flag to message with UID. 754 """ 755 if self.readonly: 756 raise ReadOnlyError, \ 757 "Tried to add imap flag for message in read-only mailbox" 758 for flag in flags: 759 (code, data) = self._server.uid('store', uid, '+FLAGS', \ 760 "(%s)" % flag ) 761 if code != 'OK': 762 raise ImapNotOkError, "%s in add_flags(%s, %s): %s" \ 763 % (uid, flag, code, data)
764
765 - def remove_imapflag(self, uid, *flags):
766 """ Remove imap flags from message with UID 767 """ 768 if self.readonly: 769 raise ReadOnlyError, \ 770 "Tried to remove imap flag from message in read-only mailbox" 771 for flag in flags: 772 (code, data) = self._server.uid('store', uid, '-FLAGS', \ 773 "(%s)" % flag ) 774 if code != 'OK': 775 raise ImapNotOkError, "%s in remove_flag(%s, %s): %s" \ 776 % (uid, flag, code, data)
777
778 - def set_imapflags(self, uid, flags):
779 """ Set imap flags for message with UID 780 flags must be an iterable of flags, or a string. 781 If flags is a string, it is taken as the single flag 782 to be set. 783 """ 784 if self.readonly: 785 raise ReadOnlyError, \ 786 "Tried to set imap flags for message in read-only mailbox" 787 if isinstance(flags, str): 788 flags = [flags] 789 flagstring = "(%s)" % ' '.join(flags) 790 (code, data) = self._server.uid('store', uid, 'FLAGS', flagstring ) 791 if code != 'OK': 792 raise ImapNotOkError, "%s in set_imapflags(%s, %s): %s" \ 793 % (code, uid, flags, data)
794
795 - def close(self):
796 """ Flush mailbox, close connection to server """ 797 self.flush() 798 self._server.close() 799 self._server.logout() 800 if hasattr(self._server, 'locked'): 801 del self._server.locked
802
803 - def expunge(self):
804 """ Expunge the mailbox (delete all messages marked for deletion)""" 805 if self.readonly: 806 raise ReadOnlyError, "Tried to expunge read-only mailbox" 807 self._server.expunge()
808