Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / pithos / backends / modular.py @ 4a669c71

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