Implement a new Python wrapper for the local database.
[cumulus.git] / python / cumulus / store / __init__.py
index f32ff9f..a59c217 100644 (file)
@@ -1,58 +1,75 @@
-import exceptions, re, urlparse
+# Cumulus: Efficient Filesystem Backup to the Cloud
+# Copyright (C) 2008-2010 The Cumulus Developers
+# See the AUTHORS file for a list of contributors.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import division, print_function, unicode_literals
+
+import importlib
+import re
+try:
+    # Python 3
+    from urllib import parse as urlparse
+    from urllib.parse import quote, unquote
+except ImportError:
+    # Python 2
+    from urllib import quote, unquote
+    import urlparse
 
 type_patterns = {
     'checksums': re.compile(r"^snapshot-(.*)\.(\w+)sums$"),
     'segments': re.compile(r"^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(\.\S+)?$"),
 
 type_patterns = {
     'checksums': re.compile(r"^snapshot-(.*)\.(\w+)sums$"),
     'segments': re.compile(r"^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(\.\S+)?$"),
-    'snapshots': re.compile(r"^snapshot-(.*)\.lbs$")
+    'snapshots': re.compile(r"^snapshot-(.*)\.(cumulus|lbs)$")
 }
 
 }
 
-class NotFoundError(exceptions.KeyError):
+class NotFoundError(KeyError):
     """Exception thrown when a file is not found in a repository."""
 
     pass
 
     """Exception thrown when a file is not found in a repository."""
 
     pass
 
-class Store (object):
+class Store(object):
     """Base class for all cumulus storage backends."""
 
     """Base class for all cumulus storage backends."""
 
-    def __new__ (cls, url, **kw):
-        """ Return the correct sub-class depending on url,
-        pass parsed url parameters to object
+    def __init__(self, url):
+        """Initializes a new storage backend.
+
+        Params:
+          url: The parsed (by urlsplit) URL that specifies the storage
+              location.
         """
         """
-        if cls != Store:
-            return super(Store, cls).__new__(cls, url, **kw)
-        (scheme, netloc, path, params, query, fragment) \
-            = urlparse.urlparse(url)
-
-        try:
-            cumulus = __import__('cumulus.store.%s' % scheme, globals())
-            subcls = getattr (cumulus.store, scheme).Store
-            obj = super(Store, cls).__new__(subcls, url, **kw)
-            obj.scheme = scheme
-            obj.netloc = netloc
-            obj.path = path
-            obj.params = params
-            obj.query = query
-            obj.fragment = fragment
-            return obj
-        except ImportError:
-            raise NotImplementedError, "Scheme %s not implemented" % scheme
+        pass
+
+    # TODO: Implement context manager.
 
 
-    def list(self, type):
+    def list(self, path):
         raise NotImplementedError
 
         raise NotImplementedError
 
-    def get(self, type, name):
+    def get(self, path):
         raise NotImplementedError
 
         raise NotImplementedError
 
-    def put(self, type, name, fp):
+    def put(self, path, fp):
         raise NotImplementedError
 
         raise NotImplementedError
 
-    def delete(self, type, name):
+    def delete(self, path):
         raise NotImplementedError
 
         raise NotImplementedError
 
-    def stat(self, type, name):
+    def stat(self, path):
         raise NotImplementedError
 
         raise NotImplementedError
 
-    def scan(self):
+    def scan(self, path):
         """Cache file information stored in this backend.
 
         This might make subsequent list or stat calls more efficient, but this
         """Cache file information stored in this backend.
 
         This might make subsequent list or stat calls more efficient, but this
@@ -60,5 +77,34 @@ class Store (object):
 
         pass
 
 
         pass
 
+    def close(self):
+        """Tear down the connection explicitly if needed
+
+        Currently needed for sftp to be able to end the program."""
+
+        pass
+
+    def __del__(self):
+        self.close()
+
 def open(url):
 def open(url):
-    return Store(url)
+    """Parse a storage url, then locate and initialize a backend for it."""
+    parsed_url = urlparse.urlsplit(url)
+
+    # If there is no scheme, fall back to treating the string as local path and
+    # construct a file:/// URL.
+    if not parsed_url.scheme:
+        parsed_url = urlparse.SplitResult("file", "", quote(url), "", "")
+
+    try:
+        # TODO: Support a registry for schemes that don't map to a module.
+        if re.match(r"^\w+$", parsed_url.scheme):
+            handler = importlib.import_module("cumulus.store.%s" %
+                                              parsed_url.scheme)
+            obj = handler.Store(parsed_url)
+            return obj
+    except ImportError:
+        # Fall through to error below
+        pass
+
+    raise NotImplementedError("Scheme %s not implemented" % scheme)