Statistics
| Branch: | Tag: | Revision:

root / pithos / backends / simple.py @ e0f916bb

History | View | Annotate | Download (35.3 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 types
39 b956618e Antony Chazapis
import hashlib
40 b956618e Antony Chazapis
import shutil
41 22dab079 Antony Chazapis
import pickle
42 b956618e Antony Chazapis
43 cca6c617 Antony Chazapis
from base import NotAllowedError, BaseBackend
44 b956618e Antony Chazapis
45 b956618e Antony Chazapis
46 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
47 b956618e Antony Chazapis
48 b956618e Antony Chazapis
49 b956618e Antony Chazapis
class SimpleBackend(BaseBackend):
50 22dab079 Antony Chazapis
    """A simple backend.
51 22dab079 Antony Chazapis
    
52 22dab079 Antony Chazapis
    Uses SQLite for storage.
53 22dab079 Antony Chazapis
    """
54 22dab079 Antony Chazapis
    
55 58a6c894 Antony Chazapis
    # TODO: Automatic/manual clean-up after a time interval.
56 58a6c894 Antony Chazapis
    
57 22dab079 Antony Chazapis
    def __init__(self, db):
58 22dab079 Antony Chazapis
        self.hash_algorithm = 'sha1'
59 22dab079 Antony Chazapis
        self.block_size = 128 * 1024 # 128KB
60 b956618e Antony Chazapis
        
61 3ab38c43 Antony Chazapis
        self.default_policy = {'quota': 0, 'versioning': 'auto'}
62 3ab38c43 Antony Chazapis
        
63 22dab079 Antony Chazapis
        basepath = os.path.split(db)[0]
64 22dab079 Antony Chazapis
        if basepath and not os.path.exists(basepath):
65 b956618e Antony Chazapis
            os.makedirs(basepath)
66 22dab079 Antony Chazapis
        
67 49350be7 Giorgos Verigakis
        self.con = sqlite3.connect(db, check_same_thread=False)
68 58a6c894 Antony Chazapis
        sql = '''create table if not exists versions (
69 58a6c894 Antony Chazapis
                    version_id integer primary key,
70 58a6c894 Antony Chazapis
                    name text,
71 104626e3 Antony Chazapis
                    user text,
72 58a6c894 Antony Chazapis
                    tstamp datetime default current_timestamp,
73 58a6c894 Antony Chazapis
                    size integer default 0,
74 58a6c894 Antony Chazapis
                    hide integer default 0)'''
75 b956618e Antony Chazapis
        self.con.execute(sql)
76 b956618e Antony Chazapis
        sql = '''create table if not exists metadata (
77 58a6c894 Antony Chazapis
                    version_id integer, key text, value text, primary key (version_id, key))'''
78 22dab079 Antony Chazapis
        self.con.execute(sql)
79 22dab079 Antony Chazapis
        sql = '''create table if not exists blocks (
80 22dab079 Antony Chazapis
                    block_id text, data blob, primary key (block_id))'''
81 22dab079 Antony Chazapis
        self.con.execute(sql)
82 22dab079 Antony Chazapis
        sql = '''create table if not exists hashmaps (
83 58a6c894 Antony Chazapis
                    version_id integer, pos integer, block_id text, primary key (version_id, pos))'''
84 b956618e Antony Chazapis
        self.con.execute(sql)
85 02c0c3fa Antony Chazapis
        sql = '''create table if not exists groups (
86 02c0c3fa Antony Chazapis
                    account text, name text, users text, primary key (account, name))'''
87 02c0c3fa Antony Chazapis
        self.con.execute(sql)
88 e0f916bb Antony Chazapis
        sql = '''create table if not exists policy (
89 e0f916bb Antony Chazapis
                    name text, key text, value text, primary key (name, key))'''
90 e0f916bb Antony Chazapis
        self.con.execute(sql)
91 3436eeb0 Antony Chazapis
        sql = '''create table if not exists permissions (
92 3436eeb0 Antony Chazapis
                    name text, read text, write text, primary key (name))'''
93 3436eeb0 Antony Chazapis
        self.con.execute(sql)
94 e0f916bb Antony Chazapis
        sql = '''create table if not exists public (
95 e0f916bb Antony Chazapis
                    name text, primary key (name))'''
96 02c0c3fa Antony Chazapis
        self.con.execute(sql)
97 b956618e Antony Chazapis
        self.con.commit()
98 b956618e Antony Chazapis
    
99 83dd59c5 Antony Chazapis
    def get_account_meta(self, user, account, until=None):
100 b956618e Antony Chazapis
        """Return a dictionary with the account metadata."""
101 b956618e Antony Chazapis
        
102 58a6c894 Antony Chazapis
        logger.debug("get_account_meta: %s %s", account, until)
103 cca6c617 Antony Chazapis
        if user != account:
104 cca6c617 Antony Chazapis
            raise NotAllowedError
105 58a6c894 Antony Chazapis
        try:
106 58a6c894 Antony Chazapis
            version_id, mtime = self._get_accountinfo(account, until)
107 58a6c894 Antony Chazapis
        except NameError:
108 58a6c894 Antony Chazapis
            version_id = None
109 60be837c Antony Chazapis
            mtime = 0
110 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(account, until)
111 31a1c80d Antony Chazapis
        if mtime > tstamp:
112 31a1c80d Antony Chazapis
            tstamp = mtime
113 58a6c894 Antony Chazapis
        if until is None:
114 58a6c894 Antony Chazapis
            modified = tstamp
115 58a6c894 Antony Chazapis
        else:
116 58a6c894 Antony Chazapis
            modified = self._get_pathstats(account)[2] # Overall last modification
117 31a1c80d Antony Chazapis
            if mtime > modified:
118 31a1c80d Antony Chazapis
                modified = mtime
119 22dab079 Antony Chazapis
        
120 22dab079 Antony Chazapis
        # Proper count.
121 58a6c894 Antony Chazapis
        sql = 'select count(name) from (%s) where name glob ? and not name glob ?'
122 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
123 22dab079 Antony Chazapis
        c = self.con.execute(sql, (account + '/*', account + '/*/*'))
124 22dab079 Antony Chazapis
        row = c.fetchone()
125 22dab079 Antony Chazapis
        count = row[0]
126 22dab079 Antony Chazapis
        
127 58a6c894 Antony Chazapis
        meta = self._get_metadata(account, version_id)
128 22dab079 Antony Chazapis
        meta.update({'name': account, 'count': count, 'bytes': bytes})
129 58a6c894 Antony Chazapis
        if modified:
130 58a6c894 Antony Chazapis
            meta.update({'modified': modified})
131 58a6c894 Antony Chazapis
        if until is not None:
132 58a6c894 Antony Chazapis
            meta.update({'until_timestamp': tstamp})
133 b956618e Antony Chazapis
        return meta
134 b956618e Antony Chazapis
    
135 83dd59c5 Antony Chazapis
    def update_account_meta(self, user, account, meta, replace=False):
136 b956618e Antony Chazapis
        """Update the metadata associated with the account."""
137 b956618e Antony Chazapis
        
138 22dab079 Antony Chazapis
        logger.debug("update_account_meta: %s %s %s", account, meta, replace)
139 cca6c617 Antony Chazapis
        if user != account:
140 cca6c617 Antony Chazapis
            raise NotAllowedError
141 104626e3 Antony Chazapis
        self._put_metadata(user, account, meta, replace)
142 58a6c894 Antony Chazapis
    
143 02c0c3fa Antony Chazapis
    def get_account_groups(self, user, account):
144 02c0c3fa Antony Chazapis
        """Return a dictionary with the user groups defined for this account."""
145 58a6c894 Antony Chazapis
        
146 02c0c3fa Antony Chazapis
        logger.debug("get_account_groups: %s", account)
147 cca6c617 Antony Chazapis
        if user != account:
148 cca6c617 Antony Chazapis
            raise NotAllowedError
149 02c0c3fa Antony Chazapis
        return self._get_groups(account)
150 b956618e Antony Chazapis
    
151 02c0c3fa Antony Chazapis
    def update_account_groups(self, user, account, groups, replace=False):
152 02c0c3fa Antony Chazapis
        """Update the groups associated with the account."""
153 b956618e Antony Chazapis
        
154 02c0c3fa Antony Chazapis
        logger.debug("update_account_groups: %s %s %s", account, groups, replace)
155 cca6c617 Antony Chazapis
        if user != account:
156 cca6c617 Antony Chazapis
            raise NotAllowedError
157 02c0c3fa Antony Chazapis
        for k, v in groups.iteritems():
158 02c0c3fa Antony Chazapis
            if True in [False or ',' in x for x in v]:
159 02c0c3fa Antony Chazapis
                raise ValueError('Bad characters in groups')
160 02c0c3fa Antony Chazapis
        if replace:
161 02c0c3fa Antony Chazapis
            sql = 'delete from groups where account = ?'
162 02c0c3fa Antony Chazapis
            self.con.execute(sql, (account,))
163 02c0c3fa Antony Chazapis
        for k, v in groups.iteritems():
164 02c0c3fa Antony Chazapis
            if len(v) == 0:
165 02c0c3fa Antony Chazapis
                if not replace:
166 02c0c3fa Antony Chazapis
                    sql = 'delete from groups where account = ? and name = ?'
167 02c0c3fa Antony Chazapis
                    self.con.execute(sql, (account, k))
168 02c0c3fa Antony Chazapis
            else:
169 02c0c3fa Antony Chazapis
                sql = 'insert or replace into groups (account, name, users) values (?, ?, ?)'
170 02c0c3fa Antony Chazapis
                self.con.execute(sql, (account, k, ','.join(v)))
171 02c0c3fa Antony Chazapis
        self.con.commit()
172 b956618e Antony Chazapis
    
173 02c0c3fa Antony Chazapis
    def delete_account(self, user, account):
174 02c0c3fa Antony Chazapis
        """Delete the account with the given name."""
175 b956618e Antony Chazapis
        
176 02c0c3fa Antony Chazapis
        logger.debug("delete_account: %s", account)
177 cca6c617 Antony Chazapis
        if user != account:
178 cca6c617 Antony Chazapis
            raise NotAllowedError
179 02c0c3fa Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(account)
180 22dab079 Antony Chazapis
        if count > 0:
181 02c0c3fa Antony Chazapis
            raise IndexError('Account is not empty')
182 02c0c3fa Antony Chazapis
        self._del_path(account) # Point of no return.
183 02c0c3fa Antony Chazapis
    
184 02c0c3fa Antony Chazapis
    def list_containers(self, user, account, marker=None, limit=10000, until=None):
185 02c0c3fa Antony Chazapis
        """Return a list of containers existing under an account."""
186 02c0c3fa Antony Chazapis
        
187 02c0c3fa Antony Chazapis
        logger.debug("list_containers: %s %s %s %s", account, marker, limit, until)
188 02c0c3fa Antony Chazapis
        if user != account:
189 02c0c3fa Antony Chazapis
            raise NotAllowedError
190 02c0c3fa Antony Chazapis
        return self._list_objects(account, '', '/', marker, limit, False, [], until)
191 b956618e Antony Chazapis
    
192 83dd59c5 Antony Chazapis
    def get_container_meta(self, user, account, container, until=None):
193 b956618e Antony Chazapis
        """Return a dictionary with the container metadata."""
194 b956618e Antony Chazapis
        
195 58a6c894 Antony Chazapis
        logger.debug("get_container_meta: %s %s %s", account, container, until)
196 cca6c617 Antony Chazapis
        if user != account:
197 cca6c617 Antony Chazapis
            raise NotAllowedError
198 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
199 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(path, until)
200 31a1c80d Antony Chazapis
        if mtime > tstamp:
201 31a1c80d Antony Chazapis
            tstamp = mtime
202 58a6c894 Antony Chazapis
        if until is None:
203 58a6c894 Antony Chazapis
            modified = tstamp
204 58a6c894 Antony Chazapis
        else:
205 31a1c80d Antony Chazapis
            modified = self._get_pathstats(path)[2] # Overall last modification
206 31a1c80d Antony Chazapis
            if mtime > modified:
207 31a1c80d Antony Chazapis
                modified = mtime
208 58a6c894 Antony Chazapis
        
209 58a6c894 Antony Chazapis
        meta = self._get_metadata(path, version_id)
210 58a6c894 Antony Chazapis
        meta.update({'name': container, 'count': count, 'bytes': bytes, 'modified': modified})
211 58a6c894 Antony Chazapis
        if until is not None:
212 58a6c894 Antony Chazapis
            meta.update({'until_timestamp': tstamp})
213 b956618e Antony Chazapis
        return meta
214 b956618e Antony Chazapis
    
215 83dd59c5 Antony Chazapis
    def update_container_meta(self, user, account, container, meta, replace=False):
216 b956618e Antony Chazapis
        """Update the metadata associated with the container."""
217 b956618e Antony Chazapis
        
218 58a6c894 Antony Chazapis
        logger.debug("update_container_meta: %s %s %s %s", account, container, meta, replace)
219 cca6c617 Antony Chazapis
        if user != account:
220 cca6c617 Antony Chazapis
            raise NotAllowedError
221 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container)
222 104626e3 Antony Chazapis
        self._put_metadata(user, path, meta, replace)
223 b956618e Antony Chazapis
    
224 02c0c3fa Antony Chazapis
    def get_container_policy(self, user, account, container):
225 02c0c3fa Antony Chazapis
        """Return a dictionary with the container policy."""
226 02c0c3fa Antony Chazapis
        
227 02c0c3fa Antony Chazapis
        logger.debug("get_container_policy: %s %s", account, container)
228 3ab38c43 Antony Chazapis
        if user != account:
229 3ab38c43 Antony Chazapis
            raise NotAllowedError
230 3ab38c43 Antony Chazapis
        path = self._get_containerinfo(account, container)[0]
231 3ab38c43 Antony Chazapis
        return self._get_policy(path)
232 02c0c3fa Antony Chazapis
    
233 02c0c3fa Antony Chazapis
    def update_container_policy(self, user, account, container, policy, replace=False):
234 02c0c3fa Antony Chazapis
        """Update the policy associated with the account."""
235 02c0c3fa Antony Chazapis
        
236 02c0c3fa Antony Chazapis
        logger.debug("update_container_policy: %s %s %s %s", account, container, policy, replace)
237 3ab38c43 Antony Chazapis
        if user != account:
238 3ab38c43 Antony Chazapis
            raise NotAllowedError
239 3ab38c43 Antony Chazapis
        path = self._get_containerinfo(account, container)[0]
240 3ab38c43 Antony Chazapis
        self._check_policy(policy)
241 3ab38c43 Antony Chazapis
        if replace:
242 3ab38c43 Antony Chazapis
            for k, v in self.default_policy.iteritems():
243 3ab38c43 Antony Chazapis
                if k not in policy:
244 3ab38c43 Antony Chazapis
                    policy[k] = v
245 3ab38c43 Antony Chazapis
        for k, v in policy.iteritems():
246 3ab38c43 Antony Chazapis
            sql = 'insert or replace into policy (name, key, value) values (?, ?, ?)'
247 3ab38c43 Antony Chazapis
            self.con.execute(sql, (path, k, v))
248 3ab38c43 Antony Chazapis
        self.con.commit()
249 02c0c3fa Antony Chazapis
    
250 02c0c3fa Antony Chazapis
    def put_container(self, user, account, container, policy=None):
251 02c0c3fa Antony Chazapis
        """Create a new container with the given name."""
252 02c0c3fa Antony Chazapis
        
253 02c0c3fa Antony Chazapis
        logger.debug("put_container: %s %s %s", account, container, policy)
254 02c0c3fa Antony Chazapis
        if user != account:
255 02c0c3fa Antony Chazapis
            raise NotAllowedError
256 02c0c3fa Antony Chazapis
        try:
257 02c0c3fa Antony Chazapis
            path, version_id, mtime = self._get_containerinfo(account, container)
258 02c0c3fa Antony Chazapis
        except NameError:
259 3ab38c43 Antony Chazapis
            pass
260 02c0c3fa Antony Chazapis
        else:
261 02c0c3fa Antony Chazapis
            raise NameError('Container already exists')
262 3ab38c43 Antony Chazapis
        if policy:
263 3ab38c43 Antony Chazapis
            self._check_policy(policy)
264 3ab38c43 Antony Chazapis
        path = os.path.join(account, container)
265 3ab38c43 Antony Chazapis
        version_id = self._put_version(path, user)
266 3ab38c43 Antony Chazapis
        for k, v in self.default_policy.iteritems():
267 3ab38c43 Antony Chazapis
            if k not in policy:
268 3ab38c43 Antony Chazapis
                policy[k] = v
269 3ab38c43 Antony Chazapis
        for k, v in policy.iteritems():
270 3ab38c43 Antony Chazapis
            sql = 'insert or replace into policy (name, key, value) values (?, ?, ?)'
271 3ab38c43 Antony Chazapis
            self.con.execute(sql, (path, k, v))
272 3ab38c43 Antony Chazapis
        self.con.commit()
273 02c0c3fa Antony Chazapis
    
274 02c0c3fa Antony Chazapis
    def delete_container(self, user, account, container):
275 02c0c3fa Antony Chazapis
        """Delete the container with the given name."""
276 02c0c3fa Antony Chazapis
        
277 02c0c3fa Antony Chazapis
        logger.debug("delete_container: %s %s", account, container)
278 02c0c3fa Antony Chazapis
        if user != account:
279 02c0c3fa Antony Chazapis
            raise NotAllowedError
280 02c0c3fa Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container)
281 02c0c3fa Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(path)
282 02c0c3fa Antony Chazapis
        if count > 0:
283 02c0c3fa Antony Chazapis
            raise IndexError('Container is not empty')
284 02c0c3fa Antony Chazapis
        self._del_path(path) # Point of no return.
285 02c0c3fa Antony Chazapis
        self._copy_version(user, account, account, True, True) # New account version.
286 02c0c3fa Antony Chazapis
    
287 83dd59c5 Antony Chazapis
    def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None):
288 b956618e Antony Chazapis
        """Return a list of objects existing under a container."""
289 b956618e Antony Chazapis
        
290 58a6c894 Antony Chazapis
        logger.debug("list_objects: %s %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit, until)
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, until)
294 58a6c894 Antony Chazapis
        return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, until)
295 22dab079 Antony Chazapis
    
296 83dd59c5 Antony Chazapis
    def list_object_meta(self, user, account, container, until=None):
297 22dab079 Antony Chazapis
        """Return a list with all the container's object meta keys."""
298 b956618e Antony Chazapis
        
299 58a6c894 Antony Chazapis
        logger.debug("list_object_meta: %s %s %s", account, container, until)
300 cca6c617 Antony Chazapis
        if user != account:
301 cca6c617 Antony Chazapis
            raise NotAllowedError
302 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
303 58a6c894 Antony Chazapis
        sql = '''select distinct m.key from (%s) o, metadata m
304 58a6c894 Antony Chazapis
                    where m.version_id = o.version_id and o.name like ?'''
305 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
306 22dab079 Antony Chazapis
        c = self.con.execute(sql, (path + '/%',))
307 22dab079 Antony Chazapis
        return [x[0] for x in c.fetchall()]
308 b956618e Antony Chazapis
    
309 83dd59c5 Antony Chazapis
    def get_object_meta(self, user, account, container, name, version=None):
310 b956618e Antony Chazapis
        """Return a dictionary with the object metadata."""
311 b956618e Antony Chazapis
        
312 58a6c894 Antony Chazapis
        logger.debug("get_object_meta: %s %s %s %s", account, container, name, version)
313 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
314 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name, version)
315 58a6c894 Antony Chazapis
        if version is None:
316 58a6c894 Antony Chazapis
            modified = mtime
317 58a6c894 Antony Chazapis
        else:
318 104626e3 Antony Chazapis
            modified = self._get_version(path, version)[2] # Overall last modification
319 58a6c894 Antony Chazapis
        
320 58a6c894 Antony Chazapis
        meta = self._get_metadata(path, version_id)
321 104626e3 Antony Chazapis
        meta.update({'name': name, 'bytes': size})
322 104626e3 Antony Chazapis
        meta.update({'version': version_id, 'version_timestamp': mtime})
323 104626e3 Antony Chazapis
        meta.update({'modified': modified, 'modified_by': muser})
324 b956618e Antony Chazapis
        return meta
325 b956618e Antony Chazapis
    
326 83dd59c5 Antony Chazapis
    def update_object_meta(self, user, account, container, name, meta, replace=False):
327 b956618e Antony Chazapis
        """Update the metadata associated with the object."""
328 b956618e Antony Chazapis
        
329 22dab079 Antony Chazapis
        logger.debug("update_object_meta: %s %s %s %s %s", account, container, name, meta, replace)
330 cca6c617 Antony Chazapis
        self._can_write(user, account, container, name)
331 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name)
332 104626e3 Antony Chazapis
        self._put_metadata(user, path, meta, replace)
333 b956618e Antony Chazapis
    
334 3436eeb0 Antony Chazapis
    def get_object_permissions(self, user, account, container, name):
335 cca6c617 Antony Chazapis
        """Return the path from which this object gets its permissions from,\
336 cca6c617 Antony Chazapis
        along with a dictionary containing the permissions."""
337 3436eeb0 Antony Chazapis
        
338 3436eeb0 Antony Chazapis
        logger.debug("get_object_permissions: %s %s %s", account, container, name)
339 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
340 3436eeb0 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
341 cca6c617 Antony Chazapis
        return self._get_permissions(path)
342 3436eeb0 Antony Chazapis
    
343 3436eeb0 Antony Chazapis
    def update_object_permissions(self, user, account, container, name, permissions):
344 3436eeb0 Antony Chazapis
        """Update the permissions associated with the object."""
345 3436eeb0 Antony Chazapis
        
346 3436eeb0 Antony Chazapis
        logger.debug("update_object_permissions: %s %s %s %s", account, container, name, permissions)
347 cca6c617 Antony Chazapis
        if user != account:
348 cca6c617 Antony Chazapis
            raise NotAllowedError
349 3436eeb0 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
350 3436eeb0 Antony Chazapis
        r, w = self._check_permissions(path, permissions)
351 3436eeb0 Antony Chazapis
        self._put_permissions(path, r, w)
352 3436eeb0 Antony Chazapis
    
353 02c0c3fa Antony Chazapis
    def get_object_public(self, user, account, container, name):
354 02c0c3fa Antony Chazapis
        """Return the public URL of the object if applicable."""
355 02c0c3fa Antony Chazapis
        
356 02c0c3fa Antony Chazapis
        logger.debug("get_object_public: %s %s %s", account, container, name)
357 e0f916bb Antony Chazapis
        self._can_read(user, account, container, name)
358 e0f916bb Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
359 e0f916bb Antony Chazapis
        if self._get_public(path):
360 e0f916bb Antony Chazapis
            return '/public/' + path
361 02c0c3fa Antony Chazapis
        return None
362 02c0c3fa Antony Chazapis
    
363 02c0c3fa Antony Chazapis
    def update_object_public(self, user, account, container, name, public):
364 02c0c3fa Antony Chazapis
        """Update the public status of the object."""
365 02c0c3fa Antony Chazapis
        
366 02c0c3fa Antony Chazapis
        logger.debug("update_object_public: %s %s %s %s", account, container, name, public)
367 e0f916bb Antony Chazapis
        self._can_write(user, account, container, name)
368 e0f916bb Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
369 e0f916bb Antony Chazapis
        self._put_public(path, public)
370 02c0c3fa Antony Chazapis
    
371 83dd59c5 Antony Chazapis
    def get_object_hashmap(self, user, account, container, name, version=None):
372 22dab079 Antony Chazapis
        """Return the object's size and a list with partial hashes."""
373 b956618e Antony Chazapis
        
374 22dab079 Antony Chazapis
        logger.debug("get_object_hashmap: %s %s %s %s", account, container, name, version)
375 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
376 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name, version)
377 58a6c894 Antony Chazapis
        sql = 'select block_id from hashmaps where version_id = ? order by pos asc'
378 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (version_id,))
379 22dab079 Antony Chazapis
        hashmap = [x[0] for x in c.fetchall()]
380 22dab079 Antony Chazapis
        return size, hashmap
381 22dab079 Antony Chazapis
    
382 cca6c617 Antony Chazapis
    def update_object_hashmap(self, user, account, container, name, size, hashmap, meta={}, replace_meta=False, permissions=None):
383 22dab079 Antony Chazapis
        """Create/update an object with the specified size and partial hashes."""
384 b956618e Antony Chazapis
        
385 cfe6939d Antony Chazapis
        logger.debug("update_object_hashmap: %s %s %s %s %s", account, container, name, size, hashmap)
386 cca6c617 Antony Chazapis
        if permissions is not None and user != account:
387 cca6c617 Antony Chazapis
            raise NotAllowedError
388 cca6c617 Antony Chazapis
        self._can_write(user, account, container, name)
389 76985443 Sofia Papagiannaki
        missing = []
390 76985443 Sofia Papagiannaki
        for i in range(len(hashmap)):
391 76985443 Sofia Papagiannaki
            sql = 'select count(*) from blocks where block_id = ?'
392 76985443 Sofia Papagiannaki
            c = self.con.execute(sql, (hashmap[i],))
393 76985443 Sofia Papagiannaki
            if c.fetchone()[0] == 0:
394 76985443 Sofia Papagiannaki
                missing.append(hashmap[i])
395 76985443 Sofia Papagiannaki
        if missing:
396 76985443 Sofia Papagiannaki
            ie = IndexError()
397 76985443 Sofia Papagiannaki
            ie.data = missing
398 76985443 Sofia Papagiannaki
            raise ie
399 58a6c894 Antony Chazapis
        path = self._get_containerinfo(account, container)[0]
400 58a6c894 Antony Chazapis
        path = os.path.join(path, name)
401 cca6c617 Antony Chazapis
        if permissions is not None:
402 3436eeb0 Antony Chazapis
            r, w = self._check_permissions(path, permissions)
403 104626e3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, path, path, not replace_meta, False)
404 58a6c894 Antony Chazapis
        sql = 'update versions set size = ? where version_id = ?'
405 58a6c894 Antony Chazapis
        self.con.execute(sql, (size, dest_version_id))
406 58a6c894 Antony Chazapis
        # TODO: Check for block_id existence.
407 22dab079 Antony Chazapis
        for i in range(len(hashmap)):
408 22dab079 Antony Chazapis
            sql = 'insert or replace into hashmaps (version_id, pos, block_id) values (?, ?, ?)'
409 58a6c894 Antony Chazapis
            self.con.execute(sql, (dest_version_id, i, hashmap[i]))
410 83dd59c5 Antony Chazapis
        for k, v in meta.iteritems():
411 83dd59c5 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
412 83dd59c5 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
413 cca6c617 Antony Chazapis
        if permissions is not None:
414 3436eeb0 Antony Chazapis
            sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
415 3436eeb0 Antony Chazapis
            self.con.execute(sql, (path, r, w))
416 22dab079 Antony Chazapis
        self.con.commit()
417 22dab079 Antony Chazapis
    
418 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):
419 22dab079 Antony Chazapis
        """Copy an object's data and metadata."""
420 b956618e Antony Chazapis
        
421 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)
422 cca6c617 Antony Chazapis
        if permissions is not None and user != account:
423 cca6c617 Antony Chazapis
            raise NotAllowedError
424 cca6c617 Antony Chazapis
        self._can_read(user, account, src_container, src_name)
425 cca6c617 Antony Chazapis
        self._can_write(user, account, dest_container, dest_name)
426 6d817842 Antony Chazapis
        self._get_containerinfo(account, src_container)
427 58a6c894 Antony Chazapis
        if src_version is None:
428 58a6c894 Antony Chazapis
            src_path = self._get_objectinfo(account, src_container, src_name)[0]
429 22dab079 Antony Chazapis
        else:
430 58a6c894 Antony Chazapis
            src_path = os.path.join(account, src_container, src_name)
431 58a6c894 Antony Chazapis
        dest_path = self._get_containerinfo(account, dest_container)[0]
432 58a6c894 Antony Chazapis
        dest_path = os.path.join(dest_path, dest_name)
433 cca6c617 Antony Chazapis
        if permissions is not None:
434 3436eeb0 Antony Chazapis
            r, w = self._check_permissions(dest_path, permissions)
435 104626e3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, src_path, dest_path, not replace_meta, True, src_version)
436 58a6c894 Antony Chazapis
        for k, v in dest_meta.iteritems():
437 58a6c894 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
438 58a6c894 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
439 cca6c617 Antony Chazapis
        if permissions is not None:
440 3436eeb0 Antony Chazapis
            sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
441 3436eeb0 Antony Chazapis
            self.con.execute(sql, (dest_path, r, w))
442 58a6c894 Antony Chazapis
        self.con.commit()
443 b956618e Antony Chazapis
    
444 cca6c617 Antony Chazapis
    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
445 b956618e Antony Chazapis
        """Move an object's data and metadata."""
446 b956618e Antony Chazapis
        
447 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)
448 3436eeb0 Antony Chazapis
        self.copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
449 83dd59c5 Antony Chazapis
        self.delete_object(user, account, src_container, src_name)
450 b956618e Antony Chazapis
    
451 83dd59c5 Antony Chazapis
    def delete_object(self, user, account, container, name):
452 b956618e Antony Chazapis
        """Delete an object."""
453 b956618e Antony Chazapis
        
454 b956618e Antony Chazapis
        logger.debug("delete_object: %s %s %s", account, container, name)
455 cca6c617 Antony Chazapis
        if user != account:
456 cca6c617 Antony Chazapis
            raise NotAllowedError
457 104626e3 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
458 104626e3 Antony Chazapis
        self._put_version(path, user, 0, 1)
459 3436eeb0 Antony Chazapis
        sql = 'delete from permissions where name = ?'
460 3436eeb0 Antony Chazapis
        self.con.execute(sql, (path,))
461 3436eeb0 Antony Chazapis
        self.con.commit()
462 58a6c894 Antony Chazapis
    
463 83dd59c5 Antony Chazapis
    def list_versions(self, user, account, container, name):
464 83dd59c5 Antony Chazapis
        """Return a list of all (version, version_timestamp) tuples for an object."""
465 58a6c894 Antony Chazapis
        
466 83dd59c5 Antony Chazapis
        logger.debug("list_versions: %s %s %s", account, container, name)
467 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
468 58a6c894 Antony Chazapis
        # This will even show deleted versions.
469 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
470 e8886082 Antony Chazapis
        sql = '''select distinct version_id, strftime('%s', tstamp) from versions where name = ? and hide = 0'''
471 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path,))
472 c9af0703 Antony Chazapis
        return [(int(x[0]), int(x[1])) for x in c.fetchall()]
473 b956618e Antony Chazapis
    
474 22dab079 Antony Chazapis
    def get_block(self, hash):
475 22dab079 Antony Chazapis
        """Return a block's data."""
476 22dab079 Antony Chazapis
        
477 cfe6939d Antony Chazapis
        logger.debug("get_block: %s", hash)
478 22dab079 Antony Chazapis
        c = self.con.execute('select data from blocks where block_id = ?', (hash,))
479 22dab079 Antony Chazapis
        row = c.fetchone()
480 22dab079 Antony Chazapis
        if row:
481 22dab079 Antony Chazapis
            return str(row[0])
482 22dab079 Antony Chazapis
        else:
483 22dab079 Antony Chazapis
            raise NameError('Block does not exist')
484 b956618e Antony Chazapis
    
485 22dab079 Antony Chazapis
    def put_block(self, data):
486 22dab079 Antony Chazapis
        """Create a block and return the hash."""
487 22dab079 Antony Chazapis
        
488 cfe6939d Antony Chazapis
        logger.debug("put_block: %s", len(data))
489 22dab079 Antony Chazapis
        h = hashlib.new(self.hash_algorithm)
490 22dab079 Antony Chazapis
        h.update(data.rstrip('\x00'))
491 22dab079 Antony Chazapis
        hash = h.hexdigest()
492 22dab079 Antony Chazapis
        sql = 'insert or ignore into blocks (block_id, data) values (?, ?)'
493 22dab079 Antony Chazapis
        self.con.execute(sql, (hash, buffer(data)))
494 22dab079 Antony Chazapis
        self.con.commit()
495 22dab079 Antony Chazapis
        return hash
496 22dab079 Antony Chazapis
    
497 22dab079 Antony Chazapis
    def update_block(self, hash, data, offset=0):
498 22dab079 Antony Chazapis
        """Update a known block and return the hash."""
499 22dab079 Antony Chazapis
        
500 cfe6939d Antony Chazapis
        logger.debug("update_block: %s %s %s", hash, len(data), offset)
501 cfe6939d Antony Chazapis
        if offset == 0 and len(data) == self.block_size:
502 cfe6939d Antony Chazapis
            return self.put_block(data)
503 22dab079 Antony Chazapis
        src_data = self.get_block(hash)
504 22dab079 Antony Chazapis
        bs = self.block_size
505 22dab079 Antony Chazapis
        if offset < 0 or offset > bs or offset + len(data) > bs:
506 22dab079 Antony Chazapis
            raise IndexError('Offset or data outside block limits')
507 22dab079 Antony Chazapis
        dest_data = src_data[:offset] + data + src_data[offset + len(data):]
508 22dab079 Antony Chazapis
        return self.put_block(dest_data)
509 b956618e Antony Chazapis
    
510 58a6c894 Antony Chazapis
    def _sql_until(self, until=None):
511 58a6c894 Antony Chazapis
        """Return the sql to get the latest versions until the timestamp given."""
512 58a6c894 Antony Chazapis
        if until is None:
513 58a6c894 Antony Chazapis
            until = int(time.time())
514 58a6c894 Antony Chazapis
        sql = '''select version_id, name, strftime('%s', tstamp) as tstamp, size from versions v
515 58a6c894 Antony Chazapis
                    where version_id = (select max(version_id) from versions
516 58a6c894 Antony Chazapis
                                        where v.name = name and tstamp <= datetime(%s, 'unixepoch'))
517 58a6c894 Antony Chazapis
                    and hide = 0'''
518 58a6c894 Antony Chazapis
        return sql % ('%s', until)
519 58a6c894 Antony Chazapis
    
520 58a6c894 Antony Chazapis
    def _get_pathstats(self, path, until=None):
521 58a6c894 Antony Chazapis
        """Return count and sum of size of everything under path and latest timestamp."""
522 58a6c894 Antony Chazapis
        
523 58a6c894 Antony Chazapis
        sql = 'select count(version_id), total(size), max(tstamp) from (%s) where name like ?'
524 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
525 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path + '/%',))
526 b956618e Antony Chazapis
        row = c.fetchone()
527 58a6c894 Antony Chazapis
        tstamp = row[2] if row[2] is not None else 0
528 58a6c894 Antony Chazapis
        return int(row[0]), int(row[1]), int(tstamp)
529 58a6c894 Antony Chazapis
    
530 58a6c894 Antony Chazapis
    def _get_version(self, path, version=None):
531 cb146cf9 Antony Chazapis
        if version is None:
532 104626e3 Antony Chazapis
            sql = '''select version_id, user, strftime('%s', tstamp), size, hide from versions where name = ?
533 58a6c894 Antony Chazapis
                        order by version_id desc limit 1'''
534 58a6c894 Antony Chazapis
            c = self.con.execute(sql, (path,))
535 58a6c894 Antony Chazapis
            row = c.fetchone()
536 104626e3 Antony Chazapis
            if not row or int(row[4]):
537 58a6c894 Antony Chazapis
                raise NameError('Object does not exist')
538 b956618e Antony Chazapis
        else:
539 104626e3 Antony Chazapis
            # The database (sqlite) will not complain if the version is not an integer.
540 104626e3 Antony Chazapis
            sql = '''select version_id, user, strftime('%s', tstamp), size from versions where name = ?
541 58a6c894 Antony Chazapis
                        and version_id = ?'''
542 58a6c894 Antony Chazapis
            c = self.con.execute(sql, (path, version))
543 58a6c894 Antony Chazapis
            row = c.fetchone()
544 58a6c894 Antony Chazapis
            if not row:
545 58a6c894 Antony Chazapis
                raise IndexError('Version does not exist')
546 104626e3 Antony Chazapis
        return str(row[0]), str(row[1]), int(row[2]), int(row[3])
547 58a6c894 Antony Chazapis
    
548 104626e3 Antony Chazapis
    def _put_version(self, path, user, size=0, hide=0):
549 104626e3 Antony Chazapis
        sql = 'insert into versions (name, user, size, hide) values (?, ?, ?, ?)'
550 104626e3 Antony Chazapis
        id = self.con.execute(sql, (path, user, size, hide)).lastrowid
551 b956618e Antony Chazapis
        self.con.commit()
552 b956618e Antony Chazapis
        return str(id)
553 b956618e Antony Chazapis
    
554 104626e3 Antony Chazapis
    def _copy_version(self, user, src_path, dest_path, copy_meta=True, copy_data=True, src_version=None):
555 58a6c894 Antony Chazapis
        if src_version is not None:
556 104626e3 Antony Chazapis
            src_version_id, muser, mtime, size = self._get_version(src_path, src_version)
557 58a6c894 Antony Chazapis
        else:
558 58a6c894 Antony Chazapis
            # Latest or create from scratch.
559 58a6c894 Antony Chazapis
            try:
560 104626e3 Antony Chazapis
                src_version_id, muser, mtime, size = self._get_version(src_path)
561 58a6c894 Antony Chazapis
            except NameError:
562 58a6c894 Antony Chazapis
                src_version_id = None
563 58a6c894 Antony Chazapis
                size = 0
564 58a6c894 Antony Chazapis
        if not copy_data:
565 58a6c894 Antony Chazapis
            size = 0
566 104626e3 Antony Chazapis
        dest_version_id = self._put_version(dest_path, user, size)
567 58a6c894 Antony Chazapis
        if copy_meta and src_version_id is not None:
568 58a6c894 Antony Chazapis
            sql = 'insert into metadata select %s, key, value from metadata where version_id = ?'
569 58a6c894 Antony Chazapis
            sql = sql % dest_version_id
570 58a6c894 Antony Chazapis
            self.con.execute(sql, (src_version_id,))
571 58a6c894 Antony Chazapis
        if copy_data and src_version_id is not None:
572 58a6c894 Antony Chazapis
            sql = 'insert into hashmaps select %s, pos, block_id from hashmaps where version_id = ?'
573 58a6c894 Antony Chazapis
            sql = sql % dest_version_id
574 58a6c894 Antony Chazapis
            self.con.execute(sql, (src_version_id,))
575 58a6c894 Antony Chazapis
        self.con.commit()
576 58a6c894 Antony Chazapis
        return src_version_id, dest_version_id
577 58a6c894 Antony Chazapis
    
578 58a6c894 Antony Chazapis
    def _get_versioninfo(self, account, container, name, until=None):
579 58a6c894 Antony Chazapis
        """Return path, latest version, associated timestamp and size until the timestamp given."""
580 58a6c894 Antony Chazapis
        
581 58a6c894 Antony Chazapis
        p = (account, container, name)
582 22dab079 Antony Chazapis
        try:
583 58a6c894 Antony Chazapis
            p = p[:p.index(None)]
584 58a6c894 Antony Chazapis
        except ValueError:
585 58a6c894 Antony Chazapis
            pass
586 58a6c894 Antony Chazapis
        path = os.path.join(*p)
587 58a6c894 Antony Chazapis
        sql = '''select version_id, tstamp, size from (%s) where name = ?'''
588 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
589 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path,))
590 58a6c894 Antony Chazapis
        row = c.fetchone()
591 58a6c894 Antony Chazapis
        if row is None:
592 58a6c894 Antony Chazapis
            raise NameError('Path does not exist')
593 58a6c894 Antony Chazapis
        return path, str(row[0]), int(row[1]), int(row[2])
594 58a6c894 Antony Chazapis
    
595 58a6c894 Antony Chazapis
    def _get_accountinfo(self, account, until=None):
596 58a6c894 Antony Chazapis
        try:
597 58a6c894 Antony Chazapis
            path, version_id, mtime, size = self._get_versioninfo(account, None, None, until)
598 58a6c894 Antony Chazapis
            return version_id, mtime
599 58a6c894 Antony Chazapis
        except:
600 58a6c894 Antony Chazapis
            raise NameError('Account does not exist')
601 58a6c894 Antony Chazapis
    
602 58a6c894 Antony Chazapis
    def _get_containerinfo(self, account, container, until=None):
603 58a6c894 Antony Chazapis
        try:
604 58a6c894 Antony Chazapis
            path, version_id, mtime, size = self._get_versioninfo(account, container, None, until)
605 58a6c894 Antony Chazapis
            return path, version_id, mtime
606 58a6c894 Antony Chazapis
        except:
607 22dab079 Antony Chazapis
            raise NameError('Container does not exist')
608 22dab079 Antony Chazapis
    
609 22dab079 Antony Chazapis
    def _get_objectinfo(self, account, container, name, version=None):
610 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
611 104626e3 Antony Chazapis
        version_id, muser, mtime, size = self._get_version(path, version)
612 104626e3 Antony Chazapis
        return path, version_id, muser, mtime, size
613 22dab079 Antony Chazapis
    
614 58a6c894 Antony Chazapis
    def _get_metadata(self, path, version):
615 58a6c894 Antony Chazapis
        sql = 'select key, value from metadata where version_id = ?'
616 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (version,))
617 58a6c894 Antony Chazapis
        return dict(c.fetchall())
618 58a6c894 Antony Chazapis
    
619 104626e3 Antony Chazapis
    def _put_metadata(self, user, path, meta, replace=False):
620 58a6c894 Antony Chazapis
        """Create a new version and store metadata."""
621 58a6c894 Antony Chazapis
        
622 104626e3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, path, path, not replace, True)
623 58a6c894 Antony Chazapis
        for k, v in meta.iteritems():
624 a6eb13e9 Antony Chazapis
            if not replace and v == '':
625 a6eb13e9 Antony Chazapis
                sql = 'delete from metadata where version_id = ? and key = ?'
626 a6eb13e9 Antony Chazapis
                self.con.execute(sql, (dest_version_id, k))
627 a6eb13e9 Antony Chazapis
            else:
628 a6eb13e9 Antony Chazapis
                sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
629 a6eb13e9 Antony Chazapis
                self.con.execute(sql, (dest_version_id, k, v))
630 58a6c894 Antony Chazapis
        self.con.commit()
631 58a6c894 Antony Chazapis
    
632 02c0c3fa Antony Chazapis
    def _get_groups(self, account):
633 02c0c3fa Antony Chazapis
        sql = 'select name, users from groups where account = ?'
634 02c0c3fa Antony Chazapis
        c = self.con.execute(sql, (account,))
635 02c0c3fa Antony Chazapis
        return dict([(x[0], x[1].split(',')) for x in c.fetchall()])
636 02c0c3fa Antony Chazapis
    
637 3ab38c43 Antony Chazapis
    def _check_policy(self, policy):
638 3ab38c43 Antony Chazapis
        for k in policy.keys():
639 3ab38c43 Antony Chazapis
            if policy[k] == '':
640 3ab38c43 Antony Chazapis
                policy[k] = self.default_policy.get(k)
641 3ab38c43 Antony Chazapis
        for k, v in policy.iteritems():
642 3ab38c43 Antony Chazapis
            if k == 'quota':
643 3ab38c43 Antony Chazapis
                q = int(v) # May raise ValueError.
644 3ab38c43 Antony Chazapis
                if q < 0:
645 3ab38c43 Antony Chazapis
                    raise ValueError
646 3ab38c43 Antony Chazapis
            elif k == 'versioning':
647 3ab38c43 Antony Chazapis
                if v not in ['auto', 'manual', 'none']:
648 3ab38c43 Antony Chazapis
                    raise ValueError
649 3ab38c43 Antony Chazapis
            else:
650 3ab38c43 Antony Chazapis
                raise ValueError
651 3ab38c43 Antony Chazapis
    
652 3ab38c43 Antony Chazapis
    def _get_policy(self, path):
653 3ab38c43 Antony Chazapis
        sql = 'select key, value from policy where name = ?'
654 3ab38c43 Antony Chazapis
        c = self.con.execute(sql, (path,))
655 3ab38c43 Antony Chazapis
        return dict(c.fetchall())
656 3ab38c43 Antony Chazapis
    
657 cca6c617 Antony Chazapis
    def _is_allowed(self, user, account, container, name, op='read'):
658 cca6c617 Antony Chazapis
        if user == account:
659 cca6c617 Antony Chazapis
            return True
660 cca6c617 Antony Chazapis
        path = os.path.join(account, container, name)
661 e0f916bb Antony Chazapis
        if op == 'read' and self._get_public(path):
662 e0f916bb Antony Chazapis
            return True
663 cca6c617 Antony Chazapis
        perm_path, perms = self._get_permissions(path)
664 02c0c3fa Antony Chazapis
        
665 02c0c3fa Antony Chazapis
        # Expand groups.
666 02c0c3fa Antony Chazapis
        for x in ('read', 'write'):
667 02c0c3fa Antony Chazapis
            g_perms = []
668 02c0c3fa Antony Chazapis
            for y in perms.get(x, []):
669 02c0c3fa Antony Chazapis
                if ':' in y:
670 02c0c3fa Antony Chazapis
                    g_account, g_name = y.split(':', 1)
671 02c0c3fa Antony Chazapis
                    groups = self._get_groups(g_account)
672 02c0c3fa Antony Chazapis
                    if g_name in groups:
673 02c0c3fa Antony Chazapis
                        g_perms += groups[g_name]
674 02c0c3fa Antony Chazapis
                else:
675 02c0c3fa Antony Chazapis
                    g_perms.append(y)
676 02c0c3fa Antony Chazapis
            perms[x] = g_perms
677 02c0c3fa Antony Chazapis
        
678 cca6c617 Antony Chazapis
        if op == 'read' and user in perms.get('read', []):
679 cca6c617 Antony Chazapis
            return True
680 cca6c617 Antony Chazapis
        if user in perms.get('write', []):
681 cca6c617 Antony Chazapis
            return True
682 cca6c617 Antony Chazapis
        return False
683 cca6c617 Antony Chazapis
    
684 cca6c617 Antony Chazapis
    def _can_read(self, user, account, container, name):
685 cca6c617 Antony Chazapis
        if not self._is_allowed(user, account, container, name, 'read'):
686 cca6c617 Antony Chazapis
            raise NotAllowedError
687 3436eeb0 Antony Chazapis
    
688 cca6c617 Antony Chazapis
    def _can_write(self, user, account, container, name):
689 cca6c617 Antony Chazapis
        if not self._is_allowed(user, account, container, name, 'write'):
690 cca6c617 Antony Chazapis
            raise NotAllowedError
691 3436eeb0 Antony Chazapis
    
692 3436eeb0 Antony Chazapis
    def _check_permissions(self, path, permissions):
693 3436eeb0 Antony Chazapis
        # Check for existing permissions.
694 3436eeb0 Antony Chazapis
        sql = '''select name from permissions
695 3436eeb0 Antony Chazapis
                    where name != ? and (name like ? or ? like name || ?)'''
696 3436eeb0 Antony Chazapis
        c = self.con.execute(sql, (path, path + '%', path, '%'))
697 e8886082 Antony Chazapis
        rows = c.fetchall()
698 e8886082 Antony Chazapis
        if rows:
699 3436eeb0 Antony Chazapis
            raise AttributeError('Permissions already set')
700 3436eeb0 Antony Chazapis
        
701 cca6c617 Antony Chazapis
        # Format given permissions.
702 cca6c617 Antony Chazapis
        if len(permissions) == 0:
703 cca6c617 Antony Chazapis
            return '', ''
704 3436eeb0 Antony Chazapis
        r = permissions.get('read', [])
705 3436eeb0 Antony Chazapis
        w = permissions.get('write', [])
706 e8886082 Antony Chazapis
        if True in [False or ',' in x for x in r]:
707 3436eeb0 Antony Chazapis
            raise ValueError('Bad characters in read permissions')
708 e8886082 Antony Chazapis
        if True in [False or ',' in x for x in w]:
709 3436eeb0 Antony Chazapis
            raise ValueError('Bad characters in write permissions')
710 cca6c617 Antony Chazapis
        return ','.join(r), ','.join(w)
711 3436eeb0 Antony Chazapis
    
712 3436eeb0 Antony Chazapis
    def _get_permissions(self, path):
713 a6eb13e9 Antony Chazapis
        # Check for permissions at path or above.
714 a6eb13e9 Antony Chazapis
        sql = 'select name, read, write from permissions where ? like name || ?'
715 a6eb13e9 Antony Chazapis
        c = self.con.execute(sql, (path, '%'))
716 3436eeb0 Antony Chazapis
        row = c.fetchone()
717 3436eeb0 Antony Chazapis
        if not row:
718 a6eb13e9 Antony Chazapis
            return path, {}
719 3436eeb0 Antony Chazapis
        
720 a6eb13e9 Antony Chazapis
        name, r, w = row
721 3436eeb0 Antony Chazapis
        ret = {}
722 3436eeb0 Antony Chazapis
        if w != '':
723 3436eeb0 Antony Chazapis
            ret['write'] = w.split(',')
724 3436eeb0 Antony Chazapis
        if r != '':
725 cca6c617 Antony Chazapis
            ret['read'] = r.split(',')
726 a6eb13e9 Antony Chazapis
        return name, ret
727 3436eeb0 Antony Chazapis
    
728 3436eeb0 Antony Chazapis
    def _put_permissions(self, path, r, w):
729 cca6c617 Antony Chazapis
        if r == '' and w == '':
730 cca6c617 Antony Chazapis
            sql = 'delete from permissions where name = ?'
731 cca6c617 Antony Chazapis
            self.con.execute(sql, (path,))
732 cca6c617 Antony Chazapis
        else:
733 cca6c617 Antony Chazapis
            sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
734 cca6c617 Antony Chazapis
            self.con.execute(sql, (path, r, w))
735 3436eeb0 Antony Chazapis
        self.con.commit()
736 3436eeb0 Antony Chazapis
    
737 e0f916bb Antony Chazapis
    def _get_public(self, path):
738 e0f916bb Antony Chazapis
        sql = 'select name from public where name = ?'
739 e0f916bb Antony Chazapis
        c = self.con.execute(sql, (path,))
740 e0f916bb Antony Chazapis
        row = c.fetchone()
741 e0f916bb Antony Chazapis
        if not row:
742 e0f916bb Antony Chazapis
            return False
743 e0f916bb Antony Chazapis
        return True
744 e0f916bb Antony Chazapis
    
745 e0f916bb Antony Chazapis
    def _put_public(self, path, public):
746 e0f916bb Antony Chazapis
        if not public:
747 e0f916bb Antony Chazapis
            sql = 'delete from public where name = ?'
748 e0f916bb Antony Chazapis
        else:
749 e0f916bb Antony Chazapis
            sql = 'insert or replace into public (name) values (?)'
750 e0f916bb Antony Chazapis
        self.con.execute(sql, (path,))
751 e0f916bb Antony Chazapis
        self.con.commit()
752 e0f916bb Antony Chazapis
    
753 58a6c894 Antony Chazapis
    def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None):
754 22dab079 Antony Chazapis
        cont_prefix = path + '/'
755 22dab079 Antony Chazapis
        if keys and len(keys) > 0:
756 58a6c894 Antony Chazapis
            sql = '''select distinct o.name, o.version_id from (%s) o, metadata m where o.name like ? and
757 58a6c894 Antony Chazapis
                        m.version_id = o.version_id and m.key in (%s) order by o.name'''
758 58a6c894 Antony Chazapis
            sql = sql % (self._sql_until(until), ', '.join('?' * len(keys)))
759 22dab079 Antony Chazapis
            param = (cont_prefix + prefix + '%',) + tuple(keys)
760 22dab079 Antony Chazapis
        else:
761 58a6c894 Antony Chazapis
            sql = 'select name, version_id from (%s) where name like ? order by name'
762 58a6c894 Antony Chazapis
            sql = sql % self._sql_until(until)
763 22dab079 Antony Chazapis
            param = (cont_prefix + prefix + '%',)
764 22dab079 Antony Chazapis
        c = self.con.execute(sql, param)
765 58a6c894 Antony Chazapis
        objects = [(x[0][len(cont_prefix):], x[1]) for x in c.fetchall()]
766 22dab079 Antony Chazapis
        if delimiter:
767 22dab079 Antony Chazapis
            pseudo_objects = []
768 22dab079 Antony Chazapis
            for x in objects:
769 58a6c894 Antony Chazapis
                pseudo_name = x[0]
770 22dab079 Antony Chazapis
                i = pseudo_name.find(delimiter, len(prefix))
771 22dab079 Antony Chazapis
                if not virtual:
772 22dab079 Antony Chazapis
                    # If the delimiter is not found, or the name ends
773 22dab079 Antony Chazapis
                    # with the delimiter's first occurence.
774 22dab079 Antony Chazapis
                    if i == -1 or len(pseudo_name) == i + len(delimiter):
775 58a6c894 Antony Chazapis
                        pseudo_objects.append(x)
776 22dab079 Antony Chazapis
                else:
777 22dab079 Antony Chazapis
                    # If the delimiter is found, keep up to (and including) the delimiter.
778 22dab079 Antony Chazapis
                    if i != -1:
779 22dab079 Antony Chazapis
                        pseudo_name = pseudo_name[:i + len(delimiter)]
780 58a6c894 Antony Chazapis
                    if pseudo_name not in [y[0] for y in pseudo_objects]:
781 83dd59c5 Antony Chazapis
                        if pseudo_name == x[0]:
782 83dd59c5 Antony Chazapis
                            pseudo_objects.append(x)
783 83dd59c5 Antony Chazapis
                        else:
784 83dd59c5 Antony Chazapis
                            pseudo_objects.append((pseudo_name, None))
785 22dab079 Antony Chazapis
            objects = pseudo_objects
786 22dab079 Antony Chazapis
        
787 22dab079 Antony Chazapis
        start = 0
788 22dab079 Antony Chazapis
        if marker:
789 22dab079 Antony Chazapis
            try:
790 58a6c894 Antony Chazapis
                start = [x[0] for x in objects].index(marker) + 1
791 22dab079 Antony Chazapis
            except ValueError:
792 22dab079 Antony Chazapis
                pass
793 22dab079 Antony Chazapis
        if not limit or limit > 10000:
794 22dab079 Antony Chazapis
            limit = 10000
795 22dab079 Antony Chazapis
        return objects[start:start + limit]
796 22dab079 Antony Chazapis
    
797 58a6c894 Antony Chazapis
    def _del_path(self, path):
798 22dab079 Antony Chazapis
        sql = '''delete from hashmaps where version_id in
799 58a6c894 Antony Chazapis
                    (select version_id from versions where name = ?)'''
800 58a6c894 Antony Chazapis
        self.con.execute(sql, (path,))
801 58a6c894 Antony Chazapis
        sql = '''delete from metadata where version_id in
802 58a6c894 Antony Chazapis
                    (select version_id from versions where name = ?)'''
803 22dab079 Antony Chazapis
        self.con.execute(sql, (path,))
804 58a6c894 Antony Chazapis
        sql = '''delete from versions where name = ?'''
805 b956618e Antony Chazapis
        self.con.execute(sql, (path,))
806 3436eeb0 Antony Chazapis
        sql = '''delete from permissions where name like ?'''
807 3436eeb0 Antony Chazapis
        self.con.execute(sql, (path + '%',)) # Redundant.
808 b956618e Antony Chazapis
        self.con.commit()