#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 Canonical, Ltd.
#
# Authors:
#  Olivier Tilloy <olivier.tilloy@canonical.com>
#
# 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; version 3.
#
# 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, see <http://www.gnu.org/licenses/>.

"""
This script sends a message over a local socket to the Android service with the
D-Bus session bus address as a parameter and waits for a reply from the service.

It is meant to be executed at session startup, or whenever the connection of the
Android service to the bus drops.
"""

import base64
import os
import socket
import sys

import glib


class StartServiceRequest(object):

    SOCKET_NAME = '/com/canonical/android/service'
    REQUEST_HEADER = 'StartServiceRequest'
    RESPONSE_HEADER = 'StartServiceResponse'
    SEPARATOR = ';'

    def __init__(self, address, callback, errback):
        self._data = self._make_request(address)
        self.callback = callback
        self.errback = errback

    def _make_request(self, address):
        data = (self.REQUEST_HEADER, base64.b64encode(address))
        return self.SEPARATOR.join(data)

    def send(self):
        try:
            client = socket.socket(socket.AF_UNIX)
            client.connect('\x00' + self.SOCKET_NAME)
        except Exception as error:
            self.errback(error)
        else:
            self._sent = 0
            conditions = glib.IO_OUT | glib.IO_ERR | glib.IO_HUP
            glib.io_add_watch(client, conditions, self._send_data)

    def _send_data(self, client, condition):
        if condition in (glib.IO_ERR, glib.IO_HUP):
            self._cleanup(client)
            self.errback(socket.error('I/O error while sending the request'))
            return False
        try:
            sent = client.send(self._data[self._sent:self._sent+4096])
        except Exception as error:
            self._cleanup(client)
            self.errback(error)
            return False
        else:
            if sent > 0:
                self._sent += sent
                return True
            else:
                client.shutdown(socket.SHUT_WR)
                self._response = ''
                conditions = glib.IO_IN | glib.IO_PRI | glib.IO_ERR | glib.IO_HUP
                glib.io_add_watch(client, conditions, self._receive_data)
                return False

    def _receive_data(self, client, condition):
        if condition in (glib.IO_ERR, glib.IO_HUP):
            self._cleanup(client)
            self.errback(socket.error('I/O error while receiving a response'))
            return False
        try:
            data = client.recv(4096)
        except Exception as error:
            self._cleanup(client)
            self.errback(error)
            return False
        else:
            if len(data) > 0:
                self._response += data
                return True
            else:
                try:
                    client.shutdown(socket.SHUT_RD)
                    client.close()
                except Exception as error:
                    self.errback(error)
                else:
                    self._handle_response(self._response)
                return False

    def _handle_response(self, response):
        data = response.split(self.SEPARATOR)
        if len(data) != 3:
            self.errback('Malformed response: %s' % response)
            return
        header = data[0]
        if header != self.RESPONSE_HEADER:
            self.errback('Invalid response header: %s' % header)
            return
        status = data[1]
        message = base64.b64decode(data[2])
        if status == 'OK':
            self.callback(message)
        elif status == 'ERROR':
            self.errback(message)
        else:
            self.errback('Invalid response status: %s' % status)

    def _cleanup(self, client):
        try:
            client.shutdown(socket.SHUT_RDWR)
            client.close()
        except:
            pass


def callback(response):
    print 'SUCCESS:', response
    sys.exit(0)


def errback(error):
    print >> sys.stderr, 'ERROR:', error
    sys.exit(3)


if __name__ == '__main__':
    address = os.environ.get('DBUS_SESSION_BUS_ADDRESS')

    if address is None:
        print >> sys.stderr, 'Failed to retrieve the address of the session bus, aborting.'
        sys.exit(1)

    if address.startswith('tcp:'):
        print >> sys.stderr, 'The session bus runs over a TCP/IP socket, no need to activate the service.'
        sys.exit(2)

    request = StartServiceRequest(address, callback, errback)
    request.send()

    glib.MainLoop().run()

