Statistics
| Branch: | Tag: | Revision:

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

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