#!/usr/bin/python
# -*- coding: UTF-8 -*-
# -*- python -*-
# Copyright (C) 2005 by Sebastien Estienne
#
# This file may be distributed and/or modified under the terms of
# the GNU General Public License version 2 as published by
# the Free Software Foundation.
# This file is distributed without any warranty; without even the implied
# warranty of merchantability or fitness for a particular purpose.
# See "COPYING" in the source distribution for more information.
#
# $id$
#
# todo
# * dict([el.split('=',1) for el in l ])

import os
import subprocess
import sys
import pygtk

sys.path.append("/usr/share/service-discovery-applet/actions/")
import services

pygtk.require('2.0')

def error_msg(msg):
    d = gtk.MessageDialog(parent=None, flags=gtk.DIALOG_MODAL,
                          type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK)
    d.set_markup(msg)
    d.show_all()
    d.run()
    d.destroy()
    
try:
    import gettext
    gettext.bindtextdomain("service-discovery-applet", "/usr/share/locale")
    gettext.textdomain("service-discovery-applet")
    _ = gettext.gettext
    import gobject
    import avahi
    import dbus
    import gtk
    import gnomeapplet
    import gnome.ui
    import gconf
    import avahi.ServiceTypeDatabase
except ImportError, e:
    error_msg(_("A required python module is missing!\n%s") % (e))
    sys.exit()

try:
    import dbus.glib
except ImportError, e:
    pass

#from gettext import gettext as _

# default browsing value
first_run_services = {
    "_http._tcp" : True, 
    "_https._tcp" : False, 
    "_ssh._tcp" : True, 
    "_sftp-ssh._tcp" : False, 
    "_ftp._tcp" : True,
    }


class ServiceDiscoveryApplet(gnomeapplet.Applet):
    def __init__(self, applet, iid):
	self.__gobject_init__()
	self.applet = applet

	self.service_browsers = {}
	self.service_menu = gtk.Menu()
        self.service_menu.connect("hide", self.on_hide_service_menu)
	self.zc_types = {}
	self.zc_services = {}
        self.zc_pretty_name = avahi.ServiceTypeDatabase.ServiceTypeDatabase()

	# Gconf Paths
	self.gc_options = "/apps/service-discovery-applet/options"
	self.gc_services =  "/apps/service-discovery-applet/services"

	# Gconf
	self.gc_client = gconf.client_get_default ()
    
	self.gc_client.add_dir (self.gc_options, gconf.CLIENT_PRELOAD_NONE)

	self.show_local_services = self.gc_client.get_bool ("%s/%s" % (self.gc_options,"show_local_services"))
	self.show_notifications = self.gc_client.get_bool ("%s/%s" % (self.gc_options,"show_notifications"))

	self.gc_client.add_dir (self.gc_services, gconf.CLIENT_PRELOAD_NONE)
	self.gc_client.notify_add (self.gc_services, self.gc_services_cb)
	self.gc_client.notify_add (self.gc_options, self.gc_options_cb)


	# init first run browsing services
	if self.gc_client.get_bool ("%s/%s" % (self.gc_options,"first_run")):
	    self.gc_client.set_bool("%s/%s" % (self.gc_options, "first_run"), False)
	    for service_type, enabled in first_run_services.iteritems():
		self.gc_client.set_bool("%s/%s" % (self.gc_services, service_type), enabled)

        applet.connect("button-press-event", self.on_button_press)
	applet.connect("size-allocate", self.on_applet_size_allocate)

	image = gtk.Image()
	image.set_from_file("/usr/share/service-discovery-applet/icons//24x24/service-discovery-applet.png")
	applet.add(image)

	# add tooltip to the applet
	tooltip = gtk.Tooltips()
	tooltip.set_tip(applet, _("Zeroconf Service Discovery"))
	tooltip.enable()
	
	applet.connect("change_background", self.on_change_background)
	applet.set_applet_flags(gnomeapplet.EXPAND_MINOR)

	# funky right-click menu
	menuXml = """
	<popup name="button3">
	<menuitem name="SDA About Item" verb="SDA About" _label="%s" pixtype="stock" pixname="gtk-about"/>
	<menuitem name="SDA Config Item" verb="SDA Config" _label="%s" pixtype="stock" pixname="gtk-preferences"/>
	<menuitem name="SDA Start Item" verb="SDA Start" _label="%s" pixtype="stock" pixname="gtk-media-play"/>
	<menuitem name="SDA Stop Item" verb="SDA Stop" _label="%s" pixtype="stock" pixname="gtk-media-stop"/>
	</popup>
	"""
	menuXml = menuXml % (_("_About"), _("_Preferences"), _("_Start"), _("S_top"))

	applet.setup_menu(menuXml, [
		("SDA About", self.on_about),
		("SDA Config", self.on_config),
		("SDA Start", self.start_service_discovery),
		("SDA Stop", self.stop_service_discovery)
		], applet)
	self.popup = applet.get_popup_component()
        self.popup_control = applet.get_control()
    #Start Service Discovery
	self.domain = ""
	self.system_bus = dbus.SystemBus()
	self.server = dbus.Interface(self.system_bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), 
				avahi.DBUS_INTERFACE_SERVER)
	
	self.session_bus = dbus.SessionBus()
	obj = self.session_bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
	self.notif = dbus.Interface(obj, "org.freedesktop.Notifications")
        
	self.start_service_discovery(None, None, None)
	
#	applet.connect("destroy",self.cleanup)
#	applet.show_all()

    def start_notifying_cb(self):
        print "start notifying"
	self.show_notifications = self.gc_client.get_bool ("%s/%s" % (self.gc_options,"show_notifications"))

    def siocgifname(self, interface):
	if interface <= 0:
	    return "any"
	else:
	    return self.server.GetNetworkInterfaceNameByIndex(interface)

    def service_resolved(self, interface, protocol, name, type, domain, host, aprotocol, address, port, txt):
	print "Service data for service '%s' of type '%s' in domain '%s' on %s.%i:" % (name, type, domain, self.siocgifname(interface), protocol)
	print "\tHost %s (%s), port %i, TXT data: %s" % (host, address, port, avahi.txt_array_to_string_array(txt))

        txts = avahi.txt_array_to_string_array(txt)
        txts = services.pair_to_dict(txts)
        services.handle(name, type, host, address, port, txts)

    def print_error(self, err):
	print "Error:", str(err)

    def menuitem_response(self, widget, interface, protocol, name, type, domain):
	self.server.ResolveService(interface, protocol, name, type, domain, avahi.PROTO_INET, reply_handler=self.service_resolved, error_handler=self.print_error)

    def new_service(self, interface, protocol, name, type, domain):
	print "Found service '%s' of type '%s' in domain '%s' on %s.%i." % (name, type, domain, self.siocgifname(interface), protocol)

        try:
            if self.show_local_services == False:
                if self.server.IsServiceLocal( interface, protocol, name, type, domain) == True:
                    return
        except dbus.dbus_bindings.DBusException:
            pass
            
        if self.zc_types.has_key(type) == False:
	    if self.zc_pretty_name.has_key(type):
		pretty_name = self.zc_pretty_name[type]
	    else:
		pretty_name = type
	    img = gtk.Image()
	    iconfile = "/usr/share/service-discovery-applet/icons//24x24/%s.png" % (type)
	    if not os.path.exists(iconfile):
		iconfile = "/usr/share/service-discovery-applet/icons//24x24/service-discovery-applet.png"
	    img.set_from_file(iconfile)
	    menuitem =  gtk.ImageMenuItem(pretty_name)
	    menuitem.set_image(img)
            
	    self.service_menu.add(menuitem)
	    self.zc_types[type] = gtk.Menu()
	    menuitem.set_submenu(self.zc_types[type])
	    menuitem.show_all()

	menuitem = gtk.MenuItem(name)
	self.zc_types[type].add(menuitem)
	self.zc_services[(interface, protocol, name, type, domain)] = menuitem
	menuitem.connect("activate", self.menuitem_response,interface, protocol, name, type, domain)
	menuitem.show_all()

        self.display_service_notification(True, name, type)


    def remove_service(self, interface, protocol, name, type, domain):
	print "Service '%s' of type '%s' in domain '%s' on %s.%i disappeared." % (name, type, domain, self.siocgifname(interface), protocol)

        if self.zc_services.has_key((interface, protocol, name, type, domain)):
            self.zc_types[type].remove(self.zc_services[(interface, protocol, name, type, domain)])
	    self.display_service_notification(False, name, type)

	if self.zc_types[type].get_children() == []:
	    self.service_menu.remove(self.zc_types[type].get_attach_widget())
	    del self.zc_types[type]


    def display_service_notification(self, new, name, type):
        # FIXME handle this in avahi.ServiceTypeDatabase
	if self.zc_pretty_name.has_key(type):
	    pretty_name = self.zc_pretty_name[type]
        else:
            pretty_name = type

	iconfile = "/usr/share/service-discovery-applet/icons//48x48/%s.png" % (type)
	if not os.path.exists(iconfile):
	    iconfile = "/usr/share/service-discovery-applet/icons//48x48/service-discovery-applet.png"

	message = _("<b>Name :</b> %s\n<b>Type : </b> %s <i>(%s)</i>") % (name, pretty_name, type)

        if new == True:
            title = _("New service found")
        else:
            title = _("Service disappeared")
            
        self.display_notification(title, message, iconfile)

    def display_notification(self, title, message, iconfile = "/usr/share/service-discovery-applet/icons//48x48/service-discovery-applet.png"):
	try:
	    if self.show_notifications == True:
		self.notif.Notify(_("Zeroconf Service Discovery"),
			     iconfile, dbus.UInt32(0),"",dbus.Byte(0),
			     title,message,
			     [iconfile],[""],[""],True,dbus.UInt32(3))
	except:
	    print "can't use notification daemon"
	    pass
 
    def add_service_type(self, interface, protocol, type, domain):
	# Are we already browsing this domain for this type? 
	if self.service_browsers.has_key((interface, protocol, type, domain)):
	    return

	print "Browsing for services of type '%s' in domain '%s' on %s.%i ..." % (type, domain, self.siocgifname(interface), protocol)
    
	b = dbus.Interface(self.system_bus.get_object(avahi.DBUS_NAME, 
						      self.server.ServiceBrowserNew(interface, protocol, type, domain))
			   , avahi.DBUS_INTERFACE_SERVICE_BROWSER)
	b.connect_to_signal('ItemNew', self.new_service)
	b.connect_to_signal('ItemRemove', self.remove_service)
	
	self.service_browsers[(interface, protocol, type, domain)] = b

    def del_service_type(self, interface, protocol, type, domain):

	service = (interface, protocol, type, domain)
	if not self.service_browsers.has_key(service):
	    return
	sb = self.service_browsers[service]
	sb.Free()
	del self.service_browsers[service]
	# delete the sub menu of service_type
	if self.zc_types.has_key(type):
	    self.service_menu.remove(self.zc_types[type].get_attach_widget())
	    del self.zc_types[type]

    def position_popup_cb(self, widget):
        x, y = self.applet.window.get_origin()
        applet_width = self.applet.allocation.width
        applet_height = self.applet.allocation.height
        widget_width ,widget_height = widget.size_request()
        orientation = self.applet.get_orient()
        if orientation == gnomeapplet.ORIENT_UP:
            y -= widget_height
        elif orientation == gnomeapplet.ORIENT_DOWN:
            y += applet_height
        elif orientation == gnomeapplet.ORIENT_LEFT:
            x -= widget_width
        elif orientation == gnomeapplet.ORIENT_RIGHT:
            x += applet_width
        return (x, y, True)

    def on_button_press(self, widget, event):
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1:
            self.service_menu.show_all()
	    self.service_menu.popup(None, None, self.position_popup_cb, event.button, event.time)
            widget.set_state(gtk.STATE_SELECTED)
#        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
#            self.popup_control.do_popup(event.button,event.time)
	return False
        
    def on_hide_service_menu(self,widget):
        self.applet.set_state(gtk.STATE_NORMAL)
        return False

    def on_applet_size_allocate(self, eventbox, rect):
        if (rect.x <= 0) or (rect.y <= 0):
            return False
        rect.x -= 1
        rect.y -= 1
        rect.width  += 2
        rect.height += 2
        gtk.Widget.size_allocate(eventbox, rect)
        return False

    def on_change_background(self, panelapplet, backgroundtype, color, pixmap):
        panelapplet.modify_bg(gtk.STATE_NORMAL, color)
	if backgroundtype == gnomeapplet.PIXMAP_BACKGROUND:
            s = panelapplet.get_style()
	    s.bg_pixmap[gtk.STATE_NORMAL] = pixmap

    def on_about(self, component, verb, applet):
	icon = gtk.Image()
	icon.set_from_file("/usr/share/service-discovery-applet/icons//48x48/service-discovery-applet.png")
	
	fullname = _("Zeroconf Service Discovery")
	copyright = _("Copyright (C) 2005 Sebastien Estienne")
	description = _("Shows Zeroconf Services on your local network and allows accessing them easily")
	authors = ["Sebastien Estienne <sebastien.estienne@gmail.com>", "Sebastian Dröge <slomo@ubuntu.com>"]
	translators = _("translator-credits")
	if translators == "translator-credits":
		translators = None
	
	about = gnome.ui.About(fullname, "0.3", copyright, description, authors, None, translators, icon.get_pixbuf())
	about.set_icon(icon.get_pixbuf())
	about.show()

    def on_config(self, component, verb, applet):
	pid = subprocess.Popen(["service-discovery-config", ""]).pid

#FIXME replace with gconf key
    def start_service_discovery(self, component, verb, applet):
	if len(self.domain) != 0:
            print "domain not null %s" % (self.domain)
            self.display_notification(_("Already Discovering"),"")
	    return 
	try:
	    self.domain = self.server.GetDomainName()
	except:
            self.display_notification(_("Error Detected!"),_("Check that the Avahi daemon is running!"))
	    return

        self.display_notification(_("Starting discovery"),"")

	self.interface = avahi.IF_UNSPEC
	self.protocol = avahi.PROTO_INET

	gc_entries = self.gc_client.all_entries(self.gc_services)
	for gc_entry in gc_entries:
	    if self.gc_client.get_bool(gc_entry.key) == True:
		service_type = os.path.basename(gc_entry.key)
		self.add_service_type(self.interface, self.protocol, service_type, self.domain)
        # Wait one second before displaying notifications
        self.show_notifications = False
        gobject.timeout_add(1000, self.start_notifying_cb)

    def stop_service_discovery(self, component, verb, applet):
	if len(self.domain) == 0:
            self.display_notification(_("Discovery already stopped"),"")
	    return

	for service in self.service_browsers.copy():
	    self.del_service_type(service[0],service[1],service[2],service[3])
	self.domain = ""
        self.display_notification(_("Discovery stopped"),"")
    
    # Callback called when a service is added/removed/enabled/disabled in gconf
    def gc_services_cb (self, client, cnxn_id, gc_entry, data):
	service_type = os.path.basename(gc_entry.key)
	# FIXME unset key
	if client.get_bool(gc_entry.key) == True:
	    # Browse for a new service
	    print "browse %s" % (service_type)
	    self.add_service_type(self.interface, self.protocol, service_type, self.domain)
	else:
	    # Stop browsing for a service
	    print "remove %s" % (service_type)
	    self.del_service_type(self.interface, self.protocol, service_type, self.domain)

    def gc_options_cb (self, client, cnxn_id, gc_entry, data):
	key = os.path.basename(gc_entry.key)
	if key == "show_notifications":
	    self.show_notifications = client.get_bool(gc_entry.key)
	if key == "show_local_services":
	    self.show_local_services = client.get_bool(gc_entry.key)
            self.show_notifications = False
            status = self.domain
            self.stop_service_discovery(None,None,None)
            # only start if it was running before
            if len(status) != 0:
                self.start_service_discovery(None,None,None)

def applet_factory(applet, iid):
    sda = ServiceDiscoveryApplet(applet, iid)
    sda.applet.show_all()
    print "applet_factory"
    return True

def activate_factory():
    gnomeapplet.bonobo_factory("OAFIID:GNOME_ServiceDiscoveryApplet_Factory", 
			       gnomeapplet.Applet.__gtype__, 
			       "Service discovery applet", "0", applet_factory)

def main():
    gobject.type_register(ServiceDiscoveryApplet)
    if len(sys.argv) == 2 and sys.argv[1] == "-window":
        applet_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        applet_window.set_title(_("Zeroconf Service Discovery"))
        applet_window.connect("destroy", gtk.main_quit)
        gnome.init("Service discovery applet", "0.3")
        applet = gnomeapplet.Applet()
        applet_factory(applet, None)
        applet.reparent(applet_window)
        applet_window.show_all()
        gtk.main()
    else:
        activate_factory()

if __name__ == "__main__":
    main()
