1from collections import OrderedDict, namedtuple
2
3from conans.errors import NotFoundException, ConanException
4from conans.search.search import (filter_outdated, search_packages, search_recipes,
5                                  filter_by_revision)
6
7
8class Search(object):
9    def __init__(self, cache, remote_manager, remotes):
10        self._cache = cache
11        self._remote_manager = remote_manager
12        self._remotes = remotes
13
14    def search_recipes(self, pattern, remote_name=None, case_sensitive=False):
15        ignorecase = not case_sensitive
16
17        references = OrderedDict()
18        if not remote_name:
19            references[None] = search_recipes(self._cache, pattern, ignorecase)
20            return references
21
22        if remote_name == 'all':
23            # We have to check if there is a remote called "all"
24            # Deprecate: 2.0 can remove this check
25            if 'all' not in self._remotes:
26                for remote in self._remotes.values():
27                    refs = self._remote_manager.search_recipes(remote, pattern, ignorecase)
28                    if refs:
29                        references[remote.name] = sorted(refs)
30                return references
31        # single remote
32        remote = self._remotes[remote_name]
33        refs = self._remote_manager.search_recipes(remote, pattern, ignorecase)
34        references[remote.name] = sorted(refs)
35        return references
36
37    remote_ref = namedtuple('remote_ref', 'ordered_packages recipe_hash')
38
39    def search_packages(self, ref=None, remote_name=None, query=None, outdated=False):
40        """ Return the single information saved in conan.vars about all the packages
41            or the packages which match with a pattern
42
43            Attributes:
44                pattern = string to match packages
45                remote_name = search on another origin to get packages info
46                packages_pattern = String query with binary
47                                   packages properties: "arch=x86 AND os=Windows"
48        """
49        if not remote_name:
50            return self._search_packages_in_local(ref, query, outdated)
51
52        if ref.revision and not self._cache.config.revisions_enabled:
53            raise ConanException("Revisions not enabled in the client, specify a "
54                                 "reference without revision")
55
56        if remote_name == 'all':
57            return self._search_packages_in_all(ref, query, outdated)
58
59        return self._search_packages_in(remote_name, ref, query, outdated)
60
61    def _search_packages_in_local(self, ref=None, query=None, outdated=False):
62        package_layout = self._cache.package_layout(ref, short_paths=None)
63        packages_props = search_packages(package_layout, query)
64        ordered_packages = OrderedDict(sorted(packages_props.items()))
65
66        try:
67            recipe_hash = package_layout.recipe_manifest().summary_hash
68        except IOError:  # It could not exist in local
69            recipe_hash = None
70
71        if outdated:
72            ordered_packages = filter_outdated(ordered_packages, recipe_hash)
73        elif self._cache.config.revisions_enabled:
74            # With revisions, by default filter the packages not belonging to the recipe
75            # unless outdated is specified.
76            metadata = package_layout.load_metadata()
77            ordered_packages = filter_by_revision(metadata, ordered_packages)
78
79        references = OrderedDict()
80        references[None] = self.remote_ref(ordered_packages, recipe_hash)
81        return references
82
83    def _search_packages_in_all(self, ref=None, query=None, outdated=False):
84        references = OrderedDict()
85        # We have to check if there is a remote called "all"
86        # Deprecate: 2.0 can remove this check
87        if 'all' not in self._remotes:
88            for remote in self._remotes.values():
89                try:
90                    packages_props = self._remote_manager.search_packages(remote, ref, query)
91                    if packages_props:
92                        ordered_packages = OrderedDict(sorted(packages_props.items()))
93                        manifest, _ = self._remote_manager.get_recipe_manifest(ref, remote)
94
95                        recipe_hash = manifest.summary_hash
96
97                        if outdated and recipe_hash:
98                            ordered_packages = filter_outdated(ordered_packages, recipe_hash)
99
100                        references[remote.name] = self.remote_ref(ordered_packages, recipe_hash)
101                except NotFoundException:
102                    continue
103            return references
104
105        return self._search_packages_in('all', ref, query, outdated)
106
107    def _search_packages_in(self, remote_name, ref=None, query=None, outdated=False):
108        remote = self._remotes[remote_name]
109        packages_props = self._remote_manager.search_packages(remote, ref, query)
110        ordered_packages = OrderedDict(sorted(packages_props.items()))
111        manifest, ref = self._remote_manager.get_recipe_manifest(ref, remote)
112
113        recipe_hash = manifest.summary_hash
114
115        if outdated and recipe_hash:
116            ordered_packages = filter_outdated(ordered_packages, recipe_hash)
117
118        references = OrderedDict()
119        references[remote.name] = self.remote_ref(ordered_packages, recipe_hash)
120        return references
121