#!/usr/bin/python

# ubuntuone-login - Client side log-in utility for Ubuntu One
#
# Author: Rodney Dawes <rodney.dawes@canonical.com>
#
# Copyright 2009 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.

import pygtk
pygtk.require('2.0')
import gtk
import pango
import sys
import gettext
from ubuntuone import clientdefs

import dbus.service

from dbus.mainloop.glib import DBusGMainLoop
from ubuntuone.oauthdesktop.main import Login

from ubuntuone.oauthdesktop.logger import setupLogging
logger = setupLogging("ubuntuone-login")

DBusGMainLoop(set_as_default=True)

_ = gettext.gettext
ngettext = gettext.ngettext

DBUS_IFACE_AUTH_NAME = "com.ubuntuone.Authentication"

OAUTH_REALM = "https://ubuntuone.com"
OAUTH_CONSUMER = "ubuntuone"

NOTIFY_ICON_SIZE = 48

# Why thank you GTK+ for enforcing style-set and breaking API
RCSTYLE = """
style 'dialogs' {
  GtkDialog::action-area-border = 12
  GtkDialog::button-spacing = 6
  GtkDialog::content-area-border = 0
}
widget_class '*Dialog*' style 'dialogs'
"""


class LoginMain(object):
    """Main login manager process class."""

    def __init__(self, *args, **kw):
        """Initializes the child threads and dbus monitor."""
        gtk.rc_parse_string(RCSTYLE)

        logger.info(_("Starting Ubuntu One login manager version %s") %
                    clientdefs.VERSION)

        self.__bus = dbus.SessionBus()

    def _connect_dbus_signals(self):
        """Set up some signal handlers for DBus signals."""
        self.__bus.add_signal_receiver(
              handler_function=self.new_credentials,
              signal_name="NewCredentials",
              dbus_interface=DBUS_IFACE_AUTH_NAME)
        self.__bus.add_signal_receiver(
              handler_function=self.auth_denied,
              signal_name="AuthorizationDenied",
              dbus_interface=DBUS_IFACE_AUTH_NAME)
        self.__bus.add_signal_receiver(
              handler_function=self.got_oauth_error,
              signal_name="OAuthError",
              dbus_interface=DBUS_IFACE_AUTH_NAME)


    def new_credentials(self, realm=None, consumer_key=None, sender=None):
        """Signal callback for when we get new credentials."""
        self.set_up_desktopcouch_pairing(consumer_key)

        def got_port(*args, **kwargs):
            # Discard the value.  We don't really care about the port, here.
            pass

        def got_error(*args, **kwargs):
            logger.warn("On trying to start desktopcouch-service via DBus, "
                    "we got an error.  %s %s" % (args, kwargs,))

        # We have auth, so start desktopcouch service by asking for its port.
        dc_serv_proxy = self.__bus.get_object("org.desktopcouch.CouchDB", "/")
        dc_serv_proxy.getPort(reply_handler=got_port, error_handler=got_error)

    def auth_denied(self):
        """Signal callback for when auth was denied by user."""
        logger.info(_("Access to Ubuntu One was denied."))

        from twisted.internet import reactor
        reactor.stop()

    def got_oauth_error(self, message=None):
        """Signal callback for when an OAuth error occured."""
        def dialog_response(dialog, response):
            """Handle the dialog closing."""
            dialog.destroy()

        if message:
            logger.error(message)
            dialog = gtk.Dialog(title=_("Ubuntu One: Error"),
                                flags=gtk.DIALOG_NO_SEPARATOR,
                                buttons=(gtk.STOCK_CLOSE,
                                         gtk.RESPONSE_CLOSE))
            dialog.set_default_response(gtk.RESPONSE_CLOSE)
            dialog.set_icon_name("ubuntuone-client")

            area = dialog.get_content_area()

            hbox = gtk.HBox(spacing=12)
            hbox.set_border_width(12)
            area.pack_start(hbox)
            hbox.show()

            image = gtk.Image()
            image.set_from_icon_name("dialog-error", gtk.ICON_SIZE_DIALOG)
            image.set_alignment(0.5, 0.0)
            image.show()
            hbox.pack_start(image, False, False)

            vbox = gtk.VBox(spacing=12)
            vbox.show()
            hbox.pack_start(vbox)

            label = gtk.Label("<b>%s</b>" % _("Authorization Error"))
            label.set_use_markup(True)
            label.set_alignment(0.0, 0.5)
            label.show()
            vbox.pack_start(label, False, False)

            label = gtk.Label(message)
            label.set_line_wrap(True)
            label.set_max_width_chars(64)
            label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
            label.set_alignment(0.0, 0.0)
            label.show()
            vbox.pack_start(label, True, True)

            dialog.connect('close', dialog_response, gtk.RESPONSE_CLOSE)
            dialog.connect('response', dialog_response)

            dialog.show()
        else:
            logger.error(_("Got an OAuth error with no message."))

    def set_up_desktopcouch_pairing(self, consumer_key):
        """Add a pairing record between desktopcouch and Ubuntu One"""
        try:
            from desktopcouch.pair.couchdb_pairing.couchdb_io import \
                 put_static_paired_service
            from desktopcouch.records.server import CouchDatabase
        except ImportError:
            # desktopcouch is not installed
            logger.debug(_("Not adding desktopcouch pairing since"
                " desktopcouch is not installed"))
            return
        # Check whether there is already a record of the Ubuntu One service
        db = CouchDatabase("management", create=True)
        if not db.view_exists("ubuntu_one_pair_record","ubuntu_one_pair_record"):
            map_js = """function(doc) {
                if (doc.service_name == "ubuntuone") {
                    if (doc.application_annotations &&
                        doc.application_annotations["Ubuntu One"] &&
                        doc.application_annotations["Ubuntu One"]["private_application_annotations"] &&
                        doc.application_annotations["Ubuntu One"]["private_application_annotations"]["deleted"]) {
                        emit(doc._id, 1);
                    } else {
                        emit(doc._id, 0)
                    }
                }
            }"""
            db.add_view("ubuntu_one_pair_record", map_js, None,
                "ubuntu_one_pair_record")
        results = db.execute_view("ubuntu_one_pair_record",
            "ubuntu_one_pair_record")
        found = False
        # Results should contain either one row or no rows
        # If there is one row, its value will be 0, meaning that there is
        #   already an Ubuntu One pairing record, or 1, meaning that there
        #   was an Ubuntu One pairing record but it has since been unpaired
        # Only create a new record if there is not one already. Specifically,
        #   do not add the record if there is a deleted one, as this means
        #   that the user explicitly unpaired it!
        for row in results:
            found = True
            if row.value == 1:
                logger.debug(_("Not adding desktopcouch pairing since"
                " the user has explicitly unpaired with Ubuntu One"))
            else:
                logger.debug(_("Not adding desktopcouch pairing since"
                " we are already paired"))
        if not found:
            put_static_paired_service(None, "ubuntuone")
            logger.debug(_("Pairing desktopcouch with Ubuntu One"))

    def main(self):
        """Starts the gtk main loop."""
        self._connect_dbus_signals()

        from twisted.internet import reactor
        reactor.run()


if __name__ == "__main__":
    gettext.bindtextdomain(clientdefs.GETTEXT_PACKAGE, clientdefs.LOCALEDIR)
    gettext.textdomain(clientdefs.GETTEXT_PACKAGE)

    # Register DBus service for making sure we run only one instance
    bus = dbus.SessionBus()
    if bus.request_name(DBUS_IFACE_AUTH_NAME, dbus.bus.NAME_FLAG_DO_NOT_QUEUE) == dbus.bus.REQUEST_NAME_REPLY_EXISTS:
        print _("Ubuntu One login manager already running, quitting")
        sys.exit(0)

    from twisted.internet import gtk2reactor
    gtk2reactor.install()

    login = Login(dbus.service.BusName(DBUS_IFACE_AUTH_NAME,
                                       bus=dbus.SessionBus()))
    manager = LoginMain()
    manager.main()
