#!/usr/bin/env python

# The url the program will try to open hoping to be redirected to the portal
URL = 'http://perdu.com/'

import sys
import gobject
import dbus
import dbus.mainloop.glib
import urllib2, urllib
from BeautifulSoup import BeautifulSoup
import re
import ConfigParser
import os
import pynotify

CONNECT_OK = 0
CONNECT_FAIL = 1
CONNECT_ALLREADY_ONLINE = 2
CONNECT_URL_DOESNT_MATCH = 3
CONNECT_INVALID_CERTIFICAT = 4
CONNECT_CERTIFICAT_DOESNT_MATCH = 5
CONNECT_NO_FORM = 6
CONNECT_NO_LINK = 7

def properties_changed_signal_handler(props):
    if not props.has_key('ActiveConnections'):
        return
    for device_path in props['ActiveConnections']:
        device = bus.get_object('org.freedesktop.NetworkManager', device_path)
        device_props = device.GetAll("org.freedesktop.NetworkManager.Connection.Active", dbus_interface="org.freedesktop.DBus.Properties")
        if device_props['Default']:
            return
        ap_path = device_props['SpecificObject']
        if ap_path.startswith('/org/freedesktop/NetworkManager/AccessPoint/'):
            ap = bus.get_object('org.freedesktop.NetworkManager', ap_path)
            ssid = ap.Get("org.freedesktop.NetworkManager.AccessPoint", "Ssid", dbus_interface="org.freedesktop.DBus.Properties")
            ssid = ''.join([chr(c) for c in ssid])
            valid_networks = [i for i in config.NETWORKS if ('ssid' in i and ssid == i['ssid']) or ('ssid_regex' in i and re.match(i['ssid_regex'], ssid))]
            if not valid_networks:
                return
            print valid_networks
            device.connect_to_signal("PropertiesChanged", device_properties_changed_signal_handler(ssid), dbus_interface="org.freedesktop.NetworkManager.Connection.Active")

def device_properties_changed_signal_handler(ssid):
    def handler(props):
        if not props.has_key('State'):
            return
        if props['State'] != 2:
            return
        print valid_networks
        for network in  valid_networks:
            n = pynotify.Notification("Open Wifi Auto Connect", "Trying to log on " + ssid, "dialog-warning")
            n.set_urgency(pynotify.URGENCY_NORMAL)
            n.set_timeout(10)
            #n.add_action("clicked","Button text", callback_function, None)
            n.show()

            status = login(network):
            status_text = {
                CONNECT_OK:  "Successfully logged on " + ssid,
                CONNECT_FAIL: "Failled to log on " + ssid,
                CONNECT_ALLREADY_ONLINE: "You seem to be allready online",
                CONNECT_URL_DOESNT_MATCH: "The login URL doesn't match the one you specify in your config file",
                CONNECT_INVALID_CERTIFICAT: "The login portal SSL certificat is invalid",
                CONNECT_CERTIFICAT_DOESNT_MATCH: "The login portal SSL doesn't match the one you specified in your config file",
                CONNECT_NO_FORM: "There's no form on the login page",
                CONNECT_NO_LINK: "Can't find the logon link on the login portal page",
            }
            n = pynotify.Notification("Open Wifi Auto Connect", status_text[status], "dialog-warning")
            n.set_urgency(pynotify.URGENCY_NORMAL)
            n.set_timeout(10)
            #n.add_action("clicked","Button text", callback_function, None)
            n.show()
    return handler


def login(options):
    # build an http fetcher that support cookies
    opener = urllib2.build_opener( urllib2.HTTPCookieProcessor() )
    urllib2.install_opener(opener)

    # try to open the portal page
    f = opener.open(URL)
    data = f.read()
    redirect_url = f.geturl().split('?')[0]
    f.close()
    if redirect_url == URL:
        # Our request wasn't hijacked by the portal (maybe the wifi network isn't the default connection)
        return CONNECT_ALLREADY_ONLINE
    if re.match(options.get('url_regex', '^$'), redirect_url):
        # the hijack URL doesn't match the config file
        return CONNECT_URL_DOESNT_MATCH

    # parse the portal page
    soup = BeautifulSoup(data)
    postBody = None
    if 'values' in option:
        form = soup.find('form')
        if not form:
            # There's no form on this page
            return CONNECT_NO_FORM
        
        # creating the post values
        login_post = {}
        for input in form.findAll('input'):
            if input.has_key('name'):
                default = ''
                if input.has_key('type') and input['type'] == 'checkbox':
                    default = 'on'
                login_post[input['name']] = input.has_key('value') and input['value'] or default

        login_post.update(options.get('values', {}))
        url = form.get('action', '')

        # GET ou POST ?
        if form.has_key('method') and form['method'].lower() == 'post':
            postBody = urllib.urlencode(login_post)
        else:
            url += '?' + urllib.urlencode(login_post).replace(' ', '+')

    elif 'link_selector' in options:
        soup

    # guessing the post url
    if not url or url.startswith('?'):
        url = redirect_url + url
    elif not url.startswith('/'):
        url = '/'.join(redirect_url.split('/')[:-1]) + '/' + url
    elif url.startswith('http://') or url.startswith('https://'):
        url = url
    else:
        url = '/'.join(redirect_url.split('/')[:3]) + url

    # submit the form
    f = opener.open(url, postBody)
    data = f.read()
    f.close()

    # Test if the login was a success
    f = opener.open(URL)
    data = f.read()
    status = f.geturl() == URL
    f.close()
    return CONNECT_OK if status else CONNECT_FAIL

if __name__ == '__main__':
    config = ConfigParser.RawConfigParser()
    config.read(os.path.expanduser('~/.OpenWifiAutoConnect'))

    pynotify.init( "Open Wifi Auto Connect" )

    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

    bus = dbus.SystemBus()
    nm = bus.get_object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
    nm.connect_to_signal("PropertiesChanged", properties_changed_signal_handler, dbus_interface="org.freedesktop.NetworkManager")

    loop = gobject.MainLoop()
    loop.run()
