qubes-installer-qubes-os/iw/task_gui.py
2011-01-18 04:24:57 -05:00

707 lines
25 KiB
Python

#
# task_gui.py: Choose tasks for installation
#
# Copyright (C) 2006, 2007, 2008 Red Hat, Inc. All rights reserved.
#
# 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; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import gtk
import gtk.glade
import gobject
import gui
import gzip
from iw_gui import *
from image import *
from constants import *
import isys
import shutil
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
import network
import iutil
from yuminstall import AnacondaYumRepo
import urlgrabber.grabber
import yum.Errors
import logging
log = logging.getLogger("anaconda")
def setupRepo(anaconda, repo):
if repo.needsNetwork() and not network.hasActiveNetDev():
if not anaconda.intf.enableNetwork():
return False
urlgrabber.grabber.reset_curl_obj()
try:
anaconda.backend.doRepoSetup(anaconda, thisrepo=repo.id, fatalerrors=False)
anaconda.backend.doSackSetup(anaconda, thisrepo=repo.id, fatalerrors=False)
log.info("added (UI) repository %s with source URL %s, id:%s" % (repo.name, repo.mirrorlist or repo.baseurl, repo.id))
# FIXME: need a per-repo way of doing this; largely cut and paste
# from yum right now
if not repo.groups_added:
try:
groupfile = repo.getGroups()
# open it up as a file object so iterparse can cope
# with our gz file
if groupfile is not None and groupfile.endswith('.gz'):
groupfile = gzip.open(groupfile)
anaconda.backend.ayum._comps.add(groupfile)
except Exception, e:
log.debug("unable to add group information for repository %s" %(repo.name))
else:
repo.groups_added = True
log.info("added group information for repository %s" %(repo.name))
except (IOError, yum.Errors.RepoError) as e:
anaconda.intf.messageWindow(_("Error"),
_("Unable to read package metadata from repository. "
"This may be due to a missing repodata directory. "
"Please ensure that your repository has been "
"correctly generated.\n\n%s" % str(e)),
type="ok", custom_icon="error")
repo.disable()
repo.close()
anaconda.backend.ayum.repos.delete(repo.id)
return False
return True
class RepoEditor:
# Window-level callbacks
def on_addRepoDialog_destroy(self, widget, *args):
pass
def on_cancelButton_clicked(self, widget, *args):
pass
def on_okButton_clicked(self, widget, *args):
pass
def on_typeComboBox_changed(self, widget, *args):
if widget.get_active() == -1:
return
# When the combo box's value is changed, set the notebook's current
# page to match. This requires that the combo box and notebook have
# the same method types at the same indices (so, HTTP must be the
# same position on both, etc.).
self.notebook.set_current_page(widget.get_active())
if widget.get_active() == 1:
if self.repo:
self.proxyCheckbox.set_active(self.repo.proxy is True)
self.proxyTable.set_sensitive(self.repo.proxy is True)
else:
self.proxyCheckbox.set_active(False)
self.proxyTable.set_sensitive(False)
# URL-specific callbacks
def on_proxyCheckbox_toggled(self, widget, *args):
table = self.dxml.get_widget("proxyTable")
table.set_sensitive(widget.get_active())
def on_mirrorlistCheckbox_toggled(self, widget, *args):
pass
def __init__(self, anaconda, repoObj):
self.anaconda = anaconda
self.backend = self.anaconda.backend
self.intf = self.anaconda.intf
self.repo = repoObj
(self.dxml, self.dialog) = gui.getGladeWidget("addrepo.glade", "addRepoDialog")
self.dxml.signal_autoconnect(self)
self.notebook = self.dxml.get_widget("typeNotebook")
self.nameEntry = self.dxml.get_widget("nameEntry")
self.typeComboBox = self.dxml.get_widget("typeComboBox")
self.baseurlEntry = self.dxml.get_widget("baseurlEntry")
self.mirrorlistCheckbox = self.dxml.get_widget("mirrorlistCheckbox")
self.proxyCheckbox = self.dxml.get_widget("proxyCheckbox")
self.proxyEntry = self.dxml.get_widget("proxyEntry")
self.proxyTable = self.dxml.get_widget("proxyTable")
self.usernameEntry = self.dxml.get_widget("usernameEntry")
self.passwordEntry = self.dxml.get_widget("passwordEntry")
self.nfsServerEntry = self.dxml.get_widget("nfsServerEntry")
self.nfsPathEntry = self.dxml.get_widget("nfsPathEntry")
self.nfsOptionsEntry = self.dxml.get_widget("nfsOptionsEntry")
self.partitionComboBox = self.dxml.get_widget("partitionComboBox")
self.directoryChooser = self.dxml.get_widget("directoryChooserButton")
self.dialog.set_title(_("Edit Repository"))
# Remove these until they are actually implemented
self.typeComboBox.remove_text(3)
# Given a method string, return the index of the typeComboBox that should
# be made active in order to match.
def _methodToIndex(self, method):
mapping = {"http": 0, "ftp": 0, "https": 0,
"cdrom": 1,
"nfs": 2}
# "nfs": 2, "nfsiso": 2,
# "hd": 3}
try:
return mapping[method.split(':')[0].lower()]
except:
return 0
def _addAndEnableRepo(self, repo):
try:
self.backend.ayum.repos.add(repo)
except yum.Errors.DuplicateRepoError, e:
self.intf.messageWindow(_("Error"),
_("The repository %s has already been added. Please "
"choose a different repository name and "
"URL.") % self.repo.name, type="ok", custom_icon="error")
return False
repo.enable()
return True
def _validURL(self, url):
return len(url) > 0 and (url.startswith("http://") or
url.startswith("https://") or
url.startswith("ftp://"))
def createDialog(self):
if self.repo:
self.nameEntry.set_text(self.repo.name)
if self.repo.anacondaBaseURLs:
url = self.repo.anacondaBaseURLs[0]
else:
url = ''
self.typeComboBox.set_active(self._methodToIndex(url))
if not url or url.startswith("http") or url.startswith("ftp"):
if self.repo.mirrorlist:
self.baseurlEntry.set_text(self.repo.mirrorlist)
self.mirrorlistCheckbox.set_active(True)
else:
self.baseurlEntry.set_text(url)
self.mirrorlistCheckbox.set_active(False)
if self.repo.proxy:
self.proxyCheckbox.set_active(True)
self.proxyTable.set_sensitive(True)
self.proxyEntry.set_text(self.repo.proxy)
self.usernameEntry.set_text(self.repo.proxy_username or '')
self.passwordEntry.set_text(self.repo.proxy_password or '')
else:
self.proxyCheckbox.set_active(False)
self.proxyTable.set_sensitive(False)
elif url.startswith("nfs"):
(opts, server, path) = iutil.parseNfsUrl(url)
self.nfsServerEntry.set_text(server)
self.nfsPathEntry.set_text(path)
self.nfsOptionsEntry.set_text(opts)
elif url.startswith("cdrom:"):
pass
elif url.startswith("hd:"):
m = url[3:]
if m.count(":") == 1:
(device, path) = m.split(":")
fstype = "auto"
else:
(device, fstype, path) = m.split(":")
# find device in self.partitionComboBox and select it
self.directoryChooser.set_current_folder("%s%s" % (self.anaconda.backend.ayum.isodir, path))
else:
self.baseurlEntry.set_text(url)
else:
self.typeComboBox.set_active(0)
self.proxyCheckbox.set_active(False)
self.proxyTable.set_sensitive(False)
gui.addFrame(self.dialog)
lbl = self.dxml.get_widget("descLabel")
txt = lbl.get_text()
lbl.set_text(txt)
self.dialog.show_all()
def _applyURL(self, repo):
if self.proxyCheckbox.get_active():
proxy = self.proxyEntry.get_text()
proxy.strip()
if not self._validURL(proxy):
self.intf.messageWindow(_("Invalid Proxy URL"),
_("You must provide an HTTP, HTTPS, "
"or FTP URL to a proxy."))
return False
repo.proxy = proxy
# with empty string yum would create invalid proxy string
repo.proxy_username = self.usernameEntry.get_text() or None
repo.proxy_password = self.passwordEntry.get_text() or None
repourl = self.baseurlEntry.get_text()
repourl.strip()
if not self._validURL(repourl):
self.intf.messageWindow(_("Invalid Repository URL"),
_("You must provide an HTTP, HTTPS, "
"or FTP URL to a repository."))
return False
if self.mirrorlistCheckbox.get_active():
repo.baseurl = []
repo.mirrorlist = repourl
else:
repo.baseurl = [repourl]
repo.mirrorlist = None
repo.anacondaBaseURLs = repo.baseurl
repo.name = self.nameEntry.get_text()
return True
def _applyMedia(self, repo):
# FIXME works only if storage has detected format of cdrom drive
ayum = self.anaconda.backend.ayum
cdr = scanForMedia(ayum.tree, self.anaconda.storage)
if not cdr:
self.intf.messageWindow(_("No Media Found"),
_("No installation media was found. "
"Please insert a disc into your drive "
"and try again."))
return False
log.info("found installation media on %s" % cdr)
repo.name = self.nameEntry.get_text()
repo.anacondaBaseURLs = ["cdrom://%s:%s" % (cdr, self.anaconda.backend.ayum.tree)]
repo.baseurl = "file://%s" % ayum.tree
ayum.mediagrabber = ayum.mediaHandler
self.anaconda.mediaDevice = cdr
ayum.currentMedia = 1
repo.mediaid = getMediaId(ayum.tree)
log.info("set mediaid of repo %s to: %s" % (repo.name, repo.mediaid))
return True
def _applyNfs(self, repo):
server = self.nfsServerEntry.get_text()
server.strip()
path = self.nfsPathEntry.get_text()
path.strip()
options = self.nfsOptionsEntry.get_text()
options.strip()
repo.name = self.nameEntry.get_text()
if not server or not path:
self.intf.messageWindow(_("Error"),
_("Please enter an NFS server and path."))
return False
if not network.hasActiveNetDev():
if not self.anaconda.intf.enableNetwork():
self.intf.messageWindow(_("No Network Available"),
_("Some of your software repositories require "
"networking, but there was an error enabling the "
"network on your system."))
return False
urlgrabber.grabber.reset_curl_obj()
import tempfile
dest = tempfile.mkdtemp("", repo.name.replace(" ", ""), "/mnt")
try:
isys.mount("%s:%s" % (server, path), dest, "nfs", options=options)
except Exception as e:
self.intf.messageWindow(_("Error Setting Up Repository"),
_("The following error occurred while setting up the "
"repository:\n\n%s") % e)
return False
repo.baseurl = "file://%s" % dest
repo.anacondaBaseURLs = ["nfs:%s:%s:%s" % (options,server,path)]
return True
def _applyHd(self, repo):
return True
def run(self):
applyFuncs = [ self._applyURL, self._applyMedia, self._applyNfs,
self._applyHd ]
while True:
rc = self.dialog.run()
if rc in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]:
break
reponame = self.nameEntry.get_text()
reponame.strip()
if len(reponame) == 0:
self.intf.messageWindow(_("Invalid Repository Name"),
_("You must provide a repository name."))
continue
# Always create a new repo object here instead of attempting to
# somehow expire the metadata and refetch. We'll just have to make
# sure that if we're just editing the repo, we grab all the
# attributes from the old one before deleting it.
if self.repo:
# use temporary id so that we don't get Duplicate Repo error
# when adding
newRepoObj = AnacondaYumRepo("UIedited_%s" %
self.anaconda.backend.ayum.repoIDcounter.next())
newRepoObj.cost = self.repo.cost
removeOld = True
else:
newRepoObj = AnacondaYumRepo(reponame.replace(" ", ""))
removeOld = False
type = self.typeComboBox.get_active()
if not applyFuncs[type](newRepoObj) or not self._addAndEnableRepo(newRepoObj) or not \
setupRepo(self.anaconda, newRepoObj):
continue
if removeOld:
try:
os.unlink("%s/cachecookie" % self.repo.cachedir)
os.unlink("%s/repomd.xml" % self.repo.cachedir)
except:
pass
self.repo.disable()
self.repo.close()
self.anaconda.backend.ayum.repos.delete(self.repo.id)
log.info("deleted (UI) repository %s with source URL %s, id:%s"
% (self.repo.name, self.repo.mirrorlist or self.repo.baseurl, self.repo.id))
try:
shutil.rmtree(self.repo.cachedir)
except Exception as e:
log.warning("error removing cachedir for %s: %s" %(self.repo, e))
pass
self.repo = newRepoObj
break
self.dialog.hide()
return rc
class RepoMethodstrEditor(RepoEditor):
def __init__(self, anaconda, methodstr):
# Create temporary repo to store methodstr needed for
# createDialog parent method.
temprepo = AnacondaYumRepo("UITmpMethodstrRepo")
temprepo.name = "Installation Repo"
temprepo.anacondaBaseURLs = [methodstr]
RepoEditor.__init__(self, anaconda, temprepo)
def createDialog(self):
RepoEditor.createDialog(self)
# Hide a bunch of stuff that doesn't apply when we're just prompting
# for enough information to form a methodstr.
self.nameEntry.set_sensitive(False)
self.mirrorlistCheckbox.hide()
self.proxyCheckbox.hide()
self.proxyTable.hide()
def _applyURL(self):
repourl = self.baseurlEntry.get_text()
repourl.strip()
if not self._validURL(repourl):
self.intf.messageWindow(_("Invalid Repository URL"),
_("You must provide an HTTP, HTTPS, "
"or FTP URL to a repository."))
return False
return repourl
def _applyMedia(self):
cdr = scanForMedia(self.anaconda.backend.ayum.tree, self.anaconda.storage)
if not cdr:
self.intf.messageWindow(_("No Media Found"),
_("No installation media was found. "
"Please insert a disc into your drive "
"and try again."))
return False
self.anaconda.backend.ayum.mediagrabber = self.anaconda.backend.ayum.mediaHandler
self.anaconda.backend.ayum.anaconda.mediaDevice = cdr
self.anaconda.backend.ayum.currentMedia = 1
log.info("found installation media on %s" % cdr)
return "cdrom://%s:%s" % (cdr, self.anaconda.backend.ayum.tree)
def _applyNfs(self):
server = self.nfsServerEntry.get_text()
server.strip()
path = self.nfsPathEntry.get_text()
path.strip()
options = self.nfsOptionsEntry.get_text()
options.strip()
if not server or not path:
self.intf.messageWindow(_("Error"),
_("Please enter an NFS server and path."))
return False
return "nfs:%s:%s:%s" % (options, server, path)
def _applyHd(self):
return None
def run(self):
applyFuncs = [ self._applyURL, self._applyMedia, self._applyNfs,
self._applyHd ]
while True:
rc = self.dialog.run()
if rc in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]:
rc = None
break
type = self.typeComboBox.get_active()
retval = applyFuncs[type]()
if not retval:
continue
rc = retval
break
self.dialog.hide()
return rc
class RepoCreator(RepoEditor):
def __init__(self, anaconda):
RepoEditor.__init__(self, anaconda, None)
self.dialog.set_title(_("Add Repository"))
class TaskWindow(InstallWindow):
def getNext(self):
if not self._anyRepoEnabled():
self.anaconda.intf.messageWindow(_("No Software Repos Enabled"),
_("You must have at least one software repository enabled to "
"continue installation."))
raise gui.StayOnScreen
if self.xml.get_widget("customRadio").get_active():
self.dispatch.skipStep("group-selection", skip = 0)
else:
self.dispatch.skipStep("group-selection", skip = 1)
tasks = self.xml.get_widget("taskList").get_model()
for (cb, task, grps) in filter(lambda x: not x[0], tasks):
map(lambda g: setattr(self.backend.ayum.comps.return_group(g),
"default", False), grps)
for (cb, task, grps) in filter(lambda x: x[0], tasks):
map(lambda g: setattr(self.backend.ayum.comps.return_group(g),
"default", True), grps)
def _editRepo(self, *args):
repo = None
# If we were passed an extra argument, it's the repo store and we
# are editing an existing repo as opposed to adding a new one.
if len(args) > 1:
(model, iter) = args[1].get_selection().get_selected()
if iter:
repo = model.get_value(iter, 2)
else:
return
else:
return
if repo.needsNetwork() and not network.hasActiveNetDev():
if not self.anaconda.intf.enableNetwork():
return gtk.RESPONSE_CANCEL
urlgrabber.grabber.reset_curl_obj()
dialog = RepoEditor(self.anaconda, repo)
dialog.createDialog()
dialog.run()
model.set_value(iter, 0, dialog.repo.isEnabled())
model.set_value(iter, 1, dialog.repo.name)
model.set_value(iter, 2, dialog.repo)
def _addRepo(self, *args):
dialog = RepoCreator(self.anaconda)
dialog.createDialog()
if dialog.run() in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]:
return gtk.RESPONSE_CANCEL
s = self.xml.get_widget("repoList").get_model()
s.append([dialog.repo.isEnabled(), dialog.repo.name, dialog.repo])
def _taskToggled(self, button, path, store):
# First, untoggle everything in the store.
for row in store:
row[0] = False
# Then, enable the one that was clicked.
store[path][0] = True
def _anyRepoEnabled(self):
model = self.rs.get_model()
iter = model.get_iter_first()
while True:
if model.get_value(iter, 0):
return True
iter = model.iter_next(iter)
if not iter:
return False
return False
def _repoToggled(self, button, row, store):
i = store.get_iter(int(row))
wasChecked = store.get_value(i, 0)
repo = store.get_value(i, 2)
if not wasChecked:
if repo.needsNetwork() and not network.hasActiveNetDev():
if not self.anaconda.intf.enableNetwork():
return
urlgrabber.grabber.reset_curl_obj()
repo.enable()
if not setupRepo(self.anaconda, repo):
return
else:
repo.disable()
repo.close()
store.set_value(i, 0, not wasChecked)
def _createTaskStore(self):
store = gtk.ListStore(gobject.TYPE_BOOLEAN,
gobject.TYPE_STRING,
gobject.TYPE_PYOBJECT)
tl = self.xml.get_widget("taskList")
tl.set_model(store)
cbr = gtk.CellRendererToggle()
cbr.set_radio(True)
cbr.connect("toggled", self._taskToggled, store)
col = gtk.TreeViewColumn('', cbr, active = 0)
tl.append_column(col)
col = gtk.TreeViewColumn('Text', gtk.CellRendererText(), text = 1)
col.set_clickable(False)
tl.append_column(col)
anyEnabled = False
for (txt, grps) in self.tasks:
if not self.backend.groupListExists(grps):
continue
enabled = self.backend.groupListDefault(grps)
store.append([not anyEnabled and enabled, _(txt), grps])
if enabled:
anyEnabled = True
return tl
def __sortRepos(self, store, aIter, bIter):
aStr = store.get_value(aIter, 1)
bStr = store.get_value(bIter, 1)
if aStr == "Installation Repo":
return -1
elif bStr == "Installation Repo":
return 1
elif aStr < bStr or bStr is None:
return -1
elif aStr > bStr or aStr is None:
return 1
else:
return aStr == bStr
def _createRepoStore(self):
store = gtk.ListStore(gobject.TYPE_BOOLEAN,
gobject.TYPE_STRING,
gobject.TYPE_PYOBJECT)
tl = self.xml.get_widget("repoList")
tl.set_model(store)
cbr = gtk.CellRendererToggle()
col = gtk.TreeViewColumn('', cbr, active = 0)
cbr.connect("toggled", self._repoToggled, store)
tl.append_column(col)
col = gtk.TreeViewColumn('Text', gtk.CellRendererText(), text = 1)
col.set_clickable(False)
tl.append_column(col)
for (reponame, repo) in self.repos.repos.items():
store.append([repo.isEnabled(), repo.name, repo])
store.set_sort_column_id(1, gtk.SORT_ASCENDING)
store.set_sort_func(1, self.__sortRepos)
return tl
def getScreen (self, anaconda):
self.intf = anaconda.intf
self.dispatch = anaconda.dispatch
self.backend = anaconda.backend
self.anaconda = anaconda
self.tasks = anaconda.instClass.tasks
self.repos = anaconda.backend.ayum.repos
(self.xml, vbox) = gui.getGladeWidget("tasksel.glade", "taskBox")
lbl = self.xml.get_widget("mainLabel")
if anaconda.instClass.description:
lbl.set_text(_(anaconda.instClass.description))
else:
txt = lbl.get_text()
lbl.set_text(txt %(productName,))
custom = not self.dispatch.stepInSkipList("group-selection")
if custom:
self.xml.get_widget("customRadio").set_active(True)
else:
self.xml.get_widget("customRadio").set_active(False)
self.ts = self._createTaskStore()
self.rs = self._createRepoStore()
if len(self.ts.get_model()) == 0:
self.xml.get_widget("cbVBox").hide()
self.xml.get_widget("mainLabel").hide()
self.xml.get_widget("addRepoButton").connect("clicked", self._addRepo)
self.xml.get_widget("editRepoButton").connect("clicked", self._editRepo, self.rs)
return vbox