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