+ def _iter_passwds(me):
+ k = me._db.firstkey()
+ while k is not None:
+ if k.startswith('$'): yield k[1:], me._db[k]
+ k = me._db.nextkey(k)
+
+try: import sqlite3 as _Q
+except ImportError: pass
+else:
+ class SQLiteStorageBackend (StorageBackend):
+ """
+ I represent a password database stored in SQLite.
+
+ Metadata and password items are stored in separate tables, so there's no
+ conflict. Some additional metadata is stored in the `meta' table, with
+ names beginning with `$' so as not to conflict with clients:
+
+ $version The schema version of the table.
+ """
+
+ NAME = 'sqlite'
+ VERSION = 0
+
+ def _open(me, file, writep):
+ try:
+ me._db = _Q.connect(file)
+ ver = me._query_scalar(
+ "SELECT value FROM meta WHERE name = '$version'",
+ "version check")
+ except (_Q.DatabaseError, _Q.OperationalError), e:
+ raise StorageBackendRefusal, e
+ if ver is None: raise ValueError, 'database broken (missing $version)'
+ elif ver < me.VERSION: me._upgrade(ver)
+ elif ver > me.VERSION:
+ raise ValueError, 'unknown database schema version (%d > %d)' % \
+ (ver, me.VERSION)
+
+ def _create(me, file):
+ fd = _OS.open(file, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_EXCL, 0600)
+ _OS.close(fd)
+ try:
+ me._db = _Q.connect(file)
+ c = me._db.cursor()
+ c.execute("""
+ CREATE TABLE meta (
+ name TEXT PRIMARY KEY NOT NULL,
+ value BLOB NOT NULL);
+ """)
+ c.execute("""
+ CREATE TABLE passwd (
+ label BLOB PRIMARY KEY NOT NULL,
+ payload BLOB NOT NULL);
+ """)
+ c.execute("""
+ INSERT INTO meta (name, value) VALUES ('$version', ?);
+ """, [me.VERSION])
+ except:
+ try: _OS.unlink(file)
+ except OSError: pass
+ raise
+
+ def _upgrade(me, ver):
+ """Upgrade the database from schema version VER."""
+ assert False, 'how embarrassing'
+
+ def _close(me, abruptp):
+ if not abruptp: me._db.commit()
+ me._db.close()
+ me._db = None
+
+ def _fetch_scalar(me, c, what, default = None):
+ try: row = next(c)
+ except StopIteration: val = default
+ else: val, = row
+ try: row = next(c)
+ except StopIteration: pass
+ else: raise ValueError, 'multiple matching records for %s' % what
+ return val
+
+ def _query_scalar(me, query, what, default = None, args = []):
+ c = me._db.cursor()
+ c.execute(query, args)
+ return me._fetch_scalar(c, what, default)
+
+ def _get_meta(me, name, default):
+ v = me._query_scalar("SELECT value FROM meta WHERE name = ?",
+ "metadata item `%s'" % name,
+ default = default, args = [name])
+ if v is default: return v
+ else: return str(v)
+
+ def _put_meta(me, name, value):
+ c = me._db.cursor()
+ c.execute("INSERT OR REPLACE INTO meta (name, value) VALUES (?, ?)",
+ [name, buffer(value)])
+
+ def _del_meta(me, name):
+ c = me._db.cursor()
+ c.execute("DELETE FROM meta WHERE name = ?", [name])
+ if not c.rowcount: raise KeyError, name
+
+ def _iter_meta(me):
+ c = me._db.cursor()
+ c.execute("SELECT name, value FROM meta WHERE name NOT LIKE '$%'")
+ for k, v in c: yield k, str(v)
+
+ def _get_passwd(me, label):
+ pld = me._query_scalar("SELECT payload FROM passwd WHERE label = ?",
+ "password", default = None,
+ args = [buffer(label)])
+ if pld is None: raise KeyError, label
+ return str(pld)
+
+ def _put_passwd(me, label, payload):
+ c = me._db.cursor()
+ c.execute("INSERT OR REPLACE INTO passwd (label, payload) "
+ "VALUES (?, ?)",
+ [buffer(label), buffer(payload)])
+
+ def _del_passwd(me, label):
+ c = me._db.cursor()
+ c.execute("DELETE FROM passwd WHERE label = ?", [label])
+ if not c.rowcount: raise KeyError, label
+
+ def _iter_passwds(me):
+ c = me._db.cursor()
+ c.execute("SELECT label, payload FROM passwd")
+ for k, v in c: yield str(k), str(v)