Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

beebox / crossover   deb

Repository URL to install this package:

Version: 18.5.0-1 

/ opt / cxoffice / lib / python / cxfixes.py

# (c) Copyright 2014-2015. CodeWeavers, Inc.

import os.path

import cxlog
import cxutils

# for localization
from cxutils import cxgettext as _

FIXES_URL = "http://ftp.codeweavers.com/pub/crossover/cxfixes/cxfixes.xml.gz"
WIKI_URL = "https://www.codeweavers.com/support/wiki/Diag/"


#####
#
# Methods for displaying error messages
#
#####

def display_error_internal(title, text):
    try:
        import gtk
        gtk_level = gtk.main_level()

        import threading
        lock = threading.Lock()
        lock.acquire()
        def done(_response):
            lock.release()
            if gtk_level == 0:
                gtk.main_quit()

        import cxguitools
        cxguitools.CXMessageDlg(
            primary=title,
            secondary=text,
            button_array=((gtk.STOCK_OK, 0),),
            response_function=done,
            message_type=gtk.MESSAGE_ERROR)
        if gtk.main_level() == 0:
            gtk.main()
        lock.acquire()
        return 0
    except:
        return -1

def display_error_zenity(title, text):
    if isinstance(title, cxutils.unicode_type):
        title = title.encode('utf8')
    if isinstance(text, cxutils.unicode_type):
        text = text.encode('utf8')
    return cxutils.run(['zenity', '--error', '--no-markup', '--title', title,
                        '--text', text])

def display_error_kdialog(title, text):
    if isinstance(title, cxutils.unicode_type):
        title = title.encode('utf8')
    if isinstance(text, cxutils.unicode_type):
        text = text.encode('utf8')
    return cxutils.run(['kdialog', '--title', title, '--error', text])

def display_error_xmessage(title, text):
    if isinstance(title, cxutils.unicode_type):
        try:
            title = title.encode('iso-8859-1')
        except UnicodeEncodeError:
            # Hope xmessage will display it right anyway
            title = title.encode('utf8')
    if isinstance(text, cxutils.unicode_type):
        try:
            text = text.encode('iso-8859-1')
        except UnicodeEncodeError:
            # Hope xmessage will display it right anyway
            text = text.encode('utf8')
    return cxutils.run(['xmessage', title + '\n' + text])

def display_error(gui, title, text):
    if not gui:
        # pylint: disable=C0325
        print(text)
        return

    # kdialog and xmessage return 1 either when an error occurs or when the user
    # closes the window instead of clicking on a button. So we cannot know if
    # the user really got notified through the GUI. So always print the error
    # on the console too. Do it first too in case the GUI garbles the message
    # (xmessage I'm looking at you).
    import sys
    sys.stderr.write(title + "\n" + text)

    # retcode is negative if the tool is not present, except for zenity which
    # also returns -5 if it cannot open the display. Either way it means the
    # user did not get the warning so try the next one when that happens.
    retcode = display_error_internal(title, text)
    if retcode < 0:
        retcode, _out, _err = display_error_zenity(title, text)
    if retcode < 0:
        retcode, _out, _err = display_error_kdialog(title, text)
    if retcode < 0:
        # xmessage should only be used as a last resort
        display_error_xmessage(title, text)


#####
#
# Loading the distributions and fixes information
#
#####

DISTROS = None
FIXES = None

def _load_fixes():
    # pylint: disable=W0603
    global DISTROS
    global FIXES

    if DISTROS is not None:
        return

    # Try to grab the latest information on distributions and fixes. Use a
    # short timeout to not risk delaying the application startup too much.
    import cxproduct
    latestfixes = os.path.join(cxproduct.get_user_dir(), "cxfixes.xml")
    try:
        import fileupdate
        fileupdate.update(latestfixes, FIXES_URL, True, 5)
    except:
        # We cannot download updates with Python 3 due to urllib2
        # among other issues.
        pass

    import cxfixesparser
    l_release, l_distros, l_fixes, l_errors = cxfixesparser.read_file(latestfixes)
    for error in l_errors:
        cxlog.warn(error)

    # Load the builtin information
    b_release, b_distros, b_fixes, b_errors = cxfixesparser.read_file(os.path.join(cxutils.CX_ROOT, "share/crossover/data/cxfixes.xml"))
    for error in b_errors:
        cxlog.warn(error)

    # Use the most recent data
    if b_distros is None and l_distros is None:
        pass
    elif b_distros is None or (l_release is not None and b_release < l_release):
        cxlog.log("using the downloaded fixes information (release %s)" % l_release)
        DISTROS = l_distros
        FIXES = l_fixes
    else:
        cxlog.log("using the builtin fixes information (release %s)" % b_release)
        DISTROS = b_distros
        FIXES = b_fixes

    if DISTROS is None:
        cxlog.warn("Could not load any fixes information")
        # Consider that there are no known distributions and fixes
        DISTROS = {}
        FIXES = {}


#####
#
# Platform detection
#
#####

_BITNESS = None

def detect_bitness():
    # pylint: disable=W0603
    global _BITNESS
    if _BITNESS is None:
        _BITNESS = '64'
        # We don't detect bitness through uname to correctly deal with chroots.
        _retcode, out, _err = cxutils.run(('env', 'POSIXLY_CORRECT=1', 'file', '/bin/ls'), stdout=cxutils.GRAB, stderr=cxutils.NULL)
        if out.find('ELF 32-bit') >= 0:
            _BITNESS = '32'
    return _BITNESS

_DISTROID = None

def detect_distribution():
    # pylint: disable=W0603
    global _DISTROID
    if _DISTROID is None:
        _load_fixes()
        import globtree
        glob_tree = globtree.FileContentGlobTree()

        import re
        for (distroid, distro) in DISTROS.items():
            for distglob in distro.globs:
                try:
                    glob_tree.add_content_glob(distglob.file_glob, distglob.patterns, distroid)
                except re.error:
                    cxlog.warn("the %s fileglob of %s contains an invalid regular expression" % (distglob.file_glob, distroid))


        best_priority = -1
        for _filename, distroid in glob_tree.matches(cxutils.b('/')):
            if _DISTROID is None or \
                    best_priority < DISTROS[distroid].priority:
                _DISTROID = distroid
                best_priority = DISTROS[distroid].priority
                cxlog.log("found a match for distro %s (%s)" % (_DISTROID, best_priority))
    if _DISTROID is None:
        cxlog.err("Could not identify the distribution")
    return _DISTROID


#####
#
# Error management
#
#####

_ERRORS = {}

_BUILTIN_DESCRIPTIONS = \
    {'dbus':        _('Could not load the D-Bus Python modules.'),
     'gtk2':        _('Could not load the GTK+ Python modules.'),
     'missinglibc': _('Could not find the 32-bit C library.'),
    }

def add_error(errid, description=None):
    if description is None:
        if errid in _BUILTIN_DESCRIPTIONS:
            description = _BUILTIN_DESCRIPTIONS[errid]
        else:
            description = _('Unknown error %s' % errid)
    _ERRORS[errid] = description

def remove_error(errid):
    del _ERRORS[errid]

def has_error(errid):
    return errid in _ERRORS

def has_errors():
    if _ERRORS:
        return True
    return False

def get_errors():
    return _ERRORS

def clear_errors():
    # pylint: disable=W0603
    global _ERRORS
    _ERRORS = {}


#####
#
# Fixes management
#
#####

def has_fix(errid):
    if get_packages(errid):
        return True
    return False

def get_distribution_property(distroid, name):
    """Returns the distribution's property, using the fallbacks if necessary.
    If there is no such property, then None is returned.
    """
    dist = DISTROS[distroid]
    while name not in dist.__dict__:
        if dist.fallback is None:
            return None
        dist = DISTROS[dist.fallback]
    return dist.__dict__[name]

def get_packages(seed_errid, seed_distroid=None, bitness=None):
    _load_fixes()
    if seed_errid not in FIXES:
        return None

    if seed_distroid is None:
        seed_distroid = detect_distribution()
        if seed_distroid is None:
            return None
    if bitness is None:
        bitness = detect_bitness()

    distroids = [seed_distroid]
    dist = DISTROS[seed_distroid]
    while dist.fallback:
        distroids.append(dist.fallback)
        dist = DISTROS[dist.fallback]

    # Check if there are any other errors that this one may have masked and
    # which should be fixed at the same time
    errids = [seed_errid]
    for fixid, fix in FIXES.items():
        if seed_errid in fix.tiedto:
            errids.append(fixid)

    # Return the packages for all the relevant errors
    packages = []
    for errid in errids:
        for distroid in distroids:
            distfixes = FIXES[errid].distfixes
            if (distroid, bitness) in distfixes:
                packages.extend(distfixes[(distroid, bitness)].packages)
                break
            if (distroid, None) in distfixes:
                packages.extend(distfixes[(distroid, None)].packages)
                break
    return packages

def get_fix_command(update=True):
    if not has_errors():
        return ([], None)

    distroid = detect_distribution()
    if distroid is None:
        return ([], None)
    bitness = detect_bitness()

    # First make sure the method for fixing errors in distribution is one
    # we know about. Currently that's the packagecmd method.
    cmd = get_distribution_property(distroid, 'packagecmd')
    if cmd is None:
        cxlog.warn("No command has been set for installing the packages on %s" % DISTROS[distroid].name)
        return ([], None)

    fixable_errors = set()
    packages = set()
    for errid in _ERRORS:
        fix_packages = get_packages(errid, distroid, bitness)
        if fix_packages:
            fixable_errors.add(errid)
            packages.update(fix_packages)

    if not fixable_errors:
        return ([], None)

    cmd += ' ' + cxutils.argvtocmdline(sorted(packages))
    updatecmd = get_distribution_property(distroid, 'updatecmd')
    if update and updatecmd:
        # Ignore update errors, hoping the package list was not too outdated
        cmd = updatecmd + "; " + cmd

    # If we need to install a Debian multiarch package, make sure we have
    # the required architecture
    for package in packages:
        if ':' in package:
            retcode, out, _err = cxutils.run(('dpkg', '--print-architecture'), stdout=cxutils.GRAB, stderr=cxutils.NULL)
            if retcode == 0 and out != 'i386\n':
                retcode, out, _err = cxutils.run(('dpkg', '--print-foreign-architectures'), stdout=cxutils.GRAB, stderr=cxutils.NULL)
                if retcode == 0 and out.find('i386\n') < 0:
                    cmd = 'dpkg --add-architecture i386 && (' + cmd + ')'
            break

    return (fixable_errors, cmd)

def fix_errors(new_console=True, update=True):
    fixable_errors, cmd = get_fix_command(update)
    if cmd is None:
        return

    # The packaging tools are likely to ask for confirmation on the console
    if new_console:
        stdin = cxutils.NULL
    else:
        stdin = None
    cxsu_cmd = [os.path.join(cxutils.CX_ROOT, "bin", "cxsu"),
                '--description', _('CrossOver needs your permission to install missing packages.'),
                '--console', '--ignore-home', 'sh', '-c', cmd]
    retcode, _out, _err = cxutils.run(cxsu_cmd, stdin=stdin)
    if retcode == 0:
        for errid in fixable_errors:
            remove_error(errid)

def report_errors(prefix=True, gui=True):
    if not has_errors():
        return

    message = ""
    if prefix:
        import distversion
        message = _("Some errors may prevent %s from working correctly:\n") % distversion.PRODUCT_NAME
        for description in sorted(_ERRORS.values()):
            message += "* " + description + "\n"

    # Get a list of packages the user could try to install.
    _load_fixes()
    packages = {}
    missing_fixes = set()
    for errid in _ERRORS:
        if errid not in FIXES:
            missing_fixes.add(errid)
            continue
        distfixes = FIXES[errid].distfixes
        for key in distfixes:
            if key in packages:
                packages[key].update(distfixes[key].packages)
            else:
                packages[key] = set(distfixes[key].packages)

    # Avoid printing separate instructions for bitness-specific and
    # bitness-independent cases.
    # pylint: disable=C0201
    for (distroid, bitness) in packages.keys():
        if bitness is not None:
            continue
        key32 = (distroid, '32')
        key64 = (distroid, '64')
        if key32 in packages or key64 in packages:
            dist_packages = packages[(distroid, None)]
            packages[(distroid, None)] = None
            if key32 in packages:
                packages[key32].update(dist_packages)
            elif 'missinglibc' not in _ERRORS:
                packages[key32] = set(dist_packages)
            if key64 in packages:
                packages[key64].update(dist_packages)
            else:
                packages[key64] = set(dist_packages)

    if packages:
        message += _("\nYou may be able to fix some issues by running one of the following commands as root:\n")
        lines = []
        for (distroid, bitness) in packages:
            if not packages[(distroid, bitness)]:
                continue
            name = DISTROS[distroid].name
            if bitness is not None:
                name = _("%(name)s (%(bitness)s bits)") % {'name': name, 'bitness': bitness}
            lines.append("%-23s\t%s %s\n" % (name, get_distribution_property(distroid, 'packagecmd'),
                                             ' '.join(sorted(packages[(distroid, bitness)]))))
        message += ''.join(sorted(lines))

    if missing_fixes:
        message += _("\nThere is no automated fix for the remaining issues but you may find help on these pages:\n")
        for errid in missing_fixes:
            message += WIKI_URL + errid + "\n"

    display_error(gui, _("Could not install some Unix packages"), message)