9 logger = logging.getLogger(__name__)
\r
10 formatter = logging.Formatter('[%(levelname)s] %(message)s')
\r
11 handler = logging.FileHandler('backend.out')
\r
12 handler.setFormatter(formatter)
\r
13 logger.addHandler(handler)
\r
19 def __init__(self, basepath, log_file='backend.out', log_level=logging.DEBUG):
\r
20 self.basepath = basepath
\r
22 # TODO: Manage log_file.
\r
23 logger.setLevel(log_level)
\r
25 if not os.path.exists(basepath):
\r
26 os.makedirs(basepath)
\r
27 db = os.path.join(basepath, 'db')
\r
28 self.con = sqlite3.connect(db)
\r
30 sql = '''create table if not exists objects(name text)'''
\r
31 self.con.execute(sql)
\r
32 sql = '''create table if not exists metadata(object_id int, name text, value text)'''
\r
33 self.con.execute(sql)
\r
36 # TODO: Create/delete account?
\r
37 # TODO: Catch OSError exceptions.
\r
39 def get_account_meta(self, account):
\r
41 returns a dictionary with the account metadata
\r
43 logger.debug("get_account_meta: %s", account)
\r
44 fullname = os.path.join(self.basepath, account)
\r
45 if not os.path.exists(fullname):
\r
46 raise NameError('Account does not exist')
\r
47 contents = os.listdir(fullname)
\r
48 count = len(contents)
\r
49 size = os.stat(fullname).st_size
\r
50 meta = self.__get_metadata(account)
\r
51 meta.update({'name': account, 'count': count, 'bytes': size})
\r
54 def update_account_meta(self, account, meta):
\r
56 updates the metadata associated with the account
\r
58 logger.debug("update_account_meta: %s %s", account, meta)
\r
59 fullname = os.path.join(self.basepath, account)
\r
60 if not os.path.exists(fullname):
\r
61 os.makedirs(fullname)
\r
62 self.__put_metadata(account, meta)
\r
65 def create_container(self, account, name):
\r
67 creates a new container with the given name
\r
68 if it doesn't exist under the basepath
\r
70 logger.debug("create_container: %s %s", account, name)
\r
71 fullname = os.path.join(self.basepath, account, name)
\r
72 if not os.path.exists(fullname):
\r
73 os.makedirs(fullname)
\r
75 raise NameError('Container already exists')
\r
78 def delete_container(self, account, name):
\r
80 deletes the container with the given name
\r
81 if it exists under the basepath and is empty
\r
83 logger.debug("delete_container: %s %s", account, name)
\r
84 fullname = os.path.join(self.basepath, account, name)
\r
85 if not os.path.exists(fullname):
\r
86 raise NameError('Container does not exist')
\r
87 if os.listdir(fullname):
\r
88 raise Exception('Container is not empty')
\r
91 self.__del_dbpath(os.path.join(account, name))
\r
94 def get_container_meta(self, account, name):
\r
96 returns a dictionary with the container metadata
\r
98 logger.debug("get_container_meta: %s %s", account, name)
\r
99 fullname = os.path.join(self.basepath, account, name)
\r
100 if not os.path.exists(fullname):
\r
101 raise NameError('Container does not exist')
\r
102 contents = os.listdir(fullname)
\r
103 count = len(contents)
\r
104 size = os.stat(fullname).st_size
\r
105 meta = self.__get_metadata(os.path.join(account, name))
\r
106 meta.update({'name': name, 'count': count, 'bytes': size})
\r
109 def update_container_meta(self, account, name, meta):
\r
111 updates the metadata associated with the container
\r
113 logger.debug("update_container_meta: %s %s %s", account, name, meta)
\r
114 fullname = os.path.join(self.basepath, account, name)
\r
115 if not os.path.exists(fullname):
\r
116 raise NameError('Container does not exist')
\r
117 self.__put_metadata(os.path.join(account, name), meta)
\r
120 def list_containers(self, account, marker = None, limit = 10000):
\r
122 returns a list of at most limit (default = 10000) containers
\r
123 starting from the next item after the optional marker
\r
125 logger.debug("list_containers: %s %s %s", account, marker, limit)
\r
126 fullname = os.path.join(self.basepath, account)
\r
127 if not os.path.exists(fullname):
\r
128 raise NameError('Account does not exist')
\r
129 containers = os.listdir(fullname)
\r
133 start = containers.index(marker) + 1
\r
136 if not limit or limit > 10000:
\r
139 return containers[start:start + limit]
\r
141 def list_objects(self, account, container, prefix = '', delimiter = None, marker = None, limit = 10000):
\r
143 returns a list of objects existing under a container
\r
145 logger.info("list_objects: %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit)
\r
146 fullname = os.path.join(self.basepath, account, container)
\r
147 if not os.path.exists(fullname):
\r
148 raise NameError('Container does not exist')
\r
150 while prefix.startswith('/'):
\r
151 prefix = prefix[1:]
\r
152 # TODO: Test this with various prefixes. Does '//' bother it?
\r
153 prefix = os.path.join(account, container, prefix)
\r
154 c = self.con.execute('select * from objects where name like ''?'' order by name', (os.path.join(prefix, '%'),))
\r
155 objects = [x[0][len(prefix):] for x in c.fetchall()]
\r
157 pseudo_objects = {}
\r
160 i = pseudo_name.find(delimiter)
\r
162 pseudo_name = pseudo_name[:i]
\r
163 # TODO: Virtual directories.
\r
164 pseudo_objects[pseudo_name] = x
\r
165 objects = pseudo_objects.keys()
\r
170 start = objects.index(marker)
\r
173 if not limit or limit > 10000:
\r
176 return objects[start:start + limit]
\r
178 def get_object_meta(self, account, container, name, keys = None):
\r
180 returns a dictionary with the object metadata
\r
182 logger.info("get_object_meta: %s %s %s %s", account, container, name, keys)
\r
183 fullname = os.path.join(self.basepath, account, container)
\r
184 if not os.path.exists(fullname):
\r
185 raise NameError('Container does not exist')
\r
187 link = self.__get_linkinfo(os.path.join(account, container, name))
\r
188 location = os.path.join(self.basepath, account, container, link)
\r
189 size = os.path.getsize(location)
\r
190 mtime = os.path.getmtime(location)
\r
191 meta = self.__get_metadata(os.path.join(account, container, name))
\r
192 meta.update({'name': name, 'bytes': size, 'last_modified': mtime})
\r
193 if 'hash' not in meta:
\r
194 meta['hash'] = self.__object_hash(location)
\r
195 if 'content_type' not in meta:
\r
196 meta['content_type'] = 'application/octet-stream'
\r
199 def update_object_meta(self, account, container, name, meta):
\r
201 updates the metadata associated with the object
\r
203 logger.info("update_object_meta: %s %s %s %s", account, container, name, meta)
\r
204 fullname = os.path.join(self.basepath, account, container)
\r
205 if not os.path.exists(fullname):
\r
206 raise NameError('Container does not exist')
\r
207 self.__put_metadata(os.path.join(account, container, name), meta)
\r
210 def get_object(self, account, container, name, offset = 0, length = -1):
\r
212 returns the object data
\r
214 logger.info("get_object: %s %s %s %s %s", account, container, name, offset, length)
\r
215 fullname = os.path.join(self.basepath, account, container)
\r
216 if not os.path.exists(fullname):
\r
217 raise NameError('Container does not exist')
\r
219 link = self.__get_linkinfo(os.path.join(account, container, name))
\r
220 location = os.path.join(self.basepath, account, container, link)
\r
221 f = open(location, 'r')
\r
224 data = f.read(length)
\r
228 def update_object(self, account, container, name, data, offset = 0):
\r
230 creates/updates an object with the specified data
\r
232 logger.info("put_object: %s %s %s %s %s", account, container, name, data, offset)
\r
233 fullname = os.path.join(self.basepath, account, container)
\r
234 if not os.path.exists(fullname):
\r
235 raise NameError('Container does not exist')
\r
238 link = self.__get_linkinfo(os.path.join(account, container, name))
\r
241 link = self.__put_linkinfo(os.path.join(account, container, name))
\r
242 location = os.path.join(self.basepath, account, container, link)
\r
243 f = open(location, 'w')
\r
248 self.__put_metadata(os.path.join(account, container, name), {'hash': self.__object_hash(location)})
\r
251 def copy_object(self, account, src_container, src_name, dest_container, dest_name):
\r
255 logger.info("copy_object: %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name)
\r
256 link = self.__get_linkinfo(os.path.join(account, src_container, src_name))
\r
257 src_location = os.path.join(self.basepath, account, src_container, link)
\r
259 dest_fullname = os.path.join(self.basepath, account, dest_container)
\r
260 if not os.path.exists(dest_fullname):
\r
261 raise NameError('Destination container does not exist')
\r
263 link = self.__get_linkinfo(os.path.join(account, dest_container, dest_name))
\r
266 link = self.__put_linkinfo(os.path.join(account, dest_container, dest_name))
\r
267 dest_location = os.path.join(self.basepath, account, dest_container, link)
\r
269 shutil.copyfile(src_location, dest_location)
\r
272 def delete_object(self, account, container, name):
\r
276 logger.info("delete_object: %s %s %s", account, container, name)
\r
277 fullname = os.path.join(self.basepath, account, container)
\r
278 if not os.path.exists(fullname):
\r
279 raise NameError('Container does not exist')
\r
281 # delete object data
\r
282 link = self.__get_linkinfo(os.path.join(account, container, name))
\r
283 location = os.path.join(self.basepath, account, container, link)
\r
285 os.remove(location)
\r
288 # delete object metadata
\r
289 self.__del_dbpath(os.path.join(account, container, name))
\r
292 def __get_linkinfo(self, path):
\r
293 c = self.con.execute('select rowid from objects where name=''?''', (path,))
\r
298 raise NameError('Requested path not found')
\r
300 def __put_linkinfo(self, path):
\r
301 id = self.con.execute('insert into objects (name) values (?)', (path,)).lastrowid
\r
305 def __get_metadata(self, path):
\r
306 c = self.con.execute('select m.name, m.value from metadata m, objects o where o.rowid = m.object_id and o.name = ''?''', (path,))
\r
307 return dict(c.fetchall())
\r
309 def __put_metadata(self, path, meta):
\r
310 c = self.con.execute('select rowid from objects where name=''?''', (path,))
\r
315 link = self.con.execute('insert into objects (name) values (?)', (path,)).lastrowid
\r
316 for k, v in meta.iteritems():
\r
317 if type(v) != types.StringType:
\r
319 self.con.execute('insert or replace into metadata (object_id, name, value) values (?, ?, ?)', (link, k, v))
\r
323 def __del_dbpath(self, path):
\r
324 self.con.execute('delete from metadata where object_id in (select rowid from objects where name = ''?'')', (path,))
\r
325 self.con.execute('delete from objects where name = ''?''', (path,))
\r
329 def __object_hash(self, location, block_size = 8192):
\r
330 md5 = hashlib.md5()
\r
331 f = open(location, 'r')
\r
333 data = f.read(block_size)
\r
338 return md5.hexdigest()
\r