23
24
update(). For upload() and get_comment_url() you can use None.
25
26
options is a dictionary with additional settings from crashdb.conf; see
26
get_crashdb() for details'''
27
get_crashdb() for details.
28
29
self.auth_file = auth_file
29
30
self.options = options
30
31
self.bugpattern_baseurl = bugpattern_baseurl
34
35
'''Return the base URL for bug patterns.
36
37
See apport.report.Report.search_bug_patterns() for details. If this
37
function returns None, bug patterns are disabled.'''
38
function returns None, bug patterns are disabled.
39
40
return self.bugpattern_baseurl
92
93
nothing and just returns None.
94
95
By default, the report gets download()ed, but for performance reasons
95
it can be explicitly passed to this function if it is already available.'''
96
it can be explicitly passed to this function if it is already available.
97
98
assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
166
167
'''Mark given crash ID as fixed in the duplicate database.
168
169
version specifies the package version the crash was fixed in (None for
171
172
assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
173
174
cur = self.duplicate_db.cursor()
177
178
self.duplicate_db.commit()
179
180
def duplicate_db_remove(self, id):
180
'''Remove crash from the duplicate database (because it got rejected or
181
manually duplicated).'''
181
'''Remove crash from the duplicate database.
183
This happens when a report got rejected or manually duplicated.
183
185
assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
185
187
cur = self.duplicate_db.cursor()
187
189
self.duplicate_db.commit()
189
191
def duplicate_db_consolidate(self):
190
'''Update the duplicate database status to the reality of the crash
192
'''Update the duplicate db status to the reality of the crash db.
193
194
This uses get_unfixed() and get_fixed_version() to get the status of
194
195
particular crashes. Invalid IDs get removed from the duplicate db, and
195
196
crashes which got fixed since the last run are marked as such in the
198
This is a very expensive operation and should not be used too often.'''
199
This is a very expensive operation and should not be used too often.
200
201
assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
202
203
unfixed = self.get_unfixed()
236
237
By default, this returns the number of seconds since the last
237
238
consolidation. If absolute is True, the date and time of last
238
consolidation will be returned as a string instead.'''
239
consolidation will be returned as a string instead.
240
241
assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
242
243
cur = self.duplicate_db.cursor()
249
250
return delta.days * 86400 + delta.seconds
251
252
def duplicate_db_needs_consolidation(self, interval=86400):
252
'''Check whether the last duplicate_db_consolidate() happened more than
253
'interval' seconds ago (default: one day).'''
253
'''Check whether DB needs consolidation.
255
This is True if last duplicate_db_consolidate() happened more than
256
'interval' seconds ago (default: one day).
255
258
return self.duplicate_db_last_consolidation() >= interval
257
260
def duplicate_db_change_master_id(self, old_id, new_id):
265
268
self.duplicate_db.commit()
267
270
def _duplicate_search_signature(self, sig):
268
'''Look up signature in the duplicate db and return an [(id,
269
fixed_version)] tuple list.
271
'''Look up signature in the duplicate db.
273
Return [(id, fixed_version)] tuple list.
271
275
There might be several matches if a crash has been reintroduced in a
274
278
cur = self.duplicate_db.cursor()
275
279
cur.execute('SELECT crash_id, fixed_version FROM crashes WHERE signature = ?', [sig])
276
280
return cur.fetchall()
278
282
def _duplicate_db_dump(self, with_timestamps=False):
279
'''Return the entire duplicate database as a dictionary signature ->
280
(crash_id, fixed_version).
282
If with_timestamps is True, then the map will contain triples
283
(crash_id, fixed_version, last_change) instead.
285
This is mainly useful for debugging and test suites.'''
283
'''Return the entire duplicate database as a dictionary.
285
The returned dictionary maps "signature" to (crash_id, fixed_version)
288
If with_timestamps is True, then the map will contain triples
289
(crash_id, fixed_version, last_change) instead.
291
This is mainly useful for debugging and test suites.
287
293
assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
308
314
If the implementation supports it, and a function progress_callback is
309
315
passed, that is called repeatedly with two arguments: the number of
310
316
bytes already sent, and the total number of bytes to send. This can be
311
used to provide a proper upload progress indication on frontends.'''
317
used to provide a proper upload progress indication on frontends.
319
This method can raise a NeedsCredentials exception in case of failure.
313
321
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
315
323
def get_comment_url(self, report, handle):
319
327
Should return None if no URL should be opened (anonymous filing without
320
328
user comments); in that case this function should do whichever
321
interactive steps it wants to perform.'''
329
interactive steps it wants to perform.
323
331
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
325
333
def download(self, id):
328
336
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
330
338
def update(self, id, report, comment):
331
'''Update the given report ID with the retraced results from the report
332
(Stacktrace, ThreadStacktrace, StacktraceTop; also Disassembly if
333
desired) and an optional comment.'''
339
'''Update the given report ID for retracing results.
341
This updates Stacktrace, ThreadStacktrace, StacktraceTop, and
342
Disassembly. You can also supply an additional comment.
344
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
346
def set_credentials(self, username, password):
347
'''Set username and password.'''
335
349
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
337
351
def get_distro_release(self, id):
338
'''Get 'DistroRelease: <release>' from the given report ID and return
352
'''Get 'DistroRelease: <release>' from the report ID.'''
341
354
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
343
356
def get_unretraced(self):
344
'''Return an ID set of all crashes which have not been retraced yet and
345
which happened on the current host architecture.'''
357
'''Return set of crash IDs which have not been retraced yet.
359
This should only include crashes which match the current host
347
362
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
349
364
def get_dup_unchecked(self):
350
'''Return an ID set of all crashes which have not been checked for
365
'''Return set of crash IDs which need duplicate checking.
353
367
This is mainly useful for crashes of scripting languages such as
354
368
Python, since they do not need to be retraced. It should not return
355
bugs that are covered by get_unretraced().'''
369
bugs that are covered by get_unretraced().
357
371
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
359
373
def get_unfixed(self):
364
378
This function should make sure that the returned list is correct. If
365
379
there are any errors with connecting to the crash database, it should
366
raise an exception (preferably IOError).'''
380
raise an exception (preferably IOError).
368
382
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
370
384
def get_fixed_version(self, id):
378
392
This function should make sure that the returned result is correct. If
379
393
there are any errors with connecting to the crash database, it should
380
raise an exception (preferably IOError).'''
394
raise an exception (preferably IOError).
382
396
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
384
398
def duplicate_of(self, id):
412
426
If invalid_msg is given, the bug should be closed as invalid with given
413
427
message, otherwise just marked as a failed retrace.
415
This can be a no-op if you are not interested in this.'''
429
This can be a no-op if you are not interested in this.
417
431
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
419
433
def _mark_dup_checked(self, id, report):
420
434
'''Mark crash id as checked for being a duplicate
422
This is an internal method that should not be called from outside.'''
436
This is an internal method that should not be called from outside.
438
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
424
raise NotImplementedError, 'this method must be implemented by a concrete subclass'
429
444
def get_crashdb(auth_file, name = None, conf = None):
430
'''Return a CrashDatabase object for the given crash db name, as specified
431
in the configuration file 'conf'.
445
'''Return a CrashDatabase object for the given crash db name.
447
This reads the configuration file 'conf'.
433
449
If name is None, it defaults to the 'default' value in conf.
444
460
in apport.crashdb_impl which contains a concrete 'CrashDatabase' class
445
461
implementation for that crash db type) and 'bug_pattern_base', which
446
462
specifies an URL for bug patterns (or None if those are not used for that
450
466
conf = os.environ.get('APPORT_CRASHDB_CONF', '/etc/apport/crashdb.conf')
472
488
m = __import__('apport.crashdb_impl.' + db['impl'], globals(), locals(), ['CrashDatabase'])
473
489
return m.CrashDatabase(auth_file, db['bug_pattern_base'], db)
491
class NeedsCredentials(Exception):
492
'''This may be raised when unable to log in to the crashdb.'''