Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / modular.py @ 8d9a3fbd

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