Beispiel #1
0
class Resources(object):

    # FIXME: class and methods need docstrings.

    def __init__(self, index_root=None, urls=[], verbose=False, prefix=None,
                 platform=None):
        self.plat = platform or custom_plat
        self.prefix = prefix or sys.prefix
        self.verbose = verbose
        self.index = []   # list of dicts of product metadata
        self.history = History(self.prefix)
        self.enst = Enstaller(Chain(verbose=verbose), [self.prefix])
        self.product_list_path = 'products'

        if index_root:
            self.load_index(index_root)

        # Cache attributes
        self._installed_cnames = None
        self._status = None
        self._installed = None

    def clear_cache(self):
        self._installed_cnames = None
        self._status = None
        self._installed = None

    def load_index(self, url_root):
        """ Append to self.index, the metadata for all products found
        at url_root
        """
        url_root = url_root.rstrip('/')
        self._product_cache = ResourceCache(join(self.prefix, 'cache'),
                                            url_root)

        try:
            product_list = self._product_cache.get(self.product_list_path + '/',
                                                   None)
        except EnstallerResourceIndexError as e:
            if e.data:
                product_list = e.data
            else:
                raise

        filtered_product_list = {}
        for product in product_list:
            slug = product['repo_slug']
            if slug not in filtered_product_list or (
                    (not filtered_product_list[slug]['subscribed'])
                    and product['subscribed']):
                filtered_product_list[slug] = product

        for product_metadata in [pm for pm in product_list if
                                 filtered_product_list[pm['repo_slug']] == pm]:
            self._add_product(product_metadata)

    def _add_product(self, product_metadata):
        """ Append a dict of product metadata to self.index after
        filling in the full product metadata
        """
        if self.verbose:
            print "Adding product:", product_metadata['product']

        self._read_full_product_metadata(product_metadata)

        if ('platform' in product_metadata and
            product_metadata['platform'] != self.plat):
            raise Exception('Product metadata file for {}, but running {}'
                            .format(product_metadata['platform'], self.plat))

        if 'eggs' in product_metadata:
            self._add_egg_repos(product_metadata['url'], product_metadata)
        else:
            product_metadata['eggs'] = {}

        self.index.append(product_metadata)

    def _read_full_product_metadata(self, product_metadata):
        """ Given partial product metadata, fill in full product metatadata
        which includes a list of the resources (e.g. eggs) which it includes.

        Fills in the product_metadata dict in place.
        """
        product_path = '{}/{}'.format(self.product_list_path,
                                      product_metadata['product'])
        product_metadata['url'] = self._product_cache.url_for(product_path)
        parts = urlsplit(product_metadata['url'])
        if product_metadata['product'] == 'EPDFree':
            path = 'account/register/'
        else:
            path = 'products/getepd/'
        product_metadata['buy_url'] = urlunsplit((parts.scheme, parts.netloc,
                                                  path, '', ''))

        if product_metadata.get('platform_independent', False):
            index_filename = 'index.json'
        else:
            index_filename = 'index-{}.json'.format(self.plat)
        product_index_path = '{}/{}'.format(product_path, index_filename)
        last_update = datetime.strptime(product_metadata['last_update'],
                                        '%Y-%m-%d %H:%M:%S')

        product_info = self._product_cache.get(product_index_path, last_update)
        product_metadata.update(product_info)

    def _add_egg_repos(self, url, product_metadata):
        if 'egg_repos' in product_metadata:
            repos = ['{}/{}/'.format(url, path)
                     for path in product_metadata['egg_repos']]
        else:
            repos = [url]
        self.enst.chain.repos.extend(repos)

        if not product_metadata['subscribed']:
            for repo in repos:
                self.enst.chain.unsubscribed_repos[repo] = product_metadata

        for cname, project in product_metadata['eggs'].iteritems():
            for distname, data in project['files'].iteritems():
                name, version, build = dist_naming.split_eggname(distname)
                spec = dict(metadata_version='1.1',
                            name=name, version=version, build=build,
                            python=data.get('python', '2.7'),
                            packages=data.get('depends', []),
                            size=data.get('size'))
                add_Reqs_to_spec(spec)
                assert spec['cname'] == cname, distname
                dist = repos[data.get('repo', 0)] + distname
                self.enst.chain.index[dist] = spec
                self.enst.chain.groups[cname].append(dist)

    def get_installed_cnames(self):
        if not self._installed_cnames:
            self._installed_cnames = self.enst.get_installed_cnames()
        return self._installed_cnames

    def get_status(self):
        if not self._status:
            # the result is a dict mapping cname to ...
            res = {}
            for cname in self.get_installed_cnames():
                d = defaultdict(str)
                info = self.enst.get_installed_info(cname)[0][1]
                if info is None:
                    continue
                d.update(info)
                res[cname] = d

            for cname in self.enst.chain.groups.iterkeys():
                dist = self.enst.chain.get_dist(Req(cname),
                                                allow_unsubscribed=True)
                if dist is None:
                    continue
                repo, fn = dist_naming.split_dist(dist)
                n, v, b = dist_naming.split_eggname(fn)
                if cname not in res:
                    d = defaultdict(str)
                    d['name'] = d.get('name', cname)
                    res[cname] = d
                res[cname]['a-egg'] = fn
                res[cname]['a-ver'] = '%s-%d' % (v, b)

            def vb_egg(fn):
                try:
                    n, v, b = dist_naming.split_eggname(fn)
                    return comparable_version(v), b
                except IrrationalVersionError:
                    return None
                except AssertionError:
                    return None

            for d in res.itervalues():
                if d['egg_name']:                    # installed
                    if d['a-egg']:
                        if vb_egg(d['egg_name']) >= vb_egg(d['a-egg']):
                            d['status'] = 'up-to-date'
                        else:
                            d['status'] = 'updateable'
                    else:
                        d['status'] = 'installed'
                else:                                # not installed
                    if d['a-egg']:
                        d['status'] = 'installable'
            self._status = res
        return self._status

    def get_installed(self):
        if not self._installed:
            self._installed = set([pkg['egg_name']
                                   for pkg in self.get_status().values()
                                   if pkg['status'] != 'installable'])
        return self._installed

    def search(self, text):
        """ Search for eggs with name or description containing the given text.

        Returns a list of canonical names for the matching eggs.
        """
        regex = re.compile(re.escape(text), re.IGNORECASE)
        results = []
        for product_metadata in self.index:
            for cname, metadata in product_metadata.get('eggs', {}).iteritems():
                name = metadata.get('name', '')
                description = metadata.get('description', '')
                if regex.search(name) or regex.search(description):
                    results.append(cname)
        return results

    def _req_list(self, reqs):
        """ Take a single req or a list of reqs and return a list of
        Req instances
        """
        if not isinstance(reqs, list):
            reqs = [reqs]

        # Convert cnames to Req instances
        for i, req in enumerate(reqs):
            if not isinstance(req, Req):
                reqs[i] = Req(req)
        return reqs

    def install(self, reqs, overall_progress_cb=None):
        reqs = self._req_list(reqs)

        full_reqs = self.enst.full_install_sequence(reqs)
        total_count = len(full_reqs)
        with self.history:
            installed_count = 0
            for req in full_reqs:
                installed_count += self.enst.install(req)
                if overall_progress_cb:
                    overall_progress_cb(installed_count, total_count)

        # Clear the cache, since the status of several packages could now be
        # invalid
        self.clear_cache()

        return installed_count

    def uninstall(self, reqs, overall_progress_cb=None):
        reqs = self._req_list(reqs)

        total_count = len(reqs)
        with self.history:
            removed_count = 0
            for req in reqs:
                self.enst.remove(req)
                removed_count += 1
                if overall_progress_cb:
                    overall_progress_cb(removed_count, total_count)

        self.clear_cache()
        return 1

    def revert(self, revert_to):
        revert(self.enst, str(revert_to), quiet=True)
        self.clear_cache()