#!/usr/bin/python

import os
import sys

PACKAGE="firmware-usb-autoupdate"
LOCALEDIR="/usr/share/locale"
PYTHONDIR="/usr/lib/python2.3/site-packages"

sys.path.insert(0,PYTHONDIR)
sys.path.insert(0,os.path.join(PYTHONDIR, "gtk-2.0"))

import pygtk
pygtk.require('2.0')
import gtk
import gobject
import dbus
import dbus.service
import ConfigParser
import glob
import subprocess
import optparse
import gettext, locale

try:
    from dbus.mainloop.glib import DBusGMainLoop
    DBusGMainLoop(set_as_default=True)
except ImportError:
    # old version of dbus bindings
    import dbus.glib

from firmware_usb_autoupdate import halhelper, util

locale.setlocale(locale.LC_ALL,'')
gettext.bindtextdomain(PACKAGE, os.environ.get("LOCALEDIR", LOCALEDIR))
gettext.textdomain(PACKAGE)
gettext.install(PACKAGE, LOCALEDIR, unicode=1)
_ = gettext.lgettext

APP_NAME = _("BIOS Update")

os.environ["PATH"] = ":".join(os.environ["PATH"].split(":") + ["/sbin", "/usr/sbin"] )
CONFIG = os.environ.get("CONFIG_PATH", "/etc/bios_autoupdate.ini")
TRUE_OPTS = ["1", "yes", "true"]

#DEFBUS=dbus.SystemBus
DEFBUS=dbus.SessionBus
DBUS_INTERFACE='com.dell.libsmbios.firmware_usb_autoupdate'
DBUS_BUS='com.dell.libsmbios.firmware_usb_autoupdate'

class NotifyGui(dbus.service.Object):
    def __init__(self, configFile, object_path, mainloop):
        dbus.service.Object.__init__(self,
            dbus.service.BusName(DBUS_BUS, bus=dbus.SessionBus()), object_path)
        self.mainloop = mainloop
        self.bus = dbus.SystemBus ()
        self.hal_obj = self.bus.get_object ('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
        self.hal = dbus.Interface (self.hal_obj, 'org.freedesktop.Hal.Manager')

        self.initConfig(configFile)

        udi = "/org/freedesktop/Hal/devices/computer"
        deviceObj = self.bus.get_object ('org.freedesktop.Hal', udi)
        self.computerInt = dbus.Interface (deviceObj, 'org.freedesktop.Hal.Device')
        self.computerInt.connect_to_signal("PropertyModified",
                    lambda *args: self.propertyModified(udi, self.computerInt, *args))

        self.initGtk()

        try:
            if self.computerInt.PropertyExists("bios.usb_autoupdate_udi"):
                self.propertyModified(udi, self.computerInt, 1, [("bios.usb_autoupdate_udi", 0, 0),])
        except dbus.DBusException:
            pass

    @dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='v', out_signature='v')
    def ping(self, value):
        print "pinged :)"
        return value

    @dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='', out_signature='')
    def shutdown(self):
        self.mainloop.quit()

    @dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='', out_signature='')
    def activate(self):
        self.window.show()

    def initGtk(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect("delete_event", self.close_application)
        self.window.set_title(APP_NAME)
        self.window.set_border_width(20)

        # tooltips
        self.tt = gtk.Tooltips()

        # main window box
        mainBox = gtk.VBox(False, 0)
        self.window.add(mainBox)
        mainBox.show()

        frameUpdateType = gtk.Frame(_("BIOS update info"))
        mainBox.pack_start(frameUpdateType, True, True, 0)
        frameUpdateType.show()

        infoBox = gtk.VBox(True, 10)
        infoBox.set_border_width(10)
        frameUpdateType.add(infoBox)
        infoBox.show()

        #
        # File Name
        #
        box = gtk.HBox(homogeneous=True, spacing=10)
        infoBox.pack_start(box, False, True, 0)
        box.show()

        label = gtk.Label(_("Filename:"))
        label.set_alignment(1, 0)
        box.pack_start(label, False, True, 0)
        label.show()

        self.filenameLabel = gtk.Label()
        self.filenameLabel.set_alignment(0,0)
        box.pack_end(self.filenameLabel, True, True, 0)
        self.filenameLabel.show()

        #
        # Product Name
        #
        box = gtk.HBox(homogeneous=True, spacing=10)
        infoBox.pack_start(box, True, True, 0)
        box.show()

        label = gtk.Label(_("Product Name:"))
        label.set_alignment(1, 0)
        box.pack_start(label, True, True, 0)
        label.show()

        self.getMachineInfo()
        label = gtk.Label()
        label.set_alignment(0,0)
        box.pack_end(label, True, True, 0)
        label.set_text(self.productName)
        label.show()

        #
        # BIOS Ver
        #
        box = gtk.HBox(homogeneous=True, spacing=10)
        infoBox.pack_start(box, True, True, 0)
        box.show()

        label = gtk.Label(_("Current BIOS Version:"))
        label.set_alignment(1, 0)
        box.pack_start(label, True, True, 0)
        label.show()

        label = gtk.Label()
        box.pack_end(label, True, True, 0)
        label.set_text(self.biosver)
        label.show()

        #
        # HDR Ver
        #
        box = gtk.HBox(homogeneous=True, spacing=10)
        infoBox.pack_start(box, True, True, 0)
        box.show()

        label = gtk.Label(_("Available BIOS Version:"))
        label.set_alignment(1, 0)
        box.pack_start(label, True, True, 0)
        label.show()

        self.hdrverLabel = gtk.Label()
        box.pack_end(self.hdrverLabel, True, True, 0)
        self.hdrverLabel.show()

        self.labelPropertyXref= {
                                    # property name,  default value
                self.filenameLabel: ["bios.usb_autoupdate_hdr", _("No HDR file present")],
                self.hdrverLabel:   ["bios.usb_autoupdate_hdrver", _("No HDR file present")],
                }

        # Messages for buttons and tooltips

        self.stdApplyMsg = _("<b>Click 'Apply' to update to this BIOS.</b>")
        self.stdCompleteMsg = _("<b>The BIOS update was staged. You must reboot the system for the update to complete.</b>")
        self.stdDevRemovedMsg = _("<b>There are no USB devices attached to this system which contain BIOS updates.</b>")
        self.stdUpdateErrorMsg = _("<b>The BIOS update encountered an error.</b>")
        self.stdCancelMsg = _("<b>BIOS update was cancelled.</b>")
        self.stdCancelButtonDisabledTT = _("Cancel currently-staged BIOS Update.\nDisabled because no update is staged.")
        self.stdCancelButtonTT = _("Cancel currently-staged BIOS Update.")
        self.stdApplyButtonDisabledNUATT = _("Apply the update displayed.\nDisabled because no update is available.")
        self.stdApplyButtonDisabledAATT = _("Apply the update displayed.\nDisabled because update is already applied.")
        self.stdApplyButtonTT = _("Apply the update displayed.")

        self.messageLabel = gtk.Label(self.stdDevRemovedMsg)
        self.messageLabel.set_justify(gtk.JUSTIFY_CENTER)
        self.messageLabel.set_line_wrap(True)
        self.messageLabel.set_use_markup(True)
        mainBox.pack_start(self.messageLabel, False, True, 0)
        self.messageLabel.show()

        buttonBox = gtk.HButtonBox()
        buttonBox.set_border_width(10)
        mainBox.pack_start(buttonBox, False, True, 0)
        buttonBox.show()

        button = gtk.Button(None, stock=gtk.STOCK_QUIT)
        self.tt.set_tip(button, _("Exit Application and dont notify for future updates."))
        button.connect_object("clicked", self.close_application, self.window, "quit")
        buttonBox.pack_start(button, True, True, 0)
        button.show()

        button = gtk.Button(None, stock=gtk.STOCK_CLOSE)
        self.tt.set_tip(button, _("Close window but still notify for future updates."))
        button.connect_object("clicked", self.close_application, self.window, "close")
        buttonBox.pack_start(button, True, True, 0)
        button.show()

        self.cancelButton = gtk.Button(None, stock=gtk.STOCK_CANCEL)
        self.tt.set_tip(self.cancelButton, self.stdCancelButtonDisabledTT)
        self.cancelButton.connect_object("clicked", self.doCancel, self.window, None)
        buttonBox.pack_start(self.cancelButton, True, True, 0)
        self.cancelButton.set_sensitive(0)
        self.cancelButton.show()

        self.applyButton = gtk.Button(None, stock=gtk.STOCK_APPLY)
        self.tt.set_tip(self.applyButton, self.stdApplyButtonDisabledNUATT)
        self.applyButton.connect_object("clicked", self.doUpdate, self.window, None)
        buttonBox.pack_start(self.applyButton, True, True, 0)
        self.applyButton.set_flags(gtk.CAN_DEFAULT)
        self.applyButton.grab_default()
        self.applyButton.set_sensitive(0)
        self.applyButton.show()

        self.resetLabels(None)

    def getMachineInfo(self):
        self.biosver = _("unknown")
        self.productName = _("unknown")
        
        try:
            self.productName = self.computerInt.GetProperty("smbios.system.product")
        except Exception:
            pass

        try:
            self.biosver = self.computerInt.GetProperty("smbios.bios.version")
        except Exception:
            pass

    def initConfig(self, configFile):
        # set config defaults (overridden by bios_autoupdate.ini)
        self.cfg = util.initDefaultConfig()
        self.configFile=configFile
        self.rereadConfig()

    def doCancel(self, widget, event, data=None):
        ret = 0
        try:
            monObj = self.bus.get_object (DBUS_BUS, '/singleton')
            monInt = dbus.Interface (monObj, DBUS_INTERFACE)
            ret = monInt.CancelBiosUpdate()
        except dbus.DBusException:
            pass
        if ret:
            self.messageLabel.set_markup(self.stdCancelMsg)

    def doUpdate(self, widget, event, data=None):
        ret = 0
        try:
            monObj = self.bus.get_object (DBUS_BUS, '/singleton')
            monInt = dbus.Interface (monObj, DBUS_INTERFACE)
            ret = monInt.UpdateBiosFromUSBDevice()
        except dbus.DBusException:
            pass
        if ret:
            self.messageLabel.set_markup(self.stdCompleteMsg)
        else:
            self.messageLabel.set_markup(self.stdUpdateErrorMsg)

    def close_application(self, widget, event, data=None):
        if event == "quit":
            self.mainloop.quit()
        else:
            self.window.hide()
        return False

    def rereadConfig(self):
        self.cfg.read( self.configFile )

    def resetLabels(self, deviceInt):
        for label, prop in self.labelPropertyXref.items():
            try:
                prop[2] = prop[1]
            except IndexError:
                prop.append(prop[1])
            if deviceInt:
                prop[2] = deviceInt.GetProperty(prop[0])
            label.set_text(prop[2])

        self.cancelButton.set_sensitive(0)
        self.applyButton.set_sensitive(0)
        self.tt.set_tip(self.cancelButton, self.stdCancelButtonDisabledTT)
        self.tt.set_tip(self.applyButton, self.stdApplyButtonDisabledNUATT)

        if self.computerInt.PropertyExists("bios.usb_autoupdate_complete") and (self.computerInt.GetProperty("bios.usb_autoupdate_complete") == 1):
            self.messageLabel.set_markup(self.stdCompleteMsg)
            self.cancelButton.set_sensitive(1)
            self.tt.set_tip(self.cancelButton, self.stdCancelButtonTT)
            self.tt.set_tip(self.applyButton, self.stdApplyButtonDisabledAATT)
        elif deviceInt:
            self.messageLabel.set_markup(self.stdApplyMsg)
            self.applyButton.set_sensitive(1)
            self.tt.set_tip(self.applyButton, self.stdApplyButtonTT)
        else:
            self.messageLabel.set_markup(self.stdDevRemovedMsg)


    def propertyModified(self, udi, deviceInt, num, arr):
        print "propertyModified: %s" % (udi)
        for propName, added, removed in arr:
            print "\tpropName: %s" % propName
            if propName in ["bios.usb_autoupdate_udi", "bios.usb_autoupdate_complete"]:
                updateDevInt=None
                try:
                    udi = deviceInt.GetProperty("bios.usb_autoupdate_udi")
                    updateDevObj = self.bus.get_object ('org.freedesktop.Hal', udi)
                    updateDevInt = dbus.Interface (updateDevObj, 'org.freedesktop.Hal.Device')
                except dbus.DBusException, e:
                    print e

                self.resetLabels(updateDevInt)
                self.rereadConfig()
                if updateDevInt and self.cfg.get("usb_auto_update", "update_type").lower() in ["update-ask", "update-auto"]:
                    self.activate()

def shutdown():
    bus = dbus.SessionBus ()
    checkObj = bus.get_object (DBUS_BUS, '/singleton')
    checkInt = dbus.Interface (checkObj, DBUS_INTERFACE)
    checkInt.shutdown()

def activate():
    bus = dbus.SessionBus ()
    checkObj = bus.get_object (DBUS_BUS, '/singleton')
    checkInt = dbus.Interface (checkObj, DBUS_INTERFACE)
    checkInt.activate()

def main():
    parser = optparse.OptionParser(_("usage: %prog [options]"))
    parser.add_option("-f", "--foreground", dest="daemonize",
                       default=True, action="store_false",
                       help=_("Do *not* put program into the background."))
    parser.add_option("-b", "--background", dest="daemonize",
                       action="store_true",
                       help=_("Put program into the background."))
    parser.add_option("-c", "--config", dest="config_path",
                       type="string", action="store", default=CONFIG,
                       help=_("Put program into the background."))
    parser.add_option("-s", "--shutdown", dest="shutdown",
                       action="store_true", default=False,
                       help=_("Shutdown background process."))
    parser.add_option("-g", "--showgui", dest="show_gui",
                       action="store_true", default=True,
                       help=_("Show GUI."))
    parser.add_option("-G", "--dontshowgui", dest="show_gui",
                       action="store_false",
                       help=_("Do not show GUI."))

    (options, args) = parser.parse_args()
    if len(args) != 0:
         parser.error(_("Error, program does not take non-option arguments."))

    if options.shutdown:
        shutdown()
        sys.exit(1)

    sessionLock = util.lockSession(os.path.expanduser("~/.sessionlock"))

    if not sessionLock: # somebody else already running
        print _("Notifier is already running, activating existing instance.")
        activate()
        sys.exit(1)

    loop = gobject.MainLoop()
    f = NotifyGui(options.config_path, "/singleton", loop)

    if options.show_gui:
        f.window.show()

    if options.daemonize:
        util.daemonize()

    loop.run()

if __name__ == "__main__":
    main()
