home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / python2.6 / dist-packages / apport / crashdb.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-10-12  |  19.4 KB  |  455 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. '''Abstract crash database interface.
  5.  
  6. Copyright (C) 2007 Canonical Ltd.
  7. Author: Martin Pitt <martin.pitt@ubuntu.com>
  8.  
  9. This program is free software; you can redistribute it and/or modify it
  10. under the terms of the GNU General Public License as published by the
  11. Free Software Foundation; either version 2 of the License, or (at your
  12. option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
  13. the full text of the license.
  14. '''
  15. import os
  16. import os.path as os
  17. import datetime
  18. import sys
  19. from packaging_impl import impl as packaging
  20.  
  21. class CrashDatabase:
  22.     
  23.     def __init__(self, auth_file, bugpattern_baseurl, options):
  24.         '''Initialize crash database connection. 
  25.         
  26.         You need to specify an implementation specific file with the
  27.         authentication credentials for retracing access for download() and
  28.         update(). For upload() and get_comment_url() you can use None.
  29.         
  30.         options is a dictionary with additional settings from crashdb.conf; see
  31.         get_crashdb() for details'''
  32.         self.auth_file = auth_file
  33.         self.options = options
  34.         self.bugpattern_baseurl = bugpattern_baseurl
  35.         self.duplicate_db = None
  36.  
  37.     
  38.     def get_bugpattern_baseurl(self):
  39.         '''Return the base URL for bug patterns.
  40.  
  41.         See apport.report.Report.search_bug_patterns() for details. If this
  42.         function returns None, bug patterns are disabled.'''
  43.         return self.bugpattern_baseurl
  44.  
  45.     
  46.     def init_duplicate_db(self, path):
  47.         '''Initialize duplicate database.
  48.  
  49.         path specifies an SQLite database. It will be created if it does not
  50.         exist yet.'''
  51.         import sqlite3 as dbapi2
  52.         if not dbapi2.paramstyle == 'qmark':
  53.             raise AssertionError, 'this module assumes qmark dbapi parameter style'
  54.         if not not os.path.exists(path) and path == ':memory:':
  55.             pass
  56.         init = os.path.getsize(path) == 0
  57.         self.duplicate_db = dbapi2.connect(path, timeout = 7200)
  58.         if init:
  59.             cur = self.duplicate_db.cursor()
  60.             cur.execute('CREATE TABLE crashes (\n                signature VARCHAR(255) NOT NULL,\n                crash_id INTEGER NOT NULL,\n                fixed_version VARCHAR(50),\n                last_change TIMESTAMP)')
  61.             cur.execute('CREATE TABLE consolidation (\n                last_update TIMESTAMP)')
  62.             cur.execute('INSERT INTO consolidation VALUES (CURRENT_TIMESTAMP)')
  63.             self.duplicate_db.commit()
  64.         
  65.         cur = self.duplicate_db.cursor()
  66.         cur.execute('PRAGMA integrity_check')
  67.         result = cur.fetchall()
  68.         if result != [
  69.             ('ok',)]:
  70.             raise SystemError, 'Corrupt duplicate db:' + str(result)
  71.         result != [
  72.             ('ok',)]
  73.  
  74.     
  75.     def check_duplicate(self, id, report = None):
  76.         '''Check whether a crash is already known.
  77.  
  78.         If the crash is new, it will be added to the duplicate database and the
  79.         function returns None. If the crash is already known, the function
  80.         returns a pair (crash_id, fixed_version), where fixed_version might be
  81.         None if the crash is not fixed in the latest version yet. Depending on
  82.         whether the version in report is smaller than/equal to the fixed
  83.         version or larger, this calls close_duplicate() or mark_regression().
  84.         
  85.         If the report does not have a valid crash signature, this function does
  86.         nothing and just returns None.
  87.         
  88.         By default, the report gets download()ed, but for performance reasons
  89.         it can be explicitly passed to this function if it is already available.'''
  90.         if not self.duplicate_db:
  91.             raise AssertionError, 'init_duplicate_db() needs to be called before'
  92.         if not report:
  93.             report = self.download(id)
  94.         
  95.         self._mark_dup_checked(id, report)
  96.         sig = report.crash_signature()
  97.         if not sig:
  98.             return None
  99.         existing = self._duplicate_search_signature(sig)
  100.         
  101.         def cmp(x, y):
  102.             if x == y:
  103.                 return 0
  104.             if x == '':
  105.                 if y == None:
  106.                     return -1
  107.                 return 1
  108.             x == ''
  109.             if y == '':
  110.                 if x == None:
  111.                     return 1
  112.                 return -1
  113.             y == ''
  114.             if x == None:
  115.                 return 1
  116.             if y == None:
  117.                 return -1
  118.             return packaging.compare_versions(x, y)
  119.  
  120.         existing.sort(cmp, (lambda k: k[1]))
  121.         if not existing:
  122.             cur = self.duplicate_db.cursor()
  123.             cur.execute('INSERT INTO crashes VALUES (?, ?, ?, CURRENT_TIMESTAMP)', (sig, id, None))
  124.             self.duplicate_db.commit()
  125.             return None
  126.         
  127.         try:
  128.             report_package_version = report['Package'].split()[1]
  129.         except (KeyError, IndexError):
  130.             existing
  131.             existing
  132.             sig
  133.             report_package_version = None
  134.         except:
  135.             existing
  136.  
  137.         for ex_id, ex_ver in existing:
  138.             if not ex_ver and not report_package_version or packaging.compare_versions(report_package_version, ex_ver) < 0:
  139.                 self.close_duplicate(id, ex_id)
  140.                 break
  141.                 continue
  142.             existing
  143.         else:
  144.             cur = self.duplicate_db.cursor()
  145.             cur.execute('INSERT INTO crashes VALUES (?, ?, ?, CURRENT_TIMESTAMP)', (sig, id, None))
  146.             self.duplicate_db.commit()
  147.         return (ex_id, ex_ver)
  148.  
  149.     
  150.     def duplicate_db_fixed(self, id, version):
  151.         """Mark given crash ID as fixed in the duplicate database.
  152.         
  153.         version specifies the package version the crash was fixed in (None for
  154.         'still unfixed')."""
  155.         if not self.duplicate_db:
  156.             raise AssertionError, 'init_duplicate_db() needs to be called before'
  157.         cur = self.duplicate_db.cursor()
  158.         n = cur.execute('UPDATE crashes SET fixed_version = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?', (version, id))
  159.         if not n.rowcount == 1:
  160.             raise AssertionError
  161.         self.duplicate_db.commit()
  162.  
  163.     
  164.     def duplicate_db_remove(self, id):
  165.         '''Remove crash from the duplicate database (because it got rejected or
  166.         manually duplicated).'''
  167.         if not self.duplicate_db:
  168.             raise AssertionError, 'init_duplicate_db() needs to be called before'
  169.         cur = self.duplicate_db.cursor()
  170.         cur.execute('DELETE FROM crashes WHERE crash_id = ?', [
  171.             id])
  172.         self.duplicate_db.commit()
  173.  
  174.     
  175.     def duplicate_db_consolidate(self):
  176.         '''Update the duplicate database status to the reality of the crash
  177.         database.
  178.         
  179.         This uses get_unfixed() and get_fixed_version() to get the status of
  180.         particular crashes. Invalid IDs get removed from the duplicate db, and
  181.         crashes which got fixed since the last run are marked as such in the
  182.         database.
  183.  
  184.         This is a very expensive operation and should not be used too often.'''
  185.         if not self.duplicate_db:
  186.             raise AssertionError, 'init_duplicate_db() needs to be called before'
  187.         unfixed = self.get_unfixed()
  188.         cur = self.duplicate_db.cursor()
  189.         cur.execute('SELECT crash_id, fixed_version FROM crashes')
  190.         cur2 = self.duplicate_db.cursor()
  191.         for id, ver in cur:
  192.             if ver != None:
  193.                 continue
  194.             
  195.             fixed_ver = self.get_fixed_version(id)
  196.             if fixed_ver == 'invalid':
  197.                 print 'DEBUG: bug %i was invalidated, removing from database' % id
  198.                 cur2.execute('DELETE FROM crashes WHERE crash_id = ?', [
  199.                     id])
  200.                 continue
  201.             if not fixed_ver:
  202.                 print 'WARNING: inconsistency detected: bug #%i does not appear in get_unfixed(), but is not fixed yet' % id
  203.                 continue
  204.             cur2.execute('UPDATE crashes SET fixed_version = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?', (fixed_ver, id))
  205.         
  206.         cur.execute('UPDATE consolidation SET last_update = CURRENT_TIMESTAMP')
  207.         self.duplicate_db.commit()
  208.  
  209.     
  210.     def duplicate_db_last_consolidation(self, absolute = False):
  211.         '''Return the date and time of last consolidation.
  212.         
  213.         By default, this returns the number of seconds since the last
  214.         consolidation. If absolute is True, the date and time of last
  215.         consolidation will be returned as a string instead.'''
  216.         if not self.duplicate_db:
  217.             raise AssertionError, 'init_duplicate_db() needs to be called before'
  218.         cur = self.duplicate_db.cursor()
  219.         cur.execute('SELECT last_update FROM consolidation')
  220.         if absolute:
  221.             return cur.fetchone()[0]
  222.         last_run = datetime.datetime.strptime(cur.fetchone()[0], '%Y-%m-%d %H:%M:%S')
  223.         delta = datetime.datetime.utcnow() - last_run
  224.         return delta.days * 86400 + delta.seconds
  225.  
  226.     
  227.     def duplicate_db_needs_consolidation(self, interval = 86400):
  228.         """Check whether the last duplicate_db_consolidate() happened more than
  229.         'interval' seconds ago (default: one day)."""
  230.         return self.duplicate_db_last_consolidation() >= interval
  231.  
  232.     
  233.     def duplicate_db_change_master_id(self, old_id, new_id):
  234.         '''Change a crash ID.'''
  235.         if not self.duplicate_db:
  236.             raise AssertionError, 'init_duplicate_db() needs to be called before'
  237.         cur = self.duplicate_db.cursor()
  238.         cur.execute('UPDATE crashes SET crash_id = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?', [
  239.             new_id,
  240.             old_id])
  241.         self.duplicate_db.commit()
  242.  
  243.     
  244.     def _duplicate_search_signature(self, sig):
  245.         '''Look up signature in the duplicate db and return an [(id,
  246.         fixed_version)] tuple list.
  247.         
  248.         There might be several matches if a crash has been reintroduced in a
  249.         later version.'''
  250.         cur = self.duplicate_db.cursor()
  251.         cur.execute('SELECT crash_id, fixed_version FROM crashes WHERE signature = ?', [
  252.             sig])
  253.         return cur.fetchall()
  254.  
  255.     
  256.     def _duplicate_db_dump(self, with_timestamps = False):
  257.         '''Return the entire duplicate database as a dictionary signature ->
  258.            (crash_id, fixed_version).
  259.  
  260.            If with_timestamps is True, then the map will contain triples
  261.            (crash_id, fixed_version, last_change) instead.
  262.  
  263.            This is mainly useful for debugging and test suites.'''
  264.         if not self.duplicate_db:
  265.             raise AssertionError, 'init_duplicate_db() needs to be called before'
  266.         dump = { }
  267.         cur = self.duplicate_db.cursor()
  268.         cur.execute('SELECT * FROM crashes')
  269.         for sig, id, ver, last_change in cur:
  270.             if with_timestamps:
  271.                 dump[sig] = (id, ver, last_change)
  272.                 continue
  273.             self.duplicate_db
  274.             dump[sig] = (id, ver)
  275.         
  276.         return dump
  277.  
  278.     
  279.     def upload(self, report, progress_callback = None):
  280.         '''Upload given problem report return a handle for it. 
  281.         
  282.         This should happen noninteractively. 
  283.         
  284.         If the implementation supports it, and a function progress_callback is
  285.         passed, that is called repeatedly with two arguments: the number of
  286.         bytes already sent, and the total number of bytes to send. This can be
  287.         used to provide a proper upload progress indication on frontends.'''
  288.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  289.  
  290.     
  291.     def get_comment_url(self, report, handle):
  292.         '''Return an URL that should be opened after report has been uploaded
  293.         and upload() returned handle.
  294.  
  295.         Should return None if no URL should be opened (anonymous filing without
  296.         user comments); in that case this function should do whichever
  297.         interactive steps it wants to perform.'''
  298.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  299.  
  300.     
  301.     def download(self, id):
  302.         '''Download the problem report from given ID and return a Report.'''
  303.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  304.  
  305.     
  306.     def update(self, id, report, comment):
  307.         '''Update the given report ID with the retraced results from the report
  308.         (Stacktrace, ThreadStacktrace, StacktraceTop; also Disassembly if
  309.         desired) and an optional comment.'''
  310.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  311.  
  312.     
  313.     def get_distro_release(self, id):
  314.         """Get 'DistroRelease: <release>' from the given report ID and return
  315.         it."""
  316.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  317.  
  318.     
  319.     def get_unretraced(self):
  320.         '''Return an ID set of all crashes which have not been retraced yet and
  321.         which happened on the current host architecture.'''
  322.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  323.  
  324.     
  325.     def get_dup_unchecked(self):
  326.         '''Return an ID set of all crashes which have not been checked for
  327.         being a duplicate.
  328.  
  329.         This is mainly useful for crashes of scripting languages such as
  330.         Python, since they do not need to be retraced. It should not return
  331.         bugs that are covered by get_unretraced().'''
  332.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  333.  
  334.     
  335.     def get_unfixed(self):
  336.         '''Return an ID set of all crashes which are not yet fixed.
  337.  
  338.         The list must not contain bugs which were rejected or duplicate.
  339.         
  340.         This function should make sure that the returned list is correct. If
  341.         there are any errors with connecting to the crash database, it should
  342.         raise an exception (preferably IOError).'''
  343.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  344.  
  345.     
  346.     def get_fixed_version(self, id):
  347.         """Return the package version that fixes a given crash.
  348.  
  349.         Return None if the crash is not yet fixed, or an empty string if the
  350.         crash is fixed, but it cannot be determined by which version. Return
  351.         'invalid' if the crash report got invalidated, such as closed a
  352.         duplicate or rejected.
  353.  
  354.         This function should make sure that the returned result is correct. If
  355.         there are any errors with connecting to the crash database, it should
  356.         raise an exception (preferably IOError)."""
  357.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  358.  
  359.     
  360.     def duplicate_of(self, id):
  361.         '''Return master ID for a duplicate bug.
  362.  
  363.         If the bug is not a duplicate, return None.
  364.         '''
  365.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  366.  
  367.     
  368.     def close_duplicate(self, id, master):
  369.         '''Mark a crash id as duplicate of given master ID.
  370.         
  371.         If master is None, id gets un-duplicated.
  372.         '''
  373.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  374.  
  375.     
  376.     def mark_regression(self, id, master):
  377.         """Mark a crash id as reintroducing an earlier crash which is
  378.         already marked as fixed (having ID 'master')."""
  379.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  380.  
  381.     
  382.     def mark_retraced(self, id):
  383.         '''Mark crash id as retraced.'''
  384.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  385.  
  386.     
  387.     def mark_retrace_failed(self, id, invalid_msg = None):
  388.         """Mark crash id as 'failed to retrace'.
  389.  
  390.         If invalid_msg is given, the bug should be closed as invalid with given
  391.         message, otherwise just marked as a failed retrace.
  392.         
  393.         This can be a no-op if you are not interested in this."""
  394.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  395.  
  396.     
  397.     def _mark_dup_checked(self, id, report):
  398.         '''Mark crash id as checked for being a duplicate
  399.         
  400.         This is an internal method that should not be called from outside.'''
  401.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  402.  
  403.  
  404.  
  405. def get_crashdb(auth_file, name = None, conf = None):
  406.     """Return a CrashDatabase object for the given crash db name, as specified
  407.     in the configuration file 'conf'.
  408.     
  409.     If name is None, it defaults to the 'default' value in conf.
  410.  
  411.     If conf is None, it defaults to the environment variable
  412.     APPORT_CRASHDB_CONF; if that does not exist, the hardcoded default is
  413.     /etc/apport/crashdb.conf. This Python syntax file needs to specify:
  414.  
  415.     - A string variable 'default', giving a default value for 'name' if that is
  416.       None.
  417.  
  418.     - A dictionary 'databases' which maps names to crash db configuration
  419.       dictionaries. These need to have at least the keys 'impl' (Python module
  420.       in apport.crashdb_impl which contains a concrete 'CrashDatabase' class
  421.       implementation for that crash db type) and 'bug_pattern_base', which
  422.       specifies an URL for bug patterns (or None if those are not used for that
  423.       crash db)."""
  424.     if not conf:
  425.         conf = os.environ.get('APPORT_CRASHDB_CONF', '/etc/apport/crashdb.conf')
  426.     
  427.     settings = { }
  428.     execfile(conf, settings)
  429.     confdDir = conf + '.d'
  430.     if os.path.isdir(confdDir):
  431.         for cf in os.listdir(confdDir):
  432.             cfpath = os.path.join(confdDir, cf)
  433.             if os.path.isfile(cfpath) and cf.endswith('.conf'):
  434.                 
  435.                 try:
  436.                     execfile(cfpath, settings['databases'])
  437.                 except Exception:
  438.                     e = None
  439.                     print >>sys.stderr, 'Invalid file %s: %s' % (cfpath, str(e))
  440.                 except:
  441.                     None<EXCEPTION MATCH>Exception
  442.                 
  443.  
  444.             None<EXCEPTION MATCH>Exception
  445.         
  446.     
  447.     if not name:
  448.         name = settings['default']
  449.     
  450.     db = settings['databases'][name]
  451.     m = __import__('apport.crashdb_impl.' + db['impl'], globals(), locals(), [
  452.         'CrashDatabase'])
  453.     return m.CrashDatabase(auth_file, db['bug_pattern_base'], db)
  454.  
  455.