diff options
| author | Michael Vogt <michael.vogt@ubuntu.com> | 2006-07-27 17:37:29 +0200 |
|---|---|---|
| committer | Michael Vogt <michael.vogt@ubuntu.com> | 2006-07-27 17:37:29 +0200 |
| commit | 180c6903e874cafd56464a0c56865aec0c646941 (patch) | |
| tree | e7781578681531a5327f19fdbc7e8c4df100910b /SoftwareProperties | |
| parent | 6d740ab2dee60d3fe10c563932fc62d5dd5d2057 (diff) | |
| parent | 54d5696fa964d3cc7a79a1ba0ed4083d9701de1e (diff) | |
| download | python-apt-180c6903e874cafd56464a0c56865aec0c646941.tar.gz | |
* merged from sebastia* merged from sebastiann
Diffstat (limited to 'SoftwareProperties')
| -rw-r--r-- | SoftwareProperties/SoftwareProperties.py | 613 | ||||
| -rw-r--r-- | SoftwareProperties/aptsources.py | 550 | ||||
| -rw-r--r-- | SoftwareProperties/dialog_add.py | 185 | ||||
| -rw-r--r-- | SoftwareProperties/dialog_add_sources_list.py | 125 |
4 files changed, 997 insertions, 476 deletions
diff --git a/SoftwareProperties/SoftwareProperties.py b/SoftwareProperties/SoftwareProperties.py index 65b72cda..07170fa6 100644 --- a/SoftwareProperties/SoftwareProperties.py +++ b/SoftwareProperties/SoftwareProperties.py @@ -21,6 +21,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA +import pdb import sys import apt import apt_pkg @@ -30,6 +31,8 @@ import gettext import tempfile from gettext import gettext as _ import os +import string +import re #sys.path.append("@prefix/share/update-manager/python") @@ -39,6 +42,7 @@ import aptsources import dialog_add import dialog_edit import dialog_cache_outdated +import dialog_add_sources_list from dialog_apt_key import apt_key from utils import * @@ -53,10 +57,27 @@ CONF_MAP = { "max_age" : "APT::Archives::MaxAge" } +( + COLUMN_ACTIVE, + COLUMN_DESC +) = range(2) + +RESPONSE_REPLACE = 1 +RESPONSE_ADD = 2 + +# columns of the source_store +( + STORE_ACTIVE, + STORE_DESCRIPTION, + STORE_SOURCE, + STORE_SEPARATOR, + STORE_VISIBLE +) = range(5) + class SoftwareProperties(SimpleGladeApp): - def __init__(self, datadir=None, options=None, parent=None): + def __init__(self, datadir=None, options=None, parent=None, file=None): gtk.window_set_default_icon_name("software-properties") # FIXME: some saner way is needed here @@ -67,8 +88,20 @@ class SoftwareProperties(SimpleGladeApp): None, domain="update-manager") self.modified = False - #self.gnome_program = gnome.init("Software Properties", "0.41") - #self.gconfclient = gconf.client_get_default() + self.file = file + + self.distro = aptsources.Distribution() + cell = gtk.CellRendererText() + self.combobox_server.pack_start(cell, True) + self.combobox_server.add_attribute(cell, 'text', 0) + + # set up the handler id for the callbacks + self.handler_server_changed = self.combobox_server.connect("changed", + self.on_combobox_server_changed) + self.handler_source_code_changed = self.checkbutton_source_code.connect( + "toggled", + self.on_checkbutton_source_code_toggled + ) if parent: self.window_main.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) @@ -83,13 +116,11 @@ class SoftwareProperties(SimpleGladeApp): toplevel = gtk.gdk.window_foreign_new(int(options.toplevel)) self.window_main.window.set_transient_for(toplevel) - self.window_main.show() - self.init_sourceslist() self.reload_sourceslist() - # internet update setings - + self.window_main.show() + # this maps the key (combo-box-index) to the auto-update-interval value # where (-1) means, no key self.combobox_interval_mapping = { 0 : 1, @@ -184,30 +215,367 @@ class SoftwareProperties(SimpleGladeApp): 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 distro_to_widgets(self): + """ + Represent the distro information in the user interface + """ + # TRANS: %s stands for the distribution name e.g. Debian or Ubuntu + self.label_updates.set_label("<b>%s</b>" % (_("%s updates") %\ + self.distro.id)) + # TRANS: %s stands for the distribution name e.g. Debian or Ubuntu + self.label_dist_name.set_label("%s" % self.distro.description) + + # Setup the checkbuttons for the components + for checkbutton in self.vbox_dist_comps.get_children(): + self.vbox_dist_comps.remove(checkbutton) + for comp in self.distro.source_template.components.keys(): + checkbox = gtk.CheckButton(label=self.distro.source_template.components[comp][2]) + # check if the comp is enabled + # FIXME: use inconsistence if there are main sources with not all comps + if comp in self.distro.download_comps: + checkbox.set_active(True) + # setup the callback and show the checkbutton + checkbox.connect("toggled", self.on_component_toggled, comp) + self.vbox_dist_comps.add(checkbox) + checkbox.show() + + # Setup the checkbuttons for the child repos / updates + for checkbutton in self.vbox_updates.get_children(): + self.vbox_updates.remove(checkbutton) + for template in self.distro.source_template.children: + checkbox = gtk.CheckButton(label=template.description) + comps = [] + for child in self.distro.child_sources: + if child.template == template: + comps.extend(child.comps) + # check if all comps of the main source are also enabled + # for the corresponding child sources + if len(comps) > 0 and \ + len(self.distro.enabled_comps ^ set(comps)) == 0: + # the cild source covers all components + checkbox.set_active(True) + elif len(comps) > 0 and\ + len(self.distro.enabled_comps ^ set(comps)) != 0: + # the cild is enabled, but doesn't cover + # all components + checkbox.set_active(False) + checkbox.set_inconsistent(True) + else: + # there is no corresponding child source at all + checkbox.set_active(False) + # setup the callback and show the checkbutton + checkbox.connect("toggled", self.on_checkbutton_child_toggled, + template) + self.vbox_updates.add(checkbox) + checkbox.show() + + if len(self.distro.enabled_comps) < 1: + self.vbox_updates.set_sensitive(False) + else: + self.vbox_updates.set_sensitive(True) + + # Intiate the combobox which allows do specify a server for all + # distro related sources + self.combobox_server.handler_block(self.handler_server_changed) + server_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + self.combobox_server.set_model(server_store) + server_store.append([_("Main server"), + self.distro.main_server]) + server_store.append([_("Server for %s") % gettext.dgettext("iso-3166", + self.distro.country).rstrip(), + self.distro.nearest_server]) + if len(self.distro.used_servers) > 0: + for server in self.distro.used_servers: + if not re.match(server, self.distro.main_server) and \ + not re.match(server, self.distro.nearest_server): + server_store.append(["%s" % server, server]) + if len(self.distro.used_servers) > 1: + server_store.append([_("Custom servers"), None]) + self.combobox_server.set_active(2) + elif self.distro.used_servers[0] == self.distro.main_server: + self.combobox_server.set_active(0) + elif self.distro.used_servers[0] == self.distro.nearest_server: + self.combobox_server.set_active(1) + else: + self.combobox_server.set_active(0) + + self.combobox_server.handler_unblock(self.handler_server_changed) + + # Check for source code sources + self.checkbutton_source_code.handler_block(self.handler_source_code_changed) + self.checkbutton_source_code.set_inconsistent(False) + if len(self.distro.source_code_sources) < 1: + # we don't have any source code sources, so + # uncheck the button + self.checkbutton_source_code.set_active(False) + self.distro.get_source_code = False + else: + # there are source code sources, so we check the button + self.checkbutton_source_code.set_active(True) + self.distro.get_source_code = True + # check if there is a corresponding source code source for + # every binary source. if not set the checkbutton to inconsistent + templates = {} + sources = [] + sources.extend(self.distro.main_sources) + sources.extend(self.distro.child_sources) + for source in sources: + if templates.has_key(source.template): + for comp in source.comps: + templates[source.template].add(comp) + else: + templates[source.template] = set(source.comps) + # add fake http sources for the cdrom, since the sources + # for the cdrom are only available in the internet + if len(self.distro.cdrom_sources) > 0: + templates[self.distro.source_template] = self.distro.cdrom_comps + for source in self.distro.source_code_sources: + if not templates.has_key(source.template) or \ + (templates.has_key(source.template) and not \ + (len(set(templates[source.template]) ^ set(source.comps)) == 0\ + or (len(set(source.comps) ^ self.distro.enabled_comps) == 0))): + self.checkbutton_source_code.set_inconsistent(True) + self.distro.get_source_code = False + break + self.checkbutton_source_code.handler_unblock(self.handler_source_code_changed) + + if len(self.cdrom_store) == 0: + self.treeview_cdroms.set_sensitive(False) + else: + self.treeview_cdroms.set_sensitive(True) + + def on_combobox_server_changed(self, combobox): + """ + Replace the servers used by the main and update sources with + the selected one + """ + server_store = combobox.get_model() + iter = combobox.get_active_iter() + uri_selected = server_store.get_value(iter, 1) + sources = [] + sources.extend(self.distro.main_sources) + sources.extend(self.distro.child_sources) + sources.extend(self.distro.source_code_sources) + for source in sources: + # FIXME: ugly + if not "security.ubuntu.com" in source.uri: + source.uri = uri_selected + self.distro.ddefault_server = uri_selected + self.modified_sourceslist() + + def on_component_toggled(self, checkbutton, comp): + """ + Sync the components of all main sources (excluding cdroms), + child sources and source code sources + """ + if checkbutton.get_active() == True: + self.distro.enable_component(self.sourceslist, comp) + else: + self.distro.disable_component(self.sourceslist, comp) + self.modified_sourceslist() + + def massive_debug_output(self): + """ + do not write our changes yet - just print them to std_out + """ + print "START SOURCES.LIST:" + for source in self.sourceslist: + print source.str() + print "END SOURCES.LIST\n" + + def on_checkbutton_child_toggled(self, checkbutton, template): + """ + Enable or disable a child repo of the distribution main repository + """ + if checkbutton.get_active() == False: + for source in self.distro.child_sources: + if source.template == template: + self.sourceslist.remove(source) + else: + self.distro.add_source(self.sourceslist, + uri=template.base_uri, + dist=template.name) + self.modified_sourceslist() + + def on_checkbutton_source_code_toggled(self, checkbutton): + """ + Disable or enable the source code for all sources + """ + self.distro.get_source_code = checkbutton.get_active() + sources = [] + sources.extend(self.distro.main_sources) + sources.extend(self.distro.child_sources) + + # remove all exisiting sources + for source in self.distro.source_code_sources: + self.sourceslist.remove(source) + + if checkbutton.get_active() == True: + for source in sources: + self.sourceslist.add("deb-src", + source.uri, + source.dist, + source.comps, + "Added by software-properties", + self.sourceslist.list.index(source)+1, + source.file) + for source in self.distro.cdrom_sources: + self.sourceslist.add("deb-src", + self.distro.source_template.base_uri, + self.distro.source_template.name, + source.comps, + "Added by software-properties", + self.sourceslist.list.index(source)+1, + source.file) + self.modified_sourceslist() + + def on_checkbutton_popcon_toggled(self, widget): + """ The user clicked on the popcon paritipcation button """ + popcon = "/etc/popularity-contest.conf" + if widget.get_active(): + new_value = "yes" + else: + new_value = "no" + if os.path.exists(popcon): + # read it + lines = open(popcon).read().split("\n") + for line in lines: + try: + (key,value) = line.split("=") + if key == "PARTICIPATE": + lines[lines.index(line)] = 'PARTICIPATE=\"%s"' % new_value + except ValueError: + continue + # write it + open(popcon,"w").write("\n".join(lines)) + + + def open_file(self, file): + """Show an confirmation for adding the channels of the specified file""" + dialog = dialog_add_sources_list.AddSourcesList(self.window_main, + self.sourceslist, + self.render_source, + self.get_comparable, + self.datadir, + file) + (res, new_sources) = dialog.run() + if res == RESPONSE_REPLACE: + self.sourceslist.list = [] + if res in (RESPONSE_ADD, RESPONSE_REPLACE): + for source in new_sources: + self.sourceslist.add(source.type, + source.uri, + source.dist, + source.comps, + source.comment) + 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) + """ + Read all valid sources into our ListStore + """ + # STORE_ACTIVE - is the source enabled or disabled + # STORE_DESCRIPTION - description of the source entry + # STORE_SOURCE - the source entry object + # STORE_SEPARATOR - if the entry is a separator + # STORE_VISIBLE - if the entry is shown or hidden + self.cdrom_store = gtk.ListStore(gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING, + gobject.TYPE_PYOBJECT, + gobject.TYPE_BOOLEAN, + gobject.TYPE_BOOLEAN) + self.treeview_cdroms.set_model(self.cdrom_store) + self.source_store = gtk.ListStore(gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING, + gobject.TYPE_PYOBJECT, + gobject.TYPE_BOOLEAN, + gobject.TYPE_BOOLEAN) 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.set_row_separator_func(self.is_separator, + STORE_SEPARATOR) - toggle = gtk.CellRendererToggle() - toggle.connect("toggled", self.on_channel_toggled) - toggle_col = gtk.TreeViewColumn("Active", toggle, active=LIST_ENABLED) + 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, self.cdrom_store) + col_active = gtk.TreeViewColumn(_("Active"), cell_toggle, + active=COLUMN_ACTIVE) + + self.treeview_cdroms.append_column(col_active) + self.treeview_cdroms.append_column(col_desc) + + 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, self.source_store) + 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.treeview_sources.append_column(toggle_col) - self.treeview_sources.append_column(source_col) - 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, store): + """Enable or disable the selected channel""" + #FIXME cdroms need to disable the comps in the childs and sources + iter = store.get_iter((int(path),)) + source_entry = store.get_value(iter, STORE_SOURCE) + source_entry.disabled = not source_entry.disabled + store.set_value(iter, STORE_ACTIVE, not source_entry.disabled) + self.modified_sourceslist() + def init_keyslist(self): self.keys_store = gtk.ListStore(str) self.treeview2.set_model(self.keys_store) @@ -217,29 +585,121 @@ 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.massive_debug_output() + self.modified = True + #self.button_revert.set_sensitive(True) + self.save_sourceslist() + self.reload_sourceslist() + + def render_source(self, source): + """Render a nice output to show the source in a treeview""" + + if source.template == None: + if source.comment: + contents = "<b>%s</b>" % source.comment + # Only show the components if there are more than one + if len(source.comps) > 1: + for c in source.comps: + contents += " %s" % c + else: + contents = "<b>%s %s</b>" % (source.uri, source.dist) + for c in source.comps: + contents += " %s" % c + if source.type in ("deb-src", "rpm-src"): + contents += " %s" % _("(Source Code)") + return contents + else: + # try to make use of an corresponding template + contents = "<b>%s</b>" % source.template.description + if source.type in ("deb-src", "rpm-src"): + contents += " (%s)" % _("Source Code") + if source.comment: + contents +=" %s" % source.comment + if source.template.child == False: + for comp in source.comps: + if source.template.components.has_key(comp): + print source.template.components[comp] + (desc, enabled, desc_long) = source.template.components[comp] + contents += "\n%s" % desc + else: + contents += "\n%s" % comp + return contents + + def get_comparable(self, source): + """extract attributes to sort the sources""" + cur_sys = 1 + has_template = 1 + has_comment = 1 + is_source = 1 + revert_numbers = string.maketrans("0123456789", "9876543210") + if source.template: + has_template = 0 + desc = source.template.description + if source.template.distribution == self.distro: + cur_sys = 0 + else: + desc = "%s %s %s" % (source.uri, source.dist, source.comps) + if source.comment: + has_comment = 0 + if source.type.find("src"): + is_source = 0 + return (cur_sys, has_template, has_comment, is_source, + desc.translate(revert_numbers)) + def reload_sourceslist(self): (path_x, path_y) = self.treeview_sources.get_cursor() self.source_store.clear() + self.cdrom_store.clear() + self.sourceslist.refresh() + self.sourceslist_visible=[] + self.distro.get_sources(self.sourceslist) + # Only show sources that are no binary or source code repos for + # the current distribution, but show cdrom based repos for source in self.sourceslist.list: - 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]) - # try to reselect the latest selected channel or if it fails the first - # one - if len(self.source_store) > 0 and \ - (path_x == None or self.treeview_sources.set_cursor(path_x)): - self.treeview_sources.set_cursor(0) - else: - # call the cursor_changed signal if no channel is selected - self.treeview_sources.emit("cursor_changed") + if not source.invalid and\ + (source not in self.distro.main_sources and\ + source not in self.distro.cdrom_sources and\ + source not in self.distro.child_sources and\ + source not in self.distro.disabled_sources) and\ + source not in self.distro.source_code_sources: + self.sourceslist_visible.append(source) + elif not source.invalid and source in self.distro.cdrom_sources: + contents = self.render_source(source) + self.cdrom_store.append([not source.disabled, contents, + source, False, True]) + # Sort the sources list + self.sourceslist_visible.sort(key=self.get_comparable) + + for source in self.sourceslist_visible: + contents = self.render_source(source) + + self.source_store.append([not source.disabled, contents, + source, False, True]) + + if len(self.source_store) < 1: + self.button_remove.set_sensitive(False) + self.button_edit.set_sensitive(False) + else: + self.treeview_sources.set_cursor(0) + self.distro.get_sources(self.sourceslist) + self.distro_to_widgets() + + def is_separator(self, model, iter, column): + return model.get_value(iter, column) + def reload_keyslist(self): self.keys_store.clear() for key in self.apt_key.list(): @@ -357,20 +817,14 @@ class SoftwareProperties(SimpleGladeApp): #shutil.copy(location, location + ".save") self.sourceslist.backup(".save") self.sourceslist.save() - # show a dialog that a reload of the channel information is required - # only if there is no parent defined - if self.modified == True and \ - self.options.toplevel == None: - d = dialog_cache_outdated.DialogCacheOutdated(self.window_main, - self.datadir) - res = d.run() def on_add_clicked(self, widget): dialog = dialog_add.dialog_add(self.window_main, self.sourceslist, self.datadir) - if dialog.run() == gtk.RESPONSE_OK: - self.reload_sourceslist() - self.modified = True + line = dialog.run() + if line != None: + self.sourceslist.list.append(aptsources.SourceEntry(line)) + self.modified_sourceslist() def on_edit_clicked(self, widget): sel = self.treeview_sources.get_selection() @@ -378,28 +832,19 @@ class SoftwareProperties(SimpleGladeApp): if not iter: return source_entry = model.get_value(iter, LIST_ENTRY_OBJ) - # see if we know what this thing should look like - found_matcher = False - for item in aptsources.SourceEntryTemplates(self.datadir).templates: - if item.matches(source_entry): - found_matcher = True - break - if found_matcher: - dialog = dialog_add.dialog_add(self.window_main, self.sourceslist, - self.datadir, source_entry) - else: - dialog = dialog_edit.dialog_edit(self.window_main, self.sourceslist, - source_entry, self.datadir) + 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() + # FIXME:outstanding from merge def on_channel_activated(self, treeview, path, column): """Open the edit dialog if a channel was double clicked""" # check if the channel can be edited if self.button_edit.get_property("sensitive") == True: self.on_edit_clicked(treeview) + # FIXME:outstanding from merge def on_treeview_sources_cursor_changed(self, treeview): """set the sensitiveness of the edit and remove button corresponding to the selected channel""" @@ -420,14 +865,14 @@ class SoftwareProperties(SimpleGladeApp): self.button_edit.set_sensitive(True) def on_remove_clicked(self, widget): - sel = self.treeview_sources.get_selection() - (model, iter) = sel.get_selected() + model = self.treeview_sources.get_model() + (path, column) = self.treeview_sources.get_cursor() + iter = model.get_iter(path) if iter: source = model.get_value(iter, LIST_ENTRY_OBJ) self.sourceslist.remove(source) - self.reload_sourceslist() - self.modified = True - + self.modified_sourceslist() + def add_key_clicked(self, widget): chooser = gtk.FileChooserDialog(title=_("Import key"), parent=self.window_main, @@ -462,11 +907,16 @@ class SoftwareProperties(SimpleGladeApp): self.reload_keyslist() def on_delete_event(self, widget, args): - self.save_sourceslist() - self.quit() - + self.on_close_button(self, widget) + def on_close_button(self, widget): - self.save_sourceslist() + # show a dialog that a reload of the channel information is required + # only if there is no parent defined + if self.modified == True and \ + self.options.toplevel == None: + d = dialog_cache_outdated.DialogCacheOutdated(self.window_main, + self.datadir) + res = d.run() self.quit() def on_help_button(self, widget): @@ -512,13 +962,13 @@ class SoftwareProperties(SimpleGladeApp): self.reload_sourceslist() self.modified = True - 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.reload_sourceslist() - self.modified = True + # def on_channel_toggled(self, cell_toggle, path, store): + # """Enable or disable the selected channel""" + # iter = store.get_iter((int(path),)) + # source_entry = store.get_value(iter, LIST_ENTRY_OBJ) + # source_entry.disabled = not source_entry.disabled + # self.reload_sourceslist() + # self.modified = True # FIXME: move this into a different file class GtkCdromProgress(apt.progress.CdromProgress, SimpleGladeApp): @@ -574,3 +1024,4 @@ class GtkCdromProgress(apt.progress.CdromProgress, SimpleGladeApp): return True return False + diff --git a/SoftwareProperties/aptsources.py b/SoftwareProperties/aptsources.py index 5da04b23..40b55dfa 100644 --- a/SoftwareProperties/aptsources.py +++ b/SoftwareProperties/aptsources.py @@ -30,6 +30,8 @@ import shutil import time import os.path +#import pdb + from UpdateManager.Common.DistInfo import DistInfo @@ -64,12 +66,8 @@ def is_mirror(master_uri, compare_uri): return False def uniq(s): - """ simple (and not efficient) way to return uniq list """ - u = [] - for x in s: - if x not in u: - u.append(x) - return u + """ simple and efficient way to return uniq list """ + return list(set(s)) @@ -89,6 +87,8 @@ class SourceEntry: file = apt_pkg.Config.FindDir("Dir::Etc")+apt_pkg.Config.Find("Dir::Etc::sourcelist") self.file = file self.parse(line) + self.template = None + self.children = [] # works mostely like split but takes [] into account def mysplit(self, line): @@ -207,6 +207,7 @@ class SourceEntry: class SourcesList: def __init__(self): self.list = [] # of Type SourceEntries + self.matcher = SourceEntryMatcher() self.refresh() def refresh(self): @@ -219,29 +220,51 @@ class SourcesList: partsdir = apt_pkg.Config.FindDir("Dir::Etc::sourceparts") for file in glob.glob("%s/*.list" % partsdir): self.load(file) + # check if the source item fits a predefined template + for source in self.list: + if source.invalid == False: + self.matcher.match(source) def __iter__(self): for entry in self.list: yield entry raise StopIteration - def add(self, type, uri, dist, comps, comment="", pos=-1): - # if there is a repo with the same (type, uri, dist) just add the - # components - for i in self.list: - if i.type == type and is_mirror(uri,i.uri) and i.dist == dist: - comps = uniq(i.comps + comps) - # set to the old position and preserve comment - comment = i.comment - pos = self.list.index(i) - self.list.remove(i) + def add(self, type, uri, dist, comps, comment="", pos=-1, file=None): + """ + Add a new source to the sources.list. + The method will search for existing matching repos and will try to + reuse them as far as possible + """ + for source in self.list: + # if there is a repo with the same (type, uri, dist) just add the + # components + if source.disabled == False and source.invalid == False and \ + source.type == type and uri == source.uri and \ + source.dist == dist: + comps = uniq(source.comps + comps) + source.comps = comps + return source + # if there is a corresponding repo which is disabled, enable it + elif source.disabled == True and source.invalid == False and \ + source.type == type and uri == source.uri and \ + source.dist == dist and \ + len(set(source.comps) & set(comps)) == len(comps): + source.disabled = False + return source + # there isn't any matching source, so create a new line and parse it line = "%s %s %s" % (type,uri,dist) for c in comps: line = line + " " + c; if comment != "": line = "%s #%s\n" %(line,comment) line = line + "\n" - self.list.insert(pos, SourceEntry(line)) + new_entry = SourceEntry(line) + if file != None: + new_entry.file = file + self.matcher.match(new_entry) + self.list.insert(pos, new_entry) + return new_entry def remove(self, source_entry): self.list.remove(source_entry) @@ -271,12 +294,16 @@ class SourcesList: def load(self,file): """ (re)load the current sources """ - f = open(file, "r") - lines = f.readlines() - for line in lines: - source = SourceEntry(line,file) - self.list.append(source) - f.close() + try: + f = open(file, "r") + lines = f.readlines() + for line in lines: + source = SourceEntry(line,file) + self.list.append(source) + except: + print "could not open file '%s'" % file + else: + f.close() def save(self): """ save the current sources """ @@ -288,6 +315,30 @@ class SourcesList: for f in files: files[f].close() + def check_for_relations(self, sources_list): + """get all parent and child channels in the sources list""" + parents = [] + used_child_templates = {} + for source in sources_list: + # try to avoid checking uninterressting sources + if source.template == None: + continue + # set up a dict with all used child templates and corresponding + # source entries + if source.template.child == True: + key = source.template + if not used_child_templates.has_key(key): + used_child_templates[key] = [] + temp = used_child_templates[key] + temp.append(source) + else: + # store each source with children aka. a parent :) + if len(source.template.children) > 0: + parents.append(source) + #print self.used_child_templates + #print self.parents + return (parents, used_child_templates) + # templates for the add dialog class SourceEntryTemplate(SourceEntry): def __init__(self,a_type,uri,dist,description,comps): @@ -354,227 +405,244 @@ class SourceEntryMatcher: self.comps_descriptions = l_comps_descr def __init__(self): + self.templates = [] + # Get the human readable channel and comp names from the channel .infos + spec_files = glob.glob("/usr/share/update-manager/channels/*.info") + for f in spec_files: + f = os.path.basename(f) + i = f.find(".info") + f = f[0:i] + dist = DistInfo(f) + for suite in dist.suites: + if suite.match_uri != None: + self.templates.append(suite) + return + + def match(self, source): + """Add a matching template to the source""" _ = gettext.gettext - self.type_list = [] - self.type_list.append(self.MatchType("^deb$",_("Binary"))) - self.type_list.append(self.MatchType("^deb-src$",_("Source"))) - - self.dist_list = [] - - 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.06", - ".*", - _("CD disk with Ubuntu 6.06 LTS"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist("cdrom:\[Ubuntu.*5.10", - ".*", - _("CD disk with Ubuntu 5.10"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist("cdrom:\[Ubuntu.*5.04", - ".*", - _("CD disk with Ubuntu 5.04"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist("cdrom:\[Ubuntu.*4.10", - ".*", - _("CD disk with Ubuntu 4.10"), - ubuntu_comps, ubuntu_comps_descr)) - # URIs - # Warty - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^warty$", - "Ubuntu 4.10", - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu", - "^warty-security$", - _("Ubuntu 4.10 Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^warty-security$", - _("Ubuntu 4.10 Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^warty-updates$", - _("Ubuntu 4.10 Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^warty-backports$", - _("Ubuntu 4.10 Backports"), - ubuntu_comps, ubuntu_comps_descr)) - # Hoary - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^hoary-security$", - _("Ubuntu 5.04 Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu", - "^hoary-security$", - _("Ubuntu 5.04 Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^hoary$", - "Ubuntu 5.04", - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^hoary-updates$", - _("Ubuntu 5.04 Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^hoary-backports$", - _("Ubuntu 5.04 Backports"), - ubuntu_comps, ubuntu_comps_descr)) - # Breezy - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^breezy-security$", - _("Ubuntu 5.10 Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu", - "^breezy-security$", - _("Ubuntu 5.10 Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^breezy$", - "Ubuntu 5.10", - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^breezy-backports$", - _("Ubuntu 5.10 Backports"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^breezy-updates$", - _("Ubuntu 5.10 Updates"), - ubuntu_comps, ubuntu_comps_descr)) - # dapper - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^dapper-security$", - _("Ubuntu 6.06 LTS Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu", - "^dapper-security$", - _("Ubuntu 6.06 LTS Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^dapper$", - "Ubuntu 6.06 LTS", - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^dapper-backports$", - _("Ubuntu 6.06 LTS Backports"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^dapper-updates$", - _("Ubuntu 6.06 LTS Updates"), - ubuntu_comps, ubuntu_comps_descr)) - # edgy - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^edgy-security$", - _("Ubuntu 6.10 Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*security.ubuntu.com/ubuntu", - "^edgy-security$", - _("Ubuntu 6.10 Security Updates"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^edgy$", - "Ubuntu 6.10", - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^edgy-backports$", - _("Ubuntu 6.10 Backports"), - ubuntu_comps, ubuntu_comps_descr)) - self.dist_list.append(self.MatchDist(".*archive.ubuntu.com/ubuntu", - "^edgy-updates$", - _("Ubuntu 6.10 Updates"), - ubuntu_comps, ubuntu_comps_descr)) - - - # DEBIAN - debian_comps = ["^main$","^contrib$","^non-free$","^non-US$"] - debian_comps_descr = [_("Officially supported"), - _("Contributed software"), - _("Non-free software"), - _("US export restricted software") - ] - - # dists by name - self.dist_list.append(self.MatchDist(".*debian.org/debian", - "^sarge$", - _("Debian 3.1 \"Sarge\""), - debian_comps, debian_comps_descr)) - self.dist_list.append(self.MatchDist(".*debian.org/debian", - "^woody$", - _("Debian 3.0 \"Woody\""), - debian_comps, debian_comps_descr)) - # securtiy - self.dist_list.append(self.MatchDist(".*security.debian.org", - "^stable.*$", - _("Debian Stable Security Updates"), - debian_comps, debian_comps_descr)) - # dists by status - self.dist_list.append(self.MatchDist(".*debian.org/debian", - "^stable$", - _("Debian Stable"), - debian_comps, debian_comps_descr)) - self.dist_list.append(self.MatchDist(".*debian.org/debian", - "^testing$", - _("Debian Testing"), - debian_comps, debian_comps_descr)) - self.dist_list.append(self.MatchDist(".*debian.org/debian", - "^unstable$", - _("Debian Unstable \"Sid\""), - debian_comps, debian_comps_descr)) - - # non-us - self.dist_list.append(self.MatchDist(".*debian.org/debian-non-US", - "^stable.*$", - _("Debian Non-US (Stable)"), - debian_comps, debian_comps_descr)) - self.dist_list.append(self.MatchDist(".*debian.org/debian-non-US", - "^testing.*$", - _("Debian Non-US (Testing)"), - debian_comps, debian_comps_descr)) - self.dist_list.append(self.MatchDist(".*debian.org/debian-non-US", - "^unstable.*$", - _("Debian Non-US (Unstable)"), - debian_comps, debian_comps_descr)) - - - - - def match(self,source): - _ = gettext.gettext - # some sane defaults first - type_description = source.type - dist_description = source.uri + " " + source.dist - comp_description = "" - for c in source.comps: - comp_description = comp_description + " " + c - - for t in self.type_list: - if re.match(t.type, source.type): - type_description = _(t.description) - break - - for d in self.dist_list: + found = False + for template in self.templates: #print "'%s'" %source.uri - if re.match(d.uri, source.uri) and re.match(d.dist,source.dist): - dist_description = d.description - comp_description = "" - 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] - found = True - if found == False: - comp_description = comp_description+" "+c + if re.search(template.match_uri, source.uri) and \ + re.match(template.match_name, source.dist): + found = True + source.template = template break - - - return (type_description,dist_description,comp_description) + return found + +class Distribution: + def __init__(self): + """" + Container for distribution specific informations + """ + # LSB information + self.id = "" + self.codename = "" + self.description = "" + self.release = "" + + # get the LSB information + lsb_info = [] + for lsb_option in ["-i", "-c", "-d", "-r"]: + pipe = os.popen("lsb_release %s | cut -d : -f 2-" % lsb_option) + lsb_info.append(pipe.read().strip()) + del pipe + (self.id, self.codename, self.description, self.release) = lsb_info + + # get a list of country codes and real names + self.countries = {} + try: + f = open("/usr/share/iso-codes/iso_3166.tab", "r") + lines = f.readlines() + for line in lines: + parts = line.split("\t") + self.countries[parts[0].lower()] = parts[1] + except: + print "could not open file '%s'" % file + else: + f.close() + + def get_sources(self, sources_list): + """ + Find the corresponding template, main and child sources + for the distribution + """ + # corresponding sources + self.source_template = None + self.child_sources = [] + self.main_sources = [] + self.disabled_sources = [] + self.cdrom_sources = [] + self.download_comps = [] + self.enabled_comps = [] + self.cdrom_comps = [] + self.used_media = [] + self.get_source_code = False + self.source_code_sources = [] + + # location of the sources + self.default_server = "" + self.main_server = "" + self.nearest_server = "" + self.used_servers = [] + + # find the distro template + for template in sources_list.matcher.templates: + if template.name == self.codename and\ + template.distribution == self.id: + #print "yeah! found a template for %s" % self.description + #print template.description, template.base_uri, template.components + self.source_template = template + break + if self.source_template == None: + print "Error: could not find a distribution template" + # FIXME: will go away - only for debugging issues + sys.exit(1) + + # find main and child sources + media = [] + comps = [] + cdrom_comps = [] + enabled_comps = [] + source_code = [] + for source in sources_list.list: + if source.invalid == False and\ + source.dist == self.codename and\ + source.template and\ + source.template.name == self.codename: + #print "yeah! found a distro repo: %s" % source.line + # cdroms need do be handled differently + if source.uri.startswith("cdrom:") and \ + source.disabled == False: + self.cdrom_sources.append(source) + cdrom_comps.extend(source.comps) + elif source.uri.startswith("cdrom:") and \ + source.disabled == True: + self.cdrom_sources.append(source) + elif source.type == "deb" and source.disabled == False: + self.main_sources.append(source) + comps.extend(source.comps) + media.append(source.uri) + elif source.type == "deb" and source.disabled == True: + self.disabled_sources.append(source) + elif source.type.endswith("-src") and source.disabled == False: + self.source_code_sources.append(source) + elif source.type.endswith("-src") and source.disabled == True: + self.disabled_sources.append(source) + if source.template in self.source_template.children: + #print "yeah! child found: %s" % source.template.name + if source.type == "deb": + self.child_sources.append(source) + elif source.type == "deb-src": + self.source_code_sources.append(source) + self.download_comps = set(comps) + self.cdrom_comps = set(cdrom_comps) + enabled_comps.extend(comps) + enabled_comps.extend(cdrom_comps) + self.enabled_comps = set(enabled_comps) + self.used_media = set(media) + + self.get_mirrors() + + def get_mirrors(self): + """ + Provide a set of mirrors where you can get the distribution from + """ + # the main server is stored in the template + self.main_server = self.source_template.base_uri + + # try to guess the nearest mirror from the locale + # FIXME: for debian we need something different + if self.id == "Ubuntu": + locale = os.getenv("LANG", default="en.UK") + a = locale.find("_") + z = locale.find(".") + if z == -1: + z = len(locale) + country_code = locale[a+1:z].lower() + self.nearest_server = "http://%s.archive.ubuntu.com/ubuntu/" % \ + country_code + self.country = self.countries[country_code] + + # other used servers + for medium in self.used_media: + if not medium.startswith("cdrom:"): + # seems to be a network source + self.used_servers.append(medium) + + if len(self.main_sources) == 0: + self.default_server = self.main_server + else: + self.default_server = self.main_sources[0].uri + + def add_source(self, sources_list, type=None, + uri=None, dist=None, comps=None, comment=""): + """ + Add distribution specific sources + """ + if uri == None: + # FIXME: Add support for the server selector + uri = self.default_server + if dist == None: + dist = self.codename + if comps == None: + comps = list(self.enabled_comps) + if type == None: + type = "deb" + if comment == "": + comment == "Added by software-properties" + new_source = sources_list.add(type, uri, dist, comps, comment) + # if source code is enabled add a deb-src line after the new + # source + if self.get_source_code == True and not type.endswith("-src"): + sources_list.add("%s-src" % type, uri, dist, comps, comment, + file=new_source.file, + pos=sources_list.list.index(new_source)+1) + + def enable_component(self, sourceslist, comp): + """ + Disable a component in all main, child and source code sources + (excluding cdrom based sources) + + sourceslist: an aptsource.sources_list + comp: the component that should be enabled + """ + sources = [] + sources.extend(self.main_sources) + sources.extend(self.child_sources) + sources.extend(self.source_code_sources) + # check if there is a main source at all + if len(self.main_sources) < 1: + # create a new main source + self.add_source(sourceslist, comps=["%s"%comp]) + else: + # add the comp to all main, child and source code sources + for source in sources: + if comp not in source.comps: + source.comps.append(comp) + if self.get_source_code == True: + for source in self.source_code_sources: + if comp not in source.comps: source.comps.append(comp) + + def disable_component(self, sourceslist, comp): + """ + Disable a component in all main, child and source code sources + (excluding cdrom based sources) + """ + sources = [] + sources.extend(self.main_sources) + sources.extend(self.child_sources) + sources.extend(self.source_code_sources) + if comp in self.cdrom_comps: + sources = [] + sources.extend(self.main_sources) + + for source in sources: + if comp in source.comps: + source.comps.remove(comp) + if len(source.comps) < 1: + sourceslist.remove(source) # some simple tests diff --git a/SoftwareProperties/dialog_add.py b/SoftwareProperties/dialog_add.py index b5fbe07f..81dd1cb2 100644 --- a/SoftwareProperties/dialog_add.py +++ b/SoftwareProperties/dialog_add.py @@ -6,7 +6,8 @@ # Authors: # Michael Vogt <mvo@debian.org> # Michiel Sikkes <michiels@gnome.org> -# +# Sebastian Heinlein <glatzor@ubuntu.com> +# # 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 @@ -29,167 +30,43 @@ import gtk.glade from gettext import gettext as _ import aptsources -import dialog_edit class dialog_add: - def __init__(self, parent, sourceslist, datadir, source_entry = None): + def __init__(self, parent, sourceslist, datadir): + """ + Initialize the dialog that allows to add a new source entering the + raw apt line + """ self.sourceslist = sourceslist self.parent = parent self.datadir = datadir - self.custom = False - # we have a source_entry that we want to modify - self.source_entry = source_entry - if source_entry: - self.source_entry_index = sourceslist.list.index(source_entry) - else: - self.source_entry_index = None - - # templates - self.templatelist = aptsources.SourceEntryTemplates(datadir) - - # FIXME: simple-glade-app should be able to do all this! - # gtk stuff - self.gladexml = gtk.glade.XML("%s/glade/SoftwarePropertiesDialogs.glade" % datadir) - - self.main = widget = self.gladexml.get_widget("dialog_add") - self.main.set_transient_for(self.parent) - - 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) - if source_entry: - self.main.set_title(_("Edit Channel")) - self.gladexml.get_widget("button_add").set_label("gtk-ok") - self.gladexml.signal_connect("on_button_custom_clicked", - self.on_button_custom_clicked, None) - + self.gladexml = gtk.glade.XML("%s/glade/SoftwarePropertiesDialogs.glade" %\ + datadir) + self.dialog = self.gladexml.get_widget("dialog_add_custom") + self.dialog.set_transient_for(self.parent) + self.entry = self.gladexml.get_widget("entry_source_line") + self.button_add = self.gladexml.get_widget("button_add_source") + self.entry.connect("changed", self.check_line) - def fill_combo(self,combo): - liststore = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_PYOBJECT) - matched_template = None - for item in self.templatelist.templates: - liststore.append((item.description, item)) - if self.source_entry and item.matches(self.source_entry): - matched_template = item - combo.set_model(liststore) - if matched_template: - try: - combo.set_active(self.templatelist.templates.index(matched_template)) - vbox = self.gladexml.get_widget("vbox_comps") - for c in vbox.get_children(): - c.set_active(c.get_data("name") in self.source_entry.comps) - except ValueError: - pass + def run(self): + res = self.dialog.run() + self.dialog.hide() + if res == gtk.RESPONSE_OK: + line = self.entry.get_text() + "\n" else: - combo.set_active(0) - - 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) - 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 - comps = template.comps - for c in comps: - checkbox = gtk.CheckButton(c.description) - checkbox.set_active(c.on_by_default) - checkbox.set_data("name",c.name) - vbox.pack_start(checkbox) - checkbox.show() + line = None + return line - def on_button_custom_clicked(self, widget, data): - #print "on_button_custom_clicked()" - # this hide here is ugly :/ - self.main.hide() - # check if we are in add or edit-matched mode - if self.source_entry: - # we are in "edit" mode - # get the SourceEntry as it is now (with local changes) - # and display the "old" edit dialog - self.selected_comps = [] - vbox = self.gladexml.get_widget("vbox_comps") - vbox.foreach(self.get_enabled_comps) - source_entry = self._make_source_entry() - # since we're passing the SourceEntry as it is now, - # this SourceEntry needs to be in the sourceslist, - # so temporarily swap the original for the current - if source_entry: - self.sourceslist.list[self.source_entry_index] = source_entry - dialog = dialog_edit.dialog_edit(self.parent, self.sourceslist, - source_entry, self.datadir) - res = dialog.run() - if res == gtk.RESPONSE_CANCEL: - # restore original SourceEntry - self.sourceslist.list[self.source_entry_index] = self.source_entry - elif res == gtk.RESPONSE_OK: - # the sourceslist is allready updated, but we'll overwrite it - # in self.run if we're not carefull - self.custom = True + def check_line(self, *args): + """ + Check for a valid apt line and set the sensitiveness of the + button 'add' accordingly + """ + 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: - # we are in "add" mode - 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.button_add.set_sensitive(True) - def get_enabled_comps(self, checkbutton): - if checkbutton.get_active(): - self.selected_comps.append(checkbutton.get_data("name")) - - def _make_source_entry(self): - " helper for the 'edit' mode " - # we use "selected" for pretty much everything *but* we use - # self.source_entry.uri to make sure that the mirror information is - # preserved - - line = "%s %s %s" % (self.selected.type, self.source_entry.uri, self.selected.dist) - if self.source_entry.disabled: - line = "#" + line - if len(self.selected.comps) > 0 and len(self.selected_comps) == 0: - line = "#" + line - elif len(self.selected_comps) > 0: - line += " " + " ".join(self.selected_comps) - if self.selected.matches(self.source_entry) and self.source_entry.comment != "": - line += " #"+self.source_entry.comment - line += "\n" - return aptsources.SourceEntry(line,self.source_entry.file) - - 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) - - # check if we are in 'add' or 'edit' mode - if self.source_entry: - # 'edit' - ode - # check if there are no selected components - if len(self.selected_comps) < 1: - # remove the source - self.sourceslist.remove(self.source_entry) - else: - if not self.custom: - entry = self._make_source_entry() - if entry: - self.sourceslist.list[self.source_entry_index] = entry - else: - # 'add' mode - self.sourceslist.add(self.selected.type, - self.selected.uri, - self.selected.dist, - self.selected_comps) - self.main.hide() - return res diff --git a/SoftwareProperties/dialog_add_sources_list.py b/SoftwareProperties/dialog_add_sources_list.py new file mode 100644 index 00000000..b0a868b1 --- /dev/null +++ b/SoftwareProperties/dialog_add_sources_list.py @@ -0,0 +1,125 @@ +#!/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, + get_comparable, datadir, file): + print file + self.parent = parent + self.source_renderer = source_renderer + self.sourceslist = sourceslist + self.get_comparable = get_comparable + 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_add_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.button_replace = self.glade.get_widget("button_replace") + self.treeview = self.glade.get_widget("treeview_sources") + 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.new_sources = SingleSourcesList(self.file) + except: + self.error() + return + + # show the found channels or an error message + if len(self.new_sources.list) > 0: + counter = 0 + + for source in self.new_sources.list: + if source.invalid or source.disabled: + continue + self.new_sources.matcher.match(source) + # sort the list + self.new_sources.list.sort(key=self.get_comparable) + + for source in self.new_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("Install software additionally or " + "only from this source?", + "Install software additionally or " + "only from these sources?", + counter) + body = _("You can either add the following sources or replace your " + "current sources by them. Only install software from " + "trusted sources.") + self.label.set_markup("<big><b>%s</b></big>\n\n%s" % (header, body)) + else: + self.error() + return + + def error(self): + self.button_add.hide() + self.button_cancel.set_use_stock(True) + self.button_cancel.set_label("gtk-close") + self.button_replace.hide() + self.scrolled.hide() + self.image.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) + header = _("There are no sources to install software from") + body = _("The file '%s' does not contain any valid " + "software sources." % self.file) + self.label.set_markup("<big><b>%s</b></big>\n\n%s" % (header, body)) + + def run(self): + res = self.dialog.run() + self.dialog.destroy() + return res, self.new_sources + + 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) |
