#!/usr/bin/python

import gtk
import gtk.glade
import gobject
import os
import sys
import string
import socket
import fcntl
import struct
from popen2 import Popen3

# starts network tool from gnome-system-tools and uses icons from there, needs to depend on it

class LTSPManager:
	def __init__(self):
		self.wTree=gtk.glade.XML ("./ltsp-manager.glade")
		self.win = self.wTree.get_widget("window1")
		self.buildwin = self.wTree.get_widget("build_window")
		self.selectwin = self.wTree.get_widget("select_window")
		self.win.connect("destroy", lambda w: gtk.main_quit())
		close_button = self.wTree.get_widget("close_button")
		close_button.connect("clicked", lambda w: gtk.main_quit())
		help_button = self.wTree.get_widget("help_button")
		help_button.connect("clicked", lambda w: os.system('firefox http://people.ubuntu.com/~ogra/LTSPManager/'))

		self.lines=0

		self.fulfill_prereq()
			
	def initialize(self):
		self.get_devices()
		self.set_toggles()
		self.parse_dhcpd_conf()
		self.get_keymaps()
		self.get_kbmodels()
		self.get_serial_protocols()
		self.get_videodrivers()
		self.wTree.get_widget("range_from_entry").connect("focus-out-event", self.compare_range)
		self.wTree.get_widget("range_to_entry").connect("focus-out-event", self.compare_range)

	def show_mainwin(self, path):
		self.root=path
		self.buildwin.hide()
		self.selectwin.hide()

		self.initialize()
		self.win.show_all()

	def fulfill_prereq(self):
		self.win.hide()
		self.selectwin.hide()
		self.buildwin.hide()
		
		print('meep')
		pathlist = self.get_root_path()
		arch = os.popen('dpkg --print-architecture').read().strip()
		
		i=0

		if len(pathlist) > 1:
			self.selectwin.show_all()

			combo = self.wTree.get_widget("chroot_combo")

			select_cancel = self.wTree.get_widget("chroot_cancel_button")
			select_cancel.connect("clicked", lambda w: gtk.main_quit())
			
			select_ok = self.wTree.get_widget("chroot_ok_button")
			select_ok.connect("clicked", lambda w: self.show_mainwin('/opt/ltsp/'+combo.get_active_text()))

			for path in pathlist:
				if path == 'i386' or path == 'powerpc' or path == 'amd64':
					combo.append_text(path)
					if path == arch:
						combo.set_active(i)
					i=i+1
				
		elif len(pathlist) == 1 and (pathlist[0] == 'i386' or
			pathlist[0] == 'amd64' or pathlist[0] == 'powerpc'):
			self.show_mainwin('/opt/ltsp/'+pathlist[0])
		else:
			combo = self.wTree.get_widget("build_arch_selector")

			build_button = self.wTree.get_widget("build_start_button")
			build_button.connect("clicked", lambda w: self.build_client(combo.get_active_text()))
			build_cancel = self.wTree.get_widget("build_cancel_button")
			build_cancel.connect("clicked", lambda w: self.cleanup(combo.get_active_text()))

			if arch == 'amd64':
				archlist = ['amd64', 'i386']
			else:
				archlist = [arch]

			for item in archlist:
				combo.append_text(item)
			combo.set_active(0)
			self.buildwin.show_all()
			self.wTree.get_widget("build_help_button").hide()

	def get_root_path(self):
		pathlist = os.popen('ls /opt/ltsp/ 2>/dev/null')
		if pathlist:
			return pathlist.read().split('\n')[:-1]
		else:
			return 0
	
	def cleanup(self, root):
		dirs = ['proc','sys','var/run','var/lock']
		os.system('pkill ltsp-build-client')
		os.system('pkill debootstrap')
		for directory in dirs:
			os.system('umount /opt/ltsp/'+root+'/'+directory+' 2>/dev/null')
		os.system('rm -rf /opt/ltsp/'+root)
		sys.exit(0)
	
	def build_client(self, arch):
		progbar = self.wTree.get_widget("build_progressbar")
		self.wTree.get_widget("build_start_button").set_sensitive(0)
		self.wTree.get_widget("build_arch_selector").set_sensitive(0)
		fraction=0
		
		pp=Popen3('/usr/sbin/ltsp-build-client --arch '+arch+' 2>/dev/null')
		for nn in xrange(2000):
			while gtk.events_pending():
				gtk.main_iteration(True)
			line = pp.fromchild.readline().strip()
			if line.startswith('I: '):
				line = line.lstrip('I: ')
			out = self.line_filter(line)
			if out:
				progbar.set_text(out)
				step = fraction+0.000666667 #0.000731529 for http, 0.000877193 for cdroms
				if step > 1.0:
					step = 1.0
				progbar.set_fraction(step)
				fraction = step

		progbar.set_fraction(1.0)
		progbar.set_text('LTSP Setup Done...')
		
		gobject.timeout_add(3000, self.show_mainwin('/opt/ltsp/'+arch))
	
	def line_filter(self, line):
		if line.startswith('Retrieving') or line.startswith('Validating') or line.startswith('Extracting') or line.startswith('Installing') or line.startswith('Unpacking') or line.startswith('Configuring') or line.startswith('Need to get') or line.startswith('Setting up') or line.startswith('Cleaning'):
			line = line.split('(')[0]
			if not line.endswith('...'):
				line=line+'...'
			self.lines=self.lines+1
			return line
		elif line.startswith('Get:'):
			line = 'Retrieving '+line.split()[3]+'...'
			self.lines=self.lines+1
			return line
		else:
			return

	def set_toggles(self):
		# Server
		nbd_checkbutton = self.wTree.get_widget("nbd_checkbutton")
		nbd_checkbutton.connect("clicked", lambda w: \
			self.toggle([self.wTree.get_widget("swap_hbox")]))
		
		# Client input
		kbd_checkbutton = self.wTree.get_widget("kbd_checkbutton")
		kbd_checkbutton.connect("clicked", lambda w: \
			self.toggle([self.wTree.get_widget("kbd_layout_hbox"), \
					self.wTree.get_widget("kbd_model_hbox")]))
		
		mouse_checkbutton = self.wTree.get_widget("mouse_checkbutton")
		mouse_checkbutton.connect("clicked", lambda w: \
			self.toggle([self.wTree.get_widget("mousedev_hbox"), \
					self.wTree.get_widget("mouseproto_hbox"), \
					self.wTree.get_widget("emu_hbox")]))
		
		# Client screen
		noauto_checkbutton = self.wTree.get_widget("noauto_checkbutton")
		noauto_checkbutton.connect("clicked", lambda w: \
			self.toggle([self.wTree.get_widget("hsync_hbox"), \
					self.wTree.get_widget("vref_hbox"), \
					self.wTree.get_widget("driver_hbox")]))
		
		xfs_checkbutton = self.wTree.get_widget("xfs_checkbutton")
		xfs_checkbutton.connect("clicked", lambda w: \
			self.toggle([self.wTree.get_widget("xfs_hbox")]))
		
		# LDM
		ldm_command_checkbutton = self.wTree.get_widget("ldm_command_checkbutton")
		ldm_command_checkbutton.connect("clicked", lambda w: \
			self.toggle([self.wTree.get_widget("ldm_command_hbox")]))
		
		env_checkbutton = self.wTree.get_widget("env_checkbutton")
		env_checkbutton.connect("clicked", lambda w: \
			self.toggle([self.wTree.get_widget("env_hbox")]))
		
		return True

	def toggle(self, widgetlist):
		status = widgetlist[0].get_property('sensitive')
		if status == True:
			status = False
		else:
			status = True
		for widget in widgetlist:
			widget.set_sensitive(status)

	def get_devices(self):
		list = []
		self.devlist = []
		devlist = os.popen("cat /proc/net/dev|grep :|\
			awk '{split($0, dev, \":\"); printf \"%s\\n\", \
			substr(dev[1], 3)}'|grep -v lo|grep -v sit")
		list.append(devlist.read().split('\n'))
		for device in list[0][:-1]:
			if not self.get_ip(device) == False:
				self.devlist.append(device)
			else:
				# make that a info dialog
				self.win.set_sensitive(False)
				dialog = gtk.MessageDialog(self.win,
						gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
						gtk.MESSAGE_INFO, gtk.BUTTONS_OK, None)
				dialog.set_markup('<big><b>Not all available interfaces activated!</b></big>\n\nThe interface <b>'+device+'</b> is present but offline. Please bring it up before running LTSP Manager if you want to use it. For LTSP.')
				dialog.connect("destroy", lambda w: self.win.set_sensitive(True))
				resp = dialog.run()
				if resp:
					dialog.destroy()
		return True

	def get_keymaps(self):
		list = []
		keyb_list = os.popen("grep xkb_keymap "+self.root+"/etc/X11/xkb/keymap/* | \
				awk '{split($0, kb);printf \"%s\\n\", kb[2]}'|\
				grep -v xkb_keymap|sort|uniq")
		list.append(keyb_list.read().split('\n'))
		for item in list[0][:-1]:
			self.wTree.get_widget("kbd_layout_combobox").append_text(str(item).strip('\"'))

	def get_kbmodels(self):
		list = []
		newlist = []
		keyb_list = os.popen("grep xkb_geometry "+self.root+"/etc/X11/xkb/geometry/*| \
			awk '{split($0, kb);printf \"%s%s\\n\", kb[2],kb[3]}'")
		list.append(keyb_list.read().split('\n'))
		for item in list[0][:-1]:
			newlist.append(item.lstrip('xkb_geometry').rstrip('{').strip('\"'))
		newlist = self.unique(newlist)
		newlist.sort(lambda x, y: cmp(string.lower(x), string.lower(y)))
		for model in newlist:
			self.wTree.get_widget("kbd_model_combobox").append_text(model)

	def get_serial_protocols(self):
		modelist = os.popen("chroot "+self.root+" inputattach --help")
		for line in modelist.read().split('\n'):
			if line.lstrip().startswith('--'):
				self.wTree.get_widget("mouseproto_combobox").append_text(string.join(line.split()[2:]).rstrip())

	def get_videodrivers(self):
		list = []
		cardlist = os.popen("ls "+self.root+"/usr/lib/xorg/modules/drivers/*.so")
		list.append(cardlist.read().split('\n'))
		for item in list[0][:-1]:
			newitem = item.split('/')[-1].rstrip('_drv.so')
			self.wTree.get_widget("driver_combobox").append_text(newitem)

	def get_ip(self, ifname):
		s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		try:
			retval = socket.inet_ntoa(fcntl.ioctl(
				s.fileno(),
				0x8915,  # SIOCGIFADDR
				struct.pack('256s', ifname[:15])
				)[20:24])
		except:
			return False
		return retval

	def parse_dhcpd_conf(self):
		try:
			file = open('/etc/ltsp/dhcpd.conf', 'r')
		except:
			# make that an error dialog
			self.win.set_sensitive(False)
			dialog = gtk.MessageDialog(self.win,
					gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
					gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, None)
			dialog.set_markup('<big><b>Can\'t open /etc/ltsp/dhcpd.conf !</b></big>\n\nThe file /etc/ltsp/dhcpd.conf could not be opened, make sure the ltsp-server-standalone package is installed correctly. LTSP Manager will quit now.')
			dialog.connect("destroy", lambda w: self.win.set_sensitive(True))
			resp = dialog.run()
			if resp:
				sys.exit(1)
		# read all the data
		for line in file.read().split('\n'):
			line = line.strip().lstrip('option ')
			if line.startswith('range'):
				self.range_from = line.strip('range').strip(';').split()[0]
				self.wTree.get_widget("range_from_entry").set_text(self.range_from)
				self.range_to = line.strip('range').strip(';').split()[1]
				self.wTree.get_widget("range_to_entry").set_text(self.range_to)
			elif line.startswith('domain-name '):
				self.domain_name = line.strip('domain-name ').strip(';').split()[0].strip('\"')
				self.wTree.get_widget("domain_entry").set_text(self.domain_name)
			elif line.startswith('domain-name-servers '):
				self.dns_server = line.strip('domain-name-servers ').strip(';').split()[0]
				self.wTree.get_widget("dns_entry").set_text(self.dns_server)
			elif line.startswith('broadcast-address '):
				self.broadcast = line.strip('broadcast-address ').strip(';').split()[0]
				self.wTree.get_widget("broadcast_entry").set_text(self.broadcast)
			elif line.startswith('routers '):
				self.routers = line.strip('routers ').strip(';').split()[0]
				self.wTree.get_widget("gateway_entry").set_text(self.routers)
			elif line.startswith('subnet-mask '):
				self.netmask = line.strip('subnet-mask ').strip(';').split()[0]
				self.wTree.get_widget("netmask_entry").set_text(self.netmask)
		net = self.range_from.rsplit(".", 1)[0]+'.'
		self.netlist = [net]
		match = False
		# determine the right interface and pre-select it
		iface = self.wTree.get_widget("iface_combobox")
		iface.append_text('none (old config)')
		for dev in self.devlist:
			iface.append_text(dev)
			self.netlist.append(self.get_ip(dev).rsplit(".", 1)[0]+'.')
			if self.get_ip(dev).startswith(net):
				iface.set_active(self.devlist.index(dev)+1)
				match = True
		if match == False:
			iface.set_active(0)
			# make that an error dialog
			self.win.set_sensitive(False)
			dialog = gtk.MessageDialog(self.win,
					gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
					gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, None)
			dialog.set_markup('<big><b>No Matching Interfaces found !</b></big>\n\nThere are no Network interfaces running that match the network configuration ('+net+'0) of the current LTSP DHCP configuration. Please adjust the DHCP settings to match a currently running interface with static IP address.')
			dialog.connect("destroy", lambda w: self.win.set_sensitive(True))
			resp = dialog.run()
			if resp:
				self.wTree.get_widget("notebook1").set_current_page(3)
				dialog.destroy()
		else:
			iface.remove_text(0)
			self.netlist.reverse()
			self.netlist.pop()
			self.netlist.reverse()
		iface.connect("changed", lambda w: self.update_dhcp(iface))

	def update_dhcp(self, iface):
		net = self.netlist[iface.get_active()]
		orig_net = [self.wTree.get_widget("range_from_entry").get_text().rsplit(".", 1)[1], \
				self.wTree.get_widget("range_to_entry").get_text().rsplit(".", 1)[1], \
				self.wTree.get_widget("broadcast_entry").get_text().rsplit(".", 1)[1], \
				self.wTree.get_widget("gateway_entry").get_text().rsplit(".", 1)[1] ]
		self.wTree.get_widget("range_from_entry").set_text(net+orig_net[0])
		self.wTree.get_widget("range_to_entry").set_text(net+orig_net[1])
		self.wTree.get_widget("broadcast_entry").set_text(net+orig_net[2])
		self.wTree.get_widget("gateway_entry").set_text(net+orig_net[3])

	def compare_range(self, widget, event):
		base,range0 = self.wTree.get_widget("range_from_entry").get_text().rsplit(".", 1)
		range1 = self.wTree.get_widget("range_to_entry").get_text().rsplit(".", 1)[1]
		if int(range0) >= 255 or int(range1) >= 255:
			self.wTree.get_widget("range_from_entry").set_text(str(base)+'.253')
			self.wTree.get_widget("range_to_entry").set_text(str(base)+'.254')
			return
		if int(range0) >= int(range1):
			self.wTree.get_widget("range_to_entry").set_text(str(base)+'.'+str(int(range0)+1))

	def unique(self,s):
		n = len(s)
		if n == 0:
			return []
		u = {}
		try:
			for x in s:
				u[x] = 1
		except TypeError:
			del u  # move on to the next method
		else:
			return u.keys()
		
		try:
			t = list(s)
			t.sort()
		except TypeError:
			del t  # move on to the next method
		else:
			assert n > 0
			last = t[0]
			lasti = i = 1
			while i < n:
				if t[i] != last:
					t[lasti] = last = t[i]
					lasti += 1
				i += 1
			return t[:lasti]
		
		# Brute force is all that's left.
		u = []
		for x in s:
			if x not in u:
				u.append(x)
		return u

	def main(self):
		gtk.main()

if __name__ == "__main__":
	base = LTSPManager()
	base.main()
