Update default settings. Minor changes.
[pithos] / pithos / backends / dummy.py
1 import os\r
2 import sqlite3\r
3 import logging\r
4 import types\r
5 import hashlib\r
6 import shutil\r
7 import basebackend\r
8 \r
9 logger = logging.getLogger(__name__)\r
10 \r
11 class BackEnd(basebackend.BaseBackEnd):\r
12 \r
13     def __init__(self, basepath):\r
14         self.basepath = basepath\r
15         \r
16         if not os.path.exists(basepath):\r
17             os.makedirs(basepath)\r
18         db = os.path.join(basepath, 'db')\r
19         self.con = sqlite3.connect(db)\r
20         # Create tables\r
21         sql = '''create table if not exists objects(name text)'''\r
22         self.con.execute(sql)\r
23         sql = '''create table if not exists metadata(object_id int, name text, value text)'''\r
24         self.con.execute(sql)\r
25         self.con.commit()\r
26     \r
27     # TODO: Create/delete account?\r
28     # TODO: Catch OSError exceptions.\r
29     \r
30     def get_account_meta(self, account):\r
31         """\r
32         returns a dictionary with the account metadata\r
33         """\r
34         logger.debug("get_account_meta: %s", account)\r
35         fullname = os.path.join(self.basepath, account)\r
36         if not os.path.exists(fullname):\r
37             raise NameError('Account does not exist')\r
38         contents = os.listdir(fullname)\r
39         count = len(contents)\r
40         size = os.stat(fullname).st_size\r
41         meta = self.__get_metadata(account)\r
42         meta.update({'name': account, 'count': count, 'bytes': size})\r
43         return meta\r
44 \r
45     def update_account_meta(self, account, meta):\r
46         """\r
47         updates the metadata associated with the account\r
48         """\r
49         logger.debug("update_account_meta: %s %s", account, meta)\r
50         fullname = os.path.join(self.basepath, account)\r
51         if not os.path.exists(fullname):\r
52             os.makedirs(fullname)\r
53         self.__put_metadata(account, meta)\r
54         return\r
55     \r
56     def create_container(self, account, name):\r
57         """\r
58         creates a new container with the given name\r
59         if it doesn't exist under the basepath\r
60         """\r
61         logger.debug("create_container: %s %s", account, name)\r
62         fullname = os.path.join(self.basepath, account, name)\r
63         if not os.path.exists(fullname):\r
64             os.makedirs(fullname)\r
65         else:\r
66             raise NameError('Container already exists')\r
67         return\r
68     \r
69     def delete_container(self, account, name):\r
70         """\r
71         deletes the container with the given name\r
72         if it exists under the basepath and is empty\r
73         """\r
74         logger.debug("delete_container: %s %s", account, name)\r
75         fullname = os.path.join(self.basepath, account, name)\r
76         if not os.path.exists(fullname):\r
77             raise NameError('Container does not exist')\r
78         if os.listdir(fullname):\r
79             raise IndexError('Container is not empty')\r
80         else:\r
81             os.rmdir(fullname)\r
82             self.__del_dbpath(os.path.join(account, name))\r
83         return\r
84     \r
85     def get_container_meta(self, account, name):\r
86         """\r
87         returns a dictionary with the container metadata\r
88         """\r
89         logger.debug("get_container_meta: %s %s", account, name)\r
90         fullname = os.path.join(self.basepath, account, name)\r
91         if not os.path.exists(fullname):\r
92             raise NameError('Container does not exist')\r
93         contents = os.listdir(fullname)\r
94         count = len(contents)\r
95         size = os.stat(fullname).st_size\r
96         meta = self.__get_metadata(os.path.join(account, name))\r
97         meta.update({'name': name, 'count': count, 'bytes': size})\r
98         return meta\r
99     \r
100     def update_container_meta(self, account, name, meta):\r
101         """\r
102         updates the metadata associated with the container\r
103         """\r
104         logger.debug("update_container_meta: %s %s %s", account, name, meta)\r
105         fullname = os.path.join(self.basepath, account, name)\r
106         if not os.path.exists(fullname):\r
107             raise NameError('Container does not exist')\r
108         self.__put_metadata(os.path.join(account, name), meta)\r
109         return\r
110     \r
111     def list_containers(self, account, marker = None, limit = 10000):\r
112         """\r
113         returns a list of at most limit (default = 10000) containers \r
114         starting from the next item after the optional marker\r
115         """\r
116         logger.debug("list_containers: %s %s %s", account, marker, limit)\r
117         fullname = os.path.join(self.basepath, account)\r
118         if not os.path.exists(fullname):\r
119             raise NameError('Account does not exist')\r
120         containers = os.listdir(fullname)\r
121         start = 0\r
122         if marker:\r
123             try:\r
124                 start = containers.index(marker) + 1\r
125             except ValueError:\r
126                 pass\r
127         if not limit or limit > 10000:\r
128             limit = 10000\r
129         \r
130         return containers[start:start + limit]\r
131     \r
132     def list_objects(self, account, container, prefix = '', delimiter = None, marker = None, limit = 10000):\r
133         """\r
134         returns a list of objects existing under a container\r
135         """\r
136         logger.info("list_objects: %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit)\r
137         fullname = os.path.join(self.basepath, account, container)\r
138         if not os.path.exists(fullname):\r
139             raise NameError('Container does not exist')\r
140         \r
141         prefix = os.path.join(account, container, prefix.lstrip('/'))\r
142         c = self.con.execute('select * from objects where name like ''?'' order by name', (os.path.join(prefix, '%'),))\r
143         objects = [x[0][len(prefix):] for x in c.fetchall()]\r
144         if delimiter:\r
145             pseudo_objects = []\r
146             for x in objects:\r
147                 pseudo_name = x\r
148                 i = pseudo_name.find(delimiter)\r
149                 if i != -1:\r
150                     pseudo_name = pseudo_name[:i]\r
151                 if pseudo_name not in pseudo_objects:\r
152                     pseudo_objects.append(pseudo_name)\r
153                 # TODO: Virtual directories.\r
154             objects = pseudo_objects\r
155         \r
156         start = 0\r
157         if marker:\r
158             try:\r
159                 start = objects.index(marker) + 1\r
160             except ValueError:\r
161                 pass\r
162         if not limit or limit > 10000:\r
163             limit = 10000\r
164         \r
165         return objects[start:start + limit]\r
166     \r
167     def get_object_meta(self, account, container, name, keys = None):\r
168         """\r
169         returns a dictionary with the object metadata\r
170         """\r
171         logger.info("get_object_meta: %s %s %s %s", account, container, name, keys)\r
172         fullname = os.path.join(self.basepath, account, container)\r
173         if not os.path.exists(fullname):\r
174             raise NameError('Container does not exist')\r
175         \r
176         link = self.__get_linkinfo(os.path.join(account, container, name))\r
177         location = os.path.join(self.basepath, account, container, link)\r
178         size = os.path.getsize(location)\r
179         mtime = os.path.getmtime(location)\r
180         meta = self.__get_metadata(os.path.join(account, container, name))\r
181         meta.update({'name': name, 'bytes': size, 'last_modified': mtime})\r
182         if 'hash' not in meta:\r
183             meta['hash'] = self.__object_hash(location)\r
184         if 'content_type' not in meta:\r
185             meta['content_type'] = 'application/octet-stream'\r
186         return meta\r
187     \r
188     def update_object_meta(self, account, container, name, meta):\r
189         """\r
190         updates the metadata associated with the object\r
191         """\r
192         logger.info("update_object_meta: %s %s %s %s", account, container, name, meta)\r
193         fullname = os.path.join(self.basepath, account, container)\r
194         if not os.path.exists(fullname):\r
195             raise NameError('Container does not exist')\r
196         try:\r
197             link = self.__get_linkinfo(os.path.join(account, container, name))\r
198         except NameError:\r
199             raise NameError('Object does not exist')\r
200         self.__put_metadata(os.path.join(account, container, name), meta)\r
201         return\r
202     \r
203     def get_object(self, account, container, name, offset = 0, length = -1):\r
204         """\r
205         returns the object data\r
206         """\r
207         logger.info("get_object: %s %s %s %s %s", account, container, name, offset, length)\r
208         fullname = os.path.join(self.basepath, account, container)\r
209         if not os.path.exists(fullname):\r
210             raise NameError('Container does not exist')\r
211         \r
212         link = self.__get_linkinfo(os.path.join(account, container, name))\r
213         location = os.path.join(self.basepath, account, container, link)\r
214         f = open(location, 'r')\r
215         if offset:\r
216             f.seek(offset)\r
217         data = f.read(length)\r
218         f.close()\r
219         return data\r
220 \r
221     def update_object(self, account, container, name, data, offset = 0):\r
222         """\r
223         creates/updates an object with the specified data\r
224         """\r
225         logger.info("put_object: %s %s %s %s %s", account, container, name, data, offset)\r
226         fullname = os.path.join(self.basepath, account, container)\r
227         if not os.path.exists(fullname):\r
228             raise NameError('Container does not exist')\r
229 \r
230         try:\r
231             link = self.__get_linkinfo(os.path.join(account, container, name))\r
232         except NameError:\r
233             # new object\r
234             link = self.__put_linkinfo(os.path.join(account, container, name))\r
235         location = os.path.join(self.basepath, account, container, link)\r
236         f = open(location, 'w')\r
237         if offset:\r
238             f.seek(offset)\r
239         f.write(data)\r
240         f.close()\r
241         self.__put_metadata(os.path.join(account, container, name), {'hash': self.__object_hash(location)})\r
242         return\r
243     \r
244     def copy_object(self, account, src_container, src_name, dest_container, dest_name):\r
245         """\r
246         copies an object\r
247         """\r
248         logger.info("copy_object: %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name)\r
249         link = self.__get_linkinfo(os.path.join(account, src_container, src_name))\r
250         src_location = os.path.join(self.basepath, account, src_container, link)\r
251         \r
252         dest_fullname = os.path.join(self.basepath, account, dest_container)\r
253         if not os.path.exists(dest_fullname):\r
254             raise NameError('Destination container does not exist')        \r
255         try:\r
256             link = self.__get_linkinfo(os.path.join(account, dest_container, dest_name))\r
257         except NameError:\r
258             # new object\r
259             link = self.__put_linkinfo(os.path.join(account, dest_container, dest_name))\r
260         dest_location = os.path.join(self.basepath, account, dest_container, link)\r
261         \r
262         shutil.copyfile(src_location, dest_location)\r
263         # TODO: accept metadata changes\r
264         self.__put_metadata(os.path.join(account, dest_container, dest_name), self.__get_metadata(os.path.join(account, src_container, src_name)))\r
265         return\r
266     \r
267     def delete_object(self, account, container, name):\r
268         """\r
269         deletes an object\r
270         """\r
271         logger.info("delete_object: %s %s %s", account, container, name)\r
272         fullname = os.path.join(self.basepath, account, container)\r
273         if not os.path.exists(fullname):\r
274             raise NameError('Container does not exist')\r
275         \r
276         # delete object data\r
277         link = self.__get_linkinfo(os.path.join(account, container, name))\r
278         location = os.path.join(self.basepath, account, container, link)\r
279         try:\r
280             os.remove(location)\r
281         except:\r
282             pass\r
283         # delete object metadata\r
284         self.__del_dbpath(os.path.join(account, container, name))\r
285         return\r
286 \r
287     def __get_linkinfo(self, path):\r
288         c = self.con.execute('select rowid from objects where name=''?''', (path,))\r
289         row = c.fetchone()\r
290         if row:\r
291             return str(row[0])\r
292         else:\r
293             raise NameError('Requested path not found')\r
294     \r
295     def __put_linkinfo(self, path):\r
296         id = self.con.execute('insert into objects (name) values (?)', (path,)).lastrowid\r
297         self.con.commit()\r
298         return str(id)\r
299     \r
300     def __get_metadata(self, path):\r
301         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
302         return dict(c.fetchall())\r
303     \r
304     def __put_metadata(self, path, meta):\r
305         c = self.con.execute('select rowid from objects where name=''?''', (path,))\r
306         row = c.fetchone()\r
307         if row:\r
308             link = str(row[0])\r
309         else:\r
310             link = self.con.execute('insert into objects (name) values (?)', (path,)).lastrowid      \r
311         for k, v in meta.iteritems():\r
312             self.con.execute('insert or replace into metadata (object_id, name, value) values (?, ?, ?)', (link, k, v))\r
313         self.con.commit()\r
314         return\r
315 \r
316     def __del_dbpath(self, path):\r
317         self.con.execute('delete from metadata where object_id in (select rowid from objects where name = ''?'')', (path,))\r
318         self.con.execute('delete from objects where name = ''?''', (path,))\r
319         self.con.commit()\r
320         return\r
321     \r
322     def __object_hash(self, location, block_size = 8192):\r
323         md5 = hashlib.md5()\r
324         f = open(location, 'r')\r
325         while True:\r
326             data = f.read(block_size)\r
327             if not data:\r
328                 break\r
329             md5.update(data)\r
330         f.close()\r
331         return md5.hexdigest()\r