--- /dev/null
+#!/usr/bin/python
+
+# Original source by Sam Burnett <sburnett@cc.gatech.edu>
+# http://www.cc.gatech.edu/~sburnett/posts/2010-11-21-imap-idle.html
+#
+# Amended by Julien Valroff <julien@kirya.net> to support getting passwords from
+# GNOME keyring and falling back to using netrc file
+# This is done through ~/.offlineimap.py from
+# http://git.kirya.net/?p=dotfiles/offlineimap.git;a=blob_plain;f=.offlineimap.py;hb=master
+#
+# requires:
+# * offlineimap
+# * python-twisted-core
+# * python-openssl
+
+from OpenSSL import SSL
+import sys
+import os.path
+import subprocess
+import re
+import imp
+import ConfigParser
+from optparse import OptionParser
+
+from twisted.internet.protocol import ReconnectingClientFactory
+from twisted.protocols.basic import LineOnlyReceiver
+from twisted.internet import ssl, reactor
+from twisted.protocols.policies import TimeoutMixin
+from twisted.python import log
+
+class ImapIdleClient(LineOnlyReceiver, TimeoutMixin):
+ def __init__(self, username, password, mailbox, account, idle_timeout):
+ self.init_state = 'init'
+ self.state_machine = { 'init': ('login', self.wait_for_hello)
+ , 'login': ('examine', self.login)
+ , 'examine': ('idle', self.examine)
+ , 'idle': ('done', self.idle)
+ , 'done': ('idle', self.done_idle)
+ }
+
+ self.state = self.init_state
+ self.username = username
+ self.password = password
+ self.mailbox = mailbox
+ self.account = account
+ self.idle_timeout = idle_timeout
+
+ def connectionMade(self):
+ self.factory.resetDelay()
+
+ def connectionLost(self, reason):
+ print 'connection for %s:%s lost (%s)' % (self.account, self.mailbox, reason)
+
+ def lineReceived(self, line):
+ log.msg('[%s:%s] (%s) %s' % (self.account, self.mailbox, self.state, line))
+
+ (next_state, action) = self.state_machine[self.state]
+ if action(line):
+ self.state = next_state
+ log.msg('[%s:%s] Transitioning to state %s' % (self.account, self.mailbox, self.state))
+
+ def wait_for_hello(self, line):
+ if re.match(r'\* OK', line) is not None:
+ self.sendLine('. login %s "%s"' % (self.username, self.password))
+ return True
+ print 'Logging in'
+ else:
+ return False
+
+ def login(self, line):
+ if re.match(r'\. OK', line) is not None:
+ self.sendLine('. examine %s' % self.mailbox)
+ return True
+ print 'Examining mailbox'
+ else:
+ return False
+
+ def examine(self, line):
+ if re.match(r'\. OK', line) is not None:
+ self.sendLine('. idle')
+ self.setTimeout(self.idle_timeout)
+ return True
+ print 'Going idle'
+ else:
+ return False
+
+ def idle(self, line):
+ if re.match(r'\* \d+ (EXISTS|EXPUNGE)', line) is not None:
+ subprocess.Popen(['offlineimap', '-a', self.account, '-f', self.mailbox, '-o', '-k', 'mbnames:enabled=no'])
+ self.sendLine('DONE')
+ return True
+ print 'Done idling (new mail)'
+ else:
+ return False
+
+ def done_idle(self, line):
+ if re.match(r'\. OK', line) is not None:
+ self.sendLine('. idle')
+ self.setTimeout(self.idle_timeout)
+ return True
+ print 'Idling again'
+ else:
+ return False
+
+ def timeoutConnection(self):
+ self.state = 'done'
+ self.sendLine('DONE')
+ print 'Done idle (timeout)'
+
+class ImapClientFactory(ReconnectingClientFactory):
+ def __init__(self, server, mailbox, account, timeout):
+ path = os.path.expanduser("~/.offlineimap.py")
+ pythonfile = imp.load_source("pythonfile", path)
+
+ self.username = pythonfile.get_username(server)
+ self.password = pythonfile.get_password(server)
+ self.mailbox = mailbox
+ self.account = account
+ self.timeout = 60*timeout
+
+ def buildProtocol(self, addr):
+ proto = ImapIdleClient(self.username, self.password, self.mailbox, self.account, self.timeout)
+ proto.factory = self
+ return proto
+
+class AccountOptions(object):
+ server = None
+ port = 993
+ mailboxes = None
+ timeout = 29
+
+def parse_options():
+ usage = "usage: %s [options] [accounts to sync (overrides config file)]"
+ parser = OptionParser(usage=usage)
+ parser.set_defaults(config=None)
+ parser.add_option('-c', '--configuration', dest='config',
+ action='store', type='string',
+ help='Alternate location of configuration file')
+ (options, args) = parser.parse_args()
+
+ if options.config is not None:
+ config_path = options.config
+ else:
+ config_path = os.path.expanduser('~/.idlemailrc')
+
+ if not os.path.isfile(config_path):
+ parser.error("Configuration file %s doesn't exist" % config_path)
+
+ if len(args) > 0:
+ accounts = args
+ else:
+ accounts = None
+
+ cfg = ConfigParser.ConfigParser()
+ cfg.read(config_path)
+
+ if cfg.has_section('general'):
+ if cfg.has_option('general', 'accounts') and accounts is None:
+ accounts = re.split('[, \t]+', cfg.get('general', 'accounts'))
+
+ account_settings = {}
+
+ for section in cfg.sections():
+ if section.split()[0].lower() != 'account':
+ continue
+
+ name = section.split(None, 1)[1].strip()
+ if accounts is not None and name not in accounts:
+ continue
+
+ account = AccountOptions()
+ account.server = cfg.get(section, 'server')
+ if cfg.has_option(section, 'port'):
+ account.port = cfg.getint(section, 'port')
+ boxes = cfg.get(section, 'mailboxes')
+ account.mailboxes = re.split('[, \t]+', boxes)
+ if cfg.has_option(section, 'timeout'):
+ account.timeout = cfg.getfloat(section, 'timeout')
+
+ account_settings[name] = account
+
+ if accounts is not None:
+ for account in accounts:
+ if account not in account_settings:
+ parser.error('Account not found: %s' % account)
+
+ if len(account_settings.keys()) == 0:
+ parser.error('No accounts defined')
+
+ return account_settings
+
+def main():
+ accounts = parse_options()
+
+ log.startLogging(sys.stdout)
+
+ for (name, settings) in accounts.items():
+ for mailbox in settings.mailboxes:
+ factory = ImapClientFactory(settings.server, mailbox, name, settings.timeout)
+ reactor.connectSSL(settings.server, settings.port, factory, ssl.ClientContextFactory())
+
+ reactor.run()
+
+if __name__ == '__main__':
+ main()