Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / modular.py @ 90ee1eb3

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