From 77fc9ad2250bc5de1a28427aa0c2b2213b6fc75b Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Fri, 7 Apr 2017 19:12:33 +0300 Subject: Add import-imap subcommand No tests, too hard to mock an IMAP server. --- distixlib/plugins/import_mail_plugin.py | 126 ++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/distixlib/plugins/import_mail_plugin.py b/distixlib/plugins/import_mail_plugin.py index 9e29503..f58c1b0 100644 --- a/distixlib/plugins/import_mail_plugin.py +++ b/distixlib/plugins/import_mail_plugin.py @@ -18,8 +18,10 @@ import contextlib import email +import imaplib import mailbox import os +import re import cliapp import ttystatus @@ -54,6 +56,27 @@ class ImportMailPlugin(cliapp.Plugin): 'import-maildir', self.import_maildir, arg_synopsis='REPO MAILDIR') + self.app.add_subcommand( + 'import-imap', self.import_imap, + arg_synopsis='[KEY=VALUE]') + + self.app.settings.string( + ['imap-server'], + 'which IMAP server to import from', + metavar='ADDR') + + self.app.settings.string( + ['imap-username'], + 'username on IMAP server') + + self.app.settings.string( + ['imap-password-cmd'], + 'shell command to run to get password on IMAP server') + + self.app.settings.string( + ['local-repos'], + 'where local repos should be when importing from IMAP') + def import_mail(self, args): repo_dirname, mail_filename, keyvalue = self._parse_command_line(args) key, value = self._parse_keyvalue(keyvalue) @@ -76,6 +99,58 @@ class ImportMailPlugin(cliapp.Plugin): return mailbox.Maildir(filename, factory=None) self._import_folder(args, maildir_factory) + def import_imap(self, args): + if args: + key, value = self._parse_keyvalue(args[0]) + else: + key, value = None, None + + collection = _RepoCollection(self.app.settings['local-repos']) + + imap_server = self.app.settings['imap-server'] + username = self.app.settings['imap-username'] + password_cmd = self.app.settings['imap-password-cmd'] + password = cliapp.runcmd(['sh', '-c', password_cmd]).strip() + + cp = self.app.settings.as_cp() + repo_rules = _RepoRules() + repo_rules.add_rules(cp) + + imap = imaplib.IMAP4_SSL(imap_server) + imap.login(username, password) + imap.select('INBOX') + + _, data = imap.search(None, 'ALL') + for msgnum in data[0].split(): + _, data = imap.fetch(msgnum, '(RFC822)') + _, text = data[0] + repo_url = repo_rules.find_url(text) + if repo_url: + msg = email.message_from_string(text) + self._import_msg_into_repo( + collection, repo_url, msg, key, value) + imap.store(msgnum, '+FLAGS', '(\\Deleted)') + + imap.expunge() + imap.close() + imap.logout() + + def _import_msg_into_repo(self, collection, repo_url, msg, key, value): + if collection.is_local(repo_url): + collection.pull(repo_url) + else: + collection.clone(repo_url) + localdir = collection.localdir(repo_url) + + context = _ImportContext() + context.set_repo(distixlib.Repository(localdir)) + context.repo.require_clean_working_tree() + context.all_ticket_ids = context.store.get_ticket_ids() + + filenames = self._import_msg_to_ticket_store(context, msg, key, value) + if filenames: + context.repo.commit_changes(filenames, self.commit_msg) + def _import_folder(self, args, folder_factory): repo_dirname, folder_filename, keyvalue = self._parse_command_line( args) @@ -276,3 +351,54 @@ class _QuietProgressReporter(object): def __exit__(self, *args): pass + + +class _RepoCollection(object): + + def __init__(self, locals_dir): + self._locals_dir = locals_dir + + def is_local(self, repo_url): + localdir = self.localdir(repo_url) + return os.path.exists(localdir) + + def clone(self, repo_url): + localdir = self.localdir(repo_url) + cliapp.runcmd(['git', 'clone', repo_url, localdir]) + cliapp.runcmd( + ['git', 'branch', '-u', 'origin', 'master'], + cwd=localdir) + + def pull(self, repo_url): + localdir = self.localdir(repo_url) + cliapp.runcmd(['git', 'pull', '--rebase'], cwd=localdir) + + def localdir(self, url): + dirname = self._dir_for_url(url) + return os.path.join(self._locals_dir, dirname) + + def _dir_for_url(self, repo_url): + s = repo_url + s = '_'.join(s.split('/')) + return s + + +class _RepoRules(object): + + def __init__(self): + self._rules = [] + + def add_rules(self, cp): + for section in cp.sections(): + if section.startswith('distix:'): + url = section[len('distix:'):].strip() + pattern = cp.get(section, 'pattern') + self._rules.append((url, pattern)) + + def find_url(self, msg_text): + msg_text = ''.join(msg_text.split('\r')) + for url, pattern in self._rules: + m = re.search(pattern, msg_text, re.M | re.I) + if m is not None: + return url + return None -- cgit v1.2.1