#!/usr/bin/python
#
# indicator-network - user interface for connman
# Copyright 2010 Canonical Ltd.
#
# Authors:
# Kalle Valo <kalle.valo@canonical.com>
#
# 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 sys
import dbus
import dbus.mainloop.glib
import gobject
import time

WPAS_DBUS_SERVICE = "fi.w1.wpa_supplicant1"
WPAS_DBUS_INTERFACE = "fi.w1.wpa_supplicant1"
WPAS_DBUS_PATH = "/fi/w1/wpa_supplicant1"

WPAS_DBUS_INTERFACES_INTERFACE = "fi.w1.wpa_supplicant1.Interface"
WPAS_DBUS_INTERFACES_OPATH = "/fi/w1/wpa_supplicant1/Interfaces"
WPAS_DBUS_BSS_INTERFACE = "fi.w1.wpa_supplicant1.BSS"

stamp = False

class ArgumentException(Exception):
	def __init__(self, msg):
		self.msg = msg

	def __str__(self):
		return self.msg

def get_bus(async=False):
	if (async):
		dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

	return dbus.SystemBus()

def get_manager(bus=None):
	if bus == None:
		bus = get_bus()

	return dbus.Interface(bus.get_object("net.connman", "/"),
			      "net.connman.Manager")

def usage(name):
	print "Usage: %s <command>" % (name)
	print ""
	print "Commands:"
	print "  state"
	print "  services"
	print "  connect <service>"
	print "  connect-hidden <name> [passphrase] [type]"
	print "  disconnect <service>"
	print "  show <service>"
	print "  edit <service> [property..]"
	print "  remove <service>"
	print "  scan [technology]"
	print "  enable <technology>"
	print "  disable <technology>"
	print "  offlinemode <on|off>"
	print "  event [-t]"
	print "  modem list"
	print "  modem pin <number>"
	print "  modem puk <number>"
	print "  wpas list"
	print "  wpas networks"
	print "  wpas bss"
	print ""
	print "Properties:"
	print "  apn <name>"
	print "  passphrase <passphrase>"
	print "  autoconnect <true|false>"
	print "  ipv4 <dhcp|manual address netmask gateway>"
	print "  nameservers auto | <nameserver> [nameserver] [nameserver]"
	print "  domains [domain] [domain] ..."

	return

def is_connected(state):
	connected = [ "ready", "offline", "online"]

	if state in connected:
		return True
	else:
		return False

def is_associating(state):
	return state == "association"

def is_configuration(state):
	return state == "configuration"

def is_failed(state):
	return state == "failure"

def find_service(name):
	bus = get_bus()
	manager = get_manager(bus)
	manager_properties = manager.GetProperties()

	path = "/profile/default/" + name

	if path in manager_properties["Services"]:
		service = dbus.Interface(bus.get_object("net.connman",
							path),
					 "net.connman.Service")
		return service

	# service not found with the path, let's try find it with name

	found = []

	for path in manager_properties["Services"]:
		service = dbus.Interface(bus.get_object("net.connman",
							path),
					 "net.connman.Service")

		properties = service.GetProperties()

		if "Name" in properties.keys():
			if properties["Name"].lower() == name.lower():
				found.append(service)

	if len(found) > 1:
		raise ArgumentException("Multiple services with same name, "
					"use id instead.")
	elif len(found) == 1:
		return found.pop()
	else:
		raise ArgumentException("Service '%s' not found." % name)

def get_service_name(path):
	bus = get_bus()

	try:
		service = dbus.Interface(bus.get_object("net.connman",
							path),
					 "net.connman.Service")
		properties = service.GetProperties()
		name = properties["Name"]
		if len(name) == 0:
			name = path[path.rfind("/") + 1:]

	except dbus.DBusException:
		name = path[path.rfind("/") + 1:]

	return name

def extract_dict(values):
	s = ""
	for k, v in values.items():
		if type(v) is dbus.Array:
			v = extract_list(v)
		s += " %s=%s" % (k,v)
	return "{ %s }" % s

def extract_list(values):
	return "[ %s ]" % " ".join(values)

def convert_dbus_value(value):
	"""Convert a dbus object to a pretty string"""

	if type(value) is dbus.Dictionary:
		result = extract_dict(value)
	elif type(value) is dbus.Array:
		result = extract_list(value)
	elif type(value) is dbus.Boolean:
		if value:
			result = "true"
		else:
			result = "false"
	elif type(value) is dbus.Byte:
		result = str(int(value))
	else:
		result = str(value)

	return result

def get_devices():
	bus = get_bus()
	manager = get_manager()

	result = []

	properties = manager.GetProperties()
	for path in properties["Technologies"]:
		technology = dbus.Interface(bus.get_object("net.connman",
							   path),
					    "net.connman.Technology")
		properties = technology.GetProperties()

		for path in properties["Devices"]:
			d = dbus.Interface(bus.get_object("net.connman",
							  path),
					   "net.connman.Device")
			result.append(d)

	return result

def cmd_state():
	manager = get_manager()
	properties = manager.GetProperties()
	order = ["State", "OfflineMode", "DefaultTechnology",
		 "ConnectedTechnologies", "EnabledTechnologies",
		 "AvailableTechnologies", "ActiveProfile", "Profiles",
		 "Providers", "EnabledDebugs", "AvailableDebugs",
		 "Technologies", "Services"]

	keys = properties.keys()

	for key in order:
		if key in keys:
			keys.remove(key)
			value = convert_dbus_value(properties[key])
			print "%s: %s" % (key, value)

	# print properties missing in the order
	for key in keys:
		value = convert_dbus_value(properties[key])
		print "%s: %s" % (key, value)

def cmd_services():
	bus = get_bus()
	manager = get_manager()
	manager_properties = manager.GetProperties()

	for path in manager_properties["Services"]:
		service = dbus.Interface(bus.get_object("net.connman",
							path),
					 "net.connman.Service")

		properties = service.GetProperties()

		identifier = path[path.rfind("/") + 1:]
		state = properties["State"]

		if is_connected(state):
			connected = "*"
		elif is_configuration(state):
			connected = "+"
		elif is_associating(state):
			connected = "-"
		elif is_failed(state):
			connected = "f"
		else:
			connected = " "

		if "Name" in properties.keys():
			name = properties["Name"]
		else:
			name = "{" + properties["Type"] + "}"

		print "%s %-26s { %s }" % (connected, name, identifier)

def cmd_edit_apn(service, argv):
	if (len(sys.argv) < 1):
		raise ArgumentException("apn value missing")

	apn = argv.pop(0)
	service.SetProperty("APN", apn);

def cmd_edit_passphrase(service, argv):
	if (len(sys.argv) < 1):
		raise ArgumentException("passphrase value missing")

	passphrase = argv.pop(0)
	service.SetProperty("Passphrase", passphrase);

def cmd_edit_autoconnect(service, argv):
	if (len(argv) < 1):
		raise ArgumentException("autoconnect value missing")

	arg = argv.pop(0)

	if arg == "true":
		autoconnect = dbus.Boolean(True)
	elif arg == "false":
		autoconnect = dbus.Boolean(False)
	else:
		raise ArgumentException("Invalid value for autoconnect: %s" %
					arg)

	print autoconnect
	service.SetProperty("AutoConnect", autoconnect);

def cmd_edit_ipv4(service, argv):
	if len(argv) < 1:
		raise ArgumentException("ipv4 method missing")

	method = argv.pop(0)

	if method == "dhcp":
		value = { "Method": "dhcp" }
	elif method == "manual":
		if len(argv) < 3:
			raise ArgumentException("invalid syntax for ipv4 "
						"manual configuration")
		
		address = argv.pop(0)
		netmask = argv.pop(0)
		gateway = argv.pop(0)

		value = { "Method": "manual",
			  "Address": address,
			  "Netmask": netmask,
			  "Gateway": gateway }

	service.SetProperty("IPv4.Configuration", value);

def cmd_edit_nameservers(service, argv):
	items = len(argv)

	if items == 1 and argv[0] == "auto":
		# disable manual nameservers
		argv = dbus.Array(signature="s")
	elif items < 1:
		raise ArgumentException("nameserver address missing")
	elif items > 3:
		raise ArgumentException("maximum of 3 nameserver addresses")

	service.SetProperty("Nameservers.Configuration", argv)

def cmd_edit_domains(service, argv):
	l = dbus.Array(argv, signature="s")
	service.SetProperty("Domains.Configuration", l)

def cmd_edit(argv):
	if len(argv) < 1:
		raise ArgumentException("service name missing")
	
	name = argv.pop(0)

	service = find_service(name)

	if len(argv) < 1:
		raise ArgumentException("property name missing")

	while len(argv) > 0:
		if len(argv) < 1:
			print "property name missing"
		prop = argv.pop(0)

		if prop == "apn":
			cmd_edit_apn(service, argv)
		elif prop in ["passphrase", "pass"]:
			cmd_edit_passphrase(service, argv)
		elif prop in ["autoconnect", "autoconn"]:
			cmd_edit_autoconnect(service, argv)
		elif prop in ["ipv4"]:
			cmd_edit_ipv4(service, argv)
		elif prop in ["nameservers"]:
			cmd_edit_nameservers(service, argv)
		elif prop in ["domains"]:
			cmd_edit_domains(service, argv)

def cmd_connect(argv):

	if (len(argv) < 1):
		raise ArgumentException("Need at least service parameter")

	name = argv.pop(0)
	service = find_service(name)

	try:
		service.Connect(timeout=60000)
	except dbus.DBusException, error:
		print "%s: %s" % (error._dbus_error_name, error.message)

def cmd_disconnect(argv):
	if (len(argv) < 1):
		raise ArgumentException("Need at least service parameter")

	name = argv.pop(0)
	service = find_service(name)

	try:
		service.Disconnect()
	except dbus.DBusException, error:
		print "%s: %s" % (error._dbus_error_name, error.message)

def cmd_remove(argv):
	if (len(argv) < 1):
		raise ArgumentException("Need at least service parameter")

	name = argv.pop(0)
	service = find_service(name)
	properties = service.GetProperties()

	if properties["Favorite"] == dbus.Boolean(0):
		raise ArgumentException("Only favorite services can be removed")

	try:
		service.Remove()
	except dbus.DBusException, error:
		print "%s: %s" % (error._dbus_error_name, error.message)

def cmd_scan(argv):
	if len(argv) > 0:
		arg = argv.pop(0)
	else:
		arg = ""

	manager = get_manager()
	manager.RequestScan(arg)

	devices = get_devices()

	if len(devices) == 0:
		# no devices available so no scan results either
		return

	# start waiting for scanning to end, but take into account
	# that not all devices, if any (!), support scanning
	scanning = False
	scan_supported = False

	while True:
		scanning = False
		for device in devices:
			properties = device.GetProperties()

			if "Scanning" not in properties:
				continue

			scan_supported = True

			if properties["Scanning"]:
				scanning = True

		if not scanning or not scan_supported:
			break;

	cmd_services()

def cmd_enable(argv):
	if len(argv) > 0:
		technology = argv.pop(0)
	else:
		technology = ""

	manager = get_manager()
	manager.EnableTechnology(technology)

def cmd_disable(argv):
	if len(argv) > 0:
		technology = argv.pop(0)
	else:
		technology = ""

	manager = get_manager()
	manager.DisableTechnology(technology)

def cmd_offlinemode(argv):
	if len(argv) > 0:
		arg = sys.argv.pop(0)
		if arg == "on":
			active = dbus.Boolean(1)
		elif arg == "off":
			active = dbus.Boolean(0)
		else:
			active = dbus.Boolean(arg)

		manager = get_manager()
		manager.SetProperty("OfflineMode", active)
	else:
		properties = manager.GetProperties()
		print "Offline mode is %s" % (properties["OfflineMode"])

def cmd_show(argv):
	if len(argv) < 1:
		raise ArgumentException("service name missing")

	name = argv.pop(0)
	service = find_service(name)
	properties = service.GetProperties()

	order = [ "Name", "Type", "State", "Strength", "Mode", "Security",
		  "IPv4", "IPv4.Configuration",
		  "IPv6", "IPv6.Configuration",
		  "Ethernet",
		  "Nameservers", "Nameservers.Configuration",
		  "Domains", "Domains.Configuration",
		  "Proxy",
		  "PassphraseRequired", "Passphrase",
		  "SetupRequired", "Roaming",
		  "Immutable", "Favorite", "AutoConnect" ]

	keys = properties.keys()

	print "[ %s ]" % service.object_path

	for key in order:
		if key in keys:
			keys.remove(key)
			val = convert_dbus_value(properties[key])
			print "  %s = %s" % (key, val)

	# print properties missing in the order
	for key in keys:
		val = convert_dbus_value(properties[key])
		print "  %s = %s" % (key, val)

def connman_property_changed(name, value, path, interface):
	global stamp

	if stamp:
		timestamp = "%f " % time.time()
	else:
		timestamp = ""

	if interface == "net.connman.Service":
		val = convert_dbus_value(value)
		print "%s[%s] %s: %s" % (timestamp, get_service_name(path),
					  name, val)
	elif interface == "net.connman.Manager":
		val = convert_dbus_value(value)
		print "%s%s: %s" % (timestamp, name, val)

def cmd_event(argv):
	global stamp

	if len(argv) > 0 and argv.pop(0) == "-t":
		stamp = True

	bus = get_bus(async=True)
	bus.add_signal_receiver(connman_property_changed,
				bus_name="net.connman",
				signal_name = "PropertyChanged",
				path_keyword="path",
				interface_keyword="interface")

	mainloop = gobject.MainLoop()
	mainloop.run()

def print_sms_manager(path):
	bus = get_bus()
	interface = "org.ofono.SmsManager"
	manager = dbus.Interface(bus.get_object("org.ofono", path),
				 interface)

	print "    [ %s ]" % interface

	properties = manager.GetProperties()
	for key in properties.keys():
		value = convert_dbus_value(properties[key])
		print "        %s = %s" % (key, value)

def print_context(path, properties):
	print "        [ %s ]" % path

	for key in properties.keys():
		value = convert_dbus_value(properties[key])
		print "            %s = %s" % (key, value)

def print_data_manager(path):
	bus = get_bus()
	interface = "org.ofono.ConnectionManager"
	manager = dbus.Interface(bus.get_object("org.ofono", path),
				 interface)

	print "    [ %s ]" % interface

	properties = manager.GetProperties()
	for key in properties.keys():
		value = convert_dbus_value(properties[key])
		print "        %s = %s" % (key, value)

	contexts = manager.GetContexts()

	for path, properties in contexts:
		print_context(path, properties)

def print_cell_broadcast(path):
	bus = get_bus()
	interface = "org.ofono.CellBroadcast"
	manager = dbus.Interface(bus.get_object("org.ofono", path),
				 interface)

	print "    [ %s ]" % interface

	properties = manager.GetProperties()
	for key in properties.keys():
		value = convert_dbus_value(properties[key])
		print "        %s = %s" % (key, value)

def print_network_registration(path):
	bus = get_bus()
	interface = "org.ofono.NetworkRegistration"
	manager = dbus.Interface(bus.get_object("org.ofono", path),
				 interface)

	print "    [ %s ]" % interface

	properties = manager.GetProperties()
	for key in properties.keys():
		value = convert_dbus_value(properties[key])
		print "        %s = %s" % (key, value)

def print_supplementary_services(path):
	bus = get_bus()
	interface = "org.ofono.SupplementaryServices"
	manager = dbus.Interface(bus.get_object("org.ofono", path),
				 interface)

	print "    [ %s ]" % interface

	properties = manager.GetProperties()
	for key in properties.keys():
		value = convert_dbus_value(properties[key])
		print "        %s = %s" % (key, value)

def print_sim_manager(path):
	bus = get_bus()
	interface = "org.ofono.SimManager"
	manager = dbus.Interface(bus.get_object("org.ofono", path),
				 interface)

	print "    [ %s ]" % interface

	properties = manager.GetProperties()
	for key in properties.keys():
		value = convert_dbus_value(properties[key])
		print "        %s = %s" % (key, value)

def print_modem(path, properties):
	print "[ %s ]" % path

	for key in properties.keys():
		value = convert_dbus_value(properties[key])
		print "    %s = %s" % (key, value)

	if "Interfaces" not in properties:
		return

	for interface in properties["Interfaces"]:
		if interface == "org.ofono.SmsManager":
			print_sms_manager(path)
		elif interface == "org.ofono.ConnectionManager":
			print_data_manager(path)
		elif interface == "org.ofono.CellBroadcast":
			print_cell_broadcast(path)
		elif interface == "org.ofono.NetworkRegistration":
			print_network_registration(path)
		elif interface == "org.ofono.SupplementaryServices":
			print_supplementary_services(path)
		elif interface == "org.ofono.SimManager":
			print_sim_manager(path)
	
def cmd_modem_list(argv):
	bus = get_bus()
	manager = dbus.Interface(bus.get_object("org.ofono", "/"),
				 "org.ofono.Manager")

	try:
		modems = manager.GetModems()
	except dbus.exceptions.DBusException as e:
		raise ArgumentException("Failed to contact ofonod:", e)

	if len(modems) == 0:
		print "No modems found"
		return

	for path, properties in modems:
		print_modem(path, properties)

def cmd_modem_pin(pin_type, argv):
	if len(argv) < 1:
		raise ArgumentException("%s value missing" % pin_type)
	
	pin = argv.pop(0)

	bus = get_bus()
	manager = dbus.Interface(bus.get_object("org.ofono", "/"),
				 "org.ofono.Manager")
	modems = manager.GetModems()

	if len(modems) == 0:
		print "No modems found"
		return

	(modem, properties) = modems[0]

	print "Using modem %s" % modem

	sim_manager = dbus.Interface(bus.get_object("org.ofono", modem),
				 "org.ofono.SimManager")

	# check that pin is really needed
	properties = sim_manager.GetProperties()

	if "PinRequired" not in properties:
		print "PinRequired not found in properties"
		return

	if properties["PinRequired"] != pin_type:
		print "pin type '%s' not required by modem %s (%s)" % (pin_type,
								       modem,
								       properties["PinRequired"])
		return
	try:
		sim_manager.EnterPin(pin_type, pin)
	except dbus.exceptions.DBusException as e:
		print "Failed to enter %s: %s" % (pin_type, str(e))
	
def cmd_modem(argv):
	if len(argv) < 1:
		raise ArgumentException("modem command missing")
	
	cmd = argv.pop(0)

	if cmd == "list":
		cmd_modem_list(argv)
	elif cmd in ["pin", "puk"]:
		cmd_modem_pin(cmd, argv)
	else:
		raise ArgumentException("unknown modem commmand: %s" % cmd)

def print_wpas_network_properties(properties):
	print "  Properties = {"

	for p in properties:
		print "    %s = %s" % (p, convert_dbus_value(properties[p]))

	print "  }"

def print_wpas_network(props):
	for prop in props:
		if prop == "Properties":
			print_wpas_network_properties(props["Properties"])
		else:
			print "  %s = %s" % (prop,
					     convert_dbus_value(props[prop]))

def print_wpas_networks(networks):
	bus = get_bus()

        for path in networks:
		print
		network = bus.get_object("fi.w1.wpa_supplicant1", path)
		props = network.GetAll("fi.w1.wpa_supplicant1.Network",
				       dbus_interface=dbus.PROPERTIES_IFACE)
		print "[ %s ]" % path
		print_wpas_network(props)

def print_wpas_bss(props):
	for prop in props:
		if prop in ["RSN", "IEs", "Rates"]:
			pass
		elif prop in ["SSID", "BSSID"]:
			print "  %s =" % prop,

			for b in props[prop]:
				print "%x" % b,

			print
		else:
			print "  %s = %s" % (prop,
					       convert_dbus_value(props[prop]))

def print_wpas_bsss(bsss):
	bus = get_bus()

	for path in bsss:
		print
		bss = bus.get_object("fi.w1.wpa_supplicant1", path)
		props = bss.GetAll("fi.w1.wpa_supplicant1.BSS",
				   dbus_interface=dbus.PROPERTIES_IFACE)
		print "[ %s ]" % path
		print_wpas_bss(props)

def print_wpas_interface(props):
    for prop in props:
	    if prop == "Capabilities":
		    pass
	    else:
		    print "  %s = %s" % (prop, convert_dbus_value(props[prop]))

def print_wpas(props):
	print "[ %s ]" % WPAS_DBUS_PATH

	for prop in props:
		print "  %s = %s " % (prop, convert_dbus_value(props[prop]))

def cmd_wpas_list(argv, show_bss=False, show_networks=False):
	bus = get_bus()
	wpas = bus.get_object(WPAS_DBUS_SERVICE, WPAS_DBUS_PATH)

	try:
		props = wpas.GetAll("fi.w1.wpa_supplicant1",
				    dbus_interface=dbus.PROPERTIES_IFACE)
	except dbus.exceptions.DBusException as e:
		raise ArgumentException("Failed to contact wpasupplicant: %s"
					"\nAre you root?" % e)


	print_wpas(props)

	if "Interfaces" not in props:
		raise ArgumentException("No Interfaces in properties")

	bsss = []
	networks = []

	for path in props["Interfaces"]:
		print
		print "[ %s ]" % path
		interface = bus.get_object("fi.w1.wpa_supplicant1", path)
		props = interface.GetAll("fi.w1.wpa_supplicant1.Interface",
					 dbus_interface=dbus.PROPERTIES_IFACE)
		print_wpas_interface(props)

		bsss = bsss + props["BSSs"]
		networks = networks + props["Networks"]

	if show_bss:
		print_wpas_bsss(bsss)

	if show_networks:
		print_wpas_networks(networks)

def cmd_wpas(argv):
	if len(argv) < 1:
		raise ArgumentException("wpas command missing")
	
	cmd = argv.pop(0)

	if cmd == "list":
		cmd_wpas_list(argv)
	elif cmd == "networks":
		cmd_wpas_list(argv, show_networks=True)
	elif cmd == "bss":
		cmd_wpas_list(argv, show_bss=True, show_networks=True)
	else:
		raise ArgumentException("unknown wpas commmand: %s" % cmd)

def cmd_connect_hidden(argv):

	if (len(argv) < 1):
		raise ArgumentException("Network name missing")

	name = argv.pop(0)

	if len(argv) > 0:
		passphrase = argv.pop(0)
	else:
		passphrase = ""

	if len(argv) > 0:
		security = argv.pop(0)
	else:
		if len(passphrase) > 0:
			security = "rsn"
		else:
			security = "none"

	properties = { "Type": "wifi", "Mode": "managed", "SSID": name,
		       "Security": security, "Passphrase": passphrase }

	try:
		manager = get_manager()
		manager.ConnectService(properties, timeout=60000)
	except dbus.DBusException, error:
		print "%s: %s" % (error._dbus_error_name, error.message)

def handle_cmd(cmd, argv, name):
	if cmd == "help":
		usage(name)
	elif cmd == "state":
		cmd_state()
	elif cmd in ["services", "list"]:
		cmd_services()
	elif cmd == "edit":
		cmd_edit(argv)
	elif cmd in ["connect", "conn"]:
		cmd_connect(argv)
	elif cmd in ["disconnect", "disc"]:
		cmd_disconnect(argv)
	elif cmd in ["remove"]:
		cmd_remove(argv)
	elif cmd == "scan":
		cmd_scan(argv)
	elif cmd == "enable":
		cmd_enable(argv)
	elif cmd == "disable":
		cmd_disable(argv)
	elif cmd in ["offlinemode", "flightmode"]:
		cmd_offlinemode(argv)
	elif cmd == "show":
		cmd_show(argv)
	elif cmd == "event":
		cmd_event(argv)
	elif cmd == "modem":
		cmd_modem(argv)
	elif cmd == "wpas":
		cmd_wpas(argv)
	elif cmd == "connect-hidden":
		cmd_connect_hidden(argv)
	else:
		print "Unknown command"

def main():
	name = sys.argv.pop(0)

	if len(sys.argv) == 0:
		usage(name)
		return

	cmd = sys.argv.pop(0)

	try:
		handle_cmd(cmd, sys.argv, name)
	except ArgumentException as e:
		print e
		sys.exit(1)

if __name__ == "__main__":
    main()
