Viewing file: pup (22.32 KB) -rwxr-xr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) | #!/usr/bin/python -tt
#
# Copyright 2004-2007 Red Hat, Inc.
#
# Jeremy Katz
# Paul Nasrat
# Luke Macken
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
import os
# # Python gettext
# import gettext
import string
import subprocess
import webbrowser
from optparse import OptionParser
import gtk
import gtk.glade
import gtk.gdk as gdk
import gobject
import pango
import yum.Errors
from yum.constants import *
import rpmUtils.miscutils
from yum.update_md import UpdateMetadata, UpdateNoticeException
from rhpl.exception import installExceptionHandler
from rhpl.translate import _, N_, textdomain, utf8
from pirut import *
from pirut.constants import *
from pirut.Errors import *
# # Python gettext
# t = gettext.translation(I18N_DOMAIN, "/usr/share/locale", fallback = True)
# _ = t.lgettext
class PackageUpdater(GraphicalYumBase):
def __init__(self, interactive = True, config = None):
self.interactive = interactive
if os.path.exists("data/pup.glade"):
fn = "data/pup.glade"
else:
fn = "/usr/share/pirut/ui/pup.glade"
self.pupxml = gtk.glade.XML(fn, domain="pirut")
self.mainwin = self.pupxml.get_widget("pupWindow")
self.mainwin.set_icon_from_file(PIRUTPIX + "pup.png")
# self.mainwin.set_icon_name("system-software-update")
self.vpaned = self.pupxml.get_widget("vpaned1")
self.details = self.pupxml.get_widget("updateDetails")
self.expander = self.pupxml.get_widget("detailsExpander")
self.scratchBuffer = gtk.TextBuffer()
self.updateMetadata = UpdateMetadata()
self._connectSignals()
self._createUpdateStore()
self.mainwin.connect("delete_event", self.quit)
self.pupMenu = self.pupxml.get_widget("pupMenu")
self.registered = False
# note that nothing which takes "time" should be called here!
GraphicalYumBase.__init__(self, False, config)
def _connectSignals(self):
sigs = {"on_quitButton_clicked": self.quit,
"on_pupWindow_delete": self.quit,
"on_applyButton_clicked": self._apply,
"on_updateList_button_press": self._updateButtonPress,
"on_updateList_popup_menu": self._updatePopup,
"on_updateNotebook_scroll_event": self._notebookScroll,
"on_refreshButton_clicked": self.doRefresh,
"on_pupMenu_select": self._selectPackages,
"on_pupMenu_unselect": self._unselectPackages }
self.pupxml.signal_autoconnect(sigs)
self.details.set_buffer(gtk.TextBuffer())
self.details.connect("event-after", UpdateDetails.event_after)
# FIXME: figure out why this event only gets called when your cursor
# enters and leaves the TextView (making it impossible to change the
# cursor when hovering over a link)
#self.details.connect("motion-notify-event",
# UpdateDetails.motion_notify_event)
def _createUpdateStore(self):
# checkbox, display string, list of
# (updateFunc, printFunc, new, old, notice) tuples
self.store = gtk.TreeStore(gobject.TYPE_BOOLEAN,
gobject.TYPE_STRING,
gobject.TYPE_PYOBJECT,
gobject.TYPE_STRING,
gobject.TYPE_PYOBJECT)
tree = self.pupxml.get_widget("updateList")
tree.set_model(self.store)
column = gtk.TreeViewColumn(None, None)
column.set_clickable(True)
column.set_spacing(6)
pixr = gtk.CellRendererPixbuf()
pixr.set_property('stock-size', 1)
column.pack_start(pixr, False)
column.add_attribute(pixr, 'stock-id', 3)
cbr = gtk.CellRendererToggle()
cbr.connect ("toggled", self._toggledUpdate)
column.pack_start(cbr, False)
column.add_attribute(cbr, 'active', 0)
tree.append_column(column)
renderer = gtk.CellRendererText()
column = gtk.TreeViewColumn('Text', renderer, text=1)
tree.append_column(column)
tree.columns_autosize()
tree.connect ("row-activated", self._rowToggled)
self.store.set_sort_column_id(1, gtk.SORT_ASCENDING)
self.details.set_buffer(self.scratchBuffer)
selection = tree.get_selection()
selection.connect("changed", self._updateSelected)
selection.set_mode(gtk.SELECTION_MULTIPLE)
tree.set_search_equal_func(self.__search_pkgs)
def __search_pkgs(self, model, col, key, i):
lst = model.get_value(i, 2)
if len(lst) < 1:
return True
(updf, strf, new, old) = lst[0]
val = new.returnSimple('sourcerpm')
if val.lower().startswith(key.lower()):
return False
return True
def _rowToggled(self, tree, path, col):
self._toggledUpdate(None, path[0])
def _toggledUpdate(self, data, row):
i = self.store.get_iter((int(row),))
val = self.store.get_value(i, 0)
self.store.set_value(i, 0, not val)
def _updateSelected(self, selection):
if selection.count_selected_rows() != 1:
self.details.get_buffer().set_text("")
return
(model, paths) = selection.get_selected_rows()
i = model.get_iter(paths[0])
lst = model.get_value(i, 2)
notice = model.get_value(i, 4)
if notice:
self.details.set_buffer(notice)
return
# FIXME: if we have other indicators, this won't work anymore :-P
needsReboot = False
if model.get_value(i, 3) is not None:
needsReboot = True
strs = []
for (updfunc, strfunc, new, old) in lst:
md = self.updateMetadata.get_notice((new.name,new.ver,new.rel))
if md: # use the update metadata
details = UpdateDetails(md.get_metadata(), needsReboot)
self.details.set_buffer(details)
model.set_value(i, 4, details)
return
else: # use the predefined strfunc
strs.append(strfunc(new, old))
self.scratchBuffer.set_text(string.join(strs, '\n'))
if needsReboot:
tag = self.scratchBuffer.create_tag(weight=pango.WEIGHT_BOLD)
theiter = self.scratchBuffer.get_end_iter()
self.scratchBuffer.insert(theiter, "\n\n")
self.scratchBuffer.insert_with_tags(theiter,
_("This update will require a reboot."),
tag)
self.details.set_buffer(self.scratchBuffer)
def _changeSelected(self, state):
tree = self.pupxml.get_widget("updateList")
sel = tree.get_selection()
if sel.count_selected_rows() < 0:
return
(model, paths) = sel.get_selected_rows()
for p in paths:
i = model.get_iter(p)
model.set_value(i, 0, state)
def _selectPackages(self, *args):
self._changeSelected(True)
def _unselectPackages(self, *args):
self._changeSelected(False)
def __doUpdatePopup(self, button, time):
menu = self.pupMenu
menu.popup(None, None, None, button, time)
menu.show_all()
def _updateButtonPress(self, widget, event):
if event.button == 3:
x = int(event.x)
y = int(event.y)
pthinfo = widget.get_path_at_pos(x, y)
if pthinfo is not None:
sel = widget.get_selection()
if sel.count_selected_rows() == 1:
path, col, cellx, celly = pthinfo
widget.grab_focus()
widget.set_cursor(path, col, 0)
self.__doUpdatePopup(event.button, event.time)
return 1
def _updatePopup(self, widget):
sel = widget.get_selection()
if sel.count_selected_rows() > 0:
self.__doUpdatePopup(0, 0)
# block mouse scroll scrolling tabs
def _notebookScroll(self, *args):
pass
def _runGtkmain(self, *args):
while gtk.events_pending():
gtk.main_iteration()
def _busyCursor(self):
self.mainwin.window.set_cursor(gdk.Cursor(gdk.WATCH))
self.mainwin.set_sensitive(False)
self._runGtkmain()
def _normalCursor(self):
self.mainwin.window.set_cursor(None)
self.mainwin.set_sensitive(True)
self._runGtkmain()
def doRefresh(self, *args):
self.mainwin.show()
pbar = self.doRefreshRepos(destroy=False)
# FIXME: this should call real repo config code that let's you
# generically add repos and would pluggably call rhn_register.
# but, for now, we just need something that works
if not self.registered and len(self.repos.listEnabled()) == 0:
self.registered = True
if os.path.exists("/etc/sysconfig/rhn") and os.path.exists("/usr/sbin/rhn_register"):
def checkRegister(p): # when rhn_register exits, refresh us
if p.poll() is not None:
gobject.idle_add(self.doRefresh)
return False
return True
pbar.destroy()
self._normalCursor()
self.mainwin.hide()
self._runGtkmain()
p = subprocess.Popen(["/usr/sbin/rhn_register"],
close_fds = True)
gobject.timeout_add(2 * 1000, checkRegister, p)
self.doUpdateSetup()
pbar.next_task()
self.populateUpdates()
pbar.next_task()
self._normalCursor()
pbar.destroy()
def _doUpdate(self, new, old):
self.tsInfo.addUpdate(new, old)
def _doObsolete(self, new, old):
self.tsInfo.addObsoleting(new, old)
self.tsInfo.addObsoleted(old, new)
def _printUpdate(self, new, old):
return _("%s updates %s") %(new, old)
def _printObsolete(self, new, old):
return _("%s obsoletes %s") %(new, old)
def populateUpdates(self):
self.store.clear()
upds = {}
reboots = {}
repos = []
# handle obsoletes
opt = self.conf.obsoletes
if opt:
obsoletes = self.up.getObsoletesTuples(newest=1)
else:
obsoletes = []
for (obs, inst) in obsoletes:
obsoleting = self.getPackageObject(obs)
installed = self.rpmdb.searchPkgTuple(inst)[0]
srpm = obsoleting.returnSimple("sourcerpm")
if upds.has_key(srpm):
upds[srpm].append( (self._doObsolete, self._printObsolete,
obsoleting, installed) )
else:
upds[srpm] = [ (self._doObsolete, self._printObsolete,
obsoleting, installed) ]
reboots[srpm] = False
if obsoleting.returnSimple("name") in rebootpkgs:
reboots[srpm] = True
# and updates
updates = self.up.getUpdatesTuples()
for (new, old) in updates:
updating = self.getPackageObject(new)
updated = self.rpmdb.searchPkgTuple(old)[0]
# populate update metadata
if not updating.repoid in repos:
repo = self.repos.getRepo(updating.repoid)
try: # attempt to grab the updateinfo.xml.gz from the repodata
self.updateMetadata.add(repo)
except yum.Errors.RepoMDError:
pass # No metadata found for this repo
except UpdateNoticeException:
pass # Metadata parsing error
repos.append(updating.repoid)
srpm = updating.returnSimple("sourcerpm")
if upds.has_key(srpm):
upds[srpm].append( (self._doUpdate, self._printUpdate,
updating, updated) )
else:
upds[srpm] = [ (self._doUpdate, self._printUpdate,
updating, updated) ]
reboots[srpm] = False
if updating.returnSimple("name") in rebootpkgs:
reboots[srpm] = True
for (srpm, lst) in upds.items():
if reboots[srpm]:
pix = 'gtk-refresh'
else:
pix = None
self.store.append(None, [True,
_("Updated %s packages available")
% (rpmUtils.miscutils.splitFilename(srpm)[0],),
lst, pix, None])
if len(upds) == 0:
self.pupxml.get_widget("updateNotebook").set_current_page(1)
self.pupxml.get_widget("applyButton").set_sensitive(False)
def _apply(self, *args):
needReboot = False
map(lambda x: self.tsInfo.remove(x.pkgtup), self.tsInfo)
self.tsInfo.makelists()
del self.ts
self.initActionTs()
# select packages that are chosen
for row in self.store:
(on, pkgstr, lst, pix, bar) = row
if on:
for (updfunc, strfunc, new, old) in lst:
if new.name in rebootpkgs:
needReboot = True
updfunc(new, old)
if len(self.tsInfo) <= 0:
d = gtk.MessageDialog(self.mainwin, gtk.DIALOG_MODAL,
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
_("No packages were selected for upgrade."))
d.show_all()
d.run()
d.destroy()
return
try:
output = self.applyChanges(self.mainwin)
except PirutError:
return
# this is a little tricky and bears some explanation
# 1) if there's no warning output and we don't need to reboot, show
# a simple "success!" dialog.
# 2) if there's no warning output and we need to reboot, only show
# the "reboot recommended" dialog
# 3) if there's warning output _and_ we need to reboot, we need
# to show as two dialogs to avoid confusion
d = PirutDetailsDialog(self.mainwin, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE,
_("Software update successfully completed."))
if output:
d.format_secondary_text("Some warnings were seen during update.")
d.set_details(buffer = outputDictAsTextBuffer(output))
if not needReboot or output:
d.set_buttons(gtk.BUTTONS_OK)
rc = d.run()
d.destroy()
if needReboot:
d = PirutDetailsDialog(self.mainwin, gtk.MESSAGE_INFO,
gtk.BUTTONS_NONE,
_("Reboot recommended"))
d.format_secondary_text(_("Due to the updates installed, it is recommended that you reboot your system. You can either reboot now or choose to do so at a later time."))
d.set_buttons([(_("Reboot _later"), gtk.RESPONSE_CANCEL),
(_("_Reboot now"), gtk.RESPONSE_DELETE_EVENT)])
d.set_default_response(gtk.RESPONSE_CANCEL)
rc = d.run()
if rc == gtk.RESPONSE_DELETE_EVENT:
d.hide()
subprocess.call(["/sbin/shutdown", "-r", "now"])
self.quit()
d.destroy()
# and we're done
self.quit()
def run(self):
self.mainwin.show()
self._runGtkmain()
self.doRefresh()
if not self.interactive:
gobject.idle_add(self._apply)
gtk.main()
hovering_over_link = False
hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
class UpdateDetails(gtk.TextBuffer):
def __init__(self, metadata, needsReboot = False):
gtk.TextBuffer.__init__(self)
self.md = metadata
self.needsReboot = needsReboot
self.iter = self.get_start_iter()
self._build_tags()
self._parse_references()
self._populate_details()
def _build_tags(self):
self.bold_tag = self.create_tag(weight=pango.WEIGHT_BOLD)
self.title_tag = self.create_tag(font='DejaVu LGC Sans Mono Bold')
self.title_tag.set_property('foreground', 'white')
self.title_tag.set_property('background-gdk',
gtk.gdk.color_parse('#CCCCCC'))
def _parse_references(self):
self.cves = []
self.bzs = []
for ref in self.md['references']:
type = ref['type']
if type == 'cve':
self.cves.append((ref['id'], ref['href']))
elif type == 'bugzilla':
self.bzs.append((ref['id'], ref['href']))
def _populate_details(self):
titlecol_width = 12
margin = ''.zfill(titlecol_width + 1).replace('0', ' ')
def _add_item(title, field=None):
title = title.zfill(titlecol_width).replace('0', ' ')
self.insert_with_tags(self.iter, '%s ' % title, self.title_tag)
if field:
self.insert_with_tags(self.iter, ' %s\n' % self.md[field])
_add_item('ID', 'update_id')
_add_item('Type', 'type')
_add_item('Status', 'status')
_add_item('Issued', 'issued')
# Append the references
for title, list, lmt in (('Bugs', self.bzs, 6), ('CVEs', self.cves, 4)):
if len(list) == 0:
continue
title = title.zfill(titlecol_width).replace('0', ' ')
self.insert_with_tags(self.iter, '%s ' % title, self.title_tag)
i = 0
for id, url in list:
self.insert(self.iter, ' %s' % id)
# Disable linking bugs and CVEs until we figure out a way to
# NOT launch firefox as root (Bug #216552)
#self._insert_link(id, url)
#self.insert(self.iter, ' ')
i += 1
if i % lmt == 0: # allow lmt references per line
self.insert(self.iter, '\n')
self.insert_with_tags(self.iter, margin, self.title_tag)
self.insert(self.iter, '\n')
if self.md['description']:
desc = 'Description'.zfill(titlecol_width).replace('0', ' ')
self.insert_with_tags(self.iter, desc + ' ', self.title_tag)
lines = self.md['description'].split('\n')
self.insert(self.iter, ' %s' % lines[0])
for line in lines[1:]:
self.insert(self.iter, '\n')
self.insert_with_tags(self.iter, margin, self.title_tag)
self.insert(self.iter, ' ' + line)
if self.needsReboot:
self.insert(self.iter, "\n")
self.insert_with_tags(self.iter,
_("This update will require a reboot."),
self.bold_tag)
def _insert_link(self, text, url):
tag = self.create_tag(underline=pango.UNDERLINE_SINGLE,
foreground='blue')
tag.set_data('page', url)
self.insert(self.iter, ' ')
self.insert_with_tags(self.iter, text, tag)
@staticmethod
def event_after(view, event):
""" Callback to monitor mouse clicks and handle links. """
if event.type != gtk.gdk.BUTTON_RELEASE or event.button != 1:
return False
# don't follow a link if the user has selected something
bounds = view.get_buffer().get_selection_bounds()
if len(bounds) != 0:
return False
x, y = view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
int(event.x), int(event.y))
iter = view.get_iter_at_location(x, y)
for tag in iter.get_tags():
page = tag.get_data('page')
if page:
webbrowser.open(page, new=True)
## FIXME: figure out why this method is only getting called when the
## cursor enters or leaves the TextView.
@staticmethod
def motion_notify_event(view, event):
""" Callback to monitor mouse motion and change the cursor if it is
hovering over a link.
"""
print "motion_notify_event"
global hovering_over_link
hovering = False
x, y = view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
int(event.x), int(event.y))
iter = view.get_iter_at_location(x, y)
for tag in iter.get_tags():
page = tag.get_data('page')
if page:
print "Hovering == True"
hovering = True
break
if hovering != hovering_over_link:
print "Changing cursor"
hovering_over_link = hovering
win = view.get_window(gtk.TEXT_WINDOW_WIDGET)
win.set_cursor(hovering_over_link and hand_cursor or regular_cursor)
def main():
textdomain("pirut")
parser = OptionParser()
parser.add_option("-a", "--apply", action="store_true",
dest="autoapply",
help="Automatically apply updates")
parser.add_option("-c", "--config", type="string",
dest="config", default="/etc/yum.conf",
help="Config file to use (default: /etc/yum.conf)")
(options,args) = parser.parse_args()
gtk.glade.bindtextdomain("pirut", "/usr/share/locale")
try:
# right now, we have to run privileged...
if os.getuid() != 0:
raise PirutError(_("Must be run as root."))
pup = PackageUpdater(not options.autoapply, options.config)
except PirutError, e:
print e
startupError(e)
pup.run()
if __name__ == "__main__":
installExceptionHandler("pirut", "")
main()
|