--- /dev/null 2008-09-09 04:55:19.000000000 +0100 +++ new/src/gui/modules/beadmin.py 2008-09-09 04:55:19.693940528 +0100 @@ -0,0 +1,510 @@ +#!/usr/bin/python2.4 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +import sys +import subprocess +import pango +import time +import datetime +from threading import Thread + +try: + import gobject + gobject.threads_init() + import gtk + import gtk.glade + import pygtk + pygtk.require("2.0") +except ImportError: + sys.exit(1) + +nobe = False + +try: + import libbe as be +except ImportError: + # All actions are disabled when libbe can't be imported. + nobe = True + +class Beadmin: + def __init__(self, parent): + if nobe: + msg = self.parent._("The libbe library was not " + \ + "found on your system." + \ + "\nAll functions for managing Boot Environments are disabled") + msgbox = gtk.MessageDialog( + buttons = gtk.BUTTONS_CLOSE, \ + flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_INFO, \ + message_format = None) + msgbox.set_markup(msg) + msgbox.set_title(self.parent._("BE management")) + result = msgbox.run() + msgbox.destroy() + return + + self.parent = parent + + self.be_list = \ + gtk.ListStore( + gobject.TYPE_INT, # BE_ID + gobject.TYPE_BOOLEAN, # BE_MARK + gobject.TYPE_STRING, # BE_NAME + gobject.TYPE_STRING, # BE_DATE_TIME + gtk.gdk.Pixbuf, # BE_CURRENT + gobject.TYPE_BOOLEAN, # BE_DEFAULT + gobject.TYPE_STRING, # BE_SIZE + ) + self.progress_stop_thread = False + self.initial_active = 0 + self.initial_default = 0 + w_tree_beadmin = gtk.glade.XML(parent.gladefile, "beadmin") + w_tree_progress = gtk.glade.XML(parent.gladefile, "progressdialog") + w_tree_beconfirmation = gtk.glade.XML(parent.gladefile, \ + "beconfirmationdialog") + self.w_beadmin_dialog = w_tree_beadmin.get_widget("beadmin") + self.w_be_treeview = w_tree_beadmin.get_widget("betreeview") + self.w_cancel_button = w_tree_beadmin.get_widget("cancelbebutton") + self.w_reset_button = w_tree_beadmin.get_widget("resetbebutton") + w_active_gtkimage = w_tree_beadmin.get_widget("activebeimage") + self.w_progress_dialog = w_tree_progress.get_widget("progressdialog") + self.w_progressinfo_label = w_tree_progress.get_widget("progressinfo") + progress_button = w_tree_progress.get_widget("progresscancel") + self.w_progressbar = w_tree_progress.get_widget("progressbar") + self.w_beconfirmation_dialog = \ + w_tree_beconfirmation.get_widget("beconfirmationdialog") + self.w_beconfirmation_treeview = \ + w_tree_beconfirmation.get_widget("beconfirmtreeview") + self.w_beconfirmationdefault_label = \ + w_tree_beconfirmation.get_widget("beconfirmationdefault") + self.w_beconfirmationsummary_label = \ + w_tree_beconfirmation.get_widget("beconfirmationsummary") + self.w_cancelbe_button = w_tree_beconfirmation.get_widget("cancel_be") + progress_button.hide() + self.w_progressbar.set_pulse_step(0.1) + self.list_filter = self.be_list.filter_new() + self.w_be_treeview.set_model(self.list_filter) + self.__init_tree_views() + treestore = gtk.ListStore(gobject.TYPE_STRING) + self.w_beconfirmation_treeview.set_model(treestore) + cell = gtk.CellRendererText() + be_column = gtk.TreeViewColumn('BE', cell, text = 0) + self.w_beconfirmation_treeview.append_column(be_column) + self.active_image = self.parent.get_icon_pixbuf("status_checkmark") + w_active_gtkimage.set_from_pixbuf(self.active_image) + + try: + dic = \ + { + "on_cancel_be_clicked": \ + self.__on_cancel_be_clicked, + "on_reset_be_clicked": \ + self.__on_reset_be_clicked, + "on_ok_be_clicked": \ + self.__on_ok_be_clicked, + } + dic_conf = \ + { + "on_cancel_be_conf_clicked": \ + self.__on_cancel_be_conf_clicked, + "on_ok_be_conf_clicked": \ + self.__on_ok_be_conf_clicked, + } + w_tree_beadmin.signal_autoconnect(dic) + w_tree_beconfirmation.signal_autoconnect(dic_conf) + except AttributeError, error: + print self.parent._('GUI will not respond to any event! %s. \ + Check beadmin.py signals') \ + % error + Thread(target = self.__progress_pulse).start() + Thread(target = self.__prepare_beadmin_list).start() + sel = self.w_be_treeview.get_selection() + self.w_cancel_button.grab_focus() + sel.set_mode(gtk.SELECTION_NONE) + sel = self.w_beconfirmation_treeview.get_selection() + sel.set_mode(gtk.SELECTION_NONE) + self.w_beadmin_dialog.show_all() + self.w_progress_dialog.set_title(\ + self.parent._("Loading Boot Environment Information")) + self.w_progressinfo_label.set_text(\ + self.parent._("Fetching BE entries...")) + self.w_progress_dialog.show() + + def __progress_pulse(self): + while not self.progress_stop_thread: + gobject.idle_add(self.w_progressbar.pulse) + time.sleep(0.1) + gobject.idle_add(self.w_progress_dialog.hide) + + def __prepare_beadmin_list(self): + be_list = be.beList() + gobject.idle_add(self.__create_view_with_be, be_list) + self.progress_stop_thread = True + return + + def __init_tree_views(self): + model = self.w_be_treeview.get_model() + + column = gtk.TreeViewColumn() + column.set_title("") + render_pixbuf = gtk.CellRendererPixbuf() + column.pack_start(render_pixbuf, expand = True) + column.add_attribute(render_pixbuf, "pixbuf", 4) # BE_CURRENT + self.w_be_treeview.append_column(column) + + name_renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn(self.parent._("Boot Environment"), \ + name_renderer, text = 2) # 2 = BE_NAME + column.set_cell_data_func(name_renderer, self.__cell_data_function, None) + column.set_expand(True) + self.w_be_treeview.append_column(column) + + datetime_renderer = gtk.CellRendererText() + datetime_renderer.set_property('xalign', 0.0) + column = gtk.TreeViewColumn(self.parent._("Created"), datetime_renderer, \ + text = 3) # 3 = BE_DATE_TIME + column.set_cell_data_func(datetime_renderer, \ + self.__cell_data_function, None) + column.set_expand(True) + self.w_be_treeview.append_column(column) + + size_renderer = gtk.CellRendererText() + size_renderer.set_property('xalign', 0.0) + column = gtk.TreeViewColumn(self.parent._("Size"), size_renderer, \ + text = 6) # 6 = BE_SIZE + column.set_cell_data_func(size_renderer, self.__cell_data_function, None) + column.set_expand(False) + self.w_be_treeview.append_column(column) + + radio_renderer = gtk.CellRendererToggle() + radio_renderer.connect('toggled', self.__active_pane_default, model) + column = gtk.TreeViewColumn(self.parent._("Active on Reboot"), \ + radio_renderer, active = 5) # 1 = BE_MARK + radio_renderer.set_property("activatable", True) + radio_renderer.set_property("radio", True) + column.set_cell_data_func(radio_renderer, \ + self.__cell_data_default_function, None) + column.set_expand(False) + self.w_be_treeview.append_column(column) + + toggle_renderer = gtk.CellRendererToggle() + toggle_renderer.connect('toggled', self.__active_pane_toggle, model) + column = gtk.TreeViewColumn(self.parent._("Delete"), toggle_renderer, \ + active = 1) # 1 = BE_MARK + toggle_renderer.set_property("activatable", True) + column.set_cell_data_func(toggle_renderer, \ + self.__cell_data_delete_function, None) + column.set_expand(False) + self.w_be_treeview.append_column(column) + + def __on_reset_be_clicked(self, widget): + self.be_list.clear() + self.w_progress_dialog.show() + self.progress_stop_thread = False + Thread(target = self.__progress_pulse).start() + Thread(target = self.__prepare_beadmin_list).start() + self.__enable_disable_reset() + + def __on_ok_be_clicked(self, widget): + self.w_progress_dialog.set_title(self.parent._("Applying changes")) + self.w_progressinfo_label.set_text(\ + self.parent._("Applying changes, please wait ...")) + if self.w_reset_button.get_property('sensitive') == 0: + self.progress_stop_thread = True + self.__on_beadmin_delete_event(None, None) + return + Thread(target = self.__activate).start() + + def __on_cancel_be_clicked(self, widget): + self.__on_beadmin_delete_event(None, None) + + def __on_cancel_be_conf_clicked(self, widget): + self.w_beconfirmation_dialog.hide() + + def __on_ok_be_conf_clicked(self, widget): + self.w_beconfirmation_dialog.hide() + self.progress_stop_thread = False + Thread(target = self.__on_progressdialog_progress).start() + Thread(target = self.__delete_activate_be).start() + + def __on_beadmin_delete_event(self, widget, event): + self.w_beadmin_dialog.destroy() + return True + + def __activate(self): + to_delete = [] + default = None + treestore = self.w_beconfirmation_treeview.get_model() + treestore.clear() + first = True + for row in self.be_list: + if row[1]: + treestore.append([row[2]]) + if first: + self.w_beconfirmation_treeview.set_sensitive(True) + first = False + if row[5] == True and row[0] != self.initial_default: + default = row[2] + summary_text = "" + be_change_no = len(treestore) + if be_change_no == 0: + treestore.append([self.parent._("No change")]) + self.w_beconfirmation_treeview.set_sensitive(False) + else: + summary_text += \ + self.parent._("%d BE's will be deleted") % be_change_no + + if default: + self.w_beconfirmationdefault_label.set_text(default+"\n") + self.w_beconfirmationdefault_label.set_sensitive(True) + if be_change_no > 0: + summary_text += "\n" + summary_text += \ + self.parent._("Default BE will be changed upon reboot") + else: + self.w_beconfirmationdefault_label.set_sensitive(False) + self.w_beconfirmationdefault_label.set_text(\ + self.parent._("No change\n")) + self.w_beconfirmationsummary_label.set_text(summary_text) + self.w_beconfirmation_treeview.expand_all() + self.w_cancelbe_button.grab_focus() + self.w_beconfirmation_dialog.show() + self.progress_stop_thread = True + + def __on_progressdialog_progress(self): + # This needs to be run in gobject.idle_add, otherwise we will get + # Xlib: unexpected async reply (sequence 0x2db0)! + gobject.idle_add(self.w_progress_dialog.show) + while not self.progress_stop_thread: + gobject.idle_add(self.w_progressbar.pulse) + time.sleep(0.1) + gobject.idle_add(self.w_progress_dialog.hide) + + def __delete_activate_be(self): + not_deleted = [] + not_default = None + for row in self.be_list: + if row[1]: + succeed = self.__destroy_be(row[2]) + if succeed == 1: + not_deleted.append(row[2]) + for row in self.be_list: + if row[5] == True and row[0] != self.initial_default: + succeed = self.__set_default_be(row[2]) + if succeed == 1: + not_default = row[2] + if len(not_deleted) == 0 and not_default == None: + self.progress_stop_thread = True + else: + self.progress_stop_thread = True + msg = "" + if not_default: + msg += self.parent._("Couldn't change Default " + \ + "Boot Environment to:\n") + not_default + if len(not_deleted) > 0: + if not_default: + msg+= "\n\n" + msg += self.parent._("Couldn't delete Boot " + \ + "Environments:\n") + for row in not_deleted: + msg += row + "\n" + gobject.idle_add(self.__error_occured, msg) + return + self.__on_beadmin_delete_event(None, None) + + def __error_occured(self, error_msg): + msg = error_msg + msgbox = gtk.MessageDialog(parent = self.w_beadmin_dialog, \ + buttons = gtk.BUTTONS_CLOSE, \ + flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_ERROR, \ + message_format = None) + msgbox.set_markup(msg) + msgbox.set_title("BE error") + result = msgbox.run() + msgbox.destroy() + self.__on_reset_be_clicked(None) + + def __active_pane_toggle(self, cell, filtered_path, filtered_model): + model = filtered_model.get_model() + path = filtered_model.convert_path_to_child_path(filtered_path) + itr = model.get_iter(path) + if itr: + modified = model.get_value(itr, 1) + model.set_value(itr, 1, \ + not modified) + self.__enable_disable_reset() + + def __enable_disable_reset(self): + for row in self.be_list: + if row[1] == True: + self.w_reset_button.set_sensitive(True) + return + self.initial_default + self.initial_active + if row[0] == self.initial_default: + if row[5] == False: + self.w_reset_button.set_sensitive(True) + return + self.w_reset_button.set_sensitive(False) + return + + def __active_pane_default(self, cell, filtered_path, filtered_model): + model = filtered_model.get_model() + path = filtered_model.convert_path_to_child_path(filtered_path) + for row in model: + row[5] = False + itr = model.get_iter(path) + if itr: + modified = model.get_value(itr, 5) + model.set_value(itr, 5, \ + not modified) + self.__enable_disable_reset() + + def __create_view_with_be(self, be_list): + dates = self.__get_dates_of_creation(be_list) + i = 0 + j = 0 + for bee in be_list: + if bee.get("orig_be_name"): + name = bee.get("orig_be_name") + active = bee.get("active") + active_boot = bee.get("active_boot") + be_size = bee.get("space_used") + converted_size = \ + self.__convert_size_of_be_to_string(be_size) + active_img = None + if dates: + try: + date_time = repr(dates[i])[1:-3] + date_tmp = time.strptime(date_time, \ + "%a %b %d %H:%M %Y") + date_tmp2 = \ + datetime.datetime(*date_tmp[0:5]) + date_time = \ + date_tmp2.strftime("%m/%d/%y %H:%M") + i += 1 + except: + date_time = None + else: + date_time = None + if active: + active_img = self.active_image + self.initial_active = j + if active_boot: + self.initial_default = j + self.be_list.insert(j, [j, False, \ + name, \ + date_time, active_img, \ + active_boot, converted_size]) + j += 1 + self.w_be_treeview.set_cursor(self.initial_active, None, \ + start_editing=True) + self.w_be_treeview.scroll_to_cell(self.initial_active) + + def __convert_size_of_be_to_string(self, be_size): + if not be_size: + return "0" + suffix = [self.parent._("bytes"), "K", "M", "G", "T"] + size = be_size + 0.0 + i = 0 + while size > 1024: + i += 1 + size = size / 1024 + if len(suffix) < i: + suf = "" + else: + suf = suffix[i] + return str(round(size,2)) + suf + + def __get_dates_of_creation(self, be_list): + #zfs list -H -o creation rpool/ROOT/opensolaris-1 + cmd = [ "/sbin/zfs", "list", "-H", "-o","creation" ] + for bee in be_list: + if bee.get("orig_be_name"): + name = bee.get("orig_be_name") + pool = bee.get("orig_be_pool") + cmd += [pool+"/ROOT/"+name] + if len(cmd) <= 5: + return None + list_of_dates = [] + try: + proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, \ + stderr = subprocess.PIPE,) + line_out = proc.stdout.readline() + while line_out: + list_of_dates.append(line_out) + line_out = proc.stdout.readline() + except OSError, e: + pass + return list_of_dates + + def __destroy_be(self, be_name): + cmd = [ "/sbin/beadm", "destroy", "-F", be_name ] + return self.__beadm_invoke_command(cmd) + + def __set_default_be(self, be_name): + cmd = [ "/sbin/beadm", "activate", be_name ] + return self.__beadm_invoke_command(cmd) + + def __beadm_invoke_command(self, cmd): + try: + # Subprocess platform specific, but beadm is only on Solars so we + # can use it... + stdouterr = open('/dev/null', 'w') + returncode = subprocess.call(cmd, stdout = stdouterr, \ + stderr = stdouterr,) + except OSError, e: + pass + return returncode + + def __cell_data_default_function(self, column, renderer, model, itr, data): + if itr: + if model.get_value(itr, 1): + self.__set_renderer_active(renderer, False) + else: + self.__set_renderer_active(renderer, True) + + def __cell_data_delete_function(self, column, renderer, model, itr, data): + if itr: + if model.get_value(itr, 5) or (self.initial_active == \ + model.get_value(itr, 0)): + self.__set_renderer_active(renderer, False) + else: + self.__set_renderer_active(renderer, True) + + + def __set_renderer_active(self, renderer, active): + if active: + renderer.set_property("sensitive", True) + renderer.set_property("mode", gtk.CELL_RENDERER_MODE_ACTIVATABLE) + else: + renderer.set_property("sensitive", False) + renderer.set_property("mode", gtk.CELL_RENDERER_MODE_INERT) + + def __cell_data_function(self, column, renderer, model, itr, data): + if itr: + if model.get_value(itr, 4): + renderer.set_property("weight", pango.WEIGHT_BOLD) + else: + renderer.set_property("weight", pango.WEIGHT_NORMAL)