+++ /dev/null
-#\r
-# Copyright (c) 2011 Greek Research and Technology Network\r
-#\r
-\r
-from django.http import HttpResponse\r
-from django.template.loader import render_to_string\r
-from django.utils import simplejson as json\r
-from django.utils.http import http_date, parse_etags\r
-\r
-try:\r
- from django.utils.http import parse_http_date_safe\r
-except:\r
- from pithos.api.util import parse_http_date_safe\r
-\r
-from pithos.api.faults import Fault, NotModified, BadRequest, Unauthorized, ItemNotFound, LengthRequired, PreconditionFailed, RangeNotSatisfiable, UnprocessableEntity\r
-from pithos.api.util import get_meta, get_range, api_method\r
-\r
-from settings import PROJECT_PATH\r
-from os import path\r
-STORAGE_PATH = path.join(PROJECT_PATH, 'data')\r
-\r
-from pithos.backends.dummy import BackEnd\r
-\r
-import logging\r
-\r
-logging.basicConfig(level=logging.DEBUG)\r
-\r
-@api_method('GET')\r
-def authenticate(request):\r
- # Normal Response Codes: 204\r
- # Error Response Codes: serviceUnavailable (503),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- x_auth_user = request.META.get('HTTP_X_AUTH_USER')\r
- x_auth_key = request.META.get('HTTP_X_AUTH_KEY')\r
- \r
- if not x_auth_user or not x_auth_key:\r
- raise BadRequest('Missing auth user or key.')\r
- \r
- response = HttpResponse(status = 204)\r
- response['X-Auth-Token'] = 'eaaafd18-0fed-4b3a-81b4-663c99ec1cbb'\r
- # TODO: Must support X-Storage-Url to be compatible.\r
- response['X-Storage-Url'] = 'http://127.0.0.1:8000/v1/asdf'\r
- return response\r
-\r
-def account_demux(request, v_account):\r
- if request.method == 'HEAD':\r
- return account_meta(request, v_account)\r
- elif request.method == 'GET':\r
- return container_list(request, v_account)\r
- elif request.method == 'POST':\r
- return account_update(request, v_account)\r
- else:\r
- return method_not_allowed(request)\r
-\r
-def container_demux(request, v_account, v_container):\r
- if request.method == 'HEAD':\r
- return container_meta(request, v_account, v_container)\r
- elif request.method == 'GET':\r
- return object_list(request, v_account, v_container)\r
- elif request.method == 'PUT':\r
- return container_create(request, v_account, v_container)\r
- elif request.method == 'POST':\r
- return container_update(request, v_account, v_container)\r
- elif request.method == 'DELETE':\r
- return container_delete(request, v_account, v_container)\r
- else:\r
- return method_not_allowed(request)\r
-\r
-def object_demux(request, v_account, v_container, v_object):\r
- if request.method == 'HEAD':\r
- return object_meta(request, v_account, v_container, v_object)\r
- elif request.method == 'GET':\r
- return object_read(request, v_account, v_container, v_object)\r
- elif request.method == 'PUT':\r
- return object_write(request, v_account, v_container, v_object)\r
- elif request.method == 'COPY':\r
- return object_copy(request, v_account, v_container, v_object)\r
- elif request.method == 'POST':\r
- return object_update(request, v_account, v_container, v_object)\r
- elif request.method == 'DELETE':\r
- return object_delete(request, v_account, v_container, v_object)\r
- else:\r
- return method_not_allowed(request)\r
-\r
-@api_method('HEAD')\r
-def account_meta(request, v_account):\r
- # Normal Response Codes: 204\r
- # Error Response Codes: serviceUnavailable (503),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- info = be.get_account_meta(request.user)\r
- except NameError:\r
- info = {'count': 0, 'bytes': 0}\r
- \r
- response = HttpResponse(status = 204)\r
- response['X-Account-Container-Count'] = info['count']\r
- response['X-Account-Bytes-Used'] = info['bytes']\r
- for k in [x for x in info.keys() if x.startswith('X-Account-Meta-')]:\r
- response[k] = info[k]\r
- \r
- return response\r
-\r
-@api_method('POST')\r
-def account_update(request, v_account):\r
- # Normal Response Codes: 202\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- meta = get_meta(request, 'X-Account-Meta-')\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- be.update_account_meta(request.user, meta)\r
- \r
- return HttpResponse(status = 202)\r
-\r
-@api_method('GET', format_allowed = True)\r
-def container_list(request, v_account):\r
- # Normal Response Codes: 200, 204\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- marker = request.GET.get('marker')\r
- limit = request.GET.get('limit')\r
- if limit:\r
- try:\r
- limit = int(limit)\r
- except ValueError:\r
- limit = 10000\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- containers = be.list_containers(request.user, marker, limit)\r
- except NameError:\r
- containers = []\r
- # TODO: The cloudfiles python bindings expect 200 if json/xml.\r
- if len(containers) == 0:\r
- return HttpResponse(status = 204)\r
- \r
- if request.serialization == 'text':\r
- return HttpResponse('\n'.join(containers), status = 200)\r
- \r
- # TODO: Do this with a backend parameter?\r
- try:\r
- containers = [be.get_container_meta(request.user, x) for x in containers]\r
- except NameError:\r
- raise ItemNotFound()\r
- # TODO: Format dates.\r
- if request.serialization == 'xml':\r
- data = render_to_string('containers.xml', {'account': request.user, 'containers': containers})\r
- elif request.serialization == 'json':\r
- data = json.dumps(containers)\r
- return HttpResponse(data, status = 200)\r
-\r
-@api_method('HEAD')\r
-def container_meta(request, v_account, v_container):\r
- # Normal Response Codes: 204\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- info = be.get_container_meta(request.user, v_container)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- response = HttpResponse(status = 204)\r
- response['X-Container-Object-Count'] = info['count']\r
- response['X-Container-Bytes-Used'] = info['bytes']\r
- for k in [x for x in info.keys() if x.startswith('X-Container-Meta-')]:\r
- response[k] = info[k]\r
- \r
- return response\r
-\r
-@api_method('PUT')\r
-def container_create(request, v_account, v_container):\r
- # Normal Response Codes: 201, 202\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- meta = get_meta(request, 'X-Container-Meta-')\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- be.create_container(request.user, v_container)\r
- ret = 201\r
- except NameError:\r
- ret = 202\r
- \r
- if len(meta) > 0:\r
- be.update_container_meta(request.user, v_container, meta)\r
- \r
- return HttpResponse(status = ret)\r
-\r
-@api_method('POST')\r
-def container_update(request, v_account, v_container):\r
- # Normal Response Codes: 202\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- meta = get_meta(request, 'X-Container-Meta-')\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- be.update_container_meta(request.user, v_container, meta)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- return HttpResponse(status = 202)\r
-\r
-@api_method('DELETE')\r
-def container_delete(request, v_account, v_container):\r
- # Normal Response Codes: 204\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- info = be.get_container_meta(request.user, v_container)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- if info['count'] > 0:\r
- return HttpResponse(status = 409)\r
- \r
- # TODO: Handle both exceptions.\r
- try:\r
- be.delete_container(request.user, v_container)\r
- except:\r
- raise ItemNotFound()\r
- return HttpResponse(status = 204)\r
-\r
-@api_method('GET', format_allowed = True)\r
-def object_list(request, v_account, v_container):\r
- # Normal Response Codes: 200, 204\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- path = request.GET.get('path')\r
- prefix = request.GET.get('prefix')\r
- delimiter = request.GET.get('delimiter')\r
- \r
- # Path overrides prefix and delimiter.\r
- if path:\r
- prefix = path\r
- delimiter = '/'\r
- # Naming policy.\r
- if prefix and delimiter:\r
- prefix = prefix + delimiter\r
- if not prefix:\r
- prefix = ''\r
- \r
- marker = request.GET.get('marker')\r
- limit = request.GET.get('limit')\r
- if limit:\r
- try:\r
- limit = int(limit)\r
- except ValueError:\r
- limit = 10000\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- objects = be.list_objects(request.user, v_container, prefix, delimiter, marker, limit)\r
- except NameError:\r
- raise ItemNotFound()\r
- # TODO: The cloudfiles python bindings expect 200 if json/xml.\r
- if len(objects) == 0:\r
- return HttpResponse(status = 204)\r
- \r
- if request.serialization == 'text':\r
- return HttpResponse('\n'.join(objects), status = 200)\r
- \r
- # TODO: Do this with a backend parameter?\r
- try:\r
- objects = [be.get_object_meta(request.user, v_container, x) for x in objects]\r
- except NameError:\r
- raise ItemNotFound()\r
- # TODO: Format dates.\r
- if request.serialization == 'xml':\r
- data = render_to_string('objects.xml', {'container': v_container, 'objects': objects})\r
- elif request.serialization == 'json':\r
- data = json.dumps(objects)\r
- return HttpResponse(data, status = 200)\r
-\r
-@api_method('HEAD')\r
-def object_meta(request, v_account, v_container, v_object):\r
- # Normal Response Codes: 204\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- info = be.get_object_meta(request.user, v_container, v_object)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- response = HttpResponse(status = 204)\r
- response['ETag'] = info['hash']\r
- response['Content-Length'] = info['bytes']\r
- response['Content-Type'] = info['content_type']\r
- response['Last-Modified'] = http_date(info['last_modified'])\r
- for k in [x for x in info.keys() if x.startswith('X-Object-Meta-')]:\r
- response[k] = info[k]\r
- \r
- return response\r
-\r
-@api_method('GET')\r
-def object_read(request, v_account, v_container, v_object):\r
- # Normal Response Codes: 200, 206\r
- # Error Response Codes: serviceUnavailable (503),\r
- # rangeNotSatisfiable (416),\r
- # preconditionFailed (412),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400),\r
- # notModified (304)\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- info = be.get_object_meta(request.user, v_container, v_object)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- # TODO: Check if the cloudfiles python bindings expect hash/content_type/last_modified on range requests.\r
- response = HttpResponse()\r
- response['ETag'] = info['hash']\r
- response['Content-Type'] = info['content_type']\r
- response['Last-Modified'] = http_date(info['last_modified'])\r
- \r
- # Range handling.\r
- range = get_range(request)\r
- if range is not None:\r
- offset, length = range\r
- if length:\r
- if offset + length > info['bytes']:\r
- raise RangeNotSatisfiable()\r
- else:\r
- if offset > info['bytes']:\r
- raise RangeNotSatisfiable()\r
- if not length:\r
- length = -1\r
- \r
- response['Content-Length'] = length \r
- response.status_code = 206\r
- else:\r
- offset = 0\r
- length = -1\r
- \r
- response['Content-Length'] = info['bytes']\r
- response.status_code = 200\r
- \r
- # Conditions (according to RFC2616 must be evaluated at the end).\r
- # TODO: Check etag/date conditions.\r
- if_match = request.META.get('HTTP_IF_MATCH')\r
- if if_match is not None and if_match != '*':\r
- if info['hash'] not in parse_etags(if_match):\r
- raise PreconditionFailed()\r
- \r
- if_none_match = request.META.get('HTTP_IF_NONE_MATCH')\r
- if if_none_match is not None:\r
- if if_none_match == '*' or info['hash'] in parse_etags(if_none_match):\r
- raise NotModified()\r
- \r
- if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')\r
- if if_modified_since is not None:\r
- if_modified_since = parse_http_date_safe(if_modified_since)\r
- if if_modified_since is not None and info['last_modified'] <= if_modified_since:\r
- raise NotModified()\r
-\r
- if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')\r
- if if_unmodified_since is not None:\r
- if_unmodified_since = parse_http_date_safe(if_unmodified_since)\r
- if if_unmodified_since is not None and info['last_modified'] > if_unmodified_since:\r
- raise PreconditionFailed()\r
- \r
- try:\r
- response.content = be.get_object(request.user, v_container, v_object, offset, length)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- return response\r
-\r
-@api_method('PUT')\r
-def object_write(request, v_account, v_container, v_object):\r
- # Normal Response Codes: 201\r
- # Error Response Codes: serviceUnavailable (503),\r
- # unprocessableEntity (422),\r
- # lengthRequired (411),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- \r
- copy_from = request.META.get('HTTP_X_COPY_FROM')\r
- if copy_from:\r
- parts = copy_from.split('/')\r
- if len(parts) < 3 or parts[0] != '':\r
- raise BadRequest('Bad X-Copy-From path.')\r
- copy_container = parts[1]\r
- copy_name = '/'.join(parts[2:])\r
- \r
- try:\r
- info = be.get_object_meta(request.user, copy_container, copy_name)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- content_length = request.META.get('CONTENT_LENGTH')\r
- content_type = request.META.get('CONTENT_TYPE')\r
- # TODO: Why is this required? Copy this ammount?\r
- if not content_length:\r
- raise LengthRequired()\r
- if content_type:\r
- info['content_type'] = content_type\r
- \r
- meta = get_meta(request, 'X-Object-Meta-')\r
- info.update(meta)\r
- \r
- try:\r
- be.copy_object(request.user, copy_container, copy_name, v_container, v_object)\r
- be.update_object_meta(request.user, v_container, v_object, info)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- response = HttpResponse(status = 201)\r
- else:\r
- content_length = request.META.get('CONTENT_LENGTH')\r
- content_type = request.META.get('CONTENT_TYPE')\r
- if not content_length or not content_type:\r
- raise LengthRequired()\r
- \r
- info = {'content_type': content_type}\r
- meta = get_meta(request, 'X-Object-Meta-')\r
- info.update(meta)\r
- \r
- data = request.raw_post_data\r
- try:\r
- be.update_object(request.user, v_container, v_object, data)\r
- be.update_object_meta(request.user, v_container, v_object, info)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- # TODO: Check before update?\r
- info = be.get_object_meta(request.user, v_container, v_object)\r
- etag = request.META.get('HTTP_ETAG')\r
- if etag:\r
- etag = parse_etags(etag)[0] # TODO: Unescape properly.\r
- if etag != info['hash']:\r
- be.delete_object(request.user, v_container, v_object)\r
- raise UnprocessableEntity()\r
- \r
- response = HttpResponse(status = 201)\r
- response['ETag'] = info['hash']\r
- \r
- return response\r
-\r
-@api_method('COPY')\r
-def object_copy(request, v_account, v_container, v_object):\r
- # Normal Response Codes: 201\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- destination = request.META.get('HTTP_DESTINATION')\r
- if not destination:\r
- raise BadRequest('Missing Destination.');\r
- \r
- parts = destination.split('/')\r
- if len(parts) < 3 or parts[0] != '':\r
- raise BadRequest('Bad Destination path.')\r
- dest_container = parts[1]\r
- dest_name = '/'.join(parts[2:])\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- info = be.get_object_meta(request.user, v_container, v_object)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- content_type = request.META.get('CONTENT_TYPE')\r
- if content_type:\r
- info['content_type'] = content_type\r
- meta = get_meta(request, 'X-Object-Meta-')\r
- info.update(meta)\r
- \r
- try:\r
- be.copy_object(request.user, v_container, v_object, dest_container, dest_name)\r
- be.update_object_meta(request.user, dest_container, dest_name, info)\r
- except NameError:\r
- raise ItemNotFound()\r
- \r
- response = HttpResponse(status = 201)\r
-\r
-@api_method('POST')\r
-def object_update(request, v_account, v_container, v_object):\r
- # Normal Response Codes: 202\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- return HttpResponse(status = 202)\r
-\r
-@api_method('DELETE')\r
-def object_delete(request, v_account, v_container, v_object):\r
- # Normal Response Codes: 204\r
- # Error Response Codes: serviceUnavailable (503),\r
- # itemNotFound (404),\r
- # unauthorized (401),\r
- # badRequest (400)\r
- \r
- be = BackEnd(STORAGE_PATH)\r
- try:\r
- be.delete_object(request.user, v_container, v_object)\r
- except NameError:\r
- raise ItemNotFound()\r
- return HttpResponse(status = 204)\r
-\r
-@api_method()\r
-def method_not_allowed(request):\r
- raise BadRequest('Method not allowed.')\r