IMAP code moved from idlers.py to backends/imap.py.

Folder is always selected when new connection is opened, not only for idle accounts.
Callback is set using Account.notify_next_change method.
Connection abort checking in callback is moved to IMAP backend.
This commit is contained in:
Timo Kankare
2016-09-07 22:46:50 +03:00
parent 45677d081d
commit 3bdb85952d
3 changed files with 75 additions and 43 deletions

View File

@@ -27,6 +27,7 @@ import email
import logging
import re
import Mailnag.common.imaplib2 as imaplib
from Mailnag.common.imaplib2 import AUTH
class ImapBackend:
"""Implementation of IMAP mail boxes."""
@@ -42,6 +43,7 @@ class ImapBackend:
self.ssl = ssl # bool
self.folders = folders
self._conn = None
self._conn_closed = True
def get_connection(self, use_existing):
@@ -82,18 +84,29 @@ class ImapBackend:
except: pass
raise # re-throw exception
self._conn_closed = False
# Need to get out of AUTH mode of fresh connections.
if self._conn.state == AUTH:
self.select()
return self._conn
def close(self):
self._conn.close()
# if conn has already been closed, don't try to close it again
if not self._conn_closed:
self._conn.close()
self._conn_closed = True
self._conn.logout()
self._conn = None
def has_connection(self):
return (self._conn != None) and \
(self._conn.state != imaplib.LOGOUT) and \
(not self._conn.Terminate)
(not self._conn.Terminate) and \
(not self._conn_closed)
def list_messages(self):
@@ -151,3 +164,38 @@ class ImapBackend:
return lst
# TODO: Temporarily public. Make private.
def select(self):
if len(self.folders) == 1:
self._conn.select(self.folders[0])
else:
self._conn.select("INBOX")
def notify_next_change(self, callback=None, timeout=None):
# register idle callback that is called whenever an idle event
# arrives (new mail / mail deleted).
# the callback is called after <idle_timeout> minutes at the latest.
# gmail sends keepalive events every 5 minutes.
# idle callback (runs on a further thread)
def _idle_callback(args):
# check if the connection has been reset by provider
self._conn_closed = (args[2] != None) and (args[2][0] is self._conn.abort)
# call actual callback
callback(args)
self._conn.idle(callback = _idle_callback, timeout = timeout)
def cancel_notifications(self):
try:
if self._conn != None:
# Exit possible active idle state.
# (also calls idle_callback)
self._conn.noop()
except:
pass

View File

@@ -92,6 +92,19 @@ class Account:
return self.backend.list_messages()
# TODO: Temporarily here. Remove when no needed.
def select(self):
self.backend.select()
def notify_next_change(self, callback=None, timeout=None):
self.backend.notify_next_change(callback, timeout)
def cancel_notifications(self):
self.backend.cancel_notifications()
# Requests folder names (list) from a server.
# Relevant for IMAP accounts only.
# Returns an empty list when used on POP3 accounts.

View File

@@ -25,7 +25,6 @@
import threading
import time
import logging
from Mailnag.common.imaplib2 import AUTH
from Mailnag.common.exceptions import InvalidOperationException
@@ -41,14 +40,7 @@ class Idler(object):
self._sync_callback = sync_callback
self._account = account
self._idle_timeout = idle_timeout
# use_existing = True:
# connection has been opened in mailnagdaemon.py already (immediate check)
self._conn = account.get_connection(use_existing = True)
self._disposed = False
# Need to get out of AUTH mode of fresh connections.
if self._conn.state == AUTH:
self._select(self._conn, account)
def start(self):
@@ -63,34 +55,27 @@ class Idler(object):
self._event.set()
self._thread.join()
try:
if self._conn != None:
# Exit possible active idle state.
# (also calls idle_callback)
self._conn.noop()
except:
pass
self._disposed = True
logging.info('Idler closed')
# idle thread
def _idle(self):
# use_existing = True:
# connection has been opened in mailnagdaemon.py already (immediate check)
self._conn = self._account.get_connection(use_existing = True)
while True:
# if the event is set here,
# disposed() must have been called
# so stop the idle thread.
if self._event.isSet():
return
break
self._needsync = False
self._conn_closed = False
# register idle callback that is called whenever an idle event arrives (new mail / mail deleted).
# the callback is called after <idle_timeout> minutes at the latest.
# gmail sends keepalive events every 5 minutes.
self._conn.idle(callback = self._idle_callback, timeout = 60 * self._idle_timeout)
self._account.notify_next_change(callback = self._idle_callback, timeout = 60 * self._idle_timeout)
# waits for the event to be set
# (in idle callback or in dispose())
@@ -99,17 +84,17 @@ class Idler(object):
# if the event is set due to idle sync
if self._needsync:
self._event.clear()
if self._conn_closed:
if not self._account.has_connection():
self._reconnect()
if self._conn != None:
if self._account.has_connection():
self._sync_callback(self._account)
self._account.cancel_notifications()
# idle callback (runs on a further thread)
def _idle_callback(self, args):
# check if the connection has been reset by provider
self._conn_closed = (args[2] != None) and (args[2][0] is self._conn.abort)
# flag that a mail sync is needed
self._needsync = True
# trigger waiting _idle thread
@@ -120,14 +105,10 @@ class Idler(object):
# connection has been reset by provider -> try to reconnect
logging.info("Idler thread for account '%s' has been disconnected" % self._account.name)
# conn has already been closed, don't try to close it again
# self._conn.close() # (calls idle_callback)
# shutdown existing callback thread
self._conn.logout()
self._account.close()
self._conn = None
while (self._conn == None) and (not self._event.isSet()):
while (not self._account.has_connection()) and (not self._event.isSet()):
logging.info("Trying to reconnect Idler thread for account '%s'." % self._account.name)
try:
self._conn = self._account.get_connection(use_existing = False)
@@ -137,18 +118,8 @@ class Idler(object):
logging.info("Trying to reconnect Idler thread for account '%s' in %s minutes" %
(self._account.name, str(self.RECONNECT_RETRY_INTERVAL)))
self._wait(60 * self.RECONNECT_RETRY_INTERVAL) # don't hammer the server
if self._conn != None:
self._select(self._conn, self._account)
def _select(self, conn, account):
if len(account.folders) == 1:
conn.select(account.folders[0])
else:
conn.select("INBOX")
def _wait(self, secs):
start_time = time.time()
while (((time.time() - start_time) < secs) and (not self._event.isSet())):