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 / cxproduct.py

# (c) Copyright 2010-2011. CodeWeavers, Inc.

import os.path
import sys
import re

import cxconfig
import cxobservable
import cxutils
import distversion


#####
#
# The current product id
#
#####

_PRODUCT_ID = None
def get_product_id():
    """Gets and checks the effective product id.

    An invalid product id is a fatal error and will raise an exception.
    """
    # pylint: disable=W0603
    global _PRODUCT_ID
    if _PRODUCT_ID is None:
        filename = os.path.join(cxutils.CX_ROOT, ".productid")
        if os.path.exists(filename):
            try:
                thisfile = open(filename, 'r')
                _PRODUCT_ID = thisfile.readline().rstrip()
                thisfile.close()
            except OSError:
                ose = sys.exc_info()[1]
                raise RuntimeError("unable to open, read or close '%s': %s" % (filename, unicode(ose)))
        else:
            _PRODUCT_ID = distversion.BUILTIN_PRODUCT_ID
        if len(_PRODUCT_ID) < 4:
            raise RuntimeError("product id '%s' is too short" % _PRODUCT_ID)

        if not re.search("^[a-zA-Z0-9_][a-zA-Z0-9_][a-zA-Z0-9_][a-zA-Z0-9_][a-zA-Z0-9_]*$", _PRODUCT_ID):
            raise RuntimeError("product id '%s' contains bad characters" % _PRODUCT_ID)
    return _PRODUCT_ID


#####
#
# CrossOver's important directories
#
#####

def get_user_dir():
    try:
        home = os.environ["HOME"]
        stat = os.stat(home)
        euid = os.geteuid()
        if euid == 0 and stat.st_uid != euid:
            raise RuntimeError("$HOME (%s) does not belong to root" % home)
    except KeyError:
        raise RuntimeError("$HOME is not set!")
    except OSError:
        import errno
        ose = sys.exc_info()[1]
        if ose.errno == errno.ENOENT:
            raise RuntimeError("$HOME (%s) does not exist!" % home)
        raise
    if distversion.IS_MACOSX:
        return cxutils.string_to_unicode(os.path.join(home, "Library/Application Support/", distversion.PRODUCT_NAME))
    return cxutils.string_to_unicode(os.path.join(home, "." + get_product_id()))

def get_games_dir():
    try:
        home = os.environ["HOME"]
        stat = os.stat(home)
        euid = os.geteuid()
        if euid == 0 and stat.st_uid != euid:
            raise RuntimeError("$HOME (%s) does not belong to root" % home)
    except KeyError:
        raise RuntimeError("$HOME is not set!")
    except OSError:
        import errno
        ose = sys.exc_info()[1]
        if ose.errno == errno.ENOENT:
            raise RuntimeError("$HOME (%s) does not exist!" % home)
        raise
    if distversion.IS_MACOSX:
        return unicode(os.path.join(home, "Library/Application Support/", "CrossOver Games"))
    return unicode(os.path.join(home, "." + "cxgames"))

def get_managed_dir():
    if distversion.IS_MACOSX:
        return unicode(os.path.join(("/Library/Application Support"), distversion.PRODUCT_NAME))
    return unicode(os.path.join(cxutils.CX_ROOT, "support"))


def is_root_install():
    """Returns True if this install is owned by root.
       This is intended to distinguish between system-wide and private installs.
    """
    stat = os.stat(cxutils.CX_ROOT)
    return stat.st_uid == 0

def is_64bit_install():
    if not os.path.exists(os.path.join(cxutils.CX_ROOT, 'lib64', 'wine')):
        return False
    try:
        return os.readlink(os.path.join(cxutils.CX_ROOT, 'bin', 'wineserver')) == 'wineserver64'
    except OSError:
        # not a symlink
        return True


#####
#
# CrossOver's configuration
#
#####

_CONFIG = None

def get_config():
    """Returns the CrossOver configuration, merged from the relevant sources
    as needed.

    To avoid races when changing the configuration file it is necessary to
    follow these rules:
    - The data obtained prior to locking the writable configuration file with
      must not be trusted. This is because the configuration file was not yet
      locked and thus may have changed between the two calls.
    - So the proper way to test the value of a setting and then modify it
      race-free is:
          config = cxproduct.get_config()

          # This returns the file the changes should be saved into
          wconfig = config.get_save_config()

          # This locks the file and re-reads it if needed, ensuring wconfig
          # *and* config see the very latest data.
          wconfig.lock_file()

          if config['section']['field'] ...:
              wconfig['section']['field'] = ...

          # Saves the changes. If nothing was changed above then the file is
          # left untouched. Then the file is unlocked.
          wconfig.save_and_unlock_file()

          All the while config can still be used in a read-only fashion and is
          guaranteed to be in sync with the latest modifications made in
          wconfig.
    """
    # pylint: disable=W0603
    global _CONFIG
    if _CONFIG is None:
        _CONFIG = cxconfig.Stack()
        basename = get_product_id() + ".conf"

        global_config_file = os.path.join(cxutils.CX_ROOT, 'etc', basename)
        _CONFIG.addconfig(global_config_file)

        try:
            user_config_file = os.path.join(get_user_dir(), basename)
            _CONFIG.addconfig(user_config_file)
        except KeyError:
            # $HOME is not set, ignore it.
            pass
        except RuntimeError:
            # $HOME is wrong, ignore it.
            pass
    return _CONFIG


def save_setting(section, field, value):
    """Saves the specified setting in the CrossOver configuration file.

    This function must not be used if value is based on  another configuration
    setting as this would have races.
    """
    config = get_config()
    wconfig = config.get_save_config()
    wconfig.lock_file()
    wconfig[section][field] = value
    wconfig.save_and_unlock_file()


#####
#
# CrossOver's environment settings
#
#####

_ENVIRON = None

def _refresh_environment(config, *_args):
    """Refreshes the block holding CrossOver's environment settings."""

    # Variables can refer to environment variables
    environ = os.environ.copy()

    # And a few CrossOver-specific variables
    if 'CX_ROOT' not in environ:
        environ['CX_ROOT'] = cxutils.CX_ROOT
    if 'CX_BOTTLE_PATH' not in environ:
        if distversion.IS_MACOSX:
            environ['CX_BOTTLE_PATH'] = os.path.join(get_user_dir(), "Bottles")
        else:
            environ['CX_BOTTLE_PATH'] = get_user_dir()
    if 'CX_MANAGED_BOTTLE_PATH' not in environ:
        if distversion.IS_MACOSX:
            environ['CX_MANAGED_BOTTLE_PATH'] = os.path.join(get_managed_dir(), "Bottles")
        else:
            environ['CX_MANAGED_BOTTLE_PATH'] = get_managed_dir()
    if 'CX_GAMES_BOTTLE_PATH' not in environ:
        if distversion.IS_MACOSX:
            environ['CX_GAMES_BOTTLE_PATH'] = os.path.join(get_games_dir(), "Bottles")
        else:
            environ['CX_GAMES_BOTTLE_PATH'] = get_games_dir()

    if config is None:
        config = get_config()
        # Note that adding ourselves as an observer means we will be called
        # from multiple threads. No locking is needed however.
        config.add_observer(cxobservable.ANY_EVENT, _refresh_environment)

    # Process each configuration file individually and in the right order in
    # case the more specific ones build on the more general ones.
    configs = config.configs()
    for conffile in reversed(configs):
        section = conffile["EnvironmentVariables"]
        # Then process the environment variables in the order in which they
        # appear in the file in case they use environment variables defined
        # before.
        for key in section.sortedkeys():
            name = section.fieldname(key)
            environ[name] = cxutils.expand_unix_string(environ, section[key])

    # pylint: disable=W0603
    global _ENVIRON
    _ENVIRON = environ

def get_bottle_path():
    if _ENVIRON is None:
        _refresh_environment(None)
    return _ENVIRON['CX_BOTTLE_PATH']

def get_managed_bottle_path():
    if _ENVIRON is None:
        _refresh_environment(None)
    return _ENVIRON['CX_MANAGED_BOTTLE_PATH']

def get_games_bottle_path():
    if _ENVIRON is None:
        _refresh_environment(None)
    return _ENVIRON['CX_GAMES_BOTTLE_PATH']

#####
#
# Inter-product configuration (currently only used on Unix)
#
#####

_CROSS_PRODUCT_CONFIG = None

def get_cross_product_config():
    """Returns a configuration object containing the registered CrossOver
    product."""
    # pylint: disable=W0603
    global _CROSS_PRODUCT_CONFIG
    if _CROSS_PRODUCT_CONFIG is None:
        _CROSS_PRODUCT_CONFIG = cxconfig.Stack()
        _CROSS_PRODUCT_CONFIG.addconfig('/etc/crossover.conf')
        if 'HOME' in os.environ and (os.geteuid() != 0 or \
                cxutils.CX_ROOT.startswith(os.environ['HOME'])):
            _CROSS_PRODUCT_CONFIG.addconfig(os.path.join(os.environ['HOME'],
                                                         '.crossover.conf'))
    return _CROSS_PRODUCT_CONFIG


#####
#
# Product discovery
#
#####

def this_product():
    """Returns a dictionary describing the current product.

    See enum_products() for the content of the dictionary."""
    result = {}
    result['root'] = cxutils.CX_ROOT
    result['name'] = distversion.PRODUCT_NAME
    result['productid'] = distversion.BUILTIN_PRODUCT_ID
    if result['productid'] == 'CrossOver':
        result['productid'] = 'cxoffice'
    elif result['productid'] == 'CrossOverGames':
        result['productid'] = 'cxgames'
    result['productversion'] = distversion.CX_VERSION
    result['publicversion'] = distversion.PUBLIC_VERSION
    return result

def product_info_from_cxroot(root):
    """Returns a dictionary describing the product in the given directory, or
    None if it is older than 9.x or not CrossOver.

    See enum_products() for the content of the dictionary."""
    if not os.path.exists(os.path.join(root, 'bin', 'cxinstaller')):
        return None

    dist = {}

    try:
        execfile(os.path.join(root, 'lib', 'python', 'distversion.py'), dist)
    except IOError:
        return None

    for key in ('CX_VERSION', 'PUBLIC_VERSION', 'BUILTIN_PRODUCT_ID', 'PRODUCT_NAME'):
        if key not in dist:
            return None

    product = {}
    product['root'] = os.path.abspath(root)
    product['name'] = dist['PRODUCT_NAME']
    product['productid'] = dist['BUILTIN_PRODUCT_ID']
    product['productversion'] = dist['CX_VERSION']
    product['publicversion'] = dist['PUBLIC_VERSION']
    return product

def enum_products():
    """Returns a dictionary of dictionaries describing the installed products.

    The dictionary key is CX_ROOT.

    The following values will be present:
    * name - Human-readable product name
    * productid - The product ID used to test <cxversion> tags.
    * productversion - The CrossOver version used to test <cxversion> tags.
    * publicversion - The public version.
    * root - The value of CX_ROOT."""

    # Check the global configuration file.
    config = get_cross_product_config()
    result = {}
    for name, section in config.iteritems():
        if name.lower().startswith('product-') and \
                os.path.isdir(section.get('Root', '')) and \
                'ProductID' in section and 'ProductVersion' in section:
            product = {}
            product['name'] = section.get('Name', '')
            product['root'] = section['Root']
            product['productid'] = section['ProductID']
            product['productversion'] = section['ProductVersion']
            product['publicversion'] = section.get('PublicVersion', '')
            result[product['root']] = product

    # Add the running product to the list, just in case.
    product = this_product()
    result[product['root']] = product

    # Check in "likely" locations
    for root in ('/opt/cxoffice', '/opt/cxgames', os.path.join(os.environ["HOME"], 'cxoffice'), os.path.join(os.environ["HOME"], 'cxgames')):
        if root in result:
            continue

        product = product_info_from_cxroot(root)

        if product is not None:
            result[product['root']] = product

    return result