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

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

import os
import re

import distversion
import cxutils
import bottlequery
import c4profilesmanager
import cxglob
import cxlog
import cxobjc

def _get_uninstall_info(bottlename):
    result = []
    for parent_key in ('HKEY_LOCAL_MACHINE\\Software',
                       'HKEY_LOCAL_MACHINE\\Software\\Wow6432Node',
                       'HKEY_CURRENT_USER\\Software'):
        uninstaller_key = '%s\\Microsoft\\Windows\\CurrentVersion\\Uninstall' % parent_key

        try:
            subkeys, _values = bottlequery.get_registry_key(bottlename, uninstaller_key)
        except bottlequery.NotFoundError:
            continue

        for keyname in subkeys:
            _subkeys, values = bottlequery.get_registry_key(bottlename, '%s\\%s' % (uninstaller_key, keyname))
            result.append((keyname, values))
    return result

class InstalledApplication(cxobjc.Proxy):
    """Describes an application installed in a specific bottle.

    Each such object has an appid property which uniquely identifies it in a
    bottle.

    If there is a corresponding C4 profile then this is the profile's id. This
    makes it easy to check if the application for a given profile is installed
    or not.

    If there is no corresponding C4 profile, then it's a string of the form
    '.local.<keyname>' (since only applications that have a proper Uninstall
    key can be detected without a profile. Since this id starts with a dot it
    cannot collide with a profile id.

    This class inherits from cxobjc.Proxy so that we can bind to it properly from
    within ObjC. That means that it must always be created via the special
    platformconstructor.
    """

    # Name of the Uninstall registry key, if any
    keyname = None

    # Display name from the registry, if any
    displayname = None

    # True if the application is a system component
    systemcomponent = False

    # Command line for "Repair or Remove", if any
    uninstallcommand = None

    # C4 application profile, if any
    _profile = None

    def __init__(self, bottlename, profile, keyname):
        cxobjc.Proxy.__init__(self)
        self.bottlename = bottlename
        self.keyname = keyname
        if profile:
            self._profile = profile
            self.appid = profile.appid
        else:
            self.appid = ".local." + keyname

    # This is a special initializer for objc. It must always be called
    #  explicitly on the mac.
    def initWithBottleName_profile_andKeyName_(self, bottlename, profile, keyname):
        self = cxobjc.Proxy.nsobject_init(self)
        if self is not None:
            self.__init__(bottlename, profile, keyname)
        return self

    # This is the constructor that should /always/ be used to create
    #  a new object. It will create the object as appropriate for
    #  the platform.
    @classmethod
    def platformconstructor(cls, bottlename, profile=None, keyname=None):
        if distversion.IS_MACOSX:
            # There's no alloc() method on non-Mac platforms...
            # pylint: disable=E1101
            return cls.alloc().initWithBottleName_profile_andKeyName_(bottlename, profile, keyname)
        return cls(bottlename, profile, keyname)

    def _getprofile(self):
        return self._profile

    def _setprofile(self, profile):
        """Associates a profile and updates the appid property accordingly."""
        self._profile = profile
        self.appid = profile.appid

    profile = property(_getprofile, _setprofile)


    def _getname(self):
        """Returns the installed application name.
        This is the name of the C4 profile if there is one, or the display
        name if there is one, and the registry key as a last resort.
        """
        if self._profile:
            return self._profile.name
        if self.displayname:
            return self.displayname
        return self.keyname

    name = property(_getname)


def _get_uninstall_applications(bottlename):
    result = []
    winebin = os.path.join(cxutils.CX_ROOT, "bin", "wine")
    for keyname, values in _get_uninstall_info(bottlename):
        app = InstalledApplication.platformconstructor(bottlename, keyname=keyname)
        app.displayname = values.get('displayname')
        app.systemcomponent = values.get('systemcomponent', 0) == 1
        if 'uninstallstring' in values:
            # we can't reliably call a windows command so just use uninstaller.exe
            app.uninstallcommand = [winebin, '--bottle', bottlename, '--wl-app', 'uninstaller', '--remove', keyname]
        result.append(app)
    return result


class AppDetector(cxobjc.Proxy):
    pass


def get_installed_applications(bottlename, profiles):
    installed_apps = {}

    # search for installed profiles
    file_glob_tree = cxglob.FileGlobTree()
    uninstall_apps = _get_uninstall_applications(bottlename)
    known_uninstall_apps = set()
    for profile in profiles.values():
        app_profile = profile.app_profile
        if app_profile is None:
            continue
        found = False

        if app_profile.installed_key_pattern is not None:
            try:
                regex = re.compile(app_profile.installed_key_pattern, re.IGNORECASE)
            except re.error:
                cxlog.warn("installed key pattern %s in profile for %s is an invalid regular expression" % (cxlog.debug_str(app_profile.installed_key_pattern), cxlog.debug_str(profile.appid)))
            else:
                for app in uninstall_apps:
                    if regex.match(app.keyname.decode('utf8')):
                        installed_apps[profile.appid] = InstalledApplication.platformconstructor(bottlename, profile, app.keyname)
                        installed_apps[profile.appid].uninstallcommand = app.uninstallcommand
                        known_uninstall_apps.add(app.keyname)
                        found = True
                        break
                if found:
                    continue

        if app_profile.steamid:
            # A special case. For a given steam appid there's a ready-made
            # key pattern to check.
            steamidstring = "Steam App %s" % app_profile.steamid
            for app in uninstall_apps:
                if steamidstring.lower() == app.keyname.decode('utf8'):
                    installed_apps[profile.appid] = InstalledApplication.platformconstructor(bottlename, profile, app.keyname)
                    installed_apps[profile.appid].uninstallcommand = app.uninstallcommand
                    known_uninstall_apps.add(app.keyname)
                    found = True
                    break
                if found:
                    continue

        if app_profile.installed_display_pattern is not None:
            try:
                regex = re.compile(app_profile.installed_display_pattern, re.IGNORECASE)
            except re.error:
                cxlog.warn("installed display pattern %s in profile for %s is an invalid regular expression" % (cxlog.debug_str(app_profile.installed_display_pattern), cxlog.debug_str(profile.appid)))
            else:
                for app in uninstall_apps:
                    if app.displayname is not None and regex.match(app.displayname.decode('utf8')):
                        installed_apps[profile.appid] = InstalledApplication.platformconstructor(bottlename, profile, app.keyname)
                        installed_apps[profile.appid].uninstallcommand = app.uninstallcommand
                        known_uninstall_apps.add(app.keyname)
                        found = True
                        break
                if found:
                    continue

        file_glob_tree.add_profile(profile, bottlename)

    # scan the filesystem in one step
    for _filename, profile in file_glob_tree.matches('', bottlename):
        if profile.appid in installed_apps:
            # don't make an extra InstalledApplication if we have one already
            continue
        app = InstalledApplication.platformconstructor(bottlename, profile)
        installed_apps[app.appid] = app

    # scan the registry in one step
    registry_glob_tree = cxglob.RegistryGlobTree.get(profiles)
    for key, (profile, glob) in registry_glob_tree.matches('', bottlename):
        if profile.appid in installed_apps:
            # don't make an extra InstalledApplication if we have one already
            continue
        elif glob.value_pattern != '':
            # test the default value
            key = key.replace('/', '\\')
            if not key.endswith('\\'):
                key = '%s\\' % key
            _subkeys, values = bottlequery.get_registry_key(bottlename, key)
            try:
                data_regex = re.compile(glob.data_pattern, re.IGNORECASE)
            except re.error:
                cxlog.warn("registry data pattern %s in profile for %s is an invalid regular expression" % (cxlog.debug_str(glob.data_pattern), cxlog.debug_str(profile.appid)))
                break
            if glob.value_pattern == '@':
                if '' not in values:
                    continue
                if not data_regex.match(values['']):
                    continue
            else:
                try:
                    value_regex = re.compile(glob.value_pattern, re.IGNORECASE)
                except re.error:
                    cxlog.warn("registry value pattern %s in profile for %s is an invalid regular expression" % (cxlog.debug_str(glob.value_pattern), cxlog.debug_str(profile.appid)))
                    break
                for value in values:
                    if value_regex.match(value) and data_regex.match(str(values[value])):
                        break
                else:
                    # no matching value found
                    continue
            keyparts = key.split('\\')
            # Note that key now contains an extra '\\' at the end
            if len(keyparts) == 8 and \
                    (keyparts[0] == 'hkey_local_machine' or keyparts[0] == 'hkey_current_user') and \
                    keyparts[1] == 'software' and \
                    keyparts[2] == 'microsoft' and \
                    keyparts[3] == 'windows' and \
                    keyparts[4] == 'currentversion' and \
                    keyparts[5] == 'uninstall':
                # Mark this 'uninstall' entry as known to avoid duplicates
                known_uninstall_apps.add(keyparts[6])
        app = InstalledApplication.platformconstructor(bottlename, profile)
        installed_apps[app.appid] = app

    # add any remaining applications with display names to the list
    for app in uninstall_apps:
        if app.displayname and not app.systemcomponent and \
                app.keyname not in known_uninstall_apps:
            installed_apps[app.appid] = app

    return installed_apps


def fast_get_installed_applications(bottlename, profiles):
    """return a list of InstalledApplication objects in a bottle, using the C4
    cache to work quickly"""
    # FIXME: not implemented
    return get_installed_applications(bottlename, profiles)

@cxobjc.method(AppDetector, 'fastGetInstalledApplicationsInBottle_')
def _mac_get_installed_apps(bottlename):
    # FIXME: This method should take a C4ProfilesSet parameter and be merged
    #        with fast_get_installed_applications()
    profiles = c4profilesmanager.C4ProfilesSet.all_profiles()
    return fast_get_installed_applications(bottlename, profiles)


def is_profile_installed(bottlename, profile):
    """returns an InstalledApplication if the profile is installed, None if not"""
    profiles = c4profilesmanager.C4ProfilesSet()
    profiles[profile.appid] = profile
    applications = get_installed_applications(bottlename, profiles)
    if profile.appid in applications:
        return applications[profile.appid]
    return None