# tests the gui. we are going to slowly add functions and handlers for this
# service daemon and a way to populate the tree view.
import gtk
import gtk.glade
import os, os.path
import subprocess



daemon_path = "/etc/init.d/"
running_daemon_path = "/var/run/"
startup_daemon_path = "/etc/rc2.d/"
d_def_file = "ubuntu_services"
d_def_output_file = "ubuntu_services"
VERBOSE = True
log_file_path = "log"
class ServiceGUI:
    def __init__(self):
        # import the glade file
        self.xml = gtk.glade.XML('services.glade')
        # definition dictionary, key: description, value: binary name
        self.desc_dict = self.daemon_array(d_def_file) 
        # retreive the treeview from our glade file.
        self.treeview1 = self.xml.get_widget("treeview1")
        # Get Buttons so we can active/deactivate them
        self.stop_button = self.xml.get_widget("stop_button")
        self.start_button = self.xml.get_widget("start_button")
        self.treeselection1 = self.treeview1.get_selection()
        # set callback function for our treeselection.
        self.treeselection1.set_select_function(self.active_cb, None)
        # create a new liststore to put in the treeview
        self.liststore1 = gtk.ListStore(str, str, 'gboolean')

        # quick hack 
        self.rc2_daemons = os.listdir(startup_daemon_path)
        rc2_daemons = []
        for daemon in self.rc2_daemons:
	    if daemon[0] == 'S':
                daemon = daemon[3:]
                rc2_daemons.append(daemon)
        self.rc2_daemons = rc2_daemons
	self.gen_autostarted()
        self.fillRows(self.liststore1, daemon_path)
        
        self.treeview1.set_model(self.liststore1)
        self.tvcolumn_service = gtk.TreeViewColumn('Service')
        self.tvcolumn_bools = gtk.TreeViewColumn('Start on Boot')
        # add them to the tree view
        self.treeview1.append_column(self.tvcolumn_service)
        self.treeview1.append_column(self.tvcolumn_bools)
        # set column type
        cell_str = gtk.CellRendererText()
        cell_bool = gtk.CellRendererToggle()
        cell_pixbuf = gtk.CellRendererPixbuf()
        # make the cell toggleable
        cell_bool.set_property('activatable', 'True')
        # add a signal handler, it passes the treemodel in so the handler can update it.
        cell_bool.connect('toggled', self.toggled_cb, self.treeview1.get_model())
        # pack them in
        self.tvcolumn_service.pack_start(cell_pixbuf, False)
        self.tvcolumn_service.pack_start(cell_str, True)
        self.tvcolumn_bools.pack_start(cell_bool, True)
        # set the column to render the cells
        self.tvcolumn_service.set_attributes(cell_pixbuf, stock_id=0)
        self.tvcolumn_service.set_cell_data_func(cell_pixbuf, self.running_cb)
        self.tvcolumn_service.set_attributes(cell_str, text=1)
        self.tvcolumn_bools.add_attribute(cell_bool, 'active', 2)
        self.tvcolumn_bools.set_cell_data_func(cell_bool, self.autostart_cb)
        # check if this daemon is in init.d/rc2, if so, set it true.
        self.connect_handlers()
        return
    
    # serves to connect the handlers defined in the .glade file
    
#Startup
    def fillRows(self, liststore, path):
        daemons = os.listdir(path)
        for daemon in daemons:
            cur_bool = False;
            if str(daemon) in self.desc_dict:
                liststore.append([gtk.STOCK_NO, self.desc_dict[str(daemon)], cur_bool])
        return
    
    def autostart_cb(self, column, cell, model, iter):
        if self.is_autostart(iter):
            cell.set_property('active', 1)
        else:
            cell.set_property('active', 0)
        return

    def running_cb(self, column, cell, model, iter):
        if self.is_running(iter):
            cell.set_property('stock-id', gtk.STOCK_YES)
        else:
            cell.set_property('stock-id', gtk.STOCK_NO)
        return

    # figure out which ones are set to autostart
    def gen_autostarted(self):
        self.autostarted = self.intersection(self.rc2_daemons, self.desc_dict.keys())
       	return 
#read a file and create a dictionary file for daemon names and their short descriptions
# file should be in the format: <binary>:<description>:S##:K##
    def daemon_array(self, path_to_db_file):
        if os.path.exists(path_to_db_file):
            desc_file = open(path_to_db_file, 'r')
            lines = desc_file.readlines()
            binaries = []
            descriptions = []
            d_array = {}
            i = -1
            while i < len(lines)-1:
                i+=1
                lines[i].strip
                # if the line is commented out, let's ignore it.
                if lines[i][0] == '#':
                    continue
                cur_line = lines[i].split(":")
                # if there is more than one semi-colon involved, this line is no good.
                if len(cur_line) != 4 and len(cur_line) != 2:
                    continue
                # create a redundant entry, so that every key,value pair also has a value, key pair.
                binary = cur_line[0].strip()
                desc = cur_line[1].strip()
                d_array[binary] = desc
                # the value to a desc key is a list. it may be a triple, it may not be.
                d_array[desc] = [binary]
                if len(cur_line) == 4:
                    seq_start = cur_line[2].strip()
                    seq_stop = cur_line[3].strip()
                    d_array[desc].append(seq_start)
                    d_array[desc].append(seq_stop)
        else:
            raise RuntimeError, "%s does not exist" % (path_to_array_file)
        return d_array
# Shutdown
#TODO: need to have it actually output to file.
    def write_definitions(self, path_to_db_file):
        daemons = self.intersection(os.listdir(daemon_path), self.desc_dict.keys())
	output_file = open(path_to_db_file, 'w')
	for daemon in daemons:	
	    list = self.desc_dict[self.desc_dict[daemon]]
	    line = daemon + ':' + self.desc_dict[daemon]
	    if len(list) == 3:
	        line += ':'+list[1]+':'+list[2]
	    output_file.write(line+"\n")
	output_file.close()
        return
#Helpers
    def handle_service(self, service, action):
        invoke_path = "/usr/sbin/invoke-rc.d"
        ret = subprocess.call(invoke_path +" " + service + " " + action, shell=True)
        return ret

    def is_autostart(self, iter):
        return self.autostarted.has_key(self.desc_dict[self.liststore1.get_value(iter, 1)][0])

    def is_running(self, iter):
        binary = self.desc_dict[self.liststore1.get_value(iter, 1)][0]
        path = running_daemon_path + binary
        return os.path.exists(path) or os.path.exists(path+".pid")

    def log(self, log_message, verbose):
        if verbose:
            print log_message
        log_file = open(log_file_path, 'a')
        log_file.write(log_message+'\n')
        log_file.close()
#TODO: actually remove the symlink once we log the information on it
# add a symlink Kxx to 2 and remove the symlink Sxx if there is one in 2.
    def handle_remove_symlink(self, service_list):
        startup_daemons = os.listdir(startup_daemon_path)
        for daemon in startup_daemons:
            if daemon.find(service_list[0]) == 3:
                if len(service_list) == 1:
                    service_list.append("")
		    service_list.append("")
                if daemon[0] == 'S':
                    service_list[1] = daemon[1:3]
		    os.unlink(startup_daemon_path+daemon)
                    # remove this and add a Kxx symlink.
                if daemon[0] == 'K':
                    service_list[2] = daemon[1:3]
	if service_list[2] != "":
	    k_sequence = 'K'+service_list[2]
	else:
	    k_sequence = 'K00'
	link_to = startup_daemon_path + k_sequence + service_list[0]
	link_from = daemon_path + service_list[0]
	if not os.path.exists(link_to):
	    os.symlink(link_from, link_to)
	del self.autostarted[service_list[0]]
        return
        

# Adds an S symlink and removes the K symlink (saves the values of the K symlink)
    def handle_add_symlink(self, service_list):
	service = service_list[0]
	if len(service_list) == 3 and service_list[1] != "":
	    s_sequence = 'S'+str(service_list[1])
	else:
	    s_sequence = 'S20'
        link_to = startup_daemon_path+s_sequence+service
	link_from = daemon_path + service
	if not os.path.exists(link_to):
            os.symlink(link_from, link_to)
	if len(service_list) == 1:
	    service_list.append("")
	    service_list.append("")
	for daemon in os.listdir(startup_daemon_path):
	    if service == daemon[3:] and daemon[0] == 'K':
		service_list[2] = daemon[1:3]
		os.unlink(startup_daemon_path+daemon)
	self.autostarted[service_list[0]] = 1
	return
     
    def intersection(self, list1, list2):
        int_dict = {}
        list1_dict = {}
        for x in list1:
            list1_dict[x] = 1
        for x in list2:
            if list1_dict.has_key(x):
                int_dict[x] = 1
        return int_dict

#Callbacks
    # callback for toggled auto-start
    # when we click it, we should either remove the symlink or create one
    # in init.d/rc2
    def toggled_cb(self, cell, path, user_data):
        model = user_data
        model[path][2] = not model[path][2]
        service_list = self.desc_dict[model[path][1]]

	# from true to false
        if cell.get_property('active'):
            self.handle_remove_symlink(service_list)
            cell.set_property('active', 0)
        # from false to true
        else:
            self.handle_add_symlink(service_list)    
	    cell.set_property('active', 1)
        # LOG
        self.log(str("toggled "+ service + " " + cell.get_property('active')), VERBOSE)
	return

    def active_cb(self, path, unused):
        iter = self.liststore1.get_iter(path)
        if self.is_running(iter):
            self.start_button.set_property('sensitive', False)
            self.stop_button.set_property('sensitive', True)
        else:
            self.start_button.set_property('sensitive', True)
            self.stop_button.set_property('sensitive', False)
        return True

#Signal Handlers
    def delete_event(self, obj):
        self.write_definitions(d_def_output_file)
        gtk.main_quit()
    # start a service using init invoke.
    def start_service(self, obj):
        model, iter = self.treeselection1.get_selected()
        if iter != None:
            
	    service = self.desc_dict[self.liststore1.get_value(iter, 1)][0]
            # LOG
            self.log(str("Starting "+ service), VERBOSE)
            ret = self.handle_service(service, "start")
            if (ret == 0):
                self.start_button.set_property('sensitive', False)
                self.stop_button.set_property('sensitive', True)
                self.liststore1.set_value(iter, 0, gtk.STOCK_YES)
                # LOG
                self.log(str(service) + " successfully started (" + str(ret) + ")", VERBOSE)
            else:
                # LOG
                self.log("EE:" + service + " unsuccessfully started (" + str(ret) + ")", VERBOSE)

    # stop a service using init invoke.
    def stop_service(self, obj):
        model, iter = self.treeselection1.get_selected()
        if iter != None:
            service = self.desc_dict[self.liststore1.get_value(iter, 1)][0]
            # LOG
            self.log("Stopping " + service, VERBOSE)
	    ret = self.handle_service(service, "stop")
            if (ret == 0):
                self.start_button.set_property('sensitive', True)
                self.stop_button.set_property('sensitive', False)
                self.liststore1.set_value(iter, 0, gtk.STOCK_NO)
                # LOG
                self.log(str(service) + " successfully stopped (" + str(ret) + ")", VERBOSE)
            else:
                # LOG
                self.log("EE:" + service + " unsuccessfully stopped (" + str(ret) + ")", VERBOSE)
        
    def connect_handlers(self):
        # connect signal handlers. kinda.
        dic = { "on_window1_destroy" : self.delete_event }
        self.xml.signal_autoconnect(dic)
        dic = { "on_close_button_clicked" : self.delete_event }
        self.xml.signal_autoconnect(dic)
        dic = { "on_start_button_clicked" : self.start_service }
        self.xml.signal_autoconnect(dic)
        dic = { "on_stop_button_clicked" : self.stop_service }
        self.xml.signal_autoconnect(dic)
        return

def main():
    gtk.main()
if __name__ == "__main__":
    driver = ServiceGUI()
    main()


