Bug fixes.
authorAntony Chazapis <chazapis@gmail.com>
Sun, 19 Jun 2011 12:52:20 +0000 (15:52 +0300)
committerAntony Chazapis <chazapis@gmail.com>
Sun, 19 Jun 2011 12:52:20 +0000 (15:52 +0300)
* Empty object update.
* Parameter version equal to 'list' in requests.
* Object HEAD response code (should be 200).
* Remove source version parameter when moving with PUT/MOVE.
* Mark 'X-Object-Public' as TBD in the docs.

docs/source/devguide.rst
pithos/api/functions.py
pithos/api/util.py
pithos/backends/base.py
pithos/backends/simple.py

index ff48bcb..53b794c 100644 (file)
@@ -295,7 +295,7 @@ last_modified               The last object modification date (regardless of ver
 x_object_version            The object's version identifier
 x_object_version_timestamp  The object's version timestamp
 x_object_manifest           Object parts prefix in ``<container>/<object>`` form (optional)
-x_object_public             Object is publicly accessible (optional)
+x_object_public             Object is publicly accessible (optional) (**TBD**)
 x_object_meta_*             Optional user defined metadata
 ==========================  ======================================
 
@@ -413,7 +413,7 @@ Content-Disposition         The presentation style of the object (optional)
 X-Object-Version            The object's version identifier
 X-Object-Version-Timestamp  The object's version timestamp
 X-Object-Manifest           Object parts prefix in ``<container>/<object>`` form (optional)
-X-Object-Public             Object is publicly accessible (optional)
+X-Object-Public             Object is publicly accessible (optional) (**TBD**)
 X-Object-Meta-*             Optional user defined metadata
 ==========================  ===============================
 
@@ -422,7 +422,7 @@ X-Object-Meta-*             Optional user defined metadata
 ================  ===============================
 Return Code       Description
 ================  ===============================
-204 (No Content)  The request succeeded
+200 (No Content)  The request succeeded
 ================  ===============================
 
 
@@ -502,7 +502,7 @@ Content-Disposition         The presentation style of the object (optional)
 X-Object-Version            The object's version identifier
 X-Object-Version-Timestamp  The object's version timestamp
 X-Object-Manifest           Object parts prefix in ``<container>/<object>`` form (optional)
-X-Object-Public             Object is publicly accessible (optional)
+X-Object-Public             Object is publicly accessible (optional) (**TBD**)
 X-Object-Meta-*             Optional user defined metadata
 ==========================  ===============================
 
@@ -531,11 +531,11 @@ Content-Type          The MIME content type of the object
 Transfer-Encoding     Set to ``chunked`` to specify incremental uploading (if used, ``Content-Length`` is ignored)
 X-Copy-From           The source path in the form ``/<container>/<object>``
 X-Move-From           The source path in the form ``/<container>/<object>``
-X-Source-Version      The source version to copy/move from
+X-Source-Version      The source version to copy from
 Content-Encoding      The encoding of the object (optional)
 Content-Disposition   The presentation style of the object (optional)
 X-Object-Manifest     Object parts prefix in ``<container>/<object>`` form (optional)
-X-Object-Public       Object is publicly accessible (optional)
+X-Object-Public       Object is publicly accessible (optional) (**TBD**)
 X-Object-Meta-*       Optional user defined metadata
 ====================  ================================
 
@@ -568,9 +568,9 @@ Destination           The destination path in the form ``/<container>/<object>``
 Content-Type          The MIME content type of the object (optional)
 Content-Encoding      The encoding of the object (optional)
 Content-Disposition   The presentation style of the object (optional)
-X-Source-Version      The source version to copy/move from
+X-Source-Version      The source version to copy from
 X-Object-Manifest     Object parts prefix in ``<container>/<object>`` form (optional)
-X-Object-Public       Object is publicly accessible (optional)
+X-Object-Public       Object is publicly accessible (optional) (**TBD**)
 X-Object-Meta-*       Optional user defined metadata
 ====================  ================================
 
@@ -586,7 +586,7 @@ Return Code                  Description
 MOVE
 """"
 
-Same as ``COPY``.
+Same as ``COPY``, without the ``X-Source-Version`` request header. The ``MOVE`` operation is always applied on the latest version.
 
 
 POST
@@ -602,11 +602,11 @@ Transfer-Encoding     Set to ``chunked`` to specify incremental uploading (if us
 Content-Encoding      The encoding of the object (optional)
 Content-Disposition   The presentation style of the object (optional)
 X-Object-Manifest     Object parts prefix in ``<container>/<object>`` form (optional)
-X-Object-Public       Object is publicly accessible (optional)
+X-Object-Public       Object is publicly accessible (optional) (**TBD**)
 X-Object-Meta-*       Optional user defined metadata
 ====================  ================================
 
-The ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest``, ``X-Object-Public`` and ``X-Object-Meta-*`` headers are considered to be user defined metadata. The update operation will overwrite all previous values and remove any keys not supplied.
+The ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest``, ``X-Object-Public`` (**TBD**) and ``X-Object-Meta-*`` headers are considered to be user defined metadata. The update operation will overwrite all previous values and remove any keys not supplied.
 
 To update an object:
 
@@ -636,7 +636,7 @@ Return Code                  Description
 202 (Accepted)               The request has been accepted (not a data update)
 204 (No Content)             The request succeeded (data updated)
 411 (Length Required)        Missing ``Content-Length`` in the request
-416 (Range Not Satisfiable)  The supplied range is out of limits or invalid size (**TBD**)
+416 (Range Not Satisfiable)  The supplied range is out of limits or invalid size
 ===========================  ==============================
 
 
@@ -656,7 +656,7 @@ Return Code                  Description
 Public Objects
 ^^^^^^^^^^^^^^
 
-Objects that are marked as public, via the ``X-Object-Public`` meta, are also available at the corresponding URI ``https://hostname/public/<account>/<container>/<object>`` for ``HEAD`` or ``GET``. Requests for public objects do not need to include an ``X-Auth-Token``. Pithos will ignore request parameters and only include the following headers in the reply (all ``X-Object-*`` meta is hidden).
+Objects that are marked as public, via the ``X-Object-Public`` meta (**TBD**), are also available at the corresponding URI ``https://hostname/public/<account>/<container>/<object>`` for ``HEAD`` or ``GET``. Requests for public objects do not need to include an ``X-Auth-Token``. Pithos will ignore request parameters and only include the following headers in the reply (all ``X-Object-*`` meta is hidden).
 
 ==========================  ===============================
 Reply Header Name           Value
@@ -682,14 +682,14 @@ List of differences from the OOS API:
 * All metadata replies, at all levels, include latest modification information.
 * At all levels, a ``GET`` request may use ``If-Modified-Since`` and ``If-Unmodified-Since`` headers.
 * Container/object lists include all associated metadata if the reply is of type json/xml. Some names are kept to their OOS API equivalents for compatibility. 
-* Object metadata allowed, in addition to ``X-Object-Meta-*``: ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest``, ``X-Object-Public``. These are all replaced with every update operation.
+* Object metadata allowed, in addition to ``X-Object-Meta-*``: ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest``, ``X-Object-Public`` (**TBD**). These are all replaced with every update operation.
 * Multi-range object GET support as outlined in RFC2616.
 * Object hashmap retrieval through GET and the ``format`` parameter.
 * Partial object updates through POST, using the ``Content-Length``, ``Content-Type``, ``Content-Range`` and ``Transfer-Encoding`` headers.
 * Object ``MOVE`` support.
 * Time-variant account/container listings via the ``until`` parameter.
-* Object versions - parameter ``version`` in HEAD/GET (list versions with GET), ``X-Object-Version-*`` meta in replies, ``X-Source-Version`` in PUT/COPY/MOVE.
-* Publicly accessible objects via ``https://hostname/public``. Control with ``X-Object-Public``.
+* Object versions - parameter ``version`` in HEAD/GET (list versions with GET), ``X-Object-Version-*`` meta in replies, ``X-Source-Version`` in PUT/COPY.
+* Publicly accessible objects via ``https://hostname/public``. Control with ``X-Object-Public`` (**TBD**).
 * Large object support with ``X-Object-Manifest``.
 
 Clarifications/suggestions:
index 76023b7..6ce4d93 100644 (file)
@@ -47,7 +47,7 @@ from pithos.api.util import (format_meta_key, printable_meta_dict, get_account_m
     update_manifest_meta, validate_modification_preconditions, validate_matching_preconditions,
     split_container_object_string, copy_or_move_object, get_int_parameter, get_content_length,
     get_content_range, raw_input_socket, socket_read_iterator, object_data_response,
-    hashmap_hash, api_method)
+    put_object_block, hashmap_hash, api_method)
 from pithos.backends import backend
 
 
@@ -377,7 +377,7 @@ def object_meta(request, v_account, v_container, v_object):
     
     update_manifest_meta(request, v_account, meta)
     
-    response = HttpResponse(status=204)
+    response = HttpResponse(status=200)
     put_object_meta(response, meta)
     return response
 
@@ -393,8 +393,9 @@ def object_read(request, v_account, v_container, v_object):
     #                       notModified (304)
     
     version = get_int_parameter(request, 'version')
-    if not version:
-        version = request.GET.get('version')
+    version_list = False
+    if version is None and request.GET.get('version') == 'list':
+        version_list = True
     try:
         meta = backend.get_object_meta(request.user, v_account, v_container, v_object, version)
     except NameError:
@@ -414,7 +415,7 @@ def object_read(request, v_account, v_container, v_object):
         return response
     
     # Reply with the version list.
-    if version == 'list':
+    if version_list:
         if request.serialization == 'text':
             raise BadRequest('No format specified for version list.')
         
@@ -634,6 +635,8 @@ def object_update(request, v_account, v_container, v_object):
     offset, length, total = ranges
     if offset is None:
         offset = size
+    elif offset > size:
+        raise RangeNotSatisfiable('Supplied offset is beyond object limits')
     if length is None or content_length == -1:
         length = content_length # Nevermind the error.
     elif length != content_length:
@@ -647,24 +650,11 @@ def object_update(request, v_account, v_container, v_object):
         # TODO: Raise 408 (Request Timeout) if this takes too long.
         # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
         data += d
-        bi = int(offset / backend.block_size)
-        bo = offset % backend.block_size
-        bl = min(len(data), backend.block_size - bo)
-        offset += bl
-        h = backend.update_block(hashmap[bi], data[:bl], bo)
-        if bi < len(hashmap):
-            hashmap[bi] = h
-        else:
-            hashmap.append(h)
-        data = data[bl:]
+        bytes = put_object_block(hashmap, data, offset)
+        offset += bytes
+        data = data[bytes:]
     if len(data) > 0:
-        bi = int(offset / backend.block_size)
-        offset += len(data)
-        h = backend.update_block(hashmap[bi], data)
-        if bi < len(hashmap):
-            hashmap[bi] = h
-        else:
-            hashmap.append(h)
+        put_object_block(hashmap, data, offset)
     
     if offset > size:
         size = offset
index 9ec9862..55308f5 100644 (file)
@@ -143,7 +143,6 @@ def put_object_meta(response, meta, public=False):
     if not public:
         response['X-Object-Version'] = meta['version']
         response['X-Object-Version-Timestamp'] = meta['version_timestamp']
-        response['X-Object-Size'] = meta['bytes']
         for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
             response[k.encode('utf-8')] = meta[k].encode('utf-8')
         for k in ('Content-Encoding', 'Content-Disposition', 'X-Object-Manifest', 'X-Object-Public'):
@@ -233,11 +232,11 @@ def copy_or_move_object(request, v_account, src_container, src_name, dest_contai
         if k in src_meta:
             meta[k] = src_meta[k]
     
-    src_version = request.META.get('HTTP_X_SOURCE_VERSION')
     try:
         if move:
-            backend.move_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, True, src_version)
+            backend.move_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, True)
         else:
+            src_version = request.META.get('HTTP_X_SOURCE_VERSION')
             backend.copy_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, True, src_version)
     except NameError:
         raise ItemNotFound('Container or object does not exist')
@@ -528,6 +527,18 @@ def object_data_response(request, sizes, hashmaps, meta, public=False):
             response['Content-Type'] = 'multipart/byteranges; boundary=%s' % (boundary,)
     return response
 
+def put_object_block(hashmap, data, offset):
+    """Put one block of data at the given offset."""
+    
+    bi = int(offset / backend.block_size)
+    bo = offset % backend.block_size
+    bl = min(len(data), backend.block_size - bo)
+    if bi < len(hashmap):
+        hashmap[bi] = backend.update_block(hashmap[bi], data[:bl], bo)
+    else:
+        hashmap.append(backend.put_block(('\x00' * bo) + data[:bl]))
+    return bl # Return ammount of data written.
+
 def hashmap_hash(hashmap):
     """Produce the root hash, treating the hashmap as a Merkle-like tree."""
     
index 4c2fe35..a6e21a6 100644 (file)
@@ -213,17 +213,15 @@ class BaseBackend(object):
         """
         return
     
-    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, src_version=None):
+    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False):
         """Move an object's data and metadata.
         
         Parameters:
             'dest_meta': Dictionary with metadata to changes from source to destination
             'replace_meta': Replace metadata instead of update
-            'src_version': Copy from the version provided.
         
         Raises:
             NameError: Container/object does not exist
-            IndexError: Version does not exist
         """
         return
     
index 682ded6..edaa22d 100644 (file)
@@ -272,11 +272,11 @@ class SimpleBackend(BaseBackend):
             self.con.execute(sql, (dest_version_id, k, v))
         self.con.commit()
     
-    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, src_version=None):
+    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False):
         """Move an object's data and metadata."""
         
-        logger.debug("move_object: %s %s %s %s, %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, src_version)
-        self.copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, src_version)
+        logger.debug("move_object: %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta)
+        self.copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, None)
         self.delete_object(user, account, src_container, src_name)
     
     def delete_object(self, user, account, container, name):
@@ -353,7 +353,7 @@ class SimpleBackend(BaseBackend):
         return int(row[0]), int(row[1]), int(tstamp)
     
     def _get_version(self, path, version=None):
-        if version in [None, 'list']:
+        if version is None:
             sql = '''select version_id, strftime('%s', tstamp), size, hide from versions where name = ?
                         order by version_id desc limit 1'''
             c = self.con.execute(sql, (path,))