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.
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.
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.
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.
19 """Amazon S3 storage backend. Uses a URL of the form s3://BUCKET/PATH/."""
21 from __future__ import division, print_function, unicode_literals
23 import os, sys, tempfile
25 from boto.exception import S3ResponseError
26 from boto.s3.bucket import Bucket
27 from boto.s3.key import Key
31 def throw_notfound(method):
32 """Decorator to convert a 404 error into a cumulus.store.NoutFoundError."""
33 def f(*args, **kwargs):
35 return method(*args, **kwargs)
36 except S3ResponseError as e:
38 raise cumulus.store.NotFoundError(e)
43 class Store(cumulus.store.Store):
44 def __init__(self, url):
45 super(Store, self).__init__(url)
46 self.conn = boto.connect_s3(is_secure=False)
47 self.bucket = self.conn.create_bucket(url.hostname)
48 self.prefix = url.path
49 if not self.prefix.endswith("/"):
51 self.prefix = self.prefix.lstrip("/")
54 def _fullpath(self, path, is_directory=False):
55 fullpath = self.prefix + path
56 if is_directory and not fullpath.endswith("/"):
60 def _get_key(self, path):
62 k.key = self._fullpath(path)
67 prefix = self._fullpath(path, is_directory=True)
68 for i in self.bucket.list(prefix):
69 assert i.key.startswith(prefix)
70 self.scan_cache[i.key] = i
74 prefix = self._fullpath(path, is_directory=True)
75 # TODO: Should use a delimiter
76 for i in self.bucket.list(prefix):
77 assert i.key.startswith(prefix)
78 yield i.key[len(prefix):]
82 fp = tempfile.TemporaryFile()
83 k = self._get_key(path)
89 def put(self, path, fp):
90 k = self._get_key(path)
91 k.set_contents_from_file(fp)
94 def delete(self, path):
95 self.bucket.delete_key(self._fullpath(path))
98 path = self._fullpath(path)
99 if path in self.scan_cache:
100 k = self.scan_cache[path]
102 k = self.bucket.get_key(path)
104 raise cumulus.store.NotFoundError
106 return {'size': int(k.size)}