Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (50.2 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 73673127 Antony Chazapis
QUEUE_INSTANCE_ID = '1'
83 228de81b Antony Chazapis
84 44ad5860 Antony Chazapis
( CLUSTER_NORMAL, CLUSTER_HISTORY, CLUSTER_DELETED ) = range(3)
85 44ad5860 Antony Chazapis
86 44ad5860 Antony Chazapis
inf = float('inf')
87 44ad5860 Antony Chazapis
88 bb4eafc6 Antony Chazapis
ULTIMATE_ANSWER = 42
89 bb4eafc6 Antony Chazapis
90 a9b3f29d Antony Chazapis
91 a9b3f29d Antony Chazapis
logger = logging.getLogger(__name__)
92 a9b3f29d Antony Chazapis
93 1c2fc0ff Antony Chazapis
94 a9b3f29d Antony Chazapis
def backend_method(func=None, autocommit=1):
95 a9b3f29d Antony Chazapis
    if func is None:
96 a9b3f29d Antony Chazapis
        def fn(func):
97 a9b3f29d Antony Chazapis
            return backend_method(func, autocommit)
98 a9b3f29d Antony Chazapis
        return fn
99 a9b3f29d Antony Chazapis
100 a9b3f29d Antony Chazapis
    if not autocommit:
101 a9b3f29d Antony Chazapis
        return func
102 a9b3f29d Antony Chazapis
    def fn(self, *args, **kw):
103 2c5363a0 Antony Chazapis
        self.wrapper.execute()
104 a9b3f29d Antony Chazapis
        try:
105 39ef6f41 Antony Chazapis
            self.messages = []
106 a9b3f29d Antony Chazapis
            ret = func(self, *args, **kw)
107 39ef6f41 Antony Chazapis
            for m in self.messages:
108 39ef6f41 Antony Chazapis
                self.queue.send(*m)
109 a74ba506 Sofia Papagiannaki
            self.wrapper.commit()
110 a9b3f29d Antony Chazapis
            return ret
111 a9b3f29d Antony Chazapis
        except:
112 2c5363a0 Antony Chazapis
            self.wrapper.rollback()
113 a9b3f29d Antony Chazapis
            raise
114 a9b3f29d Antony Chazapis
    return fn
115 a9b3f29d Antony Chazapis
116 a9b3f29d Antony Chazapis
117 a9b3f29d Antony Chazapis
class ModularBackend(BaseBackend):
118 a9b3f29d Antony Chazapis
    """A modular backend.
119 a9b3f29d Antony Chazapis
    
120 e9363f82 Antony Chazapis
    Uses modules for SQL functions and storage.
121 a9b3f29d Antony Chazapis
    """
122 a9b3f29d Antony Chazapis
    
123 46286f5f Antony Chazapis
    def __init__(self, db_module=None, db_connection=None,
124 46286f5f Antony Chazapis
                 block_module=None, block_path=None,
125 46286f5f Antony Chazapis
                 queue_module=None, queue_connection=None):
126 228de81b Antony Chazapis
        db_module = db_module or DEFAULT_DB_MODULE
127 228de81b Antony Chazapis
        db_connection = db_connection or DEFAULT_DB_CONNECTION
128 228de81b Antony Chazapis
        block_module = block_module or DEFAULT_BLOCK_MODULE
129 228de81b Antony Chazapis
        block_path = block_path or DEFAULT_BLOCK_PATH
130 46286f5f Antony Chazapis
        #queue_module = queue_module or DEFAULT_QUEUE_MODULE
131 46286f5f Antony Chazapis
        #queue_connection = queue_connection or DEFAULT_QUEUE_CONNECTION
132 f81e20b0 Giorgos Verigakis
        
133 a9b3f29d Antony Chazapis
        self.hash_algorithm = 'sha256'
134 a9b3f29d Antony Chazapis
        self.block_size = 4 * 1024 * 1024 # 4MB
135 a9b3f29d Antony Chazapis
        
136 228de81b Antony Chazapis
        self.default_policy = {'quota': DEFAULT_QUOTA, 'versioning': DEFAULT_VERSIONING}
137 a9b3f29d Antony Chazapis
        
138 46286f5f Antony Chazapis
        def load_module(m):
139 46286f5f Antony Chazapis
            __import__(m)
140 46286f5f Antony Chazapis
            return sys.modules[m]
141 a9b3f29d Antony Chazapis
        
142 46286f5f Antony Chazapis
        self.db_module = load_module(db_module)
143 46286f5f Antony Chazapis
        self.wrapper = self.db_module.DBWrapper(db_connection)
144 e9363f82 Antony Chazapis
        params = {'wrapper': self.wrapper}
145 e9363f82 Antony Chazapis
        self.permissions = self.db_module.Permissions(**params)
146 e9363f82 Antony Chazapis
        for x in ['READ', 'WRITE']:
147 e9363f82 Antony Chazapis
            setattr(self, x, getattr(self.db_module, x))
148 e9363f82 Antony Chazapis
        self.node = self.db_module.Node(**params)
149 33b4e4a6 Antony Chazapis
        for x in ['ROOTNODE', 'SERIAL', 'HASH', 'SIZE', 'TYPE', 'MTIME', 'MUSER', 'UUID', 'CHECKSUM', 'CLUSTER', 'MATCH_PREFIX', 'MATCH_EXACT']:
150 e9363f82 Antony Chazapis
            setattr(self, x, getattr(self.db_module, x))
151 e9363f82 Antony Chazapis
        
152 46286f5f Antony Chazapis
        self.block_module = load_module(block_module)
153 7ca7bb08 Antony Chazapis
        params = {'path': block_path,
154 7ca7bb08 Antony Chazapis
                  'block_size': self.block_size,
155 7ca7bb08 Antony Chazapis
                  'hash_algorithm': self.hash_algorithm}
156 7ca7bb08 Antony Chazapis
        self.store = self.block_module.Store(**params)
157 46286f5f Antony Chazapis
158 46286f5f Antony Chazapis
        if queue_module and queue_connection:
159 46286f5f Antony Chazapis
            self.queue_module = load_module(queue_module)
160 fa9cae7e Antony Chazapis
            params = {'exchange': queue_connection,
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 a74ba506 Sofia Papagiannaki
        self._report_sharing_change(user, account, path, {'members':self.permissions.access_members(path)})
607 a9b3f29d Antony Chazapis
    
608 a9b3f29d Antony Chazapis
    @backend_method
609 a9b3f29d Antony Chazapis
    def get_object_public(self, user, account, container, name):
610 bb4eafc6 Antony Chazapis
        """Return the public id of the object if applicable."""
611 a9b3f29d Antony Chazapis
        
612 a9b3f29d Antony Chazapis
        logger.debug("get_object_public: %s %s %s", account, container, name)
613 a9b3f29d Antony Chazapis
        self._can_read(user, account, container, name)
614 c915d3bf Antony Chazapis
        path = self._lookup_object(account, container, name)[0]
615 bb4eafc6 Antony Chazapis
        p = self.permissions.public_get(path)
616 bb4eafc6 Antony Chazapis
        if p is not None:
617 bb4eafc6 Antony Chazapis
            p += ULTIMATE_ANSWER
618 bb4eafc6 Antony Chazapis
        return p
619 a9b3f29d Antony Chazapis
    
620 a9b3f29d Antony Chazapis
    @backend_method
621 a9b3f29d Antony Chazapis
    def update_object_public(self, user, account, container, name, public):
622 a9b3f29d Antony Chazapis
        """Update the public status of the object."""
623 a9b3f29d Antony Chazapis
        
624 a9b3f29d Antony Chazapis
        logger.debug("update_object_public: %s %s %s %s", account, container, name, public)
625 a9b3f29d Antony Chazapis
        self._can_write(user, account, container, name)
626 c915d3bf Antony Chazapis
        path = self._lookup_object(account, container, name)[0]
627 0f9d752c Antony Chazapis
        if not public:
628 0f9d752c Antony Chazapis
            self.permissions.public_unset(path)
629 0f9d752c Antony Chazapis
        else:
630 0f9d752c Antony Chazapis
            self.permissions.public_set(path)
631 a9b3f29d Antony Chazapis
    
632 a9b3f29d Antony Chazapis
    @backend_method
633 a9b3f29d Antony Chazapis
    def get_object_hashmap(self, user, account, container, name, version=None):
634 a9b3f29d Antony Chazapis
        """Return the object's size and a list with partial hashes."""
635 a9b3f29d Antony Chazapis
        
636 a9b3f29d Antony Chazapis
        logger.debug("get_object_hashmap: %s %s %s %s", account, container, name, version)
637 a9b3f29d Antony Chazapis
        self._can_read(user, account, container, name)
638 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
639 c915d3bf Antony Chazapis
        props = self._get_version(node, version)
640 7ca7bb08 Antony Chazapis
        hashmap = self.store.map_get(binascii.unhexlify(props[self.HASH]))
641 2c5363a0 Antony Chazapis
        return props[self.SIZE], [binascii.hexlify(x) for x in hashmap]
642 a9b3f29d Antony Chazapis
    
643 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):
644 b9064632 Antony Chazapis
        if permissions is not None and user != account:
645 b9064632 Antony Chazapis
            raise NotAllowedError
646 b9064632 Antony Chazapis
        self._can_write(user, account, container, name)
647 b9064632 Antony Chazapis
        if permissions is not None:
648 b9064632 Antony Chazapis
            path = '/'.join((account, container, name))
649 b9064632 Antony Chazapis
            self._check_permissions(path, permissions)
650 b9064632 Antony Chazapis
        
651 b9064632 Antony Chazapis
        account_path, account_node = self._lookup_account(account, True)
652 b9064632 Antony Chazapis
        container_path, container_node = self._lookup_container(account, container)
653 b9064632 Antony Chazapis
        path, node = self._put_object_node(container_path, container_node, name)
654 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)
655 b9064632 Antony Chazapis
        
656 f9ea264b Antony Chazapis
        # Handle meta.
657 f9ea264b Antony Chazapis
        if src_version_id is None:
658 f9ea264b Antony Chazapis
            src_version_id = pre_version_id
659 f9ea264b Antony Chazapis
        self._put_metadata_duplicate(src_version_id, dest_version_id, domain, meta, replace_meta)
660 f9ea264b Antony Chazapis
        
661 b9064632 Antony Chazapis
        # Check quota.
662 813e42e5 Antony Chazapis
        del_size = self._apply_versioning(account, container, pre_version_id)
663 813e42e5 Antony Chazapis
        size_delta = size - del_size
664 b9064632 Antony Chazapis
        if size_delta > 0:
665 8693b873 Sofia Papagiannaki
            account_quota = long(self._get_policy(account_node)['quota'])
666 8693b873 Sofia Papagiannaki
            container_quota = long(self._get_policy(container_node)['quota'])
667 0df22aea Sofia Papagiannaki
            if (account_quota > 0 and self._get_statistics(account_node)[1] + size_delta > account_quota) or \
668 0df22aea Sofia Papagiannaki
               (container_quota > 0 and self._get_statistics(container_node)[1] + size_delta > container_quota):
669 b9064632 Antony Chazapis
                # This must be executed in a transaction, so the version is never created if it fails.
670 5df6c6d1 Antony Chazapis
                raise QuotaError
671 813e42e5 Antony Chazapis
        self._report_size_change(user, account, size_delta, {'action': 'object update'})
672 b9064632 Antony Chazapis
        
673 b9064632 Antony Chazapis
        if permissions is not None:
674 b9064632 Antony Chazapis
            self.permissions.access_set(path, permissions)
675 a74ba506 Sofia Papagiannaki
            self._report_sharing_change(user, account, path, {'members':self.permissions.access_members(path)})
676 39ef6f41 Antony Chazapis
        
677 39ef6f41 Antony Chazapis
        self._report_object_change(user, account, path, details={'version': dest_version_id, 'action': 'object update'})
678 f9ea264b Antony Chazapis
        return dest_version_id
679 b9064632 Antony Chazapis
    
680 a9b3f29d Antony Chazapis
    @backend_method
681 33b4e4a6 Antony Chazapis
    def update_object_hashmap(self, user, account, container, name, size, type, hashmap, checksum, domain, meta={}, replace_meta=False, permissions=None):
682 a9b3f29d Antony Chazapis
        """Create/update an object with the specified size and partial hashes."""
683 a9b3f29d Antony Chazapis
        
684 33b4e4a6 Antony Chazapis
        logger.debug("update_object_hashmap: %s %s %s %s %s %s %s", account, container, name, size, type, hashmap, checksum)
685 6d64339e Antony Chazapis
        if size == 0: # No such thing as an empty hashmap.
686 6d64339e Antony Chazapis
            hashmap = [self.put_block('')]
687 1c2fc0ff Antony Chazapis
        map = HashMap(self.block_size, self.hash_algorithm)
688 1c2fc0ff Antony Chazapis
        map.extend([binascii.unhexlify(x) for x in hashmap])
689 7ca7bb08 Antony Chazapis
        missing = self.store.block_search(map)
690 a9b3f29d Antony Chazapis
        if missing:
691 a9b3f29d Antony Chazapis
            ie = IndexError()
692 dd71f493 Antony Chazapis
            ie.data = [binascii.hexlify(x) for x in missing]
693 a9b3f29d Antony Chazapis
            raise ie
694 b9064632 Antony Chazapis
        
695 1c2fc0ff Antony Chazapis
        hash = map.hash()
696 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)
697 7ca7bb08 Antony Chazapis
        self.store.map_put(hash, map)
698 02c4d2ba Antony Chazapis
        return dest_version_id
699 a9b3f29d Antony Chazapis
    
700 33b4e4a6 Antony Chazapis
    @backend_method
701 33b4e4a6 Antony Chazapis
    def update_object_checksum(self, user, account, container, name, version, checksum):
702 33b4e4a6 Antony Chazapis
        """Update an object's checksum."""
703 33b4e4a6 Antony Chazapis
        
704 33b4e4a6 Antony Chazapis
        logger.debug("update_object_checksum: %s %s %s %s %s", account, container, name, version, checksum)
705 33b4e4a6 Antony Chazapis
        # Update objects with greater version and same hashmap and size (fix metadata updates).
706 33b4e4a6 Antony Chazapis
        self._can_write(user, account, container, name)
707 33b4e4a6 Antony Chazapis
        path, node = self._lookup_object(account, container, name)
708 33b4e4a6 Antony Chazapis
        props = self._get_version(node, version)
709 33b4e4a6 Antony Chazapis
        versions = self.node.node_get_versions(node)
710 33b4e4a6 Antony Chazapis
        for x in versions:
711 33b4e4a6 Antony Chazapis
            if x[self.SERIAL] >= int(version) and x[self.HASH] == props[self.HASH] and x[self.SIZE] == props[self.SIZE]:
712 33b4e4a6 Antony Chazapis
                self.node.version_put_property(x[self.SERIAL], 'checksum', checksum)
713 33b4e4a6 Antony Chazapis
    
714 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):
715 79bb41b7 Antony Chazapis
        self._can_read(user, src_account, src_container, src_name)
716 b9064632 Antony Chazapis
        path, node = self._lookup_object(src_account, src_container, src_name)
717 1730b3bf chazapis
        # TODO: Will do another fetch of the properties in duplicate version...
718 1730b3bf chazapis
        props = self._get_version(node, src_version) # Check to see if source exists.
719 b9064632 Antony Chazapis
        src_version_id = props[self.SERIAL]
720 b9064632 Antony Chazapis
        hash = props[self.HASH]
721 b9064632 Antony Chazapis
        size = props[self.SIZE]
722 b9064632 Antony Chazapis
        
723 25ae8b75 Antony Chazapis
        is_copy = not is_move and (src_account, src_container, src_name) != (dest_account, dest_container, dest_name) # New uuid.
724 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)
725 02c4d2ba Antony Chazapis
        return dest_version_id
726 a9b3f29d Antony Chazapis
    
727 a9b3f29d Antony Chazapis
    @backend_method
728 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):
729 dff7b6f1 Sofia Papagiannaki
        """Copy an object's data and metadata."""
730 dff7b6f1 Sofia Papagiannaki
        
731 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)
732 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)
733 46286f5f Antony Chazapis
        return dest_version_id
734 dff7b6f1 Sofia Papagiannaki
    
735 dff7b6f1 Sofia Papagiannaki
    @backend_method
736 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):
737 a9b3f29d Antony Chazapis
        """Move an object's data and metadata."""
738 a9b3f29d Antony Chazapis
        
739 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)
740 79bb41b7 Antony Chazapis
        if user != src_account:
741 79bb41b7 Antony Chazapis
            raise NotAllowedError
742 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)
743 371403f5 Antony Chazapis
        if (src_account, src_container, src_name) != (dest_account, dest_container, dest_name):
744 371403f5 Antony Chazapis
            self._delete_object(user, src_account, src_container, src_name)
745 02c4d2ba Antony Chazapis
        return dest_version_id
746 a9b3f29d Antony Chazapis
    
747 dff7b6f1 Sofia Papagiannaki
    def _delete_object(self, user, account, container, name, until=None):
748 a9b3f29d Antony Chazapis
        if user != account:
749 a9b3f29d Antony Chazapis
            raise NotAllowedError
750 a9b3f29d Antony Chazapis
        
751 a9b3f29d Antony Chazapis
        if until is not None:
752 a9b3f29d Antony Chazapis
            path = '/'.join((account, container, name))
753 c915d3bf Antony Chazapis
            node = self.node.node_lookup(path)
754 c915d3bf Antony Chazapis
            if node is None:
755 c915d3bf Antony Chazapis
                return
756 813e42e5 Antony Chazapis
            hashes = []
757 813e42e5 Antony Chazapis
            size = 0
758 813e42e5 Antony Chazapis
            h, s = self.node.node_purge(node, until, CLUSTER_NORMAL)
759 813e42e5 Antony Chazapis
            hashes += h
760 813e42e5 Antony Chazapis
            size += s
761 813e42e5 Antony Chazapis
            h, s = self.node.node_purge(node, until, CLUSTER_HISTORY)
762 813e42e5 Antony Chazapis
            hashes += h
763 813e42e5 Antony Chazapis
            size += s
764 04230536 Antony Chazapis
            for h in hashes:
765 04230536 Antony Chazapis
                self.store.map_delete(h)
766 4a1c29ea Antony Chazapis
            self.node.node_purge(node, until, CLUSTER_DELETED)
767 a9b3f29d Antony Chazapis
            try:
768 c915d3bf Antony Chazapis
                props = self._get_version(node)
769 a9b3f29d Antony Chazapis
            except NameError:
770 0f9d752c Antony Chazapis
                self.permissions.access_clear(path)
771 813e42e5 Antony Chazapis
            self._report_size_change(user, account, -size, {'action': 'object purge'})
772 a9b3f29d Antony Chazapis
            return
773 a9b3f29d Antony Chazapis
        
774 c915d3bf Antony Chazapis
        path, node = self._lookup_object(account, container, name)
775 33b4e4a6 Antony Chazapis
        src_version_id, dest_version_id = self._put_version_duplicate(user, node, size=0, type='', hash=None, checksum='', cluster=CLUSTER_DELETED)
776 813e42e5 Antony Chazapis
        del_size = self._apply_versioning(account, container, src_version_id)
777 813e42e5 Antony Chazapis
        if del_size:
778 813e42e5 Antony Chazapis
            self._report_size_change(user, account, -del_size, {'action': 'object delete'})
779 39ef6f41 Antony Chazapis
        self._report_object_change(user, account, path, details={'action': 'object delete'})
780 0f9d752c Antony Chazapis
        self.permissions.access_clear(path)
781 a9b3f29d Antony Chazapis
    
782 62f915a1 Antony Chazapis
    @backend_method
783 dff7b6f1 Sofia Papagiannaki
    def delete_object(self, user, account, container, name, until=None):
784 dff7b6f1 Sofia Papagiannaki
        """Delete/purge an object."""
785 dff7b6f1 Sofia Papagiannaki
        
786 dff7b6f1 Sofia Papagiannaki
        logger.debug("delete_object: %s %s %s %s", account, container, name, until)
787 dff7b6f1 Sofia Papagiannaki
        self._delete_object(user, account, container, name, until)
788 dff7b6f1 Sofia Papagiannaki
    
789 dff7b6f1 Sofia Papagiannaki
    @backend_method
790 62f915a1 Antony Chazapis
    def list_versions(self, user, account, container, name):
791 62f915a1 Antony Chazapis
        """Return a list of all (version, version_timestamp) tuples for an object."""
792 62f915a1 Antony Chazapis
        
793 62f915a1 Antony Chazapis
        logger.debug("list_versions: %s %s %s", account, container, name)
794 62f915a1 Antony Chazapis
        self._can_read(user, account, container, name)
795 60b8a083 Antony Chazapis
        path, node = self._lookup_object(account, container, name)
796 97d45f69 Antony Chazapis
        versions = self.node.node_get_versions(node)
797 97d45f69 Antony Chazapis
        return [[x[self.SERIAL], x[self.MTIME]] for x in versions if x[self.CLUSTER] != CLUSTER_DELETED]
798 a9b3f29d Antony Chazapis
    
799 bb4eafc6 Antony Chazapis
    @backend_method
800 37bee317 Antony Chazapis
    def get_uuid(self, user, uuid):
801 37bee317 Antony Chazapis
        """Return the (account, container, name) for the UUID given."""
802 af75e8a5 Antony Chazapis
        
803 37bee317 Antony Chazapis
        logger.debug("get_uuid: %s", uuid)
804 37bee317 Antony Chazapis
        info = self.node.latest_uuid(uuid)
805 37bee317 Antony Chazapis
        if info is None:
806 37bee317 Antony Chazapis
            raise NameError
807 37bee317 Antony Chazapis
        path, serial = info
808 37bee317 Antony Chazapis
        account, container, name = path.split('/', 2)
809 37bee317 Antony Chazapis
        self._can_read(user, account, container, name)
810 37bee317 Antony Chazapis
        return (account, container, name)
811 37bee317 Antony Chazapis
    
812 37bee317 Antony Chazapis
    @backend_method
813 bb4eafc6 Antony Chazapis
    def get_public(self, user, public):
814 bb4eafc6 Antony Chazapis
        """Return the (account, container, name) for the public id given."""
815 af75e8a5 Antony Chazapis
        
816 bb4eafc6 Antony Chazapis
        logger.debug("get_public: %s", public)
817 bb4eafc6 Antony Chazapis
        if public is None or public < ULTIMATE_ANSWER:
818 bb4eafc6 Antony Chazapis
            raise NameError
819 bb4eafc6 Antony Chazapis
        path = self.permissions.public_path(public - ULTIMATE_ANSWER)
820 37bee317 Antony Chazapis
        if path is None:
821 37bee317 Antony Chazapis
            raise NameError
822 bb4eafc6 Antony Chazapis
        account, container, name = path.split('/', 2)
823 bb4eafc6 Antony Chazapis
        self._can_read(user, account, container, name)
824 bb4eafc6 Antony Chazapis
        return (account, container, name)
825 bb4eafc6 Antony Chazapis
    
826 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
827 a9b3f29d Antony Chazapis
    def get_block(self, hash):
828 a9b3f29d Antony Chazapis
        """Return a block's data."""
829 a9b3f29d Antony Chazapis
        
830 a9b3f29d Antony Chazapis
        logger.debug("get_block: %s", hash)
831 7ca7bb08 Antony Chazapis
        block = self.store.block_get(binascii.unhexlify(hash))
832 7ca7bb08 Antony Chazapis
        if not block:
833 a9b3f29d Antony Chazapis
            raise NameError('Block does not exist')
834 7ca7bb08 Antony Chazapis
        return block
835 a9b3f29d Antony Chazapis
    
836 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
837 a9b3f29d Antony Chazapis
    def put_block(self, data):
838 60b8a083 Antony Chazapis
        """Store a block and return the hash."""
839 a9b3f29d Antony Chazapis
        
840 a9b3f29d Antony Chazapis
        logger.debug("put_block: %s", len(data))
841 7ca7bb08 Antony Chazapis
        return binascii.hexlify(self.store.block_put(data))
842 a9b3f29d Antony Chazapis
    
843 a9b3f29d Antony Chazapis
    @backend_method(autocommit=0)
844 a9b3f29d Antony Chazapis
    def update_block(self, hash, data, offset=0):
845 a9b3f29d Antony Chazapis
        """Update a known block and return the hash."""
846 a9b3f29d Antony Chazapis
        
847 a9b3f29d Antony Chazapis
        logger.debug("update_block: %s %s %s", hash, len(data), offset)
848 a9b3f29d Antony Chazapis
        if offset == 0 and len(data) == self.block_size:
849 a9b3f29d Antony Chazapis
            return self.put_block(data)
850 7ca7bb08 Antony Chazapis
        h = self.store.block_update(binascii.unhexlify(hash), offset, data)
851 a9b3f29d Antony Chazapis
        return binascii.hexlify(h)
852 a9b3f29d Antony Chazapis
    
853 44ad5860 Antony Chazapis
    # Path functions.
854 44ad5860 Antony Chazapis
    
855 37bee317 Antony Chazapis
    def _generate_uuid(self):
856 37bee317 Antony Chazapis
        return str(uuidlib.uuid4())
857 25ae8b75 Antony Chazapis
    
858 b9064632 Antony Chazapis
    def _put_object_node(self, path, parent, name):
859 c915d3bf Antony Chazapis
        path = '/'.join((path, name))
860 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
861 c915d3bf Antony Chazapis
        if node is None:
862 c915d3bf Antony Chazapis
            node = self.node.node_create(parent, path)
863 c915d3bf Antony Chazapis
        return path, node
864 c915d3bf Antony Chazapis
    
865 62f915a1 Antony Chazapis
    def _put_path(self, user, parent, path):
866 62f915a1 Antony Chazapis
        node = self.node.node_create(parent, path)
867 33b4e4a6 Antony Chazapis
        self.node.version_create(node, None, 0, '', None, user, self._generate_uuid(), '', CLUSTER_NORMAL)
868 62f915a1 Antony Chazapis
        return node
869 62f915a1 Antony Chazapis
    
870 44ad5860 Antony Chazapis
    def _lookup_account(self, account, create=True):
871 44ad5860 Antony Chazapis
        node = self.node.node_lookup(account)
872 44ad5860 Antony Chazapis
        if node is None and create:
873 2c5363a0 Antony Chazapis
            node = self._put_path(account, self.ROOTNODE, account) # User is account.
874 c915d3bf Antony Chazapis
        return account, node
875 44ad5860 Antony Chazapis
    
876 44ad5860 Antony Chazapis
    def _lookup_container(self, account, container):
877 c915d3bf Antony Chazapis
        path = '/'.join((account, container))
878 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
879 44ad5860 Antony Chazapis
        if node is None:
880 44ad5860 Antony Chazapis
            raise NameError('Container does not exist')
881 c915d3bf Antony Chazapis
        return path, node
882 44ad5860 Antony Chazapis
    
883 44ad5860 Antony Chazapis
    def _lookup_object(self, account, container, name):
884 c915d3bf Antony Chazapis
        path = '/'.join((account, container, name))
885 c915d3bf Antony Chazapis
        node = self.node.node_lookup(path)
886 44ad5860 Antony Chazapis
        if node is None:
887 44ad5860 Antony Chazapis
            raise NameError('Object does not exist')
888 c915d3bf Antony Chazapis
        return path, node
889 44ad5860 Antony Chazapis
    
890 44ad5860 Antony Chazapis
    def _get_properties(self, node, until=None):
891 44ad5860 Antony Chazapis
        """Return properties until the timestamp given."""
892 44ad5860 Antony Chazapis
        
893 44ad5860 Antony Chazapis
        before = until if until is not None else inf
894 44ad5860 Antony Chazapis
        props = self.node.version_lookup(node, before, CLUSTER_NORMAL)
895 44ad5860 Antony Chazapis
        if props is None and until is not None:
896 44ad5860 Antony Chazapis
            props = self.node.version_lookup(node, before, CLUSTER_HISTORY)
897 44ad5860 Antony Chazapis
        if props is None:
898 44ad5860 Antony Chazapis
            raise NameError('Path does not exist')
899 44ad5860 Antony Chazapis
        return props
900 44ad5860 Antony Chazapis
    
901 62f915a1 Antony Chazapis
    def _get_statistics(self, node, until=None):
902 62f915a1 Antony Chazapis
        """Return count, sum of size and latest timestamp of everything under node."""
903 c915d3bf Antony Chazapis
        
904 44ad5860 Antony Chazapis
        if until is None:
905 62f915a1 Antony Chazapis
            stats = self.node.statistics_get(node, CLUSTER_NORMAL)
906 44ad5860 Antony Chazapis
        else:
907 62f915a1 Antony Chazapis
            stats = self.node.statistics_latest(node, until, CLUSTER_DELETED)
908 62f915a1 Antony Chazapis
        if stats is None:
909 62f915a1 Antony Chazapis
            stats = (0, 0, 0)
910 62f915a1 Antony Chazapis
        return stats
911 44ad5860 Antony Chazapis
    
912 44ad5860 Antony Chazapis
    def _get_version(self, node, version=None):
913 44ad5860 Antony Chazapis
        if version is None:
914 44ad5860 Antony Chazapis
            props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
915 44ad5860 Antony Chazapis
            if props is None:
916 44ad5860 Antony Chazapis
                raise NameError('Object does not exist')
917 44ad5860 Antony Chazapis
        else:
918 07afd277 Antony Chazapis
            try:
919 07afd277 Antony Chazapis
                version = int(version)
920 07afd277 Antony Chazapis
            except ValueError:
921 07afd277 Antony Chazapis
                raise IndexError('Version does not exist')
922 44ad5860 Antony Chazapis
            props = self.node.version_get_properties(version)
923 2c5363a0 Antony Chazapis
            if props is None or props[self.CLUSTER] == CLUSTER_DELETED:
924 44ad5860 Antony Chazapis
                raise IndexError('Version does not exist')
925 44ad5860 Antony Chazapis
        return props
926 44ad5860 Antony Chazapis
    
927 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):
928 b9064632 Antony Chazapis
        """Create a new version of the node."""
929 c915d3bf Antony Chazapis
        
930 1730b3bf chazapis
        props = self.node.version_lookup(node if src_node is None else src_node, inf, CLUSTER_NORMAL)
931 b9064632 Antony Chazapis
        if props is not None:
932 b9064632 Antony Chazapis
            src_version_id = props[self.SERIAL]
933 b9064632 Antony Chazapis
            src_hash = props[self.HASH]
934 b9064632 Antony Chazapis
            src_size = props[self.SIZE]
935 66ce2ca5 Antony Chazapis
            src_type = props[self.TYPE]
936 33b4e4a6 Antony Chazapis
            src_checksum = props[self.CHECKSUM]
937 44ad5860 Antony Chazapis
        else:
938 b9064632 Antony Chazapis
            src_version_id = None
939 b9064632 Antony Chazapis
            src_hash = None
940 b9064632 Antony Chazapis
            src_size = 0
941 66ce2ca5 Antony Chazapis
            src_type = ''
942 33b4e4a6 Antony Chazapis
            src_checksum = ''
943 66ce2ca5 Antony Chazapis
        if size is None: # Set metadata.
944 66ce2ca5 Antony Chazapis
            hash = src_hash # This way hash can be set to None (account or container).
945 b9064632 Antony Chazapis
            size = src_size
946 66ce2ca5 Antony Chazapis
        if type is None:
947 66ce2ca5 Antony Chazapis
            type = src_type
948 33b4e4a6 Antony Chazapis
        if checksum is None:
949 33b4e4a6 Antony Chazapis
            checksum = src_checksum
950 1730b3bf chazapis
        uuid = self._generate_uuid() if (is_copy or src_version_id is None) else props[self.UUID]
951 1730b3bf chazapis
        
952 1730b3bf chazapis
        if src_node is None:
953 1730b3bf chazapis
            pre_version_id = src_version_id
954 1730b3bf chazapis
        else:
955 1730b3bf chazapis
            pre_version_id = None
956 1730b3bf chazapis
            props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
957 1730b3bf chazapis
            if props is not None:
958 1730b3bf chazapis
                pre_version_id = props[self.SERIAL]
959 1730b3bf chazapis
        if pre_version_id is not None:
960 1730b3bf chazapis
            self.node.version_recluster(pre_version_id, CLUSTER_HISTORY)
961 b9064632 Antony Chazapis
        
962 33b4e4a6 Antony Chazapis
        dest_version_id, mtime = self.node.version_create(node, hash, size, type, src_version_id, user, uuid, checksum, cluster)
963 1730b3bf chazapis
        return pre_version_id, dest_version_id
964 44ad5860 Antony Chazapis
    
965 4819d34f Antony Chazapis
    def _put_metadata_duplicate(self, src_version_id, dest_version_id, domain, meta, replace=False):
966 4819d34f Antony Chazapis
        if src_version_id is not None:
967 4819d34f Antony Chazapis
            self.node.attribute_copy(src_version_id, dest_version_id)
968 4819d34f Antony Chazapis
        if not replace:
969 4819d34f Antony Chazapis
            self.node.attribute_del(dest_version_id, domain, (k for k, v in meta.iteritems() if v == ''))
970 4819d34f Antony Chazapis
            self.node.attribute_set(dest_version_id, domain, ((k, v) for k, v in meta.iteritems() if v != ''))
971 4819d34f Antony Chazapis
        else:
972 4819d34f Antony Chazapis
            self.node.attribute_del(dest_version_id, domain)
973 4819d34f Antony Chazapis
            self.node.attribute_set(dest_version_id, domain, ((k, v) for k, v in meta.iteritems()))
974 4819d34f Antony Chazapis
    
975 4819d34f Antony Chazapis
    def _put_metadata(self, user, node, domain, meta, replace=False):
976 44ad5860 Antony Chazapis
        """Create a new version and store metadata."""
977 44ad5860 Antony Chazapis
        
978 b9064632 Antony Chazapis
        src_version_id, dest_version_id = self._put_version_duplicate(user, node)
979 4819d34f Antony Chazapis
        self._put_metadata_duplicate(src_version_id, dest_version_id, domain, meta, replace)
980 5cc484e1 Antony Chazapis
        return src_version_id, dest_version_id
981 44ad5860 Antony Chazapis
    
982 60b8a083 Antony Chazapis
    def _list_limits(self, listing, marker, limit):
983 60b8a083 Antony Chazapis
        start = 0
984 60b8a083 Antony Chazapis
        if marker:
985 60b8a083 Antony Chazapis
            try:
986 60b8a083 Antony Chazapis
                start = listing.index(marker) + 1
987 60b8a083 Antony Chazapis
            except ValueError:
988 60b8a083 Antony Chazapis
                pass
989 60b8a083 Antony Chazapis
        if not limit or limit > 10000:
990 60b8a083 Antony Chazapis
            limit = 10000
991 60b8a083 Antony Chazapis
        return start, limit
992 60b8a083 Antony Chazapis
    
993 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):
994 60b8a083 Antony Chazapis
        cont_prefix = path + '/'
995 60b8a083 Antony Chazapis
        prefix = cont_prefix + prefix
996 60b8a083 Antony Chazapis
        start = cont_prefix + marker if marker else None
997 60b8a083 Antony Chazapis
        before = until if until is not None else inf
998 4819d34f Antony Chazapis
        filterq = keys if domain else []
999 7ff57991 Antony Chazapis
        sizeq = size_range
1000 60b8a083 Antony Chazapis
        
1001 371d907a Antony Chazapis
        objects, prefixes = self.node.latest_version_list(parent, prefix, delimiter, start, limit, before, CLUSTER_DELETED, allowed, domain, filterq, sizeq, all_props)
1002 60b8a083 Antony Chazapis
        objects.extend([(p, None) for p in prefixes] if virtual else [])
1003 43be9afd Sofia Papagiannaki
        objects.sort(key=lambda x: x[0])
1004 371d907a Antony Chazapis
        objects = [(x[0][len(cont_prefix):],) + x[1:] for x in objects]
1005 60b8a083 Antony Chazapis
        
1006 60b8a083 Antony Chazapis
        start, limit = self._list_limits([x[0] for x in objects], marker, limit)
1007 60b8a083 Antony Chazapis
        return objects[start:start + limit]
1008 60b8a083 Antony Chazapis
    
1009 813e42e5 Antony Chazapis
    # Reporting functions.
1010 813e42e5 Antony Chazapis
    
1011 813e42e5 Antony Chazapis
    def _report_size_change(self, user, account, size, details={}):
1012 813e42e5 Antony Chazapis
        logger.debug("_report_size_change: %s %s %s %s", user, account, size, details)
1013 813e42e5 Antony Chazapis
        account_node = self._lookup_account(account, True)[1]
1014 813e42e5 Antony Chazapis
        total = self._get_statistics(account_node)[1]
1015 813e42e5 Antony Chazapis
        details.update({'user': user, 'total': total})
1016 73673127 Antony Chazapis
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('resource.diskspace',), account, QUEUE_INSTANCE_ID, 'diskspace', float(size), details))
1017 39ef6f41 Antony Chazapis
    
1018 39ef6f41 Antony Chazapis
    def _report_object_change(self, user, account, path, details={}):
1019 39ef6f41 Antony Chazapis
        logger.debug("_report_object_change: %s %s %s %s", user, account, path, details)
1020 39ef6f41 Antony Chazapis
        details.update({'user': user})
1021 73673127 Antony Chazapis
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('object',), account, QUEUE_INSTANCE_ID, 'object', path, details))
1022 813e42e5 Antony Chazapis
    
1023 a74ba506 Sofia Papagiannaki
    def _report_sharing_change(self, user, account, path, details={}):
1024 a74ba506 Sofia Papagiannaki
        logger.debug("_report_permissions_change: %s %s %s %s", user, account, path, details)
1025 a74ba506 Sofia Papagiannaki
        details.update({'user': user})
1026 a74ba506 Sofia Papagiannaki
        self.messages.append((QUEUE_MESSAGE_KEY_PREFIX % ('sharing',), account, QUEUE_INSTANCE_ID, 'sharing', path, details))
1027 a74ba506 Sofia Papagiannaki
    
1028 60b8a083 Antony Chazapis
    # Policy functions.
1029 60b8a083 Antony Chazapis
    
1030 60b8a083 Antony Chazapis
    def _check_policy(self, policy):
1031 60b8a083 Antony Chazapis
        for k in policy.keys():
1032 60b8a083 Antony Chazapis
            if policy[k] == '':
1033 60b8a083 Antony Chazapis
                policy[k] = self.default_policy.get(k)
1034 60b8a083 Antony Chazapis
        for k, v in policy.iteritems():
1035 60b8a083 Antony Chazapis
            if k == 'quota':
1036 60b8a083 Antony Chazapis
                q = int(v) # May raise ValueError.
1037 60b8a083 Antony Chazapis
                if q < 0:
1038 60b8a083 Antony Chazapis
                    raise ValueError
1039 60b8a083 Antony Chazapis
            elif k == 'versioning':
1040 5cc484e1 Antony Chazapis
                if v not in ['auto', 'none']:
1041 60b8a083 Antony Chazapis
                    raise ValueError
1042 60b8a083 Antony Chazapis
            else:
1043 60b8a083 Antony Chazapis
                raise ValueError
1044 60b8a083 Antony Chazapis
    
1045 b2832c6a Antony Chazapis
    def _put_policy(self, node, policy, replace):
1046 b2832c6a Antony Chazapis
        if replace:
1047 b2832c6a Antony Chazapis
            for k, v in self.default_policy.iteritems():
1048 b2832c6a Antony Chazapis
                if k not in policy:
1049 b2832c6a Antony Chazapis
                    policy[k] = v
1050 b2832c6a Antony Chazapis
        self.node.policy_set(node, policy)
1051 b2832c6a Antony Chazapis
    
1052 b9064632 Antony Chazapis
    def _get_policy(self, node):
1053 b9064632 Antony Chazapis
        policy = self.default_policy.copy()
1054 b9064632 Antony Chazapis
        policy.update(self.node.policy_get(node))
1055 b9064632 Antony Chazapis
        return policy
1056 b9064632 Antony Chazapis
    
1057 5cc484e1 Antony Chazapis
    def _apply_versioning(self, account, container, version_id):
1058 813e42e5 Antony Chazapis
        """Delete the provided version if such is the policy.
1059 813e42e5 Antony Chazapis
           Return size of object removed.
1060 813e42e5 Antony Chazapis
        """
1061 813e42e5 Antony Chazapis
        
1062 5cc484e1 Antony Chazapis
        if version_id is None:
1063 813e42e5 Antony Chazapis
            return 0
1064 5cc484e1 Antony Chazapis
        path, node = self._lookup_container(account, container)
1065 5cc484e1 Antony Chazapis
        versioning = self._get_policy(node)['versioning']
1066 5cc484e1 Antony Chazapis
        if versioning != 'auto':
1067 813e42e5 Antony Chazapis
            hash, size = self.node.version_remove(version_id)
1068 5161c672 Antony Chazapis
            self.store.map_delete(hash)
1069 813e42e5 Antony Chazapis
            return size
1070 813e42e5 Antony Chazapis
        return 0
1071 5cc484e1 Antony Chazapis
    
1072 a9b3f29d Antony Chazapis
    # Access control functions.
1073 a9b3f29d Antony Chazapis
    
1074 a9b3f29d Antony Chazapis
    def _check_groups(self, groups):
1075 0f9d752c Antony Chazapis
        # raise ValueError('Bad characters in groups')
1076 a9b3f29d Antony Chazapis
        pass
1077 a9b3f29d Antony Chazapis
    
1078 a9b3f29d Antony Chazapis
    def _check_permissions(self, path, permissions):
1079 0f9d752c Antony Chazapis
        # raise ValueError('Bad characters in permissions')
1080 5e068361 Antony Chazapis
        pass
1081 5e068361 Antony Chazapis
    
1082 92da0e5a Antony Chazapis
    def _get_formatted_paths(self, paths):
1083 92da0e5a Antony Chazapis
        formatted = []
1084 92da0e5a Antony Chazapis
        for p in paths:
1085 92da0e5a Antony Chazapis
            node = self.node.node_lookup(p)
1086 92da0e5a Antony Chazapis
            if node is not None:
1087 92da0e5a Antony Chazapis
                props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1088 92da0e5a Antony Chazapis
            if props is not None:
1089 692485cc Antony Chazapis
                if props[self.TYPE].split(';', 1)[0].strip() in ('application/directory', 'application/folder'):
1090 d57eaad4 Antony Chazapis
                    formatted.append((p.rstrip('/') + '/', self.MATCH_PREFIX))
1091 d57eaad4 Antony Chazapis
                formatted.append((p, self.MATCH_EXACT))
1092 92da0e5a Antony Chazapis
        return formatted
1093 92da0e5a Antony Chazapis
    
1094 5e068361 Antony Chazapis
    def _get_permissions_path(self, account, container, name):
1095 5e068361 Antony Chazapis
        path = '/'.join((account, container, name))
1096 5e068361 Antony Chazapis
        permission_paths = self.permissions.access_inherit(path)
1097 5e068361 Antony Chazapis
        permission_paths.sort()
1098 5e068361 Antony Chazapis
        permission_paths.reverse()
1099 5e068361 Antony Chazapis
        for p in permission_paths:
1100 5e068361 Antony Chazapis
            if p == path:
1101 5e068361 Antony Chazapis
                return p
1102 5e068361 Antony Chazapis
            else:
1103 71dbc012 Antony Chazapis
                if p.count('/') < 2:
1104 71dbc012 Antony Chazapis
                    continue
1105 92da0e5a Antony Chazapis
                node = self.node.node_lookup(p)
1106 92da0e5a Antony Chazapis
                if node is not None:
1107 92da0e5a Antony Chazapis
                    props = self.node.version_lookup(node, inf, CLUSTER_NORMAL)
1108 92da0e5a Antony Chazapis
                if props is not None:
1109 692485cc Antony Chazapis
                    if props[self.TYPE].split(';', 1)[0].strip() in ('application/directory', 'application/folder'):
1110 5e068361 Antony Chazapis
                        return p
1111 5e068361 Antony Chazapis
        return None
1112 a9b3f29d Antony Chazapis
    
1113 6f4bce7b Antony Chazapis
    def _can_read(self, user, account, container, name):
1114 a9b3f29d Antony Chazapis
        if user == account:
1115 a9b3f29d Antony Chazapis
            return True
1116 a9b3f29d Antony Chazapis
        path = '/'.join((account, container, name))
1117 aeb2b64f Antony Chazapis
        if self.permissions.public_get(path) is not None:
1118 71dbc012 Antony Chazapis
            return True
1119 5e068361 Antony Chazapis
        path = self._get_permissions_path(account, container, name)
1120 71dbc012 Antony Chazapis
        if not path:
1121 71dbc012 Antony Chazapis
            raise NotAllowedError
1122 2c5363a0 Antony Chazapis
        if not self.permissions.access_check(path, self.READ, user) and not self.permissions.access_check(path, self.WRITE, user):
1123 a9b3f29d Antony Chazapis
            raise NotAllowedError
1124 a9b3f29d Antony Chazapis
    
1125 a9b3f29d Antony Chazapis
    def _can_write(self, user, account, container, name):
1126 6f4bce7b Antony Chazapis
        if user == account:
1127 6f4bce7b Antony Chazapis
            return True
1128 6f4bce7b Antony Chazapis
        path = '/'.join((account, container, name))
1129 71dbc012 Antony Chazapis
        path = self._get_permissions_path(account, container, name)
1130 71dbc012 Antony Chazapis
        if not path:
1131 71dbc012 Antony Chazapis
            raise NotAllowedError
1132 2c5363a0 Antony Chazapis
        if not self.permissions.access_check(path, self.WRITE, user):
1133 a9b3f29d Antony Chazapis
            raise NotAllowedError
1134 a9b3f29d Antony Chazapis
    
1135 a9b3f29d Antony Chazapis
    def _allowed_accounts(self, user):
1136 a9b3f29d Antony Chazapis
        allow = set()
1137 0f9d752c Antony Chazapis
        for path in self.permissions.access_list_paths(user):
1138 a9b3f29d Antony Chazapis
            allow.add(path.split('/', 1)[0])
1139 a9b3f29d Antony Chazapis
        return sorted(allow)
1140 a9b3f29d Antony Chazapis
    
1141 a9b3f29d Antony Chazapis
    def _allowed_containers(self, user, account):
1142 a9b3f29d Antony Chazapis
        allow = set()
1143 0f9d752c Antony Chazapis
        for path in self.permissions.access_list_paths(user, account):
1144 a9b3f29d Antony Chazapis
            allow.add(path.split('/', 2)[1])
1145 a9b3f29d Antony Chazapis
        return sorted(allow)