summaryrefslogtreecommitdiff
path: root/SoftwareProperties
diff options
context:
space:
mode:
authorMichael Vogt <michael.vogt@ubuntu.com>2006-02-27 19:47:44 +0100
committerMichael Vogt <michael.vogt@ubuntu.com>2006-02-27 19:47:44 +0100
commit9fab3e0069ee2f9b05084787ce114a67ae65c13f (patch)
treea1d3680ffb37fac0550959de618d1640428eaf8c /SoftwareProperties
parenteadf5eaff85bec1548298ce14874cac070f44454 (diff)
parent67389517a2f4c58ee03b3ba6ee540df4f8f073f7 (diff)
downloadpython-apt-9fab3e0069ee2f9b05084787ce114a67ae65c13f.tar.gz
* merged with main
Diffstat (limited to 'SoftwareProperties')
-rw-r--r--SoftwareProperties/SoftwareProperties.py205
-rw-r--r--SoftwareProperties/aptsources.py328
-rw-r--r--SoftwareProperties/dialog_add.py130
-rw-r--r--SoftwareProperties/dialog_sources_list.py123
-rw-r--r--SoftwareProperties/utils.py2
5 files changed, 663 insertions, 125 deletions
diff --git a/SoftwareProperties/SoftwareProperties.py b/SoftwareProperties/SoftwareProperties.py
index 690b30dd..d3fef36a 100644
--- a/SoftwareProperties/SoftwareProperties.py
+++ b/SoftwareProperties/SoftwareProperties.py
@@ -37,6 +37,7 @@ from UpdateManager.Common.SimpleGladeApp import SimpleGladeApp
import aptsources
import dialog_add
import dialog_edit
+import dialog_sources_list
from dialog_apt_key import apt_key
from utils import *
@@ -50,11 +51,22 @@ CONF_MAP = {
"max_size" : "APT::Archives::MaxSize",
"max_age" : "APT::Archives::MaxAge"
}
-
+(
+ COLUMN_ACTIVE,
+ COLUMN_DESC
+) = range(2)
class SoftwareProperties(SimpleGladeApp):
- def __init__(self, datadir=None, options=None, parent=None):
+ def __init__(self, datadir=None, options=None, file=None, parent=None):
+
+ # set a default window icon
+ icons = gtk.icon_theme_get_default()
+ try:
+ logo=icons.load_icon("update-manager", 48, 0)
+ gtk.window_set_default_icon_list(logo)
+ except:
+ pass
# FIXME: some saner way is needed here
if datadir == None:
@@ -64,6 +76,8 @@ class SoftwareProperties(SimpleGladeApp):
None, domain="update-manager")
self.modified = False
+ self.file = file
+
#self.gnome_program = gnome.init("Software Properties", "0.41")
#self.gconfclient = gconf.client_get_default()
@@ -74,7 +88,8 @@ class SoftwareProperties(SimpleGladeApp):
if options and options.toplevel != None:
toplevel = gtk.gdk.window_foreign_new(int(options.toplevel))
self.window_main.window.set_transient_for(toplevel)
-
+
+ self.button_revert.set_sensitive(False)
self.init_sourceslist()
self.reload_sourceslist()
@@ -165,30 +180,97 @@ class SoftwareProperties(SimpleGladeApp):
else:
self.checkbutton_unattended.set_active(False)
+ # Backup the source list
+ self.sourceslist.clearBackup(".save")
+ self.sourceslist.backup(".save")
+
# apt-key stuff
self.apt_key = apt_key()
self.init_keyslist()
self.reload_keyslist()
+ # drag and drop support for sources.list
+ self.treeview_sources.drag_dest_set(gtk.DEST_DEFAULT_ALL, \
+ [('text/uri-list',0, 0)], \
+ gtk.gdk.ACTION_COPY)
+ self.treeview_sources.connect("drag_data_received",\
+ self.on_sources_drag_data_received)
+
+
+ # call the add sources.list dialog if we got a file from the cli
+ if self.file != None:
+ self.open_file(file)
+
+ def open_file(self, file):
+ """Show an confirmation for adding the channels of the specified file"""
+ dialog = dialog_sources_list.AddSourcesList(self.window_main,
+ self.sourceslist,
+ self.render_source,
+ self.datadir,
+ file)
+ res = dialog.run()
+ if res == gtk.RESPONSE_OK:
+ self.modified_sourceslist()
+
+ def on_sources_drag_data_received(self, widget, context, x, y,
+ selection, target_type, timestamp):
+ """Extract the dropped file pathes and open the first file, only"""
+ uri = selection.data.strip()
+ uri_splitted = uri.split()
+ if len(uri_splitted)>0:
+ self.open_file(uri_splitted[0])
+
def hide(self):
self.window_main.hide()
def init_sourceslist(self):
- self.source_store = gtk.ListStore(str, bool, gobject.TYPE_PYOBJECT)
+ self.source_store = gtk.ListStore(gobject.TYPE_BOOLEAN,
+ gobject.TYPE_STRING,
+ gobject.TYPE_PYOBJECT)
self.treeview_sources.set_model(self.source_store)
- tr = gtk.CellRendererText()
- tr.set_property("xpad", 10)
- tr.set_property("ypad", 10)
-
- source_col = gtk.TreeViewColumn("Description", tr, markup=LIST_MARKUP)
- source_col.set_max_width(500)
-
- self.treeview_sources.append_column(source_col)
+ cell_desc = gtk.CellRendererText()
+ cell_desc.set_property("xpad", 2)
+ cell_desc.set_property("ypad", 2)
+ col_desc = gtk.TreeViewColumn(_("Software Channel"), cell_desc,
+ markup=COLUMN_DESC)
+ col_desc.set_max_width(1000)
+
+ cell_toggle = gtk.CellRendererToggle()
+ cell_toggle.set_property("xpad", 2)
+ cell_toggle.set_property("ypad", 2)
+ cell_toggle.connect('toggled', self.on_channel_toggled)
+ col_active = gtk.TreeViewColumn(_("Active"), cell_toggle,
+ active=COLUMN_ACTIVE)
+
+ self.treeview_sources.append_column(col_active)
+ self.treeview_sources.append_column(col_desc)
self.sourceslist = aptsources.SourcesList()
self.matcher = aptsources.SourceEntryMatcher()
+ def on_channel_activate(self, treeview, path, column):
+ """Open the edit dialog if a channel was double clicked"""
+ self.on_edit_clicked(treeview)
+
+ def on_treeview_sources_cursor_changed(self, treeview):
+ """Enable the buttons remove and edit if a channel is selected"""
+ sel = self.treeview_sources.get_selection()
+ (model, iter) = sel.get_selected()
+ if iter:
+ self.button_edit.set_sensitive(True)
+ self.button_remove.set_sensitive(True)
+ else:
+ self.button_edit.set_sensitive(False)
+ self.button_remove.set_sensitive(False)
+
+ def on_channel_toggled(self, cell_toggle, path):
+ """Enable or disable the selected channel"""
+ iter = self.source_store.get_iter((int(path),))
+ source_entry = self.source_store.get_value(iter, LIST_ENTRY_OBJ)
+ source_entry.disabled = not source_entry.disabled
+ self.modified_sourceslist()
+
def init_keyslist(self):
self.keys_store = gtk.ListStore(str)
self.treeview2.set_model(self.keys_store)
@@ -198,20 +280,62 @@ class SoftwareProperties(SimpleGladeApp):
keys_col = gtk.TreeViewColumn("Key", tr, text=0)
self.treeview2.append_column(keys_col)
+ def on_button_revert_clicked(self, button):
+ """Restore the source list from the startup of the dialog"""
+ self.sourceslist.restoreBackup(".save")
+ self.sourceslist.clearBackup(".save")
+ self.sourceslist.backup(".save")
+ self.sourceslist.refresh()
+ self.reload_sourceslist()
+ self.button_revert.set_sensitive(False)
+ self.modified = False
+
+ def modified_sourceslist(self):
+ """The sources list was changed and now needs to be saved and reloaded"""
+ self.button_revert.set_sensitive(True)
+ self.save_sourceslist()
+ self.reload_sourceslist()
+ self.modified = True
+
+ def render_source(self, source):
+ """Render a nice output to show the source in a treeview"""
+ (nice_type, nice_dist, nice_comps, special) = self.matcher.match(source)
+
+ # FIXME: add this back when it's more consistent
+ #if special in (aptsources.SOURCE_UPDATES,
+ # aptsources.SOURCE_BACKPORTS,
+ # aptsources.SOURCE_SECURITY):
+ # contents = "<b>%s</b>" % nice_dist
+ #elif special == aptsources.SOURCE_SYSTEM:
+ if special in (aptsources.SOURCE_UPDATES,
+ aptsources.SOURCE_BACKPORTS,
+ aptsources.SOURCE_SECURITY,
+ aptsources.SOURCE_SYSTEM):
+ contents = "<b>%s</b>" % nice_dist
+ if source.type in ("deb-src", "rpm-src"):
+ contents += " (%s)" % nice_type
+ for comp in nice_comps:
+ contents += "\n%s" % comp
+ else:
+ contents = "<b>%s</b>" % nice_dist
+ if source.type in ("deb-src", "rpm-src"):
+ contents += " (%s)" % nice_type
+ for comp in nice_comps:
+ contents += "%s" % comp
+ return contents
+
def reload_sourceslist(self):
self.source_store.clear()
+ # FIXME: this happens with way too much magic, we need to either
+ # ask the user or provide a different way to present this
+ # )maybe some sort of configuration is enough?)
+ self.sourceslist.check_for_endangered_dists()
for source in self.sourceslist.list:
- if source.invalid or source.disabled:
+ if source.invalid:
continue
- (a_type, dist, comps) = self.matcher.match(source)
-
- contents = ""
- if source.comment != "":
- contents += "<i>%s</i>\n\n" % (source.comment)
- contents +="<big><b>%s </b></big> (%s) <small>\n%s</small>" % (dist,a_type, comps)
-
- self.source_store.append([contents, not source.disabled, source])
-
+ contents = self.render_source(source)
+ self.source_store.append([not source.disabled, contents, source])
+
def reload_keyslist(self):
self.keys_store.clear()
for key in self.apt_key.list():
@@ -220,11 +344,11 @@ class SoftwareProperties(SimpleGladeApp):
def on_combobox_update_interval_changed(self, widget):
i = self.combobox_update_interval.get_active()
if i != -1:
- value = self.combobox_interval_mapping[i]
- # Only write the key if it has changed
- if not value == apt_pkg.Config.FindI(CONF_MAP["autoupdate"]):
- apt_pkg.Config.Set(CONF_MAP["autoupdate"], str(value))
- self.write_config()
+ value = self.combobox_interval_mapping[i]
+ # Only write the key if it has changed
+ if not value == apt_pkg.Config.FindI(CONF_MAP["autoupdate"]):
+ apt_pkg.Config.Set(CONF_MAP["autoupdate"], str(value))
+ self.write_config()
def on_opt_autoupdate_toggled(self, widget):
if self.checkbutton_auto_update.get_active():
@@ -319,17 +443,17 @@ class SoftwareProperties(SimpleGladeApp):
def save_sourceslist(self):
#location = "/etc/apt/sources.list"
#shutil.copy(location, location + ".save")
- self.sourceslist.backup(".save")
self.sourceslist.save()
-
+
def on_add_clicked(self, widget):
+ """Open a dialog to add new channels"""
dialog = dialog_add.dialog_add(self.window_main, self.sourceslist,
self.datadir)
if dialog.run() == gtk.RESPONSE_OK:
- self.reload_sourceslist()
- self.modified = True
-
+ self.modified_sourceslist()
+
def on_edit_clicked(self, widget):
+ """Open a dialog to edit the currently selected dialog"""
sel = self.treeview_sources.get_selection()
(model, iter) = sel.get_selected()
if not iter:
@@ -338,17 +462,17 @@ class SoftwareProperties(SimpleGladeApp):
dialog = dialog_edit.dialog_edit(self.window_main, self.sourceslist,
source_entry, self.datadir)
if dialog.run() == gtk.RESPONSE_OK:
- self.reload_sourceslist()
- self.modified = True
-
+ self.modified_sourceslist()
+
def on_remove_clicked(self, widget):
sel = self.treeview_sources.get_selection()
(model, iter) = sel.get_selected()
if iter:
source = model.get_value(iter, LIST_ENTRY_OBJ)
self.sourceslist.remove(source)
- self.reload_sourceslist()
- self.modified = True
+ self.modified_sourceslist()
+ self.button_edit.set_sensitive(False)
+ self.button_remove.set_sensitive(False)
def add_key_clicked(self, widget):
chooser = gtk.FileChooserDialog(title=_("Import key"),
@@ -364,7 +488,7 @@ class SoftwareProperties(SimpleGladeApp):
_("Error importing selected file"),
_("The selected file may not be a GPG key file " \
"or it might be corrupt."))
- self.reload_keyslist()
+ self.reload_keyslist()
def remove_key_clicked(self, widget):
selection = self.treeview2.get_selection()
@@ -418,7 +542,7 @@ class SoftwareProperties(SimpleGladeApp):
type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_OK,
message_format=None)
- dialog.set_markup(_("<big><b>Error scaning the CD</b></big>\n\n%s"%msg))
+ dialog.set_markup(_("<big><b>Error scanning the CD</b></big>\n\n%s"%msg))
res = dialog.run()
dialog.destroy()
return
@@ -433,8 +557,7 @@ class SoftwareProperties(SimpleGladeApp):
if line != "":
full_path = "%s%s" % (apt_pkg.Config.FindDir("Dir::Etc"),saved_entry)
self.sourceslist.list.append(aptsources.SourceEntry(line,full_path))
- self.reload_sourceslist()
- self.modified = True
+ self.modified_sourceslist()
# FIXME: move this into a different file
diff --git a/SoftwareProperties/aptsources.py b/SoftwareProperties/aptsources.py
index 03459adf..820e6e77 100644
--- a/SoftwareProperties/aptsources.py
+++ b/SoftwareProperties/aptsources.py
@@ -32,6 +32,8 @@ import os.path
from UpdateManager.Common.DistInfo import DistInfo
+(SOURCE_SECURITY, SOURCE_UPDATES, SOURCE_SYSTEM, SOURCE_BACKPORTS) = range(4)
+
# actual source.list entries
class SourceEntry:
@@ -102,8 +104,16 @@ class SourceEntry:
line = line[:i]
# source is ok, split it and see what we have
pieces = self.mysplit(line)
+ # Sanity check
+ if len(pieces) < 3:
+ self.invalid = True
+ return
# Type, deb or deb-src
self.type = string.strip(pieces[0])
+ # Sanity check
+ if self.type not in ("deb", "deb-src"):
+ self.invalid = True
+ return
# URI
self.uri = string.strip(pieces[1])
# distro and components (optional)
@@ -226,9 +236,76 @@ class SourcesList:
line = line + "\n"
self.list.insert(pos, SourceEntry(line))
+ def disable_components(self, comps, source_entry):
+ """Disable components of a source"""
+ comps_remove = set(comps) & set(source_entry.comps)
+ if len(comps_remove) >= len(source_entry.comps):
+ # disable the whole source
+ source_entry.disabled = True
+ elif len(comps_remove) > 0:
+ # Remove the sections from the original source
+ comps_new = set(source_entry.comps) - comps_remove
+ comps_write=""
+ for comp in comps_new:
+ comps_write += " %s" % comp
+ line = "%s %s %s %s" % (source.type, source.uri, source.dist,
+ comps_write)
+ if source.comment:
+ line += "# %s" % source.comment
+ line += "\n"
+ index = self.list.index(source_entry)
+ file = self.list[index].file
+ self.list[index] = SourceEntry(line, file)
+
+ # Add a disabled line with the disabled comps after the
+ # original line
+ comps_write=""
+ for comp in comps_remove:
+ comps_write = " %s" % comp
+ line_disabled = "#%s %s %s %s" % (source.type, source.uri, source.dist,
+ comps_remove)
+ if source.comment:
+ line_disabled += "# %s" % source.comment
+ line_disabled += "\n"
+ self.list.insert[index+1](SourceEntry(line_disabled, file))
+
+ def remove_components(self, comps, source_entry):
+ """ Remove components of a source"""
+ # The components that need to be removed from the source
+ comps_remove = set(comps) & set(source_entry.comps)
+ if len(comps_remove) >= len(source_entry.comps):
+ # Delete the whole source if there are no comps left
+ self.list.remove(source_entry)
+ elif len(comps_remove) > 0:
+ # Remove the sections from the original source
+ comps_new = set(source_entry.comps) - comps_remove
+ comps_write = ""
+ for comp in comps_new:
+ comps_write += " %s" % comp
+ line = "%s %s %s %s" % (source.type, source.uri, source.dist,
+ comps_write)
+ if source.comment:
+ line += "# %s" % source.comment
+ line += "\n"
+ index = self.list.index(source_entry)
+ file = self.list[index].file
+ self.list[index] = SourceEntry(line, file)
+
def remove(self, source_entry):
self.list.remove(source_entry)
+ def clearBackup(self, backup_ext):
+ " remove backuped sources.list files based on the backup extension "
+ dir = apt_pkg.Config.FindDir("Dir::Etc")
+ file = apt_pkg.Config.Find("Dir::Etc::sourcelist")
+ if os.path.exists(dir+file+backup_ext):
+ os.remove(dir+file+backup_ext)
+ # now sources.list.d
+ partsdir = apt_pkg.Config.FindDir("Dir::Etc::sourceparts")
+ for file in glob.glob("%s/*.list" % partsdir):
+ if os.path.exists(file+backup_ext):
+ os.remove(file+backup_ext)
+
def restoreBackup(self, backup_ext):
" restore sources.list files based on the backup extension "
dir = apt_pkg.Config.FindDir("Dir::Etc")
@@ -270,10 +347,92 @@ class SourcesList:
files[source.file].write(source.str())
for f in files:
files[f].close()
+
+ def check_for_endangered_dists(self):
+ # To store the sources that provide updates
+ self.sources_updates = []
+ # To store the sources that provide backports
+ self.sources_backports = []
+ # To store the sources that provide securtiy fixes
+ self.sources_security = []
+ # To store the activated components of each dist
+ self.system_comps = {}
+
+ # The matcher searches sets the required special tags
+ self.matcher = SourceEntryMatcher()
+
+ for source in self.list:
+ if source.invalid or source.type != "deb":
+ continue
+ (nice_type, nice_dist, nice_comps, special) = self.matcher.match(source)
+ #print "match: %s %s" % (source.dist, special)
+
+ # Collect the components of an activated system dist
+ if special == SOURCE_SYSTEM and source.disabled != True:
+ if self.system_comps.has_key(source.dist):
+ current = self.system_comps[source.dist]
+ self.system_comps[source.dist] = (current | set(source.comps))
+ else:
+ self.system_comps[source.dist] = set(source.comps)
+
+ # Collect sources that provide updates
+ elif special == SOURCE_UPDATES:
+ self.sources_updates.append(source)
+ elif special == SOURCE_SECURITY:
+ self.sources_security.append(source)
+ elif special == SOURCE_BACKPORTS:
+ self.sources_backports.append(source)
+
+ #print "\nSystem Compos: %s " % self.system_comps
+
+ # Check if each security source contains all components of
+ # the same dist
+ res = False
+ res |= self.check_updates(self.sources_security)
+ res |= self.check_updates(self.sources_updates)
+ res |= self.check_updates(self.sources_backports)
+ return res
+
+ def check_updates(self, updates):
+ modified = False
+ for source in updates:
+ #print "SecSource: %s" % source.dist
+ # Skip the "-security" and "-updates" from the dist
+ i = source.dist.find("-")
+ dist = source.dist[:i]
+ # Are there any active components for the dist?
+ if self.system_comps.has_key(dist):
+ comps_sys = self.system_comps[dist]
+ comps_sec = set(source.comps)
+ # Are there components without updates?
+ comps_endangered = comps_sys - comps_sec
+ #print "In Danger: %s - %s = %s " % (comps_sys, comps_sec, comps_endangered)
+ if len(comps_endangered) > 0:
+ # convert the set into a list
+ comps_write=""
+ for comp in comps_sys:
+ comps_write += " %s" % comp
+ # add all system components to the securtiy line
+ line = "%s %s %s %s" % (source.type, source.uri, source.dist,
+ comps_write)
+ if source.comment:
+ line += "# %s" % source.comment
+ line += "\n"
+ index = self.list.index(source)
+ file = self.list[index].file
+ self.list[index] = SourceEntry(line, file)
+ modified = True
+ else:
+ # FIXME: What to do if there are no system sources?
+ # To disable the security updates would be the best
+ # option, but what about people with a local mirror
+ # that fetch sec updates from the ubuntu servers
+ pass
+ return modified
# templates for the add dialog
class SourceEntryTemplate(SourceEntry):
- def __init__(self,a_type,uri,dist,description,comps):
+ def __init__(self, a_type, uri, dist, description, comps):
self.comps = []
self.comps_descriptions = []
self.type = a_type
@@ -289,12 +448,14 @@ class SourceCompTemplate:
self.on_by_default = on_by_default
class SourceEntryTemplates:
- def __init__(self,datadir):
+ def __init__(self, datadir):
_ = gettext.gettext
self.templates = []
dinfo = DistInfo (base_dir=datadir+"channels/")
+ self.dist = dinfo.dist
+
for suite in dinfo.suites:
comps = []
for comp in suite.components:
@@ -315,108 +476,159 @@ class SourceEntryMatcher:
self.description = a_descr
class MatchDist:
- def __init__(self,a_uri,a_dist, a_descr,l_comps, l_comps_descr):
+ def __init__(self, a_uri, a_dist, a_descr, l_comps,
+ l_comps_descr, special=None):
self.uri = a_uri
self.dist = a_dist
self.description = a_descr
self.comps = l_comps
self.comps_descriptions = l_comps_descr
+ self.special = special
def __init__(self):
_ = gettext.gettext
self.type_list = []
self.type_list.append(self.MatchType("^deb$",_("Binary")))
- self.type_list.append(self.MatchType("^deb-src$",_("Source")))
+ self.type_list.append(self.MatchType("^deb-src$",_("Source Code")))
self.dist_list = []
+ #UBUNTU
ubuntu_comps = ["^main$","^restricted$","^universe$","^multiverse$"]
ubuntu_comps_descr = [_("Officially supported"),
_("Restricted copyright"),
_("Community maintained (Universe)"),
_("Non-free (Multiverse)")]
# CDs
+ self.dist_list.append(self.MatchDist("cdrom:\[Ubuntu.*6.04",
+ ".*",
+ _("Cdrom with Ubuntu 6.04 'Dapper "\
+ "Drake'"),
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist("cdrom:\[Ubuntu.*5.10",
".*",
- _("CD disk with Ubuntu 5.10 \"Breezy Badger\""),
- ubuntu_comps, ubuntu_comps_descr))
+ _("Cdrom with Ubuntu 5.10 'Breezy "\
+ "Badger'"),
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist("cdrom:\[Ubuntu.*5.04",
".*",
- _("CD disk with Ubuntu 5.04 \"Hoary Hedgehog\""),
- ubuntu_comps, ubuntu_comps_descr))
+ _("Cdrom with Ubuntu 5.04 'Hoary "\
+ "Hedgehog'"),
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist("cdrom:\[Ubuntu.*4.10",
".*",
- _("CD disk with Ubuntu 4.10 \"Warty Warthog\""),
- ubuntu_comps, ubuntu_comps_descr))
+ _("Cdrom with Ubuntu 4.10 'Warty "\
+ "Warthog'"),
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SYSTEM))
# URIs
# Warty
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^warty$",
- "Ubuntu 4.10 \"Warty Warthog\"",
- ubuntu_comps, ubuntu_comps_descr))
+ "Ubuntu 4.10 'Warty Warthog'",
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu",
"^warty-security$",
_("Ubuntu 4.10 Security Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SECURITY))
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^warty-security$",
_("Ubuntu 4.10 Security Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SECURITY))
+ self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
+ "^warty-backports$",
+ _("Ubuntu 4.10 Backports"),
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_BACKPORTS))
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^warty-updates$",
_("Ubuntu 4.10 Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_UPDATES))
# Hoary
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^hoary-security$",
_("Ubuntu 5.04 Security Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SECURITY))
self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu",
"^hoary-security$",
_("Ubuntu 5.04 Security Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SECURITY))
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^hoary$",
- "Ubuntu 5.04 \"Hoary Hedgehog\"",
- ubuntu_comps, ubuntu_comps_descr))
+ "Ubuntu 5.04 'Hoary Hedgehog'",
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SYSTEM))
+ self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
+ "^hoary-backports$",
+ _("Ubuntu 5.04 Backports"),
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_BACKPORTS))
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^hoary-updates$",
_("Ubuntu 5.04 Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_UPDATES))
# Breezy
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^breezy-security$",
_("Ubuntu 5.10 Security Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SECURITY))
self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu",
"^breezy-security$",
_("Ubuntu 5.10 Security Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SECURITY))
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^breezy$",
- "Ubuntu 5.10 \"Breezy Badger\"",
- ubuntu_comps, ubuntu_comps_descr))
+ "Ubuntu 5.10 'Breezy Badger'",
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SYSTEM))
+ self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
+ "^breezy-backports$",
+ _("Ubuntu 5.10 Backports"),
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_BACKPORTS))
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^breezy-updates$",
_("Ubuntu 5.10 Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_UPDATES))
# dapper
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^dapper-security$",
_("Ubuntu 6.04 Security Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SECURITY))
self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu",
"^dapper-security$",
_("Ubuntu 6.04 Security Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SECURITY))
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^dapper$",
- "Ubuntu 6.04 \"Dapper Drake\"",
- ubuntu_comps, ubuntu_comps_descr))
+ "Ubuntu 6.04 'Dapper Drake'",
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_SYSTEM))
+ self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
+ "^dapper-backports$",
+ _("Ubuntu 6.04 Backports"),
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_BACKPORTS))
self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu",
"^dapper-updates$",
_("Ubuntu 6.04 Updates"),
- ubuntu_comps, ubuntu_comps_descr))
+ ubuntu_comps, ubuntu_comps_descr,
+ SOURCE_UPDATES))
# DEBIAN
@@ -430,53 +642,64 @@ class SourceEntryMatcher:
# dists by name
self.dist_list.append(self.MatchDist(".*debian.org/debian",
"^sarge$",
- _("Debian 3.1 \"Sarge\""),
- debian_comps, debian_comps_descr))
+ _("Debian 3.1 'Sarge'"),
+ debian_comps, debian_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist(".*debian.org/debian",
"^woody$",
- _("Debian 3.0 \"Woody\""),
- debian_comps, debian_comps_descr))
+ _("Debian 3.0 'Woody'"),
+ debian_comps, debian_comps_descr,
+ SOURCE_SYSTEM))
# securtiy
self.dist_list.append(self.MatchDist(".*security.debian.org",
"^stable.*$",
_("Debian Stable Security Updates"),
- debian_comps, debian_comps_descr))
+ debian_comps, debian_comps_descr,
+ SOURCE_SECURITY))
# dists by status
self.dist_list.append(self.MatchDist(".*debian.org/debian",
"^stable$",
_("Debian Stable"),
- debian_comps, debian_comps_descr))
+ debian_comps, debian_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist(".*debian.org/debian",
"^testing$",
_("Debian Testing"),
- debian_comps, debian_comps_descr))
+ debian_comps, debian_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist(".*debian.org/debian",
"^unstable$",
- _("Debian Unstable \"Sid\""),
- debian_comps, debian_comps_descr))
+ _("Debian Unstable 'Sid'"),
+ debian_comps, debian_comps_descr,
+ SOURCE_SYSTEM))
# non-us
self.dist_list.append(self.MatchDist(".*debian.org/debian-non-US",
"^stable.*$",
_("Debian Non-US (Stable)"),
- debian_comps, debian_comps_descr))
+ debian_comps, debian_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist(".*debian.org/debian-non-US",
"^testing.*$",
_("Debian Non-US (Testing)"),
- debian_comps, debian_comps_descr))
+ debian_comps, debian_comps_descr,
+ SOURCE_SYSTEM))
self.dist_list.append(self.MatchDist(".*debian.org/debian-non-US",
"^unstable.*$",
_("Debian Non-US (Unstable)"),
- debian_comps, debian_comps_descr))
-
+ debian_comps, debian_comps_descr,
+ SOURCE_SYSTEM))
-
-
def match(self,source):
_ = gettext.gettext
# some sane defaults first
+ special = None
type_description = source.type
dist_description = source.uri + " " + source.dist
+ # if there is a comment use it instead of the url
+ if source.comment:
+ dist_description = source.comment
+
comp_description = ""
for c in source.comps:
comp_description = comp_description + " " + c
@@ -486,23 +709,24 @@ class SourceEntryMatcher:
type_description = _(t.description)
break
+ comp_descriptions = []
for d in self.dist_list:
#print "'%s'" %source.uri
- if re.match(d.uri, source.uri) and re.match(d.dist,source.dist):
+ if re.match(d.uri, source.uri) and re.match(d.dist, source.dist):
dist_description = d.description
- comp_description = ""
+ comp_descriptions = []
+ special = d.special
for c in source.comps:
found = False
for i in range(len(d.comps)):
if re.match(d.comps[i], c):
- comp_description = comp_description+"\n"+d.comps_descriptions[i]
+ comp_descriptions.append(d.comps_descriptions[i])
found = True
if found == False:
- comp_description = comp_description+" "+c
+ comp_descriptions.append(c)
break
-
-
- return (type_description,dist_description,comp_description)
+
+ return (type_description, dist_description, comp_descriptions, special)
# some simple tests
diff --git a/SoftwareProperties/dialog_add.py b/SoftwareProperties/dialog_add.py
index 9b384623..05737769 100644
--- a/SoftwareProperties/dialog_add.py
+++ b/SoftwareProperties/dialog_add.py
@@ -26,6 +26,7 @@ import os
import gobject
import gtk
import gtk.glade
+from gettext import gettext as _
import aptsources
@@ -44,17 +45,66 @@ class dialog_add:
self.main = widget = self.gladexml.get_widget("dialog_add")
self.main.set_transient_for(self.parent)
-
- combo = self.gladexml.get_widget("combobox_what")
+
+ self.vbox = self.gladexml.get_widget("vbox_comps")
+
+ # Setup the official channel widgets
+ self.combo = self.gladexml.get_widget("combobox_what")
self.gladexml.signal_connect("on_combobox_what_changed", self.on_combobox_what_changed, None)
- # combox box needs
cell = gtk.CellRendererText()
- combo.pack_start(cell, True)
- combo.add_attribute(cell, 'text', 0)
- self.fill_combo(combo)
- self.gladexml.signal_connect("on_button_custom_clicked",
- self.on_button_custom_clicked, None)
+ self.combo.pack_start(cell, True)
+ self.combo.add_attribute(cell, 'text', 0)
+ self.fill_combo(self.combo)
+ self.label_dist = self.gladexml.get_widget("label_dist")
+ if self.templatelist.dist != "":
+ # TRANSLATORS: %s is the distribution name, eg. Ubuntu or Debian
+ self.label_dist.set_markup("<b>%s</b>" % \
+ _("%s channels" % self.templatelist.dist))
+
+ # Setup the custom channel widgets
+ self.entry = self.gladexml.get_widget("entry_source_line")
+ self.gladexml.signal_connect("on_entry_source_line_changed",
+ self.check_line)
+
+ # Setup the toggle action
+ self.radio_official = self.gladexml.get_widget("radiobutton_official")
+ self.radio_custom = self.gladexml.get_widget("radiobutton_custom")
+ self.button_add = self.gladexml.get_widget("button_add_channel")
+ self.gladexml.signal_connect("on_radiobutton_custom_toggled",
+ self.on_radio_custom_toggled)
+ self.gladexml.signal_connect("on_radiobutton_official_toggled",
+ self.on_radio_official_toggled)
+
+ # We start with the official channels:
+ self.official = True
+ self.radio_custom.toggled()
+
+ def check_line(self, *args):
+ """Check for a valid apt line"""
+ if self.official == True:
+ self.button_add.set_sensitive(True)
+ return
+ line = self.entry.get_text() + "\n"
+ source_entry = aptsources.SourceEntry(line)
+ if source_entry.invalid == True or source_entry.disabled == True:
+ self.button_add.set_sensitive(False)
+ else:
+ self.button_add.set_sensitive(True)
+
+
+ def on_radio_custom_toggled(self, radio):
+ state = radio.get_active()
+ self.entry.set_sensitive(state)
+ self.check_line()
+
+ def on_radio_official_toggled(self, radio):
+ state = radio.get_active()
+ self.combo.set_sensitive(state)
+ for check in self.comps:
+ check.set_sensitive(state)
+ self.official = state
+ self.count_comps()
def fill_combo(self,combo):
liststore = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_PYOBJECT)
@@ -65,47 +115,63 @@ class dialog_add:
def on_combobox_what_changed(self, combobox, user):
#print "on_combobox_what_changed"
- vbox = self.gladexml.get_widget("vbox_comps")
- vbox.foreach(lambda widget,vbox: vbox.remove(widget), vbox)
+ self.vbox.foreach(lambda widget,vbox: self.vbox.remove(widget), self.vbox)
liststore = combobox.get_model()
a_iter = liststore.iter_nth_child(None, combobox.get_active())
(name, template) = liststore.get(a_iter, 0,1)
self.selected = template
+
+ # figure what is currently active in the sources.list
+ already_enabled_comps = []
+ for entry in self.sourceslist:
+ if entry.disabled or entry.invalid or entry.type != "deb":
+ continue
+ if template.dist == entry.dist and \
+ self.sourceslist.is_mirror(template.uri, entry.uri):
+ already_enabled_comps = entry.comps
+
comps = template.comps
+ self.comps=[]
for c in comps:
checkbox = gtk.CheckButton(c.description)
- checkbox.set_active(c.on_by_default)
+ # show what should be enabled by default if the source was not found
+ # else show the already enabled ones
+ if len(already_enabled_comps) == 0:
+ checkbox.set_active(c.on_by_default)
+ else:
+ checkbox.set_active(c.name in already_enabled_comps)
checkbox.set_data("name",c.name)
- vbox.pack_start(checkbox)
+ checkbox.connect("toggled", self.count_comps)
+ self.vbox.pack_start(checkbox)
checkbox.show()
-
- def on_button_custom_clicked(self, widget, data):
- #print "on_button_custom_clicked()"
- # this hide here is ugly :/
- self.main.hide()
- dialog = self.gladexml.get_widget("dialog_add_custom")
- dialog.set_transient_for(self.parent)
- res = dialog.run()
- dialog.hide()
- entry = self.gladexml.get_widget("entry_source_line")
- line = entry.get_text() + "\n"
- self.sourceslist.list.append(aptsources.SourceEntry(line))
- self.main.response(res)
+ self.comps.append(checkbox)
+ self.count_comps()
def get_enabled_comps(self, checkbutton):
if checkbutton.get_active():
self.selected_comps.append(checkbutton.get_data("name"))
+ def count_comps(self, *args):
+ button_add = self.gladexml.get_widget("button_add_channel")
+ self.selected_comps=[]
+ self.vbox.foreach(self.get_enabled_comps)
+ if len(self.selected_comps) > 0:
+ button_add.set_sensitive(True)
+ else:
+ button_add.set_sensitive(False)
+
def run(self):
res = self.main.run()
if res == gtk.RESPONSE_OK:
# add repository
- self.selected_comps = []
- vbox = self.gladexml.get_widget("vbox_comps")
- vbox.foreach(self.get_enabled_comps)
- self.sourceslist.add(self.selected.type,
- self.selected.uri,
- self.selected.dist,
- self.selected_comps)
+ if self.official == True:
+ #self.selected_comps = []
+ self.sourceslist.add(self.selected.type,
+ self.selected.uri,
+ self.selected.dist,
+ self.selected_comps)
+ else:
+ line = self.entry.get_text() + "\n"
+ self.sourceslist.list.append(aptsources.SourceEntry(line))
self.main.hide()
return res
diff --git a/SoftwareProperties/dialog_sources_list.py b/SoftwareProperties/dialog_sources_list.py
new file mode 100644
index 00000000..d35b9b51
--- /dev/null
+++ b/SoftwareProperties/dialog_sources_list.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+import pygtk
+import gtk
+import gtk.glade
+import gobject
+import os
+from optparse import OptionParser
+from aptsources import SourcesList, SourceEntryMatcher
+from gettext import gettext as _
+import gettext
+import urllib
+from utils import *
+
+class AddSourcesList:
+ def __init__(self, parent, sourceslist, source_renderer, datadir, file):
+ print file
+ self.parent = parent
+ self.source_renderer = source_renderer
+ self.sources_old = sourceslist
+ self.file = self.format_uri(file)
+ self.glade = gtk.glade.XML(os.path.join(datadir,
+ "glade/SoftwarePropertiesDialogs.glade"))
+ self.glade.signal_autoconnect(self)
+ self.dialog = self.glade.get_widget("dialog_sources_list")
+ self.label = self.glade.get_widget("label_sources")
+ self.button_add = self.glade.get_widget("button_add")
+ self.button_cancel = self.glade.get_widget("button_cancel")
+ self.treeview = self.glade.get_widget("treeview_sources")
+ self.button_close = self.glade.get_widget("button_close")
+ self.scrolled = self.glade.get_widget("scrolled_window")
+ self.image = self.glade.get_widget("image_sources_list")
+
+ self.dialog.realize()
+ if self.parent != None:
+ self.dialog.set_transient_for(parent)
+ else:
+ self.dialog.set_title(_("Add Software Channels"))
+ self.dialog.window.set_functions(gtk.gdk.FUNC_MOVE)
+
+ # Setup the treeview
+ self.store = gtk.ListStore(gobject.TYPE_STRING)
+ self.treeview.set_model(self.store)
+ cell = gtk.CellRendererText()
+ cell.set_property("xpad", 2)
+ cell.set_property("ypad", 2)
+ column = gtk.TreeViewColumn("Software Channel", cell, markup=0)
+ column.set_max_width(500)
+ self.treeview.append_column(column)
+
+ # Parse the source.list file
+ try:
+ self.sources = SingleSourcesList(self.file)
+ except:
+ self.error()
+ return
+
+ # show the found channels or an error message
+ if len(self.sources.list) > 0:
+ self.button_close.hide()
+ counter = 0
+ for source in self.sources.list:
+ if source.invalid or source.disabled:
+ continue
+ counter = counter +1
+ line = self.source_renderer(source)
+ self.store.append([line])
+ if counter == 0:
+ self.error()
+ return
+
+ header = gettext.ngettext("Add the following software channel?",
+ "Add the following software channels?",
+ counter)
+ body = _("You can install software from a channel. Use "\
+ "trusted channels, only.")
+ self.label.set_markup("<big><b>%s</b></big>\n\n%s" % (header, body))
+ self.button_add.set_use_underline(True)
+ self.button_add.set_label(gettext.ngettext("_Add Channel",
+ "_Add Channels",
+ counter))
+ else:
+ self.error()
+ return
+
+ def error(self):
+ self.button_add.hide()
+ self.button_cancel.hide()
+ self.scrolled.hide()
+ self.button_close.show()
+ self.image.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG)
+ header = _("Could not add any software channels")
+ body = _("The file '%s' does not contain any valid "
+ "software channels." % self.file)
+ self.label.set_markup("<big><b>%s</b></big>\n\n%s" % (header, body))
+
+ def run(self):
+ res = self.dialog.run()
+ if res == gtk.RESPONSE_OK:
+ for source in self.sources:
+ self.sources_old.add(source.type,
+ source.uri,
+ source.dist,
+ source.comps,
+ source.comment)
+ self.dialog.destroy()
+ return res
+
+ def format_uri(self, uri):
+ path = urllib.url2pathname(uri) # escape special chars
+ path = path.strip('\r\n\x00') # remove \r\n and NULL
+ if path.startswith('file:\\\\\\'): # windows
+ path = path[8:] # 8 is len('file:///')
+ elif path.startswith('file://'): #nautilus, rox
+ path = path[7:] # 7 is len('file://')
+ elif path.startswith('file:'): # xffm
+ path = path[5:] # 5 is len('file:')
+ return path
+
+class SingleSourcesList(SourcesList):
+ def __init__(self, file):
+ self.matcher = SourceEntryMatcher()
+ self.list = []
+ self.load(file)
diff --git a/SoftwareProperties/utils.py b/SoftwareProperties/utils.py
index cf9a3343..2886507f 100644
--- a/SoftwareProperties/utils.py
+++ b/SoftwareProperties/utils.py
@@ -1,5 +1,7 @@
import gtk
+
+
def dialog_error(parent, primary, secondary):
p = "<span weight=\"bold\" size=\"larger\">%s</span>" % primary
dialog = gtk.MessageDialog(parent,gtk.DIALOG_MODAL,