distro.py :  » Installer » Zero-Install » zeroinstall-injector-0.47 » zeroinstall » injector » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Installer » Zero Install 
Zero Install » zeroinstall injector 0.47 » zeroinstall » injector » distro.py
"""
Integration with native distribution package managers.
@since: 0.28
"""

# Copyright (C) 2009, Thomas Leonard
# See the README file for details, or visit http://0install.net.

from zeroinstall import _
import os, re, glob, subprocess, sys
from logging import warn,info
from zeroinstall.injector import namespaces,model,arch
from zeroinstall.support import basedir

_dotted_ints = '[0-9]+(?:\.[0-9]+)*'

# This matches a version number that would be a valid Zero Install version without modification
_zeroinstall_regexp = '(?:%s)(?:-(?:pre|rc|post|)(?:%s))*' % (_dotted_ints, _dotted_ints)

# This matches the interesting bits of distribution version numbers
_version_regexp = '(%s)(-r%s)?' % (_zeroinstall_regexp, _dotted_ints)

# We try to do updates atomically without locking, but we don't worry too much about
# duplicate entries or being a little out of sync with the on-disk copy.
class Cache(object):
  def __init__(self, cache_leaf, source):
    """Maintain a cache file (e.g. ~/.cache/0install.net/injector/$name).
    If the size or mtime of $source has changed, reset the cache first."""
    self.cache_leaf = cache_leaf
    self.source = source
    self.cache_dir = basedir.save_cache_path(namespaces.config_site,
               namespaces.config_prog)
    try:
      self._load_cache()
    except Exception, ex:
      info(_("Failed to load cache (%s). Flushing..."), ex)
      self.flush()

  def flush(self):
    try:
      info = os.stat(self.source)
      mtime = int(info.st_mtime)
      size = info.st_size
    except Exception, ex:
      warn("Failed to stat %s: %s", self.source, ex)
      mtime = size = 0
    self.cache = {}
    import tempfile
    tmp, tmp_name = tempfile.mkstemp(dir = self.cache_dir)
    data = "mtime=%d\nsize=%d\n\n" % (mtime, size)
    while data:
      wrote = os.write(tmp, data)
      data = data[wrote:]
    os.rename(tmp_name, os.path.join(self.cache_dir, self.cache_leaf))

  # Populate self.cache from our saved cache file.
  # Throws an exception if the cache doesn't exist or has the wrong format.
  def _load_cache(self):
    self.cache = cache = {}
    stream = file(os.path.join(self.cache_dir, self.cache_leaf))
    try:
      info = os.stat(self.source)
      meta = {}
      for line in stream:
        line = line.strip()
        if not line:
          break
        key, value = line.split('=', 1)
        if key == 'mtime':
          if int(value) != int(info.st_mtime):
            raise Exception("Modification time of %s has changed" % self.source)
        if key == 'size':
          if int(value) != info.st_size:
            raise Exception("Size of %s has changed" % self.source)

      for line in stream:
        key, value = line.split('=', 1)
        cache[key] = value[:-1]
    finally:
      stream.close()

  def get(self, key):
    return self.cache.get(key, None)

  def put(self, key, value):
    cache_path = os.path.join(self.cache_dir, self.cache_leaf)
    self.cache[key] = value
    try:
      stream = file(cache_path, 'a')
      try:
        stream.write('%s=%s\n' % (key, value))
      finally:
        stream.close()
    except Exception, ex:
      warn("Failed to write to cache %s: %s=%s: %s", cache_path, key, value, ex)

def try_cleanup_distro_version(version):
  """Try to turn a distribution version string into one readable by Zero Install.
  We do this by stripping off anything we can't parse.
  @return: the part we understood, or None if we couldn't parse anything
  @rtype: str"""
  match = re.match(_version_regexp, version)
  if match:
    version, revision = match.groups()
    if revision is None:
      return version
    else:
      return '%s-%s' % (version, revision[2:])
  return None

class Distribution(object):
  """Represents a distribution with which we can integrate.
  Sub-classes should specialise this to integrate with the package managers of
  particular distributions. This base class ignores the native package manager.
  @since: 0.28
  """

  def get_package_info(self, package, factory):
    """Get information about the given package.
    Add zero or more implementations using the factory (typically at most two
    will be added; the currently installed version and the latest available).
    @param package: package name (e.g. "gimp")
    @type package: str
    @param factory: function for creating new DistributionImplementation objects from IDs
    @type factory: str -> L{model.DistributionImplementation}
    """
    return

  def get_score(self, distribution):
    """Indicate how closely the host distribution matches this one.
    The <package-implementation> with the highest score is passed
    to L{Distribution.get_package_info}. If several elements get
    the same score, get_package_info is called for all of them.
    @param distribution: a distribution name
    @type distribution: str
    @return: an integer, or None if there is no match at all
    @rtype: int | None
    """
    return 0

class CachedDistribution(Distribution):
  """For distributions where querying the package database is slow (e.g. requires running
  an external command), we cache the results.
  @since: 0.39
  """

  def __init__(self, db_status_file):
    """@param db_status_file: update the cache when the timestamp of this file changes"""
    self._status_details = os.stat(db_status_file)

    self.versions = {}
    self.cache_dir = basedir.save_cache_path(namespaces.config_site,
               namespaces.config_prog)

    try:
      self._load_cache()
    except Exception, ex:
      info(_("Failed to load distribution database cache (%s). Regenerating..."), ex)
      try:
        self.generate_cache()
        self._load_cache()
      except Exception, ex:
        warn(_("Failed to regenerate distribution database cache: %s"), ex)

  def _load_cache(self):
    """Load {cache_leaf} cache file into self.versions if it is available and up-to-date.
    Throws an exception if the cache should be (re)created."""
    stream = file(os.path.join(self.cache_dir, self.cache_leaf))

    cache_version = None
    for line in stream:
      if line == '\n':
        break
      name, value = line.split(': ')
      if name == 'mtime' and int(value) != int(self._status_details.st_mtime):
        raise Exception(_("Modification time of package database file has changed"))
      if name == 'size' and int(value) != self._status_details.st_size:
        raise Exception(_("Size of package database file has changed"))
      if name == 'version':
        cache_version = int(value)
    else:
      raise Exception(_('Invalid cache format (bad header)'))

    if cache_version is None:
      raise Exception(_('Old cache format'))

    versions = self.versions
    for line in stream:
      package, version, zi_arch = line[:-1].split('\t')
      versionarch = (version, intern(zi_arch))
      if package not in versions:
        versions[package] = [versionarch]
      else:
        versions[package].append(versionarch)

  def _write_cache(self, cache):
    #cache.sort()   # Might be useful later; currently we don't care
    import tempfile
    fd, tmpname = tempfile.mkstemp(prefix = 'zeroinstall-cache-tmp',
                 dir = self.cache_dir)
    try:
      stream = os.fdopen(fd, 'wb')
      stream.write('version: 2\n')
      stream.write('mtime: %d\n' % int(self._status_details.st_mtime))
      stream.write('size: %d\n' % self._status_details.st_size)
      stream.write('\n')
      for line in cache:
        stream.write(line + '\n')
      stream.close()

      os.rename(tmpname,
          os.path.join(self.cache_dir,
                 self.cache_leaf))
    except:
      os.unlink(tmpname)
      raise

# Maps machine type names used in packages to their Zero Install versions
_canonical_machine = {
  'all' : '*',
  'any' : '*',
  'amd64': 'x86_64',
  'i386': 'i386',
}

host_machine = os.uname()[-1]
def canonical_machine(package_machine):
  machine = _canonical_machine.get(package_machine, None)
  if machine is None:
    # Safe default if we can't understand the arch
    return host_machine
  return machine

class DebianDistribution(CachedDistribution):
  """A dpkg-based distribution."""

  cache_leaf = 'dpkg-status.cache'

  def __init__(self, dpkg_status, pkgcache):
    CachedDistribution.__init__(self, dpkg_status)
    self.apt_cache = Cache('apt-cache-cache', pkgcache)

  def generate_cache(self):
    cache = []

    for line in os.popen("dpkg-query -W --showformat='${Package}\t${Version}\t${Architecture}\n'"):
      package, version, debarch = line.split('\t', 2)
      if ':' in version:
        # Debian's 'epoch' system
        version = version.split(':', 1)[1]
      clean_version = try_cleanup_distro_version(version)
      if clean_version:
        cache.append('%s\t%s\t%s' % (package, clean_version, canonical_machine(debarch.strip())))
      else:
        warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': package})

    self._write_cache(cache)

  def get_package_info(self, package, factory):
    try:
      installed_version, machine = self.versions[package][0]
    except KeyError:
      installed_version = None
    else:
      impl = factory('package:deb:%s:%s' % (package, installed_version))
      impl.version = model.parse_version(installed_version)
      if machine != '*':
        impl.machine = machine

    # Check to see whether we could get a newer version using apt-get

    cached = self.apt_cache.get(package)
    if cached is None:
      try:
        null = os.open('/dev/null', os.O_WRONLY)
        child = subprocess.Popen(['apt-cache', 'show', '--no-all-versions', '--', package], stdout = subprocess.PIPE, stderr = null)
        os.close(null)

        arch = version = None
        for line in child.stdout:
          line = line.strip()
          if line.startswith('Version: '):
            version = line[9:]
            if ':' in version:
              # Debian's 'epoch' system
              version = version.split(':', 1)[1]
            version = try_cleanup_distro_version(version)
          elif line.startswith('Architecture: '):
            arch = canonical_machine(line[14:].strip())
        if version and arch:
          cached = '%s\t%s' % (version, arch)
        else:
          cached = '-'
        child.wait()
      except Exception, ex:
        warn("'apt-cache show %s' failed: %s", package, ex)
        cached = '-'
      # (multi-arch support? can there be multiple candidates?)
      self.apt_cache.put(package, cached)

    if cached != '-':
      candidate_version, candidate_arch = cached.split('\t')
      if candidate_version and candidate_version != installed_version:
        impl = factory('package:deb:%s:%s' % (package, candidate_version))
        impl.version = model.parse_version(candidate_version)
        impl.installed = False
        if candidate_arch != '*':
          impl.machine = candidate_arch

  def get_score(self, disto_name):
    return int(disto_name == 'Debian')

class RPMDistribution(CachedDistribution):
  """An RPM-based distribution."""

  cache_leaf = 'rpm-status.cache'

  def generate_cache(self):
    cache = []

    for line in os.popen("rpm -qa --qf='%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\n'"):
      package, version, rpmarch = line.split('\t', 2)
      if package == 'gpg-pubkey':
        continue
      if rpmarch == 'amd64\n':
        zi_arch = 'x86_64'
      elif rpmarch == 'noarch\n' or rpmarch == "(none)\n":
        zi_arch = '*'
      else:
        zi_arch = rpmarch.strip()
      clean_version = try_cleanup_distro_version(version)
      if clean_version:
        cache.append('%s\t%s\t%s' % (package, clean_version, zi_arch))
      else:
        warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': package})

    self._write_cache(cache)

  def get_package_info(self, package, factory):
    try:
      versions = self.versions[package]
    except KeyError:
      return

    for version, machine in versions:
      impl = factory('package:rpm:%s:%s:%s' % (package, version, machine))
      impl.version = model.parse_version(version)
      if machine != '*':
        impl.machine = machine

  def get_score(self, disto_name):
    return int(disto_name == 'RPM')

class GentooDistribution(Distribution):

  def __init__(self, pkgdir):
    self._pkgdir = pkgdir

  def get_package_info(self, package, factory):
    _version_start_reqexp = '-[0-9]'

    if package.count('/') != 1: return

    category, leafname = package.split('/')
    category_dir = os.path.join(self._pkgdir, category)
    match_prefix = leafname + '-'

    if not os.path.isdir(category_dir): return

    for filename in os.listdir(category_dir):
      if filename.startswith(match_prefix) and filename[len(match_prefix)].isdigit():
        name = file(os.path.join(category_dir, filename, 'PF')).readline().strip()

        match = re.search(_version_start_reqexp, name)
        if match is None:
          warn(_('Cannot parse version from Gentoo package named "%(name)s"'), {'name': name})
          continue
        else:
          version = try_cleanup_distro_version(name[match.start() + 1:])

        if category == 'app-emulation' and name.startswith('emul-'):
          __, __, machine, __ = name.split('-', 3)
        else:
          machine, __ = file(os.path.join(category_dir, filename, 'CHOST')).readline().split('-', 1)
        machine = arch.canonicalize_machine(machine)

        impl = factory('package:gentoo:%s:%s:%s' % \
            (package, version, machine))
        impl.version = model.parse_version(version)
        impl.machine = machine

  def get_score(self, disto_name):
    return int(disto_name == 'Gentoo')

class PortsDistribution(Distribution):

  def __init__(self, pkgdir):
    self._pkgdir = pkgdir

  def get_package_info(self, package, factory):
    _version_start_reqexp = '-[0-9]'

    for pkgname in os.listdir(self._pkgdir):
      pkgdir = os.path.join(self._pkgdir, pkgname)
      if not os.path.isdir(pkgdir): continue

      #contents = file(os.path.join(pkgdir, '+CONTENTS')).readline().strip()

      match = re.search(_version_start_reqexp, pkgname)
      if match is None:
        warn(_('Cannot parse version from Ports package named "%(pkgname)s"'), {'name': pkgname})
        continue
      else:
        name = pkgname[0:match.start()]
        version = try_cleanup_distro_version(pkgname[match.start() + 1:])

      machine = arch.canonicalize_machine(host_machine)

      impl = factory('package:ports:%s:%s:%s' % \
            (package, version, machine))
      impl.version = model.parse_version(version)
      impl.machine = machine

  def get_score(self, disto_name):
    return int(disto_name == 'Ports')

_host_distribution = None
def get_host_distribution():
  """Get a Distribution suitable for the host operating system.
  Calling this twice will return the same object.
  @rtype: L{Distribution}"""
  global _host_distribution
  if not _host_distribution:
    dpkg_db_status = '/var/lib/dpkg/status'
    pkgcache = '/var/cache/apt/pkgcache.bin'
    _rpm_db = '/var/lib/rpm/Packages'
    _pkg_db = '/var/db/pkg'

    if os.path.isdir(_pkg_db):
      if sys.platform.startswith("linux"):
        _host_distribution = GentooDistribution(_pkg_db)
      elif sys.platform.startswith("freebsd"):
        _host_distribution = PortsDistribution(_pkg_db)
    elif os.access(dpkg_db_status, os.R_OK):
      _host_distribution = DebianDistribution(dpkg_db_status, pkgcache)
    elif os.path.isfile(_rpm_db):
      _host_distribution = RPMDistribution(_rpm_db)
    else:
      _host_distribution = Distribution()

  return _host_distribution
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.