From 687199091e4739cfab40089a2d585a18835ab812 Mon Sep 17 00:00:00 2001 From: Matěj Cepl Date: Sat, 21 Apr 2018 01:03:55 +0200 Subject: First hopefully interesting draft. --- archive_folder.py | 140 +++++++++++++++++++++++++++++++++++++++++++----------- listFolders.py | 42 ++++++++++++---- 2 files changed, 145 insertions(+), 37 deletions(-) diff --git a/archive_folder.py b/archive_folder.py index d4aca54..2b3ba14 100755 --- a/archive_folder.py +++ b/archive_folder.py @@ -7,15 +7,16 @@ # for unittest.mock tutorials # for testing import argparse +import collections import configparser import email import email.header import email.utils +import functools import locale import logging import imaplib import os -import pprint import sys from datetime import date, timedelta @@ -34,6 +35,8 @@ class FolderError(IOError): class MessageError(IOError): pass +Capas = collections.namedtuple('Capas', ['MOVE', 'UIDPLUS']) + def get_config(): config = configparser.ConfigParser() @@ -72,17 +75,67 @@ class Message(object): class Folder(object): - def __init__(self, box, folder_sep, fld_name): + def __init__(self, box, fld_name, create=False): self.box = box + log.debug('fld_name = %s', fld_name) self.fld_name = fld_name - self.folder_separator = folder_sep self.selected = False + self.__create_missing = create + self.__folder_sep = self.__get_separator() + + log.debug('create_missing = %s', self.__create_missing) + log.debug('list_folder = %s', self.__list_folder) + if self.__create_missing and self.__list_folder() is None: + self.__create_folder() def select(self): - ret = self.box.select(mailbox=self.fld_name) + ok, _ = self.box.select(mailbox=self.fld_name) + if ok != 'OK': + raise FolderError('Cannot select folder %s' % self.fld_name) self.selected = True return self + def __create_folder(self): + sep = self.__folder_sep + split_name = self.fld_name.split(sep) + log.debug('split_name = %s', split_name) + target = '' + for part in split_name: + log.debug('part = %s', part) + target += '{}{}'.format(part, self.__folder_sep) + if self.__list_folder(target) is None: + self.box.create(target) + self.box.subscribe(target) + self.__list_folder.cache_clear() + + @functools.lru_cache() + def __list_folder(self, name=None, wildcards='*'): + if name is None: + name = self.fld_name + ok, data = self.box.list(name, wildcards) + if ok != 'OK': + raise ServerError('Cannot list folder %s' % self.fld_name) + return data[0] + + def __get_separator(self): + # type: () -> None + """ + Find current folder hierarchy separator, either from the current + folder, or if it doesn't exist from INBOX. + """ + data = self.__list_folder() + + log.debug('data = %s (%s)', data, type(data)) + + if data is None: + data = self.__list_folder('""', '""') + + data = data.decode().split(' ') + if len(data) == 3: + return data[1].strip(' "') + else: + raise ServerError('Cannot find folder separator from %s' % data) + def __emails_search(self, search_type, date_str): ok, res = self.box.uid('SEARCH', '%s %s' % (search_type.upper(), date_str)) if ok != 'OK': @@ -98,8 +151,45 @@ class Folder(object): return self.__emails_search('BEFORE', before_str) def get_archive_folder(self, msg, aroot): - return self.folder_separator.join((aroot, msg.date.strftime("%Y"), self.fld_name)) - + return self.__folder_sep.join((aroot, msg.date.strftime("%Y"), self.fld_name)) + + def move_messages(self, messages): + assert self.selected == False, 'Target folder should not be selected.' + if self.box.features_present.MOVE: + ok, data = self.box.uid('MOVE', + '%s %s' % (messages, self.fld_name)) + log.debug('MOVE ok = %s, data = %s', ok, data) + if ok != 'OK': + raise FolderError('Cannot move messages to folder %s' % + self.fld_name) + elif self.box.features_present.UIDPLUS: + ok, data = self.box.uid('COPY', + '%s %s' % (messages, self.fld_name)) + log.debug('COPY ok = %s, data = %s', ok, data) + if ok != 'OK': + raise FolderError('Cannot copy messages to folder %s' % + self.fld_name) + ok, data = self.box.uid('STORE', + r'+FLAGS.SILENT (\DELETED) %s' % messages) + log.debug('STORE ok = %s, data = %s', ok, data) + if ok != 'OK': + raise FolderError('Cannot delete messages-') + ok, data = self.box.uid('EXPUNGE', messages) + log.debug('EXPUNGE ok = %s, data = %s', ok, data) + if ok != 'OK': + raise FolderError('Cannot expunge messages.') + else: + ok, data = self.box.uid('COPY', + '%s %s' % (messages, self.fld_name)) + log.debug('COPY ok = %s, data = %s', ok, data) + if ok != 'OK': + raise FolderError('Cannot copy messages to folder %s' % + self.fld_name) + ok, data = self.box.uid('STORE', + r'+FLAGS.SILENT (\DELETED) %s' % messages) + log.debug('STORE ok = %s, data = %s', ok, data) + if ok != 'OK': + raise FolderError('Cannot delete messages-') class EmailServer(object): @@ -109,30 +199,21 @@ class EmailServer(object): else config['general']['account'] self.cfg = dict(config.items(acc_name)) self.archive_root = archive_root - self.__box = self.__login(**self.cfg) - self.__folder_sep = self.__get_separator() def __login(self, host='localhost', username=None, password=None, ssl=None): box = imaplib.IMAP4_SSL(host=host) ok, data = box.login(username, password) - if ok == 'OK': - return box - else: + if ok != 'OK': raise ServerError('Cannot login with credentials %s' % str((host, username, password,))) - def __get_separator(self): - # type: () -> None - ok, data = self.__box.list('""', '""') - if ok != 'OK': - raise ServerError('Cannot list known folders') - - data = data[0].decode().split(' ') - if len(data) == 3: - return data[1].strip(' "') - else: - raise ServerError('Cannot find folder separator from %s' % data) + ok, data = box.capability() + log.debug('data = %s', data) + capas = data[0].decode() + log.debug('capas = %s', capas) + box.features_present = Capas._make(['MOVE' in capas, 'UIDPLUS' in capas]) + log.debug('features_present = %s', box.features_present) def archive_folder(self, folder_name, before_date): # type: (str, datetime.date) -> None @@ -143,7 +224,7 @@ class EmailServer(object): :param: before_date """ copy_cache = {} - fld = Folder(self.__box, self.__folder_sep, folder_name).select() + fld = Folder(self.__box, folder_name).select() before_str = before_date.strftime('%d-%b-%Y') for msg in fld.emails_before(before_str): arch_folder = fld.get_archive_folder(msg, self.archive_root) @@ -153,11 +234,17 @@ class EmailServer(object): copy_cache[arch_folder] = [msg] for key in copy_cache: - log.info('\n\n%s:', key) + log.info('***** %s:', key) for msg in copy_cache[key]: log.info('\tmsg: %s', str(msg)) - # TODO: Projdi keš a proveď operaci + # Go through the cache and make moves. + for arch_dir in copy_cache: + msg_ids = ','.join([x.uid for x in copy_cache[arch_dir]]) + log.debug('arch_dir = %s, msgs = %s', arch_dir, + msg_ids) + dir = Folder(self.__box, arch_dir, create=True) + dir.move_messages(msg_ids) def __enter__(self): return self @@ -165,8 +252,7 @@ class EmailServer(object): def __exit__(self, *args): if args != (None, None, None): log.warning('args = %s', args) - else: - self.__box.close() + self.__box.close() if __name__ == '__main__': diff --git a/listFolders.py b/listFolders.py index b815d30..741ae41 100755 --- a/listFolders.py +++ b/listFolders.py @@ -10,6 +10,26 @@ logging.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', level=logging.DEBUG) log = logging.getLogger('listFolders') +def list_folder(name, wildcards='*'): + ok, data = box.list(name, wildcards) + if ok != 'OK': + raise ServerError('Cannot list folder %s' % name) + return data[0] + +def get_separator(): + data = list_folder('pumpa') + + log.debug('data = %s (%s)', data, type(data)) + + if data is None: + data = list_folder('""', '""') + + data = data.decode().split(' ') + if len(data) == 3: + return data[1].strip(' "') + else: + raise ServerError('Cannot find folder separator from %s' % data) + config = configparser.ConfigParser() config.read(os.path.expanduser('~/.config/imap_archiver.cfg')) acc_name = config['general']['account'] @@ -20,13 +40,15 @@ ok, data = box.login(cfg['username'], cfg['password']) if ok != 'OK': raise IOError('Cannot login with credentials %s' % str(cfg)) -ok, data = box.list('""', '""') -sep = data[0].split()[1].decode() - -ok, data = box.list() -if ok != 'OK': - raise IOError('Cannot list known folders') - -for fld in data: - spl_fld = fld.decode().split(sep) - print(spl_fld[1].strip()) +sep = get_separator() +print('sep = %s' % sep) +# data = list_folder('""', '""') +# log.debug('data = %s', data) +# sep = data.split()[1].decode() +# +# data = list_folder('Work/punk') +# log.debug('ok = %s, data = %s (type %s)', ok, data, type(data)) + +# for fld in data: +# spl_fld = fld.decode().split(sep) +# print(spl_fld[1].strip()) -- cgit