Statistics
| Branch: | Tag: | Revision:

root / pithos / backends / simple.py @ b18ef3ad

History | View | Annotate | Download (40.9 kB)

1 5635f9ef Antony Chazapis
# Copyright 2011 GRNET S.A. All rights reserved.
2 5635f9ef Antony Chazapis
# 
3 5635f9ef Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 5635f9ef Antony Chazapis
# without modification, are permitted provided that the following
5 5635f9ef Antony Chazapis
# conditions are met:
6 5635f9ef Antony Chazapis
# 
7 5635f9ef Antony Chazapis
#   1. Redistributions of source code must retain the above
8 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
9 5635f9ef Antony Chazapis
#      disclaimer.
10 5635f9ef Antony Chazapis
# 
11 5635f9ef Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
13 5635f9ef Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 5635f9ef Antony Chazapis
#      provided with the distribution.
15 5635f9ef Antony Chazapis
# 
16 5635f9ef Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5635f9ef Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5635f9ef Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5635f9ef Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5635f9ef Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5635f9ef Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5635f9ef Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5635f9ef Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5635f9ef Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5635f9ef Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5635f9ef Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5635f9ef Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 5635f9ef Antony Chazapis
# 
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
import os
35 b956618e Antony Chazapis
import time
36 b956618e Antony Chazapis
import sqlite3
37 b956618e Antony Chazapis
import logging
38 b956618e Antony Chazapis
import hashlib
39 a156c8b3 Antony Chazapis
import binascii
40 b956618e Antony Chazapis
41 cca6c617 Antony Chazapis
from base import NotAllowedError, BaseBackend
42 a156c8b3 Antony Chazapis
from pithos.lib.hashfiler import Mapper, Blocker
43 b956618e Antony Chazapis
44 b956618e Antony Chazapis
45 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
46 b956618e Antony Chazapis
47 30b46c1d Antony Chazapis
def backend_method(func=None, autocommit=1):
48 30b46c1d Antony Chazapis
    if func is None:
49 30b46c1d Antony Chazapis
        def fn(func):
50 30b46c1d Antony Chazapis
            return backend_method(func, autocommit)
51 30b46c1d Antony Chazapis
        return fn
52 30b46c1d Antony Chazapis
53 30b46c1d Antony Chazapis
    if not autocommit:
54 30b46c1d Antony Chazapis
        return func
55 30b46c1d Antony Chazapis
    def fn(self, *args, **kw):
56 30b46c1d Antony Chazapis
        self.con.execute('begin deferred')
57 30b46c1d Antony Chazapis
        try:
58 30b46c1d Antony Chazapis
            ret = func(self, *args, **kw)
59 30b46c1d Antony Chazapis
            self.con.commit()
60 30b46c1d Antony Chazapis
            return ret
61 30b46c1d Antony Chazapis
        except:
62 30b46c1d Antony Chazapis
            self.con.rollback()
63 30b46c1d Antony Chazapis
            raise
64 30b46c1d Antony Chazapis
    return fn
65 30b46c1d Antony Chazapis
66 b956618e Antony Chazapis
67 b956618e Antony Chazapis
class SimpleBackend(BaseBackend):
68 22dab079 Antony Chazapis
    """A simple backend.
69 22dab079 Antony Chazapis
    
70 22dab079 Antony Chazapis
    Uses SQLite for storage.
71 22dab079 Antony Chazapis
    """
72 22dab079 Antony Chazapis
    
73 22dab079 Antony Chazapis
    def __init__(self, db):
74 a156c8b3 Antony Chazapis
        self.hash_algorithm = 'sha256'
75 a156c8b3 Antony Chazapis
        self.block_size = 4 * 1024 * 1024 # 4MB
76 b956618e Antony Chazapis
        
77 3ab38c43 Antony Chazapis
        self.default_policy = {'quota': 0, 'versioning': 'auto'}
78 3ab38c43 Antony Chazapis
        
79 22dab079 Antony Chazapis
        basepath = os.path.split(db)[0]
80 22dab079 Antony Chazapis
        if basepath and not os.path.exists(basepath):
81 b956618e Antony Chazapis
            os.makedirs(basepath)
82 a156c8b3 Antony Chazapis
        if not os.path.isdir(basepath):
83 a156c8b3 Antony Chazapis
            raise RuntimeError("Cannot open database at '%s'" % (db,))
84 22dab079 Antony Chazapis
        
85 a156c8b3 Antony Chazapis
        self.con = sqlite3.connect(basepath + '/db', check_same_thread=False)
86 84846143 Antony Chazapis
        
87 84846143 Antony Chazapis
        sql = '''pragma foreign_keys = on'''
88 84846143 Antony Chazapis
        self.con.execute(sql)
89 84846143 Antony Chazapis
        
90 58a6c894 Antony Chazapis
        sql = '''create table if not exists versions (
91 58a6c894 Antony Chazapis
                    version_id integer primary key,
92 58a6c894 Antony Chazapis
                    name text,
93 104626e3 Antony Chazapis
                    user text,
94 84846143 Antony Chazapis
                    tstamp integer not null,
95 58a6c894 Antony Chazapis
                    size integer default 0,
96 58a6c894 Antony Chazapis
                    hide integer default 0)'''
97 b956618e Antony Chazapis
        self.con.execute(sql)
98 b956618e Antony Chazapis
        sql = '''create table if not exists metadata (
99 84846143 Antony Chazapis
                    version_id integer,
100 84846143 Antony Chazapis
                    key text,
101 84846143 Antony Chazapis
                    value text,
102 84846143 Antony Chazapis
                    primary key (version_id, key)
103 84846143 Antony Chazapis
                    foreign key (version_id) references versions(version_id)
104 84846143 Antony Chazapis
                    on delete cascade)'''
105 84846143 Antony Chazapis
        self.con.execute(sql)
106 84846143 Antony Chazapis
        sql = '''create table if not exists policy (
107 84846143 Antony Chazapis
                    name text, key text, value text, primary key (name, key))'''
108 b956618e Antony Chazapis
        self.con.execute(sql)
109 84846143 Antony Chazapis
        
110 b0a2d1a6 Antony Chazapis
        # Access control tables.
111 02c0c3fa Antony Chazapis
        sql = '''create table if not exists groups (
112 b0a2d1a6 Antony Chazapis
                    account text, gname text, user text)'''
113 02c0c3fa Antony Chazapis
        self.con.execute(sql)
114 3436eeb0 Antony Chazapis
        sql = '''create table if not exists permissions (
115 b0a2d1a6 Antony Chazapis
                    name text, op text, user text)'''
116 3436eeb0 Antony Chazapis
        self.con.execute(sql)
117 e0f916bb Antony Chazapis
        sql = '''create table if not exists public (
118 e0f916bb Antony Chazapis
                    name text, primary key (name))'''
119 02c0c3fa Antony Chazapis
        self.con.execute(sql)
120 b0a2d1a6 Antony Chazapis
        
121 b956618e Antony Chazapis
        self.con.commit()
122 a156c8b3 Antony Chazapis
        
123 a156c8b3 Antony Chazapis
        params = {'blocksize': self.block_size,
124 a156c8b3 Antony Chazapis
                  'blockpath': basepath + '/blocks',
125 a156c8b3 Antony Chazapis
                  'hashtype': self.hash_algorithm}
126 a156c8b3 Antony Chazapis
        self.blocker = Blocker(**params)
127 a156c8b3 Antony Chazapis
        
128 a156c8b3 Antony Chazapis
        params = {'mappath': basepath + '/maps',
129 a156c8b3 Antony Chazapis
                  'namelen': self.blocker.hashlen}
130 a156c8b3 Antony Chazapis
        self.mapper = Mapper(**params)
131 b956618e Antony Chazapis
    
132 30b46c1d Antony Chazapis
    @backend_method
133 f6c97079 Antony Chazapis
    def list_accounts(self, user, marker=None, limit=10000):
134 f6c97079 Antony Chazapis
        """Return a list of accounts the user can access."""
135 f6c97079 Antony Chazapis
        
136 f6c97079 Antony Chazapis
        allowed = self._allowed_accounts(user)
137 f6c97079 Antony Chazapis
        start, limit = self._list_limits(allowed, marker, limit)
138 f6c97079 Antony Chazapis
        return allowed[start:start + limit]
139 f6c97079 Antony Chazapis
    
140 f6c97079 Antony Chazapis
    @backend_method
141 83dd59c5 Antony Chazapis
    def get_account_meta(self, user, account, until=None):
142 b956618e Antony Chazapis
        """Return a dictionary with the account metadata."""
143 b956618e Antony Chazapis
        
144 58a6c894 Antony Chazapis
        logger.debug("get_account_meta: %s %s", account, until)
145 cca6c617 Antony Chazapis
        if user != account:
146 f6c97079 Antony Chazapis
            if until or account not in self._allowed_accounts(user):
147 f6c97079 Antony Chazapis
                raise NotAllowedError
148 58a6c894 Antony Chazapis
        try:
149 58a6c894 Antony Chazapis
            version_id, mtime = self._get_accountinfo(account, until)
150 58a6c894 Antony Chazapis
        except NameError:
151 58a6c894 Antony Chazapis
            version_id = None
152 60be837c Antony Chazapis
            mtime = 0
153 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(account, until)
154 31a1c80d Antony Chazapis
        if mtime > tstamp:
155 31a1c80d Antony Chazapis
            tstamp = mtime
156 58a6c894 Antony Chazapis
        if until is None:
157 58a6c894 Antony Chazapis
            modified = tstamp
158 58a6c894 Antony Chazapis
        else:
159 58a6c894 Antony Chazapis
            modified = self._get_pathstats(account)[2] # Overall last modification
160 31a1c80d Antony Chazapis
            if mtime > modified:
161 31a1c80d Antony Chazapis
                modified = mtime
162 22dab079 Antony Chazapis
        
163 22dab079 Antony Chazapis
        # Proper count.
164 58a6c894 Antony Chazapis
        sql = 'select count(name) from (%s) where name glob ? and not name glob ?'
165 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
166 22dab079 Antony Chazapis
        c = self.con.execute(sql, (account + '/*', account + '/*/*'))
167 22dab079 Antony Chazapis
        row = c.fetchone()
168 22dab079 Antony Chazapis
        count = row[0]
169 22dab079 Antony Chazapis
        
170 f6c97079 Antony Chazapis
        if user != account:
171 f6c97079 Antony Chazapis
            meta = {'name': account}
172 f6c97079 Antony Chazapis
        else:
173 f6c97079 Antony Chazapis
            meta = self._get_metadata(account, version_id)
174 f6c97079 Antony Chazapis
            meta.update({'name': account, 'count': count, 'bytes': bytes})
175 f6c97079 Antony Chazapis
            if until is not None:
176 f6c97079 Antony Chazapis
                meta.update({'until_timestamp': tstamp})
177 58a6c894 Antony Chazapis
        if modified:
178 58a6c894 Antony Chazapis
            meta.update({'modified': modified})
179 b956618e Antony Chazapis
        return meta
180 b956618e Antony Chazapis
    
181 30b46c1d Antony Chazapis
    @backend_method
182 83dd59c5 Antony Chazapis
    def update_account_meta(self, user, account, meta, replace=False):
183 b956618e Antony Chazapis
        """Update the metadata associated with the account."""
184 b956618e Antony Chazapis
        
185 22dab079 Antony Chazapis
        logger.debug("update_account_meta: %s %s %s", account, meta, replace)
186 cca6c617 Antony Chazapis
        if user != account:
187 cca6c617 Antony Chazapis
            raise NotAllowedError
188 a156c8b3 Antony Chazapis
        self._put_metadata(user, account, meta, replace, False)
189 58a6c894 Antony Chazapis
    
190 30b46c1d Antony Chazapis
    @backend_method
191 02c0c3fa Antony Chazapis
    def get_account_groups(self, user, account):
192 02c0c3fa Antony Chazapis
        """Return a dictionary with the user groups defined for this account."""
193 58a6c894 Antony Chazapis
        
194 02c0c3fa Antony Chazapis
        logger.debug("get_account_groups: %s", account)
195 cca6c617 Antony Chazapis
        if user != account:
196 f6c97079 Antony Chazapis
            if account not in self._allowed_accounts(user):
197 f6c97079 Antony Chazapis
                raise NotAllowedError
198 f6c97079 Antony Chazapis
            return {}
199 02c0c3fa Antony Chazapis
        return self._get_groups(account)
200 b956618e Antony Chazapis
    
201 30b46c1d Antony Chazapis
    @backend_method
202 02c0c3fa Antony Chazapis
    def update_account_groups(self, user, account, groups, replace=False):
203 02c0c3fa Antony Chazapis
        """Update the groups associated with the account."""
204 b956618e Antony Chazapis
        
205 02c0c3fa Antony Chazapis
        logger.debug("update_account_groups: %s %s %s", account, groups, replace)
206 cca6c617 Antony Chazapis
        if user != account:
207 cca6c617 Antony Chazapis
            raise NotAllowedError
208 b0a2d1a6 Antony Chazapis
        self._check_groups(groups)
209 b0a2d1a6 Antony Chazapis
        self._put_groups(account, groups, replace)
210 b956618e Antony Chazapis
    
211 30b46c1d Antony Chazapis
    @backend_method
212 84846143 Antony Chazapis
    def put_account(self, user, account):
213 84846143 Antony Chazapis
        """Create a new account with the given name."""
214 84846143 Antony Chazapis
        
215 84846143 Antony Chazapis
        logger.debug("put_account: %s", account)
216 84846143 Antony Chazapis
        if user != account:
217 84846143 Antony Chazapis
            raise NotAllowedError
218 84846143 Antony Chazapis
        try:
219 84846143 Antony Chazapis
            version_id, mtime = self._get_accountinfo(account)
220 84846143 Antony Chazapis
        except NameError:
221 84846143 Antony Chazapis
            pass
222 84846143 Antony Chazapis
        else:
223 84846143 Antony Chazapis
            raise NameError('Account already exists')
224 84846143 Antony Chazapis
        version_id = self._put_version(account, user)
225 84846143 Antony Chazapis
    
226 30b46c1d Antony Chazapis
    @backend_method
227 02c0c3fa Antony Chazapis
    def delete_account(self, user, account):
228 02c0c3fa Antony Chazapis
        """Delete the account with the given name."""
229 b956618e Antony Chazapis
        
230 02c0c3fa Antony Chazapis
        logger.debug("delete_account: %s", account)
231 cca6c617 Antony Chazapis
        if user != account:
232 cca6c617 Antony Chazapis
            raise NotAllowedError
233 84846143 Antony Chazapis
        count = self._get_pathstats(account)[0]
234 22dab079 Antony Chazapis
        if count > 0:
235 02c0c3fa Antony Chazapis
            raise IndexError('Account is not empty')
236 84846143 Antony Chazapis
        sql = 'delete from versions where name = ?'
237 84846143 Antony Chazapis
        self.con.execute(sql, (account,))
238 b0a2d1a6 Antony Chazapis
        self._del_groups(account)
239 02c0c3fa Antony Chazapis
    
240 30b46c1d Antony Chazapis
    @backend_method
241 b18ef3ad Antony Chazapis
    def list_containers(self, user, account, marker=None, limit=10000, shared=False, until=None):
242 02c0c3fa Antony Chazapis
        """Return a list of containers existing under an account."""
243 02c0c3fa Antony Chazapis
        
244 02c0c3fa Antony Chazapis
        logger.debug("list_containers: %s %s %s %s", account, marker, limit, until)
245 02c0c3fa Antony Chazapis
        if user != account:
246 f6c97079 Antony Chazapis
            if until or account not in self._allowed_accounts(user):
247 17629fea Antony Chazapis
                raise NotAllowedError
248 f6c97079 Antony Chazapis
            allowed = self._allowed_containers(user, account)
249 f6c97079 Antony Chazapis
            start, limit = self._list_limits(allowed, marker, limit)
250 f6c97079 Antony Chazapis
            return allowed[start:start + limit]
251 b18ef3ad Antony Chazapis
        else:
252 b18ef3ad Antony Chazapis
            if shared:
253 b18ef3ad Antony Chazapis
                allowed = [x.split('/', 2)[1] for x in self._shared_paths(account)]
254 b18ef3ad Antony Chazapis
                start, limit = self._list_limits(allowed, marker, limit)
255 b18ef3ad Antony Chazapis
                return allowed[start:start + limit]
256 f6c97079 Antony Chazapis
        return [x[0] for x in self._list_objects(account, '', '/', marker, limit, False, [], until)]
257 b956618e Antony Chazapis
    
258 30b46c1d Antony Chazapis
    @backend_method
259 83dd59c5 Antony Chazapis
    def get_container_meta(self, user, account, container, until=None):
260 b956618e Antony Chazapis
        """Return a dictionary with the container metadata."""
261 b956618e Antony Chazapis
        
262 58a6c894 Antony Chazapis
        logger.debug("get_container_meta: %s %s %s", account, container, until)
263 cca6c617 Antony Chazapis
        if user != account:
264 f6c97079 Antony Chazapis
            if until or container not in self._allowed_containers(user, account):
265 f6c97079 Antony Chazapis
                raise NotAllowedError
266 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
267 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(path, until)
268 31a1c80d Antony Chazapis
        if mtime > tstamp:
269 31a1c80d Antony Chazapis
            tstamp = mtime
270 58a6c894 Antony Chazapis
        if until is None:
271 58a6c894 Antony Chazapis
            modified = tstamp
272 58a6c894 Antony Chazapis
        else:
273 31a1c80d Antony Chazapis
            modified = self._get_pathstats(path)[2] # Overall last modification
274 31a1c80d Antony Chazapis
            if mtime > modified:
275 31a1c80d Antony Chazapis
                modified = mtime
276 58a6c894 Antony Chazapis
        
277 f6c97079 Antony Chazapis
        if user != account:
278 f6c97079 Antony Chazapis
            meta = {'name': container, 'modified': modified}
279 f6c97079 Antony Chazapis
        else:
280 f6c97079 Antony Chazapis
            meta = self._get_metadata(path, version_id)
281 f6c97079 Antony Chazapis
            meta.update({'name': container, 'count': count, 'bytes': bytes, 'modified': modified})
282 f6c97079 Antony Chazapis
            if until is not None:
283 f6c97079 Antony Chazapis
                meta.update({'until_timestamp': tstamp})
284 b956618e Antony Chazapis
        return meta
285 b956618e Antony Chazapis
    
286 30b46c1d Antony Chazapis
    @backend_method
287 83dd59c5 Antony Chazapis
    def update_container_meta(self, user, account, container, meta, replace=False):
288 b956618e Antony Chazapis
        """Update the metadata associated with the container."""
289 b956618e Antony Chazapis
        
290 58a6c894 Antony Chazapis
        logger.debug("update_container_meta: %s %s %s %s", account, container, meta, replace)
291 cca6c617 Antony Chazapis
        if user != account:
292 cca6c617 Antony Chazapis
            raise NotAllowedError
293 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container)
294 a156c8b3 Antony Chazapis
        self._put_metadata(user, path, meta, replace, False)
295 b956618e Antony Chazapis
    
296 30b46c1d Antony Chazapis
    @backend_method
297 02c0c3fa Antony Chazapis
    def get_container_policy(self, user, account, container):
298 02c0c3fa Antony Chazapis
        """Return a dictionary with the container policy."""
299 02c0c3fa Antony Chazapis
        
300 02c0c3fa Antony Chazapis
        logger.debug("get_container_policy: %s %s", account, container)
301 3ab38c43 Antony Chazapis
        if user != account:
302 f6c97079 Antony Chazapis
            if container not in self._allowed_containers(user, account):
303 f6c97079 Antony Chazapis
                raise NotAllowedError
304 f6c97079 Antony Chazapis
            return {}
305 3ab38c43 Antony Chazapis
        path = self._get_containerinfo(account, container)[0]
306 3ab38c43 Antony Chazapis
        return self._get_policy(path)
307 02c0c3fa Antony Chazapis
    
308 30b46c1d Antony Chazapis
    @backend_method
309 02c0c3fa Antony Chazapis
    def update_container_policy(self, user, account, container, policy, replace=False):
310 02c0c3fa Antony Chazapis
        """Update the policy associated with the account."""
311 02c0c3fa Antony Chazapis
        
312 02c0c3fa Antony Chazapis
        logger.debug("update_container_policy: %s %s %s %s", account, container, policy, replace)
313 3ab38c43 Antony Chazapis
        if user != account:
314 3ab38c43 Antony Chazapis
            raise NotAllowedError
315 3ab38c43 Antony Chazapis
        path = self._get_containerinfo(account, container)[0]
316 3ab38c43 Antony Chazapis
        self._check_policy(policy)
317 3ab38c43 Antony Chazapis
        if replace:
318 3ab38c43 Antony Chazapis
            for k, v in self.default_policy.iteritems():
319 3ab38c43 Antony Chazapis
                if k not in policy:
320 3ab38c43 Antony Chazapis
                    policy[k] = v
321 3ab38c43 Antony Chazapis
        for k, v in policy.iteritems():
322 3ab38c43 Antony Chazapis
            sql = 'insert or replace into policy (name, key, value) values (?, ?, ?)'
323 3ab38c43 Antony Chazapis
            self.con.execute(sql, (path, k, v))
324 02c0c3fa Antony Chazapis
    
325 30b46c1d Antony Chazapis
    @backend_method
326 02c0c3fa Antony Chazapis
    def put_container(self, user, account, container, policy=None):
327 02c0c3fa Antony Chazapis
        """Create a new container with the given name."""
328 02c0c3fa Antony Chazapis
        
329 02c0c3fa Antony Chazapis
        logger.debug("put_container: %s %s %s", account, container, policy)
330 02c0c3fa Antony Chazapis
        if user != account:
331 02c0c3fa Antony Chazapis
            raise NotAllowedError
332 02c0c3fa Antony Chazapis
        try:
333 02c0c3fa Antony Chazapis
            path, version_id, mtime = self._get_containerinfo(account, container)
334 02c0c3fa Antony Chazapis
        except NameError:
335 3ab38c43 Antony Chazapis
            pass
336 02c0c3fa Antony Chazapis
        else:
337 02c0c3fa Antony Chazapis
            raise NameError('Container already exists')
338 3ab38c43 Antony Chazapis
        if policy:
339 3ab38c43 Antony Chazapis
            self._check_policy(policy)
340 3ab38c43 Antony Chazapis
        path = os.path.join(account, container)
341 3ab38c43 Antony Chazapis
        version_id = self._put_version(path, user)
342 3ab38c43 Antony Chazapis
        for k, v in self.default_policy.iteritems():
343 3ab38c43 Antony Chazapis
            if k not in policy:
344 3ab38c43 Antony Chazapis
                policy[k] = v
345 3ab38c43 Antony Chazapis
        for k, v in policy.iteritems():
346 3ab38c43 Antony Chazapis
            sql = 'insert or replace into policy (name, key, value) values (?, ?, ?)'
347 3ab38c43 Antony Chazapis
            self.con.execute(sql, (path, k, v))
348 02c0c3fa Antony Chazapis
    
349 30b46c1d Antony Chazapis
    @backend_method
350 84846143 Antony Chazapis
    def delete_container(self, user, account, container, until=None):
351 84846143 Antony Chazapis
        """Delete/purge the container with the given name."""
352 02c0c3fa Antony Chazapis
        
353 84846143 Antony Chazapis
        logger.debug("delete_container: %s %s %s", account, container, until)
354 02c0c3fa Antony Chazapis
        if user != account:
355 02c0c3fa Antony Chazapis
            raise NotAllowedError
356 02c0c3fa Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container)
357 84846143 Antony Chazapis
        
358 84846143 Antony Chazapis
        if until is not None:
359 c428326e Antony Chazapis
            sql = '''select version_id from versions where name like ? and tstamp <= ?
360 c428326e Antony Chazapis
                        and version_id not in (select version_id from (%s))'''
361 c428326e Antony Chazapis
            sql = sql % self._sql_until() # Do not delete current versions.
362 84846143 Antony Chazapis
            c = self.con.execute(sql, (path + '/%', until))
363 84846143 Antony Chazapis
            for v in [x[0] for x in c.fetchall()]:
364 84846143 Antony Chazapis
                self._del_version(v)
365 84846143 Antony Chazapis
            return
366 84846143 Antony Chazapis
        
367 84846143 Antony Chazapis
        count = self._get_pathstats(path)[0]
368 02c0c3fa Antony Chazapis
        if count > 0:
369 02c0c3fa Antony Chazapis
            raise IndexError('Container is not empty')
370 2f51ce47 Antony Chazapis
        sql = 'delete from versions where name = ? or name like ?' # May contain hidden items.
371 2f51ce47 Antony Chazapis
        self.con.execute(sql, (path, path + '/%',))
372 84846143 Antony Chazapis
        sql = 'delete from policy where name = ?'
373 84846143 Antony Chazapis
        self.con.execute(sql, (path,))
374 a156c8b3 Antony Chazapis
        self._copy_version(user, account, account, True, False) # New account version (for timestamp update).
375 02c0c3fa Antony Chazapis
    
376 30b46c1d Antony Chazapis
    @backend_method
377 b18ef3ad Antony Chazapis
    def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], shared=False, until=None):
378 b956618e Antony Chazapis
        """Return a list of objects existing under a container."""
379 b956618e Antony Chazapis
        
380 b18ef3ad Antony Chazapis
        logger.debug("list_objects: %s %s %s %s %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit, virtual, keys, shared, until)
381 f6c97079 Antony Chazapis
        allowed = []
382 cca6c617 Antony Chazapis
        if user != account:
383 f6c97079 Antony Chazapis
            if until:
384 f6c97079 Antony Chazapis
                raise NotAllowedError
385 f6c97079 Antony Chazapis
            allowed = self._allowed_paths(user, os.path.join(account, container))
386 f6c97079 Antony Chazapis
            if not allowed:
387 f6c97079 Antony Chazapis
                raise NotAllowedError
388 b18ef3ad Antony Chazapis
        else:
389 b18ef3ad Antony Chazapis
            if shared:
390 b18ef3ad Antony Chazapis
                allowed = self._shared_paths(os.path.join(account, container))
391 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
392 f6c97079 Antony Chazapis
        return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, until, allowed)
393 22dab079 Antony Chazapis
    
394 30b46c1d Antony Chazapis
    @backend_method
395 83dd59c5 Antony Chazapis
    def list_object_meta(self, user, account, container, until=None):
396 22dab079 Antony Chazapis
        """Return a list with all the container's object meta keys."""
397 b956618e Antony Chazapis
        
398 58a6c894 Antony Chazapis
        logger.debug("list_object_meta: %s %s %s", account, container, until)
399 f6c97079 Antony Chazapis
        allowed = []
400 cca6c617 Antony Chazapis
        if user != account:
401 f6c97079 Antony Chazapis
            if until:
402 f6c97079 Antony Chazapis
                raise NotAllowedError
403 f6c97079 Antony Chazapis
            allowed = self._allowed_paths(user, os.path.join(account, container))
404 f6c97079 Antony Chazapis
            if not allowed:
405 f6c97079 Antony Chazapis
                raise NotAllowedError
406 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
407 58a6c894 Antony Chazapis
        sql = '''select distinct m.key from (%s) o, metadata m
408 58a6c894 Antony Chazapis
                    where m.version_id = o.version_id and o.name like ?'''
409 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
410 f6c97079 Antony Chazapis
        param = (path + '/%',)
411 f6c97079 Antony Chazapis
        if allowed:
412 f6c97079 Antony Chazapis
            for x in allowed:
413 f6c97079 Antony Chazapis
                sql += ' and o.name like ?'
414 f6c97079 Antony Chazapis
                param += (x,)
415 f6c97079 Antony Chazapis
        c = self.con.execute(sql, param)
416 22dab079 Antony Chazapis
        return [x[0] for x in c.fetchall()]
417 b956618e Antony Chazapis
    
418 30b46c1d Antony Chazapis
    @backend_method
419 83dd59c5 Antony Chazapis
    def get_object_meta(self, user, account, container, name, version=None):
420 b956618e Antony Chazapis
        """Return a dictionary with the object metadata."""
421 b956618e Antony Chazapis
        
422 58a6c894 Antony Chazapis
        logger.debug("get_object_meta: %s %s %s %s", account, container, name, version)
423 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
424 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name, version)
425 58a6c894 Antony Chazapis
        if version is None:
426 58a6c894 Antony Chazapis
            modified = mtime
427 58a6c894 Antony Chazapis
        else:
428 104626e3 Antony Chazapis
            modified = self._get_version(path, version)[2] # Overall last modification
429 58a6c894 Antony Chazapis
        
430 58a6c894 Antony Chazapis
        meta = self._get_metadata(path, version_id)
431 104626e3 Antony Chazapis
        meta.update({'name': name, 'bytes': size})
432 104626e3 Antony Chazapis
        meta.update({'version': version_id, 'version_timestamp': mtime})
433 104626e3 Antony Chazapis
        meta.update({'modified': modified, 'modified_by': muser})
434 b956618e Antony Chazapis
        return meta
435 b956618e Antony Chazapis
    
436 30b46c1d Antony Chazapis
    @backend_method
437 83dd59c5 Antony Chazapis
    def update_object_meta(self, user, account, container, name, meta, replace=False):
438 b956618e Antony Chazapis
        """Update the metadata associated with the object."""
439 b956618e Antony Chazapis
        
440 22dab079 Antony Chazapis
        logger.debug("update_object_meta: %s %s %s %s %s", account, container, name, meta, replace)
441 cca6c617 Antony Chazapis
        self._can_write(user, account, container, name)
442 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name)
443 104626e3 Antony Chazapis
        self._put_metadata(user, path, meta, replace)
444 b956618e Antony Chazapis
    
445 30b46c1d Antony Chazapis
    @backend_method
446 3436eeb0 Antony Chazapis
    def get_object_permissions(self, user, account, container, name):
447 cca6c617 Antony Chazapis
        """Return the path from which this object gets its permissions from,\
448 cca6c617 Antony Chazapis
        along with a dictionary containing the permissions."""
449 3436eeb0 Antony Chazapis
        
450 3436eeb0 Antony Chazapis
        logger.debug("get_object_permissions: %s %s %s", account, container, name)
451 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
452 3436eeb0 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
453 cca6c617 Antony Chazapis
        return self._get_permissions(path)
454 3436eeb0 Antony Chazapis
    
455 30b46c1d Antony Chazapis
    @backend_method
456 3436eeb0 Antony Chazapis
    def update_object_permissions(self, user, account, container, name, permissions):
457 3436eeb0 Antony Chazapis
        """Update the permissions associated with the object."""
458 3436eeb0 Antony Chazapis
        
459 3436eeb0 Antony Chazapis
        logger.debug("update_object_permissions: %s %s %s %s", account, container, name, permissions)
460 cca6c617 Antony Chazapis
        if user != account:
461 cca6c617 Antony Chazapis
            raise NotAllowedError
462 3436eeb0 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
463 3436eeb0 Antony Chazapis
        r, w = self._check_permissions(path, permissions)
464 3436eeb0 Antony Chazapis
        self._put_permissions(path, r, w)
465 3436eeb0 Antony Chazapis
    
466 30b46c1d Antony Chazapis
    @backend_method
467 02c0c3fa Antony Chazapis
    def get_object_public(self, user, account, container, name):
468 02c0c3fa Antony Chazapis
        """Return the public URL of the object if applicable."""
469 02c0c3fa Antony Chazapis
        
470 02c0c3fa Antony Chazapis
        logger.debug("get_object_public: %s %s %s", account, container, name)
471 e0f916bb Antony Chazapis
        self._can_read(user, account, container, name)
472 e0f916bb Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
473 e0f916bb Antony Chazapis
        if self._get_public(path):
474 e0f916bb Antony Chazapis
            return '/public/' + path
475 02c0c3fa Antony Chazapis
        return None
476 02c0c3fa Antony Chazapis
    
477 30b46c1d Antony Chazapis
    @backend_method
478 02c0c3fa Antony Chazapis
    def update_object_public(self, user, account, container, name, public):
479 02c0c3fa Antony Chazapis
        """Update the public status of the object."""
480 02c0c3fa Antony Chazapis
        
481 02c0c3fa Antony Chazapis
        logger.debug("update_object_public: %s %s %s %s", account, container, name, public)
482 e0f916bb Antony Chazapis
        self._can_write(user, account, container, name)
483 e0f916bb Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
484 e0f916bb Antony Chazapis
        self._put_public(path, public)
485 02c0c3fa Antony Chazapis
    
486 30b46c1d Antony Chazapis
    @backend_method
487 83dd59c5 Antony Chazapis
    def get_object_hashmap(self, user, account, container, name, version=None):
488 22dab079 Antony Chazapis
        """Return the object's size and a list with partial hashes."""
489 b956618e Antony Chazapis
        
490 22dab079 Antony Chazapis
        logger.debug("get_object_hashmap: %s %s %s %s", account, container, name, version)
491 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
492 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name, version)
493 a156c8b3 Antony Chazapis
        hashmap = self.mapper.map_retr(version_id)
494 a156c8b3 Antony Chazapis
        return size, [binascii.hexlify(x) for x in hashmap]
495 22dab079 Antony Chazapis
    
496 30b46c1d Antony Chazapis
    @backend_method
497 cca6c617 Antony Chazapis
    def update_object_hashmap(self, user, account, container, name, size, hashmap, meta={}, replace_meta=False, permissions=None):
498 22dab079 Antony Chazapis
        """Create/update an object with the specified size and partial hashes."""
499 b956618e Antony Chazapis
        
500 cfe6939d Antony Chazapis
        logger.debug("update_object_hashmap: %s %s %s %s %s", account, container, name, size, hashmap)
501 cca6c617 Antony Chazapis
        if permissions is not None and user != account:
502 cca6c617 Antony Chazapis
            raise NotAllowedError
503 cca6c617 Antony Chazapis
        self._can_write(user, account, container, name)
504 a156c8b3 Antony Chazapis
        missing = self.blocker.block_ping([binascii.unhexlify(x) for x in hashmap])
505 76985443 Sofia Papagiannaki
        if missing:
506 76985443 Sofia Papagiannaki
            ie = IndexError()
507 76985443 Sofia Papagiannaki
            ie.data = missing
508 76985443 Sofia Papagiannaki
            raise ie
509 58a6c894 Antony Chazapis
        path = self._get_containerinfo(account, container)[0]
510 58a6c894 Antony Chazapis
        path = os.path.join(path, name)
511 cca6c617 Antony Chazapis
        if permissions is not None:
512 3436eeb0 Antony Chazapis
            r, w = self._check_permissions(path, permissions)
513 104626e3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, path, path, not replace_meta, False)
514 58a6c894 Antony Chazapis
        sql = 'update versions set size = ? where version_id = ?'
515 58a6c894 Antony Chazapis
        self.con.execute(sql, (size, dest_version_id))
516 a156c8b3 Antony Chazapis
        self.mapper.map_stor(dest_version_id, [binascii.unhexlify(x) for x in hashmap])
517 83dd59c5 Antony Chazapis
        for k, v in meta.iteritems():
518 83dd59c5 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
519 83dd59c5 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
520 cca6c617 Antony Chazapis
        if permissions is not None:
521 b0a2d1a6 Antony Chazapis
            self._put_permissions(path, r, w)
522 22dab079 Antony Chazapis
    
523 30b46c1d Antony Chazapis
    @backend_method
524 cca6c617 Antony Chazapis
    def copy_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
525 22dab079 Antony Chazapis
        """Copy an object's data and metadata."""
526 b956618e Antony Chazapis
        
527 3436eeb0 Antony Chazapis
        logger.debug("copy_object: %s %s %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, src_version)
528 cca6c617 Antony Chazapis
        if permissions is not None and user != account:
529 cca6c617 Antony Chazapis
            raise NotAllowedError
530 cca6c617 Antony Chazapis
        self._can_read(user, account, src_container, src_name)
531 cca6c617 Antony Chazapis
        self._can_write(user, account, dest_container, dest_name)
532 6d817842 Antony Chazapis
        self._get_containerinfo(account, src_container)
533 58a6c894 Antony Chazapis
        if src_version is None:
534 58a6c894 Antony Chazapis
            src_path = self._get_objectinfo(account, src_container, src_name)[0]
535 22dab079 Antony Chazapis
        else:
536 58a6c894 Antony Chazapis
            src_path = os.path.join(account, src_container, src_name)
537 58a6c894 Antony Chazapis
        dest_path = self._get_containerinfo(account, dest_container)[0]
538 58a6c894 Antony Chazapis
        dest_path = os.path.join(dest_path, dest_name)
539 cca6c617 Antony Chazapis
        if permissions is not None:
540 3436eeb0 Antony Chazapis
            r, w = self._check_permissions(dest_path, permissions)
541 104626e3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, src_path, dest_path, not replace_meta, True, src_version)
542 58a6c894 Antony Chazapis
        for k, v in dest_meta.iteritems():
543 58a6c894 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
544 58a6c894 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
545 cca6c617 Antony Chazapis
        if permissions is not None:
546 b0a2d1a6 Antony Chazapis
            self._put_permissions(dest_path, r, w)
547 b956618e Antony Chazapis
    
548 30b46c1d Antony Chazapis
    @backend_method
549 cca6c617 Antony Chazapis
    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
550 b956618e Antony Chazapis
        """Move an object's data and metadata."""
551 b956618e Antony Chazapis
        
552 3436eeb0 Antony Chazapis
        logger.debug("move_object: %s %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions)
553 3436eeb0 Antony Chazapis
        self.copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
554 83dd59c5 Antony Chazapis
        self.delete_object(user, account, src_container, src_name)
555 b956618e Antony Chazapis
    
556 30b46c1d Antony Chazapis
    @backend_method
557 84846143 Antony Chazapis
    def delete_object(self, user, account, container, name, until=None):
558 84846143 Antony Chazapis
        """Delete/purge an object."""
559 b956618e Antony Chazapis
        
560 84846143 Antony Chazapis
        logger.debug("delete_object: %s %s %s %s", account, container, name, until)
561 cca6c617 Antony Chazapis
        if user != account:
562 cca6c617 Antony Chazapis
            raise NotAllowedError
563 84846143 Antony Chazapis
        
564 84846143 Antony Chazapis
        if until is not None:
565 84846143 Antony Chazapis
            path = os.path.join(account, container, name)
566 84846143 Antony Chazapis
            sql = '''select version_id from versions where name = ? and tstamp <= ?'''
567 84846143 Antony Chazapis
            c = self.con.execute(sql, (path, until))
568 84846143 Antony Chazapis
            for v in [x[0] in c.fetchall()]:
569 84846143 Antony Chazapis
                self._del_version(v)
570 84846143 Antony Chazapis
            try:
571 84846143 Antony Chazapis
                version_id = self._get_version(path)[0]
572 84846143 Antony Chazapis
            except NameError:
573 84846143 Antony Chazapis
                pass
574 84846143 Antony Chazapis
            else:
575 84846143 Antony Chazapis
                self._del_sharing(path)
576 84846143 Antony Chazapis
            return
577 84846143 Antony Chazapis
        
578 104626e3 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
579 104626e3 Antony Chazapis
        self._put_version(path, user, 0, 1)
580 84846143 Antony Chazapis
        self._del_sharing(path)
581 58a6c894 Antony Chazapis
    
582 30b46c1d Antony Chazapis
    @backend_method
583 83dd59c5 Antony Chazapis
    def list_versions(self, user, account, container, name):
584 83dd59c5 Antony Chazapis
        """Return a list of all (version, version_timestamp) tuples for an object."""
585 58a6c894 Antony Chazapis
        
586 83dd59c5 Antony Chazapis
        logger.debug("list_versions: %s %s %s", account, container, name)
587 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
588 58a6c894 Antony Chazapis
        # This will even show deleted versions.
589 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
590 84846143 Antony Chazapis
        sql = '''select distinct version_id, tstamp from versions where name = ? and hide = 0'''
591 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path,))
592 c9af0703 Antony Chazapis
        return [(int(x[0]), int(x[1])) for x in c.fetchall()]
593 b956618e Antony Chazapis
    
594 30b46c1d Antony Chazapis
    @backend_method(autocommit=0)
595 22dab079 Antony Chazapis
    def get_block(self, hash):
596 22dab079 Antony Chazapis
        """Return a block's data."""
597 22dab079 Antony Chazapis
        
598 cfe6939d Antony Chazapis
        logger.debug("get_block: %s", hash)
599 a156c8b3 Antony Chazapis
        blocks = self.blocker.block_retr((binascii.unhexlify(hash),))
600 a156c8b3 Antony Chazapis
        if not blocks:
601 22dab079 Antony Chazapis
            raise NameError('Block does not exist')
602 a156c8b3 Antony Chazapis
        return blocks[0]
603 b956618e Antony Chazapis
    
604 30b46c1d Antony Chazapis
    @backend_method(autocommit=0)
605 22dab079 Antony Chazapis
    def put_block(self, data):
606 22dab079 Antony Chazapis
        """Create a block and return the hash."""
607 22dab079 Antony Chazapis
        
608 cfe6939d Antony Chazapis
        logger.debug("put_block: %s", len(data))
609 a156c8b3 Antony Chazapis
        hashes, absent = self.blocker.block_stor((data,))
610 a156c8b3 Antony Chazapis
        return binascii.hexlify(hashes[0])
611 22dab079 Antony Chazapis
    
612 30b46c1d Antony Chazapis
    @backend_method(autocommit=0)
613 22dab079 Antony Chazapis
    def update_block(self, hash, data, offset=0):
614 22dab079 Antony Chazapis
        """Update a known block and return the hash."""
615 22dab079 Antony Chazapis
        
616 cfe6939d Antony Chazapis
        logger.debug("update_block: %s %s %s", hash, len(data), offset)
617 cfe6939d Antony Chazapis
        if offset == 0 and len(data) == self.block_size:
618 cfe6939d Antony Chazapis
            return self.put_block(data)
619 a156c8b3 Antony Chazapis
        h, e = self.blocker.block_delta(binascii.unhexlify(hash), ((offset, data),))
620 a156c8b3 Antony Chazapis
        return binascii.hexlify(h)
621 b956618e Antony Chazapis
    
622 58a6c894 Antony Chazapis
    def _sql_until(self, until=None):
623 58a6c894 Antony Chazapis
        """Return the sql to get the latest versions until the timestamp given."""
624 58a6c894 Antony Chazapis
        if until is None:
625 58a6c894 Antony Chazapis
            until = int(time.time())
626 84846143 Antony Chazapis
        sql = '''select version_id, name, tstamp, size from versions v
627 58a6c894 Antony Chazapis
                    where version_id = (select max(version_id) from versions
628 2f51ce47 Antony Chazapis
                                        where v.name = name and tstamp <= %s)
629 58a6c894 Antony Chazapis
                    and hide = 0'''
630 84846143 Antony Chazapis
        return sql % (until,)
631 58a6c894 Antony Chazapis
    
632 58a6c894 Antony Chazapis
    def _get_pathstats(self, path, until=None):
633 58a6c894 Antony Chazapis
        """Return count and sum of size of everything under path and latest timestamp."""
634 58a6c894 Antony Chazapis
        
635 58a6c894 Antony Chazapis
        sql = 'select count(version_id), total(size), max(tstamp) from (%s) where name like ?'
636 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
637 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path + '/%',))
638 b956618e Antony Chazapis
        row = c.fetchone()
639 58a6c894 Antony Chazapis
        tstamp = row[2] if row[2] is not None else 0
640 58a6c894 Antony Chazapis
        return int(row[0]), int(row[1]), int(tstamp)
641 58a6c894 Antony Chazapis
    
642 58a6c894 Antony Chazapis
    def _get_version(self, path, version=None):
643 cb146cf9 Antony Chazapis
        if version is None:
644 84846143 Antony Chazapis
            sql = '''select version_id, user, tstamp, size, hide from versions where name = ?
645 58a6c894 Antony Chazapis
                        order by version_id desc limit 1'''
646 58a6c894 Antony Chazapis
            c = self.con.execute(sql, (path,))
647 58a6c894 Antony Chazapis
            row = c.fetchone()
648 104626e3 Antony Chazapis
            if not row or int(row[4]):
649 58a6c894 Antony Chazapis
                raise NameError('Object does not exist')
650 b956618e Antony Chazapis
        else:
651 104626e3 Antony Chazapis
            # The database (sqlite) will not complain if the version is not an integer.
652 84846143 Antony Chazapis
            sql = '''select version_id, user, tstamp, size from versions where name = ?
653 58a6c894 Antony Chazapis
                        and version_id = ?'''
654 58a6c894 Antony Chazapis
            c = self.con.execute(sql, (path, version))
655 58a6c894 Antony Chazapis
            row = c.fetchone()
656 58a6c894 Antony Chazapis
            if not row:
657 58a6c894 Antony Chazapis
                raise IndexError('Version does not exist')
658 104626e3 Antony Chazapis
        return str(row[0]), str(row[1]), int(row[2]), int(row[3])
659 58a6c894 Antony Chazapis
    
660 104626e3 Antony Chazapis
    def _put_version(self, path, user, size=0, hide=0):
661 84846143 Antony Chazapis
        tstamp = int(time.time())
662 84846143 Antony Chazapis
        sql = 'insert into versions (name, user, tstamp, size, hide) values (?, ?, ?, ?, ?)'
663 84846143 Antony Chazapis
        id = self.con.execute(sql, (path, user, tstamp, size, hide)).lastrowid
664 b956618e Antony Chazapis
        return str(id)
665 b956618e Antony Chazapis
    
666 104626e3 Antony Chazapis
    def _copy_version(self, user, src_path, dest_path, copy_meta=True, copy_data=True, src_version=None):
667 58a6c894 Antony Chazapis
        if src_version is not None:
668 104626e3 Antony Chazapis
            src_version_id, muser, mtime, size = self._get_version(src_path, src_version)
669 58a6c894 Antony Chazapis
        else:
670 58a6c894 Antony Chazapis
            # Latest or create from scratch.
671 58a6c894 Antony Chazapis
            try:
672 104626e3 Antony Chazapis
                src_version_id, muser, mtime, size = self._get_version(src_path)
673 58a6c894 Antony Chazapis
            except NameError:
674 58a6c894 Antony Chazapis
                src_version_id = None
675 58a6c894 Antony Chazapis
                size = 0
676 58a6c894 Antony Chazapis
        if not copy_data:
677 58a6c894 Antony Chazapis
            size = 0
678 104626e3 Antony Chazapis
        dest_version_id = self._put_version(dest_path, user, size)
679 58a6c894 Antony Chazapis
        if copy_meta and src_version_id is not None:
680 58a6c894 Antony Chazapis
            sql = 'insert into metadata select %s, key, value from metadata where version_id = ?'
681 58a6c894 Antony Chazapis
            sql = sql % dest_version_id
682 58a6c894 Antony Chazapis
            self.con.execute(sql, (src_version_id,))
683 58a6c894 Antony Chazapis
        if copy_data and src_version_id is not None:
684 a156c8b3 Antony Chazapis
            # TODO: Copy properly.
685 a156c8b3 Antony Chazapis
            hashmap = self.mapper.map_retr(src_version_id)
686 a156c8b3 Antony Chazapis
            self.mapper.map_stor(dest_version_id, hashmap)
687 58a6c894 Antony Chazapis
        return src_version_id, dest_version_id
688 58a6c894 Antony Chazapis
    
689 58a6c894 Antony Chazapis
    def _get_versioninfo(self, account, container, name, until=None):
690 58a6c894 Antony Chazapis
        """Return path, latest version, associated timestamp and size until the timestamp given."""
691 58a6c894 Antony Chazapis
        
692 58a6c894 Antony Chazapis
        p = (account, container, name)
693 22dab079 Antony Chazapis
        try:
694 58a6c894 Antony Chazapis
            p = p[:p.index(None)]
695 58a6c894 Antony Chazapis
        except ValueError:
696 58a6c894 Antony Chazapis
            pass
697 58a6c894 Antony Chazapis
        path = os.path.join(*p)
698 58a6c894 Antony Chazapis
        sql = '''select version_id, tstamp, size from (%s) where name = ?'''
699 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
700 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path,))
701 58a6c894 Antony Chazapis
        row = c.fetchone()
702 58a6c894 Antony Chazapis
        if row is None:
703 58a6c894 Antony Chazapis
            raise NameError('Path does not exist')
704 58a6c894 Antony Chazapis
        return path, str(row[0]), int(row[1]), int(row[2])
705 58a6c894 Antony Chazapis
    
706 58a6c894 Antony Chazapis
    def _get_accountinfo(self, account, until=None):
707 58a6c894 Antony Chazapis
        try:
708 58a6c894 Antony Chazapis
            path, version_id, mtime, size = self._get_versioninfo(account, None, None, until)
709 58a6c894 Antony Chazapis
            return version_id, mtime
710 58a6c894 Antony Chazapis
        except:
711 58a6c894 Antony Chazapis
            raise NameError('Account does not exist')
712 58a6c894 Antony Chazapis
    
713 58a6c894 Antony Chazapis
    def _get_containerinfo(self, account, container, until=None):
714 58a6c894 Antony Chazapis
        try:
715 58a6c894 Antony Chazapis
            path, version_id, mtime, size = self._get_versioninfo(account, container, None, until)
716 58a6c894 Antony Chazapis
            return path, version_id, mtime
717 58a6c894 Antony Chazapis
        except:
718 22dab079 Antony Chazapis
            raise NameError('Container does not exist')
719 22dab079 Antony Chazapis
    
720 22dab079 Antony Chazapis
    def _get_objectinfo(self, account, container, name, version=None):
721 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
722 104626e3 Antony Chazapis
        version_id, muser, mtime, size = self._get_version(path, version)
723 104626e3 Antony Chazapis
        return path, version_id, muser, mtime, size
724 22dab079 Antony Chazapis
    
725 58a6c894 Antony Chazapis
    def _get_metadata(self, path, version):
726 58a6c894 Antony Chazapis
        sql = 'select key, value from metadata where version_id = ?'
727 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (version,))
728 58a6c894 Antony Chazapis
        return dict(c.fetchall())
729 58a6c894 Antony Chazapis
    
730 a156c8b3 Antony Chazapis
    def _put_metadata(self, user, path, meta, replace=False, copy_data=True):
731 58a6c894 Antony Chazapis
        """Create a new version and store metadata."""
732 58a6c894 Antony Chazapis
        
733 a156c8b3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, path, path, not replace, copy_data)
734 58a6c894 Antony Chazapis
        for k, v in meta.iteritems():
735 a6eb13e9 Antony Chazapis
            if not replace and v == '':
736 a6eb13e9 Antony Chazapis
                sql = 'delete from metadata where version_id = ? and key = ?'
737 a6eb13e9 Antony Chazapis
                self.con.execute(sql, (dest_version_id, k))
738 a6eb13e9 Antony Chazapis
            else:
739 a6eb13e9 Antony Chazapis
                sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
740 a6eb13e9 Antony Chazapis
                self.con.execute(sql, (dest_version_id, k, v))
741 58a6c894 Antony Chazapis
    
742 3ab38c43 Antony Chazapis
    def _check_policy(self, policy):
743 3ab38c43 Antony Chazapis
        for k in policy.keys():
744 3ab38c43 Antony Chazapis
            if policy[k] == '':
745 3ab38c43 Antony Chazapis
                policy[k] = self.default_policy.get(k)
746 3ab38c43 Antony Chazapis
        for k, v in policy.iteritems():
747 3ab38c43 Antony Chazapis
            if k == 'quota':
748 3ab38c43 Antony Chazapis
                q = int(v) # May raise ValueError.
749 3ab38c43 Antony Chazapis
                if q < 0:
750 3ab38c43 Antony Chazapis
                    raise ValueError
751 3ab38c43 Antony Chazapis
            elif k == 'versioning':
752 3ab38c43 Antony Chazapis
                if v not in ['auto', 'manual', 'none']:
753 3ab38c43 Antony Chazapis
                    raise ValueError
754 3ab38c43 Antony Chazapis
            else:
755 3ab38c43 Antony Chazapis
                raise ValueError
756 3ab38c43 Antony Chazapis
    
757 3ab38c43 Antony Chazapis
    def _get_policy(self, path):
758 3ab38c43 Antony Chazapis
        sql = 'select key, value from policy where name = ?'
759 3ab38c43 Antony Chazapis
        c = self.con.execute(sql, (path,))
760 3ab38c43 Antony Chazapis
        return dict(c.fetchall())
761 3ab38c43 Antony Chazapis
    
762 f6c97079 Antony Chazapis
    
763 f6c97079 Antony Chazapis
    def _list_limits(self, listing, marker, limit):
764 f6c97079 Antony Chazapis
        start = 0
765 f6c97079 Antony Chazapis
        if marker:
766 f6c97079 Antony Chazapis
            try:
767 f6c97079 Antony Chazapis
                start = listing.index(marker) + 1
768 f6c97079 Antony Chazapis
            except ValueError:
769 f6c97079 Antony Chazapis
                pass
770 f6c97079 Antony Chazapis
        if not limit or limit > 10000:
771 f6c97079 Antony Chazapis
            limit = 10000
772 f6c97079 Antony Chazapis
        return start, limit
773 f6c97079 Antony Chazapis
    
774 f6c97079 Antony Chazapis
    def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None, allowed=[]):
775 b0a2d1a6 Antony Chazapis
        cont_prefix = path + '/'
776 b0a2d1a6 Antony Chazapis
        if keys and len(keys) > 0:
777 b0a2d1a6 Antony Chazapis
            sql = '''select distinct o.name, o.version_id from (%s) o, metadata m where o.name like ? and
778 f6c97079 Antony Chazapis
                        m.version_id = o.version_id and m.key in (%s)'''
779 b0a2d1a6 Antony Chazapis
            sql = sql % (self._sql_until(until), ', '.join('?' * len(keys)))
780 b0a2d1a6 Antony Chazapis
            param = (cont_prefix + prefix + '%',) + tuple(keys)
781 f6c97079 Antony Chazapis
            if allowed:
782 f6c97079 Antony Chazapis
                for x in allowed:
783 f6c97079 Antony Chazapis
                    sql += ' and o.name like ?'
784 f6c97079 Antony Chazapis
                    param += (x,)
785 f6c97079 Antony Chazapis
            sql += ' order by o.name'
786 b0a2d1a6 Antony Chazapis
        else:
787 f6c97079 Antony Chazapis
            sql = 'select name, version_id from (%s) where name like ?'
788 b0a2d1a6 Antony Chazapis
            sql = sql % self._sql_until(until)
789 b0a2d1a6 Antony Chazapis
            param = (cont_prefix + prefix + '%',)
790 f6c97079 Antony Chazapis
            if allowed:
791 f6c97079 Antony Chazapis
                for x in allowed:
792 f6c97079 Antony Chazapis
                    sql += ' and name like ?'
793 f6c97079 Antony Chazapis
                    param += (x,)
794 f6c97079 Antony Chazapis
            sql += ' order by name'
795 b0a2d1a6 Antony Chazapis
        c = self.con.execute(sql, param)
796 b0a2d1a6 Antony Chazapis
        objects = [(x[0][len(cont_prefix):], x[1]) for x in c.fetchall()]
797 b0a2d1a6 Antony Chazapis
        if delimiter:
798 b0a2d1a6 Antony Chazapis
            pseudo_objects = []
799 b0a2d1a6 Antony Chazapis
            for x in objects:
800 b0a2d1a6 Antony Chazapis
                pseudo_name = x[0]
801 b0a2d1a6 Antony Chazapis
                i = pseudo_name.find(delimiter, len(prefix))
802 b0a2d1a6 Antony Chazapis
                if not virtual:
803 b0a2d1a6 Antony Chazapis
                    # If the delimiter is not found, or the name ends
804 b0a2d1a6 Antony Chazapis
                    # with the delimiter's first occurence.
805 b0a2d1a6 Antony Chazapis
                    if i == -1 or len(pseudo_name) == i + len(delimiter):
806 b0a2d1a6 Antony Chazapis
                        pseudo_objects.append(x)
807 b0a2d1a6 Antony Chazapis
                else:
808 b0a2d1a6 Antony Chazapis
                    # If the delimiter is found, keep up to (and including) the delimiter.
809 b0a2d1a6 Antony Chazapis
                    if i != -1:
810 b0a2d1a6 Antony Chazapis
                        pseudo_name = pseudo_name[:i + len(delimiter)]
811 b0a2d1a6 Antony Chazapis
                    if pseudo_name not in [y[0] for y in pseudo_objects]:
812 b0a2d1a6 Antony Chazapis
                        if pseudo_name == x[0]:
813 b0a2d1a6 Antony Chazapis
                            pseudo_objects.append(x)
814 b0a2d1a6 Antony Chazapis
                        else:
815 b0a2d1a6 Antony Chazapis
                            pseudo_objects.append((pseudo_name, None))
816 b0a2d1a6 Antony Chazapis
            objects = pseudo_objects
817 b0a2d1a6 Antony Chazapis
        
818 f6c97079 Antony Chazapis
        start, limit = self._list_limits([x[0] for x in objects], marker, limit)
819 b0a2d1a6 Antony Chazapis
        return objects[start:start + limit]
820 b0a2d1a6 Antony Chazapis
    
821 b0a2d1a6 Antony Chazapis
    def _del_version(self, version):
822 b0a2d1a6 Antony Chazapis
        self.mapper.map_remv(version)
823 b0a2d1a6 Antony Chazapis
        sql = 'delete from versions where version_id = ?'
824 b0a2d1a6 Antony Chazapis
        self.con.execute(sql, (version,))
825 b0a2d1a6 Antony Chazapis
    
826 b0a2d1a6 Antony Chazapis
    # Access control functions.
827 b0a2d1a6 Antony Chazapis
    
828 b0a2d1a6 Antony Chazapis
    def _check_groups(self, groups):
829 b0a2d1a6 Antony Chazapis
        # Example follows.
830 b0a2d1a6 Antony Chazapis
        # for k, v in groups.iteritems():
831 b0a2d1a6 Antony Chazapis
        #     if True in [False or ',' in x for x in v]:
832 b0a2d1a6 Antony Chazapis
        #         raise ValueError('Bad characters in groups')
833 b0a2d1a6 Antony Chazapis
        pass
834 b0a2d1a6 Antony Chazapis
    
835 b0a2d1a6 Antony Chazapis
    def _get_groups(self, account):
836 b0a2d1a6 Antony Chazapis
        sql = 'select gname, user from groups where account = ?'
837 b0a2d1a6 Antony Chazapis
        c = self.con.execute(sql, (account,))
838 b0a2d1a6 Antony Chazapis
        groups = {}
839 b0a2d1a6 Antony Chazapis
        for row in c.fetchall():
840 b0a2d1a6 Antony Chazapis
            if row[0] not in groups:
841 b0a2d1a6 Antony Chazapis
                groups[row[0]] = []
842 b0a2d1a6 Antony Chazapis
            groups[row[0]].append(row[1])
843 b0a2d1a6 Antony Chazapis
        return groups
844 b0a2d1a6 Antony Chazapis
    
845 b0a2d1a6 Antony Chazapis
    def _put_groups(self, account, groups, replace=False):
846 b0a2d1a6 Antony Chazapis
        if replace:
847 b0a2d1a6 Antony Chazapis
            self._del_groups(account)
848 b0a2d1a6 Antony Chazapis
        for k, v in groups.iteritems():
849 b0a2d1a6 Antony Chazapis
            sql = 'delete from groups where account = ? and gname = ?'
850 b0a2d1a6 Antony Chazapis
            self.con.execute(sql, (account, k))
851 b0a2d1a6 Antony Chazapis
            if v:
852 b0a2d1a6 Antony Chazapis
                sql = 'insert into groups (account, gname, user) values (?, ?, ?)'
853 b0a2d1a6 Antony Chazapis
                self.con.executemany(sql, [(account, k, x) for x in v])
854 b0a2d1a6 Antony Chazapis
    
855 b0a2d1a6 Antony Chazapis
    def _del_groups(self, account):
856 b0a2d1a6 Antony Chazapis
        sql = 'delete from groups where account = ?'
857 b0a2d1a6 Antony Chazapis
        self.con.execute(sql, (account,))
858 b0a2d1a6 Antony Chazapis
    
859 3436eeb0 Antony Chazapis
    def _check_permissions(self, path, permissions):
860 3436eeb0 Antony Chazapis
        # Check for existing permissions.
861 3436eeb0 Antony Chazapis
        sql = '''select name from permissions
862 3436eeb0 Antony Chazapis
                    where name != ? and (name like ? or ? like name || ?)'''
863 3436eeb0 Antony Chazapis
        c = self.con.execute(sql, (path, path + '%', path, '%'))
864 1993fea9 Antony Chazapis
        row = c.fetchone()
865 1993fea9 Antony Chazapis
        if row:
866 1993fea9 Antony Chazapis
            ae = AttributeError()
867 1993fea9 Antony Chazapis
            ae.data = row[0]
868 1993fea9 Antony Chazapis
            raise ae
869 3436eeb0 Antony Chazapis
        
870 cca6c617 Antony Chazapis
        # Format given permissions.
871 cca6c617 Antony Chazapis
        if len(permissions) == 0:
872 b0a2d1a6 Antony Chazapis
            return [], []
873 3436eeb0 Antony Chazapis
        r = permissions.get('read', [])
874 3436eeb0 Antony Chazapis
        w = permissions.get('write', [])
875 b0a2d1a6 Antony Chazapis
        # Examples follow.
876 b0a2d1a6 Antony Chazapis
        # if True in [False or ',' in x for x in r]:
877 b0a2d1a6 Antony Chazapis
        #     raise ValueError('Bad characters in read permissions')
878 b0a2d1a6 Antony Chazapis
        # if True in [False or ',' in x for x in w]:
879 b0a2d1a6 Antony Chazapis
        #     raise ValueError('Bad characters in write permissions')
880 b0a2d1a6 Antony Chazapis
        return r, w
881 3436eeb0 Antony Chazapis
    
882 3436eeb0 Antony Chazapis
    def _get_permissions(self, path):
883 a6eb13e9 Antony Chazapis
        # Check for permissions at path or above.
884 b0a2d1a6 Antony Chazapis
        sql = 'select name, op, user from permissions where ? like name || ?'
885 a6eb13e9 Antony Chazapis
        c = self.con.execute(sql, (path, '%'))
886 b0a2d1a6 Antony Chazapis
        name = path
887 b0a2d1a6 Antony Chazapis
        perms = {} # Return nothing, if nothing is set.
888 b0a2d1a6 Antony Chazapis
        for row in c.fetchall():
889 b0a2d1a6 Antony Chazapis
            name = row[0]
890 b0a2d1a6 Antony Chazapis
            if row[1] not in perms:
891 b0a2d1a6 Antony Chazapis
                perms[row[1]] = []
892 b0a2d1a6 Antony Chazapis
            perms[row[1]].append(row[2])
893 b0a2d1a6 Antony Chazapis
        return name, perms
894 3436eeb0 Antony Chazapis
    
895 3436eeb0 Antony Chazapis
    def _put_permissions(self, path, r, w):
896 b0a2d1a6 Antony Chazapis
        sql = 'delete from permissions where name = ?'
897 b0a2d1a6 Antony Chazapis
        self.con.execute(sql, (path,))
898 b0a2d1a6 Antony Chazapis
        sql = 'insert into permissions (name, op, user) values (?, ?, ?)'
899 b0a2d1a6 Antony Chazapis
        if r:
900 b0a2d1a6 Antony Chazapis
            self.con.executemany(sql, [(path, 'read', x) for x in r])
901 b0a2d1a6 Antony Chazapis
        if w:
902 b0a2d1a6 Antony Chazapis
            self.con.executemany(sql, [(path, 'write', x) for x in w])
903 3436eeb0 Antony Chazapis
    
904 e0f916bb Antony Chazapis
    def _get_public(self, path):
905 e0f916bb Antony Chazapis
        sql = 'select name from public where name = ?'
906 e0f916bb Antony Chazapis
        c = self.con.execute(sql, (path,))
907 e0f916bb Antony Chazapis
        row = c.fetchone()
908 e0f916bb Antony Chazapis
        if not row:
909 e0f916bb Antony Chazapis
            return False
910 e0f916bb Antony Chazapis
        return True
911 e0f916bb Antony Chazapis
    
912 e0f916bb Antony Chazapis
    def _put_public(self, path, public):
913 e0f916bb Antony Chazapis
        if not public:
914 e0f916bb Antony Chazapis
            sql = 'delete from public where name = ?'
915 e0f916bb Antony Chazapis
        else:
916 e0f916bb Antony Chazapis
            sql = 'insert or replace into public (name) values (?)'
917 e0f916bb Antony Chazapis
        self.con.execute(sql, (path,))
918 e0f916bb Antony Chazapis
    
919 84846143 Antony Chazapis
    def _del_sharing(self, path):
920 84846143 Antony Chazapis
        sql = 'delete from permissions where name = ?'
921 84846143 Antony Chazapis
        self.con.execute(sql, (path,))
922 84846143 Antony Chazapis
        sql = 'delete from public where name = ?'
923 84846143 Antony Chazapis
        self.con.execute(sql, (path,))
924 17629fea Antony Chazapis
    
925 17629fea Antony Chazapis
    def _is_allowed(self, user, account, container, name, op='read'):
926 17629fea Antony Chazapis
        if user == account:
927 17629fea Antony Chazapis
            return True
928 17629fea Antony Chazapis
        path = os.path.join(account, container, name)
929 17629fea Antony Chazapis
        if op == 'read' and self._get_public(path):
930 17629fea Antony Chazapis
            return True
931 17629fea Antony Chazapis
        perm_path, perms = self._get_permissions(path)
932 17629fea Antony Chazapis
        
933 17629fea Antony Chazapis
        # Expand groups.
934 17629fea Antony Chazapis
        for x in ('read', 'write'):
935 17629fea Antony Chazapis
            g_perms = set()
936 17629fea Antony Chazapis
            for y in perms.get(x, []):
937 17629fea Antony Chazapis
                if ':' in y:
938 17629fea Antony Chazapis
                    g_account, g_name = y.split(':', 1)
939 17629fea Antony Chazapis
                    groups = self._get_groups(g_account)
940 17629fea Antony Chazapis
                    if g_name in groups:
941 17629fea Antony Chazapis
                        g_perms.update(groups[g_name])
942 17629fea Antony Chazapis
                else:
943 17629fea Antony Chazapis
                    g_perms.add(y)
944 17629fea Antony Chazapis
            perms[x] = g_perms
945 17629fea Antony Chazapis
        
946 17629fea Antony Chazapis
        if op == 'read' and ('*' in perms['read'] or user in perms['read']):
947 17629fea Antony Chazapis
            return True
948 17629fea Antony Chazapis
        if '*' in perms['write'] or user in perms['write']:
949 17629fea Antony Chazapis
            return True
950 17629fea Antony Chazapis
        return False
951 17629fea Antony Chazapis
    
952 17629fea Antony Chazapis
    def _can_read(self, user, account, container, name):
953 17629fea Antony Chazapis
        if not self._is_allowed(user, account, container, name, 'read'):
954 17629fea Antony Chazapis
            raise NotAllowedError
955 17629fea Antony Chazapis
    
956 17629fea Antony Chazapis
    def _can_write(self, user, account, container, name):
957 17629fea Antony Chazapis
        if not self._is_allowed(user, account, container, name, 'write'):
958 17629fea Antony Chazapis
            raise NotAllowedError
959 17629fea Antony Chazapis
    
960 17629fea Antony Chazapis
    def _allowed_paths(self, user, prefix=None):
961 17629fea Antony Chazapis
        sql = '''select distinct name from permissions where (user = ?
962 17629fea Antony Chazapis
                    or user in (select account || ':' || gname from groups where user = ?))'''
963 17629fea Antony Chazapis
        param = (user, user)
964 17629fea Antony Chazapis
        if prefix:
965 17629fea Antony Chazapis
            sql += ' and name like ?'
966 17629fea Antony Chazapis
            param += (prefix + '/%',)
967 17629fea Antony Chazapis
        c = self.con.execute(sql, param)
968 17629fea Antony Chazapis
        return [x[0] for x in c.fetchall()]
969 17629fea Antony Chazapis
    
970 17629fea Antony Chazapis
    def _allowed_accounts(self, user):
971 17629fea Antony Chazapis
        allow = set()
972 17629fea Antony Chazapis
        for path in self._allowed_paths(user):
973 17629fea Antony Chazapis
            allow.add(path.split('/', 1)[0])
974 17629fea Antony Chazapis
        return sorted(allow)
975 17629fea Antony Chazapis
    
976 17629fea Antony Chazapis
    def _allowed_containers(self, user, account):
977 17629fea Antony Chazapis
        allow = set()
978 17629fea Antony Chazapis
        for path in self._allowed_paths(user, account):
979 17629fea Antony Chazapis
            allow.add(path.split('/', 2)[1])
980 17629fea Antony Chazapis
        return sorted(allow)
981 b18ef3ad Antony Chazapis
    
982 b18ef3ad Antony Chazapis
    def _shared_paths(self, prefix):
983 b18ef3ad Antony Chazapis
        sql = 'select distinct name from permissions where name like ?'
984 b18ef3ad Antony Chazapis
        c = self.con.execute(sql, (prefix + '/%',))
985 b18ef3ad Antony Chazapis
        return [x[0] for x in c.fetchall()]