#!/usr/bin/env python
# evolution-smtp.py
# http://thpinfo.com/2008/evolution-smtp/
#
# Simple script that updates the Evolution SMTP server
# setting, depending on which wireless SSID we are connected to.
#
# Copyright (c) Thomas Perl.
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name of Thomas Perl nor the names of contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#


# Specify a dictionary of your SSID -> SMTP URL mappings
MY_NETWORKS = {
        'PERLNET': 'smtp://@email.aon.at/;use_ssl=never',
        'FredFeuerstein': 'smtp://@smtp.sil.at/;use_ssl=never',
# You can specify one key as None here and set a default 
# SMTP server that should be used if the network we are
# currently connected to is not a wireless network (i.e. wired)
# e.g.  None: 'smtp://@smtp.example.org/;use_ssl=never',
}

NOTIFY_ALREADY_SET = True
RUN_AS_DAEMON = True

import sys
import gobject
import gconf
from xml.dom import minidom
import dbus
import dbus.mainloop.glib
import pynotify

class NetworkManagerSSID(object):
    """
    This class uses D-Bus to retrieve the current active
    SSID from NetworkManager. This can be extended to do 
    more things, like getting the currently-assinged IP, etc..
    """

    SERVICE = 'org.freedesktop.NetworkManager'
    DEVINTERFACE = 'org.freedesktop.NetworkManager.Devices'
    PATH = '/org/freedesktop/NetworkManager'

    # Types of network devices
    (TYPE_UNKNOWN, TYPE_WIRED, TYPE_WIRELESS) = range(3)

    # Properties for a NM device
    (OP, IFACE, TYPE, UDI, ACTIVE, ACT_STAGE, IP4_ADDRESS,
     HW_ADDR_BUF_PTR, MODE, STRENGH, LINK_ACTIVE,
     DRIVER_SUPPORT_LEVEL, ACTIVE_NETWORK_PATH,
     NETWORKS, NUM_NETWORKS) = range(15)
    
    def __init__(self, on_change_callback=None):
        self.on_change_callback = on_change_callback

        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        self.__bus = dbus.Bus.get_system()

        self.__bus.add_signal_receiver(self.device_now_active, 'DeviceNowActive', self.SERVICE, self.SERVICE, self.PATH)

    def device_now_active(self, interface_path, wireless_essid=None):
        if self.on_change_callback is not None:
            self.on_change_callback(self, interface_path == 'first-run')

    def get_active_ssid(self):
        """
        Returns the SSID of the current active network,
        or None if there is no current active network.
        """
        nm = self.get_object()
        
        for device_path in nm.getDevices():
            device = self.get_object(device_path)
            props = device.getProperties(dbus_interface=self.DEVINTERFACE)
            if props[self.ACTIVE] and props[self.TYPE] == self.TYPE_WIRELESS:
                active_network_path = device.getActiveNetwork(dbus_interface=self.DEVINTERFACE)
                active_network = self.get_object(active_network_path)
                return active_network.getName()

        return None


    def get_object(self, path=None, service=None):
        """
        Returns a D-Bus proxy object. If path or service
        is None (the default), PATH and SERVICE will be
        used from the class variables.
        """
        if path is None:
            path = self.PATH
        if service is None:
            service = self.SERVICE
        
        return self.__bus.get_object(service, path)


class EvolutionSMTP(object):
    """
    This class uses gconf to read the list of Evolution
    accounts, parses it into a DOM object using minidom and
    provides functions to replace the SMTP server URL in a
    DOM object with a custom string. After that, saving the
    modified DOM objects is provided as a function, too.
    """

    DIR = '/apps/evolution/mail'
    KEY = '/apps/evolution/mail/accounts'

    def __init__(self):
        self.__client = gconf.client_get_default()
        # The following two lines are currently unused and can
        # be used for dynamically watching the accounts change
        self.__client.add_dir(self.DIR, gconf.CLIENT_PRELOAD_NONE)
        self.__client.notify_add(self.KEY, self.key_changed_callback)
        self.__accounts = self.__load_accounts()
    
    def get_accounts(self):
        """
        Returns a list of DOM objects that each represent
        an E-Mail account in the user's Evolution setup.
        """
        result = []
        for account in self.__accounts:
            result.append(minidom.parseString(account))

        return result
    
    def save_accounts(self, accounts):
        """
        Writes a list of DOM objects (most likely gotten
        via the get_accounts() function) back to gconf.
        """
        self.__accounts = []
        for account in accounts:
            self.__accounts.append(account.toxml())
        self.__save_accounts()
    
    def get_smtp_url_from_dom(self, dom):
        """
        Gets the SMTP server URL as a string from a DOM object.
        """
        for transport in dom.getElementsByTagName('transport'):
            for url in transport.getElementsByTagName('url'):
                return ''.join((n.data for n in url.childNodes))

    def set_smtp_url_in_dom(self, dom, smtp_url):
        """
        Modifies a DOM object with the new smtp_url, so that it
        has its SMTP URL set to the value specified. The old
        value is removed.
        """
        for transport in dom.getElementsByTagName('transport'):
            for url in transport.getElementsByTagName('url'):
                while url.lastChild is not None:
                    url.removeChild(url.lastChild)
                text_node = minidom.Text()
                text_node.data = smtp_url
                url.appendChild(text_node)

    def key_changed_callback(self, client, cnxn_id, entry, data):
        """
        Currently not used (see constructor). This would be
        called if we were running in a gobject main loop and the
        accounts were changed in evolution. We update the internal
        cache of the accounts here.
        """
        if entry.key == self.KEY:
            self.__accounts = self.__load_accounts()

    def __load_accounts(self):
        """
        (internal use only) Load accounts list from gconf
        """
        return self.__client.get_list(self.KEY, gconf.VALUE_STRING)
    
    def __save_accounts(self):
        """
        (internal use only) Save account list to gconf
        """
        self.__client.set_list(self.KEY, gconf.VALUE_STRING, self.__accounts)

def notify(s):
    pynotify.init(sys.argv[0])
    pynotify.Notification('Evolution SMTP', s, 'server').show()

if '--help' in sys.argv or '-h' in sys.argv:
    print 'Usage: python %s [--identify|-i] [--help|-h]' % sys.argv[0]
    print ''
    print ' --identify|-i    Show current SMTP servers and SSID'
    print ' --help|-h        Show this help screen'
    print ''
elif '--identify' in sys.argv or '-i' in sys.argv:
    ssid = NetworkManagerSSID().get_active_ssid()
    smtps = []
    evo = EvolutionSMTP()
    for account in evo.get_accounts():
        smtps.append(evo.get_smtp_url_from_dom(account))
    print 'Current SSID: %s' % ssid
    if len(smtps) == 0:
        print 'No SMTP servers found(!?!?)!'
    else:
        print 'Current SMTP servers:'
        for smtp in smtps:
            print '   %s' % smtp
        print ''
 
        print 'You can add the following code to this script:'
        print ''
        print 'MY_NETWORKS = {'
        print '# ...'
        print "        '%s': '%s'," % (ssid, smtps[0])
        print '}'
else:
    def network_changed(nm, first_run):
        """
        This is the callback that gets called when a 
        networking device gets active.
        """
        ssid = nm.get_active_ssid()
        # If we are connected to a wireless network and we know the SMTP server for it
        if ssid in MY_NETWORKS:
            evo = EvolutionSMTP()
            accounts = evo.get_accounts()
            # If necessary, update the SMTP server entry for every account
            for account in accounts:
                old_url = evo.get_smtp_url_from_dom(account)
                new_url = MY_NETWORKS[ssid]
                if old_url != new_url:
                    evo.set_smtp_url_in_dom(account, new_url)
                    notify('Set Evolution SMTP server for the wireless network "%s".' % ssid)
                elif NOTIFY_ALREADY_SET and not first_run:
                    notify('Now connected to "%s". Evolution SMTP server already set.' % ssid)
        
            evo.save_accounts(accounts)
        elif ssid not in MY_NETWORKS:
            if ssid is not None:
                notify('You are now connected to "%s". I do not know the SMTP server for this network.' % ssid)
            else:
                notify('It looks like you are not connected to a wireless network, and I do not have a default entry.')
        elif ssid is None:
            notify('I think you are not connected to a wireless network.')
        else:
            notify('There has been an error of some strange kind. Wicked!')

    # Get the currently active SSID (or None if we are not connected to wireless)
    nm = NetworkManagerSSID(network_changed)
    # First time device activation
    nm.device_now_active('first-run')
    
    if RUN_AS_DAEMON:
        print 'Running as a daemon...'
        loop = gobject.MainLoop()
        loop.run()

