#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
User Daemon to suggest packages to install when new hardware
is inserted into the machine.
"""
# Copyright (C) 2013 Petter Reinholdtsen <pere@hungry.com>
# AptDaemon gtk client code based on gtk-demo, copyright (C) 2008-2009
# Sebastian Heinlein <sevel@glatzor.de>
#
# Licensed under the GNU General Public License Version 2
#
# 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; either version 2 of the License, or
# (at your option) any later version.
#
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

__author__ = "Petter Reinholdtsen <pere@hungry.com>"

import string
#import pygtk
import subprocess
import glob
import fnmatch

import gi
from gi.repository import GLib
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
gi.require_version('GUdev', '1.0')
from gi.repository import GUdev
gi.require_version('Notify', '0.7')
from gi.repository import Notify

import isenkram.lookup

import aptdaemon.client
from aptdaemon.gtk3widgets import AptErrorDialog, \
                                 AptConfirmDialog, \
                                 AptProgressDialog
import aptdaemon.errors

class AptDaemonGUIClient(object):

    """Provides a graphical interface to aptdaemon."""

    def _run_transaction(self, transaction):
        dia = AptProgressDialog(transaction, parent=self.win)
        dia.run(close_on_finished=True, show_error=True,
                reply_handler=lambda: True,
                error_handler=self._on_error)

    def _simulate_trans(self, trans):
        trans.simulate(reply_handler=lambda: self._confirm_deps(trans),
                       error_handler=self._on_error)

    def _confirm_deps(self, trans):
        if [pkgs for pkgs in trans.dependencies if pkgs]:
            dia = AptConfirmDialog(trans, parent=self.win)
            res = dia.run()
            dia.hide()
            if res != Gtk.ResponseType.OK:
                return
        self._run_transaction(trans)

    def _on_error(self, error):
        try:
            raise error
        except aptdaemon.errors.NotAuthorizedError:
            # Silently ignore auth failures
            return
        except aptdaemon.errors.TransactionFailed as error:
            pass
        except Exception as error:
            error = aptdaemon.errors.TransactionFailed(aptdaemon.enums.ERROR_UNKNOWN,
                                                       str(error))
        dia = AptErrorDialog(error)
        dia.run()
        dia.hide()

    def request_installation(self, *args):
        self.ac.install_packages([self.package],
                                 reply_handler=self._simulate_trans,
                                 error_handler=self._on_error)

    def __init__(self, package):
        self.win = None
        self.package = package
        self.loop = GLib.MainLoop()
        self.ac = aptdaemon.client.AptClient()

    def run(self):
        self.loop.run()


# Keep refs needed for callback to work
n = None
npkgs = None

def notify_pleaseinstall(notification=None, action=None, data=None):
    pkgs = data
    pkgsstr = string.join(pkgs, " ")
#    print pkgs
    print "info: button clicked, installing %s" % pkgsstr
    demo = AptDaemonGUIClient(pkgs[0])
    demo.request_installation()

def notify(bus, vendor, device, pkgs):
    pkgstr = string.join(pkgs, " ")
    text = "New %s device [%04x:%04x] supported by package(s) %s." \
        % (bus, vendor, device, pkgstr)
    title = "New %s device" % bus

    print "info: " + text

    # Initializite pynotify
    if not Notify.init("isenkramd"):
        return False
    global n
    global npkgs
    npkgs = pkgs
    n = Notify.Notification(summary=title, body=text)
    n.set_timeout(10000)
    n.add_action("clicked",
                 "Install program(s)",
                 notify_pleaseinstall, npkgs)
    n.show()
    return True

def is_pkg_installed(packagename):
    cmd = ["dpkg", "-l", packagename]
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    retval = False
    while True:
        retcode = p.poll()
        line = p.stdout.readline()
        if 0 == line.find("ii "):
            retval = True
        if(retcode is not None):
            break
    return retval

def devid2modalias(bus, vendor, device):
    target = "%s:v%04xp%04xd*" % (bus, vendor, device)
    modalias = None
    for filename in glob.iglob("/sys/bus/%s/devices/*/modalias" % bus):
        f = open(filename)
        line = f.readline().strip()
#        print line, target
        if fnmatch.fnmatch(line, target):
            modalias = line
#            print filename
        f.close()
    return modalias

def get_pkg_suggestions_aptmodaliases(modalias):
    print "info: checking apt modaliases info"
    return isenkram.lookup.pkgs_handling_apt_modaliases([modalias])

def get_pkg_suggestions_mymodaliases(modalias):
    print "info: checking my modaliases file (from svn)"
    return isenkram.lookup.pkgs_handling_extra_modaliases([modalias])

def get_pkg_suggestions(modalias):
    pkgs = []
#    discoverpkgs = get_pkg_suggestions_discover(bus, vendor, device)
#    pkgs.extend(discoverpkgs)
    aptpkgs = get_pkg_suggestions_aptmodaliases(modalias)
    pkgs.extend(aptpkgs)
    mypkgs = get_pkg_suggestions_mymodaliases(modalias)
    pkgs.extend(mypkgs)
    return pkgs

def uevent_callback(client, action, device, user_data):
    modalias = device.get_property("MODALIAS")
    # Map loaded kernel modules to lkmodule:modulename "modalias"
    if ("add" == action and "module" == device.get_subsystem()):
        modalias = "lkmodule:%s" % (device.get_property("DEVPATH").split("/"))[2]
    if ("add" == action and modalias is not None):
        device_vendor = device.get_property("ID_VENDOR_ENC")
        device_model = device.get_property("ID_MODEL_ENC")
        print "uevent %s %s %s" % (device_vendor, device_model, modalias)
        bus = device.get_subsystem()
        if "usb" == bus:
            print "info: discovered USB device %s %s" % (device_vendor,
                                                         device_model)
            pkgs = get_pkg_suggestions(modalias)
#                print "Suggestions: ", pkgs
            newpkg = []
            alreadyinstalled = []
            for pkg in pkgs:
                if not is_pkg_installed(pkg):
                    newpkg.append(pkg)
                else:
                    alreadyinstalled.append(pkg)
            print "info: not proposing already installed package(s) %s" % \
                string.join(alreadyinstalled, ', ')
            if 0 < len(newpkg):
                vendorid, deviceid, bcdevice = \
                    device.get_property("PRODUCT").split("/")
                notify(bus, int(vendorid, 16), int(deviceid, 16), newpkg)

def main():
    client = GUdev.Client(subsystems=[])
    client.connect("uevent", uevent_callback, None)

    loop = GLib.MainLoop()
    print "info: ready to accept hardware events"
    loop.run()

if __name__ == '__main__':
    main()
