1 #!/usr/bin/python2.4
   2 #
   3 # CDDL HEADER START
   4 #
   5 # The contents of this file are subject to the terms of the
   6 # Common Development and Distribution License (the "License").
   7 # You may not use this file except in compliance with the License.
   8 #
   9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10 # or http://www.opensolaris.org/os/licensing.
  11 # See the License for the specific language governing permissions
  12 # and limitations under the License.
  13 #
  14 # When distributing Covered Code, include this CDDL HEADER in each
  15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16 # If applicable, add the following below this CDDL HEADER, with the
  17 # fields enclosed by brackets "[]" replaced with your own identifying
  18 # information: Portions Copyright [yyyy] [name of copyright owner]
  19 #
  20 # CDDL HEADER END
  21 #
  22 # Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  23 # Use is subject to license terms.
  24 #
  25 
  26 import errno
  27 import gettext # XXX Temporary workaround
  28 import itertools
  29 import os
  30 import sys
  31 from threading import Thread
  32 from urllib2 import URLError
  33 try:
  34         import gobject
  35         import gtk
  36         import gtk.glade
  37         import pygtk
  38         pygtk.require("2.0")
  39 except ImportError:
  40         sys.exit(1)
  41 import pkg.client.bootenv as bootenv
  42 import pkg.client.imageplan as imageplan
  43 import pkg.client.progress as progress
  44 import pkg.fmri as fmri
  45 import pkg.indexer as indexer
  46 import pkg.search_errors as search_errors
  47 from pkg.misc import TransferTimedOutException
  48 from pkg.misc import CLIENT_DEFAULT_MEM_USE_KB
  49 import pkg.gui.enumerations as enumerations
  50 import pkg.gui.filelist as filelist
  51 import pkg.gui.thread as guithread
  52 from pkg.gui.filelist import CancelException
  53 
  54 class InstallUpdate(progress.ProgressTracker):
  55         def __init__(self, install_list, parent, \
  56             image_update = False, ips_update = False):
  57                 # XXX Workaround as BE is using msg(_("message")) 
  58                 # which bypasses the self._ mechanism the GUI is using
  59                 gettext.install("pkg","/usr/lib/locale")
  60                 progress.ProgressTracker.__init__(self)
  61                 self.gui_thread = guithread.ThreadRun()
  62                 self.install_list = install_list
  63                 self.update_list = None
  64                 self.parent = parent
  65                 self.image_update = image_update
  66                 self.ips_update = ips_update
  67                 self.ip = None
  68                 w_tree_createplan = gtk.glade.XML(parent.gladefile, "createplandialog")
  69                 w_tree_installupdate = gtk.glade.XML(parent.gladefile, "installupdate")
  70                 w_tree_downloadingfiles = \
  71                     gtk.glade.XML(parent.gladefile, "downloadingfiles")
  72                 w_tree_installingdialog = \
  73                     gtk.glade.XML(parent.gladefile, "installingdialog") 
  74                 w_tree_networkdown = gtk.glade.XML(parent.gladefile, "networkdown")
  75                 self.w_createplan_dialog = \
  76                     w_tree_createplan.get_widget("createplandialog")                    
  77                 self.w_createplan_progressbar = \
  78                     w_tree_createplan.get_widget("createplanprogress") 
  79                 self.w_createplan_textview = \
  80                     w_tree_createplan.get_widget("createplantextview")
  81                 self.w_installupdate_dialog = w_tree_installupdate.get_widget("installupdate")
  82                 self.w_summary_label = w_tree_installupdate.get_widget("packagenamelabel3")
  83                 self.w_review_treeview = w_tree_installupdate.get_widget("treeview1")
  84                 self.w_downloadingfiles_dialog = \
  85                     w_tree_downloadingfiles.get_widget("downloadingfiles")
  86                 self.w_download_textview = \
  87                     w_tree_downloadingfiles.get_widget("downloadtextview")
  88                 self.w_download_progressbar = \
  89                     w_tree_downloadingfiles.get_widget("downloadprogress")
  90                 self.w_installing_dialog = \
  91                     w_tree_installingdialog.get_widget("installingdialog")
  92                 self.w_installing_textview = \
  93                     w_tree_installingdialog.get_widget("installingtextview")
  94                 self.w_installing_progressbar = \
  95                     w_tree_installingdialog.get_widget("installingprogress")
  96                 self.w_networkdown_dialog = w_tree_networkdown.get_widget("networkdown")
  97                 self.w_createplan_progressbar.set_pulse_step(0.1)
  98                 installed_updated_column = gtk.TreeViewColumn('Installed/Updated')
  99                 self.w_review_treeview.append_column(installed_updated_column)
 100                 cell = gtk.CellRendererText()
 101                 installed_updated_column.pack_start(cell, True)
 102                 installed_updated_column.add_attribute(cell, 'text', 0)
 103                 self.w_review_treeview.expand_all()
 104                 try:
 105                         dic_createplan = \
 106                             {
 107                                 "on_cancelcreateplan_clicked": \
 108                                     self.__on_cancelcreateplan_clicked,
 109                             }
 110                         dic_installupdate = \
 111                             {
 112                                 "on_cancel_button_clicked": \
 113                                     self.__on_cancel_button_clicked,
 114                                 "on_next_button_clicked":self.__on_next_button_clicked,
 115                             }
 116                         dic_downloadingfiles = \
 117                             {
 118                                 "on_canceldownload_clicked": \
 119                                     self.__on_cancel_download_clicked,
 120                             }
 121                         dic_networkdown = \
 122                             {
 123                                 "on_networkdown_close_clicked": \
 124                                     self.__on_networkdown_close_clicked,
 125                             }
 126                         w_tree_createplan.signal_autoconnect(dic_createplan)
 127                         w_tree_installupdate.signal_autoconnect(dic_installupdate)
 128                         w_tree_downloadingfiles.signal_autoconnect(dic_downloadingfiles)
 129                         w_tree_networkdown.signal_autoconnect(dic_networkdown)
 130                 except AttributeError, error:
 131                         print self.parent._('GUI will not respond to any event! %s. \
 132                             Check installupdate.py signals') \
 133                             % error
 134                 if image_update or ips_update:
 135                         list_of_packages = install_list
 136                 else:
 137                         list_of_packages = self.__prepare_list_of_packages()
 138                 thread = Thread(target = self.__plan_the_install_updateimage, \
 139                     args = (list_of_packages, ))
 140                 thread.start()
 141                 self.w_createplan_dialog.run()
 142 
 143         def __on_cancelcreateplan_clicked(self, widget):
 144                 '''Handler for signal send by cancel button, which user might press during
 145                 evaluation stage - while the dialog is creating plan'''
 146                 self.gui_thread.cancel()
 147                 self.w_createplan_dialog.destroy()
 148 
 149         def __on_cancel_button_clicked(self, widget):
 150                 '''Handler for signal send by cancel button, which is available for the 
 151                 user after evaluation stage on the dialog showing what will be installed
 152                 or updated'''
 153                 self.gui_thread.cancel()
 154                 self.w_installupdate_dialog.destroy()
 155 
 156         def __on_next_button_clicked(self, widget):
 157                 '''Handler for signal send by next button, which is available for the 
 158                 user after evaluation stage on the dialog showing what will be installed
 159                 or updated'''
 160                 download_thread = Thread(target = self.__download_stage, args = ())
 161                 download_thread.start()
 162 
 163         def __on_cancel_download_clicked(self, widget):
 164                 '''Handler for signal send by cancel button, which user might press during
 165                 download stage.'''
 166                 self.gui_thread.cancel()
 167                 self.w_downloadingfiles_dialog.destroy()
 168 
 169         def __on_networkdown_close_clicked(self, widget):
 170                 '''Handler for signal send by close button on the dialog showing that
 171                 there was some problem with the network connection.'''
 172                 self.gui_thread.cancel()
 173                 self.w_networkdown_dialog.destroy()
 174 
 175         def __update_createplan_progress(self, action):
 176                 buf = self.w_createplan_textview.get_buffer()
 177                 textiter = buf.get_end_iter()
 178                 buf.insert(textiter, action)
 179                 self.w_createplan_progressbar.pulse()
 180 
 181         def __update_download_progress(self, cur_bytes, total_bytes):
 182                 prog = float(cur_bytes)/total_bytes
 183                 self.w_download_progressbar.set_fraction(prog)
 184                 a = str(cur_bytes/1024)
 185                 b = str(total_bytes/1024)
 186                 c = "Downloaded: " + a + " / " + b + " KB"
 187                 self.w_download_progressbar.set_text(c)
 188 
 189         def __update_install_progress(self, current, total):
 190                 prog = float(current)/total
 191                 self.w_installing_progressbar.set_fraction(prog)
 192 
 193         def __prepare_list_of_packages(self):
 194                 ''' This method return the dictionary of 
 195                 images and newest marked packages'''
 196                 fmri_to_install_update = {}
 197                 for row in self.install_list:
 198                         if row[enumerations.MARK_COLUMN]:
 199                                 image = row[enumerations.IMAGE_OBJECT_COLUMN]
 200                                 packages = row[enumerations.PACKAGE_OBJECT_COLUMN]
 201                                 im = fmri_to_install_update.get(image)
 202                                 # XXX Hack to be bug to bug compatible - incorporations
 203                                 pkg_name = packages[0].get_name()
 204                                 if im:
 205                                         im.append(pkg_name)
 206                                 else:
 207                                         fmri_to_install_update[image] = [pkg_name, ]
 208                 return fmri_to_install_update
 209 
 210         def __plan_the_install_updateimage(self, list_of_packages):
 211                 '''Function which plans the image'''
 212                 self.gui_thread.run()
 213                 filters = []
 214                 verbose = False
 215                 noexecute = False
 216                 for image in list_of_packages:
 217                         # Take a list of packages, specified in pkg_list, and attempt
 218                         # to assemble an appropriate image plan.  This is a helper
 219                         # routine for some common operations in the client.
 220                         #
 221                         # This method checks all authorities for a package match;
 222                         # however, it defaults to choosing the preferred authority
 223                         # when an ambiguous package name is specified.  If the user
 224                         # wishes to install a package from a non-preferred authority,
 225                         # the full FMRI that contains an authority should be used
 226                         # to name the package.
 227 
 228                         pkg_list = list_of_packages.get(image)
 229 
 230                         error = 0
 231                         self.ip = imageplan.ImagePlan(image, self, filters = filters)
 232 
 233                         self.__load_optional_dependencies(image)
 234 
 235                         for p in pkg_list:
 236                                 try:
 237                                         conp = image.apply_optional_dependencies(p)
 238                                         matches = list(image.inventory([ conp ],
 239                                             all_known = True))
 240                                 except RuntimeError:
 241                                         # XXX Module directly printing.
 242                                         error = 1
 243                                         continue
 244 
 245                                 pnames = {}
 246                                 pmatch = []
 247                                 npnames = {}
 248                                 npmatch = []
 249                                 for m, state in matches:
 250                                         if m.preferred_authority():
 251                                                 pnames[m.get_pkg_stem()] = 1
 252                                                 pmatch.append(m)
 253                                         else:
 254                                                 npnames[m.get_pkg_stem()] = 1
 255                                                 npmatch.append(m)
 256 
 257                                 if len(pnames.keys()) > 1:
 258                                         # XXX Module directly printing.
 259                                         error = 1
 260                                         continue
 261                                 elif len(pnames.keys()) < 1 and len(npnames.keys()) > 1:
 262                                         # XXX Module directly printing.
 263                                         error = 1
 264                                         continue
 265 
 266                                 # matches is a list reverse sorted by version, so take
 267                                 # the first; i.e., the latest.
 268                                 if len(pmatch) > 0:
 269                                         self.ip.propose_fmri(pmatch[0])
 270                                 else:
 271                                         self.ip.propose_fmri(npmatch[0])
 272 
 273                         if error != 0:
 274                                 raise RuntimeError, "Unable to assemble image plan"
 275 
 276 
 277                         self.__evaluate(image)
 278 
 279                         image.imageplan = self.ip
 280 
 281                         gobject.idle_add(self.ip.progtrack.evaluate_done)
 282 
 283                 return
 284 
 285         def __load_optional_dependencies(self, image):
 286                 for b_fmri in image.gen_installed_pkgs():
 287                         if self.gui_thread.is_cancelled():
 288                                 return
 289                         mfst = image.get_manifest(b_fmri, filtered = True)
 290 
 291                         for dep in mfst.gen_actions_by_type("depend"):
 292                                 required, min_fmri, max_fmri = dep.parse(image)
 293                                 if required == False:
 294                                         image.update_optional_dependency(min_fmri)
 295 
 296         def __evaluate(self, image):
 297                 assert self.ip.state == imageplan.UNEVALUATED
 298 
 299                 self.ip.progtrack.evaluate_start()
 300 
 301                 outstring = ""
 302                 
 303                 # Operate on a copy, as it will be modified in flight.
 304                 for f in self.ip.target_fmris[:]:
 305                         self.ip.progtrack.evaluate_progress()
 306                         try:
 307                                 self.__evaluate_fmri(f, image)
 308                         except KeyError, e:
 309                                 outstring += "Attemping to install %s causes:\n\t%s\n" % \
 310                                     (f.get_name(), e)
 311                         except NameError:
 312                                 gobject.idle_add(self.__creating_plan_net_error)
 313                                 return
 314                 if outstring:
 315                         raise RuntimeError("No packages were installed because "
 316                             "package dependencies could not be satisfied\n" +
 317                             outstring)                        
 318                                 
 319                 for f in self.ip.target_fmris:
 320                         self.ip.add_pkg_plan(f)
 321                         self.ip.progtrack.evaluate_progress()
 322 
 323                 for f in self.ip.target_rem_fmris[:]:
 324                         self.ip.evaluate_fmri_removal(f)
 325                         self.ip.progtrack.evaluate_progress()
 326 
 327                 self.ip.progtrack.evaluate_done()
 328 
 329                 self.ip.state = imageplan.EVALUATED_OK
 330 
 331         def __creating_plan_net_error(self):
 332                 '''Helper method which shows the dialog informing user that there was
 333                 problem with network connection'''
 334                 self.w_createplan_dialog.hide()
 335                 self.w_networkdown_dialog.show()
 336 
 337         def __evaluate_fmri(self, pfmri, image):
 338 
 339                 if self.gui_thread.is_cancelled():
 340                         return
 341                 gobject.idle_add(self.__update_createplan_progress, \
 342                     self.parent._("Evaluating: %s\n") % pfmri.get_fmri())
 343 
 344                 self.ip.progtrack.evaluate_progress()
 345                 m = image.get_manifest(pfmri)
 346 
 347                 # [manifest] examine manifest for dependencies
 348                 for a in m.actions:
 349                         if a.name != "depend":
 350                                 continue
 351 
 352                         type = a.attrs["type"]
 353 
 354                         f = fmri.PkgFmri(a.attrs["fmri"],
 355                             self.ip.image.attrs["Build-Release"])
 356 
 357                         if self.ip.image.has_version_installed(f) and \
 358                                     type != "exclude":
 359                                 continue
 360 
 361                         # XXX This alone only prevents infinite recursion when a
 362                         # cycle member is on the commandline, as we never update
 363                         # target_fmris.  Is target_fmris supposed to be just
 364                         # what was specified on the commandline, or include what
 365                         # we've found while processing dependencies?
 366                         # XXX probably should just use propose_fmri() here
 367                         # instead of this and the has_version_installed() call
 368                         # above.
 369                         if self.ip.is_proposed_fmri(f):
 370                                 continue
 371 
 372                         # XXX LOG  "%s not in pending transaction;
 373                         # checking catalog" % f
 374 
 375                         required = True
 376                         excluded = False
 377                         if type == "optional" and \
 378                             not self.ip.image.attrs["Policy-Require-Optional"]:
 379                                 required = False
 380                         elif type == "transfer" and \
 381                             not self.ip.image.older_version_installed(f):
 382                                 required = False
 383                         elif type == "exclude":
 384                                 excluded = True
 385                         elif type == "incorporate":
 386                                 self.ip.image.update_optional_dependency(f)
 387                                 if self.ip.image.older_version_installed(f) or \
 388                                     self.ip.older_version_proposed(f):
 389                                         required = True
 390                                 else:
 391                                         required = False
 392 
 393                         if not required:
 394                                 continue
 395 
 396                         if excluded:
 397                                 raise RuntimeError, "excluded by '%s'" % f
 398 
 399                         # treat-as-required, treat-as-required-unless-pinned,
 400                         # ignore
 401                         # skip if ignoring
 402                         #     if pinned
 403                         #       ignore if treat-as-required-unless-pinned
 404                         #     else
 405                         #       **evaluation of incorporations**
 406                         #     [imageplan] pursue installation of this package
 407                         #     -->
 408                         #     backtrack or reset??
 409 
 410                         # This will be the newest version of the specified
 411                         # dependency package, coming from the preferred
 412                         # authority, if it's available there.
 413                         cf = self.ip.image.inventory([ a.attrs["fmri"] ],
 414                             all_known = True, preferred = True,
 415                             first_only = True).next()[0]
 416 
 417                         # XXX LOG "adding dependency %s" % pfmri
 418 
 419                         #msg("adding dependency %s" % cf)
 420 
 421                         self.ip.propose_fmri(cf)
 422                         self.__evaluate_fmri(cf, image)
 423 
 424         def __download_stage(self, rebuild=False):
 425                 '''Parts of the code duplicated from install and image-update from pkg(1) 
 426                 and pkg.client.ImagePlan.preexecute()'''
 427                 self.gui_thread.run()
 428                 if not rebuild:
 429                         self.w_installupdate_dialog.hide()
 430                 if rebuild:
 431                         self.w_installing_dialog.hide()
 432                 self.w_downloadingfiles_dialog.show()
 433 
 434                 # Checks the index to make sure it exists and is
 435                 # consistent. If it's inconsistent an exception is thrown.
 436                 # If it's totally absent, it will index the existing packages
 437                 # so that the incremental update that follows at the end of
 438                 # the function will work correctly.
 439                 self.ip.image.update_index_dir()
 440                 ind = indexer.Indexer(self.ip.image.index_dir,
 441                     CLIENT_DEFAULT_MEM_USE_KB, progtrack=self.ip.progtrack)
 442                 ind.check_index(self.ip.image.get_fmri_manifest_pairs(),
 443                     force_rebuild=False)
 444                 
 445                 for package_plan in self.ip.pkg_plans:
 446                         if self.gui_thread.is_cancelled():
 447                                 return
 448                         try:
 449                                 self.__preexecute(package_plan)
 450                         except TransferTimedOutException:
 451                                 self.w_downloadingfiles_dialog.hide()
 452                                 self.w_networkdown_dialog.show()
 453                                 return
 454                         except URLError, e:
 455                                 #if e.reason[0] == 8:
 456                                 self.w_downloadingfiles_dialog.hide()
 457                                 self.w_networkdown_dialog.show()
 458                                 return
 459                         except CancelException:
 460                                 self.w_downloadingfiles_dialog.hide()
 461                                 return
 462 
 463                 self.ip.progtrack.download_done()
 464                 self.w_downloadingfiles_dialog.hide()
 465 
 466                 try:
 467                         be = bootenv.BootEnv(self.ip.image.get_root())
 468                 except RuntimeError:
 469                         be = bootenv.BootEnvNull(self.ip.image.get_root())
 470 
 471                 if self.image_update:
 472                         be.init_image_recovery(self.ip.image)
 473                         if self.ip.image.is_liveroot():
 474                                 return 1
 475                 try:
 476                         self.__installation_stage()
 477                         if self.image_update:
 478                                 be.activate_image()
 479                         else:
 480                                 be.activate_install_uninstall()
 481                         ret_code = 0
 482                 except RuntimeError, e:
 483                         if self.image_update:
 484                                 be.restore_image()
 485                         else:
 486                                 be.restore_install_uninstall()
 487                         ret_code = 1
 488                 except search_errors.InconsistentIndexException, e:
 489                         ret_code = 2
 490                 except search_errors.PartialIndexingException, e:
 491                         ret_code = 2
 492                 except search_errors.ProblematicPermissionsIndexException, e:
 493                         ret_code = 2
 494                 except Exception, e:
 495                         if self.image_update:
 496                                 be.restore_image()
 497                         else:
 498                                 be.restore_install_uninstall()
 499                         self.ip.image.cleanup_downloads()
 500                         raise
 501 
 502                 self.ip.image.cleanup_downloads()
 503                 if ret_code == 0:
 504                         self.ip.image.cleanup_cached_content()
 505                 elif ret_code == 2:
 506                         return_code = 0
 507                         return_code = self.__rebuild_index()
 508                         if return_code == 1:
 509                                 return
 510                         self.__download_stage(True)
 511 
 512         def __preexecute(self, package_plan):
 513                 '''Code duplication from pkg.client.PkgPlan.preexecute() except that
 514                 pkg.gui.filelist is called instead of pkg.client.fileobject with shared
 515                 cancel object - self.gui_thread that allows to cancel download 
 516                 operation'''
 517                 flist = filelist.FileList(self,
 518                     package_plan.image,
 519                     package_plan.destination_fmri,
 520                     self.gui_thread,
 521                     maxbytes = None
 522                     )
 523                 _PkgPlan__prog = package_plan._PkgPlan__progtrack
 524                 _PkgPlan__prog.download_start_pkg(package_plan.get_xfername())
 525 
 526                 # retrieval step
 527                 if package_plan.destination_fmri == None:
 528                         package_plan.image.remove_install_file(package_plan.origin_fmri)
 529 
 530                         try:
 531                                 os.unlink("%s/pkg/%s/filters" % (
 532                                     package_plan.image.imgdir,
 533                                     package_plan.origin_fmri.get_dir_path()))
 534                         except EnvironmentError, e:
 535                                 if e.errno != errno.ENOENT:
 536                                         raise
 537 
 538                 for src, dest in itertools.chain(*package_plan.actions):
 539                         if dest:
 540                                 dest.preinstall(package_plan, src)
 541                                 if dest.needsdata(src):
 542                                         flist.add_action(dest)
 543                         else:
 544                                 src.preremove(package_plan)
 545 
 546                 # Tell flist to get any remaining files
 547                 flist.flush()
 548                 package_plan._PkgPlan__progtrack.download_end_pkg()
 549 
 550         def __rebuild_index(self, pargs):
 551                 '''Code duplication from pkg(1):
 552                        Forcibly rebuild the search indexes. Will remove existing indexes
 553                        and build new ones from scratch.'''
 554                 quiet = False
 555                 
 556                 try:
 557                         self.ip.image.rebuild_search_index(self.ip.image.progtrack)
 558                 except search_errors.InconsistentIndexException, iie:
 559                         return 1
 560                 except search_errors.ProblematicPermissionsIndexException, ppie:
 561                         return 1
 562 
 563         def __installation_stage(self):
 564                 self.gui_thread.run()
 565                 self.w_installing_dialog.show()
 566                 self.ip.state = imageplan.PREEXECUTED_OK
 567 
 568                 if self.ip.nothingtodo():
 569                         self.ip.state = imageplan.EXECUTED_OK
 570                         self.ip.progtrack.actions_done()
 571                         return
 572 
 573                 actions = [ (p, src, dest)
 574                             for p in self.ip.pkg_plans
 575                             for src, dest in p.gen_removal_actions()
 576                             ]
 577 
 578                 actions.sort(key = lambda obj:obj[1], reverse=True)
 579 
 580                 self.ip.progtrack.actions_set_goal("Removal Phase", len(actions))
 581                 for p, src, dest in actions:
 582                         p.execute_removal(src, dest)
 583                         self.ip.progtrack.actions_add_progress()
 584                         
 585                 # generate list of update actions, sort and execute
 586 
 587                 update_actions = [ (p, src, dest)
 588                             for p in self.ip.pkg_plans
 589                             for src, dest in p.gen_update_actions()
 590                             ]
 591 
 592                 install_actions = [ (p, src, dest)
 593                             for p in self.ip.pkg_plans
 594                             for src, dest in p.gen_install_actions()
 595                             ]
 596 
 597                 # move any user/group actions into modify list to
 598                 # permit package to add user/group and change existing
 599                 # files to that user/group in a single update
 600                 # iterate over copy since we're modify install_actions
 601 
 602                 for a in install_actions[:]:
 603                         if a[2].name == "user" or a[2].name == "group":
 604                                 update_actions.append(a)
 605                                 install_actions.remove(a)
 606 
 607                 update_actions.sort(key = lambda obj:obj[2])
 608 
 609                 self.ip.progtrack.actions_set_goal("Update Phase", len(update_actions))
 610 
 611                 for p, src, dest in update_actions:
 612                         p.execute_update(src, dest)
 613                         self.ip.progtrack.actions_add_progress()
 614 
 615                 # generate list of install actions, sort and execute
 616 
 617                 install_actions.sort(key = lambda obj:obj[2])
 618 
 619                 self.ip.progtrack.actions_set_goal("Install Phase", len(install_actions))
 620 
 621                 for p, src, dest in install_actions:
 622                         p.execute_install(src, dest)
 623                         self.ip.progtrack.actions_add_progress()
 624 
 625                 # handle any postexecute operations
 626 
 627                 for p in self.ip.pkg_plans:
 628                         p.postexecute()
 629 
 630                 self.ip.state = imageplan.EXECUTED_OK
 631                 
 632                 del actions
 633                 del update_actions
 634                 del install_actions
 635                 del self.ip.target_rem_fmris
 636                 del self.ip.target_fmris
 637                 del self.ip.directories
 638                 
 639                 # Perform the incremental update to the search indexes
 640                 # for all changed packages
 641                 plan_info = []
 642                 for p in self.ip.pkg_plans:
 643                         d_fmri = p.destination_fmri
 644                         d_manifest_path = None
 645                         if d_fmri:
 646                                 d_manifest_path = \
 647                                     self.ip.image.get_manifest_path(d_fmri)
 648                         o_fmri = p.origin_fmri
 649                         o_manifest_path = None
 650                         o_filter_file = None
 651                         if o_fmri:
 652                                 o_manifest_path = \
 653                                     self.ip.image.get_manifest_path(o_fmri)
 654                         plan_info.append((d_fmri, d_manifest_path, o_fmri,
 655                                           o_manifest_path))
 656                 self.update_list = self.ip.pkg_plans[:]
 657                 del self.ip.pkg_plans
 658 
 659                 self.ip.progtrack.actions_set_goal("Index Phase", len(plan_info))
 660 
 661                 self.ip.image.update_index_dir()
 662                 ind = indexer.Indexer(self.ip.image.index_dir,
 663                     CLIENT_DEFAULT_MEM_USE_KB, progtrack=self.ip.progtrack)
 664                 ind.client_update_index((self.ip.filters, plan_info))
 665                 
 666                 self.ip.progtrack.actions_done()
 667 
 668         def actions_done(self):
 669                 if self.parent != None:
 670                         if not self.ips_update and not self.image_update:
 671                                 gobject.idle_add(self.__update_package_list)
 672                 gobject.idle_add(self.w_installing_dialog.hide)
 673 
 674                 if self.ips_update:
 675                         gobject.idle_add(self.parent.shutdown_after_ips_update)
 676                 elif self.image_update:
 677                         gobject.idle_add(self.parent.shutdown_after_image_update)
 678 
 679         def __update_package_list(self):
 680                 for pkg in self.update_list:
 681                         pkg_name = pkg.get_xfername()
 682                         self.__update_install_list(pkg_name)
 683                 del self.update_list
 684                 self.parent.update_package_list()
 685                         
 686         def __update_install_list(self, pkg_name):
 687                 for row in self.install_list:
 688                         if row[enumerations.NAME_COLUMN] == pkg_name:
 689                                 row[enumerations.MARK_COLUMN] = True
 690                                 return
 691 
 692         def download_file_path(self, file_path):
 693                 '''Called by GUI's filelist.py through the progress, which is passed 
 694                 to the filelist.'''
 695                 # XXX this function should be removed and also pkg.gui.filelist should 
 696                 # not call it, since we don't want to show single file progress
 697                 gobject.idle_add(self.__add_file_to_downloadtext, file_path)
 698 
 699         def __add_file_to_downloadtext(self, file_path):
 700                 '''Function which adds another line text in the "more details" download 
 701                 dialog'''
 702                 buf = self.w_download_textview.get_buffer()
 703                 textiter = buf.get_end_iter()
 704                 buf.insert(textiter, self.parent._("Downloading: ") + file_path + "\n")
 705 
 706         def __add_info_to_installtext(self, text):
 707                 '''Function which adds another line text in the "more details" install 
 708                 dialog'''
 709                 buf = self.w_installing_textview.get_buffer()
 710                 textiter = buf.get_end_iter()
 711                 buf.insert(textiter, text)
 712 
 713         def cat_output_start(self): 
 714                 return
 715 
 716         def cat_output_done(self): 
 717                 return
 718 
 719         def eval_output_start(self):
 720                 '''Called by progress tracker when the evaluation of the packages just 
 721                 started.'''
 722                 return
 723 
 724         def eval_output_progress(self):
 725                 '''Called by progress tracker each time some package was evaluated. The
 726                 call is being done by calling progress tracker evaluate_progress() 
 727                 function'''
 728                 return
 729 
 730         def eval_output_done(self):
 731                 '''Called by progress tracker after the evaluation of the packages is 
 732                 finished. Gets information like how many packages will be 
 733                 updated/installed and maximum amount of data which will be downloaded. 
 734                 Later this information is being adjusted, while downloading'''
 735                 self.w_createplan_dialog.hide()
 736                 if self.gui_thread.is_cancelled():
 737                         return
 738                 updated_installed = \
 739                     [
 740                         ["Packages To Be Installed:"],
 741                         ["Packages To Be Updated:"]
 742                     ]
 743                 treestore = gtk.TreeStore(str)
 744                 install_iter = None 
 745                 updated_iter = None
 746                 install_count = 0
 747                 updated_count = 0
 748                 total_download_count = 0
 749                 total_files_count = 0
 750                 npkgs = 0
 751                 for package_plan in self.ip.pkg_plans:
 752                         npkgs += 1
 753                         if package_plan.origin_fmri and package_plan.destination_fmri:
 754                                 if not updated_iter:
 755                                         updated_iter = treestore.append(None, \
 756                                             updated_installed[1])
 757                                 d_fmri = package_plan.destination_fmri
 758                                 dt = self.get_datetime(d_fmri.version)
 759                                 dt_str = (":%02d%02d") % (dt.month, dt.day)
 760                                 pkg_version = d_fmri.version.get_short_version() + dt_str
 761                                 pkg = d_fmri.get_name() + "@" + pkg_version
 762                                 updated_count = updated_count + 1
 763                                 treestore.append(updated_iter, [pkg])
 764                         elif package_plan.destination_fmri:
 765                                 if not install_iter:
 766                                         install_iter = treestore.append(None, \
 767                                             updated_installed[0])
 768                                 d_fmri = package_plan.destination_fmri
 769                                 dt = self.get_datetime(d_fmri.version)
 770                                 dt_str = (":%02d%02d") % (dt.month, dt.day)
 771                                 pkg_version = d_fmri.version.get_short_version() + dt_str
 772                                 pkg = d_fmri.get_name() + "@" + pkg_version
 773                                 install_count = install_count + 1
 774                                 treestore.append(install_iter, [pkg])
 775                         xferfiles, xfersize = package_plan.get_xferstats()
 776                         total_download_count = total_download_count + xfersize
 777                         total_files_count = total_files_count + xferfiles
 778                 self.ip.progtrack.download_set_goal(npkgs, total_files_count, \
 779                     total_download_count)
 780                 self.w_review_treeview.set_model(treestore)
 781                 self.w_review_treeview.expand_all()
 782                 updated_str = self.parent._("%d packages will be updated\n")
 783                 if updated_count == 1:
 784                         updated_str = self.parent._("%d package will be updated\n")
 785                 install_str = self.parent._("%d packages will be installed\n\n")
 786                 if install_count == 1:
 787                         install_str = self.parent._("%d package will be installed\n\n")
 788                 self.w_summary_label.set_text((updated_str + install_str + \
 789                     self.parent._("%d MB will be downloaded"))% \
 790                     (updated_count, install_count, (total_download_count/1024/1024)))
 791                 self.w_createplan_dialog.hide()
 792                 self.w_installupdate_dialog.show()
 793                 return True
 794 
 795         def ver_output(self): 
 796                 return
 797 
 798         def ver_output_error(self, actname, errors): 
 799                 return
 800 
 801         def dl_output(self):
 802                 gobject.idle_add(self.__update_download_progress, self.dl_cur_nbytes, \
 803                     self.dl_goal_nbytes)
 804                 return
 805 
 806         def dl_output_done(self):
 807                 return
 808 
 809         def act_output(self):
 810                 gobject.idle_add(self.__update_install_progress, \
 811                     self.act_cur_nactions, self.act_goal_nactions)
 812                 return
 813 
 814         def act_output_done(self):
 815                 return
 816 
 817         def ind_output(self):
 818                 return
 819 
 820         def ind_output_done(self):
 821                 return
 822 
 823         @staticmethod
 824         def get_datetime(version):
 825                 '''Support function for change in the IPS API: get_timestamp() was
 826                 replaced by get_datetime()'''
 827                 dt = None
 828                 try:
 829                         dt = version.get_datetime()
 830                 except AttributeError:
 831                         dt = version.get_timestamp()
 832                 return dt
 833