Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / modular.py @ 39ef6f41

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