def delete(self): """ Deletes the message. """ if signals.terminate(): self.processor.clean_exit() # Make sure we have this message's folder selected (UIDs should be # globally unique but may only unique in folder scope in sufficiently # broken IMAP implementations). if self.processor.selected != self.folder: self.processor.select(self.folder) try: self._processor.log("==> Deleting %s" % self.uid) self._processor.imap.uid('store', self.uid, '+FLAGS', '\\Deleted') self._processor.imap.expunge() except self._processor.imap.error as e: # Fail hard because repeated failures here can leave a mess of # messages with `Deleted` flags. self._processor.fatal_imap_error("Deleting message %s'" % self.uid, e) raise # make sure this gets purged from cache later self.processor.cache_delete[self.folder].append(self.uid)
def copy(self, folder, create=False): """ Copies the message to folder. Optionally that folder can be created if it does not exist. In that case, specify create=True. folder may either be a string or a list of path components (a list will be joined by the IMAP server's separator character). """ if signals.terminate(): self.processor.clean_exit() # Make sure the folder name is prepended with the IMAP server's prefix # where applicable. folder = self.processor.path_ensure_prefix(folder) # Make sure we have this message's folder selected (UIDs should be # globally unique but may only unique in folder scope in sufficiently # broken IMAP implementations). if self.processor.selected != self.folder: self.processor.select(self.folder) folder = self._processor.list_path(folder, sep=self._processor.separator) try: self._processor.log("==> Copying {0} to {1}".format( self.uid, folder)) status, data = self._processor.imap.uid('copy', self.uid, folder) except self._processor.imap.error as e: self._processor.fatal_imap_error( "Copying message UID %s to %s" % (self.uid, folder), e) if status == 'NO': if create and 'TRYCREATE' in data[0].decode('ascii'): self._processor.log( "==> Destination folder %s does not exist, " "creating." % folder) self._processor.create_folder(folder) try: self._processor.log("==> Copying {0} to {1}".format( self.uid, folder)) status, data = self._processor.imap.uid( 'copy', self.uid, folder) except self._processor.imap.error as e: self._processor.fatal_imap_error( "Copying message UID %s to %s" % (self.uid, folder), e) if status == 'NO': self._processor.fatal_imap_error("Copying message UID %s " " to %s failed with NO, " " aborting." % (self.uid, folder)) else: self._processor.fatal_error("Destination folder %s does not " "exist and I am not supposed to " "create folders. Please use " "move() or copy() with " "create=True to automatically " "create nonexistent " "folders." % folder)
def forward(self, addresses, env_sender, delete=True): """ Forwards the message to one or more addresses. The original message will be deleted. """ if signals.terminate(): self.processor.clean_exit() # Make sure we have this message's folder selected (UIDs should be # globally unique but may be on a per folder basis in sufficiently # broken IMAP implementations). if self.processor.selected != self.folder: self._processor.select(self.folder) if isinstance(addresses, str): addresses = [addresses] else: addresses = list(addresses) if delete: copy = "" else: copy = " copy" flags = self._processor.sendmail_flags if env_sender is not None: flags += " -f {0}".format(env_sender) self._processor.log( "==> Forwarding{0} to {1!r}".format(copy, addresses)) try: ret, msg = self._processor.imap.uid('fetch', self.uid, "RFC822") except self._processor.imap.error as e: # Fail soft, since we haven't changed any mailbox state or forwarded # anything, yet. Hence we might as well retry later. self._processor.log_imap_error( "Error forwarding: Could not retrieve message UID {0}: " "{1}".format(self.uid, e)) return p = subprocess.Popen( "{0} {1} -- {2}".format(self._processor.sendmail, flags, " ".join(addresses) ), shell=True, stdin=subprocess.PIPE) p.stdin.write(msg) p.stdin.close() sendmail_status = p.wait() if sendmail_status != 0: self._processor.log_error("Forwarding message failed: %s " "exited %d" % (self._processor.sendmail, sendmail_status)) return if delete: self.delete()
def _update_cache(self, folder, cache): """ This method updates an existing header cache data structure for a given folder. Message UIDs that do not exist in the cache, yet, will be added. """ self.log_debug("Updating cache") message_list = self.list_messages(folder) uids_download = [] # Remove stale cache entries stale = [] for message in cache: if message not in message_list: stale.append(message) for message in stale: cache.pop(message) for message in message_list: if signals.terminate(): self.clean_exit() if message in cache: cached_headers = cache[message]['headers'] cached_flags = cache[message]['flags'] msg_obj = self._mail_class(self, folder=folder, uid=message, headers=cached_headers, flags=cached_flags) cache[message]['obj'] = msg_obj else: uids_download.append(message) self.log_debug("Cache miss for the following UIDs: %s" % ",".join(uids_download)) if len(uids_download) != 0: messages = self._download_headers_batched(folder, uids_download) for uid in uids_download: msg_obj = self._mail_class(self, folder=folder, uid=uid, headers=messages[uid]['headers'], flags=messages[uid]['flags']) cache[uid] = {} cache[uid]['headers'] = msg_obj._headers cache[uid]['flags'] = msg_obj.message_flags cache[uid]['obj'] = msg_obj return cache
def _initialize_cache(self, folder): """ This method initializes a cache data structure for a given folder. """ cache = {} uids = self.list_messages(folder) messages = self._download_headers_batched(folder, uids) for message_uid in uids: if signals.terminate(): self.clean_exit() msg_obj = self._mail_class(self, folder=folder, uid=message_uid, headers=messages[message_uid]['headers'], flags=messages[message_uid]['flags']) cache[message_uid] = {} cache[message_uid]['headers'] = msg_obj._headers cache[message_uid]['flags'] = msg_obj.message_flags cache[message_uid]['obj'] = msg_obj return cache
def move(self, folder, create=False): """ Moves the message to folder. Optionally that folder can be created if it does not exist. In that case, specify create=True. folder may either be a string or a list of path components (a list will be joined by the IMAP server's separator character). """ if signals.terminate(): self.processor.clean_exit() # Make sure the folder name is prepended with the IMAP server's prefix # where applicable. folder = self.processor.path_ensure_prefix(folder) folder = self._processor.list_path(folder, sep=self._processor.separator) self._processor.log("==> Moving UID {0} to {1}".format(self.uid, folder)) self.copy(folder, create) self.delete()
def __iter__(self): """ Iterator method used to invoke the processor from the filter configuration file. """ if not self._folders: self.fatal_error("Error: No folders to process") self.rcfile_modified = False while not signals.signal_event.is_set(): if self.auto_reload_rcfile: current_rcfile_mtime = self._get_previous_rcfile_mtime() if current_rcfile_mtime != self._previous_rcfile_mtime: self._previous_rcfile_mtime = current_rcfile_mtime self.rcfile_modified = True self.log_info("Detected modified RC file; reloading") break for folder in self.folders: for message in self.header_cache[folder]['uids']: yield self.header_cache[folder]['uids'][message]['obj'] if self._run_once: self.clean_exit() else: self.clean_sleep() signals.signal_event.wait(self.interval) if signals.terminate(): # Simply exit, since clean_sleep() will already have performed # all exit rites if get here. sys.exit(0) self.reconnect() self._cache_headers()