aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/portage/dbapi/IndexedPortdb.py')
-rw-r--r--lib/portage/dbapi/IndexedPortdb.py171
1 files changed, 171 insertions, 0 deletions
diff --git a/lib/portage/dbapi/IndexedPortdb.py b/lib/portage/dbapi/IndexedPortdb.py
new file mode 100644
index 000000000..510e0278c
--- /dev/null
+++ b/lib/portage/dbapi/IndexedPortdb.py
@@ -0,0 +1,171 @@
+# Copyright 2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import errno
+import io
+import functools
+import operator
+import os
+
+import portage
+from portage import _encodings
+from portage.dep import Atom
+from portage.exception import FileNotFound
+from portage.cache.index.IndexStreamIterator import IndexStreamIterator
+from portage.cache.index.pkg_desc_index import \
+ pkg_desc_index_line_read, pkg_desc_index_node
+from portage.util.iterators.MultiIterGroupBy import MultiIterGroupBy
+from portage.versions import _pkg_str
+
+class IndexedPortdb(object):
+ """
+ A portdbapi interface that uses a package description index to
+ improve performance. If the description index is missing for a
+ particular repository, then all metadata for that repository is
+ obtained using the normal pordbapi.aux_get method.
+
+ For performance reasons, the match method only supports package
+ name and version constraints. For the same reason, the xmatch
+ method is not implemented.
+ """
+
+ # Match returns unordered results.
+ match_unordered = True
+
+ _copy_attrs = ('cpv_exists', 'findname', 'getFetchMap',
+ '_aux_cache_keys', '_cpv_sort_ascending',
+ '_have_root_eclass_dir')
+
+ def __init__(self, portdb):
+
+ self._portdb = portdb
+
+ for k in self._copy_attrs:
+ setattr(self, k, getattr(portdb, k))
+
+ self._desc_cache = None
+ self._cp_map = None
+ self._unindexed_cp_map = None
+
+ def _init_index(self):
+
+ cp_map = {}
+ desc_cache = {}
+ self._desc_cache = desc_cache
+ self._cp_map = cp_map
+ index_missing = []
+
+ streams = []
+ for repo_path in self._portdb.porttrees:
+ outside_repo = os.path.join(self._portdb.depcachedir,
+ repo_path.lstrip(os.sep))
+ filenames = []
+ for parent_dir in (repo_path, outside_repo):
+ filenames.append(os.path.join(parent_dir,
+ "metadata", "pkg_desc_index"))
+
+ repo_name = self._portdb.getRepositoryName(repo_path)
+
+ try:
+ f = None
+ for filename in filenames:
+ try:
+ f = io.open(filename,
+ encoding=_encodings["repo.content"])
+ except IOError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ raise
+ else:
+ break
+
+ if f is None:
+ raise FileNotFound(filename)
+
+ streams.append(iter(IndexStreamIterator(f,
+ functools.partial(pkg_desc_index_line_read,
+ repo = repo_name))))
+ except FileNotFound:
+ index_missing.append(repo_path)
+
+ if index_missing:
+ self._unindexed_cp_map = {}
+
+ class _NonIndexedStream(object):
+ def __iter__(self_):
+ for cp in self._portdb.cp_all(
+ trees = index_missing):
+ # Don't call cp_list yet, since it's a waste
+ # if the package name does not match the current
+ # search.
+ self._unindexed_cp_map[cp] = index_missing
+ yield pkg_desc_index_node(cp, (), None)
+
+ streams.append(iter(_NonIndexedStream()))
+
+ if streams:
+ if len(streams) == 1:
+ cp_group_iter = ([node] for node in streams[0])
+ else:
+ cp_group_iter = MultiIterGroupBy(streams,
+ key = operator.attrgetter("cp"))
+
+ for cp_group in cp_group_iter:
+
+ new_cp = None
+ cp_list = cp_map.get(cp_group[0].cp)
+ if cp_list is None:
+ new_cp = cp_group[0].cp
+ cp_list = []
+ cp_map[cp_group[0].cp] = cp_list
+
+ for entry in cp_group:
+ cp_list.extend(entry.cpv_list)
+ if entry.desc is not None:
+ for cpv in entry.cpv_list:
+ desc_cache[cpv] = entry.desc
+
+ if new_cp is not None:
+ yield cp_group[0].cp
+
+ def cp_all(self, sort=True):
+ """
+ Returns an ordered iterator instead of a list, so that search
+ results can be displayed incrementally.
+ """
+ if self._cp_map is None:
+ return self._init_index()
+ return iter(sorted(self._cp_map)) if sort else iter(self._cp_map)
+
+ def match(self, atom):
+ """
+ For performance reasons, only package name and version
+ constraints are supported, and the returned list is
+ unordered.
+ """
+ if not isinstance(atom, Atom):
+ atom = Atom(atom)
+ cp_list = self._cp_map.get(atom.cp)
+ if cp_list is None:
+ return []
+
+ if self._unindexed_cp_map is not None:
+ try:
+ unindexed = self._unindexed_cp_map.pop(atom.cp)
+ except KeyError:
+ pass
+ else:
+ cp_list.extend(self._portdb.cp_list(atom.cp,
+ mytree=unindexed))
+
+ if atom == atom.cp:
+ return cp_list[:]
+ else:
+ return portage.match_from_list(atom, cp_list)
+
+ def aux_get(self, cpv, attrs, myrepo=None):
+ if len(attrs) == 1 and attrs[0] == "DESCRIPTION":
+ try:
+ return [self._desc_cache[cpv]]
+ except KeyError:
+ pass
+ return self._portdb.aux_get(cpv, attrs)