--- /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)