~sbeattie/+junk/ubuntu-apport

« back to all changes in this revision

Viewing changes to apport/crashdb.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-08-04 18:50:26 UTC
  • Revision ID: james.westby@ubuntu.com-20090804185026-yf6v4ua5qpbimlh1
Tags: 1.6-0ubuntu3
* Merge trunk:
  - apport-gtk: Fix ordering of choices
  - bin/package_hook: Fix crash for subdirectories in log dir. (LP: #332350)
  - doc/package-hooks.txt: Document allowed chars in report keys.
  - Show precise error message for damaged reports.
* ubuntu-bug: Call apport-kde instead of apport-qt.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
 
13
13
import os, os.path, datetime, sys
14
14
 
 
15
from exceptions import Exception
15
16
from packaging_impl import impl as packaging
16
17
 
17
18
class CrashDatabase:
23
24
        update(). For upload() and get_comment_url() you can use None.
24
25
        
25
26
        options is a dictionary with additional settings from crashdb.conf; see
26
 
        get_crashdb() for details'''
27
 
 
 
27
        get_crashdb() for details.
 
28
        '''
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.
35
36
 
36
37
        See apport.report.Report.search_bug_patterns() for details. If this
37
 
        function returns None, bug patterns are disabled.'''
38
 
 
 
38
        function returns None, bug patterns are disabled.
 
39
        '''
39
40
        return self.bugpattern_baseurl
40
41
 
41
42
    #
47
48
        '''Initialize duplicate database.
48
49
 
49
50
        path specifies an SQLite database. It will be created if it does not
50
 
        exist yet.'''
51
 
 
 
51
        exist yet.
 
52
        '''
52
53
        import sqlite3 as dbapi2
53
54
 
54
55
        assert dbapi2.paramstyle == 'qmark', \
73
74
 
74
75
        # verify integrity
75
76
        cur = self.duplicate_db.cursor()
76
 
        cur.execute('PRAGMA integrity_check');
 
77
        cur.execute('PRAGMA integrity_check')
77
78
        result = cur.fetchall() 
78
79
        if result != [('ok',)]:
79
80
            raise SystemError, 'Corrupt duplicate db:' + str(result)
92
93
        nothing and just returns None.
93
94
        
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
 
 
 
96
        it can be explicitly passed to this function if it is already available.
 
97
        '''
97
98
        assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
98
99
 
99
100
        if not report:
166
167
        '''Mark given crash ID as fixed in the duplicate database.
167
168
        
168
169
        version specifies the package version the crash was fixed in (None for
169
 
        'still unfixed').'''
170
 
 
 
170
        'still unfixed').
 
171
        '''
171
172
        assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
172
173
 
173
174
        cur = self.duplicate_db.cursor()
177
178
        self.duplicate_db.commit()
178
179
 
179
180
    def duplicate_db_remove(self, id):
180
 
        '''Remove crash from the duplicate database (because it got rejected or
181
 
        manually duplicated).'''
182
 
 
 
181
        '''Remove crash from the duplicate database.
 
182
        
 
183
        This happens when a report got rejected or manually duplicated.
 
184
        '''
183
185
        assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
184
186
 
185
187
        cur = self.duplicate_db.cursor()
187
189
        self.duplicate_db.commit()
188
190
 
189
191
    def duplicate_db_consolidate(self):
190
 
        '''Update the duplicate database status to the reality of the crash
191
 
        database.
 
192
        '''Update the duplicate db status to the reality of the crash db.
192
193
        
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
196
197
        database.
197
198
 
198
 
        This is a very expensive operation and should not be used too often.'''
199
 
 
 
199
        This is a very expensive operation and should not be used too often.
 
200
        '''
200
201
        assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
201
202
 
202
203
        unfixed = self.get_unfixed()
235
236
        
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
 
 
 
239
        consolidation will be returned as a string instead.
 
240
        '''
240
241
        assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
241
242
 
242
243
        cur = self.duplicate_db.cursor()
249
250
            return delta.days * 86400 + delta.seconds
250
251
 
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.
254
254
 
 
255
        This is True if last duplicate_db_consolidate() happened more than
 
256
        'interval' seconds ago (default: one day).
 
257
        '''
255
258
        return self.duplicate_db_last_consolidation() >= interval
256
259
 
257
260
    def duplicate_db_change_master_id(self, old_id, new_id):
265
268
        self.duplicate_db.commit()
266
269
 
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.
 
272
        
 
273
        Return [(id, fixed_version)] tuple list.
270
274
        
271
275
        There might be several matches if a crash has been reintroduced in a
272
 
        later version.'''
273
 
 
 
276
        later version.
 
277
        '''
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()
277
281
 
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).
281
 
 
282
 
           If with_timestamps is True, then the map will contain triples
283
 
           (crash_id, fixed_version, last_change) instead.
284
 
 
285
 
           This is mainly useful for debugging and test suites.'''
286
 
 
 
283
        '''Return the entire duplicate database as a dictionary.
 
284
        
 
285
        The returned dictionary maps "signature" to (crash_id, fixed_version)
 
286
        pairs.
 
287
 
 
288
        If with_timestamps is True, then the map will contain triples
 
289
        (crash_id, fixed_version, last_change) instead.
 
290
 
 
291
        This is mainly useful for debugging and test suites.
 
292
        '''
287
293
        assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
288
294
 
289
295
        dump = {}
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.
312
318
 
 
319
        This method can raise a NeedsCredentials exception in case of failure.
 
320
        '''
313
321
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
314
322
 
315
323
    def get_comment_url(self, report, handle):
318
326
 
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.'''
322
 
 
 
329
        interactive steps it wants to perform.
 
330
        '''
323
331
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
324
332
 
325
333
    def download(self, id):
328
336
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
329
337
 
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.
 
340
        
 
341
        This updates Stacktrace, ThreadStacktrace, StacktraceTop, and
 
342
        Disassembly. You can also supply an additional comment.
 
343
        '''
 
344
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
 
345
 
 
346
    def set_credentials(self, username, password):
 
347
        '''Set username and password.'''
334
348
 
335
349
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
336
350
 
337
351
    def get_distro_release(self, id):
338
 
        '''Get 'DistroRelease: <release>' from the given report ID and return
339
 
        it.'''
 
352
        '''Get 'DistroRelease: <release>' from the report ID.'''
340
353
 
341
354
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
342
355
 
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.'''
346
 
 
 
357
        '''Return set of crash IDs which have not been retraced yet.
 
358
        
 
359
        This should only include crashes which match the current host
 
360
        architecture.
 
361
        '''
347
362
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
348
363
 
349
364
    def get_dup_unchecked(self):
350
 
        '''Return an ID set of all crashes which have not been checked for
351
 
        being a duplicate.
 
365
        '''Return set of crash IDs which need duplicate checking.
352
366
 
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().'''
356
 
 
 
369
        bugs that are covered by get_unretraced().
 
370
        '''
357
371
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
358
372
 
359
373
    def get_unfixed(self):
363
377
        
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).'''
367
 
 
 
380
        raise an exception (preferably IOError).
 
381
        '''
368
382
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
369
383
 
370
384
    def get_fixed_version(self, id):
377
391
 
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).'''
381
 
 
 
394
        raise an exception (preferably IOError).
 
395
        '''
382
396
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
383
397
 
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.
414
428
        
415
 
        This can be a no-op if you are not interested in this.'''
416
 
 
 
429
        This can be a no-op if you are not interested in this.
 
430
        '''
417
431
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
418
432
 
419
433
    def _mark_dup_checked(self, id, report):
420
434
        '''Mark crash id as checked for being a duplicate
421
435
        
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.
 
437
        '''
 
438
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
423
439
 
424
 
        raise NotImplementedError, 'this method must be implemented by a concrete subclass'
425
440
#
426
441
# factory 
427
442
#
428
443
 
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.
 
446
    
 
447
    This reads the configuration file 'conf'.
432
448
    
433
449
    If name is None, it defaults to the 'default' value in conf.
434
450
 
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
447
 
      crash db).'''
448
 
 
 
463
      crash db).
 
464
    '''
449
465
    if not conf:
450
466
        conf = os.environ.get('APPORT_CRASHDB_CONF', '/etc/apport/crashdb.conf')
451
467
    settings = {}
472
488
    m = __import__('apport.crashdb_impl.' + db['impl'], globals(), locals(), ['CrashDatabase'])
473
489
    return m.CrashDatabase(auth_file, db['bug_pattern_base'], db)
474
490
 
 
491
class NeedsCredentials(Exception):
 
492
    '''This may be raised when unable to log in to the crashdb.'''
 
493
    pass