Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / modular.py @ 3f767854

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 90ee1eb3 Sofia Papagiannaki
            allowed = []
328 90ee1eb3 Sofia Papagiannaki
            if shared:
329 90ee1eb3 Sofia Papagiannaki
                allowed.extend([x.split('/', 2)[1] for x in self.permissions.access_list_shared(account)])
330 90ee1eb3 Sofia Papagiannaki
            if public:
331 90ee1eb3 Sofia Papagiannaki
                allowed.extend([x[0].split('/', 2)[1] for x in self.permissions.public_list(account)])
332 1ad56ff3 Sofia Papagiannaki
            allowed = list(set(allowed))
333 2535deff Sofia Papagiannaki
            allowed.sort()
334 62f915a1 Antony Chazapis
            start, limit = self._list_limits(allowed, marker, limit)
335 62f915a1 Antony Chazapis
            return allowed[start:start + limit]
336 62f915a1 Antony Chazapis
        node = self.node.node_lookup(account)
337 cf4a7a7b Sofia Papagiannaki
        containers = [x[0] for x in self._list_object_properties(node, account, '', '/', marker, limit, False, None, [], until)]
338 cf4a7a7b Sofia Papagiannaki
        start, limit = self._list_limits([x[0] for x in containers], marker, limit)
339 cf4a7a7b Sofia Papagiannaki
        return containers[start:start + limit]
340 371d907a Antony Chazapis
    
341 371d907a Antony Chazapis
    @backend_method
342 371d907a Antony Chazapis
    def list_container_meta(self, user, account, container, domain, until=None):
343 371d907a Antony Chazapis
        """Return a list with all the container's object meta keys for the domain."""
344 371d907a Antony Chazapis
        
345 fe2db49d Sofia Papagiannaki
        logger.debug("list_container_meta: %s %s %s %s %s", user, account, container, domain, until)
346 371d907a Antony Chazapis
        allowed = []
347 371d907a Antony Chazapis
        if user != account:
348 371d907a Antony Chazapis
            if until:
349 371d907a Antony Chazapis
                raise NotAllowedError
350 371d907a Antony Chazapis
            allowed = self.permissions.access_list_paths(user, '/'.join((account, container)))
351 371d907a Antony Chazapis
            if not allowed:
352 371d907a Antony Chazapis
                raise NotAllowedError
353 371d907a Antony Chazapis
        path, node = self._lookup_container(account, container)
354 371d907a Antony Chazapis
        before = until if until is not None else inf
355 371d907a Antony Chazapis
        allowed = self._get_formatted_paths(allowed)
356 371d907a Antony Chazapis
        return self.node.latest_attribute_keys(node, domain, before, CLUSTER_DELETED, allowed)
357 a9b3f29d Antony Chazapis
    
358 a9b3f29d Antony Chazapis
    @backend_method
359 82482e2c Antony Chazapis
    def get_container_meta(self, user, account, container, domain, until=None, include_user_defined=True):
360 cb69c154 Antony Chazapis
        """Return a dictionary with the container metadata for the domain."""
361 a9b3f29d Antony Chazapis
        
362 fe2db49d Sofia Papagiannaki
        logger.debug("get_container_meta: %s %s %s %s %s", user, account, container, domain, until)
363 a9b3f29d Antony Chazapis
        if user != account:
364 a9b3f29d Antony Chazapis
            if until or container not in self._allowed_containers(user, account):
365 a9b3f29d Antony Chazapis
                raise NotAllowedError
366 c915d3bf Antony Chazapis
        path, node = self._lookup_container(account, container)
367 c915d3bf Antony Chazapis
        props = self._get_properties(node, until)
368 2c5363a0 Antony Chazapis
        mtime = props[self.MTIME]
369 62f915a1 Antony Chazapis
        count, bytes, tstamp = self._get_statistics(node, until)
370 62f915a1 Antony Chazapis
        tstamp = max(tstamp, mtime)
371 a9b3f29d Antony Chazapis
        if until is None:
372 a9b3f29d Antony Chazapis
            modified = tstamp
373 a9b3f29d Antony Chazapis
        else:
374 62f915a1 Antony Chazapis
            modified = self._get_statistics(node)[2] # Overall last modification.
375 62f915a1 Antony Chazapis
            modified = max(modified, mtime)
376 a9b3f29d Antony Chazapis
        
377 a9b3f29d Antony Chazapis
        if user != account:
378 c915d3bf Antony Chazapis
            meta = {'name': container}
379 a9b3f29d Antony Chazapis
        else:
380 82482e2c Antony Chazapis
            meta = {}
381 82482e2c Antony Chazapis
            if include_user_defined:
382 82482e2c Antony Chazapis
                meta.update(dict(self.node.attribute_get(props[self.SERIAL], domain)))
383 a9b3f29d Antony Chazapis
            if until is not None:
384 a9b3f29d Antony Chazapis
                meta.update({'until_timestamp': tstamp})
385 c915d3bf Antony Chazapis
            meta.update({'name': container, 'count': count, 'bytes': bytes})
386 c915d3bf Antony Chazapis
        meta.update({'modified': modified})
387 a9b3f29d Antony Chazapis
        return meta
388 a9b3f29d Antony Chazapis
    
389 a9b3f29d Antony Chazapis
    @backend_method
390 cb69c154 Antony Chazapis
    def update_container_meta(self, user, account, container, domain, meta, replace=False):
391 cb69c154 Antony Chazapis
        """Update the metadata associated with the container for the domain."""
392 a9b3f29d Antony Chazapis
        
393 fe2db49d Sofia Papagiannaki
        logger.debug("update_container_meta: %s %s %s %s %s %s", user, account, container, domain, meta, replace)
394 a9b3f29d Antony Chazapis
        if user != account:
395 a9b3f29d Antony Chazapis
            raise NotAllowedError
396 c915d3bf Antony Chazapis
        path, node = self._lookup_container(account, container)
397 f9ea264b Antony Chazapis
        src_version_id, dest_version_id = self._put_metadata(user, node, domain, meta, replace)
398 f9ea264b Antony Chazapis
        if src_version_id is not None:
399 f9ea264b Antony Chazapis
            versioning = self._get_policy(node)['versioning']
400 f9ea264b Antony Chazapis
            if versioning != 'auto':
401 f9ea264b Antony Chazapis
                self.node.version_remove(src_version_id)
402 a9b3f29d Antony Chazapis
    
403 a9b3f29d Antony Chazapis
    @backend_method
404 a9b3f29d Antony Chazapis
    def get_container_policy(self, user, account, container):
405 a9b3f29d Antony Chazapis
        """Return a dictionary with the container policy."""
406 a9b3f29d Antony Chazapis
        
407 fe2db49d Sofia Papagiannaki
        logger.debug("get_container_policy: %s %s %s", user, account, container)
408 a9b3f29d Antony Chazapis
        if user != account:
409 a9b3f29d Antony Chazapis
            if container not in self._allowed_containers(user, account):
410 a9b3f29d Antony Chazapis
                raise NotAllowedError
411 a9b3f29d Antony Chazapis
            return {}
412 5e7485da Antony Chazapis
        path, node = self._lookup_container(account, container)
413 b9064632 Antony Chazapis
        return self._get_policy(node)
414 a9b3f29d Antony Chazapis
    
415 a9b3f29d Antony Chazapis
    @backend_method
416 a9b3f29d Antony Chazapis
    def update_container_policy(self, user, account, container, policy, replace=False):
417 b2832c6a Antony Chazapis
        """Update the policy associated with the container."""
418 a9b3f29d Antony Chazapis
        
419 fe2db49d Sofia Papagiannaki
        logger.debug("update_container_policy: %s %s %s %s %s", user, account, container, policy, replace)
420 a9b3f29d Antony Chazapis
        if user != account:
421 a9b3f29d Antony Chazapis
            raise NotAllowedError
422 5e7485da Antony Chazapis
        path, node = self._lookup_container(account, container)
423 a9b3f29d Antony Chazapis
        self._check_policy(policy)
424 b2832c6a Antony Chazapis
        self._put_policy(node, policy, replace)
425 a9b3f29d Antony Chazapis
    
426 a9b3f29d Antony Chazapis
    @backend_method
427 8693b873 Sofia Papagiannaki
    def put_container(self, user, account, container, policy={}):
428 a9b3f29d Antony Chazapis
        """Create a new container with the given name."""
429 a9b3f29d Antony Chazapis
        
430 fe2db49d Sofia Papagiannaki
        logger.debug("put_container: %s %s %s %s", user, account, container, policy)
431 a9b3f29d Antony Chazapis
        if user != account:
432 a9b3f29d Antony Chazapis
            raise NotAllowedError
433 a9b3f29d Antony Chazapis
        try:
434 c915d3bf Antony Chazapis
            path, node = self._lookup_container(account, container)
435 a9b3f29d Antony Chazapis
        except NameError:
436 a9b3f29d Antony Chazapis
            pass
437 a9b3f29d Antony Chazapis
        else:
438 a9b3f29d Antony Chazapis
            raise NameError('Container already exists')
439 a9b3f29d Antony Chazapis
        if policy:
440 a9b3f29d Antony Chazapis
            self._check_policy(policy)
441 a9b3f29d Antony Chazapis
        path = '/'.join((account, container))
442 5e7485da Antony Chazapis
        node = self._put_path(user, self._lookup_account(account, True)[1], path)
443 b2832c6a Antony Chazapis
        self._put_policy(node, policy, True)
444 a9b3f29d Antony Chazapis
    
445 a9b3f29d Antony Chazapis
    @backend_method
446 fe2db49d Sofia Papagiannaki
    def delete_container(self, user, account, container, until=None, prefix='', delimiter=None):
447 a9b3f29d Antony Chazapis
        """Delete/purge the container with the given name."""
448 a9b3f29d Antony Chazapis
        
449 fe2db49d Sofia Papagiannaki
        logger.debug("delete_container: %s %s %s %s %s %s", user, account, container, until, prefix, delimiter)
450 a9b3f29d Antony Chazapis
        if user != account:
451 a9b3f29d Antony Chazapis
            raise NotAllowedError
452 c915d3bf Antony Chazapis
        path, node = self._lookup_container(account, container)
453 a9b3f29d Antony Chazapis
        
454 a9b3f29d Antony Chazapis
        if until is not None:
455 813e42e5 Antony Chazapis
            hashes, size = self.node.node_purge_children(node, until, CLUSTER_HISTORY)
456 04230536 Antony Chazapis
            for h in hashes:
457 04230536 Antony Chazapis
                self.store.map_delete(h)
458 c915d3bf Antony Chazapis
            self.node.node_purge_children(node, until, CLUSTER_DELETED)
459 813e42e5 Antony Chazapis
            self._report_size_change(user, account, -size, {'action': 'container purge'})
460 a9b3f29d Antony Chazapis
            return
461 a9b3f29d Antony Chazapis
        
462 62f915a1 Antony Chazapis
        if self._get_statistics(node)[0] > 0:
463 a9b3f29d Antony Chazapis
            raise IndexError('Container is not empty')
464 813e42e5 Antony Chazapis
        hashes, size = self.node.node_purge_children(node, inf, CLUSTER_HISTORY)
465 04230536 Antony Chazapis
        for h in hashes:
466 04230536 Antony Chazapis
            self.store.map_delete(h)
467 62f915a1 Antony Chazapis
        self.node.node_purge_children(node, inf, CLUSTER_DELETED)
468 c915d3bf Antony Chazapis
        self.node.node_remove(node)
469 813e42e5 Antony Chazapis
        self._report_size_change(user, account, -size, {'action': 'container delete'})
470 a9b3f29d Antony Chazapis
    
471 90ee1eb3 Sofia Papagiannaki
    def _list_objects(self, user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, all_props, public):
472 15a96c3e Antony Chazapis
        if user != account and until:
473 15a96c3e Antony Chazapis
            raise NotAllowedError
474 cf4a7a7b Sofia Papagiannaki
        if shared and public:
475 cf4a7a7b Sofia Papagiannaki
            # get shared first
476 cf4a7a7b Sofia Papagiannaki
            shared = self._list_object_permissions(user, account, container, prefix, shared=True, public=False)
477 cf4a7a7b Sofia Papagiannaki
            objects = []
478 cf4a7a7b Sofia Papagiannaki
            if shared:
479 cf4a7a7b Sofia Papagiannaki
                path, node = self._lookup_container(account, container)
480 cf4a7a7b Sofia Papagiannaki
                shared = self._get_formatted_paths(shared)
481 cf4a7a7b Sofia Papagiannaki
                objects = self._list_object_properties(node, path, prefix, delimiter, marker, limit, virtual, domain, keys, until, size_range, shared, all_props)
482 cf4a7a7b Sofia Papagiannaki
            
483 cf4a7a7b Sofia Papagiannaki
            # get public
484 cf4a7a7b Sofia Papagiannaki
            objects.extend(self._list_public_object_properties(user, account, container, prefix, all_props))
485 cf4a7a7b Sofia Papagiannaki
            
486 cf4a7a7b Sofia Papagiannaki
            objects.sort(key=lambda x: x[0])
487 cf4a7a7b Sofia Papagiannaki
            start, limit = self._list_limits([x[0] for x in objects], marker, limit)
488 cf4a7a7b Sofia Papagiannaki
            return objects[start:start + limit]
489 cf4a7a7b Sofia Papagiannaki
        elif public:
490 cf4a7a7b Sofia Papagiannaki
            objects = self._list_public_object_properties(user, account, container, prefix, all_props)
491 cf4a7a7b Sofia Papagiannaki
            start, limit = self._list_limits([x[0] for x in objects], marker, limit)
492 cf4a7a7b Sofia Papagiannaki
            return objects[start:start + limit]
493 cf4a7a7b Sofia Papagiannaki
        
494 90ee1eb3 Sofia Papagiannaki
        allowed = self._list_object_permissions(user, account, container, prefix, shared, public)
495 cf4a7a7b Sofia Papagiannaki
        if shared and not allowed:
496 fcd37c40 Antony Chazapis
            return []
497 15a96c3e Antony Chazapis
        path, node = self._lookup_container(account, container)
498 15a96c3e Antony Chazapis
        allowed = self._get_formatted_paths(allowed)
499 cf4a7a7b Sofia Papagiannaki
        objects = self._list_object_properties(node, path, prefix, delimiter, marker, limit, virtual, domain, keys, until, size_range, allowed, all_props)
500 cf4a7a7b Sofia Papagiannaki
        start, limit = self._list_limits([x[0] for x in objects], marker, limit)
501 cf4a7a7b Sofia Papagiannaki
        return objects[start:start + limit]
502 15a96c3e Antony Chazapis
    
503 cf4a7a7b Sofia Papagiannaki
    def _list_public_object_properties(self, user, account, container, prefix, all_props):
504 cf4a7a7b Sofia Papagiannaki
        public = self._list_object_permissions(user, account, container, prefix, shared=False, public=True)
505 cf4a7a7b Sofia Papagiannaki
        paths, nodes = self._lookup_objects(public)
506 cf4a7a7b Sofia Papagiannaki
        path = '/'.join((account, container))
507 cf4a7a7b Sofia Papagiannaki
        cont_prefix = path + '/'
508 cf4a7a7b Sofia Papagiannaki
        paths = [x[len(cont_prefix):] for x in paths]
509 cf4a7a7b Sofia Papagiannaki
        props = self.node.version_lookup_bulk(nodes, all_props=all_props)
510 cf4a7a7b Sofia Papagiannaki
        objects = [(path,) + props for path, props in zip(paths, props)]
511 cf4a7a7b Sofia Papagiannaki
        return objects
512 cf4a7a7b Sofia Papagiannaki
        
513 4d15c94e Sofia Papagiannaki
    def _list_objects_no_limit(self, user, account, container, prefix, delimiter, virtual, domain, keys, shared, until, size_range, all_props, public):
514 4d15c94e Sofia Papagiannaki
        objects = []
515 4d15c94e Sofia Papagiannaki
        while True:
516 4d15c94e Sofia Papagiannaki
            marker = objects[-1] if objects else None
517 4d15c94e Sofia Papagiannaki
            limit = 10000
518 4d15c94e Sofia Papagiannaki
            l = self._list_objects(user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, all_props, public)
519 4d15c94e Sofia Papagiannaki
            objects.extend(l)
520 4d15c94e Sofia Papagiannaki
            if not l or len(l) < limit:
521 4d15c94e Sofia Papagiannaki
                break
522 4d15c94e Sofia Papagiannaki
        return objects
523 4d15c94e Sofia Papagiannaki
    
524 90ee1eb3 Sofia Papagiannaki
    def _list_object_permissions(self, user, account, container, prefix, shared, public):
525 62f915a1 Antony Chazapis
        allowed = []
526 fcd37c40 Antony Chazapis
        path = '/'.join((account, container, prefix)).rstrip('/')
527 62f915a1 Antony Chazapis
        if user != account:
528 fcd37c40 Antony Chazapis
            allowed = self.permissions.access_list_paths(user, path)
529 62f915a1 Antony Chazapis
            if not allowed:
530 62f915a1 Antony Chazapis
                raise NotAllowedError
531 62f915a1 Antony Chazapis
        else:
532 c53c4def Sofia Papagiannaki
            allowed = []
533 62f915a1 Antony Chazapis
            if shared:
534 c53c4def Sofia Papagiannaki
                allowed.extend(self.permissions.access_list_shared(path))
535 c53c4def Sofia Papagiannaki
            if public:
536 90ee1eb3 Sofia Papagiannaki
                allowed.extend([x[0] for x in self.permissions.public_list(path)])
537 c53c4def Sofia Papagiannaki
            allowed = list(set(allowed))
538 2535deff Sofia Papagiannaki
            allowed.sort()
539 c53c4def Sofia Papagiannaki
            if not allowed:
540 c53c4def Sofia Papagiannaki
                return []
541 15a96c3e Antony Chazapis
        return allowed
542 62f915a1 Antony Chazapis
    
543 62f915a1 Antony Chazapis
    @backend_method
544 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):
545 371d907a Antony Chazapis
        """Return a list of object (name, version_id) tuples existing under a container."""
546 62f915a1 Antony Chazapis
        
547 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)
548 90ee1eb3 Sofia Papagiannaki
        return self._list_objects(user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, False, public)
549 371d907a Antony Chazapis
    
550 371d907a Antony Chazapis
    @backend_method
551 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):
552 371d907a Antony Chazapis
        """Return a list of object metadata dicts existing under a container."""
553 371d907a Antony Chazapis
        
554 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)
555 90ee1eb3 Sofia Papagiannaki
        props = self._list_objects(user, account, container, prefix, delimiter, marker, limit, virtual, domain, keys, shared, until, size_range, True, public)
556 371d907a Antony Chazapis
        objects = []
557 371d907a Antony Chazapis
        for p in props:
558 371d907a Antony Chazapis
            if len(p) == 2:
559 371d907a Antony Chazapis
                objects.append({'subdir': p[0]})
560 371d907a Antony Chazapis
            else:
561 371d907a Antony Chazapis
                objects.append({'name': p[0],
562 371d907a Antony Chazapis
                                'bytes': p[self.SIZE + 1],
563 371d907a Antony Chazapis
                                'type': p[self.TYPE + 1],
564 371d907a Antony Chazapis
                                'hash': p[self.HASH + 1],
565 371d907a Antony Chazapis
                                'version': p[self.SERIAL + 1],
566 371d907a Antony Chazapis
                                'version_timestamp': p[self.MTIME + 1],
567 371d907a Antony Chazapis
                                'modified': p[self.MTIME + 1] if until is None else None,
568 371d907a Antony Chazapis
                                'modified_by': p[self.MUSER + 1],
569 371d907a Antony Chazapis
                                'uuid': p[self.UUID + 1],
570 371d907a Antony Chazapis
                                'checksum': p[self.CHECKSUM + 1]})
571 371d907a Antony Chazapis
        return objects
572 a9b3f29d Antony Chazapis
    
573 a9b3f29d Antony Chazapis
    @backend_method
574 15a96c3e Antony Chazapis
    def list_object_permissions(self, user, account, container, prefix=''):
575 15a96c3e Antony Chazapis
        """Return a list of paths that enforce permissions under a container."""
576 15a96c3e Antony Chazapis
        
577 fe2db49d Sofia Papagiannaki
        logger.debug("list_object_permissions: %s %s %s %s", user, account, container, prefix)
578 90ee1eb3 Sofia Papagiannaki
        return self._list_object_permissions(user, account, container, prefix, True, False)
579 15a96c3e Antony Chazapis
    
580 15a96c3e Antony Chazapis
    @backend_method
581 15a96c3e Antony Chazapis
    def list_object_public(self, user, account, container, prefix=''):
582 15a96c3e Antony Chazapis
        """Return a dict mapping paths to public ids for objects that are public under a container."""
583 15a96c3e Antony Chazapis
        
584 fe2db49d Sofia Papagiannaki
        logger.debug("list_object_public: %s %s %s %s", user, account, container, prefix)
585 15a96c3e Antony Chazapis
        public = {}
586 15a96c3e Antony Chazapis
        for path, p in self.permissions.public_list('/'.join((account, container, prefix))):
587 15a96c3e Antony Chazapis
            public[path] = p + ULTIMATE_ANSWER
588 15a96c3e Antony Chazapis
        return public
589 15a96c3e Antony Chazapis
    
590 15a96c3e Antony Chazapis
    @backend_method
591 82482e2c Antony Chazapis
    def get_object_meta(self, user, account, container, name, domain, version=None, include_user_defined=True):
592 cb69c154 Antony Chazapis
        """Return a dictionary with the object metadata for the domain."""
593 a9b3f29d Antony Chazapis
        
594 fe2db49d Sofia Papagiannaki
        logger.debug("get_object_meta: %s %s %s %s %s %s", user, account, container, name, domain, version)
595 a9b3f29d Antony Chazapis
        self._can_read(user, account, container, name)
596 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
597 c915d3bf Antony Chazapis
        props = self._get_version(node, version)
598 a9b3f29d Antony Chazapis
        if version is None:
599 2c5363a0 Antony Chazapis
            modified = props[self.MTIME]
600 a9b3f29d Antony Chazapis
        else:
601 97d45f69 Antony Chazapis
            try:
602 97d45f69 Antony Chazapis
                modified = self._get_version(node)[self.MTIME] # Overall last modification.
603 97d45f69 Antony Chazapis
            except NameError: # Object may be deleted.
604 97d45f69 Antony Chazapis
                del_props = self.node.version_lookup(node, inf, CLUSTER_DELETED)
605 97d45f69 Antony Chazapis
                if del_props is None:
606 97d45f69 Antony Chazapis
                    raise NameError('Object does not exist')
607 97d45f69 Antony Chazapis
                modified = del_props[self.MTIME]
608 a9b3f29d Antony Chazapis
        
609 82482e2c Antony Chazapis
        meta = {}
610 82482e2c Antony Chazapis
        if include_user_defined:
611 82482e2c Antony Chazapis
            meta.update(dict(self.node.attribute_get(props[self.SERIAL], domain)))
612 33b4e4a6 Antony Chazapis
        meta.update({'name': name,
613 33b4e4a6 Antony Chazapis
                     'bytes': props[self.SIZE],
614 33b4e4a6 Antony Chazapis
                     'type': props[self.TYPE],
615 371d907a Antony Chazapis
                     'hash': props[self.HASH],
616 33b4e4a6 Antony Chazapis
                     'version': props[self.SERIAL],
617 33b4e4a6 Antony Chazapis
                     'version_timestamp': props[self.MTIME],
618 33b4e4a6 Antony Chazapis
                     'modified': modified,
619 33b4e4a6 Antony Chazapis
                     'modified_by': props[self.MUSER],
620 33b4e4a6 Antony Chazapis
                     'uuid': props[self.UUID],
621 33b4e4a6 Antony Chazapis
                     'checksum': props[self.CHECKSUM]})
622 a9b3f29d Antony Chazapis
        return meta
623 a9b3f29d Antony Chazapis
    
624 a9b3f29d Antony Chazapis
    @backend_method
625 cb69c154 Antony Chazapis
    def update_object_meta(self, user, account, container, name, domain, meta, replace=False):
626 cb69c154 Antony Chazapis
        """Update the metadata associated with the object for the domain and return the new version."""
627 a9b3f29d Antony Chazapis
        
628 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_meta: %s %s %s %s %s %s %s", user, account, container, name, domain, meta, replace)
629 a9b3f29d Antony Chazapis
        self._can_write(user, account, container, name)
630 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
631 4819d34f Antony Chazapis
        src_version_id, dest_version_id = self._put_metadata(user, node, domain, meta, replace)
632 5cc484e1 Antony Chazapis
        self._apply_versioning(account, container, src_version_id)
633 5cc484e1 Antony Chazapis
        return dest_version_id
634 a9b3f29d Antony Chazapis
    
635 a9b3f29d Antony Chazapis
    @backend_method
636 a9b3f29d Antony Chazapis
    def get_object_permissions(self, user, account, container, name):
637 067cf1fc Antony Chazapis
        """Return the action allowed on the object, the path
638 067cf1fc Antony Chazapis
        from which the object gets its permissions from,
639 a9b3f29d Antony Chazapis
        along with a dictionary containing the permissions."""
640 a9b3f29d Antony Chazapis
        
641 fe2db49d Sofia Papagiannaki
        logger.debug("get_object_permissions: %s %s %s %s", user, account, container, name)
642 067cf1fc Antony Chazapis
        allowed = 'write'
643 92da0e5a Antony Chazapis
        permissions_path = self._get_permissions_path(account, container, name)
644 067cf1fc Antony Chazapis
        if user != account:
645 92da0e5a Antony Chazapis
            if self.permissions.access_check(permissions_path, self.WRITE, user):
646 067cf1fc Antony Chazapis
                allowed = 'write'
647 92da0e5a Antony Chazapis
            elif self.permissions.access_check(permissions_path, self.READ, user):
648 067cf1fc Antony Chazapis
                allowed = 'read'
649 067cf1fc Antony Chazapis
            else:
650 067cf1fc Antony Chazapis
                raise NotAllowedError
651 92da0e5a Antony Chazapis
        self._lookup_object(account, container, name)
652 92da0e5a Antony Chazapis
        return (allowed, permissions_path, self.permissions.access_get(permissions_path))
653 a9b3f29d Antony Chazapis
    
654 a9b3f29d Antony Chazapis
    @backend_method
655 a9b3f29d Antony Chazapis
    def update_object_permissions(self, user, account, container, name, permissions):
656 a9b3f29d Antony Chazapis
        """Update the permissions associated with the object."""
657 a9b3f29d Antony Chazapis
        
658 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_permissions: %s %s %s %s %s", user, account, container, name, permissions)
659 a9b3f29d Antony Chazapis
        if user != account:
660 a9b3f29d Antony Chazapis
            raise NotAllowedError
661 c915d3bf Antony Chazapis
        path = self._lookup_object(account, container, name)[0]
662 6f4bce7b Antony Chazapis
        self._check_permissions(path, permissions)
663 0f9d752c Antony Chazapis
        self.permissions.access_set(path, permissions)
664 a74ba506 Sofia Papagiannaki
        self._report_sharing_change(user, account, path, {'members':self.permissions.access_members(path)})
665 a9b3f29d Antony Chazapis
    
666 a9b3f29d Antony Chazapis
    @backend_method
667 a9b3f29d Antony Chazapis
    def get_object_public(self, user, account, container, name):
668 bb4eafc6 Antony Chazapis
        """Return the public id of the object if applicable."""
669 a9b3f29d Antony Chazapis
        
670 fe2db49d Sofia Papagiannaki
        logger.debug("get_object_public: %s %s %s %s", user, account, container, name)
671 a9b3f29d Antony Chazapis
        self._can_read(user, account, container, name)
672 c915d3bf Antony Chazapis
        path = self._lookup_object(account, container, name)[0]
673 bb4eafc6 Antony Chazapis
        p = self.permissions.public_get(path)
674 bb4eafc6 Antony Chazapis
        if p is not None:
675 bb4eafc6 Antony Chazapis
            p += ULTIMATE_ANSWER
676 bb4eafc6 Antony Chazapis
        return p
677 a9b3f29d Antony Chazapis
    
678 a9b3f29d Antony Chazapis
    @backend_method
679 a9b3f29d Antony Chazapis
    def update_object_public(self, user, account, container, name, public):
680 a9b3f29d Antony Chazapis
        """Update the public status of the object."""
681 a9b3f29d Antony Chazapis
        
682 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_public: %s %s %s %s %s", user, account, container, name, public)
683 a9b3f29d Antony Chazapis
        self._can_write(user, account, container, name)
684 c915d3bf Antony Chazapis
        path = self._lookup_object(account, container, name)[0]
685 0f9d752c Antony Chazapis
        if not public:
686 0f9d752c Antony Chazapis
            self.permissions.public_unset(path)
687 0f9d752c Antony Chazapis
        else:
688 0f9d752c Antony Chazapis
            self.permissions.public_set(path)
689 a9b3f29d Antony Chazapis
    
690 a9b3f29d Antony Chazapis
    @backend_method
691 a9b3f29d Antony Chazapis
    def get_object_hashmap(self, user, account, container, name, version=None):
692 a9b3f29d Antony Chazapis
        """Return the object's size and a list with partial hashes."""
693 a9b3f29d Antony Chazapis
        
694 fe2db49d Sofia Papagiannaki
        logger.debug("get_object_hashmap: %s %s %s %s %s", user, account, container, name, version)
695 a9b3f29d Antony Chazapis
        self._can_read(user, account, container, name)
696 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
697 c915d3bf Antony Chazapis
        props = self._get_version(node, version)
698 7ca7bb08 Antony Chazapis
        hashmap = self.store.map_get(binascii.unhexlify(props[self.HASH]))
699 2c5363a0 Antony Chazapis
        return props[self.SIZE], [binascii.hexlify(x) for x in hashmap]
700 a9b3f29d Antony Chazapis
    
701 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):
702 b9064632 Antony Chazapis
        if permissions is not None and user != account:
703 b9064632 Antony Chazapis
            raise NotAllowedError
704 b9064632 Antony Chazapis
        self._can_write(user, account, container, name)
705 b9064632 Antony Chazapis
        if permissions is not None:
706 b9064632 Antony Chazapis
            path = '/'.join((account, container, name))
707 b9064632 Antony Chazapis
            self._check_permissions(path, permissions)
708 b9064632 Antony Chazapis
        
709 b9064632 Antony Chazapis
        account_path, account_node = self._lookup_account(account, True)
710 b9064632 Antony Chazapis
        container_path, container_node = self._lookup_container(account, container)
711 b9064632 Antony Chazapis
        path, node = self._put_object_node(container_path, container_node, name)
712 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)
713 b9064632 Antony Chazapis
        
714 f9ea264b Antony Chazapis
        # Handle meta.
715 f9ea264b Antony Chazapis
        if src_version_id is None:
716 f9ea264b Antony Chazapis
            src_version_id = pre_version_id
717 f9ea264b Antony Chazapis
        self._put_metadata_duplicate(src_version_id, dest_version_id, domain, meta, replace_meta)
718 f9ea264b Antony Chazapis
        
719 b9064632 Antony Chazapis
        # Check quota.
720 813e42e5 Antony Chazapis
        del_size = self._apply_versioning(account, container, pre_version_id)
721 813e42e5 Antony Chazapis
        size_delta = size - del_size
722 b9064632 Antony Chazapis
        if size_delta > 0:
723 8693b873 Sofia Papagiannaki
            account_quota = long(self._get_policy(account_node)['quota'])
724 8693b873 Sofia Papagiannaki
            container_quota = long(self._get_policy(container_node)['quota'])
725 0df22aea Sofia Papagiannaki
            if (account_quota > 0 and self._get_statistics(account_node)[1] + size_delta > account_quota) or \
726 0df22aea Sofia Papagiannaki
               (container_quota > 0 and self._get_statistics(container_node)[1] + size_delta > container_quota):
727 b9064632 Antony Chazapis
                # This must be executed in a transaction, so the version is never created if it fails.
728 5df6c6d1 Antony Chazapis
                raise QuotaError
729 813e42e5 Antony Chazapis
        self._report_size_change(user, account, size_delta, {'action': 'object update'})
730 b9064632 Antony Chazapis
        
731 b9064632 Antony Chazapis
        if permissions is not None:
732 b9064632 Antony Chazapis
            self.permissions.access_set(path, permissions)
733 a74ba506 Sofia Papagiannaki
            self._report_sharing_change(user, account, path, {'members':self.permissions.access_members(path)})
734 39ef6f41 Antony Chazapis
        
735 39ef6f41 Antony Chazapis
        self._report_object_change(user, account, path, details={'version': dest_version_id, 'action': 'object update'})
736 f9ea264b Antony Chazapis
        return dest_version_id
737 b9064632 Antony Chazapis
    
738 a9b3f29d Antony Chazapis
    @backend_method
739 33b4e4a6 Antony Chazapis
    def update_object_hashmap(self, user, account, container, name, size, type, hashmap, checksum, domain, meta={}, replace_meta=False, permissions=None):
740 a9b3f29d Antony Chazapis
        """Create/update an object with the specified size and partial hashes."""
741 a9b3f29d Antony Chazapis
        
742 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_hashmap: %s %s %s %s %s %s %s %s", user, account, container, name, size, type, hashmap, checksum)
743 6d64339e Antony Chazapis
        if size == 0: # No such thing as an empty hashmap.
744 6d64339e Antony Chazapis
            hashmap = [self.put_block('')]
745 1c2fc0ff Antony Chazapis
        map = HashMap(self.block_size, self.hash_algorithm)
746 1c2fc0ff Antony Chazapis
        map.extend([binascii.unhexlify(x) for x in hashmap])
747 7ca7bb08 Antony Chazapis
        missing = self.store.block_search(map)
748 a9b3f29d Antony Chazapis
        if missing:
749 a9b3f29d Antony Chazapis
            ie = IndexError()
750 dd71f493 Antony Chazapis
            ie.data = [binascii.hexlify(x) for x in missing]
751 a9b3f29d Antony Chazapis
            raise ie
752 b9064632 Antony Chazapis
        
753 1c2fc0ff Antony Chazapis
        hash = map.hash()
754 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)
755 7ca7bb08 Antony Chazapis
        self.store.map_put(hash, map)
756 02c4d2ba Antony Chazapis
        return dest_version_id
757 a9b3f29d Antony Chazapis
    
758 33b4e4a6 Antony Chazapis
    @backend_method
759 33b4e4a6 Antony Chazapis
    def update_object_checksum(self, user, account, container, name, version, checksum):
760 33b4e4a6 Antony Chazapis
        """Update an object's checksum."""
761 33b4e4a6 Antony Chazapis
        
762 fe2db49d Sofia Papagiannaki
        logger.debug("update_object_checksum: %s %s %s %s %s %s", user, account, container, name, version, checksum)
763 33b4e4a6 Antony Chazapis
        # Update objects with greater version and same hashmap and size (fix metadata updates).
764 33b4e4a6 Antony Chazapis
        self._can_write(user, account, container, name)
765 33b4e4a6 Antony Chazapis
        path, node = self._lookup_object(account, container, name)
766 33b4e4a6 Antony Chazapis
        props = self._get_version(node, version)
767 33b4e4a6 Antony Chazapis
        versions = self.node.node_get_versions(node)
768 33b4e4a6 Antony Chazapis
        for x in versions:
769 33b4e4a6 Antony Chazapis
            if x[self.SERIAL] >= int(version) and x[self.HASH] == props[self.HASH] and x[self.SIZE] == props[self.SIZE]:
770 33b4e4a6 Antony Chazapis
                self.node.version_put_property(x[self.SERIAL], 'checksum', checksum)
771 33b4e4a6 Antony Chazapis
    
772 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):
773 4d15c94e Sofia Papagiannaki
        dest_version_ids = []
774 79bb41b7 Antony Chazapis
        self._can_read(user, src_account, src_container, src_name)
775 b9064632 Antony Chazapis
        path, node = self._lookup_object(src_account, src_container, src_name)
776 1730b3bf chazapis
        # TODO: Will do another fetch of the properties in duplicate version...
777 1730b3bf chazapis
        props = self._get_version(node, src_version) # Check to see if source exists.
778 b9064632 Antony Chazapis
        src_version_id = props[self.SERIAL]
779 b9064632 Antony Chazapis
        hash = props[self.HASH]
780 b9064632 Antony Chazapis
        size = props[self.SIZE]
781 25ae8b75 Antony Chazapis
        is_copy = not is_move and (src_account, src_container, src_name) != (dest_account, dest_container, dest_name) # New uuid.
782 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))
783 3f767854 Sofia Papagiannaki
        if is_move and (src_account, src_container, src_name) != (dest_account, dest_container, dest_name):
784 4d15c94e Sofia Papagiannaki
                self._delete_object(user, src_account, src_container, src_name)
785 4d15c94e Sofia Papagiannaki
        
786 4d15c94e Sofia Papagiannaki
        if delimiter:
787 4d15c94e Sofia Papagiannaki
            prefix = src_name + delimiter if not src_name.endswith(delimiter) else src_name
788 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)
789 4d15c94e Sofia Papagiannaki
            paths = [elem[0] for elem in src_names]
790 4d15c94e Sofia Papagiannaki
            nodes = [elem[2] for elem in src_names]
791 4d15c94e Sofia Papagiannaki
            # TODO: Will do another fetch of the properties in duplicate version...
792 4d15c94e Sofia Papagiannaki
            props = self._get_versions(nodes) # Check to see if source exists.
793 4d15c94e Sofia Papagiannaki
            
794 4d15c94e Sofia Papagiannaki
            for prop, path, node in zip(props, paths, nodes):
795 4d15c94e Sofia Papagiannaki
                src_version_id = prop[self.SERIAL]
796 4d15c94e Sofia Papagiannaki
                hash = prop[self.HASH]
797 4d15c94e Sofia Papagiannaki
                vtype = prop[self.TYPE]
798 4d15c94e Sofia Papagiannaki
                dest_prefix = dest_name + delimiter if not dest_name.endswith(delimiter) else dest_name
799 4d15c94e Sofia Papagiannaki
                vdest_name = path.replace(prefix, dest_prefix, 1)
800 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))
801 3f767854 Sofia Papagiannaki
                if is_move and (src_account, src_container, src_name) != (dest_account, dest_container, dest_name):
802 4d15c94e Sofia Papagiannaki
                        self._delete_object(user, src_account, src_container, path)
803 4d15c94e Sofia Papagiannaki
        return dest_version_ids[0] if len(dest_version_ids) == 1 else dest_version_ids
804 4d15c94e Sofia Papagiannaki
    
805 4d15c94e Sofia Papagiannaki
    @backend_method
806 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):
807 dff7b6f1 Sofia Papagiannaki
        """Copy an object's data and metadata."""
808 dff7b6f1 Sofia Papagiannaki
        
809 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)
810 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)
811 46286f5f Antony Chazapis
        return dest_version_id
812 dff7b6f1 Sofia Papagiannaki
    
813 dff7b6f1 Sofia Papagiannaki
    @backend_method
814 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):
815 a9b3f29d Antony Chazapis
        """Move an object's data and metadata."""
816 a9b3f29d Antony Chazapis
        
817 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)
818 79bb41b7 Antony Chazapis
        if user != src_account:
819 79bb41b7 Antony Chazapis
            raise NotAllowedError
820 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)
821 02c4d2ba Antony Chazapis
        return dest_version_id
822 a9b3f29d Antony Chazapis
    
823 4d15c94e Sofia Papagiannaki
    def _delete_object(self, user, account, container, name, until=None, delimiter=None):
824 a9b3f29d Antony Chazapis
        if user != account:
825 a9b3f29d Antony Chazapis
            raise NotAllowedError
826 a9b3f29d Antony Chazapis
        
827 a9b3f29d Antony Chazapis
        if until is not None:
828 a9b3f29d Antony Chazapis
            path = '/'.join((account, container, name))
829 c915d3bf Antony Chazapis
            node = self.node.node_lookup(path)
830 c915d3bf Antony Chazapis
            if node is None:
831 c915d3bf Antony Chazapis
                return
832 813e42e5 Antony Chazapis
            hashes = []
833 813e42e5 Antony Chazapis
            size = 0
834 813e42e5 Antony Chazapis
            h, s = self.node.node_purge(node, until, CLUSTER_NORMAL)
835 813e42e5 Antony Chazapis
            hashes += h
836 813e42e5 Antony Chazapis
            size += s
837 813e42e5 Antony Chazapis
            h, s = self.node.node_purge(node, until, CLUSTER_HISTORY)
838 813e42e5 Antony Chazapis
            hashes += h
839 813e42e5 Antony Chazapis
            size += s
840 04230536 Antony Chazapis
            for h in hashes:
841 04230536 Antony Chazapis
                self.store.map_delete(h)
842 4a1c29ea Antony Chazapis
            self.node.node_purge(node, until, CLUSTER_DELETED)
843 a9b3f29d Antony Chazapis
            try:
844 c915d3bf Antony Chazapis
                props = self._get_version(node)
845 a9b3f29d Antony Chazapis
            except NameError:
846 0f9d752c Antony Chazapis
                self.permissions.access_clear(path)
847 813e42e5 Antony Chazapis
            self._report_size_change(user, account, -size, {'action': 'object purge'})
848 a9b3f29d Antony Chazapis
            return
849 a9b3f29d Antony Chazapis
        
850 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
851 33b4e4a6 Antony Chazapis
        src_version_id, dest_version_id = self._put_version_duplicate(user, node, size=0, type='', hash=None, checksum='', cluster=CLUSTER_DELETED)
852 813e42e5 Antony Chazapis
        del_size = self._apply_versioning(account, container, src_version_id)
853 813e42e5 Antony Chazapis
        if del_size:
854 813e42e5 Antony Chazapis
            self._report_size_change(user, account, -del_size, {'action': 'object delete'})
855 39ef6f41 Antony Chazapis
        self._report_object_change(user, account, path, details={'action': 'object delete'})
856 0f9d752c Antony Chazapis
        self.permissions.access_clear(path)
857 4d15c94e Sofia Papagiannaki
        
858 4d15c94e Sofia Papagiannaki
        if delimiter:
859 4d15c94e Sofia Papagiannaki
            prefix = name + delimiter if not name.endswith(delimiter) else name
860 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)
861 4d15c94e Sofia Papagiannaki
            paths = []
862 4d15c94e Sofia Papagiannaki
            for t in src_names:
863 4d15c94e Sofia Papagiannaki
                    path = '/'.join((account, container, t[0]))
864 4d15c94e Sofia Papagiannaki
                    node = t[2]
865 4d15c94e Sofia Papagiannaki
                src_version_id, dest_version_id = self._put_version_duplicate(user, node, size=0, type='', hash=None, checksum='', cluster=CLUSTER_DELETED)
866 4d15c94e Sofia Papagiannaki
                del_size = self._apply_versioning(account, container, src_version_id)
867 4d15c94e Sofia Papagiannaki
                if del_size:
868 4d15c94e Sofia Papagiannaki
                    self._report_size_change(user, account, -del_size, {'action': 'object delete'})
869 4d15c94e Sofia Papagiannaki
                self._report_object_change(user, account, path, details={'action': 'object delete'})
870 4d15c94e Sofia Papagiannaki
                paths.append(path)
871 4d15c94e Sofia Papagiannaki
            self.permissions.access_clear_bulk(paths)
872 4d15c94e Sofia Papagiannaki
    
873 4d15c94e Sofia Papagiannaki
    @backend_method
874 4d15c94e Sofia Papagiannaki
    def delete_object(self, user, account, container, name, until=None, prefix='', delimiter=None):
875 dff7b6f1 Sofia Papagiannaki
        """Delete/purge an object."""
876 dff7b6f1 Sofia Papagiannaki
        
877 fe2db49d Sofia Papagiannaki
        logger.debug("delete_object: %s %s %s %s %s %s %s", user, account, container, name, until, prefix, delimiter)
878 4d15c94e Sofia Papagiannaki
        self._delete_object(user, account, container, name, until, delimiter)
879 dff7b6f1 Sofia Papagiannaki
    
880 dff7b6f1 Sofia Papagiannaki
    @backend_method
881 62f915a1 Antony Chazapis
    def list_versions(self, user, account, container, name):
882 62f915a1 Antony Chazapis
        """Return a list of all (version, version_timestamp) tuples for an object."""
883 62f915a1 Antony Chazapis
        
884 fe2db49d Sofia Papagiannaki
        logger.debug("list_versions: %s %s %s %s", user, account, container, name)
885 62f915a1 Antony Chazapis
        self._can_read(user, account, container, name)
886 60b8a083 Antony Chazapis
        path, node = self._lookup_object(account, container, name)
887 97d45f69 Antony Chazapis
        versions = self.node.node_get_versions(node)
888 97d45f69 Antony Chazapis
        return [[x[self.SERIAL], x[self.MTIME]] for x in versions if x[self.CLUSTER] != CLUSTER_DELETED]
889 a9b3f29d Antony Chazapis
    
890 bb4eafc6 Antony Chazapis
    @backend_method
891 37bee317 Antony Chazapis
    def get_uuid(self, user, uuid):
892 37bee317 Antony Chazapis
        """Return the (account, container, name) for the UUID given."""
893 af75e8a5 Antony Chazapis
        
894 fe2db49d Sofia Papagiannaki
        logger.debug("get_uuid: %s %s", user, uuid)
895 37bee317 Antony Chazapis
        info = self.node.latest_uuid(uuid)
896 37bee317 Antony Chazapis
        if info is None:
897 37bee317 Antony Chazapis
            raise NameError
898 37bee317 Antony Chazapis
        path, serial = info
899 37bee317 Antony Chazapis
        account, container, name = path.split('/', 2)
900 37bee317 Antony Chazapis
        self._can_read(user, account, container, name)
901 37bee317 Antony Chazapis
        return (account, container, name)
902 37bee317 Antony Chazapis
    
903 37bee317 Antony Chazapis
    @backend_method
904 bb4eafc6 Antony Chazapis
    def get_public(self, user, public):
905 bb4eafc6 Antony Chazapis
        """Return the (account, container, name) for the public id given."""
906 af75e8a5 Antony Chazapis
        
907 fe2db49d Sofia Papagiannaki
        logger.debug("get_public: %s %s", user, public)
908 bb4eafc6 Antony Chazapis
        if public is None or public < ULTIMATE_ANSWER:
909 bb4eafc6 Antony Chazapis
            raise NameError
910 bb4eafc6 Antony Chazapis
        path = self.permissions.public_path(public - ULTIMATE_ANSWER)
911 37bee317 Antony Chazapis
        if path is None:
912 37bee317 Antony Chazapis
            raise NameError
913 bb4eafc6 Antony Chazapis
        account, container, name = path.split('/', 2)
914 bb4eafc6 Antony Chazapis
        self._can_read(user, account, container, name)
915 bb4eafc6 Antony Chazapis
        return (account, container, name)
916 bb4eafc6 Antony Chazapis
    
917 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
918 a9b3f29d Antony Chazapis
    def get_block(self, hash):
919 a9b3f29d Antony Chazapis
        """Return a block's data."""
920 a9b3f29d Antony Chazapis
        
921 a9b3f29d Antony Chazapis
        logger.debug("get_block: %s", hash)
922 7ca7bb08 Antony Chazapis
        block = self.store.block_get(binascii.unhexlify(hash))
923 7ca7bb08 Antony Chazapis
        if not block:
924 a9b3f29d Antony Chazapis
            raise NameError('Block does not exist')
925 7ca7bb08 Antony Chazapis
        return block
926 a9b3f29d Antony Chazapis
    
927 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
928 a9b3f29d Antony Chazapis
    def put_block(self, data):
929 60b8a083 Antony Chazapis
        """Store a block and return the hash."""
930 a9b3f29d Antony Chazapis
        
931 a9b3f29d Antony Chazapis
        logger.debug("put_block: %s", len(data))
932 7ca7bb08 Antony Chazapis
        return binascii.hexlify(self.store.block_put(data))
933 a9b3f29d Antony Chazapis
    
934 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
935 a9b3f29d Antony Chazapis
    def update_block(self, hash, data, offset=0):
936 a9b3f29d Antony Chazapis
        """Update a known block and return the hash."""
937 a9b3f29d Antony Chazapis
        
938 a9b3f29d Antony Chazapis
        logger.debug("update_block: %s %s %s", hash, len(data), offset)
939 a9b3f29d Antony Chazapis
        if offset == 0 and len(data) == self.block_size:
940 a9b3f29d Antony Chazapis
            return self.put_block(data)
941 7ca7bb08 Antony Chazapis
        h = self.store.block_update(binascii.unhexlify(hash), offset, data)
942 a9b3f29d Antony Chazapis
        return binascii.hexlify(h)
943 a9b3f29d Antony Chazapis
    
944 44ad5860 Antony Chazapis
    # Path functions.
945 44ad5860 Antony Chazapis
    
946 37bee317 Antony Chazapis
    def _generate_uuid(self):
947 37bee317 Antony Chazapis
        return str(uuidlib.uuid4())
948 25ae8b75 Antony Chazapis
    
949 b9064632 Antony Chazapis
    def _put_object_node(self, path, parent, name):
950 c915d3bf Antony Chazapis
        path = '/'.join((path, name))
951 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
952 c915d3bf Antony Chazapis
        if node is None:
953 c915d3bf Antony Chazapis
            node = self.node.node_create(parent, path)
954 c915d3bf Antony Chazapis
        return path, node
955 c915d3bf Antony Chazapis
    
956 62f915a1 Antony Chazapis
    def _put_path(self, user, parent, path):
957 62f915a1 Antony Chazapis
        node = self.node.node_create(parent, path)
958 33b4e4a6 Antony Chazapis
        self.node.version_create(node, None, 0, '', None, user, self._generate_uuid(), '', CLUSTER_NORMAL)
959 62f915a1 Antony Chazapis
        return node
960 62f915a1 Antony Chazapis
    
961 44ad5860 Antony Chazapis
    def _lookup_account(self, account, create=True):
962 44ad5860 Antony Chazapis
        node = self.node.node_lookup(account)
963 44ad5860 Antony Chazapis
        if node is None and create:
964 2c5363a0 Antony Chazapis
            node = self._put_path(account, self.ROOTNODE, account) # User is account.
965 c915d3bf Antony Chazapis
        return account, node
966 44ad5860 Antony Chazapis
    
967 44ad5860 Antony Chazapis
    def _lookup_container(self, account, container):
968 c915d3bf Antony Chazapis
        path = '/'.join((account, container))
969 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
970 44ad5860 Antony Chazapis
        if node is None:
971 44ad5860 Antony Chazapis
            raise NameError('Container does not exist')
972 c915d3bf Antony Chazapis
        return path, node
973 44ad5860 Antony Chazapis
    
974 44ad5860 Antony Chazapis
    def _lookup_object(self, account, container, name):
975 c915d3bf Antony Chazapis
        path = '/'.join((account, container, name))
976 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
977 44ad5860 Antony Chazapis
        if node is None:
978 44ad5860 Antony Chazapis
            raise NameError('Object does not exist')
979 c915d3bf Antony Chazapis
        return path, node
980 44ad5860 Antony Chazapis
    
981 cf4a7a7b Sofia Papagiannaki
    def _lookup_objects(self, paths):
982 cf4a7a7b Sofia Papagiannaki
            nodes = self.node.node_lookup_bulk(paths)
983 cf4a7a7b Sofia Papagiannaki
        if nodes is None:
984 cf4a7a7b Sofia Papagiannaki
            raise NameError('Object does not exist')
985 cf4a7a7b Sofia Papagiannaki
        return paths, nodes
986 cf4a7a7b Sofia Papagiannaki
    
987 44ad5860 Antony Chazapis
    def _get_properties(self, node, until=None):
988 44ad5860 Antony Chazapis
        """Return properties until the timestamp given."""
989 44ad5860 Antony Chazapis
        
990 44ad5860 Antony Chazapis
        before = until if until is not None else inf
991 44ad5860 Antony Chazapis
        props = self.node.version_lookup(node, before, CLUSTER_NORMAL)
992 44ad5860 Antony Chazapis
        if props is None and until is not None:
993 44ad5860 Antony Chazapis
            props = self.node.version_lookup(node, before, CLUSTER_HISTORY)
994 44ad5860 Antony Chazapis
        if props is None:
995 44ad5860 Antony Chazapis
            raise NameError('Path does not exist')
996 44ad5860 Antony Chazapis
        return props
997 44ad5860 Antony Chazapis
    
998 62f915a1 Antony Chazapis
    def _get_statistics(self, node, until=None):
999 62f915a1 Antony Chazapis
        """Return count, sum of size and latest timestamp of everything under node."""
1000 c915d3bf Antony Chazapis
        
1001 44ad5860 Antony Chazapis
        if until is None:
1002 62f915a1 Antony Chazapis
            stats = self.node.statistics_get(node, CLUSTER_NORMAL)
1003 44ad5860 Antony Chazapis
        else:
1004 62f915a1 Antony Chazapis
            stats = self.node.statistics_latest(node, until, CLUSTER_DELETED)
1005 62f915a1 Antony Chazapis
        if stats is None:
1006 62f915a1 Antony Chazapis
            stats = (0, 0, 0)
1007 62f915a1 Antony Chazapis
        return stats
1008 44ad5860 Antony Chazapis
    
1009 44ad5860 Antony Chazapis
    def _get_version(self, node, version=None):
1010 44ad5860 Antony Chazapis
        if version is None:
1011 44ad5860 Antony Chazapis
            props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1012 44ad5860 Antony Chazapis
            if props is None:
1013 44ad5860 Antony Chazapis
                raise NameError('Object does not exist')
1014 44ad5860 Antony Chazapis
        else:
1015 07afd277 Antony Chazapis
            try:
1016 07afd277 Antony Chazapis
                version = int(version)
1017 07afd277 Antony Chazapis
            except ValueError:
1018 07afd277 Antony Chazapis
                raise IndexError('Version does not exist')
1019 44ad5860 Antony Chazapis
            props = self.node.version_get_properties(version)
1020 2c5363a0 Antony Chazapis
            if props is None or props[self.CLUSTER] == CLUSTER_DELETED:
1021 44ad5860 Antony Chazapis
                raise IndexError('Version does not exist')
1022 44ad5860 Antony Chazapis
        return props
1023 4d15c94e Sofia Papagiannaki
1024 4d15c94e Sofia Papagiannaki
    def _get_versions(self, nodes, version=None):
1025 4d15c94e Sofia Papagiannaki
        if version is None:
1026 4d15c94e Sofia Papagiannaki
            props = self.node.version_lookup_bulk(nodes, inf, CLUSTER_NORMAL)
1027 4d15c94e Sofia Papagiannaki
            if not props:
1028 4d15c94e Sofia Papagiannaki
                raise NameError('Object does not exist')
1029 4d15c94e Sofia Papagiannaki
        else:
1030 4d15c94e Sofia Papagiannaki
            try:
1031 4d15c94e Sofia Papagiannaki
                version = int(version)
1032 4d15c94e Sofia Papagiannaki
            except ValueError:
1033 4d15c94e Sofia Papagiannaki
                raise IndexError('Version does not exist')
1034 4d15c94e Sofia Papagiannaki
            props = self.node.version_get_properties(version)
1035 4d15c94e Sofia Papagiannaki
            if props is None or props[self.CLUSTER] == CLUSTER_DELETED:
1036 4d15c94e Sofia Papagiannaki
                raise IndexError('Version does not exist')
1037 4d15c94e Sofia Papagiannaki
        return props
1038 44ad5860 Antony Chazapis
    
1039 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):
1040 b9064632 Antony Chazapis
        """Create a new version of the node."""
1041 c915d3bf Antony Chazapis
        
1042 1730b3bf chazapis
        props = self.node.version_lookup(node if src_node is None else src_node, inf, CLUSTER_NORMAL)
1043 b9064632 Antony Chazapis
        if props is not None:
1044 b9064632 Antony Chazapis
            src_version_id = props[self.SERIAL]
1045 b9064632 Antony Chazapis
            src_hash = props[self.HASH]
1046 b9064632 Antony Chazapis
            src_size = props[self.SIZE]
1047 66ce2ca5 Antony Chazapis
            src_type = props[self.TYPE]
1048 33b4e4a6 Antony Chazapis
            src_checksum = props[self.CHECKSUM]
1049 44ad5860 Antony Chazapis
        else:
1050 b9064632 Antony Chazapis
            src_version_id = None
1051 b9064632 Antony Chazapis
            src_hash = None
1052 b9064632 Antony Chazapis
            src_size = 0
1053 66ce2ca5 Antony Chazapis
            src_type = ''
1054 33b4e4a6 Antony Chazapis
            src_checksum = ''
1055 66ce2ca5 Antony Chazapis
        if size is None: # Set metadata.
1056 66ce2ca5 Antony Chazapis
            hash = src_hash # This way hash can be set to None (account or container).
1057 b9064632 Antony Chazapis
            size = src_size
1058 66ce2ca5 Antony Chazapis
        if type is None:
1059 66ce2ca5 Antony Chazapis
            type = src_type
1060 33b4e4a6 Antony Chazapis
        if checksum is None:
1061 33b4e4a6 Antony Chazapis
            checksum = src_checksum
1062 1730b3bf chazapis
        uuid = self._generate_uuid() if (is_copy or src_version_id is None) else props[self.UUID]
1063 1730b3bf chazapis
        
1064 1730b3bf chazapis
        if src_node is None:
1065 1730b3bf chazapis
            pre_version_id = src_version_id
1066 1730b3bf chazapis
        else:
1067 1730b3bf chazapis
            pre_version_id = None
1068 1730b3bf chazapis
            props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1069 1730b3bf chazapis
            if props is not None:
1070 1730b3bf chazapis
                pre_version_id = props[self.SERIAL]
1071 1730b3bf chazapis
        if pre_version_id is not None:
1072 1730b3bf chazapis
            self.node.version_recluster(pre_version_id, CLUSTER_HISTORY)
1073 b9064632 Antony Chazapis
        
1074 33b4e4a6 Antony Chazapis
        dest_version_id, mtime = self.node.version_create(node, hash, size, type, src_version_id, user, uuid, checksum, cluster)
1075 1730b3bf chazapis
        return pre_version_id, dest_version_id
1076 44ad5860 Antony Chazapis
    
1077 4819d34f Antony Chazapis
    def _put_metadata_duplicate(self, src_version_id, dest_version_id, domain, meta, replace=False):
1078 4819d34f Antony Chazapis
        if src_version_id is not None:
1079 4819d34f Antony Chazapis
            self.node.attribute_copy(src_version_id, dest_version_id)
1080 4819d34f Antony Chazapis
        if not replace:
1081 4819d34f Antony Chazapis
            self.node.attribute_del(dest_version_id, domain, (k for k, v in meta.iteritems() if v == ''))
1082 4819d34f Antony Chazapis
            self.node.attribute_set(dest_version_id, domain, ((k, v) for k, v in meta.iteritems() if v != ''))
1083 4819d34f Antony Chazapis
        else:
1084 4819d34f Antony Chazapis
            self.node.attribute_del(dest_version_id, domain)
1085 4819d34f Antony Chazapis
            self.node.attribute_set(dest_version_id, domain, ((k, v) for k, v in meta.iteritems()))
1086 4819d34f Antony Chazapis
    
1087 4819d34f Antony Chazapis
    def _put_metadata(self, user, node, domain, meta, replace=False):
1088 44ad5860 Antony Chazapis
        """Create a new version and store metadata."""
1089 44ad5860 Antony Chazapis
        
1090 b9064632 Antony Chazapis
        src_version_id, dest_version_id = self._put_version_duplicate(user, node)
1091 4819d34f Antony Chazapis
        self._put_metadata_duplicate(src_version_id, dest_version_id, domain, meta, replace)
1092 5cc484e1 Antony Chazapis
        return src_version_id, dest_version_id
1093 44ad5860 Antony Chazapis
    
1094 60b8a083 Antony Chazapis
    def _list_limits(self, listing, marker, limit):
1095 60b8a083 Antony Chazapis
        start = 0
1096 60b8a083 Antony Chazapis
        if marker:
1097 60b8a083 Antony Chazapis
            try:
1098 60b8a083 Antony Chazapis
                start = listing.index(marker) + 1
1099 60b8a083 Antony Chazapis
            except ValueError:
1100 60b8a083 Antony Chazapis
                pass
1101 60b8a083 Antony Chazapis
        if not limit or limit > 10000:
1102 60b8a083 Antony Chazapis
            limit = 10000
1103 60b8a083 Antony Chazapis
        return start, limit
1104 60b8a083 Antony Chazapis
    
1105 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):
1106 60b8a083 Antony Chazapis
        cont_prefix = path + '/'
1107 60b8a083 Antony Chazapis
        prefix = cont_prefix + prefix
1108 60b8a083 Antony Chazapis
        start = cont_prefix + marker if marker else None
1109 60b8a083 Antony Chazapis
        before = until if until is not None else inf
1110 4819d34f Antony Chazapis
        filterq = keys if domain else []
1111 7ff57991 Antony Chazapis
        sizeq = size_range
1112 60b8a083 Antony Chazapis
        
1113 371d907a Antony Chazapis
        objects, prefixes = self.node.latest_version_list(parent, prefix, delimiter, start, limit, before, CLUSTER_DELETED, allowed, domain, filterq, sizeq, all_props)
1114 60b8a083 Antony Chazapis
        objects.extend([(p, None) for p in prefixes] if virtual else [])
1115 43be9afd Sofia Papagiannaki
        objects.sort(key=lambda x: x[0])
1116 371d907a Antony Chazapis
        objects = [(x[0][len(cont_prefix):],) + x[1:] for x in objects]
1117 cf4a7a7b Sofia Papagiannaki
        return objects
1118 60b8a083 Antony Chazapis
        
1119 813e42e5 Antony Chazapis
    # Reporting functions.
1120 813e42e5 Antony Chazapis
    
1121 813e42e5 Antony Chazapis
    def _report_size_change(self, user, account, size, details={}):
1122 813e42e5 Antony Chazapis
        logger.debug("_report_size_change: %s %s %s %s", user, account, size, details)
1123 813e42e5 Antony Chazapis
        account_node = self._lookup_account(account, True)[1]
1124 813e42e5 Antony Chazapis
        total = self._get_statistics(account_node)[1]
1125 813e42e5 Antony Chazapis
        details.update({'user': user, 'total': total})
1126 73673127 Antony Chazapis
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('resource.diskspace',), account, QUEUE_INSTANCE_ID, 'diskspace', float(size), details))
1127 39ef6f41 Antony Chazapis
    
1128 39ef6f41 Antony Chazapis
    def _report_object_change(self, user, account, path, details={}):
1129 39ef6f41 Antony Chazapis
        logger.debug("_report_object_change: %s %s %s %s", user, account, path, details)
1130 39ef6f41 Antony Chazapis
        details.update({'user': user})
1131 73673127 Antony Chazapis
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('object',), account, QUEUE_INSTANCE_ID, 'object', path, details))
1132 813e42e5 Antony Chazapis
    
1133 a74ba506 Sofia Papagiannaki
    def _report_sharing_change(self, user, account, path, details={}):
1134 a74ba506 Sofia Papagiannaki
        logger.debug("_report_permissions_change: %s %s %s %s", user, account, path, details)
1135 a74ba506 Sofia Papagiannaki
        details.update({'user': user})
1136 a74ba506 Sofia Papagiannaki
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('sharing',), account, QUEUE_INSTANCE_ID, 'sharing', path, details))
1137 a74ba506 Sofia Papagiannaki
    
1138 60b8a083 Antony Chazapis
    # Policy functions.
1139 60b8a083 Antony Chazapis
    
1140 60b8a083 Antony Chazapis
    def _check_policy(self, policy):
1141 60b8a083 Antony Chazapis
        for k in policy.keys():
1142 60b8a083 Antony Chazapis
            if policy[k] == '':
1143 60b8a083 Antony Chazapis
                policy[k] = self.default_policy.get(k)
1144 60b8a083 Antony Chazapis
        for k, v in policy.iteritems():
1145 60b8a083 Antony Chazapis
            if k == 'quota':
1146 60b8a083 Antony Chazapis
                q = int(v) # May raise ValueError.
1147 60b8a083 Antony Chazapis
                if q < 0:
1148 60b8a083 Antony Chazapis
                    raise ValueError
1149 60b8a083 Antony Chazapis
            elif k == 'versioning':
1150 5cc484e1 Antony Chazapis
                if v not in ['auto', 'none']:
1151 60b8a083 Antony Chazapis
                    raise ValueError
1152 60b8a083 Antony Chazapis
            else:
1153 60b8a083 Antony Chazapis
                raise ValueError
1154 60b8a083 Antony Chazapis
    
1155 b2832c6a Antony Chazapis
    def _put_policy(self, node, policy, replace):
1156 b2832c6a Antony Chazapis
        if replace:
1157 b2832c6a Antony Chazapis
            for k, v in self.default_policy.iteritems():
1158 b2832c6a Antony Chazapis
                if k not in policy:
1159 b2832c6a Antony Chazapis
                    policy[k] = v
1160 b2832c6a Antony Chazapis
        self.node.policy_set(node, policy)
1161 b2832c6a Antony Chazapis
    
1162 b9064632 Antony Chazapis
    def _get_policy(self, node):
1163 b9064632 Antony Chazapis
        policy = self.default_policy.copy()
1164 b9064632 Antony Chazapis
        policy.update(self.node.policy_get(node))
1165 b9064632 Antony Chazapis
        return policy
1166 b9064632 Antony Chazapis
    
1167 5cc484e1 Antony Chazapis
    def _apply_versioning(self, account, container, version_id):
1168 813e42e5 Antony Chazapis
        """Delete the provided version if such is the policy.
1169 813e42e5 Antony Chazapis
           Return size of object removed.
1170 813e42e5 Antony Chazapis
        """
1171 813e42e5 Antony Chazapis
        
1172 5cc484e1 Antony Chazapis
        if version_id is None:
1173 813e42e5 Antony Chazapis
            return 0
1174 5cc484e1 Antony Chazapis
        path, node = self._lookup_container(account, container)
1175 5cc484e1 Antony Chazapis
        versioning = self._get_policy(node)['versioning']
1176 5cc484e1 Antony Chazapis
        if versioning != 'auto':
1177 813e42e5 Antony Chazapis
            hash, size = self.node.version_remove(version_id)
1178 5161c672 Antony Chazapis
            self.store.map_delete(hash)
1179 813e42e5 Antony Chazapis
            return size
1180 813e42e5 Antony Chazapis
        return 0
1181 5cc484e1 Antony Chazapis
    
1182 a9b3f29d Antony Chazapis
    # Access control functions.
1183 a9b3f29d Antony Chazapis
    
1184 a9b3f29d Antony Chazapis
    def _check_groups(self, groups):
1185 0f9d752c Antony Chazapis
        # raise ValueError('Bad characters in groups')
1186 a9b3f29d Antony Chazapis
        pass
1187 a9b3f29d Antony Chazapis
    
1188 a9b3f29d Antony Chazapis
    def _check_permissions(self, path, permissions):
1189 0f9d752c Antony Chazapis
        # raise ValueError('Bad characters in permissions')
1190 5e068361 Antony Chazapis
        pass
1191 5e068361 Antony Chazapis
    
1192 92da0e5a Antony Chazapis
    def _get_formatted_paths(self, paths):
1193 92da0e5a Antony Chazapis
        formatted = []
1194 92da0e5a Antony Chazapis
        for p in paths:
1195 92da0e5a Antony Chazapis
            node = self.node.node_lookup(p)
1196 92da0e5a Antony Chazapis
            if node is not None:
1197 92da0e5a Antony Chazapis
                props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1198 92da0e5a Antony Chazapis
            if props is not None:
1199 692485cc Antony Chazapis
                if props[self.TYPE].split(';', 1)[0].strip() in ('application/directory', 'application/folder'):
1200 d57eaad4 Antony Chazapis
                    formatted.append((p.rstrip('/') + '/', self.MATCH_PREFIX))
1201 d57eaad4 Antony Chazapis
                formatted.append((p, self.MATCH_EXACT))
1202 92da0e5a Antony Chazapis
        return formatted
1203 92da0e5a Antony Chazapis
    
1204 5e068361 Antony Chazapis
    def _get_permissions_path(self, account, container, name):
1205 5e068361 Antony Chazapis
        path = '/'.join((account, container, name))
1206 5e068361 Antony Chazapis
        permission_paths = self.permissions.access_inherit(path)
1207 5e068361 Antony Chazapis
        permission_paths.sort()
1208 5e068361 Antony Chazapis
        permission_paths.reverse()
1209 5e068361 Antony Chazapis
        for p in permission_paths:
1210 5e068361 Antony Chazapis
            if p == path:
1211 5e068361 Antony Chazapis
                return p
1212 5e068361 Antony Chazapis
            else:
1213 71dbc012 Antony Chazapis
                if p.count('/') < 2:
1214 71dbc012 Antony Chazapis
                    continue
1215 92da0e5a Antony Chazapis
                node = self.node.node_lookup(p)
1216 92da0e5a Antony Chazapis
                if node is not None:
1217 92da0e5a Antony Chazapis
                    props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1218 92da0e5a Antony Chazapis
                if props is not None:
1219 692485cc Antony Chazapis
                    if props[self.TYPE].split(';', 1)[0].strip() in ('application/directory', 'application/folder'):
1220 5e068361 Antony Chazapis
                        return p
1221 5e068361 Antony Chazapis
        return None
1222 a9b3f29d Antony Chazapis
    
1223 6f4bce7b Antony Chazapis
    def _can_read(self, user, account, container, name):
1224 a9b3f29d Antony Chazapis
        if user == account:
1225 a9b3f29d Antony Chazapis
            return True
1226 a9b3f29d Antony Chazapis
        path = '/'.join((account, container, name))
1227 aeb2b64f Antony Chazapis
        if self.permissions.public_get(path) is not None:
1228 71dbc012 Antony Chazapis
            return True
1229 5e068361 Antony Chazapis
        path = self._get_permissions_path(account, container, name)
1230 71dbc012 Antony Chazapis
        if not path:
1231 71dbc012 Antony Chazapis
            raise NotAllowedError
1232 2c5363a0 Antony Chazapis
        if not self.permissions.access_check(path, self.READ, user) and not self.permissions.access_check(path, self.WRITE, user):
1233 a9b3f29d Antony Chazapis
            raise NotAllowedError
1234 a9b3f29d Antony Chazapis
    
1235 a9b3f29d Antony Chazapis
    def _can_write(self, user, account, container, name):
1236 6f4bce7b Antony Chazapis
        if user == account:
1237 6f4bce7b Antony Chazapis
            return True
1238 6f4bce7b Antony Chazapis
        path = '/'.join((account, container, name))
1239 71dbc012 Antony Chazapis
        path = self._get_permissions_path(account, container, name)
1240 71dbc012 Antony Chazapis
        if not path:
1241 71dbc012 Antony Chazapis
            raise NotAllowedError
1242 2c5363a0 Antony Chazapis
        if not self.permissions.access_check(path, self.WRITE, user):
1243 a9b3f29d Antony Chazapis
            raise NotAllowedError
1244 a9b3f29d Antony Chazapis
    
1245 a9b3f29d Antony Chazapis
    def _allowed_accounts(self, user):
1246 a9b3f29d Antony Chazapis
        allow = set()
1247 0f9d752c Antony Chazapis
        for path in self.permissions.access_list_paths(user):
1248 a9b3f29d Antony Chazapis
            allow.add(path.split('/', 1)[0])
1249 a9b3f29d Antony Chazapis
        return sorted(allow)
1250 a9b3f29d Antony Chazapis
    
1251 a9b3f29d Antony Chazapis
    def _allowed_containers(self, user, account):
1252 a9b3f29d Antony Chazapis
        allow = set()
1253 0f9d752c Antony Chazapis
        for path in self.permissions.access_list_paths(user, account):
1254 a9b3f29d Antony Chazapis
            allow.add(path.split('/', 2)[1])
1255 a9b3f29d Antony Chazapis
        return sorted(allow)