Revision 6b6b6c1e
b/docs/source/devguide.rst | ||
---|---|---|
36 | 36 |
\ Always reply with the MD5 in the ETag. |
37 | 37 |
\ Note that ``/login`` will only work if an external authentication system is defined. |
38 | 38 |
\ Include option to ignore Content-Type on ``COPY``/``MOVE``. |
39 |
\ Use format parameter for conflict (409) replies. |
|
39 | 40 |
0.7 (Nov 21, 2011) Suggest upload/download methods using hashmaps. |
40 | 41 |
\ Propose syncing algorithm. |
41 | 42 |
\ Support cross-account object copy and move. |
... | ... | |
840 | 841 |
====================== =================================== |
841 | 842 |
Request Parameter Name Value |
842 | 843 |
====================== =================================== |
843 |
format Optional extended request type (can be ``json`` or ``xml``) |
|
844 |
format Optional extended request/conflict response type (can be ``json`` or ``xml``)
|
|
844 | 845 |
hashmap Optional hashmap provided instead of data (no value parameter) |
845 | 846 |
====================== =================================== |
846 | 847 |
|
847 |
The request is the object's data (or part of it), except if a hashmap is provided (using ``hashmap`` and ``format`` parameters). If using a hashmap and all different parts are stored in the server, the object is created, otherwise the server returns Conflict (409) with the list of the missing parts (in a simple text format, with one hash per line).
|
|
848 |
The request is the object's data (or part of it), except if a hashmap is provided (using ``hashmap`` and ``format`` parameters). If using a hashmap and all different parts are stored in the server, the object is created. Otherwise the server returns Conflict (409) with the list of the missing parts (in simple text format, with one hash per line, or in JSON/XML - depending on the ``format`` parameter).
|
|
848 | 849 |
|
849 | 850 |
Hashmaps should be formatted as outlined in ``GET``. |
850 | 851 |
|
... | ... | |
861 | 862 |
Return Code Description |
862 | 863 |
============================== ============================== |
863 | 864 |
201 (Created) The object has been created |
864 |
409 (Conflict) The object can not be created from the provided hashmap, or there are conflicting permissions (a list of missing hashes, or a list of conflicting sharing paths will be included in the reply - in simple text format)
|
|
865 |
409 (Conflict) The object can not be created from the provided hashmap, or there are conflicting permissions (a list of missing hashes, or a list of conflicting sharing paths will be included in the reply) |
|
865 | 866 |
411 (Length Required) Missing ``Content-Length`` or ``Content-Type`` in the request |
866 | 867 |
413 (Request Entity Too Large) Insufficient quota to complete the request |
867 | 868 |
422 (Unprocessable Entity) The MD5 checksum of the data written to the storage system does not match the (optionally) supplied ETag value |
... | ... | |
871 | 872 |
COPY |
872 | 873 |
"""" |
873 | 874 |
|
874 |
====================== =================================== |
|
875 |
Request Parameter Name Value |
|
876 |
====================== =================================== |
|
877 |
ignore_content_type Ignore the supplied Content-Type |
|
878 |
====================== =================================== |
|
879 |
|
|
880 |
| |
|
881 |
|
|
882 | 875 |
==================== ================================ |
883 | 876 |
Request Header Name Value |
884 | 877 |
==================== ================================ |
... | ... | |
898 | 891 |
|
899 | 892 |
:sup:`*` *When using django locally with the supplied web server, use the ignore_content_type parameter, or do provide a valid Content-Type, as a type of text/plain is applied by default to all requests. Client software should always state ignore_content_type, except when a Content-Type is explicitly defined by the user.* |
900 | 893 |
|
894 |
====================== =================================== |
|
895 |
Request Parameter Name Value |
|
896 |
====================== =================================== |
|
897 |
format Optional conflict response type (can be ``json`` or ``xml``) |
|
898 |
ignore_content_type Ignore the supplied Content-Type |
|
899 |
====================== =================================== |
|
900 |
|
|
901 | 901 |
Refer to ``PUT``/``POST`` for a description of request headers. Metadata is also copied, updated with any values defined. Sharing/publishing options are not copied. |
902 | 902 |
|
903 | 903 |
========================== =============================== |
... | ... | |
912 | 912 |
Return Code Description |
913 | 913 |
============================== ============================== |
914 | 914 |
201 (Created) The object has been created |
915 |
409 (Conflict) There are conflicting permissions (a list of conflicting sharing paths will be included in the reply - in simple text format)
|
|
915 |
409 (Conflict) There are conflicting permissions (a list of conflicting sharing paths will be included in the reply) |
|
916 | 916 |
413 (Request Entity Too Large) Insufficient quota to complete the request |
917 | 917 |
============================== ============================== |
918 | 918 |
|
... | ... | |
952 | 952 |
====================== ============================================ |
953 | 953 |
Request Parameter Name Value |
954 | 954 |
====================== ============================================ |
955 |
format Optional conflict response type (can be ``json`` or ``xml``) |
|
955 | 956 |
update Do not replace metadata (no value parameter) |
956 | 957 |
====================== ============================================ |
957 | 958 |
|
... | ... | |
989 | 990 |
============================== ============================== |
990 | 991 |
202 (Accepted) The request has been accepted (not a data update) |
991 | 992 |
204 (No Content) The request succeeded (data updated) |
992 |
409 (Conflict) There are conflicting permissions (a list of conflicting sharing paths will be included in the reply - in simple text format)
|
|
993 |
409 (Conflict) There are conflicting permissions (a list of conflicting sharing paths will be included in the reply) |
|
993 | 994 |
411 (Length Required) Missing ``Content-Length`` in the request |
994 | 995 |
413 (Request Entity Too Large) Insufficient quota to complete the request |
995 | 996 |
416 (Range Not Satisfiable) The supplied range is invalid |
b/pithos/api/functions.py | ||
---|---|---|
46 | 46 |
|
47 | 47 |
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound, Conflict, |
48 | 48 |
LengthRequired, PreconditionFailed, RequestEntityTooLarge, RangeNotSatisfiable, UnprocessableEntity) |
49 |
from pithos.api.util import (rename_meta_key, format_header_key, printable_header_dict, get_account_headers,
|
|
50 |
put_account_headers, get_container_headers, put_container_headers, get_object_headers, put_object_headers,
|
|
51 |
update_manifest_meta, update_sharing_meta, update_public_meta, validate_modification_preconditions,
|
|
52 |
validate_matching_preconditions, split_container_object_string, copy_or_move_object,
|
|
53 |
get_int_parameter, get_content_length, get_content_range, socket_read_iterator, SaveToBackendHandler,
|
|
54 |
object_data_response, put_object_block, hashmap_md5, api_method, json_encode_decimal)
|
|
49 |
from pithos.api.util import (json_encode_decimal, rename_meta_key, format_header_key, printable_header_dict,
|
|
50 |
get_account_headers, put_account_headers, get_container_headers, put_container_headers, get_object_headers,
|
|
51 |
put_object_headers, update_manifest_meta, update_sharing_meta, update_public_meta,
|
|
52 |
validate_modification_preconditions, validate_matching_preconditions, split_container_object_string,
|
|
53 |
copy_or_move_object, get_int_parameter, get_content_length, get_content_range, socket_read_iterator,
|
|
54 |
SaveToBackendHandler, object_data_response, put_object_block, hashmap_md5, object_conflict_response, api_method)
|
|
55 | 55 |
from pithos.backends.base import NotAllowedError, QuotaError |
56 | 56 |
|
57 | 57 |
|
... | ... | |
853 | 853 |
except NotAllowedError: |
854 | 854 |
raise Forbidden('Not allowed') |
855 | 855 |
except IndexError, e: |
856 |
raise Conflict('\n'.join(e.data) + '\n')
|
|
856 |
raise Conflict(object_conflict_response(request, e.data))
|
|
857 | 857 |
except NameError: |
858 | 858 |
raise ItemNotFound('Container does not exist') |
859 | 859 |
except ValueError: |
860 | 860 |
raise BadRequest('Invalid sharing header') |
861 | 861 |
except AttributeError, e: |
862 |
raise Conflict('\n'.join(e.data) + '\n')
|
|
862 |
raise Conflict(object_conflict_response(request, e.data))
|
|
863 | 863 |
except QuotaError: |
864 | 864 |
raise RequestEntityTooLarge('Quota exceeded') |
865 | 865 |
if 'ETag' not in meta: |
... | ... | |
919 | 919 |
response['X-Object-Version'] = version_id |
920 | 920 |
return response |
921 | 921 |
|
922 |
@api_method('COPY') |
|
922 |
@api_method('COPY', format_allowed=True)
|
|
923 | 923 |
def object_copy(request, v_account, v_container, v_object): |
924 | 924 |
# Normal Response Codes: 201 |
925 | 925 |
# Error Response Codes: internalServerError (500), |
... | ... | |
956 | 956 |
response['X-Object-Version'] = version_id |
957 | 957 |
return response |
958 | 958 |
|
959 |
@api_method('MOVE') |
|
959 |
@api_method('MOVE', format_allowed=True)
|
|
960 | 960 |
def object_move(request, v_account, v_container, v_object): |
961 | 961 |
# Normal Response Codes: 201 |
962 | 962 |
# Error Response Codes: internalServerError (500), |
... | ... | |
992 | 992 |
response['X-Object-Version'] = version_id |
993 | 993 |
return response |
994 | 994 |
|
995 |
@api_method('POST') |
|
995 |
@api_method('POST', format_allowed=True)
|
|
996 | 996 |
def object_update(request, v_account, v_container, v_object): |
997 | 997 |
# Normal Response Codes: 202, 204 |
998 | 998 |
# Error Response Codes: internalServerError (500), |
... | ... | |
1044 | 1044 |
except ValueError: |
1045 | 1045 |
raise BadRequest('Invalid sharing header') |
1046 | 1046 |
except AttributeError, e: |
1047 |
raise Conflict('\n'.join(e.data) + '\n')
|
|
1047 |
raise Conflict(object_conflict_response(request, e.data))
|
|
1048 | 1048 |
if public is not None: |
1049 | 1049 |
try: |
1050 | 1050 |
request.backend.update_object_public(request.user_uniq, v_account, |
... | ... | |
1190 | 1190 |
except ValueError: |
1191 | 1191 |
raise BadRequest('Invalid sharing header') |
1192 | 1192 |
except AttributeError, e: |
1193 |
raise Conflict('\n'.join(e.data) + '\n')
|
|
1193 |
raise Conflict(object_conflict_response(request, e.data))
|
|
1194 | 1194 |
except QuotaError: |
1195 | 1195 |
raise RequestEntityTooLarge('Quota exceeded') |
1196 | 1196 |
if public is not None: |
b/pithos/api/templates/conflicts.xml | ||
---|---|---|
1 |
<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
|
|
3 |
<conflicts> |
|
4 |
{% for conflict in conflicts %} |
|
5 |
<conflict>{{ conflict }}</conflict> |
|
6 |
{% endfor %} |
|
7 |
</conflicts> |
b/pithos/api/util.py | ||
---|---|---|
41 | 41 |
|
42 | 42 |
from django.conf import settings |
43 | 43 |
from django.http import HttpResponse |
44 |
from django.template.loader import render_to_string |
|
44 | 45 |
from django.utils import simplejson as json |
45 | 46 |
from django.utils.http import http_date, parse_etags |
46 | 47 |
from django.utils.encoding import smart_unicode, smart_str |
... | ... | |
322 | 323 |
except ValueError: |
323 | 324 |
raise BadRequest('Invalid sharing header') |
324 | 325 |
except AttributeError, e: |
325 |
raise Conflict('\n'.join(e.data) + '\n')
|
|
326 |
raise Conflict(object_conflict_response(request, e.data))
|
|
326 | 327 |
except QuotaError: |
327 | 328 |
raise RequestEntityTooLarge('Quota exceeded') |
328 | 329 |
if public is not None: |
... | ... | |
758 | 759 |
md5.update(data + ('\x00' * pad)) |
759 | 760 |
return md5.hexdigest().lower() |
760 | 761 |
|
762 |
def object_conflict_response(request, l): |
|
763 |
if request.serialization == 'text': |
|
764 |
return '\n'.join(l) + '\n' |
|
765 |
if request.serialization == 'xml': |
|
766 |
return render_to_string('conflicts.xml', {'conflicts': l}) |
|
767 |
if request.serialization == 'json': |
|
768 |
return json.dumps(l) |
|
769 |
|
|
761 | 770 |
def get_backend(): |
762 | 771 |
backend = connect_backend(db_module=settings.BACKEND_DB_MODULE, |
763 | 772 |
db_connection=settings.BACKEND_DB_CONNECTION, |
b/pithos/lib/transfer.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
import os |
35 | 35 |
import types |
36 |
import json |
|
36 | 37 |
|
37 | 38 |
from hashmap import HashMap |
38 | 39 |
from binascii import hexlify, unhexlify |
... | ... | |
64 | 65 |
return v |
65 | 66 |
|
66 | 67 |
if type(fault.data) == types.StringType: |
67 |
missing = fault.data.split('\n')
|
|
68 |
missing = json.loads(fault.data)
|
|
68 | 69 |
elif type(fault.data) == types.ListType: |
69 | 70 |
missing = fault.data |
70 | 71 |
|
Also available in: Unified diff