From 9160181d83bfc72b9ffbd4a4a27169c1de9f057e Mon Sep 17 00:00:00 2001
From: Daira Hopwood <david-sarah@jacaranda.org>
Date: Mon, 15 Apr 2013 20:50:40 +0100
Subject: [PATCH] Make backupdb use dbutil.

Signed-off-by: Daira Hopwood <daira@jacaranda.org>
---
 src/allmydata/scripts/backupdb.py | 49 +++++++++----------------------
 src/allmydata/storage/leasedb.py  | 10 ++++++-
 src/allmydata/util/dbutil.py      | 23 +++++++--------
 3 files changed, 34 insertions(+), 48 deletions(-)

diff --git a/src/allmydata/scripts/backupdb.py b/src/allmydata/scripts/backupdb.py
index 75ee0d9c..d0c22616 100644
--- a/src/allmydata/scripts/backupdb.py
+++ b/src/allmydata/scripts/backupdb.py
@@ -6,6 +6,8 @@ from allmydata.util.hashutil import backupdb_dirhash
 from allmydata.util import base32
 from allmydata.util.fileutil import abspath_expanduser_unicode
 from allmydata.util.encodingutil import to_str
+from allmydata.util.dbutil import get_db, DBError
+
 
 DAY = 24*60*60
 MONTH = 30*DAY
@@ -58,47 +60,22 @@ UPDATE_v1_to_v2 = TABLE_DIRECTORY + """
 UPDATE version SET version=2;
 """
 
+UPDATERS = {
+    2: UPDATE_v1_to_v2,
+}
 
 def get_backupdb(dbfile, stderr=sys.stderr,
                  create_version=(SCHEMA_v2, 2), just_create=False):
-    # open or create the given backupdb file. The parent directory must
+    # Open or create the given backupdb file. The parent directory must
     # exist.
-    import sqlite3
-
-    must_create = not os.path.exists(dbfile)
-    try:
-        db = sqlite3.connect(dbfile)
-    except (EnvironmentError, sqlite3.OperationalError), e:
-        print >>stderr, "Unable to create/open backupdb file %s: %s" % (dbfile, e)
-        return None
-
-    c = db.cursor()
-    if must_create:
-        schema, version = create_version
-        c.executescript(schema)
-        c.execute("INSERT INTO version (version) VALUES (?)", (version,))
-        db.commit()
-
     try:
-        c.execute("SELECT version FROM version")
-        version = c.fetchone()[0]
-    except sqlite3.DatabaseError, e:
-        # this indicates that the file is not a compatible database format.
-        # Perhaps it was created with an old version, or it might be junk.
-        print >>stderr, "backupdb file is unusable: %s" % e
+        (sqlite3, db) = get_db(dbfile, stderr, create_version, updaters=UPDATERS,
+                               just_create=just_create, dbname="backupdb")
+        return BackupDB_v2(sqlite3, db)
+    except DBError, e:
+        print >>stderr, e
         return None
 
-    if just_create: # for tests
-        return True
-
-    if version == 1:
-        c.executescript(UPDATE_v1_to_v2)
-        db.commit()
-        version = 2
-    if version == 2:
-        return BackupDB_v2(sqlite3, db)
-    print >>stderr, "Unable to handle backupdb version %s" % version
-    return None
 
 class FileResult:
     def __init__(self, bdb, filecap, should_check,
@@ -127,6 +104,7 @@ class FileResult:
     def did_check_healthy(self, results):
         self.bdb.did_check_file_healthy(self.filecap, results)
 
+
 class DirectoryResult:
     def __init__(self, bdb, dirhash, dircap, should_check):
         self.bdb = bdb
@@ -148,6 +126,7 @@ class DirectoryResult:
     def did_check_healthy(self, results):
         self.bdb.did_check_directory_healthy(self.dircap, results)
 
+
 class BackupDB_v2:
     VERSION = 2
     NO_CHECK_BEFORE = 1*MONTH
@@ -180,7 +159,7 @@ class BackupDB_v2:
         is not healthy, please upload the file and call r.did_upload(filecap)
         when you're done.
 
-        I use_timestamps=True (the default), I will compare ctime and mtime
+        If use_timestamps=True (the default), I will compare ctime and mtime
         of the local file against an entry in my database, and consider the
         file to be unchanged if ctime, mtime, and filesize are all the same
         as the earlier version. If use_timestamps=False, I will not trust the
diff --git a/src/allmydata/storage/leasedb.py b/src/allmydata/storage/leasedb.py
index d64233d8..dbd72f90 100644
--- a/src/allmydata/storage/leasedb.py
+++ b/src/allmydata/storage/leasedb.py
@@ -119,8 +119,16 @@ class LeaseDB:
     STARTER_LEASE_DURATION = 2*MONTH
 
     def __init__(self, dbfile):
+        # synchronous = OFF is necessary for leasedb to pass tests for the time being,
+        # since using synchronous = NORMAL causes failures that are apparently due to
+        # a file descriptor leak, and the default synchronous = FULL causes the tests
+        # to time out. For discussion see
+        # https://tahoe-lafs.org/pipermail/tahoe-dev/2012-December/007877.html
+
         (self._sqlite,
-         self._db) = dbutil.get_db(dbfile, create_version=(LEASE_SCHEMA_V1, 1))
+         self._db) = dbutil.get_db(dbfile, create_version=(LEASE_SCHEMA_V1, 1),
+                                   # journal_mode="WAL",
+                                   synchronous="OFF")
         self._cursor = self._db.cursor()
         self.debug = False
         self.retained_history_entries = 10
diff --git a/src/allmydata/util/dbutil.py b/src/allmydata/util/dbutil.py
index fc12d4c0..f098f1c1 100644
--- a/src/allmydata/util/dbutil.py
+++ b/src/allmydata/util/dbutil.py
@@ -11,7 +11,8 @@ class DBError(Exception):
 
 
 def get_db(dbfile, stderr=sys.stderr,
-           create_version=(None, None), updaters={}, just_create=False):
+           create_version=(None, None), updaters={}, just_create=False, dbname="db",
+           journal_mode=None, synchronous=None):
     """Open or create the given db file. The parent directory must exist.
     create_version=(SCHEMA, VERNUM), and SCHEMA must have a 'version' table.
     Updaters is a {newver: commands} mapping, where e.g. updaters[2] is used
@@ -22,7 +23,7 @@ def get_db(dbfile, stderr=sys.stderr,
     try:
         db = sqlite3.connect(dbfile)
     except (EnvironmentError, sqlite3.OperationalError), e:
-        raise DBError("Unable to create/open db file %s: %s" % (dbfile, e))
+        raise DBError("Unable to create/open %s file %s: %s" % (dbname, dbfile, e))
 
     schema, target_version = create_version
     c = db.cursor()
@@ -31,13 +32,11 @@ def get_db(dbfile, stderr=sys.stderr,
     # The default is unspecified according to <http://www.sqlite.org/foreignkeys.html#fk_enable>.
     c.execute("PRAGMA foreign_keys = ON;")
 
-    # This is necessary to pass tests for the time being, since using
-    # synchronous = NORMAL causes failures that are apparently due to a
-    # file descriptor leak, and the default synchronous = FULL causes the
-    # tests to time out. For discussion see
-    # https://tahoe-lafs.org/pipermail/tahoe-dev/2012-December/007877.html
-    #c.execute("PRAGMA journal_mode = WAL;")
-    c.execute("PRAGMA synchronous = OFF;")
+    if journal_mode is not None:
+        c.execute("PRAGMA journal_mode = %s;" % (journal_mode,))
+
+    if synchronous is not None:
+        c.execute("PRAGMA synchronous = %s;" % (synchronous,))
 
     if must_create:
         c.executescript(schema)
@@ -50,17 +49,17 @@ def get_db(dbfile, stderr=sys.stderr,
     except sqlite3.DatabaseError, e:
         # this indicates that the file is not a compatible database format.
         # Perhaps it was created with an old version, or it might be junk.
-        raise DBError("db file is unusable: %s" % e)
+        raise DBError("%s file is unusable: %s" % (dbname, e))
 
     if just_create: # for tests
         return (sqlite3, db)
 
-    while version < target_version:
+    while version < target_version and version+1 in updaters:
         c.executescript(updaters[version+1])
         db.commit()
         version = version+1
     if version != target_version:
-        raise DBError("Unable to handle db version %s" % version)
+        raise DBError("Unable to handle %s version %s" % (dbname, version))
 
     return (sqlite3, db)
 
-- 
2.45.2