From 6d68a191c9b44e08f48a66586913dfd5ea6c01bd Mon Sep 17 00:00:00 2001 From: Patrick Ulbrich Date: Mon, 2 Jan 2012 20:33:42 +0100 Subject: [PATCH] mass commit fixed imap idle implementation (reuse open imap connections and don't poll unaffected accounts) reconnect if a connection has been lost (e.g. after standby) moved connection check from startup script to mailnag.py removed unused/unnecessary code refactoring, fixed exception handling --- Mailnag/common/account.py | 84 ++++++++++------- Mailnag/common/utils.py | 11 ++- Mailnag/daemon/idler.py | 42 +++++++-- Mailnag/daemon/idlers.py | 33 ++----- Mailnag/daemon/mail.py | 6 +- Mailnag/daemon/mailchecker.py | 51 ++++------ Mailnag/daemon/mails.py | 173 ++++++++++++++++------------------ Mailnag/daemon/mailsyncer.py | 76 +++++++++++++++ Mailnag/mailnag.py | 34 +++++-- mailnag | 41 ++------ 10 files changed, 313 insertions(+), 238 deletions(-) create mode 100644 Mailnag/daemon/mailsyncer.py diff --git a/Mailnag/common/account.py b/Mailnag/common/account.py index 672c485..13030ea 100644 --- a/Mailnag/common/account.py +++ b/Mailnag/common/account.py @@ -3,7 +3,7 @@ # # account.py # -# Copyright 2011 Patrick Ulbrich +# Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify @@ -22,7 +22,6 @@ # MA 02110-1301, USA. # -import time import poplib import daemon.imaplib2 as imaplib from common.i18n import _ @@ -42,7 +41,7 @@ account_defaults = { class Account: def __init__(self, enabled = False, name = _('Unnamed'), user = '', \ password = '', server = '', port = '', ssl = True, imap = False, idle = False, folder = '' ): - + self.enabled = enabled # bool self.name = name self.user = user @@ -53,55 +52,72 @@ class Account: self.imap = imap # bool self.idle = idle # bool self.folder = folder + self._conn = None - def get_connection(self): # get email server connection + def get_connection(self, use_existing = False): # get email server connection if self.imap: try: - srv = self._get_IMAP_connection() + conn = self._get_IMAP_connection(use_existing) except: print "Error: Cannot connect to IMAP account: %s. " % self.server - srv = None + conn = None else: try: - srv = self._get_POP3_connection() + conn = self._get_POP3_connection(use_existing) except: print "Error: Cannot connect to POP account: %s. " % self.server - srv = None + conn = None - return srv # server object - - - def _get_IMAP_connection(self): - if self.ssl: - if self.port == '': - srv = imaplib.IMAP4_SSL(self.server) - else: - srv = imaplib.IMAP4_SSL(self.server, self.port) - else: - if self.port == '': - srv = imaplib.IMAP4(self.server) - else: - srv = imaplib.IMAP4(self.server, self.port) - - srv.login(self.user, self.password) - return srv + return conn - def _get_POP3_connection(self): + def get_id(self): + # TODO : this id is not really unique... + return str(hash(self.user + self.server)) + + + def _get_IMAP_connection(self, use_existing): + # try to reuse existing connection + if use_existing and (self._conn != None) and \ + (self._conn.state != imaplib.LOGOUT) and (not self._conn.Terminate): + return self._conn + if self.ssl: if self.port == '': - srv = poplib.POP3_SSL(self.server) + self._conn = imaplib.IMAP4_SSL(self.server) else: - srv = poplib.POP3_SSL(self.server, self.port) + self._conn = imaplib.IMAP4_SSL(self.server, self.port) else: if self.port == '': - srv = poplib.POP3(self.server) + self._conn = imaplib.IMAP4(self.server) else: - srv = poplib.POP3(self.server, self.port) + self._conn = imaplib.IMAP4(self.server, self.port) - srv.getwelcome() - srv.user(self.user) - srv.pass_(self.password) - return srv + self._conn.login(self.user, self.password) + + return self._conn + + + def _get_POP3_connection(self, use_existing): + # try to reuse existing connection + if use_existing and (self._conn != None) and ('sock' in self._conn.__dict__): + return self._conn + + if self.ssl: + if self.port == '': + self._conn = poplib.POP3_SSL(self.server) + else: + self._conn = poplib.POP3_SSL(self.server, self.port) + else: + if self.port == '': + self._conn = poplib.POP3(self.server) + else: + self._conn = poplib.POP3(self.server, self.port) + + self._conn.getwelcome() + self._conn.user(self.user) + self._conn.pass_(self.password) + + return self._conn diff --git a/Mailnag/common/utils.py b/Mailnag/common/utils.py index f4c7d50..f827089 100644 --- a/Mailnag/common/utils.py +++ b/Mailnag/common/utils.py @@ -3,7 +3,7 @@ # # mails.py # -# Copyright 2011 Patrick Ulbrich +# Copyright 2011, 2012 Patrick Ulbrich # Copyright 2007 Marco Ferragina # # This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ from gi.repository import Gst, Gio import threading import os import time +import urllib2 PACKAGE_NAME = "mailnag" @@ -68,6 +69,14 @@ def get_default_mail_reader(): return mail_reader +def is_online(): # check for internet connection + try: + urllib2.urlopen("http://www.google.com/") + return True + except: + return False + + class _GstPlayThread(threading.Thread): def __init__(self, ply): self.ply = ply diff --git a/Mailnag/daemon/idler.py b/Mailnag/daemon/idler.py index ea3ce93..4f483de 100644 --- a/Mailnag/daemon/idler.py +++ b/Mailnag/daemon/idler.py @@ -3,8 +3,8 @@ # # idler.py # +# Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Leighton Earl -# Copyright 2011 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,13 +23,22 @@ # import threading +from daemon.imaplib2 import AUTH class Idler(object): - def __init__(self, conn, sync_callback): + def __init__(self, account, sync_callback): self._thread = threading.Thread(target=self._idle) - self._conn = conn self._event = threading.Event() self._sync_callback = sync_callback + self._account = account + self._conn = account.get_connection(use_existing = True) # connection has been opened in mailnag.py already (immediate check) + + if self._conn == None: + raise Exception("Failed to establish a connection for account '%s'" % account.name) + + # Need to get out of AUTH mode of fresh connections. + if self._conn.state == AUTH: + self._select(self._conn, account.folder) def run(self): @@ -41,8 +50,11 @@ class Idler(object): self._event.set() self._thread.join() - self._conn.close() - self._conn.logout() + try: + self._conn.close() + self._conn.logout() + except: + pass print "Idler closed" @@ -55,11 +67,19 @@ class Idler(object): self._needsync = False def callback(args): + if (args[2] != None) and (args[2][0] is self._conn.abort): + # connection has been reset by provider -> reopen + print "Idler thread for account '%s' reconnected" % self._account.name + self._conn = self._account.get_connection(use_existing = False) + self._select(self._conn, self._account.folder) + if not self._event.isSet(): self._needsync = True self._event.set() - self._conn.idle(callback=callback) + # register idle callback that is called whenever an idle event arrives (new mail / mail deleted). + # the callback is called after 10 minutes at the latest. gmail sends keepalive events every 5 minutes. + self._conn.idle(callback = callback, timeout = 60 * 10) # Waits for event to be set self._event.wait() @@ -67,6 +87,12 @@ class Idler(object): # If the event is set due to idle sync if self._needsync: self._event.clear() - self._sync_callback() + self._sync_callback(self._account) + + def _select(self, conn, folder): + folder = folder.strip() + if len(folder) > 0: + conn.select(folder) + else: + conn.select("INBOX") - diff --git a/Mailnag/daemon/idlers.py b/Mailnag/daemon/idlers.py index 9f7a250..7090f93 100644 --- a/Mailnag/daemon/idlers.py +++ b/Mailnag/daemon/idlers.py @@ -3,8 +3,8 @@ # # idlers.py # +# Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Leighton Earl -# Copyright 2011 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -35,35 +35,14 @@ class Idlers: for acc in self._accounts: if acc.imap and acc.idle: try: - self._new_idler(acc) - except: - pass + idler = Idler(acc, self._sync_callback) + idler.run() + self._idlerlist.append(idler) + except Exception as ex: + print "Error: Failed to create an idler thread for account '%s'" % acc.name def dispose(self): for idler in self._idlerlist: idler.dispose() - - - def _new_idler(self, account): - server = account.get_connection() - - if server == None: - return - - # Need to get out of AUTH mode. - if len(account.folder) > 0: - server.select(account.folder) - else: - server.select("INBOX") - - try: - tmp = server.search(None, 'UNSEEN') # ALL or UNSEEN - except: - server.select('INBOX', readonly=True) # If search fails select INBOX and try again - tmp = server.search(None, 'UNSEEN') # ALL or UNSEEN - - idler = Idler(server, self._sync_callback) - idler.run() - self._idlerlist.append(idler) diff --git a/Mailnag/daemon/mail.py b/Mailnag/daemon/mail.py index 30df11a..218af41 100644 --- a/Mailnag/daemon/mail.py +++ b/Mailnag/daemon/mail.py @@ -3,7 +3,7 @@ # # mail.py # -# Copyright 2011 Patrick Ulbrich +# Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify @@ -23,10 +23,10 @@ # class Mail: - def __init__(self, seconds, subject, sender, datetime, id, provider): + def __init__(self, seconds, subject, sender, datetime, id, account_id): self.seconds = seconds self.subject = subject self.sender = sender self.datetime = datetime self.id = id - self.provider = provider + self.account_id = account_id diff --git a/Mailnag/daemon/mailchecker.py b/Mailnag/daemon/mailchecker.py index 797ef56..e21f747 100644 --- a/Mailnag/daemon/mailchecker.py +++ b/Mailnag/daemon/mailchecker.py @@ -3,7 +3,7 @@ # # mailchecker.py # -# Copyright 2011 Patrick Ulbrich +# Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify @@ -29,18 +29,19 @@ import subprocess import os import time -from common.utils import get_data_file, gstplay, get_default_mail_reader +from common.utils import get_data_file, gstplay, is_online, get_default_mail_reader from common.i18n import _ from daemon.reminder import Reminder -from daemon.mails import Mails +from daemon.mailsyncer import MailSyncer from daemon.pid import Pid class MailChecker: - def __init__(self, cfg, accounts): + def __init__(self, cfg): self.MAIL_LIST_LIMIT = 10 # prevent flooding of the messaging tray self.firstcheck = True; # first check after startup self.mailcheck_lock = threading.Lock() - self.mails = Mails(cfg, accounts) + self.mail_list = [] + self.mailsyncer = MailSyncer(cfg) self.reminder = Reminder() self.pid = Pid() self.cfg = cfg @@ -50,14 +51,18 @@ class MailChecker: self.reminder.load() Notify.init(cfg.get('general', 'messagetray_label')) # initialize Notification - - def check(self): + + def check(self, accounts): with self.mailcheck_lock: print 'Checking email accounts at:', time.asctime() self.pid.kill() # kill all zombies - self.mail_list = self.mails.get_mail('desc') # get all mails from all inboxes - + if not is_online(): + print 'Error: No internet connection' + return + + self.mail_list = self.mailsyncer.sync(accounts) + unseen_mails = [] new_mails = [] @@ -80,7 +85,7 @@ class MailChecker: script_data = str(script_data_mailcount) + script_data if len(self.mail_list) == 0: - # no mails (e.g. email client has been launched) -> close notifications + # no mails (e.g. email client has been launched) -> close notifications for n in self.notifications.itervalues(): n.close() self.notifications = {} @@ -94,14 +99,13 @@ class MailChecker: gstplay(get_data_file(self.cfg.get('general', 'soundfile'))) self.reminder.save(self.mail_list) + self.__run_user_scripts("on_mail_check", script_data) # process user scripts + sys.stdout.flush() # write stdout to log file self.firstcheck = False - - self.__run_user_scripts("on_mail_check", script_data) # process user scripts - sys.stdout.flush() # write stdout to log file - return True - + return + def __notify_summary(self, unseen_mails): summary = "" body = "" @@ -162,22 +166,7 @@ class MailChecker: # so remove its reference del self.notifications[user_data[1]] - - def clear(self): - with self.mailcheck_lock: - # mark all mails to seen - for mail in self.mail_list: - self.reminder.set_to_seen(mail.id) - self.reminder.save(self.mail_list) - - # close all notifications - for n in self.notifications.itervalues(): - n.close() - self.notifications = {} - - self.mail_list = [] - - + def __run_user_scripts(self, event, data): if event == "on_mail_check": if self.cfg.get('script', 'script0_enabled') == '1': diff --git a/Mailnag/daemon/mails.py b/Mailnag/daemon/mails.py index d0e557d..19d73f1 100644 --- a/Mailnag/daemon/mails.py +++ b/Mailnag/daemon/mails.py @@ -3,7 +3,7 @@ # # mails.py # -# Copyright 2011 Patrick Ulbrich +# Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Leighton Earl # Copyright 2011 Ralf Hersel # @@ -24,7 +24,6 @@ # import time -import urllib2 import sys import email @@ -38,100 +37,93 @@ class Mails: self.accounts = accounts - def get_mail(self, sort_order): + def get_mail(self, sort_order = None): mail_list = [] # initialize list of mails mail_ids = [] # initialize list of mail ids - if not self.__is_online(): # check internet connection - return mail_list - filter_enabled = bool(int(self.cfg.get('filter', 'filter_enabled'))) # get filter switch for acc in self.accounts: - srv = acc.get_connection() # get server connection for this account + srv = acc.get_connection(use_existing = True) # get server connection for this account if srv == None: continue # continue with next account if server is empty elif acc.imap: # IMAP - folder_list = acc.folder.split(',') # make a list of folders - mail_count = 0 # reset email counter - if folder_list[0] == '': - folder_list = ['INBOX'] - for folder in folder_list: - - tmp = folder.strip() - srv.select(tmp, readonly=True) # select IMAP folder + folder = acc.folder.strip() + if len(folder) == 0: + folder = "INBOX" + + srv.select(folder, readonly=True) # select IMAP folder + try: + status, data = srv.search(None, 'UNSEEN') # ALL or UNSEEN + except: + print "The folder:", folder, "does not exist, using INBOX instead" try: + srv.select('INBOX', readonly=True) # If search fails select INBOX and try again status, data = srv.search(None, 'UNSEEN') # ALL or UNSEEN except: - print "The folder:", tmp, "does not exist, using INBOX instead" - try: - srv.select('INBOX', readonly=True) # If search fails select INBOX and try again - status, data = srv.search(None, 'UNSEEN') # ALL or UNSEEN - except: - print "INBOX Could not be found", sys.exc_info()[0] + print "INBOX Could not be found", sys.exc_info()[0] - if status != 'OK' or None in [d for d in data]: - print "Folder", folder, "in status", status, "| Data:", data, "\n" - continue # Bugfix LP-735071 - for num in data[0].split(): - typ, msg_data = srv.fetch(num, '(BODY.PEEK[HEADER])') # header only (without setting READ flag) - for response_part in msg_data: - if isinstance(response_part, tuple): + if status != 'OK' or None in [d for d in data]: + print "Folder", folder, "in status", status, "| Data:", data, "\n" + continue # Bugfix LP-735071 + for num in data[0].split(): + typ, msg_data = srv.fetch(num, '(BODY.PEEK[HEADER])') # header only (without setting READ flag) + for response_part in msg_data: + if isinstance(response_part, tuple): + try: + msg = email.message_from_string(response_part[1]) + except: + print "Could not get IMAP message." # debug + continue + try: try: - msg = email.message_from_string(response_part[1]) - except: - print "Could not get IMAP message." # debug - continue + sender = self.__format_header('sender', msg['From']) # get sender and format it + except KeyError: + print "KeyError exception for key 'From' in message." # debug + sender = self.__format_header('sender', msg['from']) + except: + print "Could not get sender from IMAP message." # debug + sender = "Error in sender" + try: try: - try: - sender = self.__format_header('sender', msg['From']) # get sender and format it - except KeyError: - print "KeyError exception for key 'From' in message." # debug - sender = self.__format_header('sender', msg['from']) - except: - print "Could not get sender from IMAP message." # debug - sender = "Error in sender" + datetime, seconds = self.__format_header('date', msg['Date']) # get date and format it + except KeyError: + print "KeyError exception for key 'Date' in message." # debug + datetime, seconds = self.__format_header('date', msg['date']) + except: + print "Could not get date from IMAP message." # debug + datetime = time.strftime('%Y.%m.%d %X') # take current time as "2010.12.31 13:57:04" + seconds = time.time() # take current time as seconds + try: try: - try: - datetime, seconds = self.__format_header('date', msg['Date']) # get date and format it - except KeyError: - print "KeyError exception for key 'Date' in message." # debug - datetime, seconds = self.__format_header('date', msg['date']) - except: - print "Could not get date from IMAP message." # debug - datetime = time.strftime('%Y.%m.%d %X') # take current time as "2010.12.31 13:57:04" - seconds = time.time() # take current time as seconds - try: - try: - subject = self.__format_header('subject', msg['Subject']) # get subject and format it - except KeyError: - print "KeyError exception for key 'Subject' in message." # debug - subject = self.__format_header('subject', msg['subject']) - except: - print "Could not get subject from IMAP message." # debug - subject = _('No subject') - try: - id = msg['Message-Id'] - except: - print "Could not get id from IMAP message." # debug - id = None # prepare emergency - - if id == None or id == '': - id = str(hash(subject)) # create emergency id - - if id not in mail_ids: # prevent duplicates caused by Gmail labels - if not (filter_enabled and self.__in_filter(sender + subject)): # check filter - mail_list.append(Mail(seconds, subject, \ - sender, datetime, id, acc.name)) - mail_count += 1 # increment mail counter for this IMAP folder - mail_ids.append(id) # add id to list + subject = self.__format_header('subject', msg['Subject']) # get subject and format it + except KeyError: + print "KeyError exception for key 'Subject' in message." # debug + subject = self.__format_header('subject', msg['subject']) + except: + print "Could not get subject from IMAP message." # debug + subject = _('No subject') + try: + id = msg['Message-Id'] + except: + print "Could not get id from IMAP message." # debug + id = None + + if id == None or id == '': + id = str(hash(acc.server + acc.user + sender + subject)) # create fallback id + + if id not in mail_ids: # prevent duplicates caused by Gmail labels + if not (filter_enabled and self.__in_filter(sender + subject)): # check filter + mail_list.append(Mail(seconds, subject, \ + sender, datetime, id, acc.get_id())) + mail_ids.append(id) # add id to list - acc.mail_count = mail_count # store number of emails per account - srv.close() - srv.logout() + # don't close IMAP idle connections + if not acc.idle: + srv.close() + srv.logout() else: # POP mail_total = len(srv.list()[1]) # number of mails on the server - mail_count = 0 # reset number of relevant mails for i in range(1, mail_total+1): # for each mail try: message = srv.top(i, 0)[1] # header plus first 0 lines from body @@ -176,31 +168,25 @@ class Mails: uidl = srv.uidl(i) # get id except: print "Could not get id from POP message." # debug - uidl = None # prepare emergency + uidl = None if uidl == None or uidl == '': - uidl = str(hash(subject)) # create emergency id + id = str(hash(acc.server + acc.user + sender + subject)) # create fallback id + else: + id = acc.user + uidl.split(' ')[2] # create unique id - id = acc.user + uidl.split(' ')[2] # create unique id if not (filter_enabled and self.__in_filter(sender + subject)): # check filter mail_list.append(Mail(seconds, subject, sender, \ - datetime, id, acc.name)) - mail_count += 1 # increment mail counter for this IMAP folder - acc.mail_count = mail_count # store number of emails per account + datetime, id, acc.get_id())) + srv.quit() # disconnect from Email-Server - mail_list = self.__sort_mails(mail_list, sort_order) # sort mails + + if (sort_order != None): + mail_list = self.sort_mails(mail_list, sort_order) # sort mails sys.stdout.flush() # write stdout to log file return mail_list - def __is_online(self): # check for internet connection - try: - urllib2.urlopen("http://www.google.com/") - return True - except: - return False - - def __in_filter(self, sendersubject): # check if filter appears in sendersubject status = False filter_text = self.cfg.get('filter', 'filter_text') @@ -217,7 +203,8 @@ class Mails: return status - def __sort_mails(self, mail_list, sort_order): # sort mail list + @staticmethod + def sort_mails(mail_list, sort_order): # sort mail list sort_list = [] for mail in mail_list: sort_list.append([mail.seconds, mail]) # extract seconds from mail instance diff --git a/Mailnag/daemon/mailsyncer.py b/Mailnag/daemon/mailsyncer.py new file mode 100644 index 0000000..05f5e89 --- /dev/null +++ b/Mailnag/daemon/mailsyncer.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# mailsyncer.py +# +# Copyright 2012 Patrick Ulbrich +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# + +from daemon.mails import Mails + +class MailSyncer: + def __init__(self, cfg): + self._cfg = cfg + self._mails_by_account = {} + self._mail_list = [] + + + def sync(self, accounts): + needs_rebuild = False + + # get mails from given accounts + rcv_lst = Mails(self._cfg, accounts).get_mail() + + # group received mails by account + tmp = {} + for acc in accounts: + tmp[acc.get_id()] = {} + for mail in rcv_lst: + tmp[mail.account_id][mail.id] = mail + + # compare current mails against received mails + # and remove those that are gone (probably opened in mail client). + for acc_id in self._mails_by_account.iterkeys(): + if acc_id in tmp: + del_ids = [] + for mail_id in self._mails_by_account[acc_id].iterkeys(): + if not (mail_id in tmp[acc_id]): + del_ids.append(mail_id) + needs_rebuild = True + for mail_id in del_ids: + del self._mails_by_account[acc_id][mail_id] + + # compare received mails against current mails + # and add new mails. + for acc_id in tmp: + if not (acc_id in self._mails_by_account): + self._mails_by_account[acc_id] = {} + for mail_id in tmp[acc_id]: + if not (mail_id in self._mails_by_account[acc_id]): + self._mails_by_account[acc_id][mail_id] = tmp[acc_id][mail_id] + needs_rebuild = True + + # rebuild and sort mail list + if needs_rebuild: + self._mail_list = [] + for acc_id in self._mails_by_account: + for mail_id in self._mails_by_account[acc_id]: + self._mail_list.append(self._mails_by_account[acc_id][mail_id]) + self._mail_list = Mails.sort_mails(self._mail_list, 'desc') + + return self._mail_list diff --git a/Mailnag/mailnag.py b/Mailnag/mailnag.py index ec2d01f..88fb7b8 100644 --- a/Mailnag/mailnag.py +++ b/Mailnag/mailnag.py @@ -3,7 +3,7 @@ # # mailnag.py # -# Copyright 2011 Patrick Ulbrich +# Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Leighton Earl # Copyright 2011 Ralf Hersel # @@ -25,10 +25,11 @@ import os from gi.repository import GObject, GLib +import time import signal from common.config import read_cfg, cfg_exists, cfg_folder -from common.utils import set_procname +from common.utils import set_procname, is_online from common.accountlist import AccountList from daemon.mailchecker import MailChecker from daemon.idlers import Idlers @@ -91,23 +92,38 @@ def main(): print 'Error: Cannot find configuration file. Please run mailnag_config first.' exit(1) + if not is_online(): + print 'Waiting for internet connection...' + while not is_online(): + time.sleep(5) + accounts = AccountList() accounts.load_from_cfg(cfg, enabled_only = True) - mailchecker = MailChecker(cfg, accounts) + mailchecker = MailChecker(cfg) - # immediate check - mailchecker.check() + # immediate check, check *all* accounts + mailchecker.check(accounts) + + idle_accounts = filter(lambda acc: acc.imap and acc.idle, accounts) + non_idle_accounts = filter(lambda acc: (not acc.imap) or (acc.imap and not acc.idle), accounts) # start polling thread for POP3 accounts and # IMAP accounts without idle support - if sum(1 for acc in accounts if ((not acc.imap ) or (acc.imap and not acc.idle))) > 0: + if len(non_idle_accounts) > 0: + def poll_func(): + mailchecker.check(non_idle_accounts) + return True + check_interval = int(cfg.get('general', 'check_interval')) - GObject.timeout_add_seconds(60 * check_interval, mailchecker.check) + GObject.timeout_add_seconds(60 * check_interval, poll_func) # start idler threads for IMAP accounts with idle support - if sum(1 for acc in accounts if (acc.imap and acc.idle)) > 0: - idlers = Idlers(accounts, mailchecker.check) + if len(idle_accounts) > 0: + def sync_func(account): + mailchecker.check([account]) + + idlers = Idlers(idle_accounts, sync_func) idlers.run() mainloop = GObject.MainLoop() diff --git a/mailnag b/mailnag index 143fd23..b4aed2c 100755 --- a/mailnag +++ b/mailnag @@ -3,37 +3,14 @@ LIB_DIR=./Mailnag config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/mailnag" -main() -{ - mkdir --parents "$config_dir" +mkdir --parents "$config_dir" - if [ -f "$config_dir/mailnag.pid" ]; then - kill $(cat "$config_dir/mailnag.pid") 2> /dev/null - fi - - rm --force "$config_dir/mailnag.log" - - cd $(dirname $(readlink -f $0)) - python $LIB_DIR/mailnag.py >> "$config_dir/mailnag.log" 2>&1 & -} - -check_connection() -{ - retries=50 - while ! ping -c1 www.google.com 2>/dev/null 1>&2 && [ $retries -gt 0 ] ; do - sleep 5 - # ((retries--)) - done - - if [ $retries -gt 0 ]; then - return 0 - else - return 1 # timed out - fi -} - -if check_connection; then - main -else - echo "Error: No internet connection." +if [ -f "$config_dir/mailnag.pid" ]; then + kill $(cat "$config_dir/mailnag.pid") 2> /dev/null fi + +rm --force "$config_dir/mailnag.log" + +cd $(dirname $(readlink -f $0)) +python $LIB_DIR/mailnag.py >> "$config_dir/mailnag.log" 2>&1 & +