From 073cd56218e7ad68f965c00888d053632d7c1986 Mon Sep 17 00:00:00 2001
From: YUZAWA Takahiko <yuzawataka@intellilink.co.jp>
Date: Thu, 2 May 2013 00:15:11 +0900
Subject: [PATCH] Add end_marker and path query parameters

Fix Bug 1175057

Change-Id: I1bf65fa7c4d99ddb03dd183fe3862df93455f501
---
 swiftclient/client.py     | 42 ++++++++++++++++++--------
 tests/test_swiftclient.py | 62 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 91 insertions(+), 13 deletions(-)

diff --git a/swiftclient/client.py b/swiftclient/client.py
index f934876b..d052cc4c 100644
--- a/swiftclient/client.py
+++ b/swiftclient/client.py
@@ -331,7 +331,7 @@ def get_auth(auth_url, user, key, **kwargs):
 
 
 def get_account(url, token, marker=None, limit=None, prefix=None,
-                http_conn=None, full_listing=False):
+                end_marker=None, http_conn=None, full_listing=False):
     """
     Get a listing of containers for the account.
 
@@ -340,6 +340,7 @@ def get_account(url, token, marker=None, limit=None, prefix=None,
     :param marker: marker query
     :param limit: limit query
     :param prefix: prefix query
+    :param end_marker: end_marker query
     :param http_conn: HTTP connection object (If None, it will create the
                       conn object)
     :param full_listing: if True, return a full listing, else returns a max
@@ -351,12 +352,14 @@ def get_account(url, token, marker=None, limit=None, prefix=None,
     if not http_conn:
         http_conn = http_connection(url)
     if full_listing:
-        rv = get_account(url, token, marker, limit, prefix, http_conn)
+        rv = get_account(url, token, marker, limit, prefix,
+                         end_marker, http_conn)
         listing = rv[1]
         while listing:
             marker = listing[-1]['name']
             listing = \
-                get_account(url, token, marker, limit, prefix, http_conn)[1]
+                get_account(url, token, marker, limit, prefix,
+                            end_marker, http_conn)[1]
             if listing:
                 rv[1].extend(listing)
         return rv
@@ -368,6 +371,8 @@ def get_account(url, token, marker=None, limit=None, prefix=None,
         qs += '&limit=%d' % limit
     if prefix:
         qs += '&prefix=%s' % quote(prefix)
+    if end_marker:
+        qs += '&end_marker=%s' % quote(end_marker)
     full_path = '%s?%s' % (parsed.path, qs)
     headers = {'X-Auth-Token': token}
     method = 'GET'
@@ -458,7 +463,8 @@ def post_account(url, token, headers, http_conn=None):
 
 
 def get_container(url, token, container, marker=None, limit=None,
-                  prefix=None, delimiter=None, http_conn=None,
+                  prefix=None, delimiter=None, end_marker=None,
+                  path=None, http_conn=None,
                   full_listing=False):
     """
     Get a listing of objects for the container.
@@ -469,7 +475,9 @@ def get_container(url, token, container, marker=None, limit=None,
     :param marker: marker query
     :param limit: limit query
     :param prefix: prefix query
-    :param delimeter: string to delimit the queries on
+    :param delimiter: string to delimit the queries on
+    :param end_marker: marker query
+    :param path: path query (equivalent: "delimiter=/" and "prefix=path/")
     :param http_conn: HTTP connection object (If None, it will create the
                       conn object)
     :param full_listing: if True, return a full listing, else returns a max
@@ -482,7 +490,7 @@ def get_container(url, token, container, marker=None, limit=None,
         http_conn = http_connection(url)
     if full_listing:
         rv = get_container(url, token, container, marker, limit, prefix,
-                           delimiter, http_conn)
+                           delimiter, end_marker, path, http_conn)
         listing = rv[1]
         while listing:
             if not delimiter:
@@ -490,12 +498,13 @@ def get_container(url, token, container, marker=None, limit=None,
             else:
                 marker = listing[-1].get('name', listing[-1].get('subdir'))
             listing = get_container(url, token, container, marker, limit,
-                                    prefix, delimiter, http_conn)[1]
+                                    prefix, delimiter, end_marker, path,
+                                    http_conn)[1]
             if listing:
                 rv[1].extend(listing)
         return rv
     parsed, conn = http_conn
-    path = '%s/%s' % (parsed.path, quote(container))
+    cont_path = '%s/%s' % (parsed.path, quote(container))
     qs = 'format=json'
     if marker:
         qs += '&marker=%s' % quote(marker)
@@ -505,9 +514,13 @@ def get_container(url, token, container, marker=None, limit=None,
         qs += '&prefix=%s' % quote(prefix)
     if delimiter:
         qs += '&delimiter=%s' % quote(delimiter)
+    if end_marker:
+        qs += '&end_marker=%s' % quote(end_marker)
+    if path:
+        qs += '&path=%s' % quote(path)
     headers = {'X-Auth-Token': token}
     method = 'GET'
-    conn.request(method, '%s?%s' % (path, qs), '', headers)
+    conn.request(method, '%s?%s' % (cont_path, qs), '', headers)
     resp = conn.getresponse()
     body = resp.read()
     http_log(('%s?%s' % (url, qs), method,), {'headers': headers}, resp, body)
@@ -515,7 +528,7 @@ def get_container(url, token, container, marker=None, limit=None,
     if resp.status < 200 or resp.status >= 300:
         raise ClientException('Container GET failed',
                               http_scheme=parsed.scheme, http_host=conn.host,
-                              http_port=conn.port, http_path=path,
+                              http_port=conn.port, http_path=cont_path,
                               http_query=qs, http_status=resp.status,
                               http_reason=resp.reason,
                               http_response_content=body)
@@ -1052,13 +1065,14 @@ class Connection(object):
         return self._retry(None, head_account)
 
     def get_account(self, marker=None, limit=None, prefix=None,
-                    full_listing=False):
+                    end_marker=None, full_listing=False):
         """Wrapper for :func:`get_account`"""
         # TODO(unknown): With full_listing=True this will restart the entire
         # listing with each retry. Need to make a better version that just
         # retries where it left off.
         return self._retry(None, get_account, marker=marker, limit=limit,
-                           prefix=prefix, full_listing=full_listing)
+                           prefix=prefix, end_marker=end_marker,
+                           full_listing=full_listing)
 
     def post_account(self, headers):
         """Wrapper for :func:`post_account`"""
@@ -1069,13 +1083,15 @@ class Connection(object):
         return self._retry(None, head_container, container)
 
     def get_container(self, container, marker=None, limit=None, prefix=None,
-                      delimiter=None, full_listing=False):
+                      delimiter=None, end_marker=None, path=None,
+                      full_listing=False):
         """Wrapper for :func:`get_container`"""
         # TODO(unknown): With full_listing=True this will restart the entire
         # listing with each retry. Need to make a better version that just
         # retries where it left off.
         return self._retry(None, get_container, container, marker=marker,
                            limit=limit, prefix=prefix, delimiter=delimiter,
+                           end_marker=end_marker, path=path,
                            full_listing=full_listing)
 
     def put_container(self, container, headers=None):
diff --git a/tests/test_swiftclient.py b/tests/test_swiftclient.py
index ea1a444b..aa0e1590 100644
--- a/tests/test_swiftclient.py
+++ b/tests/test_swiftclient.py
@@ -354,6 +354,30 @@ class TestGetAccount(MockHttpTest):
         value = c.get_account('http://www.test.com', 'asdf')[1]
         self.assertEquals(value, [])
 
+    def test_param_marker(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&marker=marker")
+        c.get_account('http://www.test.com', 'asdf', marker='marker')
+
+    def test_param_limit(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&limit=10")
+        c.get_account('http://www.test.com', 'asdf', limit=10)
+
+    def test_param_prefix(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&prefix=asdf/")
+        c.get_account('http://www.test.com', 'asdf', prefix='asdf/')
+
+    def test_param_end_marker(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&end_marker=end_marker")
+        c.get_account('http://www.test.com', 'asdf', end_marker='end_marker')
+
 
 class TestHeadAccount(MockHttpTest):
 
@@ -384,6 +408,44 @@ class TestGetContainer(MockHttpTest):
         value = c.get_container('http://www.test.com', 'asdf', 'asdf')[1]
         self.assertEquals(value, [])
 
+    def test_param_marker(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&marker=marker")
+        c.get_container('http://www.test.com', 'asdf', 'asdf', marker='marker')
+
+    def test_param_limit(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&limit=10")
+        c.get_container('http://www.test.com', 'asdf', 'asdf', limit=10)
+
+    def test_param_prefix(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&prefix=asdf/")
+        c.get_container('http://www.test.com', 'asdf', 'asdf', prefix='asdf/')
+
+    def test_param_delimiter(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&delimiter=/")
+        c.get_container('http://www.test.com', 'asdf', 'asdf', delimiter='/')
+
+    def test_param_end_marker(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&end_marker=end_marker")
+        c.get_container('http://www.test.com', 'asdf', 'asdf',
+                        end_marker='end_marker')
+
+    def test_param_path(self):
+        c.http_connection = self.fake_http_connection(
+            204,
+            query_string="format=json&path=asdf")
+        c.get_container('http://www.test.com', 'asdf', 'asdf',
+                        path='asdf')
+
 
 class TestHeadContainer(MockHttpTest):