[master 08/10] Add repo addon to source spoke
Brian C. Lane
bcl at redhat.com
Tue Mar 12 00:13:35 UTC 2013
From: "Brian C. Lane" <bcl at redhat.com>
This adds a TreeView of the available repos, allows the user to
enable/disable them, add their own or delete them. Repos from .treeinfo
will be displayed here, disabled by default.
---
pyanaconda/iutil.py | 22 +++
pyanaconda/ui/gui/spokes/source.py | 311 ++++++++++++++++++++++++++++++++++---
2 files changed, 315 insertions(+), 18 deletions(-)
diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
index 05e7753..d73b474 100644
--- a/pyanaconda/iutil.py
+++ b/pyanaconda/iutil.py
@@ -480,3 +480,25 @@ class ProxyString(object):
def __str__(self):
return self.url
+def cmp_obj_attrs(obj1, obj2, attr_list):
+ """ Compare attributes of 2 objects for changes
+
+ Missing attrs are considered a mismatch
+
+ :param obj1: First object to compare
+ :type obj1: Any object
+ :param obj2: Second object to compare
+ :type obj2: Any object
+ :param attr_list: List of attributes to compare
+ :type attr_list: list or tuple of strings
+ :returns: True if the attrs all match
+ :rtype: bool
+ """
+ for attr in attr_list:
+ if hasattr(obj1, attr) and hasattr(obj2, attr):
+ if getattr(obj1, attr) != getattr(obj2, attr):
+ return False
+ else:
+ return False
+ return True
+
diff --git a/pyanaconda/ui/gui/spokes/source.py b/pyanaconda/ui/gui/spokes/source.py
index fc496a4..66246d6 100644
--- a/pyanaconda/ui/gui/spokes/source.py
+++ b/pyanaconda/ui/gui/spokes/source.py
@@ -39,11 +39,11 @@ from pyanaconda.ui.gui import GUIObject
from pyanaconda.ui.gui.spokes import NormalSpoke
from pyanaconda.ui.gui.categories.software import SoftwareCategory
from pyanaconda.ui.gui.utils import enlightbox, gtk_thread_wait
-from pyanaconda.iutil import ProxyString, ProxyStringError
+from pyanaconda.iutil import ProxyString, ProxyStringError, cmp_obj_attrs
from pyanaconda.ui.gui.utils import gtk_call_once
from pyanaconda.threads import threadMgr, AnacondaThread
from pyanaconda.packaging import PayloadError, MetadataError
-from pyanaconda.constants import DRACUT_ISODIR, ISO_DIR
+from pyanaconda.constants import DRACUT_ISODIR, ISO_DIR, BASE_REPO_NAME
from blivet.util import get_mount_paths
@@ -53,6 +53,13 @@ BASEREPO_SETUP_MESSAGE = N_("Setting up installation source...")
METADATA_DOWNLOAD_MESSAGE = N_("Downloading package metadata...")
METADATA_ERROR_MESSAGE = N_("Error downloading package metadata...")
+# Repo Store Columns
+REPO_ENABLED_COL = 0
+REPO_NAME_COL = 1
+REPO_OBJ = 2
+
+REPO_PROTO = [(0, "http://"), (1, "https://"), (2, "ftp://")]
+
class ProxyDialog(GUIObject):
builderObjects = ["proxyDialog"]
mainWidgetName = "proxyDialog"
@@ -248,7 +255,7 @@ class IsoChooser(GUIObject):
chooser.set_current_folder(ISO_DIR)
class SourceSpoke(NormalSpoke):
- builderObjects = ["isoChooser", "isoFilter", "partitionStore", "sourceWindow", "dirImage"]
+ builderObjects = ["isoChooser", "isoFilter", "partitionStore", "sourceWindow", "dirImage", "repoStore"]
mainWidgetName = "sourceWindow"
uiFile = "spokes/source.glade"
@@ -266,32 +273,46 @@ class SourceSpoke(NormalSpoke):
self._cdrom = None
def apply(self):
- import copy
-
# If askmethod was provided on the command line, entering the source
# spoke wipes that out.
if flags.askmethod:
flags.askmethod = False
+ method_changed = self._method_changed()
+ repos_changed = self._update_payload_repos()
+
+ if method_changed or repos_changed:
+ threadMgr.add(AnacondaThread(name="AnaPayloadMDThread",
+ target=self.getRepoMetadata))
+ self.clear_info()
+
+ def _method_changed(self):
+ """ Check to see if the install method has changed.
+
+ :returns: True if it changed, False if not
+ :rtype: bool
+ """
+ import copy
+
old_source = copy.copy(self.data.method)
if self._autodetectButton.get_active():
if not self._cdrom:
- return
+ return False
self.data.method.method = "cdrom"
self.payload.install_device = self._cdrom
if old_source.method == "cdrom":
# XXX maybe we should always redo it for cdrom in case they
# switched disks
- return
+ return False
elif self._isoButton.get_active():
# If the user didn't select a partition (not sure how that would
# happen) or didn't choose a directory (more likely), then return
# as if they never did anything.
part = self._get_selected_partition()
if not part or not self._currentIsoFile:
- return
+ return False
self.data.method.method = "harddrive"
self.data.method.partition = part.name
@@ -300,7 +321,7 @@ class SourceSpoke(NormalSpoke):
if (old_source.method == "harddrive" and
old_source.partition == self.data.method.partition and
old_source.dir == self.data.method.dir):
- return
+ return False
# Make sure anaconda doesn't touch this device.
part.protected = True
@@ -310,7 +331,7 @@ class SourceSpoke(NormalSpoke):
self.data.method.method = None
if not old_source.method and self.payload.baseRepo and \
not self._proxyChange:
- return
+ return False
elif self._http_active() or self._ftp_active():
url = self._urlEntry.get_text().strip()
mirrorlist = False
@@ -318,7 +339,7 @@ class SourceSpoke(NormalSpoke):
# If the user didn't fill in the URL entry, just return as if they
# selected nothing.
if url == "":
- return
+ return False
# Make sure the URL starts with the protocol. yum will want that
# to know how to fetch, and the refresh method needs that to know
@@ -336,7 +357,7 @@ class SourceSpoke(NormalSpoke):
if old_source.method == "url" and not self._proxyChange and \
((not mirrorlist and old_source.url == url) or \
(mirrorlist and old_source.mirrorlist == url)):
- return
+ return False
self.data.method.method = "url"
if mirrorlist:
@@ -352,7 +373,7 @@ class SourceSpoke(NormalSpoke):
# a ':' (so, no host/directory split), just return as if they
# selected nothing.
if url == "" or not ':' in url:
- return
+ return False
self.data.method.method = "nfs"
(self.data.method.server, self.data.method.dir) = url.split(":", 2)
@@ -362,7 +383,7 @@ class SourceSpoke(NormalSpoke):
old_source.server == self.data.method.server and
old_source.dir == self.data.method.dir and
old_source.opts == self.data.method.opts):
- return
+ return False
# If the user moved from an HDISO method to some other, we need to
# clear the protected bit on that device.
@@ -374,9 +395,7 @@ class SourceSpoke(NormalSpoke):
if dev:
dev.protected = False
- threadMgr.add(AnacondaThread(name="AnaPayloadMDThread",
- target=self.getRepoMetadata))
- self.clear_info()
+ return True
def getRepoMetadata(self):
communication.send_not_ready("SoftwareSelectionSpoke")
@@ -492,6 +511,18 @@ class SourceSpoke(NormalSpoke):
self._verifyIsoButton = self.builder.get_object("verifyIsoButton")
+ # addon repo objects
+ self._repoEntryBox = self.builder.get_object("repoEntryBox")
+ self._repoStore = self.builder.get_object("repoStore")
+ self._repoSelection = self.builder.get_object("repoSelection")
+ self._repoNameEntry = self.builder.get_object("repoNameEntry")
+ self._repoProtocolComboBox = self.builder.get_object("repoProtocolComboBox")
+ self._repoUrlEntry = self.builder.get_object("repoUrlEntry")
+ self._repoMirrorlistCheckbox = self.builder.get_object("repoMirrorlistCheckbox")
+ self._repoProxyUrlEntry = self.builder.get_object("repoProxyUrlEntry")
+ self._repoProxyUsernameEntry = self.builder.get_object("repoProxyUsernameEntry")
+ self._repoProxyPasswordEntry = self.builder.get_object("repoProxyPasswordEntry")
+
def initialize(self):
from pyanaconda.threads import threadMgr, AnacondaThread
@@ -634,7 +665,10 @@ class SourceSpoke(NormalSpoke):
else:
self._networkButton.set_active(True)
- # TODO: handle noUpdatesCheckbox
+ self._noUpdatesCheckbox.set_active(not self.payload.isRepoEnabled("updates"))
+
+ # Setup the addon repos
+ self._reset_repoStore()
# Then, some widgets get enabled/disabled/greyed out depending on
# how others are set up. We can use the signal handlers to handle
@@ -747,3 +781,244 @@ class SourceSpoke(NormalSpoke):
proxyButton.set_sensitive(self._http_active() or self._mirror_active())
nfsOptsBox.set_visible(self._nfs_active())
self._mirrorlistCheckbox.set_visible(self._http_active())
+
+ def _update_payload_repos(self):
+ """ Change the packaging repos to match the new edits
+
+ This will add new repos to the addon repo list, remove
+ ones that were removed and update any changes made to
+ existing ones.
+
+ :returns: True if any repo was changed, added or removed
+ :rtype: bool
+ """
+ REPO_ATTRS=("name", "baseurl", "mirrorlist", "proxy", "enabled")
+ changed = False
+ for repo in [r[REPO_OBJ] for r in self._repoStore]:
+ orig_repo = self.payload.getAddOnRepo(repo.orig_name)
+ if not orig_repo:
+ # TODO: Need an API to do this w/o touching yum (not addRepo)
+ self.payload.data.repo.dataList().append(repo)
+ changed = True
+ elif not cmp_obj_attrs(orig_repo, repo, REPO_ATTRS):
+ for attr in REPO_ATTRS:
+ setattr(orig_repo, attr, getattr(repo, attr))
+ changed = True
+
+ # Remove repos from payload that were removed in the UI
+ ui_repo_names = [r[REPO_OBJ].name for r in self._repoStore]
+ for repo_name in self.payload.addOns:
+ if repo_name not in ui_repo_names:
+ repo = self.payload.getAddOnRepo(repo_name)
+ # TODO: Need an API to do this w/o touching yum (not addRepo)
+ self.payload.data.repo.dataList().remove(repo)
+ changed = True
+
+ return changed
+
+ def _reset_repoStore(self):
+ """ Reset the list of repos to the default list and select first entry
+
+ Populate it with all the addon repos from payload.getAddOns
+ If there are none, clear the repo entry fields
+ """
+ self._repoStore.clear()
+ repos = self.payload.addOns
+ log.debug("Setting up repos: %s" % repos)
+ for name in repos:
+ if name in [BASE_REPO_NAME, "updates"]:
+ continue
+
+ repo = self.payload.getAddOnRepo(name)
+ ks_repo = self.data.RepoData(name=repo.name,
+ baseurl=repo.baseurl,
+ mirrorlist=repo.mirrorlist,
+ proxy=repo.proxy,
+ enabled=repo.enabled)
+ # Track the original name, user may change .name
+ ks_repo.orig_name = name
+ self._repoStore.append([self.payload.isRepoEnabled(name),
+ ks_repo.name,
+ ks_repo])
+
+ if len(self._repoStore) > 0:
+ self._repoSelection.select_path(0)
+ else:
+ self._clear_repo_info()
+ self._repoEntryBox.set_sensitive(False)
+
+
+ def on_repoSelection_changed(self, *args):
+ """ Called when the selection changed.
+
+ Update the repo text boxes with the current information
+ """
+ model, itr = self._repoSelection.get_selected()
+ if not itr:
+ return
+ self._update_repo_info(self._repoStore[itr][REPO_OBJ])
+
+ def on_repoEnable_toggled(self, renderer, path):
+ """ Called when the repo Enable checkbox is clicked
+ """
+ enabled = not self._repoStore[path][REPO_ENABLED_COL]
+ self._repoStore[path][REPO_ENABLED_COL] = enabled
+ self._repoStore[path][REPO_OBJ].enabled = enabled
+
+ def _clear_repo_info(self):
+ """ Clear the text from the repo entry fields
+
+ and reset the checkbox and combobox.
+ """
+ self._repoNameEntry.set_text("")
+ self._repoMirrorlistCheckbox.handler_block_by_func(self.on_repoMirrorlistCheckbox_toggled)
+ self._repoMirrorlistCheckbox.set_active(False)
+ self._repoMirrorlistCheckbox.handler_unblock_by_func(self.on_repoMirrorlistCheckbox_toggled)
+ self._repoUrlEntry.set_text("")
+ self._repoProtocolComboBox.set_active(0)
+ self._repoProxyUrlEntry.set_text("")
+ self._repoProxyUsernameEntry.set_text("")
+ self._repoProxyPasswordEntry.set_text("")
+
+ def _update_repo_info(self, repo):
+ """ Update the text boxes with data from repo
+
+ :param repo: kickstart repository object
+ :type repo: RepoData
+ """
+ self._repoNameEntry.set_text(repo.name)
+
+ self._repoMirrorlistCheckbox.handler_block_by_func(self.on_repoMirrorlistCheckbox_toggled)
+ if repo.mirrorlist:
+ url = repo.mirrorlist
+ self._repoMirrorlistCheckbox.set_active(True)
+ else:
+ url = repo.baseurl
+ self._repoMirrorlistCheckbox.set_active(False)
+ self._repoMirrorlistCheckbox.handler_unblock_by_func(self.on_repoMirrorlistCheckbox_toggled)
+
+ if url:
+ for idx, proto in REPO_PROTO:
+ if url.startswith(proto):
+ self._repoProtocolComboBox.set_active(idx)
+ self._repoUrlEntry.set_text(url[len(proto):])
+ break
+ else:
+ # Unknown protocol, just set the url then
+ self._repoUrlEntry.set_text(url)
+ else:
+ self._repoUrlEntry.set_text("")
+
+ if not repo.proxy:
+ self._repoProxyUrlEntry.set_text("")
+ self._repoProxyUsernameEntry.set_text("")
+ self._repoProxyPasswordEntry.set_text("")
+ else:
+ try:
+ proxy = ProxyString(repo.proxy)
+ if proxy.username:
+ self._repoProxyUsernameEntry.set_text(proxy.username)
+ if proxy.password:
+ self._repoProxyPasswordEntry.set_text(proxy.password)
+ self._repoProxyUrlEntry.set_text(proxy.noauth_url)
+ except ProxyStringError as e:
+ log.error("Failed to parse proxy for repo %s: %s" % (repo.name, e))
+ return
+
+ def on_noUpdatesCheckbox_toggled(self, *args):
+ """ Toggle the enable state of the updates repo
+ """
+ if self._noUpdatesCheckbox.get_active():
+ self.payload.disableRepo("updates")
+ else:
+ self.payload.enableRepo("updates")
+
+ def on_addRepo_clicked(self, button):
+ """ Add a new repository
+ """
+ repo = self.data.RepoData(name="New Repository")
+ repo.ks_repo = True
+ repo.orig_name = ""
+ iter = self._repoStore.append([True, repo.name, repo])
+ self._repoSelection.select_iter(iter)
+ self._repoEntryBox.set_sensitive(True)
+
+
+ def on_removeRepo_clicked(self, button):
+ """ Remove the selected repository
+ """
+ model, itr = self._repoSelection.get_selected()
+ if not itr:
+ return
+ self._repoStore.remove(itr)
+ if len(self._repoStore) == 0:
+ self._clear_repo_info()
+ self._repoEntryBox.set_sensitive(False)
+
+ def on_resetRepos_clicked(self, button):
+ """ Revert to the default list of repositories
+ """
+ self._reset_repoStore()
+
+ def on_repoNameEntry_changed(self, entry):
+ """ repo name changed
+ """
+ model, itr = self._repoSelection.get_selected()
+ if not itr:
+ return
+ repo = self._repoStore[itr][REPO_OBJ]
+
+ name = self._repoNameEntry.get_text().strip()
+ self._repoStore.set_value(itr, REPO_NAME_COL, name)
+ repo.name = name
+
+ def on_repoUrl_changed(self, *args):
+ """ proxy url or protocol changed
+ """
+ model, itr = self._repoSelection.get_selected()
+ if not itr:
+ return
+ repo = self._repoStore[itr][REPO_OBJ]
+ idx = self._repoProtocolComboBox.get_active()
+ proto = REPO_PROTO[idx][1]
+ url = self._repoUrlEntry.get_text().strip()
+ if self._repoMirrorlistCheckbox.get_active():
+ repo.mirorlist = proto + url
+ else:
+ repo.baseurl = proto + url
+
+ def on_repoMirrorlistCheckbox_toggled(self, *args):
+ """ mirror state changed
+ """
+ model, itr = self._repoSelection.get_selected()
+ if not itr:
+ return
+ repo = self._repoStore[itr][REPO_OBJ]
+
+ # This is called by set_active so only swap if there is something
+ # in the variable.
+ if self._repoMirrorlistCheckbox.get_active() and repo.baseurl:
+ repo.mirrorlist = repo.baseurl
+ repo.baseurl = ""
+ elif repo.mirrorlist:
+ repo.baseurl = repo.mirrorlist
+ repo.mirrorlist = ""
+
+ def on_repoProxy_changed(self, *args):
+ """ Update the selected repo's proxy settings
+ """
+ model, itr = self._repoSelection.get_selected()
+ if not itr:
+ return
+ repo = self._repoStore[itr][REPO_OBJ]
+
+ url = self._repoProxyUrlEntry.get_text().strip()
+ username = self._repoProxyUsernameEntry.get_text().strip() or None
+ password = self._repoProxyPasswordEntry.get_text().strip() or None
+
+ try:
+ proxy = ProxyString(url=url, username=username, password=password)
+ repo.proxy = proxy.url
+ except ProxyStringError as e:
+ log.error("Failed to parse proxy - %s:%s@%s: %s" \
+ % (username, password, url, e))
--
1.8.1.2
More information about the anaconda-patches
mailing list