2 # Copyright (c) 2011 Greek Research and Technology Network
\r
5 from django.http import HttpResponse
\r
6 from django.template.loader import render_to_string
\r
7 from django.utils import simplejson as json
\r
8 from django.utils.http import http_date, parse_etags
\r
11 from django.utils.http import parse_http_date_safe
\r
13 from pithos.api.util import parse_http_date_safe
\r
15 from pithos.api.faults import Fault, NotModified, BadRequest, Unauthorized, ItemNotFound, LengthRequired, PreconditionFailed, RangeNotSatisfiable, UnprocessableEntity
\r
16 from pithos.api.util import get_object_meta, get_range, api_method
\r
18 from settings import PROJECT_PATH
\r
20 STORAGE_PATH = path.join(PROJECT_PATH, 'data')
\r
22 from pithos.backends.dummy import BackEnd
\r
23 from pithos.backends.dummy_debug import *
\r
27 logging.basicConfig(level=logging.DEBUG)
\r
30 def authenticate(request):
\r
31 # Normal Response Codes: 204
\r
32 # Error Response Codes: serviceUnavailable (503),
\r
33 # unauthorized (401),
\r
36 x_auth_user = request.META.get('HTTP_X_AUTH_USER')
\r
37 x_auth_key = request.META.get('HTTP_X_AUTH_KEY')
\r
39 if not x_auth_user or not x_auth_key:
\r
40 raise BadRequest('Missing auth user or key.')
\r
42 response = HttpResponse(status = 204)
\r
43 response['X-Auth-Token'] = 'eaaafd18-0fed-4b3a-81b4-663c99ec1cbb'
\r
44 # TODO: Must support X-Storage-Url to be compatible.
\r
45 response['X-Storage-Url'] = 'http://127.0.0.1:8000/v1/asdf'
\r
48 def account_demux(request, v_account):
\r
49 if request.method == 'HEAD':
\r
50 return account_meta(request, v_account)
\r
51 elif request.method == 'GET':
\r
52 return container_list(request, v_account)
\r
54 return method_not_allowed(request)
\r
56 def container_demux(request, v_account, v_container):
\r
57 if request.method == 'HEAD':
\r
58 return container_meta(request, v_account, v_container)
\r
59 elif request.method == 'GET':
\r
60 return object_list(request, v_account, v_container)
\r
61 elif request.method == 'PUT':
\r
62 return container_create(request, v_account, v_container)
\r
63 elif request.method == 'DELETE':
\r
64 return container_delete(request, v_account, v_container)
\r
66 return method_not_allowed(request)
\r
68 def object_demux(request, v_account, v_container, v_object):
\r
69 if request.method == 'HEAD':
\r
70 return object_meta(request, v_account, v_container, v_object)
\r
71 elif request.method == 'GET':
\r
72 return object_read(request, v_account, v_container, v_object)
\r
73 elif request.method == 'PUT':
\r
74 return object_write(request, v_account, v_container, v_object)
\r
75 elif request.method == 'COPY':
\r
76 return object_copy(request, v_account, v_container, v_object)
\r
77 elif request.method == 'POST':
\r
78 return object_update(request, v_account, v_container, v_object)
\r
79 elif request.method == 'DELETE':
\r
80 return object_delete(request, v_account, v_container, v_object)
\r
82 return method_not_allowed(request)
\r
85 def account_meta(request, v_account):
\r
86 # Normal Response Codes: 204
\r
87 # Error Response Codes: serviceUnavailable (503),
\r
88 # unauthorized (401),
\r
91 be = BackEnd(STORAGE_PATH)
\r
93 info = be.get_account_meta(request.user)
\r
95 info = {'count': 0, 'bytes': 0}
\r
97 response = HttpResponse(status = 204)
\r
98 response['X-Account-Container-Count'] = info['count']
\r
99 response['X-Account-Bytes-Used'] = info['bytes']
\r
102 @api_method('GET', format_allowed = True)
\r
103 def container_list(request, v_account):
\r
104 # Normal Response Codes: 200, 204
\r
105 # Error Response Codes: serviceUnavailable (503),
\r
106 # itemNotFound (404),
\r
107 # unauthorized (401),
\r
110 marker = request.GET.get('marker')
\r
111 limit = request.GET.get('limit')
\r
118 be = BackEnd(STORAGE_PATH)
\r
120 containers = be.list_containers(request.user, marker, limit)
\r
123 if len(containers) == 0:
\r
124 return HttpResponse(status = 204)
\r
126 if request.serialization == 'text':
\r
127 return HttpResponse('\n'.join(containers), status = 200)
\r
129 # TODO: Do this with a backend parameter?
\r
131 containers = [be.get_container_meta(request.user, x) for x in containers]
\r
133 raise ItemNotFound()
\r
134 if request.serialization == 'xml':
\r
135 data = render_to_string('containers.xml', {'account': request.user, 'containers': containers})
\r
136 elif request.serialization == 'json':
\r
137 data = json.dumps(containers)
\r
138 return HttpResponse(data, status = 200)
\r
140 @api_method('HEAD')
\r
141 def container_meta(request, v_account, v_container):
\r
142 # Normal Response Codes: 204
\r
143 # Error Response Codes: serviceUnavailable (503),
\r
144 # itemNotFound (404),
\r
145 # unauthorized (401),
\r
148 be = BackEnd(STORAGE_PATH)
\r
150 info = be.get_container_meta(request.user, v_container)
\r
152 raise ItemNotFound()
\r
154 response = HttpResponse(status = 204)
\r
155 response['X-Container-Object-Count'] = info['count']
\r
156 response['X-Container-Bytes-Used'] = info['bytes']
\r
160 def container_create(request, v_account, v_container):
\r
161 # Normal Response Codes: 201, 202
\r
162 # Error Response Codes: serviceUnavailable (503),
\r
163 # itemNotFound (404),
\r
164 # unauthorized (401),
\r
167 be = BackEnd(STORAGE_PATH)
\r
169 be.create_container(request.user, v_container)
\r
170 return HttpResponse(status = 201)
\r
172 return HttpResponse(status = 202)
\r
174 @api_method('DELETE')
\r
175 def container_delete(request, v_account, v_container):
\r
176 # Normal Response Codes: 204
\r
177 # Error Response Codes: serviceUnavailable (503),
\r
178 # itemNotFound (404),
\r
179 # unauthorized (401),
\r
182 be = BackEnd(STORAGE_PATH)
\r
184 info = be.get_container_meta(request.user, v_container)
\r
186 raise ItemNotFound()
\r
188 if info['count'] > 0:
\r
189 return HttpResponse(status = 409)
\r
191 # TODO: Handle both exceptions.
\r
193 be.delete_container(request.user, v_container)
\r
195 raise ItemNotFound()
\r
196 return HttpResponse(status = 204)
\r
198 # --- UP TO HERE ---
\r
200 @api_method('GET', format_allowed = True)
\r
201 def object_list(request, v_account, v_container):
\r
202 # Normal Response Codes: 200, 204
\r
203 # Error Response Codes: serviceUnavailable (503),
\r
204 # itemNotFound (404),
\r
205 # unauthorized (401),
\r
208 path = request.GET.get('path')
\r
209 prefix = request.GET.get('prefix')
\r
210 delimiter = request.GET.get('delimiter')
\r
211 logging.debug("path: %s", path)
\r
213 # Path overrides prefix and delimiter.
\r
218 if prefix and delimiter:
\r
219 prefix = prefix + delimiter
\r
221 marker = request.GET.get('marker')
\r
222 limit = request.GET.get('limit')
\r
229 objects = list_objects(request.user, v_container, prefix, delimiter, marker, limit)
\r
230 if len(objects) == 0:
\r
231 return HttpResponse(status = 204)
\r
233 if request.serialization == 'xml':
\r
234 data = render_to_string('objects.xml', {'container': v_container, 'objects': objects})
\r
235 elif request.serialization == 'json':
\r
236 data = json.dumps(objects)
\r
238 data = '\n'.join(x['name'] for x in objects)
\r
240 return HttpResponse(data, status = 200)
\r
242 @api_method('HEAD')
\r
243 def object_meta(request, v_account, v_container, v_object):
\r
244 # Normal Response Codes: 204
\r
245 # Error Response Codes: serviceUnavailable (503),
\r
246 # itemNotFound (404),
\r
247 # unauthorized (401),
\r
250 info = get_object_meta(request.user, v_container, v_object)
\r
252 response = HttpResponse(status = 204)
\r
253 response['ETag'] = info['hash']
\r
254 response['Content-Length'] = info['bytes']
\r
255 response['Content-Type'] = info['content_type']
\r
256 response['Last-Modified'] = http_date(info['last_modified'])
\r
257 for k, v in info['meta'].iteritems():
\r
258 response['X-Object-Meta-%s' % k.capitalize()] = v
\r
263 def object_read(request, v_account, v_container, v_object):
\r
264 # Normal Response Codes: 200, 206
\r
265 # Error Response Codes: serviceUnavailable (503),
\r
266 # rangeNotSatisfiable (416),
\r
267 # preconditionFailed (412),
\r
268 # itemNotFound (404),
\r
269 # unauthorized (401),
\r
270 # badRequest (400),
\r
271 # notModified (304)
\r
273 info = get_object_meta(request.user, v_container, v_object)
\r
275 response = HttpResponse()
\r
276 response['ETag'] = info['hash']
\r
277 response['Content-Type'] = info['content_type']
\r
278 response['Last-Modified'] = http_date(info['last_modified'])
\r
281 range = get_range(request)
\r
282 if range is not None:
\r
283 offset, length = range
\r
286 if offset + length > info['bytes']:
\r
287 raise RangeNotSatisfiable()
\r
289 response['Content-Length'] = length
\r
290 response.status_code = 206
\r
295 response['Content-Length'] = info['bytes']
\r
296 response.status_code = 200
\r
298 # Conditions (according to RFC2616 must be evaluated at the end).
\r
299 if_match = request.META.get('HTTP_IF_MATCH')
\r
300 if if_match is not None and if_match != '*':
\r
301 if info['hash'] not in parse_etags(if_match):
\r
302 raise PreconditionFailed()
\r
304 if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
\r
305 # if if_none_match is not None:
\r
306 # if if_none_match = '*' or info['hash'] in parse_etags(if_none_match):
\r
307 # raise NotModified()
\r
309 if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
\r
310 if if_modified_since is not None:
\r
311 if_modified_since = parse_http_date_safe(if_modified_since)
\r
312 if if_modified_since is not None and info['last_modified'] <= if_modified_since:
\r
313 raise NotModified()
\r
315 if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
\r
316 if if_unmodified_since is not None:
\r
317 if_unmodified_since = parse_http_date_safe(if_unmodified_since)
\r
318 if if_unmodified_since is not None and info['last_modified'] > if_unmodified_since:
\r
319 raise PreconditionFailed()
\r
321 response.content = get_object_data(request.user, v_container, v_object, offset, length)
\r
325 def object_write(request, v_account, v_container, v_object):
\r
326 # Normal Response Codes: 201
\r
327 # Error Response Codes: serviceUnavailable (503),
\r
328 # unprocessableEntity (422),
\r
329 # lengthRequired (411),
\r
330 # itemNotFound (404),
\r
331 # unauthorized (401),
\r
334 copy_from = request.META.get('HTTP_X_COPY_FROM')
\r
336 parts = copy_from.split('/')
\r
337 if len(parts) < 3 or parts[0] != '':
\r
338 raise BadRequest('Bad X-Copy-From path.')
\r
339 copy_container = parts[1]
\r
340 copy_name = '/'.join(parts[2:])
\r
342 info = get_object_meta(request.user, copy_container, copy_name)
\r
344 content_length = request.META.get('CONTENT_LENGTH')
\r
345 content_type = request.META.get('CONTENT_TYPE')
\r
346 if not content_length:
\r
347 raise LengthRequired()
\r
349 info['content_type'] = content_type
\r
351 meta = get_object_meta(request)
\r
352 for k, v in meta.iteritems():
\r
353 info['meta'][k] = v
\r
355 copy_object(request.user, copy_container, copy_name, v_container, v_object)
\r
356 update_object_meta(request.user, v_container, v_object, info)
\r
358 response = HttpResponse(status = 201)
\r
360 content_length = request.META.get('CONTENT_LENGTH')
\r
361 content_type = request.META.get('CONTENT_TYPE')
\r
362 if not content_length or not content_type:
\r
363 raise LengthRequired()
\r
365 meta = get_object_meta(request)
\r
366 info = {'bytes': content_length, 'content_type': content_type, 'meta': meta}
\r
368 etag = request.META.get('HTTP_ETAG')
\r
370 etag = parse_etags(etag)[0] # TODO: Unescape properly.
\r
371 info['hash'] = etag
\r
373 data = request.read()
\r
374 # TODO: Hash function.
\r
375 # etag = hash(data)
\r
376 # if info.get('hash') and info['hash'] != etag:
\r
377 # raise UnprocessableEntity()
\r
379 update_object_data(request.user, v_container, v_name, info, data)
\r
381 response = HttpResponse(status = 201)
\r
382 # response['ETag'] = etag
\r
386 @api_method('COPY')
\r
387 def object_copy(request, v_account, v_container, v_object):
\r
388 # Normal Response Codes: 201
\r
389 # Error Response Codes: serviceUnavailable (503),
\r
390 # itemNotFound (404),
\r
391 # unauthorized (401),
\r
394 destination = request.META.get('HTTP_DESTINATION')
\r
395 if not destination:
\r
396 raise BadRequest('Missing Destination.');
\r
398 parts = destination.split('/')
\r
399 if len(parts) < 3 or parts[0] != '':
\r
400 raise BadRequest('Bad Destination path.')
\r
401 dest_container = parts[1]
\r
402 dest_name = '/'.join(parts[2:])
\r
404 info = get_object_meta(request.user, v_container, v_object)
\r
406 content_type = request.META.get('CONTENT_TYPE')
\r
408 info['content_type'] = content_type
\r
410 meta = get_object_meta(request)
\r
411 for k, v in meta.iteritems():
\r
412 info['meta'][k] = v
\r
414 copy_object(request.user, v_container, v_object, dest_container, dest_name)
\r
415 update_object_meta(request.user, dest_container, dest_name, info)
\r
417 response = HttpResponse(status = 201)
\r
419 @api_method('POST')
\r
420 def object_update(request, v_account, v_container, v_object):
\r
421 # Normal Response Codes: 202
\r
422 # Error Response Codes: serviceUnavailable (503),
\r
423 # itemNotFound (404),
\r
424 # unauthorized (401),
\r
427 meta = get_object_meta(request)
\r
429 update_object_meta(request.user, v_container, v_object, meta)
\r
430 return HttpResponse(status = 202)
\r
432 @api_method('DELETE')
\r
433 def object_delete(request, v_account, v_container, v_object):
\r
434 # Normal Response Codes: 204
\r
435 # Error Response Codes: serviceUnavailable (503),
\r
436 # itemNotFound (404),
\r
437 # unauthorized (401),
\r
440 delete_object(request.user, v_container, v_object)
\r
441 return HttpResponse(status = 204)
\r
444 def method_not_allowed(request):
\r
445 raise BadRequest('Method not allowed.')
\r