home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / python2.4 / site-packages / UpdateManager / Common / aptsources.py < prev    next >
Encoding:
Python Source  |  2006-08-24  |  21.0 KB  |  673 lines

  1. # aptsource.py.in - parse sources.list
  2. #  
  3. #  Copyright (c) 2004,2005 Canonical
  4. #                2004 Michiel Sikkes
  5. #  
  6. #  Author: Michiel Sikkes <michiel@eyesopened.nl>
  7. #          Michael Vogt <mvo@debian.org>
  8. #  This program is free software; you can redistribute it and/or 
  9. #  modify it under the terms of the GNU General Public License as 
  10. #  published by the Free Software Foundation; either version 2 of the
  11. #  License, or (at your option) any later version.
  12. #  This program is distributed in the hope that it will be useful,
  13. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. #  GNU General Public License for more details.
  16. #  You should have received a copy of the GNU General Public License
  17. #  along with this program; if not, write to the Free Software
  18. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  19. #  USA
  20.  
  21. import string
  22. import gettext
  23. import re
  24. import apt_pkg
  25. import glob
  26. import shutil
  27. import time
  28. import os.path
  29.  
  30. #import pdb
  31.  
  32. from UpdateManager.Common.DistInfo import DistInfo
  33.  
  34.  
  35. # some global helpers
  36. def is_mirror(master_uri, compare_uri):
  37.   """check if the given add_url is idential or a mirror of orig_uri
  38.     e.g. master_uri = archive.ubuntu.com
  39.       compare_uri = de.archive.ubuntu.com
  40.       -> True
  41.   """
  42.   # remove traling spaces and "/"
  43.   compare_uri = compare_uri.rstrip("/ ")
  44.   master_uri = master_uri.rstrip("/ ")
  45.   # uri is identical
  46.   if compare_uri == master_uri:
  47.     #print "Identical"
  48.     return True
  49.   # add uri is a master site and orig_uri has the from "XX.mastersite"
  50.   # (e.g. de.archive.ubuntu.com)
  51.   try:
  52.     compare_srv = compare_uri.split("//")[1]
  53.     master_srv = master_uri.split("//")[1]
  54.     #print "%s == %s " % (add_srv, orig_srv)
  55.   except IndexError: # ok, somethings wrong here
  56.     #print "IndexError"
  57.     return False
  58.   # remove the leading "<country>." (if any) and see if that helps
  59.   if "." in compare_srv and \
  60.          compare_srv[compare_srv.index(".")+1:] == master_srv:
  61.     #print "Mirror"
  62.     return True
  63.   return False
  64.  
  65. def uniq(s):
  66.   """ simple and efficient way to return uniq list """
  67.   return list(set(s))
  68.  
  69.  
  70.  
  71. # actual source.list entries
  72. class SourceEntry:
  73.  
  74.   def __init__(self, line,file=None):
  75.     self.invalid = False
  76.     self.disabled = False
  77.     self.type = ""
  78.     self.uri = ""
  79.     self.dist = ""
  80.     self.comps = []
  81.     self.comment = ""
  82.     self.line = line
  83.     if file == None:
  84.       file = apt_pkg.Config.FindDir("Dir::Etc")+apt_pkg.Config.Find("Dir::Etc::sourcelist")
  85.     self.file = file
  86.     self.parse(line)
  87.     self.template = None
  88.     self.children = []
  89.  
  90.   # works mostely like split but takes [] into account
  91.   def mysplit(self, line):
  92.     line = string.strip(line)
  93.     pieces = []
  94.     tmp = ""
  95.     # we are inside a [..] block
  96.     p_found = False
  97.     space_found = False
  98.     for i in range(len(line)):
  99.       if line[i] == "[":
  100.         p_found=True
  101.         tmp += line[i]
  102.       elif line[i] == "]":
  103.         p_found=False
  104.         tmp += line[i]
  105.       elif space_found and not line[i].isspace(): # we skip one or more space
  106.         space_found = False
  107.         pieces.append(tmp)
  108.         tmp = line[i]
  109.       elif line[i].isspace() and not p_found:     # found a whitespace
  110.         space_found = True
  111.       else:
  112.         tmp += line[i]
  113.     # append last piece
  114.     if len(tmp) > 0:
  115.       pieces.append(tmp)
  116.     return pieces
  117.  
  118.  
  119.   # parse a given source line and split it into the fields we need
  120.   def parse(self,line):
  121.     line  = string.strip(self.line)
  122.     #print line
  123.     # check if the source is enabled/disabled
  124.     if line == "" or line == "#": # empty line
  125.       self.invalid = True
  126.       return
  127.     if line[0] == "#":
  128.       self.disabled = True
  129.       pieces = string.split(line[1:])
  130.       # if it looks not like a disabled deb line return 
  131.       if not (pieces[0] == "deb" or pieces[0] == "deb-src"):
  132.         self.invalid = True
  133.         return
  134.       else:
  135.         line = line[1:]
  136.     # check for another "#" in the line (this is treated as a comment)
  137.     i = line.find("#")
  138.     if i > 0:
  139.       self.comment = line[i+1:]
  140.       line = line[:i]
  141.     # source is ok, split it and see what we have
  142.     pieces = self.mysplit(line)
  143.     # Sanity check
  144.     if len(pieces) < 3:
  145.         self.invalid = True
  146.         return
  147.     # Type, deb or deb-src
  148.     self.type = string.strip(pieces[0])
  149.     # Sanity check
  150.     if self.type not in ("deb", "deb-src"):
  151.       self.invalid = True
  152.       return
  153.     # URI
  154.     self.uri = string.strip(pieces[1])
  155.     if len(self.uri) < 1:
  156.       self.invalid = True
  157.     # distro and components (optional)
  158.     # Directory or distro
  159.     self.dist = string.strip(pieces[2])
  160.     if len(pieces) > 3:
  161.       # List of components
  162.       self.comps = pieces[3:]
  163.     else:
  164.       self.comps = []
  165.  
  166.     #print self.__dict__
  167.  
  168.  
  169.   # set enabled/disabled
  170.   def set_enabled(self, new_value):
  171.     self.disabled = not new_value
  172.     # enable, remove all "#" from the start of the line
  173.     if new_value == True:
  174.       i=0
  175.       self.line = string.lstrip(self.line)
  176.       while self.line[i] == "#":
  177.         i += 1
  178.       self.line = self.line[i:]
  179.     else:
  180.       # disabled, add a "#" 
  181.       if string.strip(self.line)[0] != "#":
  182.         self.line = "#" + self.line
  183.  
  184.   def __str__(self):
  185.     """ debug helper """
  186.     return self.str().strip()
  187.  
  188.   def str(self):
  189.     """ return the current line as string """
  190.     if self.invalid:
  191.       return self.line
  192.     line = ""
  193.     if self.disabled:
  194.       line = "# "
  195.     line += "%s %s %s" % (self.type, self.uri, self.dist)
  196.     if len(self.comps) > 0:
  197.       line += " " + " ".join(self.comps)
  198.     if self.comment != "":
  199.       line += " #"+self.comment
  200.     line += "\n"
  201.     return line
  202.     
  203. # the SourceList file as a class
  204. class NullMatcher(object):
  205.   def match(self, s):
  206.     return True
  207.  
  208. class SourcesList:
  209.   def __init__(self, withMatcher=True):
  210.     self.list = []      # of Type SourceEntries
  211.     if withMatcher:
  212.       self.matcher = SourceEntryMatcher()
  213.     else:
  214.       self.matcher = NullMatcher()
  215.     self.refresh()
  216.  
  217.   def refresh(self):
  218.     self.list = []
  219.     # read sources.list
  220.     dir = apt_pkg.Config.FindDir("Dir::Etc")
  221.     file = apt_pkg.Config.Find("Dir::Etc::sourcelist")
  222.     self.load(dir+file)
  223.     # read sources.list.d
  224.     partsdir = apt_pkg.Config.FindDir("Dir::Etc::sourceparts")
  225.     for file in glob.glob("%s/*.list" % partsdir):
  226.       self.load(file)
  227.     # check if the source item fits a predefined template
  228.     for source in self.list:
  229.         if source.invalid == False:
  230.             self.matcher.match(source)
  231.  
  232.   def __iter__(self):
  233.     for entry in self.list:
  234.       yield entry
  235.     raise StopIteration
  236.  
  237.   def add(self, type, uri, dist, comps, comment="", pos=-1, file=None):
  238.     """
  239.     Add a new source to the sources.list.
  240.     The method will search for existing matching repos and will try to 
  241.     reuse them as far as possible
  242.     """
  243.     for source in self.list:
  244.       # if there is a repo with the same (type, uri, dist) just add the
  245.       # components
  246.       if source.disabled == False and source.invalid == False and \
  247.          source.type == type and uri == source.uri and \
  248.          source.dist == dist:
  249.         comps = uniq(source.comps + comps)
  250.         source.comps = comps
  251.         return source
  252.       # if there is a corresponding repo which is disabled, enable it
  253.       elif source.disabled == True and source.invalid == False and \
  254.            source.type == type and uri == source.uri and \
  255.            source.dist == dist and \
  256.            len(set(source.comps) & set(comps)) == len(comps):
  257.         source.disabled = False
  258.         return source
  259.     # there isn't any matching source, so create a new line and parse it
  260.     line = "%s %s %s" % (type,uri,dist)
  261.     for c in comps:
  262.       line = line + " " + c;
  263.     if comment != "":
  264.       line = "%s #%s\n" %(line,comment)
  265.     line = line + "\n"
  266.     new_entry = SourceEntry(line)
  267.     if file != None:
  268.       new_entry.file = file
  269.     self.matcher.match(new_entry)
  270.     self.list.insert(pos, new_entry)
  271.     return new_entry
  272.  
  273.   def remove(self, source_entry):
  274.     self.list.remove(source_entry)
  275.  
  276.   def restoreBackup(self, backup_ext):
  277.     " restore sources.list files based on the backup extension "
  278.     dir = apt_pkg.Config.FindDir("Dir::Etc")
  279.     file = apt_pkg.Config.Find("Dir::Etc::sourcelist")
  280.     if os.path.exists(dir+file+backup_ext):
  281.       shutil.copy(dir+file+backup_ext,dir+file)
  282.     # now sources.list.d
  283.     partsdir = apt_pkg.Config.FindDir("Dir::Etc::sourceparts")
  284.     for file in glob.glob("%s/*.list" % partsdir):
  285.       if os.path.exists(file+backup_ext):
  286.         shutil.copy(file+backup_ext,file)
  287.  
  288.   def backup(self, backup_ext=None):
  289.     """ make a backup of the current source files, if no backup extension
  290.         is given, the current date/time is used (and returned) """
  291.     already_backuped = set()
  292.     if backup_ext == None:
  293.       backup_ext = time.strftime("%y%m%d.%H%M")
  294.     for source in self.list:
  295.       if not source.file in already_backuped:
  296.         shutil.copy(source.file,"%s%s" % (source.file,backup_ext))
  297.     return backup_ext
  298.  
  299.   def load(self,file):
  300.     """ (re)load the current sources """
  301.     try:
  302.       f = open(file, "r")
  303.       lines = f.readlines()
  304.       for line in lines:
  305.         source = SourceEntry(line,file)
  306.         self.list.append(source)
  307.     except:
  308.       print "could not open file '%s'" % file
  309.     else:
  310.       f.close()
  311.  
  312.   def save(self):
  313.     """ save the current sources """
  314.     files = {}
  315.     for source in self.list:
  316.       if not files.has_key(source.file):
  317.         files[source.file]=open(source.file,"w")
  318.       files[source.file].write(source.str())
  319.     for f in files:
  320.       files[f].close()
  321.  
  322.   def check_for_relations(self, sources_list):
  323.     """get all parent and child channels in the sources list"""
  324.     parents = []
  325.     used_child_templates = {}
  326.     for source in sources_list:
  327.       # try to avoid checking uninterressting sources
  328.       if source.template == None:
  329.         continue
  330.       # set up a dict with all used child templates and corresponding 
  331.       # source entries
  332.       if source.template.child == True:
  333.           key = source.template
  334.           if not used_child_templates.has_key(key):
  335.               used_child_templates[key] = []
  336.           temp = used_child_templates[key]
  337.           temp.append(source)
  338.       else:
  339.           # store each source with children aka. a parent :)
  340.           if len(source.template.children) > 0:
  341.               parents.append(source)
  342.     #print self.used_child_templates
  343.     #print self.parents
  344.     return (parents, used_child_templates)
  345.  
  346. # templates for the add dialog
  347. class SourceEntryTemplate(SourceEntry):
  348.   def __init__(self,a_type,uri,dist,description,comps):
  349.     self.comps_descriptions = []
  350.     self.type = a_type
  351.     self.uri = uri
  352.     self.dist = dist
  353.     self.description = description
  354.     self.comps = comps
  355.  
  356.   def matches(self,source_entry):
  357.     """ check if a given source_entry matches this one """
  358.     if (self.type != source_entry.type):
  359.       return False
  360.     if (self.dist != source_entry.dist):
  361.       return False
  362.     if not is_mirror(self.uri,source_entry.uri):
  363.       return False
  364.     for e_comp in source_entry.comps:
  365.       for t_comp in self.comps:
  366.         if e_comp == t_comp.name: break
  367.       else:
  368.         return False
  369.     return True
  370.  
  371. class SourceCompTemplate:
  372.   def __init__(self, name, description, on_by_default):
  373.     self.name = name
  374.     self.description = description
  375.     self.on_by_default = on_by_default
  376.  
  377. class SourceEntryTemplates:
  378.   def __init__(self,datadir):
  379.     _ = gettext.gettext
  380.     self.templates = []
  381.  
  382.     dinfo = DistInfo (base_dir=datadir+"channels/")
  383.  
  384.     for suite in dinfo.suites:
  385.       comps = []
  386.       for comp in suite.components:
  387.         comps.append(SourceCompTemplate(comp.name, _(comp.description),
  388.                                         comp.enabled))
  389.       self.templates.append (SourceEntryTemplate(suite.repository_type,
  390.                                                  suite.base_uri,
  391.                                                  suite.name,
  392.                                                  suite.description,
  393.                                                  comps))
  394.  
  395. # matcher class to make a source entry look nice
  396. # lots of predefined matchers to make it i18n/gettext friendly
  397. class SourceEntryMatcher:
  398.   class MatchType:
  399.     def __init__(self, a_type,a_descr):
  400.       self.type = a_type
  401.       self.description = a_descr
  402.   
  403.   class MatchDist:
  404.     def __init__(self,a_uri,a_dist, a_descr,l_comps, l_comps_descr):
  405.       self.uri = a_uri
  406.       self.dist = a_dist
  407.       self.description = a_descr
  408.       self.comps = l_comps
  409.       self.comps_descriptions = l_comps_descr
  410.  
  411.   def __init__(self):
  412.     self.templates = []
  413.     # Get the human readable channel and comp names from the channel .infos
  414.     spec_files = glob.glob("/usr/share/update-manager/channels/*.info")
  415.     for f in spec_files:
  416.         f = os.path.basename(f)
  417.         i = f.find(".info")
  418.         f = f[0:i]
  419.         dist = DistInfo(f)
  420.         for suite in dist.suites:
  421.             if suite.match_uri != None:
  422.                 self.templates.append(suite)
  423.     return
  424.  
  425.   def match(self, source):
  426.     """Add a matching template to the source"""
  427.     _ = gettext.gettext
  428.     found = False
  429.     for template in self.templates:
  430.       #print "'%s'" %source.uri
  431.       if re.search(template.match_uri, source.uri) and \
  432.          re.match(template.match_name, source.dist):
  433.         found = True
  434.         source.template = template
  435.         break
  436.     return found
  437.  
  438. class Distribution:
  439.   def __init__(self):
  440.     """"
  441.     Container for distribution specific informations
  442.     """
  443.     # LSB information
  444.     self.id = ""
  445.     self.codename = ""
  446.     self.description = ""
  447.     self.release = ""
  448.  
  449.     # get the LSB information
  450.     lsb_info = []
  451.     for lsb_option in ["-i", "-c", "-d", "-r"]:
  452.         pipe = os.popen("lsb_release %s -s" % lsb_option)
  453.         lsb_info.append(pipe.read().strip())
  454.         del pipe
  455.     (self.id, self.codename, self.description, self.release) = lsb_info
  456.  
  457.     # get a list of country codes and real names
  458.     self.countries = {}
  459.     try:
  460.         f = open("/usr/share/iso-codes/iso_3166.tab", "r")
  461.         lines = f.readlines()
  462.         for line in lines:
  463.             parts = line.split("\t")
  464.             self.countries[parts[0].lower()] = parts[1]
  465.     except:
  466.         print "could not open file '%s'" % file
  467.     else:
  468.         f.close()
  469.  
  470.   def get_sources(self, sources_list):
  471.     """
  472.     Find the corresponding template, main and child sources 
  473.     for the distribution 
  474.     """
  475.     # corresponding sources
  476.     self.source_template = None
  477.     self.child_sources = []
  478.     self.main_sources = []
  479.     self.disabled_sources = []
  480.     self.cdrom_sources = []
  481.     self.download_comps = []
  482.     self.enabled_comps = []
  483.     self.cdrom_comps = []
  484.     self.used_media = []
  485.     self.get_source_code = False
  486.     self.source_code_sources = []
  487.  
  488.     # location of the sources
  489.     self.default_server = ""
  490.     self.main_server = ""
  491.     self.nearest_server = ""
  492.     self.used_servers = []
  493.  
  494.     # find the distro template
  495.     for template in sources_list.matcher.templates:
  496.         if template.name == self.codename and\
  497.            template.distribution == self.id:
  498.             #print "yeah! found a template for %s" % self.description
  499.             #print template.description, template.base_uri, template.components
  500.             self.source_template = template
  501.             break
  502.     if self.source_template == None:
  503.         print "Error: could not find a distribution template"
  504.         # FIXME: will go away - only for debugging issues
  505.         sys.exit(1)
  506.  
  507.     # find main and child sources
  508.     media = []
  509.     comps = []
  510.     cdrom_comps = []
  511.     enabled_comps = []
  512.     source_code = []
  513.     for source in sources_list.list:
  514.         if source.invalid == False and\
  515.            source.dist == self.codename and\
  516.            source.template and\
  517.            source.template.name == self.codename:
  518.             #print "yeah! found a distro repo:  %s" % source.line
  519.             # cdroms need do be handled differently
  520.             if source.uri.startswith("cdrom:") and \
  521.                source.disabled == False:
  522.                 self.cdrom_sources.append(source)
  523.                 cdrom_comps.extend(source.comps)
  524.             elif source.uri.startswith("cdrom:") and \
  525.                  source.disabled == True:
  526.                 self.cdrom_sources.append(source)
  527.             elif source.type == "deb" and source.disabled == False:
  528.                 self.main_sources.append(source)
  529.                 comps.extend(source.comps)
  530.                 media.append(source.uri)
  531.             elif source.type == "deb" and source.disabled == True:
  532.                 self.disabled_sources.append(source)
  533.             elif source.type.endswith("-src") and source.disabled == False:
  534.                 self.source_code_sources.append(source)
  535.             elif source.type.endswith("-src") and source.disabled == True:
  536.                 self.disabled_sources.append(source)
  537.         if source.template in self.source_template.children:
  538.             #print "yeah! child found: %s" % source.template.name
  539.             if source.type == "deb":
  540.                 self.child_sources.append(source)
  541.             elif source.type == "deb-src":
  542.                 self.source_code_sources.append(source)
  543.     self.download_comps = set(comps)
  544.     self.cdrom_comps = set(cdrom_comps)
  545.     enabled_comps.extend(comps)
  546.     enabled_comps.extend(cdrom_comps)
  547.     self.enabled_comps = set(enabled_comps)
  548.     self.used_media = set(media)
  549.  
  550.     self.get_mirrors()
  551.   
  552.   def get_mirrors(self):
  553.     """
  554.     Provide a set of mirrors where you can get the distribution from
  555.     """
  556.     # the main server is stored in the template
  557.     self.main_server = self.source_template.base_uri
  558.  
  559.     # try to guess the nearest mirror from the locale
  560.     # FIXME: for debian we need something different
  561.     if self.id == "Ubuntu":
  562.         locale = os.getenv("LANG", default="en.UK")
  563.         a = locale.find("_")
  564.         z = locale.find(".")
  565.         if z == -1:
  566.             z = len(locale)
  567.         country_code = locale[a+1:z].lower()
  568.         self.nearest_server = "http://%s.archive.ubuntu.com/ubuntu/" % \
  569.                               country_code
  570.         if self.countries.has_key(country_code):
  571.             self.country = self.countries[country_code]
  572.         else:
  573.             self.country = None
  574.  
  575.     # other used servers
  576.     for medium in self.used_media:
  577.         if not medium.startswith("cdrom:"):
  578.             # seems to be a network source
  579.             self.used_servers.append(medium)
  580.  
  581.     if len(self.main_sources) == 0:
  582.         self.default_server = self.main_server
  583.     else:
  584.         self.default_server = self.main_sources[0].uri
  585.  
  586.   def add_source(self, sources_list, type=None, 
  587.                  uri=None, dist=None, comps=None, comment=""):
  588.     """
  589.     Add distribution specific sources
  590.     """
  591.     if uri == None:
  592.         # FIXME: Add support for the server selector
  593.         uri = self.default_server
  594.     if dist == None:
  595.         dist = self.codename
  596.     if comps == None:
  597.         comps = list(self.enabled_comps)
  598.     if type == None:
  599.         type = "deb"
  600.     if comment == "":
  601.         comment == "Added by software-properties"
  602.     new_source = sources_list.add(type, uri, dist, comps, comment)
  603.     # if source code is enabled add a deb-src line after the new
  604.     # source
  605.     if self.get_source_code == True and not type.endswith("-src"):
  606.         sources_list.add("%s-src" % type, uri, dist, comps, comment, 
  607.                          file=new_source.file,
  608.                          pos=sources_list.list.index(new_source)+1)
  609.  
  610.   def enable_component(self, sourceslist, comp):
  611.     """
  612.     Disable a component in all main, child and source code sources
  613.     (excluding cdrom based sources)
  614.  
  615.     sourceslist:  an aptsource.sources_list
  616.     comp:         the component that should be enabled
  617.     """
  618.     sources = []
  619.     sources.extend(self.main_sources)
  620.     sources.extend(self.child_sources)
  621.     sources.extend(self.source_code_sources)
  622.     # check if there is a main source at all
  623.     if len(self.main_sources) < 1:
  624.         # create a new main source
  625.         self.add_source(sourceslist, comps=["%s"%comp])
  626.     else:
  627.         # add the comp to all main, child and source code sources
  628.         for source in sources:
  629.             if comp not in source.comps:
  630.                 source.comps.append(comp)
  631.     if self.get_source_code == True:
  632.         for source in self.source_code_sources:
  633.             if comp not in source.comps: source.comps.append(comp)
  634.  
  635.   def disable_component(self, sourceslist, comp):
  636.     """
  637.     Disable a component in all main, child and source code sources
  638.     (excluding cdrom based sources)
  639.     """
  640.     sources = []
  641.     sources.extend(self.main_sources)
  642.     sources.extend(self.child_sources)
  643.     sources.extend(self.source_code_sources)
  644.     if comp in self.cdrom_comps:
  645.         sources = []
  646.         sources.extend(self.main_sources)
  647.  
  648.     for source in sources:
  649.         if comp in source.comps: 
  650.             source.comps.remove(comp)
  651.             if len(source.comps) < 1: 
  652.                sourceslist.remove(source)
  653.  
  654.  
  655. # some simple tests
  656. if __name__ == "__main__":
  657.   apt_pkg.InitConfig()
  658.   sources = SourcesList()
  659.  
  660.   for entry in sources:
  661.     print entry.str()
  662.     #print entry.uri
  663.  
  664.   mirror = is_mirror("http://archive.ubuntu.com/ubuntu/",
  665.                      "http://de.archive.ubuntu.com/ubuntu/")
  666.   print "is_mirror(): %s" % mirror
  667.   
  668.   print is_mirror("http://archive.ubuntu.com/ubuntu",
  669.                   "http://de.archive.ubuntu.com/ubuntu/")
  670.