1# repodict.py
2# Managing repo configuration in DNF.
3#
4# Copyright (C) 2013-2016 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use,
7# modify, copy, or redistribute it subject to the terms and conditions of
8# the GNU General Public License v.2, or (at your option) any later version.
9# This program is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY expressed or implied, including the implied warranties of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
12# Public License for more details.  You should have received a copy of the
13# GNU General Public License along with this program; if not, write to the
14# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
16# source code or documentation are not subject to the GNU General Public
17# License and may only be used or replicated with the express permission of
18# Red Hat, Inc.
19#
20
21from __future__ import unicode_literals
22from dnf.exceptions import ConfigError
23from dnf.i18n import _
24
25import dnf.util
26import libdnf.conf
27import fnmatch
28import os
29
30logger = dnf.util.logger
31
32
33class RepoDict(dict):
34    # :api
35    def add(self, repo):
36        # :api
37        id_ = repo.id
38        if id_ in self:
39            msg = 'Repository %s is listed more than once in the configuration'
40            raise ConfigError(msg % id_)
41        try:
42            repo._repo.verify()
43        except RuntimeError as e:
44            raise ConfigError("{0}".format(e))
45        self[id_] = repo
46
47    def all(self):
48        # :api
49        return dnf.util.MultiCallList(self.values())
50
51    def _any_enabled(self):
52        return not dnf.util.empty(self.iter_enabled())
53
54    def _enable_sub_repos(self, sub_name_fn):
55        for repo in self.iter_enabled():
56            for found in self.get_matching(sub_name_fn(repo.id)):
57                if not found.enabled:
58                    logger.info(_('enabling %s repository'), found.id)
59                    found.enable()
60
61    def add_new_repo(self, repoid, conf, baseurl=(), **kwargs):
62        # :api
63        """
64        Creates new repo object and add it into RepoDict. Variables in provided values will be
65        automatically substituted using conf.substitutions (like $releasever, ...)
66
67        @param repoid: Repo ID - string
68        @param conf: dnf Base().conf object
69        @param baseurl: List of strings
70        @param kwargs: keys and values that will be used to setattr on dnf.repo.Repo() object
71        @return: dnf.repo.Repo() object
72        """
73        def substitute(values):
74            if isinstance(values, str):
75                return libdnf.conf.ConfigParser.substitute(values, conf.substitutions)
76            elif isinstance(values, list) or isinstance(values, tuple):
77                substituted = []
78                for value in values:
79                    if isinstance(value, str):
80                        substituted.append(
81                            libdnf.conf.ConfigParser.substitute(value, conf.substitutions))
82                    if substituted:
83                        return substituted
84            return values
85
86        repo = dnf.repo.Repo(repoid, conf)
87        for path in baseurl:
88            if '://' not in path:
89                path = 'file://{}'.format(os.path.abspath(path))
90            repo.baseurl += [substitute(path)]
91        for (key, value) in kwargs.items():
92            setattr(repo, key, substitute(value))
93        self.add(repo)
94        logger.info(_("Added %s repo from %s"), repoid, ', '.join(baseurl))
95        return repo
96
97    def enable_debug_repos(self):
98        # :api
99        """enable debug repos corresponding to already enabled binary repos"""
100
101        def debug_name(name):
102            return ("{}-debug-rpms".format(name[:-5]) if name.endswith("-rpms")
103                    else "{}-debuginfo".format(name))
104
105        self._enable_sub_repos(debug_name)
106
107    def enable_source_repos(self):
108        # :api
109        """enable source repos corresponding to already enabled binary repos"""
110
111        def source_name(name):
112            return ("{}-source-rpms".format(name[:-5]) if name.endswith("-rpms")
113                    else "{}-source".format(name))
114
115        self._enable_sub_repos(source_name)
116
117    def get_matching(self, key):
118        # :api
119        if dnf.util.is_glob_pattern(key):
120            l = [self[k] for k in self if fnmatch.fnmatch(k, key)]
121            return dnf.util.MultiCallList(l)
122        repo = self.get(key, None)
123        if repo is None:
124            return dnf.util.MultiCallList([])
125        return dnf.util.MultiCallList([repo])
126
127    def iter_enabled(self):
128        # :api
129        return (r for r in self.values() if r.enabled)
130
131    def items(self):
132        """return repos sorted by priority"""
133        return (item for item in sorted(super(RepoDict, self).items(),
134                                        key=lambda x: (x[1].priority, x[1].cost)))
135
136    def __iter__(self):
137        return self.keys()
138
139    def keys(self):
140        return (k for k, v in self.items())
141
142    def values(self):
143        return (v for k, v in self.items())
144