Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / modular.py @ 56ac7c81

History | View | Annotate | Download (57 kB)

1 2e662088 Antony Chazapis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 a9b3f29d Antony Chazapis
# 
3 a9b3f29d Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 a9b3f29d Antony Chazapis
# without modification, are permitted provided that the following
5 a9b3f29d Antony Chazapis
# conditions are met:
6 a9b3f29d Antony Chazapis
# 
7 a9b3f29d Antony Chazapis
#   1. Redistributions of source code must retain the above
8 a9b3f29d Antony Chazapis
#      copyright notice, this list of conditions and the following
9 a9b3f29d Antony Chazapis
#      disclaimer.
10 a9b3f29d Antony Chazapis
# 
11 7ff57991 Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 7ff57991 Antony Chazapis
#      copyright notice, this list of conditions and the following
13 a9b3f29d Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 a9b3f29d Antony Chazapis
#      provided with the distribution.
15 a9b3f29d Antony Chazapis
# 
16 a9b3f29d Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 a9b3f29d Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 a9b3f29d Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 a9b3f29d Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 a9b3f29d Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 a9b3f29d Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 a9b3f29d Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 a9b3f29d Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 a9b3f29d Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 a9b3f29d Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 a9b3f29d Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 a9b3f29d Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 a9b3f29d Antony Chazapis
# 
29 a9b3f29d Antony Chazapis
# The views and conclusions contained in the software and
30 a9b3f29d Antony Chazapis
# documentation are those of the authors and should not be
31 a9b3f29d Antony Chazapis
# interpreted as representing official policies, either expressed
32 a9b3f29d Antony Chazapis
# or implied, of GRNET S.A.
33 a9b3f29d Antony Chazapis
34 2c5363a0 Antony Chazapis
import sys
35 a9b3f29d Antony Chazapis
import os
36 a9b3f29d Antony Chazapis
import time
37 37bee317 Antony Chazapis
import uuid as uuidlib
38 a9b3f29d Antony Chazapis
import logging
39 6e147ecc Antony Chazapis
import hashlib
40 a9b3f29d Antony Chazapis
import binascii
41 a9b3f29d Antony Chazapis
42 228de81b Antony Chazapis
from base import DEFAULT_QUOTA, DEFAULT_VERSIONING, NotAllowedError, QuotaError, BaseBackend
43 a9b3f29d Antony Chazapis
44 6e147ecc Antony Chazapis
# Stripped-down version of the HashMap class found in tools.
45 6e147ecc Antony Chazapis
class HashMap(list):
46 6e147ecc Antony Chazapis
47 6e147ecc Antony Chazapis
    def __init__(self, blocksize, blockhash):
48 6e147ecc Antony Chazapis
        super(HashMap, self).__init__()
49 6e147ecc Antony Chazapis
        self.blocksize = blocksize
50 6e147ecc Antony Chazapis
        self.blockhash = blockhash
51 6e147ecc Antony Chazapis
52 6e147ecc Antony Chazapis
    def _hash_raw(self, v):
53 6e147ecc Antony Chazapis
        h = hashlib.new(self.blockhash)
54 6e147ecc Antony Chazapis
        h.update(v)
55 6e147ecc Antony Chazapis
        return h.digest()
56 6e147ecc Antony Chazapis
57 6e147ecc Antony Chazapis
    def hash(self):
58 6e147ecc Antony Chazapis
        if len(self) == 0:
59 6e147ecc Antony Chazapis
            return self._hash_raw('')
60 6e147ecc Antony Chazapis
        if len(self) == 1:
61 6e147ecc Antony Chazapis
            return self.__getitem__(0)
62 6e147ecc Antony Chazapis
63 6e147ecc Antony Chazapis
        h = list(self)
64 6e147ecc Antony Chazapis
        s = 2
65 6e147ecc Antony Chazapis
        while s < len(h):
66 6e147ecc Antony Chazapis
            s = s * 2
67 6e147ecc Antony Chazapis
        h += [('\x00' * len(h[0]))] * (s - len(h))
68 6e147ecc Antony Chazapis
        while len(h) > 1:
69 6e147ecc Antony Chazapis
            h = [self._hash_raw(h[x] + h[x + 1]) for x in range(0, len(h), 2)]
70 6e147ecc Antony Chazapis
        return h[0]
71 5a96180b Antony Chazapis
72 228de81b Antony Chazapis
# Default modules and settings.
73 228de81b Antony Chazapis
DEFAULT_DB_MODULE = 'pithos.backends.lib.sqlalchemy'
74 228de81b Antony Chazapis
DEFAULT_DB_CONNECTION = 'sqlite:///backend.db'
75 228de81b Antony Chazapis
DEFAULT_BLOCK_MODULE = 'pithos.backends.lib.hashfiler'
76 228de81b Antony Chazapis
DEFAULT_BLOCK_PATH = 'data/'
77 f3b65e8f Antony Chazapis
DEFAULT_BLOCK_UMASK = 0o022
78 fa9cae7e Antony Chazapis
#DEFAULT_QUEUE_MODULE = 'pithos.backends.lib.rabbitmq'
79 fa9cae7e Antony Chazapis
#DEFAULT_QUEUE_CONNECTION = 'rabbitmq://guest:guest@localhost:5672/pithos'
80 fa9cae7e Antony Chazapis
81 8d9a3fbd Antony Chazapis
QUEUE_MESSAGE_KEY_PREFIX = 'pithos.%s'
82 39ef6f41 Antony Chazapis
QUEUE_CLIENT_ID = 'pithos'
83 73673127 Antony Chazapis
QUEUE_INSTANCE_ID = '1'
84 228de81b Antony Chazapis
85 44ad5860 Antony Chazapis
( CLUSTER_NORMAL, CLUSTER_HISTORY, CLUSTER_DELETED ) = range(3)
86 44ad5860 Antony Chazapis
87 44ad5860 Antony Chazapis
inf = float('inf')
88 44ad5860 Antony Chazapis
89 bb4eafc6 Antony Chazapis
ULTIMATE_ANSWER = 42
90 bb4eafc6 Antony Chazapis
91 a9b3f29d Antony Chazapis
92 a9b3f29d Antony Chazapis
logger = logging.getLogger(__name__)
93 a9b3f29d Antony Chazapis
94 1c2fc0ff Antony Chazapis
95 a9b3f29d Antony Chazapis
def backend_method(func=None, autocommit=1):
96 a9b3f29d Antony Chazapis
    if func is None:
97 a9b3f29d Antony Chazapis
        def fn(func):
98 a9b3f29d Antony Chazapis
            return backend_method(func, autocommit)
99 a9b3f29d Antony Chazapis
        return fn
100 a9b3f29d Antony Chazapis
101 a9b3f29d Antony Chazapis
    if not autocommit:
102 a9b3f29d Antony Chazapis
        return func
103 a9b3f29d Antony Chazapis
    def fn(self, *args, **kw):
104 2c5363a0 Antony Chazapis
        self.wrapper.execute()
105 a9b3f29d Antony Chazapis
        try:
106 39ef6f41 Antony Chazapis
            self.messages = []
107 a9b3f29d Antony Chazapis
            ret = func(self, *args, **kw)
108 39ef6f41 Antony Chazapis
            for m in self.messages:
109 39ef6f41 Antony Chazapis
                self.queue.send(*m)
110 a74ba506 Sofia Papagiannaki
            self.wrapper.commit()
111 a9b3f29d Antony Chazapis
            return ret
112 a9b3f29d Antony Chazapis
        except:
113 2c5363a0 Antony Chazapis
            self.wrapper.rollback()
114 a9b3f29d Antony Chazapis
            raise
115 a9b3f29d Antony Chazapis
    return fn
116 a9b3f29d Antony Chazapis
117 a9b3f29d Antony Chazapis
118 a9b3f29d Antony Chazapis
class ModularBackend(BaseBackend):
119 a9b3f29d Antony Chazapis
    """A modular backend.
120 a9b3f29d Antony Chazapis
    
121 e9363f82 Antony Chazapis
    Uses modules for SQL functions and storage.
122 a9b3f29d Antony Chazapis
    """
123 a9b3f29d Antony Chazapis
    
124 46286f5f Antony Chazapis
    def __init__(self, db_module=None, db_connection=None,
125 f3b65e8f Antony Chazapis
                 block_module=None, block_path=None, block_umask=None,
126 46286f5f Antony Chazapis
                 queue_module=None, queue_connection=None):
127 228de81b Antony Chazapis
        db_module = db_module or DEFAULT_DB_MODULE
128 228de81b Antony Chazapis
        db_connection = db_connection or DEFAULT_DB_CONNECTION
129 228de81b Antony Chazapis
        block_module = block_module or DEFAULT_BLOCK_MODULE
130 228de81b Antony Chazapis
        block_path = block_path or DEFAULT_BLOCK_PATH
131 f3b65e8f Antony Chazapis
        block_umask = block_umask or DEFAULT_BLOCK_UMASK
132 46286f5f Antony Chazapis
        #queue_module = queue_module or DEFAULT_QUEUE_MODULE
133 46286f5f Antony Chazapis
        #queue_connection = queue_connection or DEFAULT_QUEUE_CONNECTION
134 f81e20b0 Giorgos Verigakis
        
135 a9b3f29d Antony Chazapis
        self.hash_algorithm = 'sha256'
136 a9b3f29d Antony Chazapis
        self.block_size = 4 * 1024 * 1024 # 4MB
137 a9b3f29d Antony Chazapis
        
138 228de81b Antony Chazapis
        self.default_policy = {'quota': DEFAULT_QUOTA, 'versioning': DEFAULT_VERSIONING}
139 a9b3f29d Antony Chazapis
        
140 46286f5f Antony Chazapis
        def load_module(m):
141 46286f5f Antony Chazapis
            __import__(m)
142 46286f5f Antony Chazapis
            return sys.modules[m]
143 a9b3f29d Antony Chazapis
        
144 46286f5f Antony Chazapis
        self.db_module = load_module(db_module)
145 46286f5f Antony Chazapis
        self.wrapper = self.db_module.DBWrapper(db_connection)
146 e9363f82 Antony Chazapis
        params = {'wrapper': self.wrapper}
147 e9363f82 Antony Chazapis
        self.permissions = self.db_module.Permissions(**params)
148 e9363f82 Antony Chazapis
        for x in ['READ', 'WRITE']:
149 e9363f82 Antony Chazapis
            setattr(self, x, getattr(self.db_module, x))
150 e9363f82 Antony Chazapis
        self.node = self.db_module.Node(**params)
151 33b4e4a6 Antony Chazapis
        for x in ['ROOTNODE', 'SERIAL', 'HASH', 'SIZE', 'TYPE', 'MTIME', 'MUSER', 'UUID', 'CHECKSUM', 'CLUSTER', 'MATCH_PREFIX', 'MATCH_EXACT']:
152 e9363f82 Antony Chazapis
            setattr(self, x, getattr(self.db_module, x))
153 e9363f82 Antony Chazapis
        
154 46286f5f Antony Chazapis
        self.block_module = load_module(block_module)
155 7ca7bb08 Antony Chazapis
        params = {'path': block_path,
156 7ca7bb08 Antony Chazapis
                  'block_size': self.block_size,
157 f3b65e8f Antony Chazapis
                  'hash_algorithm': self.hash_algorithm,
158 f3b65e8f Antony Chazapis
                  'umask': block_umask}
159 7ca7bb08 Antony Chazapis
        self.store = self.block_module.Store(**params)
160 46286f5f Antony Chazapis
161 46286f5f Antony Chazapis
        if queue_module and queue_connection:
162 46286f5f Antony Chazapis
            self.queue_module = load_module(queue_module)
163 fa9cae7e Antony Chazapis
            params = {'exchange': queue_connection,
164 fa9cae7e Antony Chazapis
                      'client_id': QUEUE_CLIENT_ID}
165 46286f5f Antony Chazapis
            self.queue = self.queue_module.Queue(**params)
166 46286f5f Antony Chazapis
        else:
167 46286f5f Antony Chazapis
            class NoQueue:
168 1a239dc1 Sofia Papagiannaki
                def send(self, *args):
169 46286f5f Antony Chazapis
                    pass
170 b9a8feec root
                
171 b9a8feec root
                def close(self):
172 b9a8feec root
                    pass
173 46286f5f Antony Chazapis
            
174 46286f5f Antony Chazapis
            self.queue = NoQueue()
175 a9b3f29d Antony Chazapis
    
176 d14fe290 Antony Chazapis
    def close(self):
177 d14fe290 Antony Chazapis
        self.wrapper.close()
178 b9a8feec root
        self.queue.close()
179 d14fe290 Antony Chazapis
    
180 a9b3f29d Antony Chazapis
    @backend_method
181 a9b3f29d Antony Chazapis
    def list_accounts(self, user, marker=None, limit=10000):
182 a9b3f29d Antony Chazapis
        """Return a list of accounts the user can access."""
183 a9b3f29d Antony Chazapis
        
184 02c4d2ba Antony Chazapis
        logger.debug("list_accounts: %s %s %s", user, marker, limit)
185 a9b3f29d Antony Chazapis
        allowed = self._allowed_accounts(user)
186 a9b3f29d Antony Chazapis
        start, limit = self._list_limits(allowed, marker, limit)
187 a9b3f29d Antony Chazapis
        return allowed[start:start + limit]
188 a9b3f29d Antony Chazapis
    
189 a9b3f29d Antony Chazapis
    @backend_method
190 82482e2c Antony Chazapis
    def get_account_meta(self, user, account, domain, until=None, include_user_defined=True):
191 cb69c154 Antony Chazapis
        """Return a dictionary with the account metadata for the domain."""
192 a9b3f29d Antony Chazapis
        
193 fe2db49d Sofia Papagiannaki
        logger.debug("get_account_meta: %s %s %s %s", user, account, domain, until)
194 c915d3bf Antony Chazapis
        path, node = self._lookup_account(account, user == account)
195 a9b3f29d Antony Chazapis
        if user != account:
196 44ad5860 Antony Chazapis
            if until or node is None or account not in self._allowed_accounts(user):
197 a9b3f29d Antony Chazapis
                raise NotAllowedError
198 a9b3f29d Antony Chazapis
        try:
199 44ad5860 Antony Chazapis
            props = self._get_properties(node, until)
200 2c5363a0 Antony Chazapis
            mtime = props[self.MTIME]
201 a9b3f29d Antony Chazapis
        except NameError:
202 62f915a1 Antony Chazapis
            props = None
203 a9b3f29d Antony Chazapis
            mtime = until
204 62f915a1 Antony Chazapis
        count, bytes, tstamp = self._get_statistics(node, until)
205 62f915a1 Antony Chazapis
        tstamp = max(tstamp, mtime)
206 a9b3f29d Antony Chazapis
        if until is None:
207 a9b3f29d Antony Chazapis
            modified = tstamp
208 a9b3f29d Antony Chazapis
        else:
209 62f915a1 Antony Chazapis
            modified = self._get_statistics(node)[2] # Overall last modification.
210 62f915a1 Antony Chazapis
            modified = max(modified, mtime)
211 a9b3f29d Antony Chazapis
        
212 a9b3f29d Antony Chazapis
        if user != account:
213 a9b3f29d Antony Chazapis
            meta = {'name': account}
214 a9b3f29d Antony Chazapis
        else:
215 44ad5860 Antony Chazapis
            meta = {}
216 82482e2c Antony Chazapis
            if props is not None and include_user_defined:
217 4819d34f Antony Chazapis
                meta.update(dict(self.node.attribute_get(props[self.SERIAL], domain)))
218 a9b3f29d Antony Chazapis
            if until is not None:
219 a9b3f29d Antony Chazapis
                meta.update({'until_timestamp': tstamp})
220 44ad5860 Antony Chazapis
            meta.update({'name': account, 'count': count, 'bytes': bytes})
221 a9b3f29d Antony Chazapis
        meta.update({'modified': modified})
222 a9b3f29d Antony Chazapis
        return meta
223 a9b3f29d Antony Chazapis
    
224 a9b3f29d Antony Chazapis
    @backend_method
225 cb69c154 Antony Chazapis
    def update_account_meta(self, user, account, domain, meta, replace=False):
226 cb69c154 Antony Chazapis
        """Update the metadata associated with the account for the domain."""
227 a9b3f29d Antony Chazapis
        
228 fe2db49d Sofia Papagiannaki
        logger.debug("update_account_meta: %s %s %s %s %s", user, account, domain, meta, replace)
229 a9b3f29d Antony Chazapis
        if user != account:
230 a9b3f29d Antony Chazapis
            raise NotAllowedError
231 c915d3bf Antony Chazapis
        path, node = self._lookup_account(account, True)
232 4819d34f Antony Chazapis
        self._put_metadata(user, node, domain, meta, replace)
233 a9b3f29d Antony Chazapis
    
234 a9b3f29d Antony Chazapis
    @backend_method
235 a9b3f29d Antony Chazapis
    def get_account_groups(self, user, account):
236 a9b3f29d Antony Chazapis
        """Return a dictionary with the user groups defined for this account."""
237 a9b3f29d Antony Chazapis
        
238 fe2db49d Sofia Papagiannaki
        logger.debug("get_account_groups: %s %s", user, account)
239 a9b3f29d Antony Chazapis
        if user != account:
240 a9b3f29d Antony Chazapis
            if account not in self._allowed_accounts(user):
241 a9b3f29d Antony Chazapis
                raise NotAllowedError
242 a9b3f29d Antony Chazapis
            return {}
243 44ad5860 Antony Chazapis
        self._lookup_account(account, True)
244 0f9d752c Antony Chazapis
        return self.permissions.group_dict(account)
245 a9b3f29d Antony Chazapis
    
246 a9b3f29d Antony Chazapis
    @backend_method
247 a9b3f29d Antony Chazapis
    def update_account_groups(self, user, account, groups, replace=False):
248 a9b3f29d Antony Chazapis
        """Update the groups associated with the account."""
249 a9b3f29d Antony Chazapis
        
250 fe2db49d Sofia Papagiannaki
        logger.debug("update_account_groups: %s %s %s %s", user, account, groups, replace)
251 a9b3f29d Antony Chazapis
        if user != account:
252 a9b3f29d Antony Chazapis
            raise NotAllowedError
253 44ad5860 Antony Chazapis
        self._lookup_account(account, True)
254 a9b3f29d Antony Chazapis
        self._check_groups(groups)
255 0f9d752c Antony Chazapis
        if replace:
256 0f9d752c Antony Chazapis
            self.permissions.group_destroy(account)
257 0f9d752c Antony Chazapis
        for k, v in groups.iteritems():
258 0f9d752c Antony Chazapis
            if not replace: # If not already deleted.
259 0f9d752c Antony Chazapis
                self.permissions.group_delete(account, k)
260 0f9d752c Antony Chazapis
            if v:
261 0f9d752c Antony Chazapis
                self.permissions.group_addmany(account, k, v)
262 a9b3f29d Antony Chazapis
    
263 a9b3f29d Antony Chazapis
    @backend_method
264 b2832c6a Antony Chazapis
    def get_account_policy(self, user, account):
265 b2832c6a Antony Chazapis
        """Return a dictionary with the account policy."""
266 b2832c6a Antony Chazapis
        
267 fe2db49d Sofia Papagiannaki
        logger.debug("get_account_policy: %s %s", user, account)
268 b2832c6a Antony Chazapis
        if user != account:
269 647a5f48 Antony Chazapis
            if account not in self._allowed_accounts(user):
270 647a5f48 Antony Chazapis
                raise NotAllowedError
271 647a5f48 Antony Chazapis
            return {}
272 b2832c6a Antony Chazapis
        path, node = self._lookup_account(account, True)
273 b9064632 Antony Chazapis
        return self._get_policy(node)
274 b2832c6a Antony Chazapis
    
275 b2832c6a Antony Chazapis
    @backend_method
276 b2832c6a Antony Chazapis
    def update_account_policy(self, user, account, policy, replace=False):
277 b2832c6a Antony Chazapis
        """Update the policy associated with the account."""
278 b2832c6a Antony Chazapis
        
279 fe2db49d Sofia Papagiannaki
        logger.debug("update_account_policy: %s %s %s %s", user, account, policy, replace)
280 b2832c6a Antony Chazapis
        if user != account:
281 b2832c6a Antony Chazapis
            raise NotAllowedError
282 b2832c6a Antony Chazapis
        path, node = self._lookup_account(account, True)
283 b2832c6a Antony Chazapis
        self._check_policy(policy)
284 b2832c6a Antony Chazapis
        self._put_policy(node, policy, replace)
285 b2832c6a Antony Chazapis
    
286 b2832c6a Antony Chazapis
    @backend_method
287 8693b873 Sofia Papagiannaki
    def put_account(self, user, account, policy={}):
288 a9b3f29d Antony Chazapis
        """Create a new account with the given name."""
289 a9b3f29d Antony Chazapis
        
290 fe2db49d Sofia Papagiannaki
        logger.debug("put_account: %s %s %s", user, account, policy)
291 a9b3f29d Antony Chazapis
        if user != account:
292 a9b3f29d Antony Chazapis
            raise NotAllowedError
293 44ad5860 Antony Chazapis
        node = self.node.node_lookup(account)
294 44ad5860 Antony Chazapis
        if node is not None:
295 a9b3f29d Antony Chazapis
            raise NameError('Account already exists')
296 b2832c6a Antony Chazapis
        if policy:
297 b2832c6a Antony Chazapis
            self._check_policy(policy)
298 b2832c6a Antony Chazapis
        node = self._put_path(user, self.ROOTNODE, account)
299 b2832c6a Antony Chazapis
        self._put_policy(node, policy, True)
300 a9b3f29d Antony Chazapis
    
301 a9b3f29d Antony Chazapis
    @backend_method
302 a9b3f29d Antony Chazapis
    def delete_account(self, user, account):
303 a9b3f29d Antony Chazapis
        """Delete the account with the given name."""
304 a9b3f29d Antony Chazapis
        
305 fe2db49d Sofia Papagiannaki
        logger.debug("delete_account: %s %s", user, account)
306 a9b3f29d Antony Chazapis
        if user != account:
307 a9b3f29d Antony Chazapis
            raise NotAllowedError
308 c915d3bf Antony Chazapis
        node = self.node.node_lookup(account)
309 c915d3bf Antony Chazapis
        if node is None:
310 c915d3bf Antony Chazapis
            return
311 c915d3bf Antony Chazapis
        if not self.node.node_remove(node):
312 a9b3f29d Antony Chazapis
            raise IndexError('Account is not empty')
313 0f9d752c Antony Chazapis
        self.permissions.group_destroy(account)
314 a9b3f29d Antony Chazapis
    
315 62f915a1 Antony Chazapis
    @backend_method
316 90ee1eb3 Sofia Papagiannaki
    def list_containers(self, user, account, marker=None, limit=10000, shared=False, until=None, public=False):
317 62f915a1 Antony Chazapis
        """Return a list of containers existing under an account."""
318 62f915a1 Antony Chazapis
        
319 fe2db49d Sofia Papagiannaki
        logger.debug("list_containers: %s %s %s %s %s %s %s", user, account, marker, limit, shared, until, public)
320 62f915a1 Antony Chazapis
        if user != account:
321 62f915a1 Antony Chazapis
            if until or account not in self._allowed_accounts(user):
322 62f915a1 Antony Chazapis
                raise NotAllowedError
323 62f915a1 Antony Chazapis
            allowed = self._allowed_containers(user, account)
324 62f915a1 Antony Chazapis
            start, limit = self._list_limits(allowed, marker, limit)
325 62f915a1 Antony Chazapis
            return allowed[start:start + limit]
326 90ee1eb3 Sofia Papagiannaki
        if shared or public:
327 56ac7c81 Sofia Papagiannaki
            allowed = set()
328 90ee1eb3 Sofia Papagiannaki
            if shared:
329 56ac7c81 Sofia Papagiannaki
                allowed.update([x.split('/', 2)[1] for x in self.permissions.access_list_shared(account)])
330 90ee1eb3 Sofia Papagiannaki
            if public:
331 56ac7c81 Sofia Papagiannaki
                allowed.update([x[0].split('/', 2)[1] for x in self.permissions.public_list(account)])
332 56ac7c81 Sofia Papagiannaki
            allowed = sorted(allowed)
333 62f915a1 Antony Chazapis
            start, limit = self._list_limits(allowed, marker, limit)
334 62f915a1 Antony Chazapis
            return allowed[start:start + limit]
335 62f915a1 Antony Chazapis
        node = self.node.node_lookup(account)
336 cf4a7a7b Sofia Papagiannaki
        containers = [x[0] for x in self._list_object_properties(node, account, '', '/', marker, limit, False, None, [], until)]
337 cf4a7a7b Sofia Papagiannaki
        start, limit = self._list_limits([x[0] for x in containers], marker, limit)
338 cf4a7a7b Sofia Papagiannaki
        return containers[start:start + limit]
339 371d907a Antony Chazapis
    
340 371d907a Antony Chazapis
    @backend_method
341 371d907a Antony Chazapis
    def list_container_meta(self, user, account, container, domain, until=None):
342 371d907a Antony Chazapis
        """Return a list with all the container's object meta keys for the domain."""
343 371d907a Antony Chazapis
        
344 fe2db49d Sofia Papagiannaki
        logger.debug("list_container_meta: %s %s %s %s %s", user, account, container, domain, until)
345 371d907a Antony Chazapis
        allowed = []
346 371d907a Antony Chazapis
        if user != account:
347 371d907a Antony Chazapis
            if until:
348 371d907a Antony Chazapis
                raise NotAllowedError
349 371d907a Antony Chazapis
            allowed = self.permissions.access_list_paths(user, '/'.join((account, container)))
350 371d907a Antony Chazapis
            if not allowed:
351 371d907a Antony Chazapis
                raise NotAllowedError
352 371d907a Antony Chazapis
        path, node = self._lookup_container(account, container)
353 371d907a Antony Chazapis
        before = until if until is not None else inf
354 371d907a Antony Chazapis
        allowed = self._get_formatted_paths(allowed)
355 371d907a Antony Chazapis
        return self.node.latest_attribute_keys(node, domain, before, CLUSTER_DELETED, allowed)
356 a9b3f29d Antony Chazapis
    
357 a9b3f29d Antony Chazapis
    @backend_method
358 82482e2c Antony Chazapis
    def get_container_meta(self, user, account, container, domain, until=None, include_user_defined=True):
359 cb69c154 Antony Chazapis
        """Return a dictionary with the container metadata for the domain."""
360 a9b3f29d Antony Chazapis
        
361 fe2db49d Sofia Papagiannaki
        logger.debug("get_container_meta: %s %s %s %s %s", user, account, container, domain, until)
362 a9b3f29d Antony Chazapis
        if user != account:
363 a9b3f29d Antony Chazapis
            if until or container not in self._allowed_containers(user, account):
364 a9b3f29d Antony Chazapis
                raise NotAllowedError
365 c915d3bf Antony Chazapis
        path, node = self._lookup_container(account, container)
366 c915d3bf Antony Chazapis
        props = self._get_properties(node, until)
367 2c5363a0 Antony Chazapis
        mtime = props[self.MTIME]
368 62f915a1 Antony Chazapis
        count, bytes, tstamp = self._get_statistics(node, until)
369 62f915a1 Antony Chazapis
        tstamp = max(tstamp, mtime)
370 a9b3f29d Antony Chazapis
        if until is None:
371 a9b3f29d Antony Chazapis
            modified = tstamp
372 a9b3f29d Antony Chazapis
        else:
373 62f915a1 Antony Chazapis
            modified = self._get_statistics(node)[2] # Overall last modification.
374 62f915a1 Antony Chazapis
            modified = max(modified, mtime)
375 a9b3f29d Antony Chazapis
        
376 a9b3f29d Antony Chazapis
        if user != account:
377 c915d3bf Antony Chazapis
            meta = {'name': container}
378 a9b3f29d Antony Chazapis
        else:
379 82482e2c Antony Chazapis
            meta = {}
380 82482e2c Antony Chazapis
            if include_user_defined:
381 82482e2c Antony Chazapis
                meta.update(dict(self.node.attribute_get(props[self.SERIAL], domain)))
382 a9b3f29d Antony Chazapis
            if until is not None:
383 a9b3f29d Antony Chazapis
                meta.update({'until_timestamp': tstamp})
384 c915d3bf Antony Chazapis
            meta.update({'name': container, 'count': count, 'bytes': bytes})
385 c915d3bf Antony Chazapis
        meta.update({'modified': modified})
386 a9b3f29d Antony Chazapis
        return meta
387 a9b3f29d Antony Chazapis
    
388 a9b3f29d Antony Chazapis
    @backend_method
389 cb69c154 Antony Chazapis
    def update_container_meta(self, user, account, container, domain, meta, replace=False):
390 cb69c154 Antony Chazapis
        """Update the metadata associated with the container for the domain."""
391 a9b3f29d Antony Chazapis
        
392 fe2db49d Sofia Papagiannaki
        logger.debug("update_container_meta: %s %s %s %s %s %s", user, account, container, domain, meta, replace)
393 a9b3f29d Antony Chazapis
        if user != account:
394 a9b3f29d Antony Chazapis
            raise NotAllowedError
395 c915d3bf Antony Chazapis
        path, node = self._lookup_container(account, container)
396 f9ea264b Antony Chazapis
        src_version_id, dest_version_id = self._put_metadata(user, node, domain, meta, replace)
397 f9ea264b Antony Chazapis
        if src_version_id is not None:
398 f9ea264b Antony Chazapis
            versioning = self._get_policy(node)['versioning']
399 f9ea264b Antony Chazapis
            if versioning != 'auto':
400 f9ea264b Antony Chazapis
                self.node.version_remove(src_version_id)
401 a9b3f29d Antony Chazapis
    
402 a9b3f29d Antony Chazapis
    @backend_method
403 a9b3f29d Antony Chazapis
    def get_container_policy(self, user, account, container):
404 a9b3f29d Antony Chazapis
        """Return a dictionary with the container policy."""
405 a9b3f29d Antony Chazapis
        
406 fe2db49d Sofia Papagiannaki
        logger.debug("get_container_policy: %s %s %s", user, account, container)
407 a9b3f29d Antony Chazapis
        if user != account:
408 a9b3f29d Antony Chazapis
            if container not in self._allowed_containers(user, account):
409 a9b3f29d Antony Chazapis
                raise NotAllowedError
410 a9b3f29d Antony Chazapis
            return {}
411 5e7485da Antony Chazapis
        path, node = self._lookup_container(account, container)
412 b9064632 Antony Chazapis
        return self._get_policy(node)
413 a9b3f29d Antony Chazapis
    
414 a9b3f29d Antony Chazapis
    @backend_method
415 a9b3f29d Antony Chazapis
    def update_container_policy(self, user, account, container, policy, replace=False):
416 b2832c6a Antony Chazapis
        """Update the policy associated with the container."""
417 a9b3f29d Antony Chazapis
        
418 fe2db49d Sofia Papagiannaki
        logger.debug("update_container_policy: %s %s %s %s %s", user, account, container, policy, replace)
419 a9b3f29d Antony Chazapis
        if user != account:
420 a9b3f29d Antony Chazapis
            raise NotAllowedError
421 5e7485da Antony Chazapis
        path, node = self._lookup_container(account, container)
422 a9b3f29d Antony Chazapis
        self._check_policy(policy)
423 b2832c6a Antony Chazapis
        self._put_policy(node, policy, replace)
424 a9b3f29d Antony Chazapis
    
425 a9b3f29d Antony Chazapis
    @backend_method
426 8693b873 Sofia Papagiannaki
    def put_container(self, user, account, container, policy={}):
427 a9b3f29d Antony Chazapis
        """Create a new container with the given name."""
428 a9b3f29d Antony Chazapis
        
429 fe2db49d Sofia Papagiannaki
        logger.debug("put_container: %s %s %s %s", user, account, container, policy)
430 a9b3f29d Antony Chazapis
        if user != account:
431 a9b3f29d Antony Chazapis
            raise NotAllowedError
432 a9b3f29d Antony Chazapis
        try:
433 c915d3bf Antony Chazapis
            path, node = self._lookup_container(account, container)
434 a9b3f29d Antony Chazapis
        except NameError:
435 a9b3f29d Antony Chazapis
            pass
436 a9b3f29d Antony Chazapis
        else:
437 a9b3f29d Antony Chazapis
            raise NameError('Container already exists')
438 a9b3f29d Antony Chazapis
        if policy:
439 a9b3f29d Antony Chazapis
            self._check_policy(policy)
440 a9b3f29d Antony Chazapis
        path = '/'.join((account, container))
441 5e7485da Antony Chazapis
        node = self._put_path(user, self._lookup_account(account, True)[1], path)
442 b2832c6a Antony Chazapis
        self._put_policy(node, policy, True)
443 a9b3f29d Antony Chazapis
    
444 a9b3f29d Antony Chazapis
    @backend_method
445 fe2db49d Sofia Papagiannaki
    def delete_container(self, user, account, container, until=None, prefix='', delimiter=None):
446 a9b3f29d Antony Chazapis
        """Delete/purge the container with the given name."""
447 a9b3f29d Antony Chazapis
        
448 fe2db49d Sofia Papagiannaki
        logger.debug("delete_container: %s %s %s %s %s %s", user, account, container, until, prefix, delimiter)
449 a9b3f29d Antony Chazapis
        if user != account:
450 a9b3f29d Antony Chazapis
            raise NotAllowedError
451 c915d3bf Antony Chazapis
        path, node = self._lookup_container(account, container)
452 a9b3f29d Antony Chazapis
        
453 a9b3f29d Antony Chazapis
        if until is not None:
454 813e42e5 Antony Chazapis
            hashes, size = self.node.node_purge_children(node, until, CLUSTER_HISTORY)
455 04230536 Antony Chazapis
            for h in hashes:
456 04230536 Antony Chazapis
                self.store.map_delete(h)
457 c915d3bf Antony Chazapis
            self.node.node_purge_children(node, until, CLUSTER_DELETED)
458 813e42e5 Antony Chazapis
            self._report_size_change(user, account, -size, {'action': 'container purge'})
459 a9b3f29d Antony Chazapis
            return
460 a9b3f29d Antony Chazapis
        
461 62f915a1 Antony Chazapis
        if self._get_statistics(node)[0] > 0:
462 a9b3f29d Antony Chazapis
            raise IndexError('Container is not empty')
463 813e42e5 Antony Chazapis
        hashes, size = self.node.node_purge_children(node, inf, CLUSTER_HISTORY)
464 04230536 Antony Chazapis
        for h in hashes:
465 04230536 Antony Chazapis
            self.store.map_delete(h)
466 62f915a1 Antony Chazapis
        self.node.node_purge_children(node, inf, CLUSTER_DELETED)
467 c915d3bf Antony Chazapis
        self.node.node_remove(node)
468 813e42e5 Antony Chazapis
        self._report_size_change(user, account, -size, {'action': 'container delete'})
469 a9b3f29d Antony Chazapis
    
470 90ee1eb3 Sofia Papagiannaki
    def _list_objects(self, user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, all_props, public):
471 15a96c3e Antony Chazapis
        if user != account and until:
472 15a96c3e Antony Chazapis
            raise NotAllowedError
473 cf4a7a7b Sofia Papagiannaki
        if shared and public:
474 cf4a7a7b Sofia Papagiannaki
            # get shared first
475 cf4a7a7b Sofia Papagiannaki
            shared = self._list_object_permissions(user, account, container, prefix, shared=True, public=False)
476 cf4a7a7b Sofia Papagiannaki
            objects = []
477 cf4a7a7b Sofia Papagiannaki
            if shared:
478 cf4a7a7b Sofia Papagiannaki
                path, node = self._lookup_container(account, container)
479 cf4a7a7b Sofia Papagiannaki
                shared = self._get_formatted_paths(shared)
480 cf4a7a7b Sofia Papagiannaki
                objects = self._list_object_properties(node, path, prefix, delimiter, marker, limit, virtual, domain, keys, until, size_range, shared, all_props)
481 cf4a7a7b Sofia Papagiannaki
            
482 cf4a7a7b Sofia Papagiannaki
            # get public
483 cf4a7a7b Sofia Papagiannaki
            objects.extend(self._list_public_object_properties(user, account, container, prefix, all_props))
484 cf4a7a7b Sofia Papagiannaki
            
485 cf4a7a7b Sofia Papagiannaki
            objects.sort(key=lambda x: x[0])
486 cf4a7a7b Sofia Papagiannaki
            start, limit = self._list_limits([x[0] for x in objects], marker, limit)
487 cf4a7a7b Sofia Papagiannaki
            return objects[start:start + limit]
488 cf4a7a7b Sofia Papagiannaki
        elif public:
489 cf4a7a7b Sofia Papagiannaki
            objects = self._list_public_object_properties(user, account, container, prefix, all_props)
490 cf4a7a7b Sofia Papagiannaki
            start, limit = self._list_limits([x[0] for x in objects], marker, limit)
491 cf4a7a7b Sofia Papagiannaki
            return objects[start:start + limit]
492 cf4a7a7b Sofia Papagiannaki
        
493 90ee1eb3 Sofia Papagiannaki
        allowed = self._list_object_permissions(user, account, container, prefix, shared, public)
494 cf4a7a7b Sofia Papagiannaki
        if shared and not allowed:
495 fcd37c40 Antony Chazapis
            return []
496 15a96c3e Antony Chazapis
        path, node = self._lookup_container(account, container)
497 15a96c3e Antony Chazapis
        allowed = self._get_formatted_paths(allowed)
498 cf4a7a7b Sofia Papagiannaki
        objects = self._list_object_properties(node, path, prefix, delimiter, marker, limit, virtual, domain, keys, until, size_range, allowed, all_props)
499 cf4a7a7b Sofia Papagiannaki
        start, limit = self._list_limits([x[0] for x in objects], marker, limit)
500 cf4a7a7b Sofia Papagiannaki
        return objects[start:start + limit]
501 15a96c3e Antony Chazapis
    
502 cf4a7a7b Sofia Papagiannaki
    def _list_public_object_properties(self, user, account, container, prefix, all_props):
503 cf4a7a7b Sofia Papagiannaki
        public = self._list_object_permissions(user, account, container, prefix, shared=False, public=True)
504 cf4a7a7b Sofia Papagiannaki
        paths, nodes = self._lookup_objects(public)
505 cf4a7a7b Sofia Papagiannaki
        path = '/'.join((account, container))
506 cf4a7a7b Sofia Papagiannaki
        cont_prefix = path + '/'
507 cf4a7a7b Sofia Papagiannaki
        paths = [x[len(cont_prefix):] for x in paths]
508 cf4a7a7b Sofia Papagiannaki
        props = self.node.version_lookup_bulk(nodes, all_props=all_props)
509 cf4a7a7b Sofia Papagiannaki
        objects = [(path,) + props for path, props in zip(paths, props)]
510 cf4a7a7b Sofia Papagiannaki
        return objects
511 cf4a7a7b Sofia Papagiannaki
        
512 4d15c94e Sofia Papagiannaki
    def _list_objects_no_limit(self, user, account, container, prefix, delimiter, virtual, domain, keys, shared, until, size_range, all_props, public):
513 4d15c94e Sofia Papagiannaki
        objects = []
514 4d15c94e Sofia Papagiannaki
        while True:
515 4d15c94e Sofia Papagiannaki
            marker = objects[-1] if objects else None
516 4d15c94e Sofia Papagiannaki
            limit = 10000
517 4d15c94e Sofia Papagiannaki
            l = self._list_objects(user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, all_props, public)
518 4d15c94e Sofia Papagiannaki
            objects.extend(l)
519 4d15c94e Sofia Papagiannaki
            if not l or len(l) < limit:
520 4d15c94e Sofia Papagiannaki
                break
521 4d15c94e Sofia Papagiannaki
        return objects
522 4d15c94e Sofia Papagiannaki
    
523 90ee1eb3 Sofia Papagiannaki
    def _list_object_permissions(self, user, account, container, prefix, shared, public):
524 62f915a1 Antony Chazapis
        allowed = []
525 fcd37c40 Antony Chazapis
        path = '/'.join((account, container, prefix)).rstrip('/')
526 62f915a1 Antony Chazapis
        if user != account:
527 fcd37c40 Antony Chazapis
            allowed = self.permissions.access_list_paths(user, path)
528 62f915a1 Antony Chazapis
            if not allowed:
529 62f915a1 Antony Chazapis
                raise NotAllowedError
530 62f915a1 Antony Chazapis
        else:
531 56ac7c81 Sofia Papagiannaki
            allowed = set()
532 62f915a1 Antony Chazapis
            if shared:
533 56ac7c81 Sofia Papagiannaki
                allowed.update(self.permissions.access_list_shared(path))
534 c53c4def Sofia Papagiannaki
            if public:
535 56ac7c81 Sofia Papagiannaki
                allowed.update([x[0] for x in self.permissions.public_list(path)])
536 56ac7c81 Sofia Papagiannaki
            allowed = sorted(allowed)
537 c53c4def Sofia Papagiannaki
            if not allowed:
538 c53c4def Sofia Papagiannaki
                return []
539 15a96c3e Antony Chazapis
        return allowed
540 62f915a1 Antony Chazapis
    
541 62f915a1 Antony Chazapis
    @backend_method
542 90ee1eb3 Sofia Papagiannaki
    def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, domain=None, keys=[], shared=False, until=None, size_range=None, public=False):
543 371d907a Antony Chazapis
        """Return a list of object (name, version_id) tuples existing under a container."""
544 62f915a1 Antony Chazapis
        
545 fe2db49d Sofia Papagiannaki
        logger.debug("list_objects: %s %s %s %s %s %s %s %s %s %s %s %s %s %s", user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, public)
546 90ee1eb3 Sofia Papagiannaki
        return self._list_objects(user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, False, public)
547 371d907a Antony Chazapis
    
548 371d907a Antony Chazapis
    @backend_method
549 90ee1eb3 Sofia Papagiannaki
    def list_object_meta(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, domain=None, keys=[], shared=False, until=None, size_range=None, public=False):
550 371d907a Antony Chazapis
        """Return a list of object metadata dicts existing under a container."""
551 371d907a Antony Chazapis
        
552 fe2db49d Sofia Papagiannaki
        logger.debug("list_object_meta: %s %s %s %s %s %s %s %s %s %s %s %s %s %s", user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, public)
553 90ee1eb3 Sofia Papagiannaki
        props = self._list_objects(user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, True, public)
554 371d907a Antony Chazapis
        objects = []
555 371d907a Antony Chazapis
        for p in props:
556 371d907a Antony Chazapis
            if len(p) == 2:
557 371d907a Antony Chazapis
                objects.append({'subdir': p[0]})
558 371d907a Antony Chazapis
            else:
559 371d907a Antony Chazapis
                objects.append({'name': p[0],
560 371d907a Antony Chazapis
                                'bytes': p[self.SIZE + 1],
561 371d907a Antony Chazapis
                                'type': p[self.TYPE + 1],
562 371d907a Antony Chazapis
                                'hash': p[self.HASH + 1],
563 371d907a Antony Chazapis
                                'version': p[self.SERIAL + 1],
564 371d907a Antony Chazapis
                                'version_timestamp': p[self.MTIME + 1],
565 371d907a Antony Chazapis
                                'modified': p[self.MTIME + 1] if until is None else None,
566 371d907a Antony Chazapis
                                'modified_by': p[self.MUSER + 1],
567 371d907a Antony Chazapis
                                'uuid': p[self.UUID + 1],
568 371d907a Antony Chazapis
                                'checksum': p[self.CHECKSUM + 1]})
569 371d907a Antony Chazapis
        return objects
570 a9b3f29d Antony Chazapis
    
571 a9b3f29d Antony Chazapis
    @backend_method
572 15a96c3e Antony Chazapis
    def list_object_permissions(self, user, account, container, prefix=''):
573 15a96c3e Antony Chazapis
        """Return a list of paths that enforce permissions under a container."""
574 15a96c3e Antony Chazapis
        
575 fe2db49d Sofia Papagiannaki
        logger.debug("list_object_permissions: %s %s %s %s", user, account, container, prefix)
576 90ee1eb3 Sofia Papagiannaki
        return self._list_object_permissions(user, account, container, prefix, True, False)
577 15a96c3e Antony Chazapis
    
578 15a96c3e Antony Chazapis
    @backend_method
579 15a96c3e Antony Chazapis
    def list_object_public(self, user, account, container, prefix=''):
580 15a96c3e Antony Chazapis
        """Return a dict mapping paths to public ids for objects that are public under a container."""
581 15a96c3e Antony Chazapis
        
582 fe2db49d Sofia Papagiannaki
        logger.debug("list_object_public: %s %s %s %s", user, account, container, prefix)
583 15a96c3e Antony Chazapis
        public = {}
584 15a96c3e Antony Chazapis
        for path, p in self.permissions.public_list('/'.join((account, container, prefix))):
585 15a96c3e Antony Chazapis
            public[path] = p + ULTIMATE_ANSWER
586 15a96c3e Antony Chazapis
        return public
587 15a96c3e Antony Chazapis
    
588 15a96c3e Antony Chazapis
    @backend_method
589 82482e2c Antony Chazapis
    def get_object_meta(self, user, account, container, name, domain, version=None, include_user_defined=True):
590 cb69c154 Antony Chazapis
        """Return a dictionary with the object metadata for the domain."""
591 a9b3f29d Antony Chazapis
        
592 fe2db49d Sofia Papagiannaki
        logger.debug("get_object_meta: %s %s %s %s %s %s", user, account, container, name, domain, version)
593 a9b3f29d Antony Chazapis
        self._can_read(user, account, container, name)
594 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
595 c915d3bf Antony Chazapis
        props = self._get_version(node, version)
596 a9b3f29d Antony Chazapis
        if version is None:
597 2c5363a0 Antony Chazapis
            modified = props[self.MTIME]
598 a9b3f29d Antony Chazapis
        else:
599 97d45f69 Antony Chazapis
            try:
600 97d45f69 Antony Chazapis
                modified = self._get_version(node)[self.MTIME] # Overall last modification.
601 97d45f69 Antony Chazapis
            except NameError: # Object may be deleted.
602 97d45f69 Antony Chazapis
                del_props = self.node.version_lookup(node, inf, CLUSTER_DELETED)
603 97d45f69 Antony Chazapis
                if del_props is None:
604 97d45f69 Antony Chazapis
                    raise NameError('Object does not exist')
605 97d45f69 Antony Chazapis
                modified = del_props[self.MTIME]
606 a9b3f29d Antony Chazapis
        
607 82482e2c Antony Chazapis
        meta = {}
608 82482e2c Antony Chazapis
        if include_user_defined:
609 82482e2c Antony Chazapis
            meta.update(dict(self.node.attribute_get(props[self.SERIAL], domain)))
610 33b4e4a6 Antony Chazapis
        meta.update({'name': name,
611 33b4e4a6 Antony Chazapis
                     'bytes': props[self.SIZE],
612 33b4e4a6 Antony Chazapis
                     'type': props[self.TYPE],
613 371d907a Antony Chazapis
                     'hash': props[self.HASH],
614 33b4e4a6 Antony Chazapis
                     'version': props[self.SERIAL],
615 33b4e4a6 Antony Chazapis
                     'version_timestamp': props[self.MTIME],
616 33b4e4a6 Antony Chazapis
                     'modified': modified,
617 33b4e4a6 Antony Chazapis
                     'modified_by': props[self.MUSER],
618 33b4e4a6 Antony Chazapis
                     'uuid': props[self.UUID],
619 33b4e4a6 Antony Chazapis
                     'checksum': props[self.CHECKSUM]})
620 a9b3f29d Antony Chazapis
        return meta
621 a9b3f29d Antony Chazapis
    
622 a9b3f29d Antony Chazapis
    @backend_method
623 cb69c154 Antony Chazapis
    def update_object_meta(self, user, account, container, name, domain, meta, replace=False):
624 cb69c154 Antony Chazapis
        """Update the metadata associated with the object for the domain and return the new version."""
625 a9b3f29d Antony Chazapis
        
626 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_meta: %s %s %s %s %s %s %s", user, account, container, name, domain, meta, replace)
627 a9b3f29d Antony Chazapis
        self._can_write(user, account, container, name)
628 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
629 4819d34f Antony Chazapis
        src_version_id, dest_version_id = self._put_metadata(user, node, domain, meta, replace)
630 5cc484e1 Antony Chazapis
        self._apply_versioning(account, container, src_version_id)
631 5cc484e1 Antony Chazapis
        return dest_version_id
632 a9b3f29d Antony Chazapis
    
633 a9b3f29d Antony Chazapis
    @backend_method
634 a9b3f29d Antony Chazapis
    def get_object_permissions(self, user, account, container, name):
635 067cf1fc Antony Chazapis
        """Return the action allowed on the object, the path
636 067cf1fc Antony Chazapis
        from which the object gets its permissions from,
637 a9b3f29d Antony Chazapis
        along with a dictionary containing the permissions."""
638 a9b3f29d Antony Chazapis
        
639 fe2db49d Sofia Papagiannaki
        logger.debug("get_object_permissions: %s %s %s %s", user, account, container, name)
640 067cf1fc Antony Chazapis
        allowed = 'write'
641 92da0e5a Antony Chazapis
        permissions_path = self._get_permissions_path(account, container, name)
642 067cf1fc Antony Chazapis
        if user != account:
643 92da0e5a Antony Chazapis
            if self.permissions.access_check(permissions_path, self.WRITE, user):
644 067cf1fc Antony Chazapis
                allowed = 'write'
645 92da0e5a Antony Chazapis
            elif self.permissions.access_check(permissions_path, self.READ, user):
646 067cf1fc Antony Chazapis
                allowed = 'read'
647 067cf1fc Antony Chazapis
            else:
648 067cf1fc Antony Chazapis
                raise NotAllowedError
649 92da0e5a Antony Chazapis
        self._lookup_object(account, container, name)
650 92da0e5a Antony Chazapis
        return (allowed, permissions_path, self.permissions.access_get(permissions_path))
651 a9b3f29d Antony Chazapis
    
652 a9b3f29d Antony Chazapis
    @backend_method
653 a9b3f29d Antony Chazapis
    def update_object_permissions(self, user, account, container, name, permissions):
654 a9b3f29d Antony Chazapis
        """Update the permissions associated with the object."""
655 a9b3f29d Antony Chazapis
        
656 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_permissions: %s %s %s %s %s", user, account, container, name, permissions)
657 a9b3f29d Antony Chazapis
        if user != account:
658 a9b3f29d Antony Chazapis
            raise NotAllowedError
659 c915d3bf Antony Chazapis
        path = self._lookup_object(account, container, name)[0]
660 6f4bce7b Antony Chazapis
        self._check_permissions(path, permissions)
661 0f9d752c Antony Chazapis
        self.permissions.access_set(path, permissions)
662 a74ba506 Sofia Papagiannaki
        self._report_sharing_change(user, account, path, {'members':self.permissions.access_members(path)})
663 a9b3f29d Antony Chazapis
    
664 a9b3f29d Antony Chazapis
    @backend_method
665 a9b3f29d Antony Chazapis
    def get_object_public(self, user, account, container, name):
666 bb4eafc6 Antony Chazapis
        """Return the public id of the object if applicable."""
667 a9b3f29d Antony Chazapis
        
668 fe2db49d Sofia Papagiannaki
        logger.debug("get_object_public: %s %s %s %s", user, account, container, name)
669 a9b3f29d Antony Chazapis
        self._can_read(user, account, container, name)
670 c915d3bf Antony Chazapis
        path = self._lookup_object(account, container, name)[0]
671 bb4eafc6 Antony Chazapis
        p = self.permissions.public_get(path)
672 bb4eafc6 Antony Chazapis
        if p is not None:
673 bb4eafc6 Antony Chazapis
            p += ULTIMATE_ANSWER
674 bb4eafc6 Antony Chazapis
        return p
675 a9b3f29d Antony Chazapis
    
676 a9b3f29d Antony Chazapis
    @backend_method
677 a9b3f29d Antony Chazapis
    def update_object_public(self, user, account, container, name, public):
678 a9b3f29d Antony Chazapis
        """Update the public status of the object."""
679 a9b3f29d Antony Chazapis
        
680 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_public: %s %s %s %s %s", user, account, container, name, public)
681 a9b3f29d Antony Chazapis
        self._can_write(user, account, container, name)
682 c915d3bf Antony Chazapis
        path = self._lookup_object(account, container, name)[0]
683 0f9d752c Antony Chazapis
        if not public:
684 0f9d752c Antony Chazapis
            self.permissions.public_unset(path)
685 0f9d752c Antony Chazapis
        else:
686 0f9d752c Antony Chazapis
            self.permissions.public_set(path)
687 a9b3f29d Antony Chazapis
    
688 a9b3f29d Antony Chazapis
    @backend_method
689 a9b3f29d Antony Chazapis
    def get_object_hashmap(self, user, account, container, name, version=None):
690 a9b3f29d Antony Chazapis
        """Return the object's size and a list with partial hashes."""
691 a9b3f29d Antony Chazapis
        
692 fe2db49d Sofia Papagiannaki
        logger.debug("get_object_hashmap: %s %s %s %s %s", user, account, container, name, version)
693 a9b3f29d Antony Chazapis
        self._can_read(user, account, container, name)
694 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
695 c915d3bf Antony Chazapis
        props = self._get_version(node, version)
696 7ca7bb08 Antony Chazapis
        hashmap = self.store.map_get(binascii.unhexlify(props[self.HASH]))
697 2c5363a0 Antony Chazapis
        return props[self.SIZE], [binascii.hexlify(x) for x in hashmap]
698 a9b3f29d Antony Chazapis
    
699 f9ea264b Antony Chazapis
    def _update_object_hash(self, user, account, container, name, size, type, hash, checksum, domain, meta, replace_meta, permissions, src_node=None, src_version_id=None, is_copy=False):
700 b9064632 Antony Chazapis
        if permissions is not None and user != account:
701 b9064632 Antony Chazapis
            raise NotAllowedError
702 b9064632 Antony Chazapis
        self._can_write(user, account, container, name)
703 b9064632 Antony Chazapis
        if permissions is not None:
704 b9064632 Antony Chazapis
            path = '/'.join((account, container, name))
705 b9064632 Antony Chazapis
            self._check_permissions(path, permissions)
706 b9064632 Antony Chazapis
        
707 b9064632 Antony Chazapis
        account_path, account_node = self._lookup_account(account, True)
708 b9064632 Antony Chazapis
        container_path, container_node = self._lookup_container(account, container)
709 b9064632 Antony Chazapis
        path, node = self._put_object_node(container_path, container_node, name)
710 33b4e4a6 Antony Chazapis
        pre_version_id, dest_version_id = self._put_version_duplicate(user, node, src_node=src_node, size=size, type=type, hash=hash, checksum=checksum, is_copy=is_copy)
711 b9064632 Antony Chazapis
        
712 f9ea264b Antony Chazapis
        # Handle meta.
713 f9ea264b Antony Chazapis
        if src_version_id is None:
714 f9ea264b Antony Chazapis
            src_version_id = pre_version_id
715 f9ea264b Antony Chazapis
        self._put_metadata_duplicate(src_version_id, dest_version_id, domain, meta, replace_meta)
716 f9ea264b Antony Chazapis
        
717 b9064632 Antony Chazapis
        # Check quota.
718 813e42e5 Antony Chazapis
        del_size = self._apply_versioning(account, container, pre_version_id)
719 813e42e5 Antony Chazapis
        size_delta = size - del_size
720 b9064632 Antony Chazapis
        if size_delta > 0:
721 8693b873 Sofia Papagiannaki
            account_quota = long(self._get_policy(account_node)['quota'])
722 8693b873 Sofia Papagiannaki
            container_quota = long(self._get_policy(container_node)['quota'])
723 0df22aea Sofia Papagiannaki
            if (account_quota > 0 and self._get_statistics(account_node)[1] + size_delta > account_quota) or \
724 0df22aea Sofia Papagiannaki
               (container_quota > 0 and self._get_statistics(container_node)[1] + size_delta > container_quota):
725 b9064632 Antony Chazapis
                # This must be executed in a transaction, so the version is never created if it fails.
726 5df6c6d1 Antony Chazapis
                raise QuotaError
727 813e42e5 Antony Chazapis
        self._report_size_change(user, account, size_delta, {'action': 'object update'})
728 b9064632 Antony Chazapis
        
729 b9064632 Antony Chazapis
        if permissions is not None:
730 b9064632 Antony Chazapis
            self.permissions.access_set(path, permissions)
731 a74ba506 Sofia Papagiannaki
            self._report_sharing_change(user, account, path, {'members':self.permissions.access_members(path)})
732 39ef6f41 Antony Chazapis
        
733 39ef6f41 Antony Chazapis
        self._report_object_change(user, account, path, details={'version': dest_version_id, 'action': 'object update'})
734 f9ea264b Antony Chazapis
        return dest_version_id
735 b9064632 Antony Chazapis
    
736 a9b3f29d Antony Chazapis
    @backend_method
737 33b4e4a6 Antony Chazapis
    def update_object_hashmap(self, user, account, container, name, size, type, hashmap, checksum, domain, meta={}, replace_meta=False, permissions=None):
738 a9b3f29d Antony Chazapis
        """Create/update an object with the specified size and partial hashes."""
739 a9b3f29d Antony Chazapis
        
740 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_hashmap: %s %s %s %s %s %s %s %s", user, account, container, name, size, type, hashmap, checksum)
741 6d64339e Antony Chazapis
        if size == 0: # No such thing as an empty hashmap.
742 6d64339e Antony Chazapis
            hashmap = [self.put_block('')]
743 1c2fc0ff Antony Chazapis
        map = HashMap(self.block_size, self.hash_algorithm)
744 1c2fc0ff Antony Chazapis
        map.extend([binascii.unhexlify(x) for x in hashmap])
745 7ca7bb08 Antony Chazapis
        missing = self.store.block_search(map)
746 a9b3f29d Antony Chazapis
        if missing:
747 a9b3f29d Antony Chazapis
            ie = IndexError()
748 dd71f493 Antony Chazapis
            ie.data = [binascii.hexlify(x) for x in missing]
749 a9b3f29d Antony Chazapis
            raise ie
750 b9064632 Antony Chazapis
        
751 1c2fc0ff Antony Chazapis
        hash = map.hash()
752 f9ea264b Antony Chazapis
        dest_version_id = self._update_object_hash(user, account, container, name, size, type, binascii.hexlify(hash), checksum, domain, meta, replace_meta, permissions)
753 7ca7bb08 Antony Chazapis
        self.store.map_put(hash, map)
754 02c4d2ba Antony Chazapis
        return dest_version_id
755 a9b3f29d Antony Chazapis
    
756 33b4e4a6 Antony Chazapis
    @backend_method
757 33b4e4a6 Antony Chazapis
    def update_object_checksum(self, user, account, container, name, version, checksum):
758 33b4e4a6 Antony Chazapis
        """Update an object's checksum."""
759 33b4e4a6 Antony Chazapis
        
760 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_checksum: %s %s %s %s %s %s", user, account, container, name, version, checksum)
761 33b4e4a6 Antony Chazapis
        # Update objects with greater version and same hashmap and size (fix metadata updates).
762 33b4e4a6 Antony Chazapis
        self._can_write(user, account, container, name)
763 33b4e4a6 Antony Chazapis
        path, node = self._lookup_object(account, container, name)
764 33b4e4a6 Antony Chazapis
        props = self._get_version(node, version)
765 33b4e4a6 Antony Chazapis
        versions = self.node.node_get_versions(node)
766 33b4e4a6 Antony Chazapis
        for x in versions:
767 33b4e4a6 Antony Chazapis
            if x[self.SERIAL] >= int(version) and x[self.HASH] == props[self.HASH] and x[self.SIZE] == props[self.SIZE]:
768 33b4e4a6 Antony Chazapis
                self.node.version_put_property(x[self.SERIAL], 'checksum', checksum)
769 33b4e4a6 Antony Chazapis
    
770 4d15c94e Sofia Papagiannaki
    def _copy_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, type, dest_domain=None, dest_meta={}, replace_meta=False, permissions=None, src_version=None, is_move=False, delimiter=None):
771 4d15c94e Sofia Papagiannaki
        dest_version_ids = []
772 79bb41b7 Antony Chazapis
        self._can_read(user, src_account, src_container, src_name)
773 b9064632 Antony Chazapis
        path, node = self._lookup_object(src_account, src_container, src_name)
774 1730b3bf chazapis
        # TODO: Will do another fetch of the properties in duplicate version...
775 1730b3bf chazapis
        props = self._get_version(node, src_version) # Check to see if source exists.
776 b9064632 Antony Chazapis
        src_version_id = props[self.SERIAL]
777 b9064632 Antony Chazapis
        hash = props[self.HASH]
778 b9064632 Antony Chazapis
        size = props[self.SIZE]
779 25ae8b75 Antony Chazapis
        is_copy = not is_move and (src_account, src_container, src_name) != (dest_account, dest_container, dest_name) # New uuid.
780 4d15c94e Sofia Papagiannaki
        dest_version_ids.append(self._update_object_hash(user, dest_account, dest_container, dest_name, size, type, hash, None, dest_domain, dest_meta, replace_meta, permissions, src_node=node, src_version_id=src_version_id, is_copy=is_copy))
781 3f767854 Sofia Papagiannaki
        if is_move and (src_account, src_container, src_name) != (dest_account, dest_container, dest_name):
782 4d15c94e Sofia Papagiannaki
                self._delete_object(user, src_account, src_container, src_name)
783 4d15c94e Sofia Papagiannaki
        
784 4d15c94e Sofia Papagiannaki
        if delimiter:
785 4d15c94e Sofia Papagiannaki
            prefix = src_name + delimiter if not src_name.endswith(delimiter) else src_name
786 4d15c94e Sofia Papagiannaki
            src_names = self._list_objects_no_limit(user, src_account, src_container, prefix, delimiter=None, virtual=True, domain=None, keys=[], shared=False, until=None, size_range=None, all_props=True, public=False)
787 4d15c94e Sofia Papagiannaki
            paths = [elem[0] for elem in src_names]
788 4d15c94e Sofia Papagiannaki
            nodes = [elem[2] for elem in src_names]
789 4d15c94e Sofia Papagiannaki
            # TODO: Will do another fetch of the properties in duplicate version...
790 4d15c94e Sofia Papagiannaki
            props = self._get_versions(nodes) # Check to see if source exists.
791 4d15c94e Sofia Papagiannaki
            
792 4d15c94e Sofia Papagiannaki
            for prop, path, node in zip(props, paths, nodes):
793 4d15c94e Sofia Papagiannaki
                src_version_id = prop[self.SERIAL]
794 4d15c94e Sofia Papagiannaki
                hash = prop[self.HASH]
795 4d15c94e Sofia Papagiannaki
                vtype = prop[self.TYPE]
796 4d15c94e Sofia Papagiannaki
                dest_prefix = dest_name + delimiter if not dest_name.endswith(delimiter) else dest_name
797 4d15c94e Sofia Papagiannaki
                vdest_name = path.replace(prefix, dest_prefix, 1)
798 4d15c94e Sofia Papagiannaki
                dest_version_ids.append(self._update_object_hash(user, dest_account, dest_container, vdest_name, size, vtype, hash, None, dest_domain, dest_meta, replace_meta, permissions, src_node=node, src_version_id=src_version_id, is_copy=is_copy))
799 3f767854 Sofia Papagiannaki
                if is_move and (src_account, src_container, src_name) != (dest_account, dest_container, dest_name):
800 4d15c94e Sofia Papagiannaki
                        self._delete_object(user, src_account, src_container, path)
801 4d15c94e Sofia Papagiannaki
        return dest_version_ids[0] if len(dest_version_ids) == 1 else dest_version_ids
802 4d15c94e Sofia Papagiannaki
    
803 4d15c94e Sofia Papagiannaki
    @backend_method
804 4d15c94e Sofia Papagiannaki
    def copy_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, type, domain, meta={}, replace_meta=False, permissions=None, src_version=None, delimiter=None):
805 dff7b6f1 Sofia Papagiannaki
        """Copy an object's data and metadata."""
806 dff7b6f1 Sofia Papagiannaki
        
807 4d15c94e Sofia Papagiannaki
        logger.debug("copy_object: %s %s %s %s %s %s %s %s %s %s %s %s %s %s", user, src_account, src_container, src_name, dest_account, dest_container, dest_name, type, domain, meta, replace_meta, permissions, src_version, delimiter)
808 4d15c94e Sofia Papagiannaki
        dest_version_id = self._copy_object(user, src_account, src_container, src_name, dest_account, dest_container, dest_name, type, domain, meta, replace_meta, permissions, src_version, False, delimiter)
809 46286f5f Antony Chazapis
        return dest_version_id
810 dff7b6f1 Sofia Papagiannaki
    
811 dff7b6f1 Sofia Papagiannaki
    @backend_method
812 4d15c94e Sofia Papagiannaki
    def move_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, type, domain, meta={}, replace_meta=False, permissions=None, delimiter=None):
813 a9b3f29d Antony Chazapis
        """Move an object's data and metadata."""
814 a9b3f29d Antony Chazapis
        
815 4d15c94e Sofia Papagiannaki
        logger.debug("move_object: %s %s %s %s %s %s %s %s %s %s %s %s %s", user, src_account, src_container, src_name, dest_account, dest_container, dest_name, type, domain, meta, replace_meta, permissions, delimiter)
816 79bb41b7 Antony Chazapis
        if user != src_account:
817 79bb41b7 Antony Chazapis
            raise NotAllowedError
818 4d15c94e Sofia Papagiannaki
        dest_version_id = self._copy_object(user, src_account, src_container, src_name, dest_account, dest_container, dest_name, type, domain, meta, replace_meta, permissions, None, True, delimiter)
819 02c4d2ba Antony Chazapis
        return dest_version_id
820 a9b3f29d Antony Chazapis
    
821 4d15c94e Sofia Papagiannaki
    def _delete_object(self, user, account, container, name, until=None, delimiter=None):
822 a9b3f29d Antony Chazapis
        if user != account:
823 a9b3f29d Antony Chazapis
            raise NotAllowedError
824 a9b3f29d Antony Chazapis
        
825 a9b3f29d Antony Chazapis
        if until is not None:
826 a9b3f29d Antony Chazapis
            path = '/'.join((account, container, name))
827 c915d3bf Antony Chazapis
            node = self.node.node_lookup(path)
828 c915d3bf Antony Chazapis
            if node is None:
829 c915d3bf Antony Chazapis
                return
830 813e42e5 Antony Chazapis
            hashes = []
831 813e42e5 Antony Chazapis
            size = 0
832 813e42e5 Antony Chazapis
            h, s = self.node.node_purge(node, until, CLUSTER_NORMAL)
833 813e42e5 Antony Chazapis
            hashes += h
834 813e42e5 Antony Chazapis
            size += s
835 813e42e5 Antony Chazapis
            h, s = self.node.node_purge(node, until, CLUSTER_HISTORY)
836 813e42e5 Antony Chazapis
            hashes += h
837 813e42e5 Antony Chazapis
            size += s
838 04230536 Antony Chazapis
            for h in hashes:
839 04230536 Antony Chazapis
                self.store.map_delete(h)
840 4a1c29ea Antony Chazapis
            self.node.node_purge(node, until, CLUSTER_DELETED)
841 a9b3f29d Antony Chazapis
            try:
842 c915d3bf Antony Chazapis
                props = self._get_version(node)
843 a9b3f29d Antony Chazapis
            except NameError:
844 0f9d752c Antony Chazapis
                self.permissions.access_clear(path)
845 813e42e5 Antony Chazapis
            self._report_size_change(user, account, -size, {'action': 'object purge'})
846 a9b3f29d Antony Chazapis
            return
847 a9b3f29d Antony Chazapis
        
848 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
849 33b4e4a6 Antony Chazapis
        src_version_id, dest_version_id = self._put_version_duplicate(user, node, size=0, type='', hash=None, checksum='', cluster=CLUSTER_DELETED)
850 813e42e5 Antony Chazapis
        del_size = self._apply_versioning(account, container, src_version_id)
851 813e42e5 Antony Chazapis
        if del_size:
852 813e42e5 Antony Chazapis
            self._report_size_change(user, account, -del_size, {'action': 'object delete'})
853 39ef6f41 Antony Chazapis
        self._report_object_change(user, account, path, details={'action': 'object delete'})
854 0f9d752c Antony Chazapis
        self.permissions.access_clear(path)
855 4d15c94e Sofia Papagiannaki
        
856 4d15c94e Sofia Papagiannaki
        if delimiter:
857 4d15c94e Sofia Papagiannaki
            prefix = name + delimiter if not name.endswith(delimiter) else name
858 4d15c94e Sofia Papagiannaki
            src_names = self._list_objects_no_limit(user, account, container, prefix, delimiter=None, virtual=True, domain=None, keys=[], shared=False, until=None, size_range=None, all_props=True, public=False)
859 4d15c94e Sofia Papagiannaki
            paths = []
860 4d15c94e Sofia Papagiannaki
            for t in src_names:
861 4d15c94e Sofia Papagiannaki
                    path = '/'.join((account, container, t[0]))
862 4d15c94e Sofia Papagiannaki
                    node = t[2]
863 4d15c94e Sofia Papagiannaki
                src_version_id, dest_version_id = self._put_version_duplicate(user, node, size=0, type='', hash=None, checksum='', cluster=CLUSTER_DELETED)
864 4d15c94e Sofia Papagiannaki
                del_size = self._apply_versioning(account, container, src_version_id)
865 4d15c94e Sofia Papagiannaki
                if del_size:
866 4d15c94e Sofia Papagiannaki
                    self._report_size_change(user, account, -del_size, {'action': 'object delete'})
867 4d15c94e Sofia Papagiannaki
                self._report_object_change(user, account, path, details={'action': 'object delete'})
868 4d15c94e Sofia Papagiannaki
                paths.append(path)
869 4d15c94e Sofia Papagiannaki
            self.permissions.access_clear_bulk(paths)
870 4d15c94e Sofia Papagiannaki
    
871 4d15c94e Sofia Papagiannaki
    @backend_method
872 4d15c94e Sofia Papagiannaki
    def delete_object(self, user, account, container, name, until=None, prefix='', delimiter=None):
873 dff7b6f1 Sofia Papagiannaki
        """Delete/purge an object."""
874 dff7b6f1 Sofia Papagiannaki
        
875 fe2db49d Sofia Papagiannaki
        logger.debug("delete_object: %s %s %s %s %s %s %s", user, account, container, name, until, prefix, delimiter)
876 4d15c94e Sofia Papagiannaki
        self._delete_object(user, account, container, name, until, delimiter)
877 dff7b6f1 Sofia Papagiannaki
    
878 dff7b6f1 Sofia Papagiannaki
    @backend_method
879 62f915a1 Antony Chazapis
    def list_versions(self, user, account, container, name):
880 62f915a1 Antony Chazapis
        """Return a list of all (version, version_timestamp) tuples for an object."""
881 62f915a1 Antony Chazapis
        
882 fe2db49d Sofia Papagiannaki
        logger.debug("list_versions: %s %s %s %s", user, account, container, name)
883 62f915a1 Antony Chazapis
        self._can_read(user, account, container, name)
884 60b8a083 Antony Chazapis
        path, node = self._lookup_object(account, container, name)
885 97d45f69 Antony Chazapis
        versions = self.node.node_get_versions(node)
886 97d45f69 Antony Chazapis
        return [[x[self.SERIAL], x[self.MTIME]] for x in versions if x[self.CLUSTER] != CLUSTER_DELETED]
887 a9b3f29d Antony Chazapis
    
888 bb4eafc6 Antony Chazapis
    @backend_method
889 37bee317 Antony Chazapis
    def get_uuid(self, user, uuid):
890 37bee317 Antony Chazapis
        """Return the (account, container, name) for the UUID given."""
891 af75e8a5 Antony Chazapis
        
892 fe2db49d Sofia Papagiannaki
        logger.debug("get_uuid: %s %s", user, uuid)
893 37bee317 Antony Chazapis
        info = self.node.latest_uuid(uuid)
894 37bee317 Antony Chazapis
        if info is None:
895 37bee317 Antony Chazapis
            raise NameError
896 37bee317 Antony Chazapis
        path, serial = info
897 37bee317 Antony Chazapis
        account, container, name = path.split('/', 2)
898 37bee317 Antony Chazapis
        self._can_read(user, account, container, name)
899 37bee317 Antony Chazapis
        return (account, container, name)
900 37bee317 Antony Chazapis
    
901 37bee317 Antony Chazapis
    @backend_method
902 bb4eafc6 Antony Chazapis
    def get_public(self, user, public):
903 bb4eafc6 Antony Chazapis
        """Return the (account, container, name) for the public id given."""
904 af75e8a5 Antony Chazapis
        
905 fe2db49d Sofia Papagiannaki
        logger.debug("get_public: %s %s", user, public)
906 bb4eafc6 Antony Chazapis
        if public is None or public < ULTIMATE_ANSWER:
907 bb4eafc6 Antony Chazapis
            raise NameError
908 bb4eafc6 Antony Chazapis
        path = self.permissions.public_path(public - ULTIMATE_ANSWER)
909 37bee317 Antony Chazapis
        if path is None:
910 37bee317 Antony Chazapis
            raise NameError
911 bb4eafc6 Antony Chazapis
        account, container, name = path.split('/', 2)
912 bb4eafc6 Antony Chazapis
        self._can_read(user, account, container, name)
913 bb4eafc6 Antony Chazapis
        return (account, container, name)
914 bb4eafc6 Antony Chazapis
    
915 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
916 a9b3f29d Antony Chazapis
    def get_block(self, hash):
917 a9b3f29d Antony Chazapis
        """Return a block's data."""
918 a9b3f29d Antony Chazapis
        
919 a9b3f29d Antony Chazapis
        logger.debug("get_block: %s", hash)
920 7ca7bb08 Antony Chazapis
        block = self.store.block_get(binascii.unhexlify(hash))
921 7ca7bb08 Antony Chazapis
        if not block:
922 a9b3f29d Antony Chazapis
            raise NameError('Block does not exist')
923 7ca7bb08 Antony Chazapis
        return block
924 a9b3f29d Antony Chazapis
    
925 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
926 a9b3f29d Antony Chazapis
    def put_block(self, data):
927 60b8a083 Antony Chazapis
        """Store a block and return the hash."""
928 a9b3f29d Antony Chazapis
        
929 a9b3f29d Antony Chazapis
        logger.debug("put_block: %s", len(data))
930 7ca7bb08 Antony Chazapis
        return binascii.hexlify(self.store.block_put(data))
931 a9b3f29d Antony Chazapis
    
932 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
933 a9b3f29d Antony Chazapis
    def update_block(self, hash, data, offset=0):
934 a9b3f29d Antony Chazapis
        """Update a known block and return the hash."""
935 a9b3f29d Antony Chazapis
        
936 a9b3f29d Antony Chazapis
        logger.debug("update_block: %s %s %s", hash, len(data), offset)
937 a9b3f29d Antony Chazapis
        if offset == 0 and len(data) == self.block_size:
938 a9b3f29d Antony Chazapis
            return self.put_block(data)
939 7ca7bb08 Antony Chazapis
        h = self.store.block_update(binascii.unhexlify(hash), offset, data)
940 a9b3f29d Antony Chazapis
        return binascii.hexlify(h)
941 a9b3f29d Antony Chazapis
    
942 44ad5860 Antony Chazapis
    # Path functions.
943 44ad5860 Antony Chazapis
    
944 37bee317 Antony Chazapis
    def _generate_uuid(self):
945 37bee317 Antony Chazapis
        return str(uuidlib.uuid4())
946 25ae8b75 Antony Chazapis
    
947 b9064632 Antony Chazapis
    def _put_object_node(self, path, parent, name):
948 c915d3bf Antony Chazapis
        path = '/'.join((path, name))
949 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
950 c915d3bf Antony Chazapis
        if node is None:
951 c915d3bf Antony Chazapis
            node = self.node.node_create(parent, path)
952 c915d3bf Antony Chazapis
        return path, node
953 c915d3bf Antony Chazapis
    
954 62f915a1 Antony Chazapis
    def _put_path(self, user, parent, path):
955 62f915a1 Antony Chazapis
        node = self.node.node_create(parent, path)
956 33b4e4a6 Antony Chazapis
        self.node.version_create(node, None, 0, '', None, user, self._generate_uuid(), '', CLUSTER_NORMAL)
957 62f915a1 Antony Chazapis
        return node
958 62f915a1 Antony Chazapis
    
959 44ad5860 Antony Chazapis
    def _lookup_account(self, account, create=True):
960 44ad5860 Antony Chazapis
        node = self.node.node_lookup(account)
961 44ad5860 Antony Chazapis
        if node is None and create:
962 2c5363a0 Antony Chazapis
            node = self._put_path(account, self.ROOTNODE, account) # User is account.
963 c915d3bf Antony Chazapis
        return account, node
964 44ad5860 Antony Chazapis
    
965 44ad5860 Antony Chazapis
    def _lookup_container(self, account, container):
966 c915d3bf Antony Chazapis
        path = '/'.join((account, container))
967 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
968 44ad5860 Antony Chazapis
        if node is None:
969 44ad5860 Antony Chazapis
            raise NameError('Container does not exist')
970 c915d3bf Antony Chazapis
        return path, node
971 44ad5860 Antony Chazapis
    
972 44ad5860 Antony Chazapis
    def _lookup_object(self, account, container, name):
973 c915d3bf Antony Chazapis
        path = '/'.join((account, container, name))
974 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
975 44ad5860 Antony Chazapis
        if node is None:
976 44ad5860 Antony Chazapis
            raise NameError('Object does not exist')
977 c915d3bf Antony Chazapis
        return path, node
978 44ad5860 Antony Chazapis
    
979 cf4a7a7b Sofia Papagiannaki
    def _lookup_objects(self, paths):
980 cf4a7a7b Sofia Papagiannaki
            nodes = self.node.node_lookup_bulk(paths)
981 cf4a7a7b Sofia Papagiannaki
        if nodes is None:
982 cf4a7a7b Sofia Papagiannaki
            raise NameError('Object does not exist')
983 cf4a7a7b Sofia Papagiannaki
        return paths, nodes
984 cf4a7a7b Sofia Papagiannaki
    
985 44ad5860 Antony Chazapis
    def _get_properties(self, node, until=None):
986 44ad5860 Antony Chazapis
        """Return properties until the timestamp given."""
987 44ad5860 Antony Chazapis
        
988 44ad5860 Antony Chazapis
        before = until if until is not None else inf
989 44ad5860 Antony Chazapis
        props = self.node.version_lookup(node, before, CLUSTER_NORMAL)
990 44ad5860 Antony Chazapis
        if props is None and until is not None:
991 44ad5860 Antony Chazapis
            props = self.node.version_lookup(node, before, CLUSTER_HISTORY)
992 44ad5860 Antony Chazapis
        if props is None:
993 44ad5860 Antony Chazapis
            raise NameError('Path does not exist')
994 44ad5860 Antony Chazapis
        return props
995 44ad5860 Antony Chazapis
    
996 62f915a1 Antony Chazapis
    def _get_statistics(self, node, until=None):
997 62f915a1 Antony Chazapis
        """Return count, sum of size and latest timestamp of everything under node."""
998 c915d3bf Antony Chazapis
        
999 44ad5860 Antony Chazapis
        if until is None:
1000 62f915a1 Antony Chazapis
            stats = self.node.statistics_get(node, CLUSTER_NORMAL)
1001 44ad5860 Antony Chazapis
        else:
1002 62f915a1 Antony Chazapis
            stats = self.node.statistics_latest(node, until, CLUSTER_DELETED)
1003 62f915a1 Antony Chazapis
        if stats is None:
1004 62f915a1 Antony Chazapis
            stats = (0, 0, 0)
1005 62f915a1 Antony Chazapis
        return stats
1006 44ad5860 Antony Chazapis
    
1007 44ad5860 Antony Chazapis
    def _get_version(self, node, version=None):
1008 44ad5860 Antony Chazapis
        if version is None:
1009 44ad5860 Antony Chazapis
            props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1010 44ad5860 Antony Chazapis
            if props is None:
1011 44ad5860 Antony Chazapis
                raise NameError('Object does not exist')
1012 44ad5860 Antony Chazapis
        else:
1013 07afd277 Antony Chazapis
            try:
1014 07afd277 Antony Chazapis
                version = int(version)
1015 07afd277 Antony Chazapis
            except ValueError:
1016 07afd277 Antony Chazapis
                raise IndexError('Version does not exist')
1017 44ad5860 Antony Chazapis
            props = self.node.version_get_properties(version)
1018 2c5363a0 Antony Chazapis
            if props is None or props[self.CLUSTER] == CLUSTER_DELETED:
1019 44ad5860 Antony Chazapis
                raise IndexError('Version does not exist')
1020 44ad5860 Antony Chazapis
        return props
1021 4d15c94e Sofia Papagiannaki
1022 4d15c94e Sofia Papagiannaki
    def _get_versions(self, nodes, version=None):
1023 4d15c94e Sofia Papagiannaki
        if version is None:
1024 4d15c94e Sofia Papagiannaki
            props = self.node.version_lookup_bulk(nodes, inf, CLUSTER_NORMAL)
1025 4d15c94e Sofia Papagiannaki
            if not props:
1026 4d15c94e Sofia Papagiannaki
                raise NameError('Object does not exist')
1027 4d15c94e Sofia Papagiannaki
        else:
1028 4d15c94e Sofia Papagiannaki
            try:
1029 4d15c94e Sofia Papagiannaki
                version = int(version)
1030 4d15c94e Sofia Papagiannaki
            except ValueError:
1031 4d15c94e Sofia Papagiannaki
                raise IndexError('Version does not exist')
1032 4d15c94e Sofia Papagiannaki
            props = self.node.version_get_properties(version)
1033 4d15c94e Sofia Papagiannaki
            if props is None or props[self.CLUSTER] == CLUSTER_DELETED:
1034 4d15c94e Sofia Papagiannaki
                raise IndexError('Version does not exist')
1035 4d15c94e Sofia Papagiannaki
        return props
1036 44ad5860 Antony Chazapis
    
1037 33b4e4a6 Antony Chazapis
    def _put_version_duplicate(self, user, node, src_node=None, size=None, type=None, hash=None, checksum=None, cluster=CLUSTER_NORMAL, is_copy=False):
1038 b9064632 Antony Chazapis
        """Create a new version of the node."""
1039 c915d3bf Antony Chazapis
        
1040 1730b3bf chazapis
        props = self.node.version_lookup(node if src_node is None else src_node, inf, CLUSTER_NORMAL)
1041 b9064632 Antony Chazapis
        if props is not None:
1042 b9064632 Antony Chazapis
            src_version_id = props[self.SERIAL]
1043 b9064632 Antony Chazapis
            src_hash = props[self.HASH]
1044 b9064632 Antony Chazapis
            src_size = props[self.SIZE]
1045 66ce2ca5 Antony Chazapis
            src_type = props[self.TYPE]
1046 33b4e4a6 Antony Chazapis
            src_checksum = props[self.CHECKSUM]
1047 44ad5860 Antony Chazapis
        else:
1048 b9064632 Antony Chazapis
            src_version_id = None
1049 b9064632 Antony Chazapis
            src_hash = None
1050 b9064632 Antony Chazapis
            src_size = 0
1051 66ce2ca5 Antony Chazapis
            src_type = ''
1052 33b4e4a6 Antony Chazapis
            src_checksum = ''
1053 66ce2ca5 Antony Chazapis
        if size is None: # Set metadata.
1054 66ce2ca5 Antony Chazapis
            hash = src_hash # This way hash can be set to None (account or container).
1055 b9064632 Antony Chazapis
            size = src_size
1056 66ce2ca5 Antony Chazapis
        if type is None:
1057 66ce2ca5 Antony Chazapis
            type = src_type
1058 33b4e4a6 Antony Chazapis
        if checksum is None:
1059 33b4e4a6 Antony Chazapis
            checksum = src_checksum
1060 1730b3bf chazapis
        uuid = self._generate_uuid() if (is_copy or src_version_id is None) else props[self.UUID]
1061 1730b3bf chazapis
        
1062 1730b3bf chazapis
        if src_node is None:
1063 1730b3bf chazapis
            pre_version_id = src_version_id
1064 1730b3bf chazapis
        else:
1065 1730b3bf chazapis
            pre_version_id = None
1066 1730b3bf chazapis
            props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1067 1730b3bf chazapis
            if props is not None:
1068 1730b3bf chazapis
                pre_version_id = props[self.SERIAL]
1069 1730b3bf chazapis
        if pre_version_id is not None:
1070 1730b3bf chazapis
            self.node.version_recluster(pre_version_id, CLUSTER_HISTORY)
1071 b9064632 Antony Chazapis
        
1072 33b4e4a6 Antony Chazapis
        dest_version_id, mtime = self.node.version_create(node, hash, size, type, src_version_id, user, uuid, checksum, cluster)
1073 1730b3bf chazapis
        return pre_version_id, dest_version_id
1074 44ad5860 Antony Chazapis
    
1075 4819d34f Antony Chazapis
    def _put_metadata_duplicate(self, src_version_id, dest_version_id, domain, meta, replace=False):
1076 4819d34f Antony Chazapis
        if src_version_id is not None:
1077 4819d34f Antony Chazapis
            self.node.attribute_copy(src_version_id, dest_version_id)
1078 4819d34f Antony Chazapis
        if not replace:
1079 4819d34f Antony Chazapis
            self.node.attribute_del(dest_version_id, domain, (k for k, v in meta.iteritems() if v == ''))
1080 4819d34f Antony Chazapis
            self.node.attribute_set(dest_version_id, domain, ((k, v) for k, v in meta.iteritems() if v != ''))
1081 4819d34f Antony Chazapis
        else:
1082 4819d34f Antony Chazapis
            self.node.attribute_del(dest_version_id, domain)
1083 4819d34f Antony Chazapis
            self.node.attribute_set(dest_version_id, domain, ((k, v) for k, v in meta.iteritems()))
1084 4819d34f Antony Chazapis
    
1085 4819d34f Antony Chazapis
    def _put_metadata(self, user, node, domain, meta, replace=False):
1086 44ad5860 Antony Chazapis
        """Create a new version and store metadata."""
1087 44ad5860 Antony Chazapis
        
1088 b9064632 Antony Chazapis
        src_version_id, dest_version_id = self._put_version_duplicate(user, node)
1089 4819d34f Antony Chazapis
        self._put_metadata_duplicate(src_version_id, dest_version_id, domain, meta, replace)
1090 5cc484e1 Antony Chazapis
        return src_version_id, dest_version_id
1091 44ad5860 Antony Chazapis
    
1092 60b8a083 Antony Chazapis
    def _list_limits(self, listing, marker, limit):
1093 60b8a083 Antony Chazapis
        start = 0
1094 60b8a083 Antony Chazapis
        if marker:
1095 60b8a083 Antony Chazapis
            try:
1096 60b8a083 Antony Chazapis
                start = listing.index(marker) + 1
1097 60b8a083 Antony Chazapis
            except ValueError:
1098 60b8a083 Antony Chazapis
                pass
1099 60b8a083 Antony Chazapis
        if not limit or limit > 10000:
1100 60b8a083 Antony Chazapis
            limit = 10000
1101 60b8a083 Antony Chazapis
        return start, limit
1102 60b8a083 Antony Chazapis
    
1103 371d907a Antony Chazapis
    def _list_object_properties(self, parent, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, domain=None, keys=[], until=None, size_range=None, allowed=[], all_props=False):
1104 60b8a083 Antony Chazapis
        cont_prefix = path + '/'
1105 60b8a083 Antony Chazapis
        prefix = cont_prefix + prefix
1106 60b8a083 Antony Chazapis
        start = cont_prefix + marker if marker else None
1107 60b8a083 Antony Chazapis
        before = until if until is not None else inf
1108 4819d34f Antony Chazapis
        filterq = keys if domain else []
1109 7ff57991 Antony Chazapis
        sizeq = size_range
1110 60b8a083 Antony Chazapis
        
1111 371d907a Antony Chazapis
        objects, prefixes = self.node.latest_version_list(parent, prefix, delimiter, start, limit, before, CLUSTER_DELETED, allowed, domain, filterq, sizeq, all_props)
1112 60b8a083 Antony Chazapis
        objects.extend([(p, None) for p in prefixes] if virtual else [])
1113 43be9afd Sofia Papagiannaki
        objects.sort(key=lambda x: x[0])
1114 371d907a Antony Chazapis
        objects = [(x[0][len(cont_prefix):],) + x[1:] for x in objects]
1115 cf4a7a7b Sofia Papagiannaki
        return objects
1116 60b8a083 Antony Chazapis
        
1117 813e42e5 Antony Chazapis
    # Reporting functions.
1118 813e42e5 Antony Chazapis
    
1119 813e42e5 Antony Chazapis
    def _report_size_change(self, user, account, size, details={}):
1120 813e42e5 Antony Chazapis
        logger.debug("_report_size_change: %s %s %s %s", user, account, size, details)
1121 813e42e5 Antony Chazapis
        account_node = self._lookup_account(account, True)[1]
1122 813e42e5 Antony Chazapis
        total = self._get_statistics(account_node)[1]
1123 813e42e5 Antony Chazapis
        details.update({'user': user, 'total': total})
1124 73673127 Antony Chazapis
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('resource.diskspace',), account, QUEUE_INSTANCE_ID, 'diskspace', float(size), details))
1125 39ef6f41 Antony Chazapis
    
1126 39ef6f41 Antony Chazapis
    def _report_object_change(self, user, account, path, details={}):
1127 39ef6f41 Antony Chazapis
        logger.debug("_report_object_change: %s %s %s %s", user, account, path, details)
1128 39ef6f41 Antony Chazapis
        details.update({'user': user})
1129 73673127 Antony Chazapis
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('object',), account, QUEUE_INSTANCE_ID, 'object', path, details))
1130 813e42e5 Antony Chazapis
    
1131 a74ba506 Sofia Papagiannaki
    def _report_sharing_change(self, user, account, path, details={}):
1132 a74ba506 Sofia Papagiannaki
        logger.debug("_report_permissions_change: %s %s %s %s", user, account, path, details)
1133 a74ba506 Sofia Papagiannaki
        details.update({'user': user})
1134 a74ba506 Sofia Papagiannaki
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('sharing',), account, QUEUE_INSTANCE_ID, 'sharing', path, details))
1135 a74ba506 Sofia Papagiannaki
    
1136 60b8a083 Antony Chazapis
    # Policy functions.
1137 60b8a083 Antony Chazapis
    
1138 60b8a083 Antony Chazapis
    def _check_policy(self, policy):
1139 60b8a083 Antony Chazapis
        for k in policy.keys():
1140 60b8a083 Antony Chazapis
            if policy[k] == '':
1141 60b8a083 Antony Chazapis
                policy[k] = self.default_policy.get(k)
1142 60b8a083 Antony Chazapis
        for k, v in policy.iteritems():
1143 60b8a083 Antony Chazapis
            if k == 'quota':
1144 60b8a083 Antony Chazapis
                q = int(v) # May raise ValueError.
1145 60b8a083 Antony Chazapis
                if q < 0:
1146 60b8a083 Antony Chazapis
                    raise ValueError
1147 60b8a083 Antony Chazapis
            elif k == 'versioning':
1148 5cc484e1 Antony Chazapis
                if v not in ['auto', 'none']:
1149 60b8a083 Antony Chazapis
                    raise ValueError
1150 60b8a083 Antony Chazapis
            else:
1151 60b8a083 Antony Chazapis
                raise ValueError
1152 60b8a083 Antony Chazapis
    
1153 b2832c6a Antony Chazapis
    def _put_policy(self, node, policy, replace):
1154 b2832c6a Antony Chazapis
        if replace:
1155 b2832c6a Antony Chazapis
            for k, v in self.default_policy.iteritems():
1156 b2832c6a Antony Chazapis
                if k not in policy:
1157 b2832c6a Antony Chazapis
                    policy[k] = v
1158 b2832c6a Antony Chazapis
        self.node.policy_set(node, policy)
1159 b2832c6a Antony Chazapis
    
1160 b9064632 Antony Chazapis
    def _get_policy(self, node):
1161 b9064632 Antony Chazapis
        policy = self.default_policy.copy()
1162 b9064632 Antony Chazapis
        policy.update(self.node.policy_get(node))
1163 b9064632 Antony Chazapis
        return policy
1164 b9064632 Antony Chazapis
    
1165 5cc484e1 Antony Chazapis
    def _apply_versioning(self, account, container, version_id):
1166 813e42e5 Antony Chazapis
        """Delete the provided version if such is the policy.
1167 813e42e5 Antony Chazapis
           Return size of object removed.
1168 813e42e5 Antony Chazapis
        """
1169 813e42e5 Antony Chazapis
        
1170 5cc484e1 Antony Chazapis
        if version_id is None:
1171 813e42e5 Antony Chazapis
            return 0
1172 5cc484e1 Antony Chazapis
        path, node = self._lookup_container(account, container)
1173 5cc484e1 Antony Chazapis
        versioning = self._get_policy(node)['versioning']
1174 5cc484e1 Antony Chazapis
        if versioning != 'auto':
1175 813e42e5 Antony Chazapis
            hash, size = self.node.version_remove(version_id)
1176 5161c672 Antony Chazapis
            self.store.map_delete(hash)
1177 813e42e5 Antony Chazapis
            return size
1178 813e42e5 Antony Chazapis
        return 0
1179 5cc484e1 Antony Chazapis
    
1180 a9b3f29d Antony Chazapis
    # Access control functions.
1181 a9b3f29d Antony Chazapis
    
1182 a9b3f29d Antony Chazapis
    def _check_groups(self, groups):
1183 0f9d752c Antony Chazapis
        # raise ValueError('Bad characters in groups')
1184 a9b3f29d Antony Chazapis
        pass
1185 a9b3f29d Antony Chazapis
    
1186 a9b3f29d Antony Chazapis
    def _check_permissions(self, path, permissions):
1187 0f9d752c Antony Chazapis
        # raise ValueError('Bad characters in permissions')
1188 5e068361 Antony Chazapis
        pass
1189 5e068361 Antony Chazapis
    
1190 92da0e5a Antony Chazapis
    def _get_formatted_paths(self, paths):
1191 92da0e5a Antony Chazapis
        formatted = []
1192 92da0e5a Antony Chazapis
        for p in paths:
1193 92da0e5a Antony Chazapis
            node = self.node.node_lookup(p)
1194 92da0e5a Antony Chazapis
            if node is not None:
1195 92da0e5a Antony Chazapis
                props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1196 92da0e5a Antony Chazapis
            if props is not None:
1197 692485cc Antony Chazapis
                if props[self.TYPE].split(';', 1)[0].strip() in ('application/directory', 'application/folder'):
1198 d57eaad4 Antony Chazapis
                    formatted.append((p.rstrip('/') + '/', self.MATCH_PREFIX))
1199 d57eaad4 Antony Chazapis
                formatted.append((p, self.MATCH_EXACT))
1200 92da0e5a Antony Chazapis
        return formatted
1201 92da0e5a Antony Chazapis
    
1202 5e068361 Antony Chazapis
    def _get_permissions_path(self, account, container, name):
1203 5e068361 Antony Chazapis
        path = '/'.join((account, container, name))
1204 5e068361 Antony Chazapis
        permission_paths = self.permissions.access_inherit(path)
1205 5e068361 Antony Chazapis
        permission_paths.sort()
1206 5e068361 Antony Chazapis
        permission_paths.reverse()
1207 5e068361 Antony Chazapis
        for p in permission_paths:
1208 5e068361 Antony Chazapis
            if p == path:
1209 5e068361 Antony Chazapis
                return p
1210 5e068361 Antony Chazapis
            else:
1211 71dbc012 Antony Chazapis
                if p.count('/') < 2:
1212 71dbc012 Antony Chazapis
                    continue
1213 92da0e5a Antony Chazapis
                node = self.node.node_lookup(p)
1214 92da0e5a Antony Chazapis
                if node is not None:
1215 92da0e5a Antony Chazapis
                    props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1216 92da0e5a Antony Chazapis
                if props is not None:
1217 692485cc Antony Chazapis
                    if props[self.TYPE].split(';', 1)[0].strip() in ('application/directory', 'application/folder'):
1218 5e068361 Antony Chazapis
                        return p
1219 5e068361 Antony Chazapis
        return None
1220 a9b3f29d Antony Chazapis
    
1221 6f4bce7b Antony Chazapis
    def _can_read(self, user, account, container, name):
1222 a9b3f29d Antony Chazapis
        if user == account:
1223 a9b3f29d Antony Chazapis
            return True
1224 a9b3f29d Antony Chazapis
        path = '/'.join((account, container, name))
1225 aeb2b64f Antony Chazapis
        if self.permissions.public_get(path) is not None:
1226 71dbc012 Antony Chazapis
            return True
1227 5e068361 Antony Chazapis
        path = self._get_permissions_path(account, container, name)
1228 71dbc012 Antony Chazapis
        if not path:
1229 71dbc012 Antony Chazapis
            raise NotAllowedError
1230 2c5363a0 Antony Chazapis
        if not self.permissions.access_check(path, self.READ, user) and not self.permissions.access_check(path, self.WRITE, user):
1231 a9b3f29d Antony Chazapis
            raise NotAllowedError
1232 a9b3f29d Antony Chazapis
    
1233 a9b3f29d Antony Chazapis
    def _can_write(self, user, account, container, name):
1234 6f4bce7b Antony Chazapis
        if user == account:
1235 6f4bce7b Antony Chazapis
            return True
1236 6f4bce7b Antony Chazapis
        path = '/'.join((account, container, name))
1237 71dbc012 Antony Chazapis
        path = self._get_permissions_path(account, container, name)
1238 71dbc012 Antony Chazapis
        if not path:
1239 71dbc012 Antony Chazapis
            raise NotAllowedError
1240 2c5363a0 Antony Chazapis
        if not self.permissions.access_check(path, self.WRITE, user):
1241 a9b3f29d Antony Chazapis
            raise NotAllowedError
1242 a9b3f29d Antony Chazapis
    
1243 a9b3f29d Antony Chazapis
    def _allowed_accounts(self, user):
1244 a9b3f29d Antony Chazapis
        allow = set()
1245 0f9d752c Antony Chazapis
        for path in self.permissions.access_list_paths(user):
1246 a9b3f29d Antony Chazapis
            allow.add(path.split('/', 1)[0])
1247 a9b3f29d Antony Chazapis
        return sorted(allow)
1248 a9b3f29d Antony Chazapis
    
1249 a9b3f29d Antony Chazapis
    def _allowed_containers(self, user, account):
1250 a9b3f29d Antony Chazapis
        allow = set()
1251 0f9d752c Antony Chazapis
        for path in self.permissions.access_list_paths(user, account):
1252 a9b3f29d Antony Chazapis
            allow.add(path.split('/', 2)[1])
1253 a9b3f29d Antony Chazapis
        return sorted(allow)