~ubuntu-branches/ubuntu/raring/software-center/raring-proposed

« back to all changes in this revision

Viewing changes to softwarecenter/db/update.py

  • Committer: Package Import Robot
  • Author(s): Michael Vogt
  • Date: 2012-10-11 15:33:05 UTC
  • mfrom: (195.1.18 quantal)
  • Revision ID: package-import@ubuntu.com-20121011153305-fm5ln7if3rpzts4n
Tags: 5.4.1.1
* lp:~mvo/software-center/reinstall-previous-purchase-token-fix:
  - fix reinstall previous purchases that have a system-wide
    license key LP: #1065481
* lp:~mvo/software-center/lp1060106:
  - Add missing gettext init for utils/update-software-center-agent
    (LP: #1060106)

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
from gi.repository import GObject
31
31
from piston_mini_client import PistonResponseObject
32
32
 
 
33
from softwarecenter.backend.scagent import SoftwareCenterAgent
 
34
from softwarecenter.backend.ubuntusso import UbuntuSSO
33
35
from softwarecenter.distro import get_distro
34
36
from softwarecenter.utils import utf8
35
37
 
 
38
from gettext import gettext as _
 
39
 
36
40
# py3 compat
37
41
try:
38
42
    from configparser import RawConfigParser, NoOptionError
49
53
    import pickle
50
54
 
51
55
 
52
 
from gettext import gettext as _
53
56
from glob import glob
54
57
from urlparse import urlparse
55
58
 
56
59
import softwarecenter.paths
57
60
 
58
 
from softwarecenter.enums import (XapianValues,
59
 
                                  DB_SCHEMA_VERSION,
60
 
                                  AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME,
61
 
                                  PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME,
62
 
                                  )
 
61
from softwarecenter.enums import (
 
62
    AppInfoFields,
 
63
    AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME,
 
64
    DB_SCHEMA_VERSION,
 
65
    XapianValues,
 
66
)
63
67
from softwarecenter.db.database import parse_axi_values_file
64
68
 
65
69
from locale import getdefaultlocale
67
71
 
68
72
 
69
73
from softwarecenter.db.pkginfo import get_pkg_info
70
 
from softwarecenter.distro import get_current_arch, get_foreign_architectures
71
 
from softwarecenter.region import get_region_cached, REGION_BLACKLIST_TAG
 
74
from softwarecenter.distro import (
 
75
    get_current_arch,
 
76
    get_foreign_architectures,
 
77
    )
 
78
from softwarecenter.region import (
 
79
    get_region_cached,
 
80
    REGION_BLACKLIST_TAG,
 
81
    REGION_WHITELIST_TAG,
 
82
    )
 
83
 
72
84
 
73
85
# weights for the different fields
74
86
WEIGHT_DESKTOP_NAME = 10
95
107
    try:
96
108
        cataloged_times = pickle.load(open(CF))
97
109
    except Exception as e:
98
 
        LOG.warn("failed to load %s: %s" % (CF, e))
 
110
        LOG.warn("failed to load file %s: %s", CF, e)
99
111
del CF
100
112
 
101
113
# Enable Xapian's CJK tokenizer (see LP: #745243)
102
114
os.environ['XAPIAN_CJK_NGRAM'] = '1'
103
115
 
104
116
 
 
117
def process_date(date):
 
118
    result = None
 
119
    if re.match("\d+-\d+-\d+ \d+:\d+:\d+", date):
 
120
        # strip the subseconds from the end of the published date string
 
121
        result = str(date).split(".")[0]
 
122
    return result
 
123
 
 
124
 
 
125
def process_popcon(popcon):
 
126
    xapian.sortable_serialise(float(popcon))
 
127
    return popcon
 
128
 
 
129
 
 
130
def get_pkgname_terms(pkgname):
 
131
    result = ["AP" + pkgname,
 
132
              # workaround xapian oddness by providing a "mangled" version
 
133
              # with a different prefix
 
134
              "APM" + pkgname.replace('-', '_')]
 
135
    return result
 
136
 
 
137
 
 
138
def get_default_locale():
 
139
    return getdefaultlocale(('LANGUAGE', 'LANG', 'LC_CTYPE', 'LC_ALL'))[0]
 
140
 
 
141
 
105
142
class AppInfoParserBase(object):
106
 
    """ base class for reading AppInfo meta-data """
107
 
 
 
143
    """Base class for reading AppInfo meta-data."""
 
144
 
 
145
    # map Application Info Fields to xapian "values"
 
146
    FIELD_TO_XAPIAN = {
 
147
        AppInfoFields.ARCH: XapianValues.ARCHIVE_ARCH,
 
148
        AppInfoFields.CHANNEL: XapianValues.ARCHIVE_CHANNEL,
 
149
        AppInfoFields.DEB_LINE: XapianValues.ARCHIVE_DEB_LINE,
 
150
        AppInfoFields.DESCRIPTION: XapianValues.SC_DESCRIPTION,
 
151
        AppInfoFields.CATEGORIES: XapianValues.CATEGORIES,
 
152
        AppInfoFields.CURRENCY: XapianValues.CURRENCY,
 
153
        AppInfoFields.DATE_PUBLISHED: XapianValues.DATE_PUBLISHED,
 
154
        AppInfoFields.ICON: XapianValues.ICON,
 
155
        AppInfoFields.ICON_URL: XapianValues.ICON_URL,
 
156
        AppInfoFields.GETTEXT_DOMAIN: XapianValues.GETTEXT_DOMAIN,
 
157
        AppInfoFields.LICENSE: XapianValues.LICENSE,
 
158
        AppInfoFields.LICENSE_KEY: XapianValues.LICENSE_KEY,
 
159
        AppInfoFields.LICENSE_KEY_PATH: XapianValues.LICENSE_KEY_PATH,
 
160
        AppInfoFields.NAME: XapianValues.APPNAME,
 
161
        AppInfoFields.NAME_UNTRANSLATED: XapianValues.APPNAME_UNTRANSLATED,
 
162
        AppInfoFields.PACKAGE: XapianValues.PKGNAME,
 
163
        AppInfoFields.POPCON: XapianValues.POPCON,
 
164
        AppInfoFields.PPA: XapianValues.ARCHIVE_PPA,
 
165
        AppInfoFields.PRICE: XapianValues.PRICE,
 
166
        AppInfoFields.PURCHASED_DATE: XapianValues.PURCHASED_DATE,
 
167
        AppInfoFields.SECTION: XapianValues.ARCHIVE_SECTION,
 
168
        AppInfoFields.SIGNING_KEY_ID: XapianValues.ARCHIVE_SIGNING_KEY_ID,
 
169
        AppInfoFields.SCREENSHOT_URLS: XapianValues.SCREENSHOT_URLS,
 
170
        AppInfoFields.SUMMARY: XapianValues.SUMMARY,
 
171
        AppInfoFields.SUPPORT_URL: XapianValues.SUPPORT_SITE_URL,
 
172
        AppInfoFields.SUPPORTED_DISTROS: XapianValues.SC_SUPPORTED_DISTROS,
 
173
        AppInfoFields.THUMBNAIL_URL: XapianValues.THUMBNAIL_URL,
 
174
        AppInfoFields.VERSION: XapianValues.VERSION_INFO,
 
175
        AppInfoFields.VIDEO_URL: XapianValues.VIDEO_URL,
 
176
        AppInfoFields.WEBSITE: XapianValues.WEBSITE,
 
177
    }
 
178
 
 
179
    # map Application Info Fields to xapian "terms"
 
180
    FIELD_TO_TERMS = {
 
181
        AppInfoFields.NAME: lambda name: ('AA' + name,),
 
182
        AppInfoFields.CHANNEL: lambda channel: ('AH' + channel,),
 
183
        AppInfoFields.SECTION: lambda section: ('AS' + section,),
 
184
        AppInfoFields.PACKAGE: get_pkgname_terms,
 
185
        AppInfoFields.PPA:
 
186
            # add archive origin data here so that its available even if
 
187
            # the PPA is not (yet) enabled
 
188
            lambda ppa: ('XOOlp-ppa-' + ppa.replace('/', '-'),),
 
189
    }
 
190
 
 
191
    # map apt cache origins to terms
 
192
    ORIGINS_TO_TERMS = {
 
193
        "XOA": "archive",
 
194
        "XOC": "component",
 
195
        "XOL": "label",
 
196
        "XOO": "origin",
 
197
        "XOS": "site",
 
198
    }
 
199
 
 
200
    # data that needs a transformation during the processing
 
201
    FIELD_TRANSFORMERS = {
 
202
        AppInfoFields.DATE_PUBLISHED: process_date,
 
203
        AppInfoFields.PACKAGE:
 
204
            lambda pkgname, pkgname_extension: pkgname + pkgname_extension,
 
205
        AppInfoFields.POPCON: process_popcon,
 
206
        AppInfoFields.PURCHASED_DATE: process_date,
 
207
        AppInfoFields.SUMMARY: lambda s, name: s if s != name else None,
 
208
        AppInfoFields.SUPPORTED_DISTROS: json.dumps,
 
209
    }
 
210
 
 
211
    # a mapping that the subclasses override, it defines the mapping
 
212
    # from the Application Info Fileds to the "native" keywords used
 
213
    # by the various subclasses, e.g. "
 
214
    #   X-AppInstall-Channel for desktop files
 
215
    # or
 
216
    #   "channel" for the json data
108
217
    MAPPING = {}
109
218
 
110
 
    def get_desktop(self, key, translated=True):
111
 
        """ get a AppInfo entry for the given key """
112
 
 
113
 
    def has_option_desktop(self, key):
114
 
        """ return True if there is a given AppInfo info """
115
 
 
116
 
    def _get_desktop_list(self, key, split_str=";"):
 
219
    NOT_DEFINED = object()
 
220
    SPLIT_STR_CHAR = ';'
 
221
 
 
222
    def get_value(self, key, translated=True):
 
223
        """Get the AppInfo entry for the given key."""
 
224
        return getattr(self, self._apply_mapping(key), None)
 
225
 
 
226
    def _get_value_list(self, key, split_str=None):
 
227
        if split_str is None:
 
228
            split_str = self.SPLIT_STR_CHAR
117
229
        result = []
118
 
        try:
119
 
            list_str = self.get_desktop(key)
120
 
            for item in list_str.split(split_str):
121
 
                if item:
 
230
        list_str = self.get_value(key)
 
231
        if list_str is not None:
 
232
            try:
 
233
                for item in filter(lambda s: s, list_str.split(split_str)):
122
234
                    result.append(item)
123
 
        except (NoOptionError, KeyError):
124
 
            pass
 
235
            except (NoOptionError, KeyError):
 
236
                pass
125
237
        return result
126
238
 
127
239
    def _apply_mapping(self, key):
128
 
        # strip away bogus prefixes
129
 
        if key.startswith("X-AppInstall-"):
130
 
            key = key[len("X-AppInstall-"):]
131
 
        if key in self.MAPPING:
132
 
            return self.MAPPING[key]
133
 
        return key
134
 
 
135
 
    def get_desktop_categories(self):
136
 
        return self._get_desktop_list("Categories")
137
 
 
138
 
    def get_desktop_mimetypes(self):
139
 
        if not self.has_option_desktop("MimeType"):
140
 
            return []
141
 
        return self._get_desktop_list("MimeType")
 
240
        return self.MAPPING.get(key, key)
 
241
 
 
242
    def get_categories(self):
 
243
        return self._get_value_list(AppInfoFields.CATEGORIES)
 
244
 
 
245
    def get_mimetypes(self):
 
246
        result = self._get_value_list(AppInfoFields.MIMETYPE)
 
247
        if not result:
 
248
            result = []
 
249
        return result
 
250
 
 
251
    def _set_doc_from_key(self, doc, key, translated=True, dry_run=False,
 
252
                          **kwargs):
 
253
        value = self.get_value(key, translated=translated)
 
254
        if value is not None:
 
255
            modifier = self.FIELD_TRANSFORMERS.get(key, lambda i, **kw: i)
 
256
            value = modifier(value, **kwargs)
 
257
            if value is not None and not dry_run:
 
258
                # add value to the xapian database if defined
 
259
                doc_key = self.FIELD_TO_XAPIAN[key]
 
260
                doc.add_value(doc_key, value)
 
261
                # add terms to the xapian database
 
262
                get_terms = self.FIELD_TO_TERMS.get(key, lambda i: [])
 
263
                for t in get_terms(value):
 
264
                    doc.add_term(t)
 
265
 
 
266
        return value
142
267
 
143
268
    @property
144
269
    def desktopf(self):
145
 
        """ return the file that the AppInfo comes from """
 
270
        """Return the file that the AppInfo comes from."""
 
271
 
 
272
    @property
 
273
    def is_ignored(self):
 
274
        ignored = self.get_value(AppInfoFields.IGNORE)
 
275
        if ignored:
 
276
            ignored = ignored.strip().lower()
 
277
 
 
278
        return (ignored == "true")
 
279
 
 
280
    def make_doc(self, cache):
 
281
        """Build a Xapian document from the desktop info."""
 
282
        doc = xapian.Document()
 
283
        # app name is the data
 
284
        name = self._set_doc_from_key(doc, AppInfoFields.NAME)
 
285
        assert name is not None
 
286
        doc.set_data(name)
 
287
        self._set_doc_from_key(doc, AppInfoFields.NAME_UNTRANSLATED,
 
288
                               translated=False)
 
289
 
 
290
        # check if we should ignore this file
 
291
        if self.is_ignored:
 
292
            LOG.debug("%r.make_doc: %r is ignored.",
 
293
                      self.__class__.__name__, self.desktopf)
 
294
            return
 
295
 
 
296
        # architecture
 
297
        pkgname_extension = ''
 
298
        arches = self._set_doc_from_key(doc, AppInfoFields.ARCH)
 
299
        if arches:
 
300
            native_archs = get_current_arch() in arches.split(',')
 
301
            foreign_archs = list(set(arches.split(',')) &
 
302
                                 set(get_foreign_architectures()))
 
303
            if not (native_archs or foreign_archs):
 
304
                return
 
305
            if not native_archs and foreign_archs:
 
306
                pkgname_extension = ':' + foreign_archs[0]
 
307
 
 
308
        # package name
 
309
        pkgname = self._set_doc_from_key(doc, AppInfoFields.PACKAGE,
 
310
                                         pkgname_extension=pkgname_extension)
 
311
        doc.add_value(XapianValues.DESKTOP_FILE, self.desktopf)
 
312
 
 
313
        # display name
 
314
        display_name = axi_values.get("display_name")
 
315
        if display_name is not None:
 
316
            doc.add_value(display_name, name)
 
317
 
 
318
        # cataloged_times
 
319
        catalogedtime = axi_values.get("catalogedtime")
 
320
        if catalogedtime is not None and pkgname in cataloged_times:
 
321
            doc.add_value(catalogedtime,
 
322
                          xapian.sortable_serialise(cataloged_times[pkgname]))
 
323
 
 
324
        # section (mail, base, ..)
 
325
        if pkgname in cache and cache[pkgname].candidate:
 
326
            section = cache[pkgname].section
 
327
            doc.add_term("AE" + section)
 
328
 
 
329
        fields = (
 
330
            AppInfoFields.CHANNEL,  # channel (third party stuff)
 
331
            AppInfoFields.DEB_LINE,  # deb-line (third party)
 
332
            AppInfoFields.DESCRIPTION,  # description software-center extension
 
333
            AppInfoFields.GETTEXT_DOMAIN,  # check gettext domain
 
334
            AppInfoFields.ICON,  # icon
 
335
            AppInfoFields.LICENSE,  # license (third party)
 
336
            AppInfoFields.LICENSE_KEY,  # license key (third party)
 
337
            AppInfoFields.LICENSE_KEY_PATH,  # license keypath (third party)
 
338
            AppInfoFields.PPA,  # PPA (third party stuff)
 
339
            AppInfoFields.PURCHASED_DATE,  # purchased date
 
340
            AppInfoFields.SCREENSHOT_URLS,  # screenshot (for third party)
 
341
            AppInfoFields.SECTION,  # pocket (main, restricted, ...)
 
342
            AppInfoFields.SIGNING_KEY_ID,  # signing key (third party)
 
343
            AppInfoFields.SUPPORT_URL,  # support url (mainly pay stuff)
 
344
            AppInfoFields.SUPPORTED_DISTROS,  # supported distros
 
345
            AppInfoFields.THUMBNAIL_URL,  # thumbnail (for third party)
 
346
            AppInfoFields.VERSION,  # version support (for e.g. the scagent)
 
347
            AppInfoFields.VIDEO_URL,  # video support (for third party mostly)
 
348
            AppInfoFields.WEBSITE,  # homepage url (developer website)
 
349
        )
 
350
        for field in fields:
 
351
            self._set_doc_from_key(doc, field)
 
352
 
 
353
        # date published
 
354
        date_published_str = self._set_doc_from_key(
 
355
            doc, AppInfoFields.DATE_PUBLISHED)
 
356
        # we use the date published value for the cataloged time as well
 
357
        if date_published_str is not None:
 
358
            LOG.debug("pkgname: %s, date_published cataloged time is: %s",
 
359
                      pkgname, date_published_str)
 
360
            date_published = time.mktime(time.strptime(date_published_str,
 
361
                                                       "%Y-%m-%d  %H:%M:%S"))
 
362
            # a value for our own DB
 
363
            doc.add_value(XapianValues.DB_CATALOGED_TIME,
 
364
                          xapian.sortable_serialise(date_published))
 
365
            if "catalogedtime" in axi_values:
 
366
                # compat with a-x-i
 
367
                doc.add_value(axi_values["catalogedtime"],
 
368
                              xapian.sortable_serialise(date_published))
 
369
 
 
370
        # icon (for third party)
 
371
        url = self._set_doc_from_key(doc, AppInfoFields.ICON_URL)
 
372
        if url and self.get_value(AppInfoFields.ICON) is None:
 
373
            # prefix pkgname to avoid name clashes
 
374
            doc.add_value(XapianValues.ICON,
 
375
                          "%s-icon-%s" % (pkgname, os.path.basename(url)))
 
376
 
 
377
        # price (pay stuff)
 
378
        price = self._set_doc_from_key(doc, AppInfoFields.PRICE)
 
379
        if price:
 
380
            # this is a commercial app, indicate it in the component value
 
381
            doc.add_value(XapianValues.ARCHIVE_SECTION, "commercial")
 
382
            # this is hardcoded to US dollar for now, but if the server
 
383
            # ever changes we can update
 
384
            doc.add_value(XapianValues.CURRENCY, "US$")
 
385
 
 
386
        # write out categories
 
387
        for cat in self.get_categories():
 
388
            doc.add_term("AC" + cat.lower())
 
389
        categories_string = ";".join(self.get_categories())
 
390
        doc.add_value(XapianValues.CATEGORIES, categories_string)
 
391
 
 
392
        # mimetypes
 
393
        for mime in self.get_mimetypes():
 
394
            doc.add_term("AM" + mime.lower())
 
395
 
 
396
        # get type (to distinguish between apps and packages)
 
397
        app_type = self.get_value(AppInfoFields.TYPE)
 
398
        if app_type:
 
399
            doc.add_term("AT" + app_type.lower())
 
400
 
 
401
        # (deb)tags (in addition to the pkgname debtags)
 
402
        tags_string = self.get_value(AppInfoFields.TAGS)
 
403
        if tags_string:
 
404
            # convert to list and register
 
405
            tags = [tag.strip().lower() for tag in tags_string.split(",")]
 
406
            for tag in tags:
 
407
                doc.add_term("XT" + tag)
 
408
            # ENFORCE region blacklist/whitelist by not registering
 
409
            #          the app at all
 
410
            region = get_region_cached()
 
411
            if region:
 
412
                countrycode = region["countrycode"].lower()
 
413
                # blacklist
 
414
                if "%s%s" % (REGION_BLACKLIST_TAG, countrycode) in tags:
 
415
                    LOG.info("%r.make_doc: skipping region restricted app %r "
 
416
                             "(blacklisted)", self.__class__.__name__, name)
 
417
                    return
 
418
                # whitelist
 
419
                for tag in tags:
 
420
                    if (tag.startswith(REGION_WHITELIST_TAG) and not
 
421
                        "%s%s" % (REGION_WHITELIST_TAG, countrycode) in tag):
 
422
                        LOG.info("%r.make_doc: skipping region restricted "
 
423
                                 "app %r (region not whitelisted)",
 
424
                                 self.__class__.__name__, name)
 
425
                        return
 
426
 
 
427
        # popcon
 
428
        # FIXME: popularity not only based on popcon but also
 
429
        #        on archive section, third party app etc
 
430
        popcon = self._set_doc_from_key(doc, AppInfoFields.POPCON)
 
431
        if popcon is not None:
 
432
            global popcon_max
 
433
            popcon_max = max(popcon_max, popcon)
 
434
 
 
435
        # comment goes into the summary data if there is one,
 
436
        # otherwise we try GenericName and if nothing else,
 
437
        # the summary of the candidate package
 
438
        summary = self._set_doc_from_key(doc, AppInfoFields.SUMMARY, name=name)
 
439
        if summary is None and pkgname in cache and cache[pkgname].candidate:
 
440
            summary = cache[pkgname].candidate.summary
 
441
            doc.add_value(XapianValues.SUMMARY, summary)
 
442
 
 
443
        return doc
 
444
 
 
445
    def index_app_info(self, db, cache):
 
446
        term_generator = xapian.TermGenerator()
 
447
        term_generator.set_database(db)
 
448
        try:
 
449
            # this tests if we have spelling suggestions (there must be
 
450
            # a better way?!?) - this is needed as inmemory does not have
 
451
            # spelling corrections, but it allows setting the flag and will
 
452
            # raise a exception much later
 
453
            db.add_spelling("test")
 
454
            db.remove_spelling("test")
 
455
            # this enables the flag for it (we only reach this line if
 
456
            # the db supports spelling suggestions)
 
457
            term_generator.set_flags(xapian.TermGenerator.FLAG_SPELLING)
 
458
        except xapian.UnimplementedError:
 
459
            pass
 
460
        doc = self.make_doc(cache)
 
461
        if not doc:
 
462
            LOG.debug("%r.index_app_info: returned invalid doc %r, ignoring.",
 
463
                      self.__class__.__name__, doc)
 
464
            return
 
465
        name = doc.get_data()
 
466
 
 
467
        if name in seen:
 
468
            LOG.debug("%r.index_app_info: duplicated name %r (%r)",
 
469
                      self.__class__.__name__, name, self.desktopf)
 
470
        LOG.debug("%r.index_app_info: indexing %r",
 
471
                  self.__class__.__name__, name)
 
472
        seen.add(name)
 
473
 
 
474
        term_generator.set_document(doc)
 
475
        term_generator.index_text_without_positions(name, WEIGHT_DESKTOP_NAME)
 
476
 
 
477
        pkgname = doc.get_value(XapianValues.PKGNAME)
 
478
        # add packagename as meta-data too
 
479
        term_generator.index_text_without_positions(pkgname,
 
480
            WEIGHT_APT_PKGNAME)
 
481
 
 
482
        # now add search data from the desktop file
 
483
        for weigth, key in [('GENERICNAME', AppInfoFields.GENERIC_NAME),
 
484
                            ('COMMENT', AppInfoFields.SUMMARY),
 
485
                            ('DESCRIPTION', AppInfoFields.DESCRIPTION)]:
 
486
            s = self.get_value(key)
 
487
            if not s:
 
488
                continue
 
489
            k = "WEIGHT_DESKTOP_" + weigth
 
490
            w = globals().get(k)
 
491
            if w is None:
 
492
                LOG.debug("%r.index_app_info: WEIGHT %r not found",
 
493
                          self.__class__.__name__, k)
 
494
                w = 1
 
495
            term_generator.index_text_without_positions(s, w)
 
496
 
 
497
        # add data from the apt cache
 
498
        if pkgname in cache and cache[pkgname].candidate:
 
499
            term_generator.index_text_without_positions(
 
500
                cache[pkgname].candidate.summary, WEIGHT_APT_SUMMARY)
 
501
            term_generator.index_text_without_positions(
 
502
                cache[pkgname].candidate.description, WEIGHT_APT_DESCRIPTION)
 
503
            for origin in cache[pkgname].candidate.origins:
 
504
                for (term, attr) in self.ORIGINS_TO_TERMS.items():
 
505
                    doc.add_term(term + getattr(origin, attr))
 
506
 
 
507
        # add our keywords (with high priority)
 
508
        keywords = self.get_value(AppInfoFields.KEYWORDS)
 
509
        if keywords:
 
510
            for keyword in filter(lambda s: s, keywords.split(";")):
 
511
                term_generator.index_text_without_positions(
 
512
                    keyword, WEIGHT_DESKTOP_KEYWORD)
 
513
 
 
514
        # now add it
 
515
        db.add_document(doc)
146
516
 
147
517
 
148
518
class SCAApplicationParser(AppInfoParserBase):
149
 
    """ map the data we get from the software-center-agent """
 
519
    """Map the data we get from the software-center-agent."""
150
520
 
151
521
    # map from requested key to sca_application attribute
152
 
    MAPPING = {'Name': 'name',
153
 
               'Price': 'price',
154
 
               'Package': 'package_name',
155
 
               'Categories': 'categories',
156
 
               'Channel': 'channel',
157
 
               'Signing-Key-Id': 'signing_key_id',
158
 
               'License': 'license',
159
 
               'Date-Published': 'date_published',
160
 
               'PPA': 'archive_id',
161
 
               'Screenshot-Url': 'screenshot_url',
162
 
               'Thumbnail-Url': 'thumbnail_url',
163
 
               'Video-Url': 'video_embedded_html_url',
164
 
               'Icon-Url': 'icon_url',
165
 
               'Support-Url': 'support_url',
166
 
               'Description': 'Description',
167
 
               'Comment': 'Comment',
168
 
               'Version': 'version',
169
 
               'Supported-Distros': 'series',
170
 
               # tags are special, see _apply_exception
171
 
              }
 
522
    MAPPING = {
 
523
        AppInfoFields.KEYWORDS: 'keywords',
 
524
        AppInfoFields.TAGS: 'tags',
 
525
        AppInfoFields.NAME: 'name',
 
526
        AppInfoFields.NAME_UNTRANSLATED: 'name',
 
527
        AppInfoFields.CHANNEL: 'channel',
 
528
        AppInfoFields.PPA: 'archive_id',
 
529
        AppInfoFields.SIGNING_KEY_ID: 'signing_key_id',
 
530
        AppInfoFields.CATEGORIES: 'categories',
 
531
        AppInfoFields.DATE_PUBLISHED: 'date_published',
 
532
        AppInfoFields.ICON_URL: 'icon_url',
 
533
        AppInfoFields.LICENSE: 'license',
 
534
        AppInfoFields.PACKAGE: 'package_name',
 
535
        AppInfoFields.PRICE: 'price',
 
536
        AppInfoFields.DESCRIPTION: 'description',
 
537
        AppInfoFields.SUPPORTED_DISTROS: 'series',
 
538
        AppInfoFields.SCREENSHOT_URLS: 'screenshot_url',
 
539
        AppInfoFields.SUMMARY: 'comment',
 
540
        AppInfoFields.SUPPORT_URL: 'support_url',
 
541
        AppInfoFields.THUMBNAIL_URL: 'thumbnail_url',
 
542
        AppInfoFields.VERSION: 'version',
 
543
        AppInfoFields.VIDEO_URL: 'video_embedded_html_url',
 
544
        AppInfoFields.WEBSITE: 'website',
 
545
        # tags are special, see _apply_exception
 
546
    }
172
547
 
173
548
    # map from requested key to a static data element
174
 
    STATIC_DATA = {'Type': 'Application',
175
 
                  }
 
549
    STATIC_DATA = {
 
550
        AppInfoFields.TYPE: 'Application',
 
551
    }
176
552
 
177
553
    def __init__(self, sca_application):
 
554
        super(SCAApplicationParser, self).__init__()
178
555
        # the piston object we got from software-center-agent
179
556
        self.sca_application = sca_application
180
557
        self.origin = "software-center-agent"
189
566
            self.sca_application.thumbnail_url = \
190
567
                self.sca_application.screenshot_url
191
568
        if hasattr(self.sca_application, "description"):
192
 
            comment = self.sca_application.description.split("\n")[0].strip()
193
 
            self.sca_application.Comment = comment
194
 
            self.sca_application.Description = "\n".join(
195
 
                self.sca_application.description.split("\n")[1:]).strip()
 
569
            comment, desc = self.sca_application.description.split("\n", 1)
 
570
            self.sca_application.comment = comment.strip()
 
571
            self.sca_application.description = desc.strip()
196
572
 
197
573
        # debtags is send as a list, but we need it as a comma seperated string
198
 
        self.sca_application.Tags = ",".join(getattr(self.sca_application,
199
 
            "debtags", []))
 
574
        debtags = getattr(self.sca_application, "debtags", [])
 
575
        self.sca_application.tags = ",".join(debtags)
200
576
 
201
577
        # we only support a single video currently :/
202
 
        if hasattr(self.sca_application, "video_embedded_html_urls"):
203
 
            if self.sca_application.video_embedded_html_urls:
204
 
                video_url = self.sca_application.video_embedded_html_urls[0]
205
 
                self.sca_application.video_embedded_html_url = video_url
 
578
        urls = getattr(self.sca_application, "video_embedded_html_urls", None)
 
579
        if urls:
 
580
            self.sca_application.video_embedded_html_url = urls[0]
 
581
        else:
 
582
            self.sca_application.video_embedded_html_url = None
206
583
 
207
584
        # XXX 2012-01-16 bug=917109
208
585
        # We can remove these work-arounds once the above bug is fixed on
232
609
                          for url in self.sca_application.screenshot_urls])
233
610
            self.sca_application.screenshot_url = s
234
611
 
235
 
    def get_desktop(self, key, translated=True):
 
612
        keywords = getattr(self.sca_application, 'keywords', self.NOT_DEFINED)
 
613
        if keywords is self.NOT_DEFINED:
 
614
            self.sca_application.keywords = ''
 
615
 
 
616
    def get_value(self, key, translated=True):
236
617
        if key in self.STATIC_DATA:
237
618
            return self.STATIC_DATA[key]
238
 
        return getattr(self.sca_application, self._apply_mapping(key))
 
619
        return getattr(self.sca_application, self._apply_mapping(key), None)
239
620
 
240
 
    def get_desktop_categories(self):
 
621
    def get_categories(self):
241
622
        try:
242
 
            return (['DEPARTMENT:' + self.sca_application.department[-1]] +
243
 
                self._get_desktop_list("Categories"))
 
623
            dept = ['DEPARTMENT:' + self.sca_application.department[-1]]
 
624
            return (dept + self._get_value_list(AppInfoFields.CATEGORIES))
244
625
        except:
245
 
            return self._get_desktop_list("Categories")
246
 
 
247
 
    def has_option_desktop(self, key):
248
 
        return (key in self.STATIC_DATA or
249
 
                hasattr(self.sca_application, self._apply_mapping(key)))
 
626
            return self._get_value_list(AppInfoFields.CATEGORIES)
250
627
 
251
628
    @property
252
629
    def desktopf(self):
256
633
class SCAPurchasedApplicationParser(SCAApplicationParser):
257
634
    """A purchased application hase some additional subscription attributes."""
258
635
 
259
 
    def __init__(self, sca_subscription):
260
 
        # The sca_subscription is a PistonResponseObject, whereas any child
261
 
        # objects are normal Python dicts.
262
 
        self.sca_subscription = sca_subscription
263
 
        super(SCAPurchasedApplicationParser, self).__init__(
264
 
            PistonResponseObject.from_dict(sca_subscription.application))
265
 
 
266
636
    SUBSCRIPTION_MAPPING = {
267
637
        # this key can be used to get the original deb_line that the
268
638
        # server returns, it will be at the distroseries that was current
269
639
        # at purchase time
270
 
        'Deb-Line-Orig': 'deb_line',
 
640
        AppInfoFields.DEB_LINE_ORIG: 'deb_line',
271
641
        # this is what s-c will always use, the deb_line updated to the
272
642
        # current distroseries, note that you should ensure that the app
273
643
        # is not in state: PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES
274
 
        'Deb-Line': 'deb_line',
275
 
        'Purchased-Date': 'purchase_date',
276
 
        'License-Key': 'license_key',
277
 
        'License-Key-Path': 'license_key_path',
278
 
        }
 
644
        AppInfoFields.DEB_LINE: 'deb_line',
 
645
        AppInfoFields.PURCHASED_DATE: 'purchase_date',
 
646
        AppInfoFields.LICENSE_KEY: 'license_key',
 
647
        AppInfoFields.LICENSE_KEY_PATH: 'license_key_path',
 
648
    }
279
649
 
280
 
    MAPPING = dict(
281
 
        SCAApplicationParser.MAPPING.items() + SUBSCRIPTION_MAPPING.items())
 
650
    def __init__(self, sca_subscription):
 
651
        # The sca_subscription is a PistonResponseObject, whereas any child
 
652
        # objects are normal Python dicts.
 
653
        self.sca_subscription = sca_subscription
 
654
        self.MAPPING.update(self.SUBSCRIPTION_MAPPING)
 
655
        super(SCAPurchasedApplicationParser, self).__init__(
 
656
            PistonResponseObject.from_dict(sca_subscription.application))
282
657
 
283
658
    @classmethod
284
659
    def update_debline(cls, debline):
290
665
 
291
666
        return unicode(source_entry)
292
667
 
293
 
    def get_desktop(self, key, translated=True):
294
 
        if self._subscription_has_option_desktop(key):
295
 
            DEB_LINE_KEY = 'X-AppInstall-Deb-Line'
296
 
            if key.startswith(DEB_LINE_KEY):
297
 
                debline_orig = getattr(
298
 
                    self.sca_subscription, self._apply_mapping(DEB_LINE_KEY))
299
 
                if key == 'X-AppInstall-Deb-Line-Orig':
300
 
                    return debline_orig
301
 
                else:
302
 
                    deb_line = self.update_debline(debline_orig)
303
 
                    return deb_line
304
 
 
305
 
            return getattr(self.sca_subscription, self._apply_mapping(key))
306
 
        return super(SCAPurchasedApplicationParser, self).get_desktop(key)
307
 
 
308
 
    def _subscription_has_option_desktop(self, key):
309
 
        return hasattr(
310
 
            self.sca_subscription, self._apply_mapping(key))
311
 
 
312
 
    def has_option_desktop(self, key):
313
 
        subscription_has_option = self._subscription_has_option_desktop(key)
314
 
        application_has_option = super(
315
 
            SCAPurchasedApplicationParser, self).has_option_desktop(key)
316
 
        return subscription_has_option or application_has_option
 
668
    def get_value(self, key, translated=True):
 
669
        result = getattr(self.sca_subscription, self._apply_mapping(key),
 
670
                         self.NOT_DEFINED)
 
671
        if result is not self.NOT_DEFINED and key == AppInfoFields.DEB_LINE:
 
672
            result = self.update_debline(result)
 
673
        elif result is self.NOT_DEFINED:
 
674
            result = super(
 
675
                SCAPurchasedApplicationParser, self).get_value(key)
 
676
 
 
677
        return result
317
678
 
318
679
    def _apply_exceptions(self):
319
680
        super(SCAPurchasedApplicationParser, self)._apply_exceptions()
322
683
        #          gets confused about (appname, pkgname) duplication
323
684
        self.sca_application.name = utf8(_("%s (already purchased)")) % utf8(
324
685
            self.sca_application.name)
325
 
        self.sca_application.channel = (
326
 
            PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME)
 
686
        for attr_name in ('license_key', 'license_key_path'):
 
687
            attr = getattr(self.sca_subscription, attr_name, self.NOT_DEFINED)
 
688
            if attr is self.NOT_DEFINED:
 
689
                setattr(self.sca_subscription, attr_name, None)
327
690
 
328
691
 
329
692
class JsonTagSectionParser(AppInfoParserBase):
330
693
 
331
 
    MAPPING = {'Name': 'application_name',
332
 
               'Comment': 'description',
333
 
               'Price': 'price',
334
 
               'Package': 'package_name',
335
 
               'Categories': 'categories',
336
 
              }
 
694
    MAPPING = {
 
695
        AppInfoFields.CATEGORIES: 'categories',
 
696
        AppInfoFields.NAME: 'application_name',
 
697
        AppInfoFields.PACKAGE: 'package_name',
 
698
        AppInfoFields.PRICE: 'price',
 
699
        AppInfoFields.SUMMARY: 'description',
 
700
    }
 
701
 
 
702
    STATIC_DATA = {
 
703
        AppInfoFields.TYPE: 'Application',
 
704
    }
337
705
 
338
706
    def __init__(self, tag_section, url):
 
707
        super(JsonTagSectionParser, self).__init__()
339
708
        self.tag_section = tag_section
340
709
        self.url = url
341
710
 
342
 
    def get_desktop(self, key, translated=True):
343
 
        return self.tag_section[self._apply_mapping(key)]
344
 
 
345
 
    def has_option_desktop(self, key):
346
 
        return self._apply_mapping(key) in self.tag_section
 
711
    def get_value(self, key, translated=True):
 
712
        if key in self.STATIC_DATA:
 
713
            return self.STATIC_DATA[key]
 
714
        return self.tag_section.get(self._apply_mapping(key))
347
715
 
348
716
    @property
349
717
    def desktopf(self):
352
720
 
353
721
class AppStreamXMLParser(AppInfoParserBase):
354
722
 
355
 
    MAPPING = {'Name': 'name',
356
 
               'Comment': 'summary',
357
 
               'Package': 'pkgname',
358
 
               'Categories': 'appcategories',
359
 
               'Keywords': 'keywords',
360
 
               'MimeType': 'mimetypes',
361
 
               'Icon': 'icon',
362
 
              }
 
723
    MAPPING = {
 
724
        AppInfoFields.CATEGORIES: 'appcategories',
 
725
        AppInfoFields.ICON: 'icon',
 
726
        AppInfoFields.KEYWORDS: 'keywords',
 
727
        AppInfoFields.MIMETYPE: 'mimetypes',
 
728
        AppInfoFields.NAME: 'name',
 
729
        AppInfoFields.PACKAGE: 'pkgname',
 
730
        AppInfoFields.SUMMARY: 'summary',
 
731
    }
363
732
 
364
 
    LISTS = {"appcategories": "appcategory",
365
 
             "keywords": "keyword",
366
 
             "mimetypes": "mimetype",
367
 
            }
 
733
    LISTS = {
 
734
        "appcategories": "appcategory",
 
735
        "keywords": "keyword",
 
736
        "mimetypes": "mimetype",
 
737
    }
368
738
 
369
739
    # map from requested key to a static data element
370
 
    STATIC_DATA = {'Type': 'Application',
371
 
                  }
 
740
    STATIC_DATA = {
 
741
        AppInfoFields.TYPE: 'Application',
 
742
    }
 
743
 
 
744
    SPLIT_STR_CHAR = ','
372
745
 
373
746
    def __init__(self, appinfo_xml, xmlfile):
 
747
        super(AppStreamXMLParser, self).__init__()
374
748
        self.appinfo_xml = appinfo_xml
375
749
        self.xmlfile = xmlfile
376
750
 
377
 
    def get_desktop(self, key, translated=True):
 
751
    def get_value(self, key, translated=True):
378
752
        if key in self.STATIC_DATA:
379
753
            return self.STATIC_DATA[key]
380
754
        key = self._apply_mapping(key)
383
757
        else:
384
758
            return self._parse_value(key, translated)
385
759
 
386
 
    def get_desktop_categories(self):
387
 
        return self._get_desktop_list("Categories", split_str=',')
388
 
 
389
 
    def get_desktop_mimetypes(self):
390
 
        if not self.has_option_desktop("MimeType"):
391
 
            return []
392
 
        return self._get_desktop_list("MimeType", split_str=',')
393
 
 
394
760
    def _parse_value(self, key, translated):
395
 
        locale = getdefaultlocale(('LANGUAGE', 'LANG', 'LC_CTYPE',
396
 
            'LC_ALL'))[0]
 
761
        locale = get_default_locale()
397
762
        for child in self.appinfo_xml.iter(key):
398
763
            if translated:
399
764
                if child.get("lang") == locale:
413
778
                l.append(child.text)
414
779
        return ",".join(l)
415
780
 
416
 
    def has_option_desktop(self, key):
417
 
        if key in self.STATIC_DATA:
418
 
            return True
419
 
        key = self._apply_mapping(key)
420
 
        return not self.appinfo_xml.find(key) is None
421
 
 
422
781
    @property
423
782
    def desktopf(self):
424
783
        subelm = self.appinfo_xml.find("id")
426
785
 
427
786
 
428
787
class DesktopTagSectionParser(AppInfoParserBase):
 
788
 
 
789
    MAPPING = {
 
790
        AppInfoFields.ARCH: 'X-AppInstall-Architectures',
 
791
        AppInfoFields.CHANNEL: 'X-AppInstall-Channel',
 
792
        AppInfoFields.DATE_PUBLISHED: 'X-AppInstall-Date-Published',
 
793
        AppInfoFields.DEB_LINE: 'X-AppInstall-Deb-Line',
 
794
        AppInfoFields.DESCRIPTION: 'X-AppInstall-Description',
 
795
        AppInfoFields.GENERIC_NAME: 'GenericName',
 
796
        AppInfoFields.GETTEXT_DOMAIN: 'X-Ubuntu-Gettext-Domain',
 
797
        AppInfoFields.ICON: 'Icon',
 
798
        AppInfoFields.ICON_URL: 'X-AppInstall-Icon-Url',
 
799
        AppInfoFields.IGNORE: 'X-AppInstall-Ignore',
 
800
        AppInfoFields.KEYWORDS: 'X-AppInstall-Keywords',
 
801
        AppInfoFields.LICENSE: 'X-AppInstall-License',
 
802
        AppInfoFields.LICENSE_KEY: 'X-AppInstall-License-Key',
 
803
        AppInfoFields.LICENSE_KEY_PATH: 'X-AppInstall-License-Key-Path',
 
804
        AppInfoFields.NAME:
 
805
            ('X-Ubuntu-Software-Center-Name', 'X-GNOME-FullName', 'Name'),
 
806
        AppInfoFields.NAME_UNTRANSLATED:
 
807
            ('X-Ubuntu-Software-Center-Name', 'X-GNOME-FullName', 'Name'),
 
808
        AppInfoFields.PACKAGE: 'X-AppInstall-Package',
 
809
        AppInfoFields.POPCON: 'X-AppInstall-Popcon',
 
810
        AppInfoFields.PPA: 'X-AppInstall-PPA',
 
811
        AppInfoFields.PRICE: 'X-AppInstall-Price',
 
812
        AppInfoFields.PURCHASED_DATE: 'X-AppInstall-Purchased-Date',
 
813
        AppInfoFields.SCREENSHOT_URLS: 'X-AppInstall-Screenshot-Url',
 
814
        AppInfoFields.SECTION: 'X-AppInstall-Section',
 
815
        AppInfoFields.SIGNING_KEY_ID: 'X-AppInstall-Signing-Key-Id',
 
816
        AppInfoFields.SUMMARY: ('Comment', 'GenericName'),
 
817
        AppInfoFields.SUPPORTED_DISTROS: 'Supported-Distros',
 
818
        AppInfoFields.SUPPORT_URL: 'X-AppInstall-Support-Url',
 
819
        AppInfoFields.TAGS: 'X-AppInstall-Tags',
 
820
        AppInfoFields.THUMBNAIL_URL: 'X-AppInstall-Thumbnail-Url',
 
821
        AppInfoFields.TYPE: 'Type',
 
822
        AppInfoFields.VERSION: 'X-AppInstall-Version',
 
823
        AppInfoFields.VIDEO_URL: 'X-AppInstall-Video-Url',
 
824
        AppInfoFields.WEBSITE: 'Homepage',
 
825
    }
 
826
 
 
827
    LOCALE_EXPR = '%s-%s'
 
828
 
429
829
    def __init__(self, tag_section, tagfile):
 
830
        super(DesktopTagSectionParser, self).__init__()
430
831
        self.tag_section = tag_section
431
832
        self.tagfile = tagfile
432
833
 
433
 
    def get_desktop(self, key, translated=True):
434
 
        # strip away bogus prefixes
435
 
        if key.startswith("X-AppInstall-"):
436
 
            key = key[len("X-AppInstall-"):]
 
834
    def get_value(self, key, translated=True):
 
835
        keys = self.MAPPING.get(key, key)
 
836
        if isinstance(keys, basestring):
 
837
            keys = (keys,)
 
838
 
 
839
        for key in keys:
 
840
            result = self._get_desktop(key, translated)
 
841
            if result:
 
842
                return result
 
843
 
 
844
    def _get_desktop(self, key, translated=True):
 
845
        untranslated_value = self._get_option_desktop(key)
437
846
        # shortcut
438
847
        if not translated:
439
 
            return self.tag_section[key]
440
 
        # FIXME: make i18n work similar to get_desktop
 
848
            return untranslated_value
 
849
 
441
850
        # first try dgettext
442
 
        if "Gettext-Domain" in self.tag_section:
443
 
            value = self.tag_section.get(key)
444
 
            if value:
445
 
                domain = self.tag_section["Gettext-Domain"]
446
 
                translated_value = gettext.dgettext(domain, value)
447
 
                if value != translated_value:
448
 
                    return translated_value
 
851
        domain = self._get_option_desktop('X-Ubuntu-Gettext-Domain')
 
852
        if domain and untranslated_value:
 
853
            translated_value = gettext.dgettext(domain, untranslated_value)
 
854
            if untranslated_value != translated_value:
 
855
                return translated_value
 
856
 
 
857
        # then try app-install-data
 
858
        if untranslated_value:
 
859
            translated_value = gettext.dgettext('app-install-data',
 
860
                                                untranslated_value)
 
861
            if untranslated_value != translated_value:
 
862
                return translated_value
 
863
 
449
864
        # then try the i18n version of the key (in [de_DE] or
450
865
        # [de]) but ignore errors and return the untranslated one then
451
866
        try:
452
 
            locale = getdefaultlocale(('LANGUAGE', 'LANG', 'LC_CTYPE',
453
 
                'LC_ALL'))[0]
 
867
            locale = get_default_locale()
454
868
            if locale:
455
 
                if self.has_option_desktop("%s-%s" % (key, locale)):
456
 
                    return self.tag_section["%s-%s" % (key, locale)]
457
 
                if "_" in locale:
 
869
                new_key = self.LOCALE_EXPR % (key, locale)
 
870
                result = self._get_option_desktop(new_key)
 
871
                if not result and "_" in locale:
458
872
                    locale_short = locale.split("_")[0]
459
 
                    if self.has_option_desktop("%s-%s" % (key, locale_short)):
460
 
                        return self.tag_section["%s-%s" % (key, locale_short)]
 
873
                    new_key = self.LOCALE_EXPR % (key, locale_short)
 
874
                    result = self._get_option_desktop(new_key)
 
875
                if result:
 
876
                    return result
461
877
        except ValueError:
462
878
            pass
 
879
 
463
880
        # and then the untranslated field
464
 
        return self.tag_section[key]
 
881
        return untranslated_value
465
882
 
466
 
    def has_option_desktop(self, key):
467
 
        # strip away bogus prefixes
468
 
        if key.startswith("X-AppInstall-"):
469
 
            key = key[len("X-AppInstall-"):]
470
 
        return key in self.tag_section
 
883
    def _get_option_desktop(self, key):
 
884
        if key in self.tag_section:
 
885
            return self.tag_section.get(key)
471
886
 
472
887
    @property
473
888
    def desktopf(self):
474
889
        return self.tagfile
475
890
 
476
891
 
477
 
class DesktopConfigParser(RawConfigParser, AppInfoParserBase):
478
 
    " thin wrapper that is tailored for xdg Desktop files "
 
892
class DesktopConfigParser(RawConfigParser, DesktopTagSectionParser):
 
893
    """Thin wrapper that is tailored for xdg Desktop files."""
 
894
 
479
895
    DE = "Desktop Entry"
 
896
    LOCALE_EXPR = '%s[%s]'
480
897
 
481
 
    def get_desktop(self, key, translated=True):
482
 
        " get generic option under 'Desktop Entry'"
 
898
    def _get_desktop(self, key, translated=True):
 
899
        """Get the generic option 'key' under 'Desktop Entry'."""
483
900
        # never translate the pkgname
484
 
        if key == "X-AppInstall-Package":
485
 
            return self.get(self.DE, key)
486
 
        # shortcut
487
 
        if not translated:
488
 
            return self.get(self.DE, key)
489
 
        # first try dgettext
490
 
        if self.has_option_desktop("X-Ubuntu-Gettext-Domain"):
491
 
            value = self.get(self.DE, key)
492
 
            if value:
493
 
                domain = self.get(self.DE, "X-Ubuntu-Gettext-Domain")
494
 
                translated_value = gettext.dgettext(domain, value)
495
 
                if value != translated_value:
496
 
                    return translated_value
497
 
        # then try app-install-data
498
 
        value = self.get(self.DE, key)
499
 
        if value:
500
 
            translated_value = gettext.dgettext("app-install-data", value)
501
 
            if value != translated_value:
502
 
                return translated_value
503
 
        # then try the i18n version of the key (in [de_DE] or
504
 
        # [de]) but ignore errors and return the untranslated one then
505
 
        try:
506
 
            locale = getdefaultlocale(('LANGUAGE', 'LANG', 'LC_CTYPE',
507
 
                'LC_ALL'))[0]
508
 
            if locale:
509
 
                if self.has_option_desktop("%s[%s]" % (key, locale)):
510
 
                    return self.get(self.DE, "%s[%s]" % (key, locale))
511
 
                if "_" in locale:
512
 
                    locale_short = locale.split("_")[0]
513
 
                    if self.has_option_desktop("%s[%s]" % (key, locale_short)):
514
 
                        return self.get(self.DE, "%s[%s]" %
515
 
                            (key, locale_short))
516
 
        except ValueError:
517
 
            pass
518
 
        # and then the untranslated field
519
 
        return self.get(self.DE, key)
520
 
 
521
 
    def has_option_desktop(self, key):
522
 
        " test if there is the option under 'Desktop Entry'"
523
 
        return self.has_option(self.DE, key)
 
901
        if key == self.MAPPING[AppInfoFields.PACKAGE]:
 
902
            return self._get_option_desktop(key)
 
903
 
 
904
        return super(DesktopConfigParser, self)._get_desktop(key, translated)
 
905
 
 
906
    def _get_option_desktop(self, key):
 
907
        if self.has_option(self.DE, key):
 
908
            return self.get(self.DE, key)
524
909
 
525
910
    def read(self, filename):
526
911
        self._filename = filename
539
924
    return key.translate(ascii_trans_table)
540
925
 
541
926
 
542
 
def index_name(doc, name, term_generator):
543
 
    """ index the name of the application """
544
 
    doc.add_value(XapianValues.APPNAME, name)
545
 
    doc.add_term("AA" + name)
546
 
    w = globals()["WEIGHT_DESKTOP_NAME"]
547
 
    term_generator.index_text_without_positions(name, w)
548
 
 
549
 
 
550
927
def update(db, cache, datadir=None):
551
928
    if not datadir:
552
929
        datadir = softwarecenter.paths.APP_INSTALL_DESKTOP_PATH
553
930
    update_from_app_install_data(db, cache, datadir)
554
931
    update_from_var_lib_apt_lists(db, cache)
555
932
    # add db global meta-data
556
 
    LOG.debug("adding popcon_max_desktop '%s'" % popcon_max)
 
933
    LOG.debug("adding popcon_max_desktop %r", popcon_max)
557
934
    db.set_metadata("popcon_max_desktop",
558
 
        xapian.sortable_serialise(float(popcon_max)))
 
935
                    xapian.sortable_serialise(float(popcon_max)))
559
936
 
560
937
 
561
938
def update_from_json_string(db, cache, json_string, origin):
562
 
    """ index from a json string, should include origin url (free form string)
563
 
    """
 
939
    """Index from json string, must include origin url (free form string)."""
564
940
    for sec in json.loads(json_string):
565
941
        parser = JsonTagSectionParser(sec, origin)
566
 
        index_app_info_from_parser(parser, db, cache)
 
942
        parser.index_app_info(db, cache)
567
943
    return True
568
944
 
569
945
 
577
953
        listsdir = apt_pkg.config.find_dir("Dir::State::lists")
578
954
    context = GObject.main_context_default()
579
955
    for appinfo in glob("%s/*AppInfo" % listsdir):
580
 
        LOG.debug("processing %s" % appinfo)
 
956
        LOG.debug("processing %r", appinfo)
581
957
        # process events
582
958
        while context.pending():
583
959
            context.iteration()
584
960
        tagf = apt_pkg.TagFile(open(appinfo))
585
961
        for section in tagf:
586
962
            parser = DesktopTagSectionParser(section, appinfo)
587
 
            index_app_info_from_parser(parser, db, cache)
 
963
            parser.index_app_info(db, cache)
588
964
    return True
589
965
 
590
966
 
594
970
    tree = etree.parse(open(filename))
595
971
    root = tree.getroot()
596
972
    if not root.tag == "applications":
597
 
        LOG.error("failed to read '%s' expected Applications root tag" %
598
 
            filename)
 
973
        LOG.error("failed to read %r expected Applications root tag",
 
974
                  filename)
599
975
        return
600
976
    for appinfo in root.iter("application"):
601
977
        parser = AppStreamXMLParser(appinfo, filename)
602
 
        index_app_info_from_parser(parser, db, cache)
 
978
        parser.index_app_info(db, cache)
603
979
 
604
980
 
605
981
def update_from_appstream_xml(db, cache, xmldir=None):
612
988
        return True
613
989
 
614
990
    for appstream_xml in glob(os.path.join(xmldir, "*.xml")):
615
 
        LOG.debug("processing %s" % appstream_xml)
 
991
        LOG.debug("processing %r", appstream_xml)
616
992
        # process events
617
993
        while context.pending():
618
994
            context.iteration()
626
1002
        datadir = softwarecenter.paths.APP_INSTALL_DESKTOP_PATH
627
1003
    context = GObject.main_context_default()
628
1004
    for desktopf in glob(datadir + "/*.desktop"):
629
 
        LOG.debug("processing %s" % desktopf)
 
1005
        LOG.debug("processing %r", desktopf)
630
1006
        # process events
631
1007
        while context.pending():
632
1008
            context.iteration()
633
1009
        try:
634
1010
            parser = DesktopConfigParser()
635
1011
            parser.read(desktopf)
636
 
            index_app_info_from_parser(parser, db, cache)
 
1012
            parser.index_app_info(db, cache)
637
1013
        except Exception as e:
638
1014
            # Print a warning, no error (Debian Bug #568941)
639
 
            LOG.debug("error processing: %s %s" % (desktopf, e))
 
1015
            LOG.debug("error processing: %r %r", desktopf, e)
640
1016
            warning_text = _(
641
1017
                "The file: '%s' could not be read correctly. The application "
642
1018
                "associated with this file will not be included in the "
643
1019
                "software catalog. Please consider raising a bug report "
644
 
                "for this issue with the maintainer of that "
645
 
                "application") % desktopf
646
 
            LOG.warning(warning_text)
 
1020
                "for this issue with the maintainer of that application")
 
1021
            LOG.warning(warning_text, desktopf)
647
1022
    return True
648
1023
 
649
1024
 
650
 
def add_from_purchased_but_needs_reinstall_data(
651
 
    purchased_but_may_need_reinstall_list, db, cache):
652
 
    """Add application that have been purchased but may require a reinstall
653
 
 
654
 
    This adds a inmemory database to the main db with the special
655
 
    PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME channel prefix
656
 
 
657
 
    :return: a xapian query to get all the apps that need reinstall
658
 
    """
659
 
    # magic
660
 
    db_purchased = xapian.inmemory_open()
661
 
    # go over the items we have
662
 
    for item in purchased_but_may_need_reinstall_list:
663
 
        # FIXME: what to do with duplicated entries? we will end
664
 
        #        up with two xapian.Document, one for the for-pay
665
 
        #        and one for the availalbe one from s-c-agent
666
 
        #try:
667
 
        #    db.get_xapian_document(item.name,
668
 
        #                           item.package_name)
669
 
        #except IndexError:
670
 
        #    # item is not in the xapian db
671
 
        #    pass
672
 
        #else:
673
 
        #    # ignore items we already have in the db, ignore
674
 
        #    continue
675
 
        # index the item
676
 
        try:
677
 
            parser = SCAPurchasedApplicationParser(item)
678
 
            index_app_info_from_parser(parser, db_purchased, cache)
679
 
        except Exception as e:
680
 
            LOG.exception("error processing: %s " % e)
681
 
    # add new in memory db to the main db
682
 
    db.add_database(db_purchased)
683
 
    # return a query
684
 
    query = xapian.Query("AH" + PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME)
685
 
    return query
686
 
 
687
 
 
688
1025
def update_from_software_center_agent(db, cache, ignore_cache=False,
689
1026
                                      include_sca_qa=False):
690
 
    """ update index based on the software-center-agent data """
 
1027
    """Update the index based on the software-center-agent data."""
 
1028
 
691
1029
    def _available_cb(sca, available):
692
 
        # print "available: ", available
693
 
        LOG.debug("available: '%s'" % available)
 
1030
        LOG.debug("update_from_software_center_agent: available: %r",
 
1031
                  available)
694
1032
        sca.available = available
695
1033
        sca.good_data = True
696
1034
        loop.quit()
697
1035
 
 
1036
    def _available_for_me_cb(sca, available_for_me):
 
1037
        LOG.debug("update_from_software_center_agent: available_for_me: %r",
 
1038
                  available_for_me)
 
1039
        sca.available_for_me = available_for_me
 
1040
        loop.quit()
 
1041
 
698
1042
    def _error_cb(sca, error):
699
 
        LOG.warn("error: %s" % error)
700
 
        sca.available = []
 
1043
        LOG.warn("update_from_software_center_agent: error: %r", error)
701
1044
        sca.good_data = False
702
1045
        loop.quit()
703
 
    # use the anonymous interface to s-c-agent, scales much better and is
704
 
    # much cache friendlier
705
 
    from softwarecenter.backend.scagent import SoftwareCenterAgent
706
 
    # FIXME: honor ignore_etag here somehow with the new piston based API
 
1046
 
 
1047
    context = GObject.main_context_default()
 
1048
    loop = GObject.MainLoop(context)
 
1049
 
707
1050
    sca = SoftwareCenterAgent(ignore_cache)
708
1051
    sca.connect("available", _available_cb)
 
1052
    sca.connect("available-for-me", _available_for_me_cb)
709
1053
    sca.connect("error", _error_cb)
710
 
    sca.available = None
 
1054
    sca.available = []
 
1055
    sca.available_for_me = []
 
1056
 
 
1057
    # query what is available for me first
 
1058
    available_for_me_pkgnames = set()
 
1059
    # this will ensure we do not trigger a login dialog
 
1060
    helper = UbuntuSSO()
 
1061
    token = helper.find_oauth_token_sync()
 
1062
    if token:
 
1063
        sca.query_available_for_me(no_relogin=True)
 
1064
        loop.run()
 
1065
        for item in sca.available_for_me:
 
1066
            try:
 
1067
                parser = SCAPurchasedApplicationParser(item)
 
1068
                parser.index_app_info(db, cache)
 
1069
                available_for_me_pkgnames.add(item.application["package_name"])
 
1070
            except:
 
1071
                LOG.exception("error processing: %r", item)
 
1072
 
 
1073
    # ... now query all that is available
711
1074
    if include_sca_qa:
712
1075
        sca.query_available_qa()
713
1076
    else:
714
1077
        sca.query_available()
 
1078
 
715
1079
    # create event loop and run it until data is available
716
1080
    # (the _available_cb and _error_cb will quit it)
717
 
    context = GObject.main_context_default()
718
 
    loop = GObject.MainLoop(context)
719
1081
    loop.run()
 
1082
 
720
1083
    # process data
721
1084
    for entry in sca.available:
 
1085
 
 
1086
        # do not add stuff here thats already purchased to avoid duplication
 
1087
        if entry.package_name in available_for_me_pkgnames:
 
1088
            continue
 
1089
 
722
1090
        # process events
723
1091
        while context.pending():
724
1092
            context.iteration()
725
1093
        try:
726
1094
            # now the normal parser
727
1095
            parser = SCAApplicationParser(entry)
728
 
            index_app_info_from_parser(parser, db, cache)
729
 
        except Exception as e:
730
 
            LOG.warning("error processing: %s " % e)
 
1096
            parser.index_app_info(db, cache)
 
1097
        except:
 
1098
            LOG.exception("update_from_software_center_agent: "
 
1099
                          "error processing %r:", entry.name)
 
1100
 
731
1101
    # return true if we have updated entries (this can also be an empty list)
732
1102
    # but only if we did not got a error from the agent
733
1103
    return sca.good_data
734
1104
 
735
1105
 
736
 
def make_doc_from_parser(parser, cache):
737
 
    # XXX 2012-01-19 michaeln I'm just pulling this code out from
738
 
    # index_app_info_from_parser, but it'd be great to further
739
 
    # refactor it - it looks quite scary :-)
740
 
    doc = xapian.Document()
741
 
    # app name is the data
742
 
    if parser.has_option_desktop("X-Ubuntu-Software-Center-Name"):
743
 
        name = parser.get_desktop("X-Ubuntu-Software-Center-Name")
744
 
        untranslated_name = parser.get_desktop("X-Ubuntu-Software-Center-Name",
745
 
            translated=False)
746
 
    elif parser.has_option_desktop("X-GNOME-FullName"):
747
 
        name = parser.get_desktop("X-GNOME-FullName")
748
 
        untranslated_name = parser.get_desktop("X-GNOME-FullName",
749
 
            translated=False)
750
 
    else:
751
 
        name = parser.get_desktop("Name")
752
 
        untranslated_name = parser.get_desktop("Name", translated=False)
753
 
 
754
 
    doc.set_data(name)
755
 
    doc.add_value(XapianValues.APPNAME_UNTRANSLATED, untranslated_name)
756
 
 
757
 
    # check if we should ignore this file
758
 
    if parser.has_option_desktop("X-AppInstall-Ignore"):
759
 
        ignore = parser.get_desktop("X-AppInstall-Ignore")
760
 
        if ignore.strip().lower() == "true":
761
 
            LOG.debug("X-AppInstall-Ignore found for '%s'" % parser.desktopf)
762
 
            return
763
 
    # architecture
764
 
    pkgname_extension = ''
765
 
    if parser.has_option_desktop("X-AppInstall-Architectures"):
766
 
        arches = parser.get_desktop("X-AppInstall-Architectures")
767
 
        doc.add_value(XapianValues.ARCHIVE_ARCH, arches)
768
 
        native_archs = get_current_arch() in arches.split(',')
769
 
        foreign_archs = list(set(arches.split(',')) &
770
 
            set(get_foreign_architectures()))
771
 
        if not (native_archs or foreign_archs):
772
 
            return
773
 
        if not native_archs and foreign_archs:
774
 
            pkgname_extension = ':' + foreign_archs[0]
775
 
    # package name
776
 
    pkgname = parser.get_desktop("X-AppInstall-Package") + pkgname_extension
777
 
    doc.add_term("AP" + pkgname)
778
 
    if '-' in pkgname:
779
 
        # we need this to work around xapian oddness
780
 
        doc.add_term(pkgname.replace('-', '_'))
781
 
    doc.add_value(XapianValues.PKGNAME, pkgname)
782
 
    doc.add_value(XapianValues.DESKTOP_FILE, parser.desktopf)
783
 
    # display name
784
 
    if "display_name" in axi_values:
785
 
        doc.add_value(axi_values["display_name"], name)
786
 
    # cataloged_times
787
 
    if "catalogedtime" in axi_values:
788
 
        if pkgname in cataloged_times:
789
 
            doc.add_value(axi_values["catalogedtime"],
790
 
                          xapian.sortable_serialise(cataloged_times[pkgname]))
791
 
    # pocket (main, restricted, ...)
792
 
    if parser.has_option_desktop("X-AppInstall-Section"):
793
 
        archive_section = parser.get_desktop("X-AppInstall-Section")
794
 
        doc.add_term("AS" + archive_section)
795
 
        doc.add_value(XapianValues.ARCHIVE_SECTION, archive_section)
796
 
    # section (mail, base, ..)
797
 
    if pkgname in cache and cache[pkgname].candidate:
798
 
        section = cache[pkgname].section
799
 
        doc.add_term("AE" + section)
800
 
    # channel (third party stuff)
801
 
    if parser.has_option_desktop("X-AppInstall-Channel"):
802
 
        archive_channel = parser.get_desktop("X-AppInstall-Channel")
803
 
        doc.add_term("AH" + archive_channel)
804
 
        doc.add_value(XapianValues.ARCHIVE_CHANNEL, archive_channel)
805
 
    # signing key (third party)
806
 
    if parser.has_option_desktop("X-AppInstall-Signing-Key-Id"):
807
 
        keyid = parser.get_desktop("X-AppInstall-Signing-Key-Id")
808
 
        doc.add_value(XapianValues.ARCHIVE_SIGNING_KEY_ID, keyid)
809
 
    # license (third party)
810
 
    if parser.has_option_desktop("X-AppInstall-License"):
811
 
        license = parser.get_desktop("X-AppInstall-License")
812
 
        doc.add_value(XapianValues.LICENSE, license)
813
 
    # date published
814
 
    if parser.has_option_desktop("X-AppInstall-Date-Published"):
815
 
        date_published = parser.get_desktop("X-AppInstall-Date-Published")
816
 
        if (date_published and
817
 
            re.match("\d+-\d+-\d+ \d+:\d+:\d+", date_published)):
818
 
            # strip the subseconds from the end of the published date string
819
 
            date_published = str(date_published).split(".")[0]
820
 
            doc.add_value(XapianValues.DATE_PUBLISHED,
821
 
                          date_published)
822
 
            # we use the date published value for the cataloged time as well
823
 
            if "catalogedtime" in axi_values:
824
 
                LOG.debug(
825
 
                        ("pkgname: %s, date_published cataloged time is: %s" %
826
 
                             (pkgname, parser.get_desktop("date_published"))))
827
 
                date_published_sec = time.mktime(
828
 
                                        time.strptime(date_published,
829
 
                                                      "%Y-%m-%d  %H:%M:%S"))
830
 
                doc.add_value(axi_values["catalogedtime"],
831
 
                              xapian.sortable_serialise(date_published_sec))
832
 
    # purchased date
833
 
    if parser.has_option_desktop("X-AppInstall-Purchased-Date"):
834
 
        date = parser.get_desktop("X-AppInstall-Purchased-Date")
835
 
        # strip the subseconds from the end of the date string
836
 
        doc.add_value(XapianValues.PURCHASED_DATE, str(date).split(".")[0])
837
 
    # deb-line (third party)
838
 
    if parser.has_option_desktop("X-AppInstall-Deb-Line"):
839
 
        debline = parser.get_desktop("X-AppInstall-Deb-Line")
840
 
        doc.add_value(XapianValues.ARCHIVE_DEB_LINE, debline)
841
 
    # license key (third party)
842
 
    if parser.has_option_desktop("X-AppInstall-License-Key"):
843
 
        key = parser.get_desktop("X-AppInstall-License-Key")
844
 
        doc.add_value(XapianValues.LICENSE_KEY, key)
845
 
    # license keypath (third party)
846
 
    if parser.has_option_desktop("X-AppInstall-License-Key-Path"):
847
 
        path = parser.get_desktop("X-AppInstall-License-Key-Path")
848
 
        doc.add_value(XapianValues.LICENSE_KEY_PATH, path)
849
 
    # PPA (third party stuff)
850
 
    if parser.has_option_desktop("X-AppInstall-PPA"):
851
 
        archive_ppa = parser.get_desktop("X-AppInstall-PPA")
852
 
        if archive_ppa:
853
 
            doc.add_value(XapianValues.ARCHIVE_PPA, archive_ppa)
854
 
            # add archive origin data here so that its available even if
855
 
            # the PPA is not (yet) enabled
856
 
            doc.add_term("XOO" + "lp-ppa-%s" % archive_ppa.replace("/", "-"))
857
 
    # screenshot (for third party)
858
 
    if parser.has_option_desktop("X-AppInstall-Screenshot-Url"):
859
 
        url = parser.get_desktop("X-AppInstall-Screenshot-Url")
860
 
        doc.add_value(XapianValues.SCREENSHOT_URLS, url)
861
 
    # thumbnail (for third party)
862
 
    if parser.has_option_desktop("X-AppInstall-Thumbnail-Url"):
863
 
        url = parser.get_desktop("X-AppInstall-Thumbnail-Url")
864
 
        doc.add_value(XapianValues.THUMBNAIL_URL, url)
865
 
    # video support (for third party mostly)
866
 
    if parser.has_option_desktop("X-AppInstall-Video-Url"):
867
 
        url = parser.get_desktop("X-AppInstall-Video-Url")
868
 
        doc.add_value(XapianValues.VIDEO_URL, url)
869
 
    # icon (for third party)
870
 
    if parser.has_option_desktop("X-AppInstall-Icon-Url"):
871
 
        url = parser.get_desktop("X-AppInstall-Icon-Url")
872
 
        doc.add_value(XapianValues.ICON_URL, url)
873
 
        if not parser.has_option_desktop("X-AppInstall-Icon"):
874
 
            # prefix pkgname to avoid name clashes
875
 
            doc.add_value(XapianValues.ICON, "%s-icon-%s" % (
876
 
                    pkgname, os.path.basename(url)))
877
 
 
878
 
    # price (pay stuff)
879
 
    if parser.has_option_desktop("X-AppInstall-Price"):
880
 
        price = parser.get_desktop("X-AppInstall-Price")
881
 
        doc.add_value(XapianValues.PRICE, price)
882
 
        # since this is a commercial app, indicate it in the component value
883
 
        doc.add_value(XapianValues.ARCHIVE_SECTION, "commercial")
884
 
    # support url (mainly pay stuff)
885
 
    if parser.has_option_desktop("X-AppInstall-Support-Url"):
886
 
        url = parser.get_desktop("X-AppInstall-Support-Url")
887
 
        doc.add_value(XapianValues.SUPPORT_SITE_URL, url)
888
 
    # icon
889
 
    if parser.has_option_desktop("Icon"):
890
 
        icon = parser.get_desktop("Icon")
891
 
        doc.add_value(XapianValues.ICON, icon)
892
 
    # write out categories
893
 
    for cat in parser.get_desktop_categories():
894
 
        doc.add_term("AC" + cat.lower())
895
 
    categories_string = ";".join(parser.get_desktop_categories())
896
 
    doc.add_value(XapianValues.CATEGORIES, categories_string)
897
 
    for mime in parser.get_desktop_mimetypes():
898
 
        doc.add_term("AM" + mime.lower())
899
 
    # get type (to distinguish between apps and packages
900
 
    if parser.has_option_desktop("Type"):
901
 
        type = parser.get_desktop("Type")
902
 
        doc.add_term("AT" + type.lower())
903
 
    # check gettext domain
904
 
    if parser.has_option_desktop("X-Ubuntu-Gettext-Domain"):
905
 
        domain = parser.get_desktop("X-Ubuntu-Gettext-Domain")
906
 
        doc.add_value(XapianValues.GETTEXT_DOMAIN, domain)
907
 
    # Description (software-center extension)
908
 
    if parser.has_option_desktop("X-AppInstall-Description"):
909
 
        descr = parser.get_desktop("X-AppInstall-Description")
910
 
        doc.add_value(XapianValues.SC_DESCRIPTION, descr)
911
 
    if parser.has_option_desktop("Supported-Distros"):
912
 
        doc.add_value(XapianValues.SC_SUPPORTED_DISTROS,
913
 
            json.dumps(parser.get_desktop("Supported-Distros")))
914
 
    # version support (for e.g. the scagent)
915
 
    if parser.has_option_desktop("X-AppInstall-Version"):
916
 
        ver = parser.get_desktop("X-AppInstall-Version")
917
 
        doc.add_value(XapianValues.VERSION_INFO, ver)
918
 
 
919
 
    # (deb)tags (in addition to the pkgname debtags
920
 
    if parser.has_option_desktop("X-AppInstall-Tags"):
921
 
        # register tags
922
 
        tags = parser.get_desktop("X-AppInstall-Tags")
923
 
        if tags:
924
 
            for tag in tags.split(","):
925
 
                doc.add_term("XT" + tag.strip())
926
 
        # ENFORCE region blacklist by not registering the app at all
927
 
        region = get_region_cached()
928
 
        if region:
929
 
            countrycode = region["countrycode"].lower()
930
 
            if "%s%s" % (REGION_BLACKLIST_TAG, countrycode) in tags:
931
 
                LOG.info("skipping region restricted app: '%s'" % name)
932
 
                return
933
 
 
934
 
    # popcon
935
 
    # FIXME: popularity not only based on popcon but also
936
 
    #        on archive section, third party app etc
937
 
    if parser.has_option_desktop("X-AppInstall-Popcon"):
938
 
        popcon = float(parser.get_desktop("X-AppInstall-Popcon"))
939
 
        # sort_by_value uses string compare, so we need to pad here
940
 
        doc.add_value(XapianValues.POPCON,
941
 
                      xapian.sortable_serialise(popcon))
942
 
        global popcon_max
943
 
        popcon_max = max(popcon_max, popcon)
944
 
 
945
 
    # comment goes into the summary data if there is one,
946
 
    # other wise we try GenericName and if nothing else,
947
 
    # the summary of the package
948
 
    if parser.has_option_desktop("Comment"):
949
 
        s = parser.get_desktop("Comment")
950
 
        doc.add_value(XapianValues.SUMMARY, s)
951
 
    elif parser.has_option_desktop("GenericName"):
952
 
        s = parser.get_desktop("GenericName")
953
 
        if s != name:
954
 
            doc.add_value(XapianValues.SUMMARY, s)
955
 
    elif pkgname in cache and cache[pkgname].candidate:
956
 
        s = cache[pkgname].candidate.summary
957
 
        doc.add_value(XapianValues.SUMMARY, s)
958
 
 
959
 
    return doc
960
 
 
961
 
 
962
 
def index_app_info_from_parser(parser, db, cache):
963
 
        term_generator = xapian.TermGenerator()
964
 
        term_generator.set_database(db)
965
 
        try:
966
 
            # this tests if we have spelling suggestions (there must be
967
 
            # a better way?!?) - this is needed as inmemory does not have
968
 
            # spelling corrections, but it allows setting the flag and will
969
 
            # raise a exception much later
970
 
            db.add_spelling("test")
971
 
            db.remove_spelling("test")
972
 
            # this enables the flag for it (we only reach this line if
973
 
            # the db supports spelling suggestions)
974
 
            term_generator.set_flags(xapian.TermGenerator.FLAG_SPELLING)
975
 
        except xapian.UnimplementedError:
976
 
            pass
977
 
        doc = make_doc_from_parser(parser, cache)
978
 
        if not doc:
979
 
            LOG.debug("make_doc_from_parser() returned '%s', ignoring" % doc)
980
 
            return
981
 
        term_generator.set_document(doc)
982
 
        name = doc.get_data()
983
 
 
984
 
        if name in seen:
985
 
            LOG.debug("duplicated name '%s' (%s)" % (name, parser.desktopf))
986
 
        LOG.debug("indexing app '%s'" % name)
987
 
        seen.add(name)
988
 
 
989
 
        index_name(doc, name, term_generator)
990
 
 
991
 
        pkgname = doc.get_value(XapianValues.PKGNAME)
992
 
        # add packagename as meta-data too
993
 
        term_generator.index_text_without_positions(pkgname,
994
 
            WEIGHT_APT_PKGNAME)
995
 
 
996
 
        # now add search data from the desktop file
997
 
        for key in ["GenericName", "Comment", "X-AppInstall-Description"]:
998
 
            if not parser.has_option_desktop(key):
999
 
                continue
1000
 
            s = parser.get_desktop(key)
1001
 
            # we need the ascii_upper here for e.g. turkish locales, see
1002
 
            # bug #581207
1003
 
            k = "WEIGHT_DESKTOP_" + ascii_upper(key.replace(" ", ""))
1004
 
            if k in globals():
1005
 
                w = globals()[k]
1006
 
            else:
1007
 
                LOG.debug("WEIGHT %s not found" % k)
1008
 
                w = 1
1009
 
            term_generator.index_text_without_positions(s, w)
1010
 
        # add data from the apt cache
1011
 
        if pkgname in cache and cache[pkgname].candidate:
1012
 
            s = cache[pkgname].candidate.summary
1013
 
            term_generator.index_text_without_positions(s,
1014
 
                WEIGHT_APT_SUMMARY)
1015
 
            s = cache[pkgname].candidate.description
1016
 
            term_generator.index_text_without_positions(s,
1017
 
                WEIGHT_APT_DESCRIPTION)
1018
 
            for origin in cache[pkgname].candidate.origins:
1019
 
                doc.add_term("XOA" + origin.archive)
1020
 
                doc.add_term("XOC" + origin.component)
1021
 
                doc.add_term("XOL" + origin.label)
1022
 
                doc.add_term("XOO" + origin.origin)
1023
 
                doc.add_term("XOS" + origin.site)
1024
 
 
1025
 
        # add our keywords (with high priority)
1026
 
        keywords = None
1027
 
        if parser.has_option_desktop("Keywords"):
1028
 
            keywords = parser.get_desktop("Keywords")
1029
 
        elif parser.has_option_desktop("X-AppInstall-Keywords"):
1030
 
            keywords = parser.get_desktop("X-AppInstall-Keywords")
1031
 
        if keywords:
1032
 
            for s in keywords.split(";"):
1033
 
                if s:
1034
 
                    term_generator.index_text_without_positions(s,
1035
 
                        WEIGHT_DESKTOP_KEYWORD)
1036
 
        # now add it
1037
 
        db.add_document(doc)
1038
 
 
1039
 
 
1040
1106
def rebuild_database(pathname, debian_sources=True, appstream_sources=False):
1041
1107
    #cache = apt.Cache(memonly=True)
1042
1108
    cache = get_pkg_info()
1048
1114
        try:
1049
1115
            os.makedirs(rebuild_path)
1050
1116
        except:
1051
 
            LOG.warn("Problem creating rebuild path '%s'." % rebuild_path)
 
1117
            LOG.warn("Problem creating rebuild path %r.", rebuild_path)
1052
1118
            LOG.warn("Please check you have the relevant permissions.")
1053
1119
            return False
1054
1120
 
1055
1121
    # check permission
1056
1122
    if not os.access(pathname, os.W_OK):
1057
 
        LOG.warn("Cannot write to '%s'." % pathname)
 
1123
        LOG.warn("Cannot write to %r.", pathname)
1058
1124
        LOG.warn("Please check you have the relevant permissions.")
1059
1125
        return False
1060
1126
 
1061
1127
    #check if old unrequired version of db still exists on filesystem
1062
1128
    if os.path.exists(old_path):
1063
 
        LOG.warn("Existing xapian old db was not previously cleaned: '%s'." %
1064
 
            old_path)
 
1129
        LOG.warn("Existing xapian old db was not previously cleaned: %r.",
 
1130
                 old_path)
1065
1131
        if os.access(old_path, os.W_OK):
1066
1132
            #remove old unrequired db before beginning
1067
1133
            shutil.rmtree(old_path)
1068
1134
        else:
1069
 
            LOG.warn("Cannot write to '%s'." % old_path)
 
1135
            LOG.warn("Cannot write to %r.", old_path)
1070
1136
            LOG.warn("Please check you have the relevant permissions.")
1071
1137
            return False
1072
1138
 
1099
1165
        shutil.rmtree(old_path)
1100
1166
        return True
1101
1167
    except:
1102
 
        LOG.warn("Cannot copy refreshed database to correct location: '%s'." %
1103
 
            pathname)
 
1168
        LOG.warn("Cannot copy refreshed database to correct location: %r.",
 
1169
                 pathname)
1104
1170
        return False