Statistics
| Branch: | Tag: | Revision:

root / pithos / backends / simple.py @ 2f51ce47

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