Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / modular.py @ f9ea264b

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