From f7a263eb0b2d0b740886f2f64744c8f607335746 Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@allmydata.com>
Date: Tue, 10 Feb 2009 18:49:10 -0700
Subject: [PATCH] #619: make 'tahoe backup' complain and refuse to run if
 sqlite is unavailable and --no-backupdb is not passed

---
 src/allmydata/scripts/backupdb.py     | 17 ++++++++++-
 src/allmydata/scripts/cli.py          |  2 +-
 src/allmydata/scripts/tahoe_backup.py |  5 +++
 src/allmydata/test/test_backupdb.py   |  6 ++--
 src/allmydata/test/test_cli.py        | 44 ++++++++++++++++-----------
 5 files changed, 51 insertions(+), 23 deletions(-)

diff --git a/src/allmydata/scripts/backupdb.py b/src/allmydata/scripts/backupdb.py
index 51fbda7d..15bb3453 100644
--- a/src/allmydata/scripts/backupdb.py
+++ b/src/allmydata/scripts/backupdb.py
@@ -50,7 +50,22 @@ def get_backupdb(dbfile, stderr=sys.stderr):
             from pysqlite2 import dbapi2
             sqlite = dbapi2 # .. when this clause does it too
         except ImportError:
-            print >>stderr, "sqlite unavailable, not using backupdb"
+            print >>stderr, """\
+The backup command uses a SQLite database to avoid duplicate uploads, but
+I was unable to import a python sqlite library. You have two options:
+
+ 1: Install a python sqlite library. python2.5 and beyond have one built-in.
+    If you are using python2.4, you can install the 'pysqlite' package,
+    perhaps with 'apt-get install python-pysqlite2', or 'easy_install
+    pysqlite', or by installing the 'pysqlite' package from
+    http://pypi.python.org . Make sure you get the version with support for
+    SQLite 3.
+
+ 2: run me with the --no-backupdb option to disable use of the database. This
+    will be somewhat slower, since I will be unable to avoid re-uploading
+    files that were uploaded in the past, but the basic functionality will be
+    unimpaired.
+"""
             return None
 
     must_create = not os.path.exists(dbfile)
diff --git a/src/allmydata/scripts/cli.py b/src/allmydata/scripts/cli.py
index de5df036..87d6b279 100644
--- a/src/allmydata/scripts/cli.py
+++ b/src/allmydata/scripts/cli.py
@@ -193,7 +193,7 @@ class LnOptions(VDriveOptions):
 class BackupOptions(VDriveOptions):
     optFlags = [
         ("verbose", "v", "Be noisy about what is happening."),
-        ("no-backupdb", None, "Do not use the backup-database (always upload all files)."),
+        ("no-backupdb", None, "Do not use the SQLite-based backup-database (always upload all files)."),
         ("ignore-timestamps", None, "Do not use backupdb timestamps to decide if a local file is unchanged."),
         ]
 
diff --git a/src/allmydata/scripts/tahoe_backup.py b/src/allmydata/scripts/tahoe_backup.py
index c38dd455..6b829c02 100644
--- a/src/allmydata/scripts/tahoe_backup.py
+++ b/src/allmydata/scripts/tahoe_backup.py
@@ -145,6 +145,11 @@ class BackerUpper:
                                    "private", "backupdb.sqlite")
             bdbfile = os.path.abspath(bdbfile)
             self.backupdb = backupdb.get_backupdb(bdbfile, stderr)
+            if not self.backupdb:
+                # get_backupdb() has already delivered a lengthy speech about
+                # where to find pysqlite and how to add --no-backupdb
+                print >>stderr, "ERROR: Unable to import sqlite."
+                return 1
 
         rootcap, path = get_alias(options.aliases, options.to_dir, DEFAULT_ALIAS)
         to_url = nodeurl + "uri/%s/" % urllib.quote(rootcap)
diff --git a/src/allmydata/test/test_backupdb.py b/src/allmydata/test/test_backupdb.py
index 0c209e13..ee2e0168 100644
--- a/src/allmydata/test/test_backupdb.py
+++ b/src/allmydata/test/test_backupdb.py
@@ -11,7 +11,7 @@ class BackupDB(unittest.TestCase):
         stderr = StringIO()
         bdb = backupdb.get_backupdb(dbfile, stderr=stderr)
         if not bdb:
-            if "sqlite unavailable" in stderr.getvalue():
+            if "I was unable to import a python sqlite library" in stderr.getvalue():
                 raise unittest.SkipTest("sqlite unavailable, skipping test")
         return bdb
 
@@ -34,7 +34,7 @@ class BackupDB(unittest.TestCase):
                                     stderr_f)
         self.failUnlessEqual(bdb, None)
         stderr = stderr_f.getvalue()
-        if "sqlite unavailable" in stderr:
+        if "I was unable to import a python sqlite library" in stderr:
             pass
         else:
             self.failUnless("backupdb file is unusable" in stderr)
@@ -47,7 +47,7 @@ class BackupDB(unittest.TestCase):
         bdb = backupdb.get_backupdb(where, stderr_f)
         self.failUnlessEqual(bdb, None)
         stderr = stderr_f.getvalue()
-        if "sqlite unavailable" in stderr:
+        if "I was unable to import a python sqlite library" in stderr:
             pass
         else:
             self.failUnless(("Unable to create/open backupdb file %s" % where)
diff --git a/src/allmydata/test/test_cli.py b/src/allmydata/test/test_cli.py
index 6bb855a7..7d171d2d 100644
--- a/src/allmydata/test/test_cli.py
+++ b/src/allmydata/test/test_cli.py
@@ -636,13 +636,6 @@ class Backup(SystemTestMixin, CLITestMixin, unittest.TestCase):
         mo = re.search(r"(\d)+ files checked, (\d+) directories checked, (\d+) directories read", out)
         return [int(s) for s in mo.groups()]
 
-    def nosqlite_is_ok(self, err, have_bdb):
-        if have_bdb:
-            self.failUnlessEqual(err, "")
-        else:
-            self.failUnlessEqual(err.strip(),
-                                 "sqlite unavailable, not using backupdb")
-
     def test_backup(self):
         self.basedir = os.path.dirname(self.mktemp())
 
@@ -659,11 +652,28 @@ class Backup(SystemTestMixin, CLITestMixin, unittest.TestCase):
         self.writeto("parent/subdir/bar.txt", "bar\n" * 1000)
         self.writeto("parent/blah.txt", "blah")
 
+        def do_backup(use_backupdb=True, verbose=False):
+            cmd = ["backup"]
+            if not have_bdb or not use_backupdb:
+                cmd.append("--no-backupdb")
+            if verbose:
+                cmd.append("--verbose")
+            cmd.append(source)
+            cmd.append("tahoe:backups")
+            return self.do_cli(*cmd)
+
         d = self.set_up_nodes()
         d.addCallback(lambda res: self.do_cli("create-alias", "tahoe"))
-        d.addCallback(lambda res: self.do_cli("backup", source, "tahoe:backups"))
+
+        if not have_bdb:
+            d.addCallback(lambda res: self.do_cli("backup", source, "tahoe:backups"))
+            def _should_complain((rc, out, err)):
+                self.failUnless("I was unable to import a python sqlite library" in err, err)
+            d.addCallback(_should_complain)
+
+        d.addCallback(lambda res: do_backup())
         def _check0((rc, out, err)):
-            self.nosqlite_is_ok(err, have_bdb)
+            self.failUnlessEqual(err, "")
             self.failUnlessEqual(rc, 0)
             fu, fr, dc, dr = self.count_output(out)
             # foo.txt, bar.txt, blah.txt
@@ -707,11 +717,11 @@ class Backup(SystemTestMixin, CLITestMixin, unittest.TestCase):
         d.addCallback(_check4)
 
 
-        d.addCallback(lambda res: self.do_cli("backup", source, "tahoe:backups"))
+        d.addCallback(lambda res: do_backup())
         def _check4a((rc, out, err)):
             # second backup should reuse everything, if the backupdb is
             # available
-            self.nosqlite_is_ok(err, have_bdb)
+            self.failUnlessEqual(err, "")
             self.failUnlessEqual(rc, 0)
             if have_bdb:
                 fu, fr, dc, dr = self.count_output(out)
@@ -736,12 +746,11 @@ class Backup(SystemTestMixin, CLITestMixin, unittest.TestCase):
 
             d.addCallback(_reset_last_checked)
 
-            d.addCallback(lambda res:
-                          self.do_cli("backup", "--verbose", source, "tahoe:backups"))
+            d.addCallback(lambda res: do_backup(verbose=True))
             def _check4b((rc, out, err)):
                 # we should check all files, and re-use all of them. None of
                 # the directories should have been changed.
-                self.nosqlite_is_ok(err, have_bdb)
+                self.failUnlessEqual(err, "")
                 self.failUnlessEqual(rc, 0)
                 fu, fr, dc, dr = self.count_output(out)
                 fchecked, dchecked, dread = self.count_output2(out)
@@ -782,12 +791,12 @@ class Backup(SystemTestMixin, CLITestMixin, unittest.TestCase):
             # turn a directory into a file
             os.rmdir(os.path.join(source, "empty"))
             self.writeto("empty", "imagine nothing being here")
-            return self.do_cli("backup", source, "tahoe:backups")
+            return do_backup()
         d.addCallback(_modify)
         def _check5a((rc, out, err)):
             # second backup should reuse bar.txt (if backupdb is available),
             # and upload the rest. None of the directories can be reused.
-            self.nosqlite_is_ok(err, have_bdb)
+            self.failUnlessEqual(err, "")
             self.failUnlessEqual(rc, 0)
             if have_bdb:
                 fu, fr, dc, dr = self.count_output(out)
@@ -825,8 +834,7 @@ class Backup(SystemTestMixin, CLITestMixin, unittest.TestCase):
             self.failUnlessEqual(out, "foo")
         d.addCallback(_check8)
 
-        d.addCallback(lambda res:
-                      self.do_cli("backup", "--no-backupdb", source, "tahoe:backups"))
+        d.addCallback(lambda res: do_backup(use_backupdb=False))
         def _check9((rc, out, err)):
             # --no-backupdb means re-upload everything. We still get to
             # re-use the directories, since nothing changed.
-- 
2.45.2