diff --git a/Mailnag/common/account.py b/Mailnag/common/account.py new file mode 100644 index 0000000..92cf821 --- /dev/null +++ b/Mailnag/common/account.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# account.py +# +# Copyright 2011 Patrick Ulbrich +# Copyright 2011 Ralf Hersel +# +# 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. +# + +PACKAGE_NAME = "mailnag" + +import gettext +import time +import poplib +import daemon.imaplib2 as imaplib + +gettext.bindtextdomain(PACKAGE_NAME, './locale') +gettext.textdomain(PACKAGE_NAME) +_ = gettext.gettext + +account_defaults = { + 'enabled' : '0', + 'name' : '', + 'user' : '', + 'server' : '', + 'port' : '', + 'ssl' : '0', + 'imap' : '0', + 'idle' : '0', + 'folder' : '' +} + +class Account: + def __init__(self, enabled = False, name = _('Unnamed'), user = '', \ + password = '', server = '', port = '', ssl = False, imap = False, idle = False, folder = '' ): + + self.enabled = enabled # bool + self.name = name + self.user = user + self.password = password + self.server = server + self.port = port + self.ssl = ssl # bool + self.imap = imap # bool + self.idle = idle # bool + self.folder = folder + + + def get_connection(self): # get email server connection + if self.imap: # IMAP + try: + srv = self._get_IMAP_connection() + except: + print "Warning: Cannot connect to IMAP account: %s. " \ + "Next try in 30 seconds." % self.server + time.sleep(30) # wait 30 seconds + try: + srv = self._get_IMAP_connection() + except: + print "Error: Cannot connect to IMAP account: %s. " % self.server + srv = None + else: # POP + try: + srv = self._get_POP3_connection() + except: + print "Warning: Cannot connect to POP account: %s. " \ + "Next try in 30 seconds." % self.server + time.sleep(30) # wait 30 seconds + try: + srv = self._get_POP3_connection() + except: + print "Error: Cannot connect to POP account: %s. " % self.server + srv = 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 + + + def _get_POP3_connection(self): + if self.ssl: + if self.port == '': + srv = poplib.POP3_SSL(self.server) + else: + srv = poplib.POP3_SSL(self.server, self.port) + else: + if self.port == '': + srv = poplib.POP3(self.server) + else: + srv = poplib.POP3(self.server, self.port) + + srv.getwelcome() + srv.user(self.user) + srv.pass_(self.password) + return srv + diff --git a/Mailnag/common/accountlist.py b/Mailnag/common/accountlist.py new file mode 100644 index 0000000..e4dd941 --- /dev/null +++ b/Mailnag/common/accountlist.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# accountlist.py +# +# 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 +# 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. +# + +PACKAGE_NAME = "mailnag" + +from ConfigParser import NoSectionError +from common.account import Account, account_defaults +from common.keyring import Keyring + +class AccountList(list): + def __init__(self): + self._keyring = Keyring() + + + def load_from_cfg(self, cfg, enabled_only=False): + del self[:] + + i = 1 + section_name = "Account" + str(i) + + while cfg.has_section(section_name): + enabled = bool(int( self._get_account_cfg(cfg, section_name, 'enabled') )) + + if (not enabled_only) or (enabled_only and enabled): + name = self._get_account_cfg(cfg, section_name, 'name') + user = self._get_account_cfg(cfg, section_name, 'user') + server = self._get_account_cfg(cfg, section_name, 'server') + port = self._get_account_cfg(cfg, section_name, 'port') + ssl = bool(int( self._get_account_cfg(cfg, section_name, 'ssl') )) + imap = bool(int( self._get_account_cfg(cfg, section_name, 'imap') )) + idle = bool(int( self._get_account_cfg(cfg, section_name, 'idle') )) + folder = self._get_account_cfg(cfg, section_name, 'folder') + + protocol = 'imap' if imap else 'pop' + password = self._keyring.get(protocol, user, server) + + acc = Account(enabled, name, user, password, server, port, ssl, imap, idle, folder) + self.append(acc) + + i = i + 1 + section_name = "Account" + str(i) + + + def save_to_cfg(self, cfg): + # remove existing accounts from cfg + i = 1 + section_name = "Account" + str(i) + while cfg.has_section(section_name): + cfg.remove_section(section_name) + i = i + 1 + section_name = "Account" + str(i) + + # add accounts + i = 1 + for acc in self: + section_name = "Account" + str(i) + + cfg.add_section(section_name) + + cfg.set(section_name, 'enabled', int(acc.enabled)) + cfg.set(section_name, 'name', acc.name) + cfg.set(section_name, 'user', acc.user) + cfg.set(section_name, 'server', acc.server) + cfg.set(section_name, 'port', acc.port) + cfg.set(section_name, 'ssl', int(acc.ssl)) + cfg.set(section_name, 'imap', int(acc.imap)) + cfg.set(section_name, 'idle', int(acc.idle)) + cfg.set(section_name, 'folder', acc.folder) + + protocol = 'imap' if acc.imap else 'pop' + self._keyring.set(protocol, acc.user, acc.server, acc.password) + + i = i + 1 + + # delete obsolete entries from Keyring + self._keyring.remove(self) + + + def import_from_keyring(self): + # append imported accounts to existing accounts + self.extend(self._keyring.import_accounts()) + + + def _get_account_cfg(self, cfg, section_name, option_name): + if cfg.has_option(section_name, option_name): + return cfg.get(section_name, option_name) + else: + return account_defaults[option_name] + diff --git a/Mailnag/common/config.py b/Mailnag/common/config.py index 9bc82b5..730cacc 100644 --- a/Mailnag/common/config.py +++ b/Mailnag/common/config.py @@ -21,45 +21,33 @@ # MA 02110-1301, USA. # import os -import ConfigParser import xdg.BaseDirectory as bd - +from ConfigParser import RawConfigParser from utils import get_default_mail_reader - mailnag_defaults = { 'general': { 'mail_client' : get_default_mail_reader(), - 'messagetray_label' : "mailnag", - 'check_interval' : 5, - 'notification_mode' : 0, - 'sender_format' : 1, - 'playsound' : 1, + 'messagetray_label' : 'mailnag', + 'check_interval' : '5', + 'notification_mode' : '0', + 'sender_format' : '1', + 'playsound' : '1', 'soundfile' : 'mailnag.ogg', - 'autostart' : 1 + 'autostart' : '1' }, 'filter': { - 'filter_on' : 0, + 'filter_enabled' : '0', 'filter_text' : 'newsletter, viagra' }, 'script': { - 'script0_on' : 0, - 'script1_on' : 0, + 'script0_enabled' : '0', + 'script1_enabled' : '0', 'script0_file' : '', - 'script1_file' : '', - }, - 'account': - { - 'on' : '', - 'name' : '', - 'server' : '', - 'user' : '', - 'imap' : '', - 'folder' : '', - 'port' : '' + 'script1_file' : '' } } @@ -71,7 +59,7 @@ def cfg_exists(): def read_cfg(): - cfg = ConfigParser.RawConfigParser() + cfg = RawConfigParser() cfg._sections = mailnag_defaults # HACK : use cfg.read_dict(mailnag_defaults) in python 3 if os.path.exists(cfg_file): diff --git a/Mailnag/common/keyring.py b/Mailnag/common/keyring.py index eb56068..3735db5 100644 --- a/Mailnag/common/keyring.py +++ b/Mailnag/common/keyring.py @@ -27,7 +27,9 @@ from gi.repository import GObject, GLib, GdkPixbuf, Gtk import locale import gettext import gnomekeyring -from utils import get_data_file + +from common.utils import get_data_file +from common.account import Account PACKAGE_NAME = "mailnag" @@ -89,7 +91,7 @@ class Keyring: return '' - def import_accounts(self): # get email accounts from Gnome-Keyring + def import_accounts(self): # get email accounts from Gnome-Keyring accounts = [] if gnomekeyring.list_item_ids_sync(self.defaultKeyring): displayNameDict = {} @@ -101,21 +103,26 @@ class Keyring: or displayName.startswith('imap://'): server = displayName.split('@')[1][:-1] if displayName.startswith('imap://'): - imap = 1 + imap = True user = displayName.split('@')[0][7:] else: - imap = 0 + imap = False user = displayName.split('@')[0][6:] + user = user.replace('%40','@') + if ';' in user: user = user.split(';')[0] + password = gnomekeyring.item_get_info_sync(self.defaultKeyring, \ displayNameDict[displayName]).get_secret() - accounts.append([server, user, password, imap]) + + accounts.append(Account(enabled = True, name = "%s (%s)" % (server, user), \ + server = server, user = user, password = password, imap = imap)) return accounts - def set(self, protocol, user, server, password): # store password in Gnome-Keyring + def set(self, protocol, user, server, password): # store password in Gnome-Keyring if password != '': displayNameDict = {} for identity in gnomekeyring.list_item_ids_sync(self.defaultKeyring): diff --git a/Mailnag/configuration/account.py b/Mailnag/configuration/account.py deleted file mode 100644 index 35125ad..0000000 --- a/Mailnag/configuration/account.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# account.py -# -# Copyright 2011 Patrick Ulbrich -# Copyright 2011 Ralf Hersel -# -# 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. -# - -class Account: - def __init__(self, on, name, server, user, password, imap, folder, port): - self.id = str(id(self)) # unique identifier - self.on = on # int - self.name = name - self.server = server - self.user = user - self.password = password - self.imap = imap # int - self.folder = folder - self.port = port - - - def get_row(self): - return [self.id, self.on, self.name] - diff --git a/Mailnag/configuration/accountdialog.py b/Mailnag/configuration/accountdialog.py index 6a8a7a2..1e6b014 100644 --- a/Mailnag/configuration/accountdialog.py +++ b/Mailnag/configuration/accountdialog.py @@ -23,8 +23,8 @@ PACKAGE_NAME = "mailnag" -from gi.repository import GLib, GdkPixbuf, Gtk import gettext +from gi.repository import GLib, GdkPixbuf, Gtk from common.utils import get_data_file gettext.bindtextdomain(PACKAGE_NAME, './locale') @@ -38,8 +38,8 @@ class AccountDialog: builder.set_translation_domain(PACKAGE_NAME) builder.add_from_file(get_data_file("account_dialog.ui")) builder.connect_signals({ \ + "account_type_changed" : self.__on_cmb_account_type_changed, \ "entry_changed" : self.__on_entry_changed, \ - "chk_account_imap_toggled" : self.__on_chk_account_imap_toggled, \ "btn_cancel_clicked" : self.__on_btn_cancel_clicked, \ "btn_save_clicked" : self.__on_btn_save_clicked \ }) @@ -47,15 +47,20 @@ class AccountDialog: self.window = builder.get_object("account_dialog") self.window.set_transient_for(parent) + self.cmb_account_type = builder.get_object("cmb_account_type") self.entry_account_name = builder.get_object("entry_account_name") self.entry_account_user = builder.get_object("entry_account_user") self.entry_account_password = builder.get_object("entry_account_password") self.entry_account_server = builder.get_object("entry_account_server") self.entry_account_port = builder.get_object("entry_account_port") - self.chk_account_imap = builder.get_object("chk_account_imap") + self.label_account_folder = builder.get_object("label_account_folder") self.entry_account_folder = builder.get_object("entry_account_folder") + self.chk_account_push = builder.get_object("chk_account_push") + self.chk_account_ssl = builder.get_object("chk_account_ssl") self.button_save = builder.get_object("button_save") + self.cmb_account_type.set_active(0) # default to POP3 + def run(self): return self.window.run() @@ -83,7 +88,14 @@ class AccountDialog: self.button_save.set_sensitive(ok) - def __on_chk_account_imap_toggled(self, widget): - self.entry_account_folder.set_sensitive(self.chk_account_imap.get_active()) + def __on_cmb_account_type_changed(self, widget): + if self.cmb_account_type.get_active() == 0: # POP3 + self.label_account_folder.set_visible(False) + self.entry_account_folder.set_visible(False) + self.chk_account_push.set_visible(False) + else: # IMAP + self.label_account_folder.set_visible(True) + self.entry_account_folder.set_visible(True) + self.chk_account_push.set_visible(True) diff --git a/Mailnag/configuration/accounts.py b/Mailnag/configuration/accounts.py deleted file mode 100644 index 416fce8..0000000 --- a/Mailnag/configuration/accounts.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# accounts.py -# -# Copyright 2011 Patrick Ulbrich -# Copyright 2011 Ralf Hersel -# -# 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. -# - -PACKAGE_NAME = "mailnag" - -import gettext -from configuration.account import Account - -gettext.bindtextdomain(PACKAGE_NAME, './locale') -gettext.textdomain(PACKAGE_NAME) -_ = gettext.gettext - - -class Accounts: - def __init__(self, cfg, keyring): - self.account = [] - self.current = None # currently selected account_id - self.cfg = cfg - self.keyring = keyring - - def add(self, on = 0, name = _('Unnamed'), server = '', user= '' , \ - password = '', imap = 0, folder = '', port = ''): # add one new account - self.account.append(Account(on, name, server, user, \ - password, imap, folder, port)) - this_account = self.account[-1] # get last element of the list - id = this_account.id - return id - - - def remove(self, id): # delete account by id - for acc in self.account[:]: # iterate copy of account list - if acc.id == id: # find matching account - self.account.remove(acc) # delete it - break # stop iteration - - - def load(self): - self.account = [] # empty account list - - on = self.cfg.get('account', 'on') - name = self.cfg.get('account', 'name') - server = self.cfg.get('account', 'server') - user = self.cfg.get('account', 'user') - imap = self.cfg.get('account', 'imap') - folder = self.cfg.get('account', 'folder') - port = self.cfg.get('account', 'port') - - separator = '|' - on_list = on.split(separator) - name_list = name.split(separator) - server_list = server.split(separator) - user_list = user.split(separator) - imap_list = imap.split(separator) - folder_list = folder.split(separator) - port_list = port.split(separator) - - for i in range(len(name_list)): # iterate 0 to nr of elements in name_list - name = name_list[i] - if name == '': continue - on = int(on_list[i]) - server = server_list[i] - user = user_list[i] - imap = int(imap_list[i]) - folder = folder_list[i] - port = port_list[i] - if imap: protocol = 'imap' - else: protocol = 'pop' - password = self.keyring.get(protocol, user, server) - self.add(on, name, server, user, password, imap, folder, port) # fill Account list - - - def get(self, id): # return all data of one account - self.current = id - for acc in self.account: - if acc.id == id: - return acc - return None - - - def get_current(self): # return current account - if self.current != None: - for acc in self.account: - if acc.id == self.current: - return acc - return None - - - def get_cfg(self): # return arrays of account strings for cfg - separator = '|' - - on_list = [] - name_list = [] - server_list = [] - user_list = [] - password_list = [] - imap_list = [] - folder_list = [] - port_list = [] - - for acc in self.account: # collect all values - on_list.append(str(int(acc.on))) - name_list.append(acc.name) - server_list.append(acc.server) - user_list.append(acc.user) - password_list.append(acc.password) - imap_list.append(str(int(acc.imap))) - folder_list.append(acc.folder) - port_list.append(acc.port) - - cfg_on = separator.join(on_list) # concatenate values - cfg_name = separator.join(name_list) - cfg_server = separator.join(server_list) - cfg_user = separator.join(user_list) - cfg_imap = separator.join(imap_list) - cfg_folder = separator.join(folder_list) - cfg_port = separator.join(port_list) - - return cfg_on, cfg_name, cfg_server, cfg_user, password_list, cfg_imap, cfg_folder, cfg_port - - diff --git a/Mailnag/configuration/configwindow.py b/Mailnag/configuration/configwindow.py index 4f2c6d1..97145c4 100644 --- a/Mailnag/configuration/configwindow.py +++ b/Mailnag/configuration/configwindow.py @@ -31,9 +31,9 @@ import gettext from common.utils import get_data_file from common.config import read_cfg, write_cfg -from common.keyring import Keyring +from common.accountlist import AccountList +from common.account import Account from configuration.accountdialog import AccountDialog -from configuration.accounts import Accounts locale.bindtextdomain(PACKAGE_NAME, './locale') gettext.bindtextdomain(PACKAGE_NAME, './locale') @@ -61,13 +61,12 @@ class ConfigWindow: self.window = builder.get_object("config_window") self.window.set_icon(GdkPixbuf.Pixbuf.new_from_file_at_size(get_data_file("mailnag.svg"), 48, 48)); - self.keyring = Keyring() self.cfg = read_cfg() # # account tab # - self.accounts = Accounts(self.cfg, self.keyring) + self.accounts = AccountList() self.treeview_accounts = builder.get_object("treeview_accounts") self.liststore_accounts = builder.get_object("liststore_accounts") @@ -75,11 +74,6 @@ class ConfigWindow: self.button_edit = builder.get_object("button_edit") self.button_remove = builder.get_object("button_remove") -# colhead = [('Id'), _('Active'), _('Name')] # column headings -# -# renderer_id = Gtk.CellRendererText() -# column_id = Gtk.TreeViewColumn(colhead[0], renderer_id, text=0) # Account Id - renderer_on = Gtk.CellRendererToggle() renderer_on.connect("toggled", self.__on_account_toggled) # bind toggle signal column_on = Gtk.TreeViewColumn(_('Enabled'), renderer_on) # Account On/Off @@ -135,38 +129,35 @@ class ConfigWindow: self.entry_label.set_text(self.cfg.get('general', 'messagetray_label')) self.spinbutton_interval.set_value(int(self.cfg.get('general', 'check_interval'))) self.cb_notification_mode.set_active(int(self.cfg.get('general', 'notification_mode'))) - self.chk_playsound.set_active(int(self.cfg.get('general', 'playsound'))) - self.chk_autostart.set_active(int(self.cfg.get('general', 'autostart'))) + self.chk_playsound.set_active(bool(int(self.cfg.get('general', 'playsound')))) + self.chk_autostart.set_active(bool(int(self.cfg.get('general', 'autostart')))) - self.chk_enable_filter.set_active(int(self.cfg.get('filter', 'filter_on'))) + self.chk_enable_filter.set_active(bool(int(self.cfg.get('filter', 'filter_enabled')))) self.textbuffer_filter.set_text(self.cfg.get('filter', 'filter_text')) - self.chk_script0.set_active(int(self.cfg.get('script', 'script0_on'))) + self.chk_script0.set_active(bool(int(self.cfg.get('script', 'script0_enabled')))) tmp = self.cfg.get('script', 'script0_file') if len(tmp) > 0: self.filechooser_script0.set_filename(tmp) - self.chk_script1.set_active(int(self.cfg.get('script', 'script1_on'))) + self.chk_script1.set_active(bool(int(self.cfg.get('script', 'script1_enabled')))) tmp = self.cfg.get('script', 'script1_file') if len(tmp) > 0: self.filechooser_script1.set_filename(tmp) - self.accounts.load() + self.accounts.load_from_cfg(self.cfg) - if len(self.accounts.account) == 0: - imported_accounts = self.keyring.import_accounts() - if len(imported_accounts) > 0 and \ - self.show_yesno_dialog(_("Mailnag found %s mail accounts on this computer.\n\nDo you want to import them?") % len(imported_accounts)): - for arr in imported_accounts: - self.accounts.add(name = "%s (%s)" % (arr[1], arr[0]), \ - on = 1, server = arr[0], user = arr[1], \ - password = arr[2], imap = arr[3]) - - for acc in self.accounts.account: - row = acc.get_row() + if len(self.accounts) == 0: + self.accounts.import_from_keyring() + if len(self.accounts) > 0 and \ + (not self.show_yesno_dialog(_("Mailnag found %s mail accounts on this computer.\n\nDo you want to import them?") % len(self.accounts))): + del self.accounts[:] + + for acc in self.accounts: + row = [acc, acc.enabled, acc.name] self.liststore_accounts.append(row) self.select_path((0,)) @@ -180,34 +171,22 @@ class ConfigWindow: autostart = self.chk_autostart.get_active() self.cfg.set('general', 'autostart', int(autostart)) - self.cfg.set('filter', 'filter_on', int(self.chk_enable_filter.get_active())) + self.cfg.set('filter', 'filter_enabled', int(self.chk_enable_filter.get_active())) start, end = self.textbuffer_filter.get_bounds() self.cfg.set('filter', 'filter_text', self.textbuffer_filter.get_text(start, end, True)) - self.cfg.set('script', 'script0_on', int(self.chk_script0.get_active())) + self.cfg.set('script', 'script0_enabled', int(self.chk_script0.get_active())) tmp = self.filechooser_script0.get_filename() if tmp == None: tmp = "" self.cfg.set('script', 'script0_file', tmp) - self.cfg.set('script', 'script1_on', int(self.chk_script1.get_active())) + self.cfg.set('script', 'script1_enabled', int(self.chk_script1.get_active())) tmp = self.filechooser_script1.get_filename() if tmp == None: tmp = "" self.cfg.set('script', 'script1_file', tmp) - on, name, server, user, password, imap, folder, port = self.accounts.get_cfg() - self.cfg.set('account', 'on', on) - self.cfg.set('account', 'name', name) - self.cfg.set('account', 'server', server) - self.cfg.set('account', 'user', user) - self.cfg.set('account', 'imap', imap) - self.cfg.set('account', 'folder', folder) - self.cfg.set('account', 'port', port) - - for acc in self.accounts.account: - if bool(acc.imap): protocol = 'imap' - else: protocol = 'pop' - self.keyring.set(protocol, acc.user, acc.server, acc.password) - + self.accounts.save_to_cfg(self.cfg) + write_cfg(self.cfg) if autostart: self.create_autostart() @@ -227,9 +206,9 @@ class ConfigWindow: treeselection = self.treeview_accounts.get_selection() # get tree_selection object selection = treeselection.get_selected() # get selected tupel (model, iter) model, iter = selection # get selected iter - if iter != None: id = model.get_value(iter, 0) # get account_id from treeviews 1. column - else: id = None - return id, model, iter + if iter != None: acc = model.get_value(iter, 0) # get account object from treeviews 1. column + else: acc = None + return acc, model, iter def select_path(self, path): # select path in treeview @@ -239,18 +218,20 @@ class ConfigWindow: def edit_account(self): - id, model, iter = self.get_selected_account() + acc, model, iter = self.get_selected_account() if iter != None: - acc = self.accounts.get(id) d = AccountDialog(self.window) + d.cmb_account_type.set_active(acc.imap) + d.entry_account_name.set_text(acc.name) d.entry_account_user.set_text(acc.user) d.entry_account_password.set_text(acc.password) d.entry_account_server.set_text(acc.server) d.entry_account_port.set_text(acc.port) - d.chk_account_imap.set_active(acc.imap) d.entry_account_folder.set_text(acc.folder) + d.chk_account_push.set_active(acc.idle) + d.chk_account_ssl.set_active(acc.ssl) res = d.run() @@ -260,8 +241,16 @@ class ConfigWindow: acc.password = d.entry_account_password.get_text() acc.server = d.entry_account_server.get_text() acc.port = d.entry_account_port.get_text() - acc.imap = d.chk_account_imap.get_active() - acc.folder = d.entry_account_folder.get_text() + acc.ssl = d.chk_account_ssl.get_active() + + if d.cmb_account_type.get_active() == 0: # POP3 + acc.imap = False + acc.folder = '' + acc.idle = False + else: # IMAP + acc.imap = True + acc.folder = d.entry_account_folder.get_text() + acc.idle = d.chk_account_push.get_active() model.set_value(iter, 2, acc.name) @@ -303,25 +292,33 @@ class ConfigWindow: def __on_account_toggled(self, cell, path): # chk_box account_on toggled model = self.liststore_accounts iter = model.get_iter(path) - id = model.get_value(iter, 0) - acc = self.accounts.get(id) # get account by id - acc.on = not acc.on # update account.on + acc = model.get_value(iter, 0) + acc.enabled = not acc.enabled self.liststore_accounts.set_value(iter, 1, not cell.get_active()) - + def __on_btn_add_clicked(self, widget): d = AccountDialog(self.window) res = d.run() if res == 1: - id = self.accounts.add(1, d.entry_account_name.get_text(), - d.entry_account_server.get_text(), d.entry_account_user.get_text(), - d.entry_account_password.get_text(), d.chk_account_imap.get_active(), - d.entry_account_folder.get_text(), d.entry_account_port.get_text() - ) + if d.cmb_account_type.get_active() == 0: # POP3 + imap = False + folder = '' + idle = False + else: # IMAP + imap = True + folder = d.entry_account_folder.get_text() + idle = d.chk_account_push.get_active() - row = [id, 1, d.entry_account_name.get_text()] + acc = Account(enabled = True, name = d.entry_account_name.get_text(), \ + user = d.entry_account_user.get_text(), password = d.entry_account_password.get_text(), \ + server = d.entry_account_server.get_text(), port = d.entry_account_port.get_text(), \ + ssl = d.chk_account_ssl.get_active(), imap = imap, idle = idle , folder = folder) + self.accounts.append(acc) + + row = [acc, True, acc.name] iter = self.liststore_accounts.append(row) model = self.treeview_accounts.get_model() path = model.get_path(iter) @@ -336,11 +333,10 @@ class ConfigWindow: def __on_btn_remove_clicked(self, widget): - id, model, iter = self.get_selected_account() + acc, model, iter = self.get_selected_account() if iter != None: - name = model.get_value(iter, 2) # get account_name if self.show_yesno_dialog(_('Delete this account:') + \ - '\n\n' + name): + '\n\n' + acc.name): p = model.get_path(iter) if not p.prev(): @@ -348,7 +344,7 @@ class ConfigWindow: self.select_path(p) # select prev/next account model.remove(iter) # delete in treeview - self.accounts.remove(id) # delete in accounts list + self.accounts.remove(acc) # delete in accounts list def __on_treeview_accounts_row_activated(self, treeview, path, view_column): @@ -378,8 +374,7 @@ class ConfigWindow: def __save_and_quit(self): - self.save_config() - self.keyring.remove(self.accounts.account) # delete obsolete entries from Keyring + self.save_config() Gtk.main_quit() diff --git a/Mailnag/daemon/account.py b/Mailnag/daemon/account.py deleted file mode 100644 index 554e689..0000000 --- a/Mailnag/daemon/account.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# account.py -# -# Copyright 2011 Patrick Ulbrich -# Copyright 2011 Ralf Hersel -# -# 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. -# - -import time -import poplib -import daemon.imaplib2 as imaplib - -class Account: - def __init__(self, check_interval, name, server, user, password, imap, folder, port): - self.check_interval = check_interval - self.name = name - self.server = server - self.user = user - self.password = password - self.imap = imap # int - self.folder = folder - self.port = port - self.mail_count = 0 - - - def get_connection(self): # get email server connection - if self.imap: # IMAP - try: - try: - if self.port == '': - srv = imaplib.IMAP4_SSL(self.server) # SSL - else: - srv = imaplib.IMAP4_SSL(self.server, self.port) - except: - if self.port == '': - srv = imaplib.IMAP4(self.server) # non SSL - else: - srv = imaplib.IMAP4(self.server, self.port) - srv.login(self.user, self.password) - except: - print "Warning: Cannot connect to IMAP account: %s. " \ - "Next try in 30 seconds." % self.server - time.sleep(30) # wait 30 seconds - try: - try: - if self.port == '': - srv = imaplib.IMAP4_SSL(self.server) # SSL - else: - srv = imaplib.IMAP4_SSL(self.server, self.port) - except: - if self.port == '': - srv = imaplib.IMAP4(self.server) # non SSL - else: - srv = imaplib.IMAP4(self.server, self.port) - srv.login(self.user, self.password) - except: - print "Error: Cannot connect to IMAP account: %s. " \ - "Next try in %s minutes." % (self.server, self.check_interval) - srv = None - else: # POP - try: - try: - if self.port == '': - srv = poplib.POP3_SSL(self.server) # connect to Email-Server via SSL - else: - srv = poplib.POP3_SSL(self.server, self.port) - except: - if self.port == '': - srv = poplib.POP3(self.server) # non SSL - else: - srv = poplib.POP3(self.server, self.port) - srv.getwelcome() - srv.user(self.user) - srv.pass_(self.password) - except: - print "Warning: Cannot connect to POP account: %s. " \ - "Next try in 30 seconds." % self.server - time.sleep(30) # wait 30 seconds - try: - try: - if self.port == '': - srv = poplib.POP3_SSL(self.server) # try it again - else: - srv = poplib.POP3_SSL(self.server, self.port) - except: - if self.port == '': - srv = poplib.POP3(self.server) # non SSL - else: - srv = poplib.POP3(self.server, self.port) - srv.getwelcome() - srv.user(self.user) - srv.pass_(self.password) - except: - print "Error: Cannot connect to POP account: %s. " \ - "Next try in %s minutes." % (self.server, self.check_interval) - srv = None - - return srv # server object - - diff --git a/Mailnag/daemon/accounts.py b/Mailnag/daemon/accounts.py deleted file mode 100644 index 39ae891..0000000 --- a/Mailnag/daemon/accounts.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# accounts.py -# -# Copyright 2011 Patrick Ulbrich -# Copyright 2011 Ralf Hersel -# -# 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 common.keyring import Keyring -from daemon.account import Account - -class Accounts: - def __init__(self, cfg): - self.account = [] - keyring = Keyring() - self.keyring_was_locked = keyring.was_locked - - separator = '|' - on_list = cfg.get('account', 'on').split(separator) - name_list = cfg.get('account', 'name').split(separator) - server_list = cfg.get('account', 'server').split(separator) - user_list = cfg.get('account', 'user').split(separator) - imap_list = cfg.get('account', 'imap').split(separator) - folder_list = cfg.get('account', 'folder').split(separator) - port_list = cfg.get('account', 'port').split(separator) - check_interval = cfg.get('general', 'check_interval') - - # check if the account list is empty - if len(name_list) == 1 and name_list[0] == '': - return - - for i in range(len(name_list)): # iterate 0 to nr of elements in name_list - on = int(on_list[i]) - name = name_list[i] - if not on or name == '': continue # ignore accounts that are off or have no name - server = server_list[i] - user = user_list[i] - imap = int(imap_list[i]) - folder = folder_list[i] - port = port_list[i] - if imap: protocol = 'imap' - else: protocol = 'pop' - password = keyring.get(protocol, user, server) - self.account.append(Account(check_interval, name, server, user, password, imap, folder, port)) - - - def get_count(self, name): # get number of emails for this provider - count = 'error' - for acc in self.account: - if acc.name == name: - count = str(acc.mail_count) - break - if count == 'error': - print 'Cannot find account (get_count)' - return count diff --git a/Mailnag/daemon/idlers.py b/Mailnag/daemon/idlers.py index 4765de6..9f7a250 100644 --- a/Mailnag/daemon/idlers.py +++ b/Mailnag/daemon/idlers.py @@ -32,8 +32,8 @@ class Idlers: def run(self): - for acc in self._accounts.account: - if acc.imap: # TODO : and enable_push + for acc in self._accounts: + if acc.imap and acc.idle: try: self._new_idler(acc) except: @@ -52,7 +52,7 @@ class Idlers: return # Need to get out of AUTH mode. - if account.folder: + if len(account.folder) > 0: server.select(account.folder) else: server.select("INBOX") diff --git a/Mailnag/daemon/mailchecker.py b/Mailnag/daemon/mailchecker.py index c65ce3f..9782fc5 100644 --- a/Mailnag/daemon/mailchecker.py +++ b/Mailnag/daemon/mailchecker.py @@ -187,14 +187,14 @@ class MailChecker: def run_user_scripts(self, event, data): if event == "on_mail_check": - if self.cfg.get('script', 'script0_on') == '1': + if self.cfg.get('script', 'script0_enabled') == '1': script_file = self.cfg.get('script', 'script0_file') if script_file != '' and os.path.exists(script_file): self.pid.append(subprocess.Popen("%s %s" % (script_file, data), shell = True)) else: print 'Warning: cannot execute script:', script_file - if (data != '0') and (self.cfg.get('script', 'script1_on') == '1'): + if (data != '0') and (self.cfg.get('script', 'script1_enabled') == '1'): script_file = self.cfg.get('script', 'script1_file') if script_file != '' and os.path.exists(script_file): self.pid.append(subprocess.Popen("%s %s" % (script_file, data), shell = True)) diff --git a/Mailnag/daemon/mails.py b/Mailnag/daemon/mails.py index 6f4eebd..ebba522 100644 --- a/Mailnag/daemon/mails.py +++ b/Mailnag/daemon/mails.py @@ -41,9 +41,9 @@ class Mails: mail_list = [] # initialize list of mails mail_ids = [] # initialize list of mail ids while not self.is_online(): time.sleep(5) # wait for internet connection - filter_on = int(self.cfg.get('filter', 'filter_on')) # get filter switch + filter_enabled = bool(int(self.cfg.get('filter', 'filter_enabled'))) # get filter switch - for acc in self.accounts.account: # loop all email accounts + for acc in self.accounts: srv = acc.get_connection() # get server connection for this account if srv == None: continue # continue with next account if server is empty @@ -116,7 +116,7 @@ class Mails: id = str(hash(subject)) # create emergency id if id not in mail_ids: # prevent duplicates caused by Gmail labels - if not (filter_on and self.in_filter(sender + subject)): # check filter + 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 @@ -178,7 +178,7 @@ class Mails: uidl = str(hash(subject)) # create emergency id id = acc.user + uidl.split(' ')[2] # create unique id - if not (filter_on and self.in_filter(sender + subject)): # check filter + 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 diff --git a/Mailnag/mailnag.py b/Mailnag/mailnag.py index 99a91ef..121a204 100644 --- a/Mailnag/mailnag.py +++ b/Mailnag/mailnag.py @@ -29,7 +29,7 @@ import signal from common.config import read_cfg, cfg_exists, cfg_folder from common.utils import set_procname -from daemon.accounts import Accounts +from common.accountlist import AccountList from daemon.mailchecker import MailChecker from daemon.idlers import Idlers @@ -91,14 +91,20 @@ def main(): print 'Error: Cannot find configuration file. Please run mailnag_config first.' exit(1) - accounts = Accounts(cfg) + accounts = AccountList() + accounts.load_from_cfg(cfg, enabled_only = True) + mailchecker = MailChecker(cfg, accounts) - mailchecker.check(True) # immediate check, firstcheck=True - check_interval = int(cfg.get('general', 'check_interval')) - GObject.timeout_add_seconds(60 * check_interval, mailchecker.check) + # 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: + mailchecker.check(True) # immediate check, firstcheck=True + check_interval = int(cfg.get('general', 'check_interval')) + GObject.timeout_add_seconds(60 * check_interval, mailchecker.check) - if False: + # 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) idlers.run() diff --git a/data/account_dialog.ui b/data/account_dialog.ui index e1a1123..6b3a32f 100644 --- a/data/account_dialog.ui +++ b/data/account_dialog.ui @@ -67,67 +67,25 @@ 6 6 6 - 7 + 9 2 - + True False 0 - + True False 0 - Accountname: + Port: 0 - 0 - 1 - 1 - - - - - True - False - 0 - - - True - False - 0 - User: - - - - - 0 - 1 - 1 - 1 - - - - - True - False - 0 - - - True - False - 0 - Password: - - - - - 0 - 2 + 5 1 1 @@ -146,27 +104,6 @@ - - 0 - 3 - 1 - 1 - - - - - True - False - 0 - - - True - False - 0 - Port (optional): - - - 0 4 @@ -175,23 +112,86 @@ - + True - True - True - - True - + False + 0 + + + True + False + 0 + Password: + + - 1 - 0 + 0 + 3 1 1 - + + True + False + 0 + + + True + False + 0 + User: + + + + + 0 + 2 + 1 + 1 + + + + + True + False + 0 + + + True + False + 0 + Accountname: + + + + + 0 + 1 + 1 + 1 + + + + + True + True + True + + True + optional + + + 1 + 5 + 1 + 1 + + + + True True True @@ -201,7 +201,7 @@ 1 - 1 + 4 1 1 @@ -218,13 +218,13 @@ 1 - 2 + 3 1 1 - + True True True @@ -234,59 +234,34 @@ 1 - 3 + 2 1 1 - + True True True True + 1 - 4 + 1 1 1 - - True - False - 12 - - - IMAP - True - True - False - False - 0 - True - - - - - - 0 - 5 - 1 - 1 - - - - + True False 0 - 24 - + True False 0 @@ -304,11 +279,11 @@ True - False True True True + optional 1 @@ -317,9 +292,81 @@ 1 + + + Enable Push-IMAP + True + True + False + False + 0 + True + + + 0 + 7 + 2 + 1 + + + + + True + False + 0 + + + True + False + 0 + Account type: + + + + + 0 + 0 + 1 + 1 + + + + + True + False + + POP3 + IMAP + + + + + 1 + 0 + 1 + 1 + + + + + Enable SSL encryption + True + True + False + False + 0 + True + + + 0 + 8 + 2 + 1 + + True diff --git a/data/config_window.ui b/data/config_window.ui index 06fd717..0837d81 100644 --- a/data/config_window.ui +++ b/data/config_window.ui @@ -623,9 +623,9 @@ Copyright (c) 2011 Ralf Hersel - - - + + +