Use format parameter for Conflict (409) replies.
authorAntony Chazapis <chazapis@gmail.com>
Tue, 24 Jan 2012 15:23:23 +0000 (17:23 +0200)
committerAntony Chazapis <chazapis@gmail.com>
Tue, 24 Jan 2012 15:23:23 +0000 (17:23 +0200)
Fixes #1934

docs/source/devguide.rst
pithos/api/functions.py
pithos/api/templates/conflicts.xml [new file with mode: 0644]
pithos/api/util.py
pithos/lib/transfer.py

index c781670..2ad3688 100644 (file)
@@ -36,6 +36,7 @@ Revision                   Description
 \                          Always reply with the MD5 in the ETag.
 \                          Note that ``/login`` will only work if an external authentication system is defined.
 \                          Include option to ignore Content-Type on ``COPY``/``MOVE``.
+\                          Use format parameter for conflict (409) replies.
 0.7 (Nov 21, 2011)         Suggest upload/download methods using hashmaps.
 \                          Propose syncing algorithm.
 \                          Support cross-account object copy and move.
@@ -840,11 +841,11 @@ X-Object-Meta-*       Optional user defined metadata
 ======================  ===================================
 Request Parameter Name  Value
 ======================  ===================================
-format                  Optional extended request type (can be ``json`` or ``xml``)
+format                  Optional extended request/conflict response type (can be ``json`` or ``xml``)
 hashmap                 Optional hashmap provided instead of data (no value parameter)
 ======================  ===================================
 
-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).
+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).
 
 Hashmaps should be formatted as outlined in ``GET``.
 
@@ -861,7 +862,7 @@ The ``X-Object-Sharing`` header may include either a ``read=...`` comma-separate
 Return Code                     Description
 ==============================  ==============================
 201 (Created)                   The object has been created
-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)
+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)
 411 (Length Required)           Missing ``Content-Length`` or ``Content-Type`` in the request
 413 (Request Entity Too Large)  Insufficient quota to complete the request
 422 (Unprocessable Entity)      The MD5 checksum of the data written to the storage system does not match the (optionally) supplied ETag value
@@ -871,14 +872,6 @@ Return Code                     Description
 COPY
 """"
 
-======================  ===================================
-Request Parameter Name  Value
-======================  ===================================
-ignore_content_type     Ignore the supplied Content-Type
-======================  ===================================
-
-|
-
 ====================  ================================
 Request Header Name   Value
 ====================  ================================
@@ -898,6 +891,13 @@ X-Object-Meta-*       Optional user defined metadata
 
 :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.*
 
+======================  ===================================
+Request Parameter Name  Value
+======================  ===================================
+format                  Optional conflict response type (can be ``json`` or ``xml``)
+ignore_content_type     Ignore the supplied Content-Type
+======================  ===================================
+
 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.
 
 ==========================  ===============================
@@ -912,7 +912,7 @@ X-Object-Version            The object's new version
 Return Code                     Description
 ==============================  ==============================
 201 (Created)                   The object has been created
-409 (Conflict)                  There are conflicting permissions (a list of conflicting sharing paths will be included in the reply - in simple text format)
+409 (Conflict)                  There are conflicting permissions (a list of conflicting sharing paths will be included in the reply)
 413 (Request Entity Too Large)  Insufficient quota to complete the request
 ==============================  ==============================
 
@@ -952,6 +952,7 @@ X-Object-Meta-*       Optional user defined metadata
 ======================  ============================================
 Request Parameter Name  Value
 ======================  ============================================
+format                  Optional conflict response type (can be ``json`` or ``xml``)
 update                  Do not replace metadata (no value parameter)
 ======================  ============================================
 
@@ -989,7 +990,7 @@ Return Code                     Description
 ==============================  ==============================
 202 (Accepted)                  The request has been accepted (not a data update)
 204 (No Content)                The request succeeded (data updated)
-409 (Conflict)                  There are conflicting permissions (a list of conflicting sharing paths will be included in the reply - in simple text format)
+409 (Conflict)                  There are conflicting permissions (a list of conflicting sharing paths will be included in the reply)
 411 (Length Required)           Missing ``Content-Length`` in the request
 413 (Request Entity Too Large)  Insufficient quota to complete the request
 416 (Range Not Satisfiable)     The supplied range is invalid
index 0dfb1ce..f7e3adf 100644 (file)
@@ -46,12 +46,12 @@ from pithos.lib.filter import parse_filters
 
 from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound, Conflict,
     LengthRequired, PreconditionFailed, RequestEntityTooLarge, RangeNotSatisfiable, UnprocessableEntity)
-from pithos.api.util import (rename_meta_key, format_header_key, printable_header_dict, get_account_headers,
-    put_account_headers, get_container_headers, put_container_headers, get_object_headers, put_object_headers,
-    update_manifest_meta, update_sharing_meta, update_public_meta, validate_modification_preconditions,
-    validate_matching_preconditions, split_container_object_string, copy_or_move_object,
-    get_int_parameter, get_content_length, get_content_range, socket_read_iterator, SaveToBackendHandler,
-    object_data_response, put_object_block, hashmap_md5, api_method, json_encode_decimal)
+from pithos.api.util import (json_encode_decimal, rename_meta_key, format_header_key, printable_header_dict,
+    get_account_headers, put_account_headers, get_container_headers, put_container_headers, get_object_headers,
+    put_object_headers, update_manifest_meta, update_sharing_meta, update_public_meta,
+    validate_modification_preconditions, validate_matching_preconditions, split_container_object_string,
+    copy_or_move_object, get_int_parameter, get_content_length, get_content_range, socket_read_iterator,
+    SaveToBackendHandler, object_data_response, put_object_block, hashmap_md5, object_conflict_response, api_method)
 from pithos.backends.base import NotAllowedError, QuotaError
 
 
@@ -853,13 +853,13 @@ def object_write(request, v_account, v_container, v_object):
     except NotAllowedError:
         raise Forbidden('Not allowed')
     except IndexError, e:
-        raise Conflict('\n'.join(e.data) + '\n')
+        raise Conflict(object_conflict_response(request, e.data))
     except NameError:
         raise ItemNotFound('Container does not exist')
     except ValueError:
         raise BadRequest('Invalid sharing header')
     except AttributeError, e:
-        raise Conflict('\n'.join(e.data) + '\n')
+        raise Conflict(object_conflict_response(request, e.data))
     except QuotaError:
         raise RequestEntityTooLarge('Quota exceeded')
     if 'ETag' not in meta:
@@ -919,7 +919,7 @@ def object_write_form(request, v_account, v_container, v_object):
     response['X-Object-Version'] = version_id
     return response
 
-@api_method('COPY')
+@api_method('COPY', format_allowed=True)
 def object_copy(request, v_account, v_container, v_object):
     # Normal Response Codes: 201
     # Error Response Codes: internalServerError (500),
@@ -956,7 +956,7 @@ def object_copy(request, v_account, v_container, v_object):
     response['X-Object-Version'] = version_id
     return response
 
-@api_method('MOVE')
+@api_method('MOVE', format_allowed=True)
 def object_move(request, v_account, v_container, v_object):
     # Normal Response Codes: 201
     # Error Response Codes: internalServerError (500),
@@ -992,7 +992,7 @@ def object_move(request, v_account, v_container, v_object):
     response['X-Object-Version'] = version_id
     return response
 
-@api_method('POST')
+@api_method('POST', format_allowed=True)
 def object_update(request, v_account, v_container, v_object):
     # Normal Response Codes: 202, 204
     # Error Response Codes: internalServerError (500),
@@ -1044,7 +1044,7 @@ def object_update(request, v_account, v_container, v_object):
             except ValueError:
                 raise BadRequest('Invalid sharing header')
             except AttributeError, e:
-                raise Conflict('\n'.join(e.data) + '\n')
+                raise Conflict(object_conflict_response(request, e.data))
         if public is not None:
             try:
                 request.backend.update_object_public(request.user_uniq, v_account,
@@ -1190,7 +1190,7 @@ def object_update(request, v_account, v_container, v_object):
     except ValueError:
         raise BadRequest('Invalid sharing header')
     except AttributeError, e:
-        raise Conflict('\n'.join(e.data) + '\n')
+        raise Conflict(object_conflict_response(request, e.data))
     except QuotaError:
         raise RequestEntityTooLarge('Quota exceeded')
     if public is not None:
diff --git a/pithos/api/templates/conflicts.xml b/pithos/api/templates/conflicts.xml
new file mode 100644 (file)
index 0000000..8353cb5
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<conflicts>
+  {% for conflict in conflicts %}
+  <conflict>{{ conflict }}</conflict>
+  {% endfor %}
+</conflicts>
index 4e3d437..5c8f076 100644 (file)
@@ -41,6 +41,7 @@ from urllib import quote, unquote
 
 from django.conf import settings
 from django.http import HttpResponse
+from django.template.loader import render_to_string
 from django.utils import simplejson as json
 from django.utils.http import http_date, parse_etags
 from django.utils.encoding import smart_unicode, smart_str
@@ -322,7 +323,7 @@ def copy_or_move_object(request, src_account, src_container, src_name, dest_acco
     except ValueError:
         raise BadRequest('Invalid sharing header')
     except AttributeError, e:
-        raise Conflict('\n'.join(e.data) + '\n')
+        raise Conflict(object_conflict_response(request, e.data))
     except QuotaError:
         raise RequestEntityTooLarge('Quota exceeded')
     if public is not None:
@@ -758,6 +759,14 @@ def hashmap_md5(request, hashmap, size):
         md5.update(data + ('\x00' * pad))
     return md5.hexdigest().lower()
 
+def object_conflict_response(request, l):
+    if request.serialization == 'text':
+        return '\n'.join(l) + '\n'
+    if request.serialization == 'xml':
+        return render_to_string('conflicts.xml', {'conflicts': l})
+    if request.serialization == 'json':
+        return json.dumps(l)
+
 def get_backend():
     backend = connect_backend(db_module=settings.BACKEND_DB_MODULE,
                               db_connection=settings.BACKEND_DB_CONNECTION,
index 2feecb1..7ad1527 100644 (file)
@@ -33,6 +33,7 @@
 
 import os
 import types
+import json
 
 from hashmap import HashMap
 from binascii import hexlify, unhexlify
@@ -64,7 +65,7 @@ def upload(client, path, container, prefix, name=None, mimetype=None):
         return v
     
     if type(fault.data) == types.StringType:
-        missing = fault.data.split('\n')
+        missing = json.loads(fault.data)
     elif type(fault.data) == types.ListType:
         missing = fault.data