Statistics
| Branch: | Tag: | Revision:

root / pithos / backends / simple.py @ 104626e3

History | View | Annotate | Download (28.9 kB)

1 5635f9ef Antony Chazapis
# Copyright 2011 GRNET S.A. All rights reserved.
2 5635f9ef Antony Chazapis
# 
3 5635f9ef Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 5635f9ef Antony Chazapis
# without modification, are permitted provided that the following
5 5635f9ef Antony Chazapis
# conditions are met:
6 5635f9ef Antony Chazapis
# 
7 5635f9ef Antony Chazapis
#   1. Redistributions of source code must retain the above
8 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
9 5635f9ef Antony Chazapis
#      disclaimer.
10 5635f9ef Antony Chazapis
# 
11 5635f9ef Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
13 5635f9ef Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 5635f9ef Antony Chazapis
#      provided with the distribution.
15 5635f9ef Antony Chazapis
# 
16 5635f9ef Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5635f9ef Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5635f9ef Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5635f9ef Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5635f9ef Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5635f9ef Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5635f9ef Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5635f9ef Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5635f9ef Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5635f9ef Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5635f9ef Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5635f9ef Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 5635f9ef Antony Chazapis
# 
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
import os
35 b956618e Antony Chazapis
import time
36 b956618e Antony Chazapis
import sqlite3
37 b956618e Antony Chazapis
import logging
38 b956618e Antony Chazapis
import 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 22dab079 Antony Chazapis
        basepath = os.path.split(db)[0]
62 22dab079 Antony Chazapis
        if basepath and not os.path.exists(basepath):
63 b956618e Antony Chazapis
            os.makedirs(basepath)
64 22dab079 Antony Chazapis
        
65 49350be7 Giorgos Verigakis
        self.con = sqlite3.connect(db, check_same_thread=False)
66 58a6c894 Antony Chazapis
        sql = '''create table if not exists versions (
67 58a6c894 Antony Chazapis
                    version_id integer primary key,
68 58a6c894 Antony Chazapis
                    name text,
69 104626e3 Antony Chazapis
                    user text,
70 58a6c894 Antony Chazapis
                    tstamp datetime default current_timestamp,
71 58a6c894 Antony Chazapis
                    size integer default 0,
72 58a6c894 Antony Chazapis
                    hide integer default 0)'''
73 b956618e Antony Chazapis
        self.con.execute(sql)
74 b956618e Antony Chazapis
        sql = '''create table if not exists metadata (
75 58a6c894 Antony Chazapis
                    version_id integer, key text, value text, primary key (version_id, key))'''
76 22dab079 Antony Chazapis
        self.con.execute(sql)
77 22dab079 Antony Chazapis
        sql = '''create table if not exists blocks (
78 22dab079 Antony Chazapis
                    block_id text, data blob, primary key (block_id))'''
79 22dab079 Antony Chazapis
        self.con.execute(sql)
80 22dab079 Antony Chazapis
        sql = '''create table if not exists hashmaps (
81 58a6c894 Antony Chazapis
                    version_id integer, pos integer, block_id text, primary key (version_id, pos))'''
82 b956618e Antony Chazapis
        self.con.execute(sql)
83 3436eeb0 Antony Chazapis
        sql = '''create table if not exists permissions (
84 3436eeb0 Antony Chazapis
                    name text, read text, write text, primary key (name))'''
85 3436eeb0 Antony Chazapis
        self.con.execute(sql)
86 b956618e Antony Chazapis
        self.con.commit()
87 b956618e Antony Chazapis
    
88 83dd59c5 Antony Chazapis
    def delete_account(self, user, account):
89 58a6c894 Antony Chazapis
        """Delete the account with the given name."""
90 58a6c894 Antony Chazapis
        
91 58a6c894 Antony Chazapis
        logger.debug("delete_account: %s", account)
92 cca6c617 Antony Chazapis
        if user != account:
93 cca6c617 Antony Chazapis
            raise NotAllowedError
94 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(account)
95 58a6c894 Antony Chazapis
        if count > 0:
96 58a6c894 Antony Chazapis
            raise IndexError('Account is not empty')
97 58a6c894 Antony Chazapis
        self._del_path(account) # Point of no return.
98 58a6c894 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 83dd59c5 Antony Chazapis
    def list_containers(self, user, account, marker=None, limit=10000, until=None):
144 58a6c894 Antony Chazapis
        """Return a list of containers existing under an account."""
145 58a6c894 Antony Chazapis
        
146 58a6c894 Antony Chazapis
        logger.debug("list_containers: %s %s %s %s", account, marker, limit, until)
147 cca6c617 Antony Chazapis
        if user != account:
148 cca6c617 Antony Chazapis
            raise NotAllowedError
149 58a6c894 Antony Chazapis
        return self._list_objects(account, '', '/', marker, limit, False, [], until)
150 b956618e Antony Chazapis
    
151 83dd59c5 Antony Chazapis
    def put_container(self, user, account, container):
152 b956618e Antony Chazapis
        """Create a new container with the given name."""
153 b956618e Antony Chazapis
        
154 58a6c894 Antony Chazapis
        logger.debug("put_container: %s %s", account, container)
155 cca6c617 Antony Chazapis
        if user != account:
156 cca6c617 Antony Chazapis
            raise NotAllowedError
157 22dab079 Antony Chazapis
        try:
158 58a6c894 Antony Chazapis
            path, version_id, mtime = self._get_containerinfo(account, container)
159 22dab079 Antony Chazapis
        except NameError:
160 58a6c894 Antony Chazapis
            path = os.path.join(account, container)
161 104626e3 Antony Chazapis
            version_id = self._put_version(path, user)
162 b956618e Antony Chazapis
        else:
163 b956618e Antony Chazapis
            raise NameError('Container already exists')
164 b956618e Antony Chazapis
    
165 83dd59c5 Antony Chazapis
    def delete_container(self, user, account, container):
166 b956618e Antony Chazapis
        """Delete the container with the given name."""
167 b956618e Antony Chazapis
        
168 58a6c894 Antony Chazapis
        logger.debug("delete_container: %s %s", account, container)
169 cca6c617 Antony Chazapis
        if user != account:
170 cca6c617 Antony Chazapis
            raise NotAllowedError
171 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container)
172 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(path)
173 22dab079 Antony Chazapis
        if count > 0:
174 b956618e Antony Chazapis
            raise IndexError('Container is not empty')
175 58a6c894 Antony Chazapis
        self._del_path(path) # Point of no return.
176 104626e3 Antony Chazapis
        self._copy_version(user, account, account, True, True) # New account version.
177 b956618e Antony Chazapis
    
178 83dd59c5 Antony Chazapis
    def get_container_meta(self, user, account, container, until=None):
179 b956618e Antony Chazapis
        """Return a dictionary with the container metadata."""
180 b956618e Antony Chazapis
        
181 58a6c894 Antony Chazapis
        logger.debug("get_container_meta: %s %s %s", account, container, until)
182 cca6c617 Antony Chazapis
        if user != account:
183 cca6c617 Antony Chazapis
            raise NotAllowedError
184 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
185 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(path, until)
186 31a1c80d Antony Chazapis
        if mtime > tstamp:
187 31a1c80d Antony Chazapis
            tstamp = mtime
188 58a6c894 Antony Chazapis
        if until is None:
189 58a6c894 Antony Chazapis
            modified = tstamp
190 58a6c894 Antony Chazapis
        else:
191 31a1c80d Antony Chazapis
            modified = self._get_pathstats(path)[2] # Overall last modification
192 31a1c80d Antony Chazapis
            if mtime > modified:
193 31a1c80d Antony Chazapis
                modified = mtime
194 58a6c894 Antony Chazapis
        
195 58a6c894 Antony Chazapis
        meta = self._get_metadata(path, version_id)
196 58a6c894 Antony Chazapis
        meta.update({'name': container, 'count': count, 'bytes': bytes, 'modified': modified})
197 58a6c894 Antony Chazapis
        if until is not None:
198 58a6c894 Antony Chazapis
            meta.update({'until_timestamp': tstamp})
199 b956618e Antony Chazapis
        return meta
200 b956618e Antony Chazapis
    
201 83dd59c5 Antony Chazapis
    def update_container_meta(self, user, account, container, meta, replace=False):
202 b956618e Antony Chazapis
        """Update the metadata associated with the container."""
203 b956618e Antony Chazapis
        
204 58a6c894 Antony Chazapis
        logger.debug("update_container_meta: %s %s %s %s", account, container, meta, replace)
205 cca6c617 Antony Chazapis
        if user != account:
206 cca6c617 Antony Chazapis
            raise NotAllowedError
207 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container)
208 104626e3 Antony Chazapis
        self._put_metadata(user, path, meta, replace)
209 b956618e Antony Chazapis
    
210 83dd59c5 Antony Chazapis
    def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None):
211 b956618e Antony Chazapis
        """Return a list of objects existing under a container."""
212 b956618e Antony Chazapis
        
213 58a6c894 Antony Chazapis
        logger.debug("list_objects: %s %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit, until)
214 cca6c617 Antony Chazapis
        if user != account:
215 cca6c617 Antony Chazapis
            raise NotAllowedError
216 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
217 58a6c894 Antony Chazapis
        return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, until)
218 22dab079 Antony Chazapis
    
219 83dd59c5 Antony Chazapis
    def list_object_meta(self, user, account, container, until=None):
220 22dab079 Antony Chazapis
        """Return a list with all the container's object meta keys."""
221 b956618e Antony Chazapis
        
222 58a6c894 Antony Chazapis
        logger.debug("list_object_meta: %s %s %s", account, container, until)
223 cca6c617 Antony Chazapis
        if user != account:
224 cca6c617 Antony Chazapis
            raise NotAllowedError
225 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
226 58a6c894 Antony Chazapis
        sql = '''select distinct m.key from (%s) o, metadata m
227 58a6c894 Antony Chazapis
                    where m.version_id = o.version_id and o.name like ?'''
228 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
229 22dab079 Antony Chazapis
        c = self.con.execute(sql, (path + '/%',))
230 22dab079 Antony Chazapis
        return [x[0] for x in c.fetchall()]
231 b956618e Antony Chazapis
    
232 83dd59c5 Antony Chazapis
    def get_object_meta(self, user, account, container, name, version=None):
233 b956618e Antony Chazapis
        """Return a dictionary with the object metadata."""
234 b956618e Antony Chazapis
        
235 58a6c894 Antony Chazapis
        logger.debug("get_object_meta: %s %s %s %s", account, container, name, version)
236 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
237 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name, version)
238 58a6c894 Antony Chazapis
        if version is None:
239 58a6c894 Antony Chazapis
            modified = mtime
240 58a6c894 Antony Chazapis
        else:
241 104626e3 Antony Chazapis
            modified = self._get_version(path, version)[2] # Overall last modification
242 58a6c894 Antony Chazapis
        
243 58a6c894 Antony Chazapis
        meta = self._get_metadata(path, version_id)
244 104626e3 Antony Chazapis
        meta.update({'name': name, 'bytes': size})
245 104626e3 Antony Chazapis
        meta.update({'version': version_id, 'version_timestamp': mtime})
246 104626e3 Antony Chazapis
        meta.update({'modified': modified, 'modified_by': muser})
247 b956618e Antony Chazapis
        return meta
248 b956618e Antony Chazapis
    
249 83dd59c5 Antony Chazapis
    def update_object_meta(self, user, account, container, name, meta, replace=False):
250 b956618e Antony Chazapis
        """Update the metadata associated with the object."""
251 b956618e Antony Chazapis
        
252 22dab079 Antony Chazapis
        logger.debug("update_object_meta: %s %s %s %s %s", account, container, name, meta, replace)
253 cca6c617 Antony Chazapis
        self._can_write(user, account, container, name)
254 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name)
255 104626e3 Antony Chazapis
        self._put_metadata(user, path, meta, replace)
256 b956618e Antony Chazapis
    
257 3436eeb0 Antony Chazapis
    def get_object_permissions(self, user, account, container, name):
258 cca6c617 Antony Chazapis
        """Return the path from which this object gets its permissions from,\
259 cca6c617 Antony Chazapis
        along with a dictionary containing the permissions."""
260 3436eeb0 Antony Chazapis
        
261 3436eeb0 Antony Chazapis
        logger.debug("get_object_permissions: %s %s %s", account, container, name)
262 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
263 3436eeb0 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
264 cca6c617 Antony Chazapis
        return self._get_permissions(path)
265 3436eeb0 Antony Chazapis
    
266 3436eeb0 Antony Chazapis
    def update_object_permissions(self, user, account, container, name, permissions):
267 3436eeb0 Antony Chazapis
        """Update the permissions associated with the object."""
268 3436eeb0 Antony Chazapis
        
269 3436eeb0 Antony Chazapis
        logger.debug("update_object_permissions: %s %s %s %s", account, container, name, permissions)
270 cca6c617 Antony Chazapis
        if user != account:
271 cca6c617 Antony Chazapis
            raise NotAllowedError
272 3436eeb0 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
273 3436eeb0 Antony Chazapis
        r, w = self._check_permissions(path, permissions)
274 3436eeb0 Antony Chazapis
        self._put_permissions(path, r, w)
275 3436eeb0 Antony Chazapis
    
276 83dd59c5 Antony Chazapis
    def get_object_hashmap(self, user, account, container, name, version=None):
277 22dab079 Antony Chazapis
        """Return the object's size and a list with partial hashes."""
278 b956618e Antony Chazapis
        
279 22dab079 Antony Chazapis
        logger.debug("get_object_hashmap: %s %s %s %s", account, container, name, version)
280 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
281 104626e3 Antony Chazapis
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name, version)
282 58a6c894 Antony Chazapis
        sql = 'select block_id from hashmaps where version_id = ? order by pos asc'
283 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (version_id,))
284 22dab079 Antony Chazapis
        hashmap = [x[0] for x in c.fetchall()]
285 22dab079 Antony Chazapis
        return size, hashmap
286 22dab079 Antony Chazapis
    
287 cca6c617 Antony Chazapis
    def update_object_hashmap(self, user, account, container, name, size, hashmap, meta={}, replace_meta=False, permissions=None):
288 22dab079 Antony Chazapis
        """Create/update an object with the specified size and partial hashes."""
289 b956618e Antony Chazapis
        
290 cfe6939d Antony Chazapis
        logger.debug("update_object_hashmap: %s %s %s %s %s", account, container, name, size, hashmap)
291 cca6c617 Antony Chazapis
        if permissions is not None and user != account:
292 cca6c617 Antony Chazapis
            raise NotAllowedError
293 cca6c617 Antony Chazapis
        self._can_write(user, account, container, name)
294 58a6c894 Antony Chazapis
        path = self._get_containerinfo(account, container)[0]
295 58a6c894 Antony Chazapis
        path = os.path.join(path, name)
296 cca6c617 Antony Chazapis
        if permissions is not None:
297 3436eeb0 Antony Chazapis
            r, w = self._check_permissions(path, permissions)
298 104626e3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, path, path, not replace_meta, False)
299 58a6c894 Antony Chazapis
        sql = 'update versions set size = ? where version_id = ?'
300 58a6c894 Antony Chazapis
        self.con.execute(sql, (size, dest_version_id))
301 58a6c894 Antony Chazapis
        # TODO: Check for block_id existence.
302 22dab079 Antony Chazapis
        for i in range(len(hashmap)):
303 22dab079 Antony Chazapis
            sql = 'insert or replace into hashmaps (version_id, pos, block_id) values (?, ?, ?)'
304 58a6c894 Antony Chazapis
            self.con.execute(sql, (dest_version_id, i, hashmap[i]))
305 83dd59c5 Antony Chazapis
        for k, v in meta.iteritems():
306 83dd59c5 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
307 83dd59c5 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
308 cca6c617 Antony Chazapis
        if permissions is not None:
309 3436eeb0 Antony Chazapis
            sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
310 3436eeb0 Antony Chazapis
            self.con.execute(sql, (path, r, w))
311 22dab079 Antony Chazapis
        self.con.commit()
312 22dab079 Antony Chazapis
    
313 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):
314 22dab079 Antony Chazapis
        """Copy an object's data and metadata."""
315 b956618e Antony Chazapis
        
316 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)
317 cca6c617 Antony Chazapis
        if permissions is not None and user != account:
318 cca6c617 Antony Chazapis
            raise NotAllowedError
319 cca6c617 Antony Chazapis
        self._can_read(user, account, src_container, src_name)
320 cca6c617 Antony Chazapis
        self._can_write(user, account, dest_container, dest_name)
321 6d817842 Antony Chazapis
        self._get_containerinfo(account, src_container)
322 58a6c894 Antony Chazapis
        if src_version is None:
323 58a6c894 Antony Chazapis
            src_path = self._get_objectinfo(account, src_container, src_name)[0]
324 22dab079 Antony Chazapis
        else:
325 58a6c894 Antony Chazapis
            src_path = os.path.join(account, src_container, src_name)
326 58a6c894 Antony Chazapis
        dest_path = self._get_containerinfo(account, dest_container)[0]
327 58a6c894 Antony Chazapis
        dest_path = os.path.join(dest_path, dest_name)
328 cca6c617 Antony Chazapis
        if permissions is not None:
329 3436eeb0 Antony Chazapis
            r, w = self._check_permissions(dest_path, permissions)
330 104626e3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, src_path, dest_path, not replace_meta, True, src_version)
331 58a6c894 Antony Chazapis
        for k, v in dest_meta.iteritems():
332 58a6c894 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
333 58a6c894 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
334 cca6c617 Antony Chazapis
        if permissions is not None:
335 3436eeb0 Antony Chazapis
            sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
336 3436eeb0 Antony Chazapis
            self.con.execute(sql, (dest_path, r, w))
337 58a6c894 Antony Chazapis
        self.con.commit()
338 b956618e Antony Chazapis
    
339 cca6c617 Antony Chazapis
    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
340 b956618e Antony Chazapis
        """Move an object's data and metadata."""
341 b956618e Antony Chazapis
        
342 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)
343 3436eeb0 Antony Chazapis
        self.copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
344 83dd59c5 Antony Chazapis
        self.delete_object(user, account, src_container, src_name)
345 b956618e Antony Chazapis
    
346 83dd59c5 Antony Chazapis
    def delete_object(self, user, account, container, name):
347 b956618e Antony Chazapis
        """Delete an object."""
348 b956618e Antony Chazapis
        
349 b956618e Antony Chazapis
        logger.debug("delete_object: %s %s %s", account, container, name)
350 cca6c617 Antony Chazapis
        if user != account:
351 cca6c617 Antony Chazapis
            raise NotAllowedError
352 104626e3 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
353 104626e3 Antony Chazapis
        self._put_version(path, user, 0, 1)
354 3436eeb0 Antony Chazapis
        sql = 'delete from permissions where name = ?'
355 3436eeb0 Antony Chazapis
        self.con.execute(sql, (path,))
356 3436eeb0 Antony Chazapis
        self.con.commit()
357 58a6c894 Antony Chazapis
    
358 83dd59c5 Antony Chazapis
    def list_versions(self, user, account, container, name):
359 83dd59c5 Antony Chazapis
        """Return a list of all (version, version_timestamp) tuples for an object."""
360 58a6c894 Antony Chazapis
        
361 83dd59c5 Antony Chazapis
        logger.debug("list_versions: %s %s %s", account, container, name)
362 cca6c617 Antony Chazapis
        self._can_read(user, account, container, name)
363 58a6c894 Antony Chazapis
        # This will even show deleted versions.
364 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
365 e8886082 Antony Chazapis
        sql = '''select distinct version_id, strftime('%s', tstamp) from versions where name = ? and hide = 0'''
366 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path,))
367 c9af0703 Antony Chazapis
        return [(int(x[0]), int(x[1])) for x in c.fetchall()]
368 b956618e Antony Chazapis
    
369 22dab079 Antony Chazapis
    def get_block(self, hash):
370 22dab079 Antony Chazapis
        """Return a block's data."""
371 22dab079 Antony Chazapis
        
372 cfe6939d Antony Chazapis
        logger.debug("get_block: %s", hash)
373 22dab079 Antony Chazapis
        c = self.con.execute('select data from blocks where block_id = ?', (hash,))
374 22dab079 Antony Chazapis
        row = c.fetchone()
375 22dab079 Antony Chazapis
        if row:
376 22dab079 Antony Chazapis
            return str(row[0])
377 22dab079 Antony Chazapis
        else:
378 22dab079 Antony Chazapis
            raise NameError('Block does not exist')
379 b956618e Antony Chazapis
    
380 22dab079 Antony Chazapis
    def put_block(self, data):
381 22dab079 Antony Chazapis
        """Create a block and return the hash."""
382 22dab079 Antony Chazapis
        
383 cfe6939d Antony Chazapis
        logger.debug("put_block: %s", len(data))
384 22dab079 Antony Chazapis
        h = hashlib.new(self.hash_algorithm)
385 22dab079 Antony Chazapis
        h.update(data.rstrip('\x00'))
386 22dab079 Antony Chazapis
        hash = h.hexdigest()
387 22dab079 Antony Chazapis
        sql = 'insert or ignore into blocks (block_id, data) values (?, ?)'
388 22dab079 Antony Chazapis
        self.con.execute(sql, (hash, buffer(data)))
389 22dab079 Antony Chazapis
        self.con.commit()
390 22dab079 Antony Chazapis
        return hash
391 22dab079 Antony Chazapis
    
392 22dab079 Antony Chazapis
    def update_block(self, hash, data, offset=0):
393 22dab079 Antony Chazapis
        """Update a known block and return the hash."""
394 22dab079 Antony Chazapis
        
395 cfe6939d Antony Chazapis
        logger.debug("update_block: %s %s %s", hash, len(data), offset)
396 cfe6939d Antony Chazapis
        if offset == 0 and len(data) == self.block_size:
397 cfe6939d Antony Chazapis
            return self.put_block(data)
398 22dab079 Antony Chazapis
        src_data = self.get_block(hash)
399 22dab079 Antony Chazapis
        bs = self.block_size
400 22dab079 Antony Chazapis
        if offset < 0 or offset > bs or offset + len(data) > bs:
401 22dab079 Antony Chazapis
            raise IndexError('Offset or data outside block limits')
402 22dab079 Antony Chazapis
        dest_data = src_data[:offset] + data + src_data[offset + len(data):]
403 22dab079 Antony Chazapis
        return self.put_block(dest_data)
404 b956618e Antony Chazapis
    
405 58a6c894 Antony Chazapis
    def _sql_until(self, until=None):
406 58a6c894 Antony Chazapis
        """Return the sql to get the latest versions until the timestamp given."""
407 58a6c894 Antony Chazapis
        if until is None:
408 58a6c894 Antony Chazapis
            until = int(time.time())
409 58a6c894 Antony Chazapis
        sql = '''select version_id, name, strftime('%s', tstamp) as tstamp, size from versions v
410 58a6c894 Antony Chazapis
                    where version_id = (select max(version_id) from versions
411 58a6c894 Antony Chazapis
                                        where v.name = name and tstamp <= datetime(%s, 'unixepoch'))
412 58a6c894 Antony Chazapis
                    and hide = 0'''
413 58a6c894 Antony Chazapis
        return sql % ('%s', until)
414 58a6c894 Antony Chazapis
    
415 58a6c894 Antony Chazapis
    def _get_pathstats(self, path, until=None):
416 58a6c894 Antony Chazapis
        """Return count and sum of size of everything under path and latest timestamp."""
417 58a6c894 Antony Chazapis
        
418 58a6c894 Antony Chazapis
        sql = 'select count(version_id), total(size), max(tstamp) from (%s) where name like ?'
419 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
420 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path + '/%',))
421 b956618e Antony Chazapis
        row = c.fetchone()
422 58a6c894 Antony Chazapis
        tstamp = row[2] if row[2] is not None else 0
423 58a6c894 Antony Chazapis
        return int(row[0]), int(row[1]), int(tstamp)
424 58a6c894 Antony Chazapis
    
425 58a6c894 Antony Chazapis
    def _get_version(self, path, version=None):
426 cb146cf9 Antony Chazapis
        if version is None:
427 104626e3 Antony Chazapis
            sql = '''select version_id, user, strftime('%s', tstamp), size, hide from versions where name = ?
428 58a6c894 Antony Chazapis
                        order by version_id desc limit 1'''
429 58a6c894 Antony Chazapis
            c = self.con.execute(sql, (path,))
430 58a6c894 Antony Chazapis
            row = c.fetchone()
431 104626e3 Antony Chazapis
            if not row or int(row[4]):
432 58a6c894 Antony Chazapis
                raise NameError('Object does not exist')
433 b956618e Antony Chazapis
        else:
434 104626e3 Antony Chazapis
            # The database (sqlite) will not complain if the version is not an integer.
435 104626e3 Antony Chazapis
            sql = '''select version_id, user, strftime('%s', tstamp), size from versions where name = ?
436 58a6c894 Antony Chazapis
                        and version_id = ?'''
437 58a6c894 Antony Chazapis
            c = self.con.execute(sql, (path, version))
438 58a6c894 Antony Chazapis
            row = c.fetchone()
439 58a6c894 Antony Chazapis
            if not row:
440 58a6c894 Antony Chazapis
                raise IndexError('Version does not exist')
441 104626e3 Antony Chazapis
        return str(row[0]), str(row[1]), int(row[2]), int(row[3])
442 58a6c894 Antony Chazapis
    
443 104626e3 Antony Chazapis
    def _put_version(self, path, user, size=0, hide=0):
444 104626e3 Antony Chazapis
        sql = 'insert into versions (name, user, size, hide) values (?, ?, ?, ?)'
445 104626e3 Antony Chazapis
        id = self.con.execute(sql, (path, user, size, hide)).lastrowid
446 b956618e Antony Chazapis
        self.con.commit()
447 b956618e Antony Chazapis
        return str(id)
448 b956618e Antony Chazapis
    
449 104626e3 Antony Chazapis
    def _copy_version(self, user, src_path, dest_path, copy_meta=True, copy_data=True, src_version=None):
450 58a6c894 Antony Chazapis
        if src_version is not None:
451 104626e3 Antony Chazapis
            src_version_id, muser, mtime, size = self._get_version(src_path, src_version)
452 58a6c894 Antony Chazapis
        else:
453 58a6c894 Antony Chazapis
            # Latest or create from scratch.
454 58a6c894 Antony Chazapis
            try:
455 104626e3 Antony Chazapis
                src_version_id, muser, mtime, size = self._get_version(src_path)
456 58a6c894 Antony Chazapis
            except NameError:
457 58a6c894 Antony Chazapis
                src_version_id = None
458 58a6c894 Antony Chazapis
                size = 0
459 58a6c894 Antony Chazapis
        if not copy_data:
460 58a6c894 Antony Chazapis
            size = 0
461 104626e3 Antony Chazapis
        dest_version_id = self._put_version(dest_path, user, size)
462 58a6c894 Antony Chazapis
        if copy_meta and src_version_id is not None:
463 58a6c894 Antony Chazapis
            sql = 'insert into metadata select %s, key, value from metadata where version_id = ?'
464 58a6c894 Antony Chazapis
            sql = sql % dest_version_id
465 58a6c894 Antony Chazapis
            self.con.execute(sql, (src_version_id,))
466 58a6c894 Antony Chazapis
        if copy_data and src_version_id is not None:
467 58a6c894 Antony Chazapis
            sql = 'insert into hashmaps select %s, pos, block_id from hashmaps where version_id = ?'
468 58a6c894 Antony Chazapis
            sql = sql % dest_version_id
469 58a6c894 Antony Chazapis
            self.con.execute(sql, (src_version_id,))
470 58a6c894 Antony Chazapis
        self.con.commit()
471 58a6c894 Antony Chazapis
        return src_version_id, dest_version_id
472 58a6c894 Antony Chazapis
    
473 58a6c894 Antony Chazapis
    def _get_versioninfo(self, account, container, name, until=None):
474 58a6c894 Antony Chazapis
        """Return path, latest version, associated timestamp and size until the timestamp given."""
475 58a6c894 Antony Chazapis
        
476 58a6c894 Antony Chazapis
        p = (account, container, name)
477 22dab079 Antony Chazapis
        try:
478 58a6c894 Antony Chazapis
            p = p[:p.index(None)]
479 58a6c894 Antony Chazapis
        except ValueError:
480 58a6c894 Antony Chazapis
            pass
481 58a6c894 Antony Chazapis
        path = os.path.join(*p)
482 58a6c894 Antony Chazapis
        sql = '''select version_id, tstamp, size from (%s) where name = ?'''
483 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
484 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path,))
485 58a6c894 Antony Chazapis
        row = c.fetchone()
486 58a6c894 Antony Chazapis
        if row is None:
487 58a6c894 Antony Chazapis
            raise NameError('Path does not exist')
488 58a6c894 Antony Chazapis
        return path, str(row[0]), int(row[1]), int(row[2])
489 58a6c894 Antony Chazapis
    
490 58a6c894 Antony Chazapis
    def _get_accountinfo(self, account, until=None):
491 58a6c894 Antony Chazapis
        try:
492 58a6c894 Antony Chazapis
            path, version_id, mtime, size = self._get_versioninfo(account, None, None, until)
493 58a6c894 Antony Chazapis
            return version_id, mtime
494 58a6c894 Antony Chazapis
        except:
495 58a6c894 Antony Chazapis
            raise NameError('Account does not exist')
496 58a6c894 Antony Chazapis
    
497 58a6c894 Antony Chazapis
    def _get_containerinfo(self, account, container, until=None):
498 58a6c894 Antony Chazapis
        try:
499 58a6c894 Antony Chazapis
            path, version_id, mtime, size = self._get_versioninfo(account, container, None, until)
500 58a6c894 Antony Chazapis
            return path, version_id, mtime
501 58a6c894 Antony Chazapis
        except:
502 22dab079 Antony Chazapis
            raise NameError('Container does not exist')
503 22dab079 Antony Chazapis
    
504 22dab079 Antony Chazapis
    def _get_objectinfo(self, account, container, name, version=None):
505 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
506 104626e3 Antony Chazapis
        version_id, muser, mtime, size = self._get_version(path, version)
507 104626e3 Antony Chazapis
        return path, version_id, muser, mtime, size
508 22dab079 Antony Chazapis
    
509 58a6c894 Antony Chazapis
    def _get_metadata(self, path, version):
510 58a6c894 Antony Chazapis
        sql = 'select key, value from metadata where version_id = ?'
511 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (version,))
512 58a6c894 Antony Chazapis
        return dict(c.fetchall())
513 58a6c894 Antony Chazapis
    
514 104626e3 Antony Chazapis
    def _put_metadata(self, user, path, meta, replace=False):
515 58a6c894 Antony Chazapis
        """Create a new version and store metadata."""
516 58a6c894 Antony Chazapis
        
517 104626e3 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(user, path, path, not replace, True)
518 58a6c894 Antony Chazapis
        for k, v in meta.iteritems():
519 a6eb13e9 Antony Chazapis
            if not replace and v == '':
520 a6eb13e9 Antony Chazapis
                sql = 'delete from metadata where version_id = ? and key = ?'
521 a6eb13e9 Antony Chazapis
                self.con.execute(sql, (dest_version_id, k))
522 a6eb13e9 Antony Chazapis
            else:
523 a6eb13e9 Antony Chazapis
                sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
524 a6eb13e9 Antony Chazapis
                self.con.execute(sql, (dest_version_id, k, v))
525 58a6c894 Antony Chazapis
        self.con.commit()
526 58a6c894 Antony Chazapis
    
527 cca6c617 Antony Chazapis
    def _is_allowed(self, user, account, container, name, op='read'):
528 cca6c617 Antony Chazapis
        if user == account:
529 cca6c617 Antony Chazapis
            return True
530 cca6c617 Antony Chazapis
        path = os.path.join(account, container, name)
531 cca6c617 Antony Chazapis
        perm_path, perms = self._get_permissions(path)
532 cca6c617 Antony Chazapis
        if op == 'read' and user in perms.get('read', []):
533 cca6c617 Antony Chazapis
            return True
534 cca6c617 Antony Chazapis
        if user in perms.get('write', []):
535 cca6c617 Antony Chazapis
            return True
536 cca6c617 Antony Chazapis
        return False
537 cca6c617 Antony Chazapis
    
538 cca6c617 Antony Chazapis
    def _can_read(self, user, account, container, name):
539 cca6c617 Antony Chazapis
        if not self._is_allowed(user, account, container, name, 'read'):
540 cca6c617 Antony Chazapis
            raise NotAllowedError
541 3436eeb0 Antony Chazapis
    
542 cca6c617 Antony Chazapis
    def _can_write(self, user, account, container, name):
543 cca6c617 Antony Chazapis
        if not self._is_allowed(user, account, container, name, 'write'):
544 cca6c617 Antony Chazapis
            raise NotAllowedError
545 3436eeb0 Antony Chazapis
    
546 3436eeb0 Antony Chazapis
    def _check_permissions(self, path, permissions):
547 3436eeb0 Antony Chazapis
        # Check for existing permissions.
548 3436eeb0 Antony Chazapis
        sql = '''select name from permissions
549 3436eeb0 Antony Chazapis
                    where name != ? and (name like ? or ? like name || ?)'''
550 3436eeb0 Antony Chazapis
        c = self.con.execute(sql, (path, path + '%', path, '%'))
551 e8886082 Antony Chazapis
        rows = c.fetchall()
552 e8886082 Antony Chazapis
        if rows:
553 3436eeb0 Antony Chazapis
            raise AttributeError('Permissions already set')
554 3436eeb0 Antony Chazapis
        
555 cca6c617 Antony Chazapis
        # Format given permissions.
556 cca6c617 Antony Chazapis
        if len(permissions) == 0:
557 cca6c617 Antony Chazapis
            return '', ''
558 3436eeb0 Antony Chazapis
        r = permissions.get('read', [])
559 3436eeb0 Antony Chazapis
        w = permissions.get('write', [])
560 e8886082 Antony Chazapis
        if True in [False or ',' in x for x in r]:
561 3436eeb0 Antony Chazapis
            raise ValueError('Bad characters in read permissions')
562 e8886082 Antony Chazapis
        if True in [False or ',' in x for x in w]:
563 3436eeb0 Antony Chazapis
            raise ValueError('Bad characters in write permissions')
564 cca6c617 Antony Chazapis
        return ','.join(r), ','.join(w)
565 3436eeb0 Antony Chazapis
    
566 3436eeb0 Antony Chazapis
    def _get_permissions(self, path):
567 a6eb13e9 Antony Chazapis
        # Check for permissions at path or above.
568 a6eb13e9 Antony Chazapis
        sql = 'select name, read, write from permissions where ? like name || ?'
569 a6eb13e9 Antony Chazapis
        c = self.con.execute(sql, (path, '%'))
570 3436eeb0 Antony Chazapis
        row = c.fetchone()
571 3436eeb0 Antony Chazapis
        if not row:
572 a6eb13e9 Antony Chazapis
            return path, {}
573 3436eeb0 Antony Chazapis
        
574 a6eb13e9 Antony Chazapis
        name, r, w = row
575 3436eeb0 Antony Chazapis
        ret = {}
576 3436eeb0 Antony Chazapis
        if w != '':
577 3436eeb0 Antony Chazapis
            ret['write'] = w.split(',')
578 3436eeb0 Antony Chazapis
        if r != '':
579 cca6c617 Antony Chazapis
            ret['read'] = r.split(',')
580 a6eb13e9 Antony Chazapis
        return name, ret
581 3436eeb0 Antony Chazapis
    
582 3436eeb0 Antony Chazapis
    def _put_permissions(self, path, r, w):
583 cca6c617 Antony Chazapis
        if r == '' and w == '':
584 cca6c617 Antony Chazapis
            sql = 'delete from permissions where name = ?'
585 cca6c617 Antony Chazapis
            self.con.execute(sql, (path,))
586 cca6c617 Antony Chazapis
        else:
587 cca6c617 Antony Chazapis
            sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
588 cca6c617 Antony Chazapis
            self.con.execute(sql, (path, r, w))
589 3436eeb0 Antony Chazapis
        self.con.commit()
590 3436eeb0 Antony Chazapis
    
591 58a6c894 Antony Chazapis
    def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None):
592 22dab079 Antony Chazapis
        cont_prefix = path + '/'
593 22dab079 Antony Chazapis
        if keys and len(keys) > 0:
594 58a6c894 Antony Chazapis
            sql = '''select distinct o.name, o.version_id from (%s) o, metadata m where o.name like ? and
595 58a6c894 Antony Chazapis
                        m.version_id = o.version_id and m.key in (%s) order by o.name'''
596 58a6c894 Antony Chazapis
            sql = sql % (self._sql_until(until), ', '.join('?' * len(keys)))
597 22dab079 Antony Chazapis
            param = (cont_prefix + prefix + '%',) + tuple(keys)
598 22dab079 Antony Chazapis
        else:
599 58a6c894 Antony Chazapis
            sql = 'select name, version_id from (%s) where name like ? order by name'
600 58a6c894 Antony Chazapis
            sql = sql % self._sql_until(until)
601 22dab079 Antony Chazapis
            param = (cont_prefix + prefix + '%',)
602 22dab079 Antony Chazapis
        c = self.con.execute(sql, param)
603 58a6c894 Antony Chazapis
        objects = [(x[0][len(cont_prefix):], x[1]) for x in c.fetchall()]
604 22dab079 Antony Chazapis
        if delimiter:
605 22dab079 Antony Chazapis
            pseudo_objects = []
606 22dab079 Antony Chazapis
            for x in objects:
607 58a6c894 Antony Chazapis
                pseudo_name = x[0]
608 22dab079 Antony Chazapis
                i = pseudo_name.find(delimiter, len(prefix))
609 22dab079 Antony Chazapis
                if not virtual:
610 22dab079 Antony Chazapis
                    # If the delimiter is not found, or the name ends
611 22dab079 Antony Chazapis
                    # with the delimiter's first occurence.
612 22dab079 Antony Chazapis
                    if i == -1 or len(pseudo_name) == i + len(delimiter):
613 58a6c894 Antony Chazapis
                        pseudo_objects.append(x)
614 22dab079 Antony Chazapis
                else:
615 22dab079 Antony Chazapis
                    # If the delimiter is found, keep up to (and including) the delimiter.
616 22dab079 Antony Chazapis
                    if i != -1:
617 22dab079 Antony Chazapis
                        pseudo_name = pseudo_name[:i + len(delimiter)]
618 58a6c894 Antony Chazapis
                    if pseudo_name not in [y[0] for y in pseudo_objects]:
619 83dd59c5 Antony Chazapis
                        if pseudo_name == x[0]:
620 83dd59c5 Antony Chazapis
                            pseudo_objects.append(x)
621 83dd59c5 Antony Chazapis
                        else:
622 83dd59c5 Antony Chazapis
                            pseudo_objects.append((pseudo_name, None))
623 22dab079 Antony Chazapis
            objects = pseudo_objects
624 22dab079 Antony Chazapis
        
625 22dab079 Antony Chazapis
        start = 0
626 22dab079 Antony Chazapis
        if marker:
627 22dab079 Antony Chazapis
            try:
628 58a6c894 Antony Chazapis
                start = [x[0] for x in objects].index(marker) + 1
629 22dab079 Antony Chazapis
            except ValueError:
630 22dab079 Antony Chazapis
                pass
631 22dab079 Antony Chazapis
        if not limit or limit > 10000:
632 22dab079 Antony Chazapis
            limit = 10000
633 22dab079 Antony Chazapis
        return objects[start:start + limit]
634 22dab079 Antony Chazapis
    
635 58a6c894 Antony Chazapis
    def _del_path(self, path):
636 22dab079 Antony Chazapis
        sql = '''delete from hashmaps where version_id in
637 58a6c894 Antony Chazapis
                    (select version_id from versions where name = ?)'''
638 58a6c894 Antony Chazapis
        self.con.execute(sql, (path,))
639 58a6c894 Antony Chazapis
        sql = '''delete from metadata where version_id in
640 58a6c894 Antony Chazapis
                    (select version_id from versions where name = ?)'''
641 22dab079 Antony Chazapis
        self.con.execute(sql, (path,))
642 58a6c894 Antony Chazapis
        sql = '''delete from versions where name = ?'''
643 b956618e Antony Chazapis
        self.con.execute(sql, (path,))
644 3436eeb0 Antony Chazapis
        sql = '''delete from permissions where name like ?'''
645 3436eeb0 Antony Chazapis
        self.con.execute(sql, (path + '%',)) # Redundant.
646 b956618e Antony Chazapis
        self.con.commit()