This file is indexed.

/usr/lib/python3/dist-packages/popcon.py is in python3-popcon 1.3.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
#!/usr/bin/env python

# popcon.py -
# Copyright (C) 2010-2015  Bastian Venthur
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

# relevant specifications:
# * BASEDIRSPEC: http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html


"""Get Debian popcon values for given packages.

The usage of this module is easy:

    >>> import popcon
    >>> popcon.package('reportbug-ng')
    {'reportbug-ng': 323}
    >>> popcon.package('reportbug-ng', 'reportbug')
    {'reportbug-ng': 323, 'reportbug': 75065}

The raw data (vote, old, recent, no-file) is also available, the sum of
the raw numbers is the number of installations as reported by
`popcon.package`.

    >>> popcon.package_raw('reportbug-ng', 'reportbug')
    {'reportbug-ng': Package(vote=50, old=187, recent=86, no_files=0),
            'reportbug': Package(vote=5279, old=59652, recent=10118,
            no_files=16)}

Behind the scences popcon will try to use cached infomation saved in
`DUMPFILE`. If that file is not available, or older than `EXPIRY`
seconds (default is 7 days) it will download fresh data and save that
into `DUMPFILE`.

The cached data will be kept in memory unless `KEEP_DATA` is set to
False.

"""

from __future__ import division, print_function

import warnings
import time
try:
    # python2
    from urllib2 import Request, urlopen
except ImportError:
    # python3
    from urllib.request import Request, urlopen
import gzip
try:
    # python2
    import StringIO as io
except ImportError:
    # python3
    import io
import tempfile
try:
    # python2
    import cPickle as pickle
except ImportError:
    # python3
    import pickle
import os
import collections

import xdg.BaseDirectory


__author__ = 'Bastian Venthur <venthur@debian.org>'


Package = collections.namedtuple(
    "Package", ["vote", "old", "recent", "no_files"])


# week in seconds
EXPIRY = 86400 * 7
DUMPFILE = os.path.join(
    xdg.BaseDirectory.xdg_cache_home,
     'popcon',
     'debian')  # implements BASEDIRSPEC
RESULTS_URL = "http://popcon.debian.org/all-popcon-results.txt.gz"
KEEP_DATA = True
cached_data = None
cached_timestamp = None


def _fetch():
    """Fetch all popcon results and return unparsed data."""
    request = Request(RESULTS_URL)
    response = urlopen(request)
    txt = response.read()
    response.close()
    txt = _decompress(txt)
    return txt


def _parse(results):
    """Parse all-popcon-results file."""
    ans = dict()
    results = results.splitlines()
    for line in results:
        elems = line.split()
        if elems[0] != "Package:":
            continue
        ans[elems[1]] = Package(*(int(i) for i in elems[2:]))
    return ans


def _decompress(compressed):
    """Decompress a gzipped string."""
    try:
        # python2
        gzippedstream = io.StringIO(compressed)
    except TypeError:
        # python3
        gzippedstream = io.BytesIO(compressed)
    gzipper = gzip.GzipFile(fileobj=gzippedstream)
    data = gzipper.read()
    data = data.decode()
    return data


def package(*packages):
    """Return the number of installations.

    The return value is a dict where the keys are the packages and the
    values the number of installations. If a package was not found it is
    not in the dict.
    """
    raw = package_raw(*packages)
    ans = dict()
    for pkg, values in list(raw.items()):
        ans[pkg] = sum(values)
    return ans


def package_raw(*packages):
    """Return the raw popcon values for the given packages.

    The return value is a dict where the keys are the packages and the
    values a named tuple of integers: (vote, old, recent, no-files)

        vote: number of people who use this package regulary

        old: is the number of people who installed, but don't use this
            package regularly

        recent: is the number of people who upgraded this package
            recently

        no-files: is the number of people whose entry didn't contain
            enough information (atime and ctime were 0)

    """
    global cached_data, cached_timestamp

    earliest_possible_mtime = max(
        time.time() - EXPIRY,
        os.stat(__file__).st_mtime)

    if cached_data is not None and cached_timestamp <= earliest_possible_mtime:
        cached_data = None

    data = cached_data
    if data is None and os.path.exists(DUMPFILE) and os.stat(DUMPFILE).st_mtime > earliest_possible_mtime:
        try:
            with open(DUMPFILE, 'rb') as fh:
                data = pickle.load(fh)
            cached_timestamp = os.stat(DUMPFILE).st_mtime
        except:
            warnings.warn("Problems loading cache file: %s" % DUMPFILE)

    if data is None:
        data = _fetch()
        data = _parse(data)
        if not os.path.isdir(os.path.dirname(DUMPFILE)):  # i still think that makedirs should behave like mkdir -p
            os.makedirs(
                os.path.dirname(DUMPFILE),
                mode=0o700)  # mode according to BASEDIRSPEC
        # as soon as python2.6 is in stable, we can use delete=False
        # here and replace the flush/rename/try:close sequence with the
        # cleaner close/rename.
        temp = tempfile.NamedTemporaryFile(dir=os.path.dirname(DUMPFILE))
        pickle.dump(data, temp)
        temp.flush()
        os.rename(temp.name, DUMPFILE)
        try:
            temp.close()
        except OSError:
            pass
        cached_timestamp = time.time()
    ans = dict()
    for pkg in packages:
        if pkg in data:
            ans[pkg] = data[pkg]
    if KEEP_DATA:
        cached_data = data
    return ans


if __name__ ==  "__main__":
    print(package('reportbug-ng'))
    print(package('reportbug-ng', 'reportbug'))