# # 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 . # 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