commit aed77afe4e4e9fd1161ad078be8333031d4bfc28
Author: Christos Stavrakakis <cstavr@grnet.gr>
Date:   Thu Nov 22 13:16:13 2012 +0200

    Extend Plankton API with shared-images
    
    Extend Plankton API, to support listing all non-public images that
    are available to the user. This can be done by issuing a GET to
    shared-images/ and shared-images/detail.

diff --git a/snf-cyclades-app/synnefo/plankton/backend.py b/snf-cyclades-app/synnefo/plankton/backend.py
index 74900fa..ad95e71 100644
--- a/snf-cyclades-app/synnefo/plankton/backend.py
+++ b/snf-cyclades-app/synnefo/plankton/backend.py
@@ -255,27 +255,12 @@ class ImageBackend(object):
         location = get_location(account, container, object)
         return self._get_image(location)
 
-    def iter(self):
-        """Iter over all images available to the user"""
-
-        backend = self.backend
-        for account in backend.list_accounts(self.user):
-            for container in backend.list_containers(self.user, account,
-                                                     shared=True):
-                for path, version_id in backend.list_objects(self.user,
-                        account, container, domain=PLANKTON_DOMAIN):
-                    location = get_location(account, container, path)
-                    image = self._get_image(location)
-                    if image:
-                        yield image
-
-    def iter_public(self, filters=None):
+    def _iter(self, public=False, filters=None, shared_from=None):
         filters = filters or {}
-        backend = self.backend
 
+        # Fix keys
         keys = [PLANKTON_PREFIX + 'name']
         size_range = (None, None)
-
         for key, val in filters.items():
             if key == 'size_min':
                 size_range = (int(val), size_range[1])
@@ -284,41 +269,50 @@ class ImageBackend(object):
             else:
                 keys.append('%s = %s' % (PLANKTON_PREFIX + key, val))
 
-        for account in backend.list_accounts(None):
-            for container in backend.list_containers(None, account,
+        backend = self.backend
+        if shared_from:
+            # To get shared images, we connect as shared_from member and
+            # get the list shared by us
+            user = shared_from
+            accounts = [self.user]
+        else:
+            user = None if public else self.user
+            accounts = backend.list_accounts(user)
+
+        for account in accounts:
+            for container in backend.list_containers(user, account,
                                                      shared=True):
-                for path, version_id in backend.list_objects(None, account,
-                        container, domain=PLANKTON_DOMAIN, keys=keys,
-                        shared=True, size_range=size_range):
+                for path, _ in backend.list_objects(user, account, container,
+                                                    domain=PLANKTON_DOMAIN,
+                                                    keys=keys, shared=True,
+                                                    size_range=size_range):
                     location = get_location(account, container, path)
                     image = self._get_image(location)
                     if image:
                         yield image
 
-    def iter_shared(self, member):
-        """Iterate over image ids shared to this member"""
+    def iter(self, filters=None):
+        """Iter over all images available to the user"""
+        return self._iter(filters=filters)
 
-        backend = self.backend
+    def iter_public(self, filters=None):
+        """Iter over public images"""
+        return self._iter(public=True, filters=filters)
 
-        # To get the list we connect as member and get the list shared by us
-        for container in  backend.list_containers(member, self.user):
-            for object, version_id in backend.list_objects(member, self.user,
-                    container, domain=PLANKTON_DOMAIN):
-                try:
-                    location = get_location(self.user, container, object)
-                    meta = backend.get_object_meta(member, self.user,
-                            container, object, PLANKTON_DOMAIN)
-                    if PLANKTON_PREFIX + 'name' in meta:
-                        yield meta['uuid']
-                except (NameError, NotAllowedError):
-                    continue
-
-    def list(self):
-        """Iter over all images available to the user"""
+    def iter_shared(self, filters=None, member=None):
+        """Iter over images shared to member"""
+        return self._iter(filters=filters)
 
-        return list(self.iter())
+    def list(self, filters=None, params={}):
+        """Return all images available to the user"""
+        images = list(self.iter(filters))
+        key = itemgetter(params.get('sort_key', 'created_at'))
+        reverse = params.get('sort_dir', 'desc') == 'desc'
+        images.sort(key=key, reverse=reverse)
+        return images
 
-    def list_public(self, filters, params):
+    def list_public(self, filters, params={}):
+        """Return public images"""
         images = list(self.iter_public(filters))
         key = itemgetter(params.get('sort_key', 'created_at'))
         reverse = params.get('sort_dir', 'desc') == 'desc'
@@ -385,7 +379,10 @@ class ImageBackend(object):
             raise BackendException("Invalid checksum")
 
         is_public = params.pop('is_public', False)
-        permissions = {'read': ['*']} if is_public else {}
+        if is_public:
+            permissions = {'read': ['*']}
+        else:
+            permissions = {'read': [self.user]}
 
         meta = {}
         meta['properties'] = params.pop('properties', {})
diff --git a/snf-cyclades-app/synnefo/plankton/urls.py b/snf-cyclades-app/synnefo/plankton/urls.py
index 6d1e535..2dfca48 100644
--- a/snf-cyclades-app/synnefo/plankton/urls.py
+++ b/snf-cyclades-app/synnefo/plankton/urls.py
@@ -45,6 +45,7 @@ def demux(request):
     else:
         return HttpResponseNotAllowed(['GET', 'POST'])
 
+
 def demux_image(request, image_id):
     if request.method == 'GET':
         return views.get_image(request, image_id)
@@ -55,6 +56,7 @@ def demux_image(request, image_id):
     else:
         return HttpResponseNotAllowed(['GET', 'HEAD', 'PUT'])
 
+
 def demux_image_members(request, image_id):
     if request.method == 'GET':
         return views.list_image_members(request, image_id)
@@ -63,6 +65,7 @@ def demux_image_members(request, image_id):
     else:
         return HttpResponseNotAllowed(['GET', 'PUT'])
 
+
 def demux_members(request, image_id, member):
     if request.method == 'DELETE':
         return views.remove_image_member(request, image_id, member)
@@ -77,6 +80,8 @@ urlpatterns = patterns('',
     (r'^images/detail$', views.list_public_images, {'detail': True}),
     (r'^images/([\w-]+)$', demux_image),
     (r'^images/([\w-]+)/members$', demux_image_members),
-    (r'^images/([\w-]+)/members/(\w+)$', demux_members),
-    (r'^shared-images/(\w+)$', views.list_shared_images)
+    (r'^images/([\w-]+)/members/([\w@._-]+)$', demux_members),
+    (r'^shared-images/$', views.list_shared_images),
+    (r'^shared-images/detail', views.list_shared_images, {'detail': True}),
+    (r'^shared-images/([\w@._-]+)$', views.list_shared_images_with)
 )
diff --git a/snf-cyclades-app/synnefo/plankton/views.py b/snf-cyclades-app/synnefo/plankton/views.py
index 1edd741..4aa605a 100644
--- a/snf-cyclades-app/synnefo/plankton/views.py
+++ b/snf-cyclades-app/synnefo/plankton/views.py
@@ -35,7 +35,6 @@ import json
 
 from logging import getLogger
 from string import punctuation
-from StringIO import StringIO
 from urllib import unquote
 
 from django.conf import settings
@@ -272,7 +271,7 @@ def list_public_images(request, detail=False):
 
 
 @plankton_method('GET')
-def list_shared_images(request, member):
+def list_shared_images_with(request, member):
     """Request shared images
 
     Described in:
@@ -283,10 +282,11 @@ def list_shared_images(request, member):
         the users's images that are accessible by `member`.
     """
 
-    log.debug('list_shared_images %s', member)
+    log.debug('list_shared_images_with %s', member)
 
     images = []
-    for image_id in request.backend.iter_shared(member):
+    for image in request.backend.iter_shared(member):
+        image_id = image['id']
         images.append({'image_id': image_id, 'can_share': False})
 
     data = json.dumps({'shared_images': images}, indent=settings.DEBUG)
@@ -350,3 +350,38 @@ def update_image_members(request, image_id):
 
     request.backend.replace_users(image_id, members)
     return HttpResponse(status=204)
+
+
+@plankton_method('GET')
+def list_shared_images(request, detail=False):
+    def get_request_params(keys):
+        params = {}
+        for key in keys:
+            val = request.GET.get(key, None)
+            if val is not None:
+                params[key] = val
+        return params
+
+    log.debug('list_shared_images detail=%s, request %s', detail, request)
+
+    filters = get_request_params(FILTERS)
+    params = get_request_params(PARAMS)
+
+    params.setdefault('sort_key', 'created_at')
+    params.setdefault('sort_dir', 'desc')
+
+    assert params['sort_key'] in SORT_KEY_OPTIONS
+    assert params['sort_dir'] in SORT_DIR_OPTIONS
+
+    images = request.backend.list(filters, params)
+    images = filter(lambda x: not x['is_public'], images)
+
+    # Remove keys that should not be returned
+    fields = DETAIL_FIELDS if detail else LIST_FIELDS
+    for image in images:
+        for key in image.keys():
+            if key not in fields:
+                del image[key]
+
+    data = json.dumps(images, indent=settings.DEBUG)
+    return HttpResponse(data)
