mirror of
https://github.com/pulb/mailnag.git
synced 2026-02-12 06:46:45 +01:00
mass commit:
added single/summary notification modes support for GNOME 3 notification counter (single mode only) added button to mark mails as read (single mode only) removed open-in-mailreader and close buttons removed unused code and some comments removed unused config settings fixed program termination and cleanup fixed some potential multithreading issues refactored config stuff other minor refactoring
This commit is contained in:
85
Mailnag/config.py
Normal file
85
Mailnag/config.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# config.py
|
||||
#
|
||||
# Copyright 2011 Patrick Ulbrich <zulu99@gmx.net>
|
||||
#
|
||||
# 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 os
|
||||
import ConfigParser
|
||||
import xdg.BaseDirectory as bd
|
||||
|
||||
mailnag_defaults = {
|
||||
'general':
|
||||
{
|
||||
'mail_client' : "evolution",
|
||||
'messagetray_label' : "mailnag",
|
||||
'check_interval' : 5,
|
||||
'notification_mode' : 0,
|
||||
'remember' : 1,
|
||||
'sender_format' : 1,
|
||||
'playsound' : 1,
|
||||
'soundfile' : 'mailnag.wav',
|
||||
'autostart' : 1
|
||||
},
|
||||
'filter':
|
||||
{
|
||||
'filter_on' : 0,
|
||||
'filter_text' : 'newsletter, viagra'
|
||||
},
|
||||
'script':
|
||||
{
|
||||
'script0_on' : 0,
|
||||
'script1_on' : 0,
|
||||
'script0_file' : '',
|
||||
'script1_file' : '',
|
||||
},
|
||||
'account':
|
||||
{
|
||||
'on' : '',
|
||||
'name' : '',
|
||||
'server' : '',
|
||||
'user' : '',
|
||||
'imap' : '',
|
||||
'folder' : '',
|
||||
'port' : ''
|
||||
}
|
||||
}
|
||||
|
||||
cfg_folder = os.path.join(bd.xdg_config_home, "mailnag")
|
||||
cfg_file = os.path.join(cfg_folder, "mailnag.cfg")
|
||||
|
||||
def cfg_exists():
|
||||
return os.path.exists(cfg_file)
|
||||
|
||||
|
||||
def read_cfg():
|
||||
cfg = ConfigParser.RawConfigParser()
|
||||
cfg._sections = mailnag_defaults # HACK : use cfg.read_dict(mailnag_defaults) in python 3
|
||||
|
||||
if os.path.exists(cfg_file):
|
||||
cfg.read(cfg_file)
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
def write_cfg(cfg):
|
||||
if not os.path.exists(cfg_folder):
|
||||
os.popen("mkdir -p " + cfg_folder)
|
||||
|
||||
with open(cfg_file, 'wb') as configfile: cfg.write(configfile)
|
||||
@@ -25,13 +25,11 @@
|
||||
PACKAGE_NAME = "mailnag"
|
||||
|
||||
from gi.repository import GLib, GdkPixbuf, Gtk, GObject
|
||||
|
||||
import os
|
||||
import ConfigParser
|
||||
import locale
|
||||
import gettext
|
||||
import xdg.BaseDirectory as bd
|
||||
from utils import *
|
||||
from config import read_cfg, write_cfg
|
||||
from keyring import Keyring
|
||||
from account_dialog import AccountDialog
|
||||
|
||||
@@ -63,11 +61,13 @@ 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.accounts = Accounts(self.cfg, self.keyring)
|
||||
|
||||
self.treeview_accounts = builder.get_object("treeview_accounts")
|
||||
self.liststore_accounts = builder.get_object("liststore_accounts")
|
||||
@@ -97,6 +97,10 @@ class ConfigWindow:
|
||||
self.entry_mail_client = builder.get_object("entry_mail_client")
|
||||
self.entry_label = builder.get_object("entry_label")
|
||||
self.spinbutton_interval = builder.get_object("spinbutton_interval")
|
||||
self.cb_notification_mode = builder.get_object("cb_notification_mode")
|
||||
cell = Gtk.CellRendererText()
|
||||
self.cb_notification_mode.pack_start(cell, True)
|
||||
self.cb_notification_mode.add_attribute(cell, "text", 0)
|
||||
self.chk_playsound = builder.get_object("chk_playsound")
|
||||
self.chk_autostart = builder.get_object("chk_autostart")
|
||||
|
||||
@@ -127,32 +131,33 @@ class ConfigWindow:
|
||||
|
||||
|
||||
def load_config(self):
|
||||
self.entry_mail_client.set_text(cfg.get('general', 'mail_client'))
|
||||
self.entry_label.set_text(cfg.get('general', 'messagetray_label'))
|
||||
self.spinbutton_interval.set_value(int(cfg.get('general', 'check_interval')))
|
||||
self.chk_playsound.set_active(int(cfg.get('general', 'playsound')))
|
||||
self.chk_autostart.set_active(int(cfg.get('general', 'autostart')))
|
||||
self.entry_mail_client.set_text(self.cfg.get('general', 'mail_client'))
|
||||
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_enable_filter.set_active(int(cfg.get('filter', 'filter_on')))
|
||||
self.textbuffer_filter.set_text(cfg.get('filter', 'filter_text'))
|
||||
self.chk_enable_filter.set_active(int(self.cfg.get('filter', 'filter_on')))
|
||||
self.textbuffer_filter.set_text(self.cfg.get('filter', 'filter_text'))
|
||||
|
||||
self.chk_script0.set_active(int(cfg.get('script', 'script0_on')))
|
||||
self.chk_script0.set_active(int(self.cfg.get('script', 'script0_on')))
|
||||
|
||||
tmp = cfg.get('script', 'script0_file')
|
||||
tmp = self.cfg.get('script', 'script0_file')
|
||||
if len(tmp) > 0:
|
||||
self.filechooser_script0.set_filename(tmp)
|
||||
|
||||
self.chk_script1.set_active(int(cfg.get('script', 'script1_on')))
|
||||
self.chk_script1.set_active(int(self.cfg.get('script', 'script1_on')))
|
||||
|
||||
tmp = cfg.get('script', 'script1_file')
|
||||
tmp = self.cfg.get('script', 'script1_file')
|
||||
if len(tmp) > 0:
|
||||
self.filechooser_script1.set_filename(tmp)
|
||||
|
||||
self.accounts.load()
|
||||
|
||||
if len(self.accounts.account) == 0:
|
||||
imported_accounts = keyring.import_accounts()
|
||||
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:
|
||||
@@ -167,43 +172,43 @@ class ConfigWindow:
|
||||
|
||||
|
||||
def save_config(self):
|
||||
cfg.set('general', 'mail_client', self.entry_mail_client.get_text())
|
||||
cfg.set('general', 'messagetray_label', self.entry_label.get_text())
|
||||
cfg.set('general', 'check_interval', int(self.spinbutton_interval.get_value()))
|
||||
cfg.set('general', 'playsound',int(self.chk_playsound.get_active()))
|
||||
self.cfg.set('general', 'mail_client', self.entry_mail_client.get_text())
|
||||
self.cfg.set('general', 'messagetray_label', self.entry_label.get_text())
|
||||
self.cfg.set('general', 'check_interval', int(self.spinbutton_interval.get_value()))
|
||||
self.cfg.set('general', 'notification_mode', int(self.cb_notification_mode.get_active()))
|
||||
self.cfg.set('general', 'playsound',int(self.chk_playsound.get_active()))
|
||||
autostart = self.chk_autostart.get_active()
|
||||
cfg.set('general', 'autostart', int(autostart))
|
||||
self.cfg.set('general', 'autostart', int(autostart))
|
||||
|
||||
cfg.set('filter', 'filter_on', int(self.chk_enable_filter.get_active()))
|
||||
self.cfg.set('filter', 'filter_on', int(self.chk_enable_filter.get_active()))
|
||||
start, end = self.textbuffer_filter.get_bounds()
|
||||
cfg.set('filter', 'filter_text', self.textbuffer_filter.get_text(start, end, True))
|
||||
self.cfg.set('filter', 'filter_text', self.textbuffer_filter.get_text(start, end, True))
|
||||
|
||||
cfg.set('script', 'script0_on', int(self.chk_script0.get_active()))
|
||||
self.cfg.set('script', 'script0_on', int(self.chk_script0.get_active()))
|
||||
tmp = self.filechooser_script0.get_filename()
|
||||
if tmp == None: tmp = ""
|
||||
cfg.set('script', 'script0_file', tmp)
|
||||
self.cfg.set('script', 'script0_file', tmp)
|
||||
|
||||
cfg.set('script', 'script1_on', int(self.chk_script1.get_active()))
|
||||
self.cfg.set('script', 'script1_on', int(self.chk_script1.get_active()))
|
||||
tmp = self.filechooser_script1.get_filename()
|
||||
if tmp == None: tmp = ""
|
||||
cfg.set('script', 'script1_file', tmp)
|
||||
self.cfg.set('script', 'script1_file', tmp)
|
||||
|
||||
on, name, server, user, password, imap, folder, port = self.accounts.get_cfg()
|
||||
cfg.set('account', 'on', on)
|
||||
cfg.set('account', 'name', name)
|
||||
cfg.set('account', 'server', server)
|
||||
cfg.set('account', 'user', user)
|
||||
cfg.set('account', 'imap', imap)
|
||||
cfg.set('account', 'folder', folder)
|
||||
cfg.set('account', 'port', port)
|
||||
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'
|
||||
keyring.set(protocol, acc.user, acc.server, acc.password)
|
||||
self.keyring.set(protocol, acc.user, acc.server, acc.password)
|
||||
|
||||
cfg_file = os.path.join(cfg_folder, "mailnag.cfg")
|
||||
with open(cfg_file, 'wb') as configfile: cfg.write(configfile)
|
||||
write_cfg(self.cfg)
|
||||
|
||||
if autostart: create_autostart()
|
||||
else: delete_autostart()
|
||||
@@ -342,7 +347,7 @@ class ConfigWindow:
|
||||
|
||||
def __save_and_quit(self):
|
||||
self.save_config()
|
||||
keyring.remove(self.accounts.account) # delete obsolete entries from Keyring
|
||||
self.keyring.remove(self.accounts.account) # delete obsolete entries from Keyring
|
||||
Gtk.main_quit()
|
||||
|
||||
|
||||
@@ -371,10 +376,11 @@ class Account:
|
||||
|
||||
|
||||
class Accounts:
|
||||
def __init__(self):
|
||||
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
|
||||
@@ -395,13 +401,13 @@ class Accounts:
|
||||
def load(self):
|
||||
self.account = [] # empty account list
|
||||
|
||||
on = cfg.get('account', 'on')
|
||||
name = cfg.get('account', 'name')
|
||||
server = cfg.get('account', 'server')
|
||||
user = cfg.get('account', 'user')
|
||||
imap = cfg.get('account', 'imap')
|
||||
folder = cfg.get('account', 'folder')
|
||||
port = cfg.get('account', 'port')
|
||||
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)
|
||||
@@ -423,7 +429,7 @@ class Accounts:
|
||||
port = port_list[i]
|
||||
if imap: protocol = 'imap'
|
||||
else: protocol = 'pop'
|
||||
password = keyring.get(protocol, user, server)
|
||||
password = self.keyring.get(protocol, user, server)
|
||||
self.add(on, name, server, user, password, imap, folder, port) # fill Account list
|
||||
|
||||
|
||||
@@ -476,87 +482,9 @@ class Accounts:
|
||||
return cfg_on, cfg_name, cfg_server, cfg_user, password_list, cfg_imap, cfg_folder, cfg_port
|
||||
|
||||
|
||||
def ok(self, window): # check if name, server, user, password are not empty
|
||||
nok = [] # list of not-ok accounts
|
||||
for acc in self.account:
|
||||
if acc.name == '' or \
|
||||
acc.server == '' or \
|
||||
acc.user == '' or \
|
||||
acc.password == '':
|
||||
nok.append(acc.name)
|
||||
if len(nok) > 0:
|
||||
message_text = _("Missing data in account(s):") + "\n\n"
|
||||
for acc_name in nok:
|
||||
message_text += acc_name + '\n'
|
||||
message_text += "\n" + _("Please correct this first.")
|
||||
window.show_message(message_text)
|
||||
return False, nok
|
||||
else:
|
||||
return True, True
|
||||
|
||||
|
||||
#
|
||||
# Misc
|
||||
#
|
||||
def read_config(): # read config file or create it
|
||||
cfg = ConfigParser.RawConfigParser()
|
||||
cfg_file = cfg_file = os.path.join(cfg_folder, "mailnag.cfg")
|
||||
if not os.path.exists(cfg_file): # create a fresh config file
|
||||
print _("Config file does not exist, creating new one")
|
||||
cfg = set_default_config(cfg) # write default values to cfg
|
||||
with open(cfg_file, 'wb') as configfile: cfg.write(configfile)
|
||||
|
||||
cfg.read(cfg_file)
|
||||
return cfg
|
||||
|
||||
|
||||
def set_default_config(cfg):
|
||||
try: cfg.add_section('general')
|
||||
except ConfigParser.DuplicateSectionError: pass
|
||||
cfg.set('general', 'mail_client', "evolution")
|
||||
cfg.set('general', 'messagetray_label', "mailnag")
|
||||
cfg.set('general', 'check_interval', 5)
|
||||
cfg.set('general', 'show_only_new', 0)
|
||||
cfg.set('general', 'remember', 1)
|
||||
cfg.set('general', 'check_once', 0)
|
||||
cfg.set('general', 'sender_format', 1)
|
||||
cfg.set('general', 'playsound', 1)
|
||||
cfg.set('general', 'soundfile', 'mailnag.wav')
|
||||
cfg.set('general', 'autostart', 1)
|
||||
|
||||
try: cfg.add_section('filter')
|
||||
except ConfigParser.DuplicateSectionError: pass
|
||||
cfg.set('filter', 'filter_on', 0)
|
||||
cfg.set('filter', 'filter_text', 'newsletter, viagra')
|
||||
|
||||
try: cfg.add_section('script')
|
||||
except ConfigParser.DuplicateSectionError: pass
|
||||
cfg.set('script', 'script0_on', 0)
|
||||
cfg.set('script', 'script1_on', 0)
|
||||
cfg.set('script', 'script2_on', 0)
|
||||
cfg.set('script', 'script3_on', 0)
|
||||
cfg.set('script', 'script4_on', 0)
|
||||
cfg.set('script', 'script5_on', 0)
|
||||
cfg.set('script', 'script0_file', '')
|
||||
cfg.set('script', 'script1_file', '')
|
||||
cfg.set('script', 'script2_file', '')
|
||||
cfg.set('script', 'script3_file', '')
|
||||
cfg.set('script', 'script4_file', '')
|
||||
cfg.set('script', 'script5_file', '')
|
||||
|
||||
try: cfg.add_section('account')
|
||||
except ConfigParser.DuplicateSectionError: pass
|
||||
cfg.set('account', 'on', '')
|
||||
cfg.set('account', 'name', '')
|
||||
cfg.set('account', 'server', '')
|
||||
cfg.set('account', 'user', '')
|
||||
cfg.set('account', 'imap', '')
|
||||
cfg.set('account', 'folder', '')
|
||||
cfg.set('account', 'port', '')
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
def create_autostart():
|
||||
curdir = os.getcwd() # get working directory
|
||||
exec_file = os.path.join(curdir, "mailnag") # path of the shell script to start mailnag.py
|
||||
@@ -593,20 +521,9 @@ def delete_autostart():
|
||||
# Entry point
|
||||
#
|
||||
def main():
|
||||
global cfg, cfg_folder, keyring
|
||||
|
||||
set_procname("mailnag_config")
|
||||
|
||||
cfg_folder = os.path.join(bd.xdg_config_home, "mailnag")
|
||||
if not os.path.exists(cfg_folder):
|
||||
os.popen("mkdir -p " + cfg_folder)
|
||||
|
||||
cfg = read_config() # get configurations
|
||||
keyring = Keyring()
|
||||
|
||||
confwin = ConfigWindow()
|
||||
|
||||
Gtk.main() # start main loop
|
||||
Gtk.main()
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
||||
|
||||
@@ -22,27 +22,24 @@
|
||||
# MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
PACKAGE_NAME = "mailnag"
|
||||
|
||||
import poplib
|
||||
import imaplib
|
||||
import urllib2
|
||||
import ConfigParser
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
PACKAGE_NAME = "mailnag"
|
||||
|
||||
import threading
|
||||
from gi.repository import GObject, GLib, GdkPixbuf, Gtk, Notify
|
||||
|
||||
import time
|
||||
import email
|
||||
from email.header import decode_header
|
||||
import sys
|
||||
import gettext
|
||||
from config import read_cfg, cfg_exists, cfg_folder
|
||||
from keyring import Keyring
|
||||
from utils import *
|
||||
import signal
|
||||
import xdg.BaseDirectory as bd
|
||||
|
||||
|
||||
gettext.bindtextdomain(PACKAGE_NAME, './locale')
|
||||
gettext.textdomain(PACKAGE_NAME)
|
||||
@@ -347,7 +344,7 @@ class Mails:
|
||||
def in_filter(self, sendersubject): # check if filter appears in sendersubject
|
||||
status = False
|
||||
filter_text = cfg.get('filter', 'filter_text')
|
||||
filter_list = filter_text.replace('\n', '').split(',') # convert text to list
|
||||
filter_list = filter_text.replace('\n', '').split(',') # convert text to list
|
||||
for filter_item in filter_list:
|
||||
filter_stripped_item = filter_item.strip() # remove CR and white space
|
||||
|
||||
@@ -429,27 +426,24 @@ class Mails:
|
||||
|
||||
|
||||
# Misc =================================================================
|
||||
def read_config(cfg_file): # read config file or create it
|
||||
cfg = ConfigParser.RawConfigParser()
|
||||
if not os.path.exists(cfg_file):
|
||||
print 'Error: Cannot find configuration file: ', cfg_file
|
||||
exit(1)
|
||||
def read_config():
|
||||
if not cfg_exists():
|
||||
return None
|
||||
else:
|
||||
cfg.read(cfg_file)
|
||||
return cfg
|
||||
return read_cfg()
|
||||
|
||||
|
||||
def write_pid(): # write Mailnags's process id to file
|
||||
pid_file = os.path.join(user_path, 'mailnag.pid')
|
||||
def write_pid(): # write Mailnags's process id to file
|
||||
pid_file = os.path.join(cfg_folder, 'mailnag.pid')
|
||||
f = open(pid_file, 'w')
|
||||
f.write(str(os.getpid())) # get PID and write to file
|
||||
f.write(str(os.getpid()))
|
||||
f.close()
|
||||
|
||||
|
||||
def delete_pid(): # delete file mailnag.pid
|
||||
pid_file = os.path.join(user_path, 'mailnag.pid')
|
||||
def delete_pid(): # delete file mailnag.pid
|
||||
pid_file = os.path.join(cfg_folder, 'mailnag.pid')
|
||||
if os.path.exists(pid_file):
|
||||
os.popen("rm " + pid_file) # delete it
|
||||
os.popen("rm " + pid_file)
|
||||
|
||||
|
||||
def user_scripts(event, data):
|
||||
@@ -488,99 +482,151 @@ def commandExecutor(command, context_menu_command=None):
|
||||
# MailChecker ============================================================
|
||||
class MailChecker:
|
||||
def __init__(self):
|
||||
self.MAIL_LIST_LIMIT = 10 # prevent flooding of the messaging tray
|
||||
self.mails = Mails() # create Mails object
|
||||
self.reminder = Reminder() # create Reminder object
|
||||
|
||||
Notify.init(cfg.get('general', 'messagetray_label')) # initialize Notification
|
||||
self.notification = Notify.Notification.new(" ", None, None) # empty string will emit a gtk warning
|
||||
self.notification.set_hint("resident", GLib.Variant("b", True)) # don't close when the bubble or actions are clicked
|
||||
self.notification.set_category("email")
|
||||
self.notification.add_action("open", _("Open in mail reader"), self.__notification_action_handler, None, None)
|
||||
self.notification.add_action("close", _("Close"), self.__notification_action_handler, None, None)
|
||||
self.MAIL_LIST_LIMIT = 10 # prevent flooding of the messaging tray
|
||||
self.mailcheck_lock = threading.Lock()
|
||||
self.mails = Mails()
|
||||
self.reminder = Reminder()
|
||||
# dict that tracks all notifications that need to be closed
|
||||
self.notifications = {}
|
||||
|
||||
Notify.init(cfg.get('general', 'messagetray_label')) # initialize Notification
|
||||
|
||||
|
||||
def timeout(self, firstcheck = False):
|
||||
print 'Checking email accounts at:', time.asctime() # debug
|
||||
pid.kill() # kill all Zombies
|
||||
with self.mailcheck_lock:
|
||||
print 'Checking email accounts at:', time.asctime()
|
||||
pid.kill() # kill all zombies
|
||||
|
||||
if firstcheck: # Manual firststart
|
||||
self.reminder.load()
|
||||
if firstcheck: # Manual firststart
|
||||
self.reminder.load()
|
||||
|
||||
self.mail_list = self.mails.get_mail('desc') # get all mails from all inboxes
|
||||
self.mail_list = self.mails.get_mail('desc') # get all mails from all inboxes
|
||||
|
||||
all_mails = len(self.mail_list)
|
||||
new_mails = 0
|
||||
summary = ""
|
||||
body = ""
|
||||
script_data = ""
|
||||
unseen_mails = []
|
||||
new_mails = []
|
||||
|
||||
for mail in self.mail_list:
|
||||
if not self.reminder.contains(mail.id):
|
||||
new_mails += 1
|
||||
script_data += ' "<%s> %s"' % (mail.sender, mail.subject)
|
||||
|
||||
script_data = str(new_mails) + script_data
|
||||
|
||||
if all_mails == 0:
|
||||
# no mails (e.g. email client has been launched) -> close notification
|
||||
self.notification.close()
|
||||
elif (firstcheck and all_mails > 0) or (new_mails > 0):
|
||||
ubound = all_mails if all_mails <= self.MAIL_LIST_LIMIT else self.MAIL_LIST_LIMIT
|
||||
script_data = ""
|
||||
script_data_mailcount = 0
|
||||
|
||||
for i in range(ubound):
|
||||
body += self.mail_list[i].sender + ":\n<i>" + self.mail_list[i].subject + "</i>\n\n"
|
||||
for mail in self.mail_list:
|
||||
if self.reminder.contains(mail.id): # mail was fetched before
|
||||
if self.reminder.unseen(mail.id): # mail was not marked as seen
|
||||
unseen_mails.append(mail)
|
||||
if firstcheck: # first check after startup
|
||||
new_mails.append(mail)
|
||||
|
||||
else: # mail is fetched the first time
|
||||
unseen_mails.append(mail)
|
||||
new_mails.append(mail)
|
||||
script_data += ' "<%s> %s"' % (mail.sender, mail.subject)
|
||||
script_data_mailcount += 1
|
||||
|
||||
if all_mails > self.MAIL_LIST_LIMIT:
|
||||
body += "<i>" + _("(and {0} more)").format(str(all_mails - self.MAIL_LIST_LIMIT)) + "</i>"
|
||||
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
|
||||
for n in self.notifications.itervalues():
|
||||
n.close()
|
||||
self.notifications = {}
|
||||
elif len(new_mails) > 0:
|
||||
if cfg.get('general', 'notification_mode') == '1':
|
||||
self.notify_summary(unseen_mails)
|
||||
else:
|
||||
self.notify_single(new_mails)
|
||||
|
||||
if all_mails > 1: # multiple new emails
|
||||
summary = _("You have {0} new mails.").format(str(all_mails))
|
||||
else:
|
||||
summary = _("You have a new mail.")
|
||||
if cfg.get('general', 'playsound') == '1': # play sound?
|
||||
soundcommand = ['aplay', '-q', get_data_file(cfg.get('general', 'soundfile'))]
|
||||
pid.append(subprocess.Popen(soundcommand))
|
||||
|
||||
if cfg.get('general', 'playsound') == '1': # play sound?
|
||||
soundcommand = ['aplay', '-q', get_data_file(cfg.get('general', 'soundfile'))]
|
||||
pid.append(subprocess.Popen(soundcommand))
|
||||
self.reminder.save(self.mail_list)
|
||||
|
||||
self.notification.update(summary, body, "mail-unread")
|
||||
self.notification.show()
|
||||
|
||||
user_scripts("on_mail_check", script_data) # process user scripts
|
||||
|
||||
self.reminder.save(self.mail_list)
|
||||
sys.stdout.flush() # write stdout to log file
|
||||
user_scripts("on_mail_check", script_data) # process user scripts
|
||||
|
||||
sys.stdout.flush() # write stdout to log file
|
||||
return True
|
||||
|
||||
|
||||
def __notification_action_handler(self, n, action, user_data):
|
||||
self.notification.close()
|
||||
|
||||
if action == "open":
|
||||
emailclient = cfg.get('general', 'mail_client').split(' ') # create list of command arguments
|
||||
pid.append(subprocess.Popen(emailclient))
|
||||
elif action == "close":
|
||||
pass
|
||||
def notify_summary(self, unseen_mails):
|
||||
summary = ""
|
||||
body = ""
|
||||
|
||||
|
||||
def clear(self): # clear the messages list (not the menu entries)
|
||||
show_only_new = bool(int(cfg.get('general', 'show_only_new'))) # get show_only_new flag
|
||||
if len(self.notifications) == 0:
|
||||
self.notifications['0'] = self.get_notification(" ", None, None) # empty string will emit a gtk warning
|
||||
|
||||
if show_only_new:
|
||||
ubound = len(unseen_mails) if len(unseen_mails) <= self.MAIL_LIST_LIMIT else self.MAIL_LIST_LIMIT
|
||||
|
||||
for i in range(ubound):
|
||||
body += unseen_mails[i].sender + ":\n<i>" + unseen_mails[i].subject + "</i>\n\n"
|
||||
|
||||
if len(unseen_mails) > self.MAIL_LIST_LIMIT:
|
||||
body += "<i>" + _("(and {0} more)").format(str(len(unseen_mails) - self.MAIL_LIST_LIMIT)) + "</i>"
|
||||
|
||||
if len(unseen_mails) > 1: # multiple new emails
|
||||
summary = _("You have {0} new mails.").format(str(len(unseen_mails)))
|
||||
else:
|
||||
summary = _("You have a new mail.")
|
||||
|
||||
self.notifications['0'].update(summary, body, "mail-unread")
|
||||
self.notifications['0'].show()
|
||||
|
||||
|
||||
def notify_single(self, new_mails):
|
||||
for mail in new_mails:
|
||||
n = self.get_notification(mail.sender, mail.subject, "mail-unread")
|
||||
notification_id = str(id(n))
|
||||
n.add_action("mark-as-read", _("Mark as read"), self.__notification_action_handler, (mail, notification_id), None)
|
||||
n.show()
|
||||
self.notifications[notification_id] = n
|
||||
|
||||
|
||||
def get_notification(self, summary, body, icon):
|
||||
n = Notify.Notification.new(summary, body, icon)
|
||||
n.set_category("email")
|
||||
n.add_action("default", "default", self.__notification_action_handler, None, None)
|
||||
|
||||
return n
|
||||
|
||||
|
||||
def __notification_action_handler(self, n, action, user_data):
|
||||
with self.mailcheck_lock:
|
||||
if action == "default":
|
||||
emailclient = cfg.get('general', 'mail_client').split(' ') # create list of command arguments
|
||||
pid.append(subprocess.Popen(emailclient))
|
||||
|
||||
# clicking the notification bubble has closed all notifications
|
||||
# so clear the reference array as well.
|
||||
self.notifications = {}
|
||||
|
||||
elif action == "mark-as-read":
|
||||
self.reminder.set_to_seen(user_data[0].id)
|
||||
self.reminder.save(self.mail_list)
|
||||
|
||||
# clicking the action has closed the notification
|
||||
# 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) # save to mailnag.dat
|
||||
else: # keep 'list' filled
|
||||
self.mail_list = [] # clear mail list
|
||||
|
||||
self.reminder.save(self.mail_list)
|
||||
|
||||
# close all notifications
|
||||
for n in self.notifications.itervalues():
|
||||
n.close()
|
||||
self.notifications = {}
|
||||
|
||||
self.mail_list = []
|
||||
|
||||
|
||||
# Reminder =============================================================
|
||||
class Reminder(dict):
|
||||
|
||||
def load(self): # load last known messages from mailnag.dat
|
||||
remember = cfg.get('general', 'remember')
|
||||
dat_file = os.path.join(user_path, 'mailnag.dat')
|
||||
dat_file = os.path.join(cfg_folder, 'mailnag.dat')
|
||||
we_have_a_file = os.path.exists(dat_file) # check if file exists
|
||||
if remember == '1' and we_have_a_file:
|
||||
f = open(dat_file, 'r') # open file again
|
||||
@@ -595,7 +641,7 @@ class Reminder(dict):
|
||||
|
||||
|
||||
def save(self, mail_list): # save mail ids to file
|
||||
dat_file = os.path.join(user_path, 'mailnag.dat')
|
||||
dat_file = os.path.join(cfg_folder, 'mailnag.dat')
|
||||
f = open(dat_file, 'w') # open the file for overwrite
|
||||
for m in mail_list:
|
||||
try:
|
||||
@@ -609,11 +655,7 @@ class Reminder(dict):
|
||||
|
||||
|
||||
def contains(self, id): # check if mail id is in reminder list
|
||||
try:
|
||||
self[id]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
return (id in self)
|
||||
|
||||
|
||||
def set_to_seen(self, id): # set seen flag for this email on True
|
||||
@@ -626,10 +668,7 @@ class Reminder(dict):
|
||||
def unseen(self, id): # return True if flag == '0'
|
||||
try:
|
||||
flag = self[id]
|
||||
if flag == '0':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return (flag == '0')
|
||||
except KeyError:
|
||||
return True
|
||||
|
||||
@@ -650,36 +689,48 @@ class Pid(list): # List class to manage subprocess PIDs
|
||||
def cleanup():
|
||||
# clean up resources
|
||||
try:
|
||||
mailchecker.notification.close()
|
||||
for n in mailchecker.notifications.itervalues():
|
||||
n.close()
|
||||
except NameError: pass
|
||||
delete_pid()
|
||||
|
||||
|
||||
def sig_handler(signum, frame):
|
||||
if mainloop != None:
|
||||
mainloop.quit()
|
||||
|
||||
|
||||
# Main =================================================================
|
||||
def main():
|
||||
global cfg, user_path, accounts, mails, mailchecker, pid
|
||||
global mainloop, cfg, accounts, mails, mailchecker, pid
|
||||
|
||||
mainloop = None
|
||||
|
||||
set_procname("mailnag")
|
||||
|
||||
signal.signal(signal.SIGTERM, cleanup)
|
||||
|
||||
signal.signal(signal.SIGTERM, sig_handler)
|
||||
|
||||
try:
|
||||
user_path = os.path.join(bd.xdg_config_home, "mailnag")
|
||||
write_pid() # write Mailnag's process id to file
|
||||
cfg = read_config(os.path.join(user_path, 'mailnag.cfg')) # get configuration from file
|
||||
write_pid() # write Mailnag's process id to file
|
||||
cfg = read_config()
|
||||
|
||||
if (cfg == None):
|
||||
print 'Error: Cannot find configuration file. Please run mailnag_config first.'
|
||||
exit(1)
|
||||
|
||||
accounts = Accounts() # create Accounts object
|
||||
pid = Pid() # create Pid object
|
||||
mailchecker = MailChecker() # create MailChecker object
|
||||
mailchecker.timeout(True) # immediate check, firstcheck=True
|
||||
|
||||
if cfg.get('general', 'check_once') == '0': # wanna do more than one email check?
|
||||
check_interval = int(cfg.get('general', 'check_interval'))
|
||||
GObject.timeout_add_seconds(60 * check_interval, mailchecker.timeout)
|
||||
Gtk.main() # start Loop
|
||||
|
||||
cleanup()
|
||||
return 0
|
||||
check_interval = int(cfg.get('general', 'check_interval'))
|
||||
GObject.timeout_add_seconds(60 * check_interval, mailchecker.timeout)
|
||||
mainloop = GObject.MainLoop()
|
||||
mainloop.run()
|
||||
except KeyboardInterrupt:
|
||||
pass # ctrl+c pressed
|
||||
finally:
|
||||
cleanup()
|
||||
|
||||
|
||||
if __name__ == '__main__': main()
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
<property name="margin_right">6</property>
|
||||
<property name="margin_top">6</property>
|
||||
<property name="margin_bottom">6</property>
|
||||
<property name="n_rows">5</property>
|
||||
<property name="n_rows">6</property>
|
||||
<property name="n_columns">2</property>
|
||||
<property name="column_spacing">6</property>
|
||||
<property name="row_spacing">6</property>
|
||||
@@ -150,8 +150,8 @@
|
||||
</object>
|
||||
<packing>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
@@ -233,8 +233,8 @@
|
||||
</object>
|
||||
<packing>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
@@ -254,8 +254,8 @@
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
@@ -294,6 +294,40 @@
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="alignment4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label15">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Notification mode:</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="cb_notification_mode">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">liststore_notification_mode</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
@@ -599,5 +633,19 @@ Copyright (c) 2011 Ralf Hersel</property>
|
||||
<signal name="row-deleted" handler="liststore_accounts_row_deleted" swapped="no"/>
|
||||
<signal name="row-inserted" handler="liststore_accounts_row_inserted" swapped="no"/>
|
||||
</object>
|
||||
<object class="GtkListStore" id="liststore_notification_mode">
|
||||
<columns>
|
||||
<!-- column-name gchararray1 -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<data>
|
||||
<row>
|
||||
<col id="0" translatable="yes">Single</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0" translatable="yes">Summary</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkTextBuffer" id="textbuffer_filter"/>
|
||||
</interface>
|
||||
|
||||
Reference in New Issue
Block a user