Byte/string handling fixes for Python 3.
[cumulus.git] / python / cumulus / store / __init__.py
index 35de0ee..a59c217 100644 (file)
@@ -1,35 +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+)?$"),
-    '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
 
-class Store:
+class Store(object):
     """Base class for all cumulus storage backends."""
 
-    def list(self, type):
-        raise NotImplementedException
+    def __init__(self, url):
+        """Initializes a new storage backend.
+
+        Params:
+          url: The parsed (by urlsplit) URL that specifies the storage
+              location.
+        """
+        pass
+
+    # TODO: Implement context manager.
+
+    def list(self, path):
+        raise NotImplementedError
 
-    def get(self, type, name):
-        raise NotImplementedException
+    def get(self, path):
+        raise NotImplementedError
 
-    def put(self, type, name, fp):
-        raise NotImplementedException
+    def put(self, path, fp):
+        raise NotImplementedError
 
-    def delete(self, type, name):
-        raise NotImplementedException
+    def delete(self, path):
+        raise NotImplementedError
 
-    def stat(self, type, name):
-        raise NotImplementedException
+    def stat(self, path):
+        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
@@ -37,17 +77,34 @@ class Store:
 
         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):
-    (scheme, netloc, path, params, query, fragment) \
-        = urlparse.urlparse(url)
-
-    if scheme == "file":
-        import cumulus.store.file
-        return cumulus.store.file.FileStore(path)
-    elif scheme == "s3":
-        import cumulus.store.s3
-        while path.startswith("/"): path = path[1:]
-        (bucket, path) = path.split("/", 1)
-        return cumulus.store.s3.S3Store(bucket, path)
-    else:
-        raise NotImplementedException
+    """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)