aboutsummaryrefslogtreecommitdiff
blob: 22bad3f4d278af2e5eb6184998c15b178516a78e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import os

import portage
from gentoolkit.metadata import MetaData

import xml.etree.cElementTree as etree

from django.db.transaction import commit_on_success
from django.core.management.color import color_style
from django.core.exceptions import ValidationError

from djeuscan.models import Package, Version, Herd, Maintainer
from djeuscan.processing import FakeLogger


class ScanMetadata(object):
    def __init__(self, logger=None):
        self.style = color_style()
        self.logger = logger or FakeLogger()

    def get_package(self, query):
        try:
            return Package.objects.get(name=query)
        except Package.DoesNotExist:
            pass

        try:
            category, package = portage.catsplit(query)
            return Package.objects.get(category=category, name=package)
        except Package.DoesNotExist:
            pass

        try:
            category, package, ver, rev = portage.catpkgsplit(query)
            return Package.objects.get(category=category, name=package)
        except Package.DoesNotExist:
            pass

        return None

    def metadata_from_db(self, query, pkg=None):
        if not pkg:
            pkg = self.get_package(query)

        try:
            version = Version.objects.filter(package=pkg).\
                values('metadata_path').order_by('version', 'revision')[0]
        except IndexError:
            return pkg, None

        if not version['metadata_path']:
            return pkg, None
        return pkg, MetaData(version['metadata_path'])

    def metadata_from_portage(self, query, pkg=None):
        from gentoolkit.query import Query

        matches = Query(query).smart_find(
                in_installed=True,
                in_porttree=True,
                in_overlay=True,
                include_masked=True,
                show_progress=False,
                no_matches_fatal=False,
        )

        if not matches:
            self.logger.error(
                self.style.ERROR("Unknown package '%s'" % query)
            )
            return pkg, None

        matches = sorted(matches)
        package = matches.pop()
        if '9999' in package.version and len(matches):
            package = matches.pop()

        if not pkg:
            pkg, created = Package.objects.get_or_create(
                category=package.category, name=package.name
            )
        else:
            created = False

        if created:
            self.logger.info('+ [p] %s/%s' % (pkg.category, pkg.name))

        return pkg, package.metadata

    def scan(self, query=None, pkg=None):
        try:
            metadata = None
            pkg, metadata = self.metadata_from_db(query, pkg)

            if not metadata:
                pkg, metadata = self.metadata_from_portage(query, pkg)

            if not metadata:
                return
        except Exception as e:
            if pkg:
                self.logger.error(
                    self.style.ERROR('%s/%s: %s' %
                                     (pkg.category, pkg.name, str(e)))
                )
            else:
                self.logger.error(
                    self.style.ERROR('%s: %s' % (query, str(e)))
                )
            return

        herds = dict(
            [(herd[0], herd) for herd in metadata.herds(True)]
        )
        maintainers = dict(
            [(m.email, m) for m in metadata.maintainers()]
        )

        existing_herds = [h.herd for h in pkg.herds.all()]
        new_herds = set(herds.keys()).difference(existing_herds)
        old_herds = set(existing_herds).difference(herds.keys())

        existing_maintainers = [m.email for m in pkg.maintainers.all()]
        new_maintainers = set(maintainers.keys()).\
                          difference(existing_maintainers)
        old_maintainers = set(existing_maintainers).\
                          difference(maintainers.keys())

        for herd in pkg.herds.all():
            if herd.herd in old_herds:
                pkg.herds.remove(herd)

        for herd in new_herds:
            herd = self.store_herd(*herds[herd])
            pkg.herds.add(herd)

        for maintainer in pkg.maintainers.all():
            email = maintainer.email
            name = maintainer.name
            if email in old_maintainers:
                pkg.maintainers.remove(maintainer)
            if (email in maintainers and
                email == name and
                maintainers[email].name != name and
                maintainers[email].name):
                maintainer.name = maintainers[email].name
                maintainer.save()

        for maintainer in new_maintainers:
            maintainer = maintainers[maintainer]
            try:
                maintainer = self.store_maintainer(
                    maintainer.name, maintainer.email
                    )
                pkg.maintainers.add(maintainer)
            except ValidationError:
                self.logger.error(
                    self.style.ERROR("Bad maintainer: '%s' '%s'" % \
                                         (maintainer.name, maintainer.email))
                )

        pkg.save()

    def store_herd(self, name, email):
        if not name:
            name = '{nil}'
        name = name.strip("\r").strip("\n").strip("\t").strip()

        herd, created = Herd.objects.get_or_create(
            herd=name,
            defaults={"email": email}
        )

        if created:
            self.logger.info('+ [h] %s <%s>' % (name, email))

        herd.email = email
        herd.save()

        return herd

    def store_maintainer(self, name, email):
        if not name:
            name = email
        if not name:
            name = '{nil}'

        maintainer, created = Maintainer.objects.get_or_create(
            email=email,
            defaults={"name": name}
        )

        if created:
            self.logger.info(
                '+ [m] %s <%s>' % (name.encode('utf-8'), email)
            )
        return maintainer

    def populate_herds_and_maintainers(self, herds_xml_path=None):
        self.logger.info("Populating herds and maintainers from herds.xml...")

        herds_xml_path = herds_xml_path or os.path.join(
            portage.settings["PORTDIR"], "metadata", "herds.xml"
        )
        try:
            self._herdstree = etree.parse(herds_xml_path)
        except IOError:
            return None

        for herd_node in self._herdstree.getiterator('herd'):
            herd_name = herd_node.findtext('name')
            herd_email = herd_node.findtext('email')

            try:
                herd = self.store_herd(herd_name, herd_email)
            except ValidationError:  # just skip invalid data
                continue

            herd.maintainers.clear()  # clear previous data

            for maintainer_node in herd_node:
                if maintainer_node.tag == "maintainer":
                    maintainer_name = maintainer_node.findtext('name')
                    maintainer_email = maintainer_node.findtext('email')

                    try:
                        maintainer = self.store_maintainer(
                            maintainer_name, maintainer_email
                        )
                    except ValidationError:
                        self.logger.error(
                            self.style.ERROR("Bad maintainer: '%s' '%s'" % \
                                             (maintainer_name, maintainer_email))
                            )

                    herd.maintainers.add(maintainer)


@commit_on_success
def scan_metadata(packages=None, category=None, logger=None, populate=False):
    scan_handler = ScanMetadata(logger=logger)

    if category:
        packages = Package.objects.filter(category=category)
    elif packages is None:
        packages = Package.objects.all()

    if populate:
        scan_handler.populate_herds_and_maintainers()

    for pkg in packages:
        if isinstance(pkg, Package):
            scan_handler.scan('%s/%s' % (pkg.category, pkg.name), pkg)
        else:
            scan_handler.scan(pkg)