Statistics
| Branch: | Tag: | Revision:

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

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