Start a cleanup on the storage backends.
[cumulus.git] / python / cumulus / store / __init__.py
1 # Cumulus: Efficient Filesystem Backup to the Cloud
2 # Copyright (C) 2008-2010 The Cumulus Developers
3 # See the AUTHORS file for a list of contributors.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19 from __future__ import division, print_function, unicode_literals
20
21 import importlib
22 import re
23 try:
24     # Python 3
25     from urllib import parse as urlparse
26     from urllib.parse import quote, unquote
27 except ImportError:
28     # Python 2
29     from urllib import quote, unquote
30     import urlparse
31
32 type_patterns = {
33     'checksums': re.compile(r"^snapshot-(.*)\.(\w+)sums$"),
34     '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+)?$"),
35     'snapshots': re.compile(r"^snapshot-(.*)\.(cumulus|lbs)$")
36 }
37
38 class NotFoundError(KeyError):
39     """Exception thrown when a file is not found in a repository."""
40
41     pass
42
43 class Store(object):
44     """Base class for all cumulus storage backends."""
45
46     def __init__(self, url):
47         """Initializes a new storage backend.
48
49         Params:
50           url: The parsed (by urlsplit) URL that specifies the storage
51               location.
52         """
53         pass
54
55     # TODO: Implement context manager.
56
57     def list(self, path):
58         raise NotImplementedError
59
60     def get(self, path):
61         raise NotImplementedError
62
63     def put(self, path, fp):
64         raise NotImplementedError
65
66     def delete(self, path):
67         raise NotImplementedError
68
69     def stat(self, path):
70         raise NotImplementedError
71
72     def scan(self, path):
73         """Cache file information stored in this backend.
74
75         This might make subsequent list or stat calls more efficient, but this
76         function is intended purely as a performance optimization."""
77
78         pass
79
80     def close(self):
81         """Tear down the connection explicitly if needed
82
83         Currently needed for sftp to be able to end the program."""
84
85         pass
86
87     def __del__(self):
88         self.close()
89
90 def open(url):
91     """Parse a storage url, then locate and initialize a backend for it."""
92     parsed_url = urlparse.urlsplit(url)
93
94     # If there is no scheme, fall back to treating the string as local path and
95     # construct a file:/// URL.
96     if not parsed_url.scheme:
97         parsed_url = urlparse.SplitResult("file", "", quote(url), "", "")
98
99     try:
100         # TODO: Support a registry for schemes that don't map to a module.
101         if re.match(r"^\w+$", parsed_url.scheme):
102             handler = importlib.import_module("cumulus.store.%s" %
103                                               parsed_url.scheme)
104             obj = handler.Store(parsed_url)
105             return obj
106     except ImportError:
107         # Fall through to error below
108         pass
109
110     raise NotImplementedError("Scheme %s not implemented" % scheme)