#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Unity Mail
# Authors: Dmitry Shachnev <mitya57@gmail.com>
#          Andre Ryser <andre.ryser@gmail.com>
# License: GNU GPL 3 or higher; http://www.gnu.org/licenses/gpl.html

app_name = 'unity-mail'
app_version = '0.92.3'

import imaplib
import email
import email.header
import email.utils
import os
import sys
import shutil
import gettext
import signal
import time

from socket import error as socketerror
from configparser import RawConfigParser
from subprocess import Popen

from gi.repository import GObject, GLib, Gtk, Unity, \
Notify, Indicate, Dbusmenu, GnomeKeyring

gettext.bindtextdomain('unity-mail')
gettext.textdomain('unity-mail')
_ = gettext.gettext

config_dir = GLib.get_user_config_dir()

def decode_wrapper(header):
	"""Decodes an Email header, returns a string"""
	# A hack for headers without a space between decoded name and email
	dec = email.header.decode_header(header.replace('=?=<', '=?= <'))
	parts = []
	for dec_part in dec:
		if dec_part[1]:
			try:
				parts.append(dec_part[0].decode(dec_part[1]))
			except:
				print('unity-mail: Exception in decode, skipping')
		elif isinstance(dec_part[0], bytes):
			parts.append(dec_part[0].decode())
		else:
			parts.append(dec_part[0])
	return str.join(' ', parts)

def get_header_wrapper(message, header_name, decode=False):
	header = message[header_name]
	if isinstance(header, str):
		header = header.replace(' \r\n', '').replace('\r\n', '')
		return (decode_wrapper(header) if decode else header)
	return ''

def get_sender_name(sender):
	"""Strips address, and returns only name"""
	sname = email.utils.parseaddr(sender)[0]
	return sname if sname else sender

def get_value_from_attributes_list(attributes, name):
	for attribute in attributes:
		if attribute.name == name:
			if attribute.type == GnomeKeyring.AttributeType.STRING:
				return attribute.get_string()
			else:
				return attribute.get_uint32()

get_item_id = lambda key: key.item_id
fix_format = lambda string: string.replace('%(t0)s', '{t0}').replace('%(t1)s', '{t1}')

class TableLabel(Gtk.Label):
	def __init__(self, name = None):
		Gtk.Label.__init__(self, name)
		self.set_alignment(1, 0.5)

class AccountsDialog(Gtk.Dialog):
	"""Accounts configuration dialog"""
	def __init__(self):
		self.init = False
		self.init_config()
		Gtk.Dialog.__init__(self, _('Unity Mail Preferences'), None, 0,
			(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY,
			Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))
		self.set_icon_name('preferences-system')
		self.connect('destroy', lambda w: Gtk.main_quit())
		self.set_position(Gtk.WindowPosition.CENTER)
		self.set_border_width(5)
		content_area = self.get_content_area()
		
		notebook = Gtk.Notebook()
		notebook.append_page(self.accounts_page(), Gtk.Label(_('Accounts')))
		notebook.append_page(self.urls_page(), Gtk.Label(_('URLs')))
		notebook.append_page(self.options_page(), Gtk.Label(_('Options')))
		content_area.add(notebook)
		
		self.datadicts = [{'Host': 'imap.gmail.com', 'Port': '993', 'Login': '', 'Passwd': ''}]
		self.show_all()
		self.init = True
		self.index = 0
		
	def init_config(self):
		config = ConfigFile(config_dir+'/unity-mail.conf')
		self.urls = {}
		for urlname in 'Home', 'Compose', 'Inbox', 'Sent':
			self.urls[urlname] = config.get_str_with_default(urlname, section = 'URLs')
		self.interval = config.get_int_with_default('Interval', 30)
		self.enable_notifications = config.get_bool_with_default('EnableNotifications', True)
		self.display_icons = config.get_bool_with_default('NotificationsHaveIcons', False)
		self.draw_attention = config.get_bool_with_default('DrawAttention', True)
		self.hide_count = config.get_bool_with_default('HideMessagesCount', False)
		self.command = config.get_str_with_default('ExecOnReceive')
	
	def accounts_page(self):
		acclabel = Gtk.Label()
		acclabel.set_markup('<b>'+_('Choose an account')+'</b>')
		self.sb = Gtk.SpinButton.new_with_range(1, 1, 1)
		self.sb.connect('changed', self.on_sb_changed)
		self.addbtn = Gtk.Button.new_from_stock(Gtk.STOCK_ADD)
		self.addbtn.connect('clicked', self.add_dict)
		self.rmbtn = Gtk.Button.new_from_stock(Gtk.STOCK_REMOVE)
		self.rmbtn.connect('clicked', self.remove_dict)
		self.rmbtn.set_sensitive(False)
		accbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
		accbox.expand = True
		accbox.pack_start(self.sb, True, True, 0)
		accbox.pack_end(self.rmbtn, False, False, 0)
		accbox.pack_end(self.addbtn, False, False, 0)
		self.hostentry = Gtk.Entry()
		self.portentry = Gtk.Entry()
		self.loginentry = Gtk.Entry()
		self.passwdentry = Gtk.Entry()
		self.passwdentry.set_visibility(False)
		self.passwdentry.set_property('caps-lock-warning', True)
		srvlabel = Gtk.Label()
		srvlabel.set_markup('<b>'+_('Server data')+'</b>')
		infolabel = Gtk.Label()
		infolabel.set_markup('<b>'+_('Account data')+'</b>')
		table = Gtk.Table()
		table.attach(srvlabel, 0, 2, 0, 1)
		table.attach(Gtk.Label(_('Host:')), 0, 1, 1, 2, Gtk.AttachOptions.SHRINK)
		table.attach(Gtk.Label(_('Port:')), 0, 1, 2, 3, Gtk.AttachOptions.SHRINK)
		table.attach(self.hostentry, 1, 2, 1, 2)
		table.attach(self.portentry, 1, 2, 2, 3)
		table.attach(infolabel, 0, 2, 3, 4)
		table.attach(Gtk.Label(_('Login:')), 0, 1, 4, 5, Gtk.AttachOptions.SHRINK)
		table.attach(Gtk.Label(_('Password:')), 0, 1, 5, 6, Gtk.AttachOptions.SHRINK)
		table.attach(self.loginentry, 1, 2, 4, 5)
		table.attach(self.passwdentry, 1, 2, 5, 6)
		table.set_row_spacing(0, 5)
		table.set_row_spacing(2, 5)
		table.set_row_spacing(3, 5)
		table.set_col_spacings(8)
		
		page = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
		page.add(acclabel)
		page.add(accbox)
		page.add(table)
		return page
	
	def urls_page(self):
		self.homeentry = Gtk.Entry()
		self.homeentry.set_text(self.urls['Home'])
		self.composeentry = Gtk.Entry()
		self.composeentry.set_text(self.urls['Compose'])
		self.inboxentry = Gtk.Entry()
		self.inboxentry.set_text(self.urls['Inbox'])
		self.sententry = Gtk.Entry()
		self.sententry.set_text(self.urls['Sent'])
		table = Gtk.Table()
		table.set_col_spacings(8)
		table.attach(Gtk.Label(_('Home:')), 0, 1, 0, 1, Gtk.AttachOptions.SHRINK)
		table.attach(self.homeentry, 1, 2, 0, 1)
		table.attach(Gtk.Label(_('Compose:')), 0, 1, 1, 2, Gtk.AttachOptions.SHRINK)
		table.attach(self.composeentry, 1, 2, 1, 2)
		table.attach(Gtk.Label(_('Inbox:')), 0, 1, 2, 3, Gtk.AttachOptions.SHRINK)
		table.attach(self.inboxentry, 1, 2, 2, 3)
		table.attach(Gtk.Label(_('Sent:')), 0, 1, 3, 4, Gtk.AttachOptions.SHRINK)
		table.attach(self.sententry, 1, 2, 3, 4)
		page = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
		page.add(table)
		return page
	
	def options_page(self):
		self.intbox = Gtk.SpinButton.new_with_range(1, 60, 1)
		self.intbox.set_value(self.interval)
		self.notifyswitch = Gtk.Switch()
		self.notifyswitch.set_active(self.enable_notifications)
		self.notifyiconswitch = Gtk.Switch()
		self.notifyiconswitch.set_active(self.display_icons)
		self.mmswitch = Gtk.Switch()
		self.mmswitch.set_active(self.draw_attention)
		self.hcswitch = Gtk.Switch()
		self.hcswitch.set_active(self.hide_count)
		self.commandchooser = Gtk.FileChooserButton()
		if self.command:
			self.commandchooser.set_filename(self.command)
		table = Gtk.Table()
		table.set_col_spacings(8)
		table.attach(TableLabel(_('Refresh interval (seconds):')), 0, 1, 0, 1)
		table.attach(self.intbox, 1, 2, 0, 1, Gtk.AttachOptions.SHRINK)
		table.attach(TableLabel(_('Enable notifications:')), 0, 1, 1, 2)
		table.attach(self.notifyswitch, 1, 2, 1, 2, Gtk.AttachOptions.SHRINK)
		table.attach(TableLabel(_('Show icon in notifications:')), 0, 1, 2, 3)
		table.attach(self.notifyiconswitch, 1, 2, 2, 3, Gtk.AttachOptions.SHRINK)
		table.attach(TableLabel(_('Change color of messaging menu icon:')), 0, 1, 3, 4)
		table.attach(self.mmswitch, 1, 2, 3, 4, Gtk.AttachOptions.SHRINK)
		table.attach(TableLabel(_('Hide count when it is zero:')), 0, 1, 4, 5)
		table.attach(self.hcswitch, 1, 2, 4, 5, Gtk.AttachOptions.SHRINK)
		commandbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
		commandbox.add(Gtk.Label(_('Execute this command on messages receive:')))
		commandbox.pack_end(self.commandchooser, True, True, 0)
		page = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
		page.add(table)
		page.add(Gtk.Separator())
		page.add(commandbox)
		return page
	
	def run(self):
		self.update_entries()
		Gtk.main()
	
	def save_all_settings(self):
		config = ConfigFile(config_dir+'/unity-mail.conf')
		for urlname, urlwidget in \
		('Home', self.homeentry), ('Compose', self.composeentry), \
		('Inbox', self.inboxentry), ('Sent', self.sententry):
			config.save_with_default(urlname, urlwidget.get_text(), '', section='URLs')
		config.save_with_default('Interval', int(self.intbox.get_value()), 30)
		config.save_with_default('EnableNotifications', self.notifyswitch.get_active(), True)
		config.save_with_default('NotificationsHaveIcons', self.notifyiconswitch.get_active(), False)
		config.save_with_default('DrawAttention', self.mmswitch.get_active(), True)
		config.save_with_default('HideMessagesCount', self.hcswitch.get_active(), False)
		config.save_with_default('ExecOnReceive', self.commandchooser.get_filename(), None)
		config.save_to_file(config_dir+'/unity-mail.conf')
	
	def add_dict(self, btn):
		self.update_datadicts()
		self.datadicts.append({'Host': 'imap.gmail.com', 'Port': '993', 'Login': '', 'Passwd': ''})
		self.sb.set_range(1, len(self.datadicts))
		self.sb.set_value(len(self.datadicts))
	
	def remove_dict(self, btn):
		self.update_datadicts()
		index = self.sb.get_value_as_int()-1
		del self.datadicts[index]
		self.init = False
		self.sb.set_range(1, len(self.datadicts))
		self.init = True
		self.update_entries()
		self.rmbtn.set_sensitive(len(self.datadicts)>1)
		if index+1 > len(self.datadicts):
			self.sb.set_value(index)
			index -= 1
	
	def set_dicts(self, datadicts):
		self.init = False
		self.datadicts = datadicts
		self.index = 0
		self.sb.set_range(1, len(datadicts))
		self.update_entries()
		self.rmbtn.set_sensitive(len(self.datadicts)>1)
		self.init = True
	
	def on_sb_changed(self, sb):
		self.update_datadicts()
		self.index = self.sb.get_value_as_int()-1
		self.update_entries()
		self.rmbtn.set_sensitive(len(self.datadicts)>1)
	
	def update_entries(self):
		if self.datadicts:
			index = self.sb.get_value_as_int()-1
			self.hostentry.set_text(self.datadicts[index]['Host'])
			self.portentry.set_text(str(self.datadicts[index]['Port']))
			self.loginentry.set_text(self.datadicts[index]['Login'])
			self.passwdentry.set_text(self.datadicts[index]['Passwd'])
	
	def update_datadicts(self):
		if self.init:
			index = self.index
			self.datadicts[index]['Host'] = self.hostentry.get_text()
			if self.portentry.get_text():
				self.datadicts[index]['Port'] = self.portentry.get_text()
			else:
				self.datadicts[index]['Port'] = '993'
			self.datadicts[index]['Login'] = self.loginentry.get_text()
			self.datadicts[index]['Passwd'] = self.passwdentry.get_text()

class ConfigFile(RawConfigParser):
	"""Extension of RawConfigParser with some useful additions"""
	
	def __init__(self, filename):
		RawConfigParser.__init__(self)
		self.optionxform = str
		self.read(filename)
	
	def get_str_with_default(self, item, defaultvalue = '', section = 'General'):
		if self.has_option(section, item):
			return self.get(section, item)
		else:
			return defaultvalue
	
	def get_bool_with_default(self, item, defaultvalue, section = 'General'):
		if self.has_option(section, item):
			return self.getboolean(section, item)
		else:
			return defaultvalue
	
	def get_int_with_default(self, item, defaultvalue, section = 'General'):
		if self.has_option(section, item):
			return self.getint(section, item)
		else:
			return defaultvalue
	
	def save_with_default(self, item, value, defaultvalue, section = 'General'):
		if value == defaultvalue:
			if self.has_option(section, item):
				self.remove_option(section, item)
		else:
			if not self.has_section(section):
				self.add_section(section)
			self.set(section, item, value)
	
	def save_to_file(self, filename):
		of = open(filename, 'w')
		self.write(of)
		of.close()

class TimeoutException(Exception):
	"""Timeout exception for internal use"""
	pass

class UnityMail(object):
	"""Main Unity Mail Application"""
	
	def __init__(self):
		self.dicts = []
		self.host = []
		self.port = []
		self.login = []
		self.passwd = []
		self.accdlg = None
		self.init_argv_help()
		self.init_keyring()
		self.init_autostart()
		self.init_argv_change()
		self.init_config()
		Notify.init('Unity Mail')
		self.l = len(self.dicts)
		self.mail_client = [None]*self.l
		self.notinit = [True]*self.l
		self.unread_messages = [] # [accountid, number, indicator, isactive, folder, message-id]
		desktop_id = 'unity-mail.desktop'
		favorites = Unity.LauncherFavorites.get_default().enumerate_ids()
		for i in favorites:
			if 'unity-mail' in i: desktop_id = i
		self.launcher = Unity.LauncherEntry.get_for_desktop_id(desktop_id)
		self.first_run = True
		
		self.mm = Indicate.Server.ref_default() # Messaging Menu server
		self.mm.set_type('message.im')
		self.mm.set_desktop_file('/usr/share/applications/unity-mail.desktop')
		self.mm.connect('server-display', self.on_server_display)
		menuserver = Dbusmenu.Server()
		root = Dbusmenu.Menuitem()
		menuserver.set_root(root)
		item_markread = Dbusmenu.Menuitem()
		item_markread.property_set('label', _('Mark all as read'))
		item_markread.connect('item-activated', self.on_mark_all_read)
		root.child_append(item_markread)
		self.mm.set_menu(menuserver)
		self.mm.show()
		
		self.loop = GObject.MainLoop()
		GObject.set_application_name('unity-mail')
		GObject.timeout_add_seconds(self.interval, self.update)
		self.update()
		self.start_loop()
	
	def start_loop(self):
		try:
			self.loop.run()
		except KeyboardInterrupt:
			try:
				if input('\nDo you really want to exit (y/n)? ') in ('y', 'yes'):
					sys.exit()
			except EOFError:
				print('\n')
			self.start_loop()
	
	def on_server_display(self, server, timestamp):
		Popen(('um-url', 'Home'))
	
	def on_mm_item_clicked(self, timestamp, indicator, urlid):
		Popen(('um-url', urlid))
	
	def on_mark_all_read(self, menuitem, timestamp):
		for message in self.unread_messages:
			if message[3]:
				client = self.mail_client[message[0]]
				if message[4]:
					client.select(message[4])
				else:
					client.select()
				client.store(message[1], '+FLAGS', '\Seen')
				client.close()
				message[2].hide()
	
	def init_config(self):
		config = ConfigFile(config_dir+'/unity-mail.conf')
		self.interval = config.get_int_with_default('Interval', 30)
		self.enable_notifications = config.get_bool_with_default('EnableNotifications', True)
		self.display_icons = config.get_bool_with_default('NotificationsHaveIcons', False)
		self.draw_attention = config.get_bool_with_default('DrawAttention', True)
		self.hide_count = config.get_bool_with_default('HideMessagesCount', False)
		
		self.command = config.get_str_with_default('ExecOnReceive')
		value = config.get_str_with_default('Blacklist')
		self.black_list = value.split(', ') if value else []
		value = config.get_str_with_default('ExtraFolders')
		self.extra_folders = value.split(', ') if value else []
		
		self.on_click_urls = []
		if config.has_section('URLs'):
			self.on_click_urls = config.items('URLs')
	
	def init_autostart(self):
		as_dir = config_dir+'/autostart/'
		if os.path.exists('/usr/share/unity-mail/unity-mail-autostart.desktop') and \
		not os.path.exists(as_dir+'unity-mail-autostart.desktop'):
			print('unity-mail: Adding to Autostart')
			if not os.path.exists(as_dir):
				os.makedirs(as_dir)
			shutil.copy('/usr/share/unity-mail/unity-mail-autostart.desktop', \
			as_dir+'unity-mail-autostart.desktop')
	
	def init_argv_help(self):
		if len(sys.argv) > 1:
			if sys.argv[1] == '--help' or sys.argv[1] == '-h':
				print('Unity Mail, version', app_version)
				print('Usage:')
				print('  unity-mail [options]')
				print('Options:')
				print('  -c, --change  : Change accounts data')
				print('  -h, --help    : Display this help message and exit')
				print('  -v, --version : Display version number and exit')
				sys.exit(0)
			if sys.argv[1] == '--version' or sys.argv[1] == '-v':
				print('Unity Mail, version', app_version)
				sys.exit(0)
	
	def init_argv_change(self):
		if len(sys.argv) > 1:
			if sys.argv[1] == '--change' or sys.argv[1] == '-c':
				self.open_dialog(set_dicts=True)
		if not self.dicts:
			print('unity-mail: No accounts registered; exiting')
			sys.exit()
	
	def init_keyring(self):
		search_attrs = GnomeKeyring.Attribute.list_new()
		GnomeKeyring.Attribute.list_append_string(search_attrs, 'application', 'unity-mail')
		result, self.mail_keys = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.NETWORK_PASSWORD, search_attrs)
		if result == GnomeKeyring.Result.NO_MATCH:
			self.open_dialog()
		for key in sorted(self.mail_keys, key=get_item_id):
			attributes = GnomeKeyring.Attribute.list_to_glist(key.attributes)
			self.host.append(get_value_from_attributes_list(attributes, 'server'))
			self.port.append(get_value_from_attributes_list(attributes, 'port'))
			self.login.append(get_value_from_attributes_list(attributes, 'username'))
			self.passwd.append(key.secret)
			self.dicts.append({'Host': self.host[-1], 'Port': self.port[-1], \
			'Login': self.login[-1], 'Passwd': self.passwd[-1]})
	
	def create_keyring_item(self, ind):
		attrs = GnomeKeyring.Attribute.list_new()
		GnomeKeyring.Attribute.list_append_string(attrs, 'application', 'unity-mail')
		GnomeKeyring.Attribute.list_append_string(attrs, 'service', 'imap')
		GnomeKeyring.Attribute.list_append_string(attrs, 'server', self.host[ind])
		GnomeKeyring.Attribute.list_append_uint32(attrs, 'port', self.port[ind])
		GnomeKeyring.Attribute.list_append_string(attrs, 'username', self.login[ind])
		name = 'unity-mail-{0}'.format(ind)
		GnomeKeyring.item_create_sync(None, GnomeKeyring.ItemType.NETWORK_PASSWORD, \
		name, attrs, self.passwd[ind], True)
	
	def open_dialog(self, set_dicts=False):
		self.accdlg = AccountsDialog()
		self.accdlg.connect('response', self.on_dialog_response)
		if set_dicts:
			self.accdlg.set_dicts(self.dicts)
		self.accdlg.run()
	
	def on_dialog_response(self, dlg, response):
		if response == Gtk.ResponseType.APPLY:
			dlg.update_datadicts()
			dlg.save_all_settings()
			self.dicts = dlg.datadicts
			self.load_data_from_dicts()
			# Remove all old keys
			for key in self.mail_keys:
				GnomeKeyring.item_delete_sync(key.keyring, key.item_id)
			# Create all new keys
			for i in range(len(self.dicts)):
				self.create_keyring_item(i)
		dlg.destroy()
	
	def load_data_from_dicts(self):
		self.host = []
		self.port = []
		self.login = []
		self.passwd = []
		for dict_ in self.dicts:
			self.host.append(dict_['Host'])
			try:
				self.port.append(int(dict_['Port']))
			except:
				# Port empty or non-integer
				self.port.append(993)
			self.login.append(dict_['Login'])
			self.passwd.append(dict_['Passwd'])
	
	def establish_connection(self, cn):
		try:
			self.mail_client[cn] = imaplib.IMAP4_SSL(self.host[cn], int(self.port[cn]))
		except:
			self.mail_client[cn] = imaplib.IMAP4(self.host[cn], int(self.port[cn]))
	
	def try_establish_connection(self, cn):
		try:
			self.establish_connection(cn)
		except:
			return False
		else:
			print('unity-mail: Connection #{0} established'.format(cn))
			if self.mail_client[cn].login(str(self.login[cn]), str(self.passwd[cn]))[0] == 'OK':
				self.notinit[cn]=False
				return True
			else:
				print('unity-mail: Invalid account data provided for connection #{0}; exiting'.format(cn))
				sys.exit(-1)
	
	def raise_error(self, index, frame):
		raise TimeoutException('Timeout error')
	
	def update_single(self, cn, folder_name=None):
		if self.notinit[cn]:
			if not self.try_establish_connection(cn):
				return
		if folder_name:
			status = self.mail_client[cn].select(mailbox=folder_name, readonly=True)
			if status[0] != 'OK':
				return
		else:
			self.mail_client[cn].select(readonly=True)
		search = self.mail_client[cn].search(None, '(UNSEEN)')
		if search[1][0] == None:
			ui = []
		else:
			ui = search[1][0].split()
		self.unread_count[cn] = len(ui)
		for m in ui:
			typ, msg_data = self.mail_client[cn].fetch(m, '(BODY.PEEK[HEADER])')
			for response_part in msg_data:
				if isinstance(response_part, tuple):
					msg = email.message_from_bytes(response_part[1])
			message_id = msg['Message-Id']
			if message_id == None:
				message_id = m
			existing_message = None
			for message in self.unread_messages:
				if message[0] == cn and message[5] == message_id:
					existing_message = message
			if existing_message == None:
				sender = get_header_wrapper(msg, 'From', decode=True)
				subj = get_header_wrapper(msg, 'Subject', decode=True)
				date = get_header_wrapper(msg, 'Date')
				try:
					tuple_time = email.utils.parsedate_tz(date)
					timestamp = email.utils.mktime_tz(tuple_time)
					if timestamp < time.time():
						struct_time = time.localtime(timestamp)
					else:
						# Message time is larger than the current one
						struct_time = time.localtime()
				except:
					# Failed to get time from message
					struct_time = time.localtime()
				iso8601 = time.strftime("%Y-%m-%dT%H:%M:%S", struct_time)
				while subj.lower().startswith('re:'):
					subj = subj[3:]
				while subj.lower().startswith('fwd:'):
					subj = subj[4:]
				subj = subj.strip()
				if sender.startswith('"'):
					pos = sender[1:].find('"')
					if pos >= 0:
						sender = sender[1:pos+1]+sender[pos+2:]
				ilabel = subj if subj else _('No subject')
				self.unread_messages.append([cn, m, self.msg_indicator(ilabel, iso8601), True, folder_name, message_id])
				if self.enable_notifications:
					self.notifications_queue.append((sender, subj))
				if self.command and not self.first_run:
					try:
						Popen((self.command, sender, ilabel))
					except OSError:
						# File doesn't exist or is not executable
						print('unity-mail: Command cannot be executed!')
			else:
				existing_message[3] = True
		self.mail_client[cn].close()
	
	def check_email(self, cn, email):
		login, host = email.split('@')
		return (self.login[cn]==login and \
		(self.host[cn]==host or self.host[cn]=='imap.'+host))
	
	def get_urlid(self):
		for urlid, url in self.on_click_urls:
			if urlid.startswith('Inbox[') and urlid.endswith(']'):
				if self.check_email(self.cn, urlid[6:-1]):
					return urlid
	
	def msg_indicator(self, title, iso8601):
		indicator = Indicate.Indicator()
		if len(title) > 50:
			title = title[:50] + '...'
		indicator.set_property('sender', title)
		indicator.set_property('time', iso8601)
		urlid = self.get_urlid()
		if urlid:
			indicator.connect('user-display', self.on_mm_item_clicked, self.get_urlid())
		if self.draw_attention:
			allowed = True
			for item in self.black_list:
				if self.check_email(self.cn, item):
					allowed = False
			indicator.set_property_bool('draw-attention', allowed)
		return indicator
	
	def get_folders_list(self, cn):
		result = []
		for folder in self.extra_folders:
			email, folder_name = folder.split(':')
			if self.check_email(self.cn, email):
				result.append(folder_name)
		return result
	
	def update(self):
		self.unread_count = [0]*self.l
		self.notifications_queue = []
		for msg in self.unread_messages:
			msg[3] = False
		for self.cn in range(self.l):
			signal.signal(signal.SIGALRM, self.raise_error)
			folders = self.get_folders_list(self.cn)
			for folder in [None]+folders:
				signal.alarm(4)
				try:
					self.update_single(self.cn, folder_name=folder)
					signal.alarm(0) # Cancels alarm
				except TimeoutException:
					print('unity-mail: Timeout error occured in connection #{0}'.format(self.cn))
					self.notinit[self.cn]=True
				except imaplib.IMAP4.error as e:
					print(e)
					print('unity-mail: IMAP4 error occured in connection #{0}'.format(self.cn))
					self.notinit[self.cn]=True
				except socketerror as e:
					print(e)
					print('unity-mail: Socket error occured in connection #{0}'.format(self.cn))
					self.notinit[self.cn]=True
		for msg in self.unread_messages:
			# Show or hide indicator depending on it's activeness
			if msg[3]:
				msg[2].show()
			else:
				msg[2].hide()
		number_of_mails = 0
		icon = 'unity-mail' if self.display_icons else None
		for i in self.unread_count:
			number_of_mails += int(i)
		basemessage = gettext.ngettext('You have %d unread mail', 'You have %d unread mails', number_of_mails)
		basemessage = basemessage.replace('%d', '{0}')
		if len(self.notifications_queue) > (1 if self.first_run else 2):
			senders = set(get_sender_name(notification[0]) for notification in self.notifications_queue)
			if '' in senders:
				senders.remove('')
			ts = tuple(senders)
			if len(ts) > 2:
				message = fix_format(_('from %(t0)s, %(t1)s and others')).format(t0=ts[0], t1=ts[1])
			elif len(ts) == 2:
				message = fix_format(_('from %(t0)s and %(t1)s')).format(t0=ts[0], t1=ts[1])
			elif len(ts) == 1:
				message = _('from %s').replace('%s', '{0}').format(ts[0])
			else:
				message = ''
			Notify.Notification.new(basemessage.format(number_of_mails), message, icon).show()
		else:
			for notification in self.notifications_queue:
				if notification[0]:
					message = _('New mail from %s').replace('%s', '{0}').format(notification[0])
					Notify.Notification.new(message, notification[1], icon).show()
				else:
					Notify.Notification.new(basemessage.format(1), notification[1], icon).show()
		self.first_run = False
		self.launcher.set_property('count', number_of_mails)
		if self.notinit == [True]*self.l:
			# None of the connections is working
			count_visible = False
		else:
			count_visible = (number_of_mails > 0) or not self.hide_count
		self.launcher.set_property('count_visible', count_visible)
		return True

if __name__ == '__main__':
	UnityMail()
