Statistics
| Branch: | Tag: | Revision:

root / pithos / backends / simple.py @ 3436eeb0

History | View | Annotate | Download (26.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 b956618e Antony Chazapis
from base import 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 58a6c894 Antony Chazapis
                    tstamp datetime default current_timestamp,
70 58a6c894 Antony Chazapis
                    size integer default 0,
71 58a6c894 Antony Chazapis
                    hide integer default 0)'''
72 b956618e Antony Chazapis
        self.con.execute(sql)
73 b956618e Antony Chazapis
        sql = '''create table if not exists metadata (
74 58a6c894 Antony Chazapis
                    version_id integer, key text, value text, primary key (version_id, key))'''
75 22dab079 Antony Chazapis
        self.con.execute(sql)
76 22dab079 Antony Chazapis
        sql = '''create table if not exists blocks (
77 22dab079 Antony Chazapis
                    block_id text, data blob, primary key (block_id))'''
78 22dab079 Antony Chazapis
        self.con.execute(sql)
79 22dab079 Antony Chazapis
        sql = '''create table if not exists hashmaps (
80 58a6c894 Antony Chazapis
                    version_id integer, pos integer, block_id text, primary key (version_id, pos))'''
81 b956618e Antony Chazapis
        self.con.execute(sql)
82 3436eeb0 Antony Chazapis
        sql = '''create table if not exists permissions (
83 3436eeb0 Antony Chazapis
                    name text, read text, write text, primary key (name))'''
84 3436eeb0 Antony Chazapis
        self.con.execute(sql)
85 b956618e Antony Chazapis
        self.con.commit()
86 b956618e Antony Chazapis
    
87 83dd59c5 Antony Chazapis
    def delete_account(self, user, account):
88 58a6c894 Antony Chazapis
        """Delete the account with the given name."""
89 58a6c894 Antony Chazapis
        
90 58a6c894 Antony Chazapis
        logger.debug("delete_account: %s", account)
91 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(account)
92 58a6c894 Antony Chazapis
        if count > 0:
93 58a6c894 Antony Chazapis
            raise IndexError('Account is not empty')
94 58a6c894 Antony Chazapis
        self._del_path(account) # Point of no return.
95 58a6c894 Antony Chazapis
    
96 83dd59c5 Antony Chazapis
    def get_account_meta(self, user, account, until=None):
97 b956618e Antony Chazapis
        """Return a dictionary with the account metadata."""
98 b956618e Antony Chazapis
        
99 58a6c894 Antony Chazapis
        logger.debug("get_account_meta: %s %s", account, until)
100 58a6c894 Antony Chazapis
        try:
101 58a6c894 Antony Chazapis
            version_id, mtime = self._get_accountinfo(account, until)
102 58a6c894 Antony Chazapis
        except NameError:
103 58a6c894 Antony Chazapis
            version_id = None
104 60be837c Antony Chazapis
            mtime = 0
105 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(account, until)
106 31a1c80d Antony Chazapis
        if mtime > tstamp:
107 31a1c80d Antony Chazapis
            tstamp = mtime
108 58a6c894 Antony Chazapis
        if until is None:
109 58a6c894 Antony Chazapis
            modified = tstamp
110 58a6c894 Antony Chazapis
        else:
111 58a6c894 Antony Chazapis
            modified = self._get_pathstats(account)[2] # Overall last modification
112 31a1c80d Antony Chazapis
            if mtime > modified:
113 31a1c80d Antony Chazapis
                modified = mtime
114 22dab079 Antony Chazapis
        
115 22dab079 Antony Chazapis
        # Proper count.
116 58a6c894 Antony Chazapis
        sql = 'select count(name) from (%s) where name glob ? and not name glob ?'
117 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
118 22dab079 Antony Chazapis
        c = self.con.execute(sql, (account + '/*', account + '/*/*'))
119 22dab079 Antony Chazapis
        row = c.fetchone()
120 22dab079 Antony Chazapis
        count = row[0]
121 22dab079 Antony Chazapis
        
122 58a6c894 Antony Chazapis
        meta = self._get_metadata(account, version_id)
123 22dab079 Antony Chazapis
        meta.update({'name': account, 'count': count, 'bytes': bytes})
124 58a6c894 Antony Chazapis
        if modified:
125 58a6c894 Antony Chazapis
            meta.update({'modified': modified})
126 58a6c894 Antony Chazapis
        if until is not None:
127 58a6c894 Antony Chazapis
            meta.update({'until_timestamp': tstamp})
128 b956618e Antony Chazapis
        return meta
129 b956618e Antony Chazapis
    
130 83dd59c5 Antony Chazapis
    def update_account_meta(self, user, account, meta, replace=False):
131 b956618e Antony Chazapis
        """Update the metadata associated with the account."""
132 b956618e Antony Chazapis
        
133 22dab079 Antony Chazapis
        logger.debug("update_account_meta: %s %s %s", account, meta, replace)
134 58a6c894 Antony Chazapis
        self._put_metadata(account, meta, replace)
135 58a6c894 Antony Chazapis
    
136 83dd59c5 Antony Chazapis
    def list_containers(self, user, account, marker=None, limit=10000, until=None):
137 58a6c894 Antony Chazapis
        """Return a list of containers existing under an account."""
138 58a6c894 Antony Chazapis
        
139 58a6c894 Antony Chazapis
        logger.debug("list_containers: %s %s %s %s", account, marker, limit, until)
140 58a6c894 Antony Chazapis
        return self._list_objects(account, '', '/', marker, limit, False, [], until)
141 b956618e Antony Chazapis
    
142 83dd59c5 Antony Chazapis
    def put_container(self, user, account, container):
143 b956618e Antony Chazapis
        """Create a new container with the given name."""
144 b956618e Antony Chazapis
        
145 58a6c894 Antony Chazapis
        logger.debug("put_container: %s %s", account, container)
146 22dab079 Antony Chazapis
        try:
147 58a6c894 Antony Chazapis
            path, version_id, mtime = self._get_containerinfo(account, container)
148 22dab079 Antony Chazapis
        except NameError:
149 58a6c894 Antony Chazapis
            path = os.path.join(account, container)
150 58a6c894 Antony Chazapis
            version_id = self._put_version(path)
151 b956618e Antony Chazapis
        else:
152 b956618e Antony Chazapis
            raise NameError('Container already exists')
153 b956618e Antony Chazapis
    
154 83dd59c5 Antony Chazapis
    def delete_container(self, user, account, container):
155 b956618e Antony Chazapis
        """Delete the container with the given name."""
156 b956618e Antony Chazapis
        
157 58a6c894 Antony Chazapis
        logger.debug("delete_container: %s %s", account, container)
158 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container)
159 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(path)
160 22dab079 Antony Chazapis
        if count > 0:
161 b956618e Antony Chazapis
            raise IndexError('Container is not empty')
162 58a6c894 Antony Chazapis
        self._del_path(path) # Point of no return.
163 58a6c894 Antony Chazapis
        self._copy_version(account, account, True, True) # New account version.
164 b956618e Antony Chazapis
    
165 83dd59c5 Antony Chazapis
    def get_container_meta(self, user, account, container, until=None):
166 b956618e Antony Chazapis
        """Return a dictionary with the container metadata."""
167 b956618e Antony Chazapis
        
168 58a6c894 Antony Chazapis
        logger.debug("get_container_meta: %s %s %s", account, container, until)
169 58a6c894 Antony Chazapis
        
170 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
171 58a6c894 Antony Chazapis
        count, bytes, tstamp = self._get_pathstats(path, until)
172 31a1c80d Antony Chazapis
        if mtime > tstamp:
173 31a1c80d Antony Chazapis
            tstamp = mtime
174 58a6c894 Antony Chazapis
        if until is None:
175 58a6c894 Antony Chazapis
            modified = tstamp
176 58a6c894 Antony Chazapis
        else:
177 31a1c80d Antony Chazapis
            modified = self._get_pathstats(path)[2] # Overall last modification
178 31a1c80d Antony Chazapis
            if mtime > modified:
179 31a1c80d Antony Chazapis
                modified = mtime
180 58a6c894 Antony Chazapis
        
181 58a6c894 Antony Chazapis
        meta = self._get_metadata(path, version_id)
182 58a6c894 Antony Chazapis
        meta.update({'name': container, 'count': count, 'bytes': bytes, 'modified': modified})
183 58a6c894 Antony Chazapis
        if until is not None:
184 58a6c894 Antony Chazapis
            meta.update({'until_timestamp': tstamp})
185 b956618e Antony Chazapis
        return meta
186 b956618e Antony Chazapis
    
187 83dd59c5 Antony Chazapis
    def update_container_meta(self, user, account, container, meta, replace=False):
188 b956618e Antony Chazapis
        """Update the metadata associated with the container."""
189 b956618e Antony Chazapis
        
190 58a6c894 Antony Chazapis
        logger.debug("update_container_meta: %s %s %s %s", account, container, meta, replace)
191 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container)
192 58a6c894 Antony Chazapis
        self._put_metadata(path, meta, replace)
193 b956618e Antony Chazapis
    
194 83dd59c5 Antony Chazapis
    def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None):
195 b956618e Antony Chazapis
        """Return a list of objects existing under a container."""
196 b956618e Antony Chazapis
        
197 58a6c894 Antony Chazapis
        logger.debug("list_objects: %s %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit, until)
198 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
199 58a6c894 Antony Chazapis
        return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, until)
200 22dab079 Antony Chazapis
    
201 83dd59c5 Antony Chazapis
    def list_object_meta(self, user, account, container, until=None):
202 22dab079 Antony Chazapis
        """Return a list with all the container's object meta keys."""
203 b956618e Antony Chazapis
        
204 58a6c894 Antony Chazapis
        logger.debug("list_object_meta: %s %s %s", account, container, until)
205 58a6c894 Antony Chazapis
        path, version_id, mtime = self._get_containerinfo(account, container, until)
206 58a6c894 Antony Chazapis
        sql = '''select distinct m.key from (%s) o, metadata m
207 58a6c894 Antony Chazapis
                    where m.version_id = o.version_id and o.name like ?'''
208 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
209 22dab079 Antony Chazapis
        c = self.con.execute(sql, (path + '/%',))
210 22dab079 Antony Chazapis
        return [x[0] for x in c.fetchall()]
211 b956618e Antony Chazapis
    
212 83dd59c5 Antony Chazapis
    def get_object_meta(self, user, account, container, name, version=None):
213 b956618e Antony Chazapis
        """Return a dictionary with the object metadata."""
214 b956618e Antony Chazapis
        
215 58a6c894 Antony Chazapis
        logger.debug("get_object_meta: %s %s %s %s", account, container, name, version)
216 58a6c894 Antony Chazapis
        path, version_id, mtime, size = self._get_objectinfo(account, container, name, version)
217 58a6c894 Antony Chazapis
        if version is None:
218 58a6c894 Antony Chazapis
            modified = mtime
219 58a6c894 Antony Chazapis
        else:
220 58a6c894 Antony Chazapis
            modified = self._get_version(path)[1] # Overall last modification
221 58a6c894 Antony Chazapis
        
222 58a6c894 Antony Chazapis
        meta = self._get_metadata(path, version_id)
223 58a6c894 Antony Chazapis
        meta.update({'name': name, 'bytes': size, 'version': version_id, 'version_timestamp': mtime, 'modified': modified})
224 b956618e Antony Chazapis
        return meta
225 b956618e Antony Chazapis
    
226 83dd59c5 Antony Chazapis
    def update_object_meta(self, user, account, container, name, meta, replace=False):
227 b956618e Antony Chazapis
        """Update the metadata associated with the object."""
228 b956618e Antony Chazapis
        
229 22dab079 Antony Chazapis
        logger.debug("update_object_meta: %s %s %s %s %s", account, container, name, meta, replace)
230 58a6c894 Antony Chazapis
        path, version_id, mtime, size = self._get_objectinfo(account, container, name)
231 58a6c894 Antony Chazapis
        self._put_metadata(path, meta, replace)
232 b956618e Antony Chazapis
    
233 3436eeb0 Antony Chazapis
    def get_object_permissions(self, user, account, container, name):
234 3436eeb0 Antony Chazapis
        """Return a dictionary with the object permissions."""
235 3436eeb0 Antony Chazapis
        
236 3436eeb0 Antony Chazapis
        logger.debug("get_object_permissions: %s %s %s", account, container, name)
237 3436eeb0 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
238 3436eeb0 Antony Chazapis
        return self._get_permissions(path)
239 3436eeb0 Antony Chazapis
    
240 3436eeb0 Antony Chazapis
    def update_object_permissions(self, user, account, container, name, permissions):
241 3436eeb0 Antony Chazapis
        """Update the permissions associated with the object."""
242 3436eeb0 Antony Chazapis
        
243 3436eeb0 Antony Chazapis
        logger.debug("update_object_permissions: %s %s %s %s", account, container, name, permissions)
244 3436eeb0 Antony Chazapis
        path = self._get_objectinfo(account, container, name)[0]
245 3436eeb0 Antony Chazapis
        r, w = self._check_permissions(path, permissions)
246 3436eeb0 Antony Chazapis
        self._put_permissions(path, r, w)
247 3436eeb0 Antony Chazapis
    
248 83dd59c5 Antony Chazapis
    def get_object_hashmap(self, user, account, container, name, version=None):
249 22dab079 Antony Chazapis
        """Return the object's size and a list with partial hashes."""
250 b956618e Antony Chazapis
        
251 22dab079 Antony Chazapis
        logger.debug("get_object_hashmap: %s %s %s %s", account, container, name, version)
252 58a6c894 Antony Chazapis
        path, version_id, mtime, size = self._get_objectinfo(account, container, name, version)
253 58a6c894 Antony Chazapis
        sql = 'select block_id from hashmaps where version_id = ? order by pos asc'
254 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (version_id,))
255 22dab079 Antony Chazapis
        hashmap = [x[0] for x in c.fetchall()]
256 22dab079 Antony Chazapis
        return size, hashmap
257 22dab079 Antony Chazapis
    
258 3436eeb0 Antony Chazapis
    def update_object_hashmap(self, user, account, container, name, size, hashmap, meta={}, replace_meta=False, permissions={}):
259 22dab079 Antony Chazapis
        """Create/update an object with the specified size and partial hashes."""
260 b956618e Antony Chazapis
        
261 cfe6939d Antony Chazapis
        logger.debug("update_object_hashmap: %s %s %s %s %s", account, container, name, size, hashmap)
262 58a6c894 Antony Chazapis
        path = self._get_containerinfo(account, container)[0]
263 58a6c894 Antony Chazapis
        path = os.path.join(path, name)
264 3436eeb0 Antony Chazapis
        if permissions:
265 3436eeb0 Antony Chazapis
            r, w = self._check_permissions(path, permissions)
266 83dd59c5 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(path, path, not replace_meta, False)
267 58a6c894 Antony Chazapis
        sql = 'update versions set size = ? where version_id = ?'
268 58a6c894 Antony Chazapis
        self.con.execute(sql, (size, dest_version_id))
269 58a6c894 Antony Chazapis
        # TODO: Check for block_id existence.
270 22dab079 Antony Chazapis
        for i in range(len(hashmap)):
271 22dab079 Antony Chazapis
            sql = 'insert or replace into hashmaps (version_id, pos, block_id) values (?, ?, ?)'
272 58a6c894 Antony Chazapis
            self.con.execute(sql, (dest_version_id, i, hashmap[i]))
273 83dd59c5 Antony Chazapis
        for k, v in meta.iteritems():
274 83dd59c5 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
275 83dd59c5 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
276 3436eeb0 Antony Chazapis
        if permissions:
277 3436eeb0 Antony Chazapis
            sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
278 3436eeb0 Antony Chazapis
            self.con.execute(sql, (path, r, w))
279 22dab079 Antony Chazapis
        self.con.commit()
280 22dab079 Antony Chazapis
    
281 3436eeb0 Antony Chazapis
    def copy_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions={}, src_version=None):
282 22dab079 Antony Chazapis
        """Copy an object's data and metadata."""
283 b956618e Antony Chazapis
        
284 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)
285 6d817842 Antony Chazapis
        self._get_containerinfo(account, src_container)
286 58a6c894 Antony Chazapis
        if src_version is None:
287 58a6c894 Antony Chazapis
            src_path = self._get_objectinfo(account, src_container, src_name)[0]
288 22dab079 Antony Chazapis
        else:
289 58a6c894 Antony Chazapis
            src_path = os.path.join(account, src_container, src_name)
290 58a6c894 Antony Chazapis
        dest_path = self._get_containerinfo(account, dest_container)[0]
291 58a6c894 Antony Chazapis
        dest_path = os.path.join(dest_path, dest_name)
292 3436eeb0 Antony Chazapis
        if permissions:
293 3436eeb0 Antony Chazapis
            r, w = self._check_permissions(dest_path, permissions)
294 58a6c894 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(src_path, dest_path, not replace_meta, True, src_version)
295 58a6c894 Antony Chazapis
        for k, v in dest_meta.iteritems():
296 58a6c894 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
297 58a6c894 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
298 3436eeb0 Antony Chazapis
        if permissions:
299 3436eeb0 Antony Chazapis
            sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
300 3436eeb0 Antony Chazapis
            self.con.execute(sql, (dest_path, r, w))
301 58a6c894 Antony Chazapis
        self.con.commit()
302 b956618e Antony Chazapis
    
303 3436eeb0 Antony Chazapis
    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions={}):
304 b956618e Antony Chazapis
        """Move an object's data and metadata."""
305 b956618e Antony Chazapis
        
306 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)
307 3436eeb0 Antony Chazapis
        self.copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
308 83dd59c5 Antony Chazapis
        self.delete_object(user, account, src_container, src_name)
309 b956618e Antony Chazapis
    
310 83dd59c5 Antony Chazapis
    def delete_object(self, user, account, container, name):
311 b956618e Antony Chazapis
        """Delete an object."""
312 b956618e Antony Chazapis
        
313 b956618e Antony Chazapis
        logger.debug("delete_object: %s %s %s", account, container, name)
314 58a6c894 Antony Chazapis
        path, version_id, mtime, size = self._get_objectinfo(account, container, name)
315 58a6c894 Antony Chazapis
        self._put_version(path, 0, 1)
316 3436eeb0 Antony Chazapis
        sql = 'delete from permissions where name = ?'
317 3436eeb0 Antony Chazapis
        self.con.execute(sql, (path,))
318 3436eeb0 Antony Chazapis
        self.con.commit()
319 58a6c894 Antony Chazapis
    
320 83dd59c5 Antony Chazapis
    def list_versions(self, user, account, container, name):
321 83dd59c5 Antony Chazapis
        """Return a list of all (version, version_timestamp) tuples for an object."""
322 58a6c894 Antony Chazapis
        
323 83dd59c5 Antony Chazapis
        logger.debug("list_versions: %s %s %s", account, container, name)
324 58a6c894 Antony Chazapis
        # This will even show deleted versions.
325 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
326 58a6c894 Antony Chazapis
        sql = '''select distinct version_id, strftime('%s', tstamp) from versions where name = ?'''
327 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path,))
328 c9af0703 Antony Chazapis
        return [(int(x[0]), int(x[1])) for x in c.fetchall()]
329 b956618e Antony Chazapis
    
330 22dab079 Antony Chazapis
    def get_block(self, hash):
331 22dab079 Antony Chazapis
        """Return a block's data."""
332 22dab079 Antony Chazapis
        
333 cfe6939d Antony Chazapis
        logger.debug("get_block: %s", hash)
334 22dab079 Antony Chazapis
        c = self.con.execute('select data from blocks where block_id = ?', (hash,))
335 22dab079 Antony Chazapis
        row = c.fetchone()
336 22dab079 Antony Chazapis
        if row:
337 22dab079 Antony Chazapis
            return str(row[0])
338 22dab079 Antony Chazapis
        else:
339 22dab079 Antony Chazapis
            raise NameError('Block does not exist')
340 b956618e Antony Chazapis
    
341 22dab079 Antony Chazapis
    def put_block(self, data):
342 22dab079 Antony Chazapis
        """Create a block and return the hash."""
343 22dab079 Antony Chazapis
        
344 cfe6939d Antony Chazapis
        logger.debug("put_block: %s", len(data))
345 22dab079 Antony Chazapis
        h = hashlib.new(self.hash_algorithm)
346 22dab079 Antony Chazapis
        h.update(data.rstrip('\x00'))
347 22dab079 Antony Chazapis
        hash = h.hexdigest()
348 22dab079 Antony Chazapis
        sql = 'insert or ignore into blocks (block_id, data) values (?, ?)'
349 22dab079 Antony Chazapis
        self.con.execute(sql, (hash, buffer(data)))
350 22dab079 Antony Chazapis
        self.con.commit()
351 22dab079 Antony Chazapis
        return hash
352 22dab079 Antony Chazapis
    
353 22dab079 Antony Chazapis
    def update_block(self, hash, data, offset=0):
354 22dab079 Antony Chazapis
        """Update a known block and return the hash."""
355 22dab079 Antony Chazapis
        
356 cfe6939d Antony Chazapis
        logger.debug("update_block: %s %s %s", hash, len(data), offset)
357 cfe6939d Antony Chazapis
        if offset == 0 and len(data) == self.block_size:
358 cfe6939d Antony Chazapis
            return self.put_block(data)
359 22dab079 Antony Chazapis
        src_data = self.get_block(hash)
360 22dab079 Antony Chazapis
        bs = self.block_size
361 22dab079 Antony Chazapis
        if offset < 0 or offset > bs or offset + len(data) > bs:
362 22dab079 Antony Chazapis
            raise IndexError('Offset or data outside block limits')
363 22dab079 Antony Chazapis
        dest_data = src_data[:offset] + data + src_data[offset + len(data):]
364 22dab079 Antony Chazapis
        return self.put_block(dest_data)
365 b956618e Antony Chazapis
    
366 58a6c894 Antony Chazapis
    def _sql_until(self, until=None):
367 58a6c894 Antony Chazapis
        """Return the sql to get the latest versions until the timestamp given."""
368 58a6c894 Antony Chazapis
        if until is None:
369 58a6c894 Antony Chazapis
            until = int(time.time())
370 58a6c894 Antony Chazapis
        sql = '''select version_id, name, strftime('%s', tstamp) as tstamp, size from versions v
371 58a6c894 Antony Chazapis
                    where version_id = (select max(version_id) from versions
372 58a6c894 Antony Chazapis
                                        where v.name = name and tstamp <= datetime(%s, 'unixepoch'))
373 58a6c894 Antony Chazapis
                    and hide = 0'''
374 58a6c894 Antony Chazapis
        return sql % ('%s', until)
375 58a6c894 Antony Chazapis
    
376 58a6c894 Antony Chazapis
    def _get_pathstats(self, path, until=None):
377 58a6c894 Antony Chazapis
        """Return count and sum of size of everything under path and latest timestamp."""
378 58a6c894 Antony Chazapis
        
379 58a6c894 Antony Chazapis
        sql = 'select count(version_id), total(size), max(tstamp) from (%s) where name like ?'
380 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
381 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path + '/%',))
382 b956618e Antony Chazapis
        row = c.fetchone()
383 58a6c894 Antony Chazapis
        tstamp = row[2] if row[2] is not None else 0
384 58a6c894 Antony Chazapis
        return int(row[0]), int(row[1]), int(tstamp)
385 58a6c894 Antony Chazapis
    
386 58a6c894 Antony Chazapis
    def _get_version(self, path, version=None):
387 cb146cf9 Antony Chazapis
        if version is None:
388 58a6c894 Antony Chazapis
            sql = '''select version_id, strftime('%s', tstamp), size, hide from versions where name = ?
389 58a6c894 Antony Chazapis
                        order by version_id desc limit 1'''
390 58a6c894 Antony Chazapis
            c = self.con.execute(sql, (path,))
391 58a6c894 Antony Chazapis
            row = c.fetchone()
392 58a6c894 Antony Chazapis
            if not row or int(row[3]):
393 58a6c894 Antony Chazapis
                raise NameError('Object does not exist')
394 b956618e Antony Chazapis
        else:
395 58a6c894 Antony Chazapis
            sql = '''select version_id, strftime('%s', tstamp), size from versions where name = ?
396 58a6c894 Antony Chazapis
                        and version_id = ?'''
397 58a6c894 Antony Chazapis
            c = self.con.execute(sql, (path, version))
398 58a6c894 Antony Chazapis
            row = c.fetchone()
399 58a6c894 Antony Chazapis
            if not row:
400 58a6c894 Antony Chazapis
                raise IndexError('Version does not exist')
401 58a6c894 Antony Chazapis
        return str(row[0]), int(row[1]), int(row[2])
402 58a6c894 Antony Chazapis
    
403 58a6c894 Antony Chazapis
    def _put_version(self, path, size=0, hide=0):
404 58a6c894 Antony Chazapis
        sql = 'insert into versions (name, size, hide) values (?, ?, ?)'
405 58a6c894 Antony Chazapis
        id = self.con.execute(sql, (path, size, hide)).lastrowid
406 b956618e Antony Chazapis
        self.con.commit()
407 b956618e Antony Chazapis
        return str(id)
408 b956618e Antony Chazapis
    
409 58a6c894 Antony Chazapis
    def _copy_version(self, src_path, dest_path, copy_meta=True, copy_data=True, src_version=None):
410 58a6c894 Antony Chazapis
        if src_version is not None:
411 58a6c894 Antony Chazapis
            src_version_id, mtime, size = self._get_version(src_path, src_version)
412 58a6c894 Antony Chazapis
        else:
413 58a6c894 Antony Chazapis
            # Latest or create from scratch.
414 58a6c894 Antony Chazapis
            try:
415 58a6c894 Antony Chazapis
                src_version_id, mtime, size = self._get_version(src_path)
416 58a6c894 Antony Chazapis
            except NameError:
417 58a6c894 Antony Chazapis
                src_version_id = None
418 58a6c894 Antony Chazapis
                size = 0
419 58a6c894 Antony Chazapis
        if not copy_data:
420 58a6c894 Antony Chazapis
            size = 0
421 58a6c894 Antony Chazapis
        dest_version_id = self._put_version(dest_path, size)
422 58a6c894 Antony Chazapis
        if copy_meta and src_version_id is not None:
423 58a6c894 Antony Chazapis
            sql = 'insert into metadata select %s, key, value from metadata where version_id = ?'
424 58a6c894 Antony Chazapis
            sql = sql % dest_version_id
425 58a6c894 Antony Chazapis
            self.con.execute(sql, (src_version_id,))
426 58a6c894 Antony Chazapis
        if copy_data and src_version_id is not None:
427 58a6c894 Antony Chazapis
            sql = 'insert into hashmaps select %s, pos, block_id from hashmaps where version_id = ?'
428 58a6c894 Antony Chazapis
            sql = sql % dest_version_id
429 58a6c894 Antony Chazapis
            self.con.execute(sql, (src_version_id,))
430 58a6c894 Antony Chazapis
        self.con.commit()
431 58a6c894 Antony Chazapis
        return src_version_id, dest_version_id
432 58a6c894 Antony Chazapis
    
433 58a6c894 Antony Chazapis
    def _get_versioninfo(self, account, container, name, until=None):
434 58a6c894 Antony Chazapis
        """Return path, latest version, associated timestamp and size until the timestamp given."""
435 58a6c894 Antony Chazapis
        
436 58a6c894 Antony Chazapis
        p = (account, container, name)
437 22dab079 Antony Chazapis
        try:
438 58a6c894 Antony Chazapis
            p = p[:p.index(None)]
439 58a6c894 Antony Chazapis
        except ValueError:
440 58a6c894 Antony Chazapis
            pass
441 58a6c894 Antony Chazapis
        path = os.path.join(*p)
442 58a6c894 Antony Chazapis
        sql = '''select version_id, tstamp, size from (%s) where name = ?'''
443 58a6c894 Antony Chazapis
        sql = sql % self._sql_until(until)
444 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (path,))
445 58a6c894 Antony Chazapis
        row = c.fetchone()
446 58a6c894 Antony Chazapis
        if row is None:
447 58a6c894 Antony Chazapis
            raise NameError('Path does not exist')
448 58a6c894 Antony Chazapis
        return path, str(row[0]), int(row[1]), int(row[2])
449 58a6c894 Antony Chazapis
    
450 58a6c894 Antony Chazapis
    def _get_accountinfo(self, account, until=None):
451 58a6c894 Antony Chazapis
        try:
452 58a6c894 Antony Chazapis
            path, version_id, mtime, size = self._get_versioninfo(account, None, None, until)
453 58a6c894 Antony Chazapis
            return version_id, mtime
454 58a6c894 Antony Chazapis
        except:
455 58a6c894 Antony Chazapis
            raise NameError('Account does not exist')
456 58a6c894 Antony Chazapis
    
457 58a6c894 Antony Chazapis
    def _get_containerinfo(self, account, container, until=None):
458 58a6c894 Antony Chazapis
        try:
459 58a6c894 Antony Chazapis
            path, version_id, mtime, size = self._get_versioninfo(account, container, None, until)
460 58a6c894 Antony Chazapis
            return path, version_id, mtime
461 58a6c894 Antony Chazapis
        except:
462 22dab079 Antony Chazapis
            raise NameError('Container does not exist')
463 22dab079 Antony Chazapis
    
464 22dab079 Antony Chazapis
    def _get_objectinfo(self, account, container, name, version=None):
465 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
466 58a6c894 Antony Chazapis
        version_id, mtime, size = self._get_version(path, version)
467 58a6c894 Antony Chazapis
        return path, version_id, mtime, size
468 22dab079 Antony Chazapis
    
469 58a6c894 Antony Chazapis
    def _get_metadata(self, path, version):
470 58a6c894 Antony Chazapis
        sql = 'select key, value from metadata where version_id = ?'
471 58a6c894 Antony Chazapis
        c = self.con.execute(sql, (version,))
472 58a6c894 Antony Chazapis
        return dict(c.fetchall())
473 58a6c894 Antony Chazapis
    
474 58a6c894 Antony Chazapis
    def _put_metadata(self, path, meta, replace=False):
475 58a6c894 Antony Chazapis
        """Create a new version and store metadata."""
476 58a6c894 Antony Chazapis
        
477 58a6c894 Antony Chazapis
        src_version_id, dest_version_id = self._copy_version(path, path, not replace, True)
478 58a6c894 Antony Chazapis
        for k, v in meta.iteritems():
479 58a6c894 Antony Chazapis
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
480 58a6c894 Antony Chazapis
            self.con.execute(sql, (dest_version_id, k, v))
481 58a6c894 Antony Chazapis
        self.con.commit()
482 58a6c894 Antony Chazapis
    
483 3436eeb0 Antony Chazapis
    def _can_read(self, user, path):
484 3436eeb0 Antony Chazapis
        return True
485 3436eeb0 Antony Chazapis
    
486 3436eeb0 Antony Chazapis
    def _can_write(self, user, path):
487 3436eeb0 Antony Chazapis
        return True
488 3436eeb0 Antony Chazapis
    
489 3436eeb0 Antony Chazapis
    def _check_permissions(self, path, permissions):
490 3436eeb0 Antony Chazapis
        # Check for existing permissions.
491 3436eeb0 Antony Chazapis
        sql = '''select name from permissions
492 3436eeb0 Antony Chazapis
                    where name != ? and (name like ? or ? like name || ?)'''
493 3436eeb0 Antony Chazapis
        c = self.con.execute(sql, (path, path + '%', path, '%'))
494 3436eeb0 Antony Chazapis
        if c.fetchall() is not None:
495 3436eeb0 Antony Chazapis
            raise AttributeError('Permissions already set')
496 3436eeb0 Antony Chazapis
        
497 3436eeb0 Antony Chazapis
        # Format given permissions set.
498 3436eeb0 Antony Chazapis
        r = permissions.get('read', [])
499 3436eeb0 Antony Chazapis
        w = permissions.get('write', [])
500 3436eeb0 Antony Chazapis
        if True in [False or '*' in x or ',' in x for x in r]:
501 3436eeb0 Antony Chazapis
            raise ValueError('Bad characters in read permissions')
502 3436eeb0 Antony Chazapis
        if True in [False or '*' in x or ',' in x for x in w]:
503 3436eeb0 Antony Chazapis
            raise ValueError('Bad characters in write permissions')
504 3436eeb0 Antony Chazapis
        r = ','.join(r)
505 3436eeb0 Antony Chazapis
        w = ','.join(w)
506 3436eeb0 Antony Chazapis
        if 'public' in permissions:
507 3436eeb0 Antony Chazapis
            r = '*'
508 3436eeb0 Antony Chazapis
        if 'private' in permissions:
509 3436eeb0 Antony Chazapis
            r = ''
510 3436eeb0 Antony Chazapis
            w = ''
511 3436eeb0 Antony Chazapis
        return r, w
512 3436eeb0 Antony Chazapis
    
513 3436eeb0 Antony Chazapis
    def _get_permissions(self, path):
514 3436eeb0 Antony Chazapis
        sql = 'select read, write from permissions where name = ?'
515 3436eeb0 Antony Chazapis
        c = self.con.execute(sql, (path,))
516 3436eeb0 Antony Chazapis
        row = c.fetchone()
517 3436eeb0 Antony Chazapis
        if not row:
518 3436eeb0 Antony Chazapis
            return {}
519 3436eeb0 Antony Chazapis
        
520 3436eeb0 Antony Chazapis
        r, w = row
521 3436eeb0 Antony Chazapis
        if r == '' and w == '':
522 3436eeb0 Antony Chazapis
            return {'private': True}
523 3436eeb0 Antony Chazapis
        ret = {}
524 3436eeb0 Antony Chazapis
        if w != '':
525 3436eeb0 Antony Chazapis
            ret['write'] = w.split(',')
526 3436eeb0 Antony Chazapis
        if r != '':
527 3436eeb0 Antony Chazapis
            if r == '*':
528 3436eeb0 Antony Chazapis
                ret['public'] = True
529 3436eeb0 Antony Chazapis
            else:
530 3436eeb0 Antony Chazapis
                ret['read'] = r.split(',')        
531 3436eeb0 Antony Chazapis
        return ret
532 3436eeb0 Antony Chazapis
    
533 3436eeb0 Antony Chazapis
    def _put_permissions(self, path, r, w):
534 3436eeb0 Antony Chazapis
        sql = 'insert or replace into permissions (name, read, write) values (?, ?, ?)'
535 3436eeb0 Antony Chazapis
        self.con.execute(sql, (path, r, w))
536 3436eeb0 Antony Chazapis
        self.con.commit()
537 3436eeb0 Antony Chazapis
    
538 58a6c894 Antony Chazapis
    def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None):
539 22dab079 Antony Chazapis
        cont_prefix = path + '/'
540 22dab079 Antony Chazapis
        if keys and len(keys) > 0:
541 58a6c894 Antony Chazapis
            sql = '''select distinct o.name, o.version_id from (%s) o, metadata m where o.name like ? and
542 58a6c894 Antony Chazapis
                        m.version_id = o.version_id and m.key in (%s) order by o.name'''
543 58a6c894 Antony Chazapis
            sql = sql % (self._sql_until(until), ', '.join('?' * len(keys)))
544 22dab079 Antony Chazapis
            param = (cont_prefix + prefix + '%',) + tuple(keys)
545 22dab079 Antony Chazapis
        else:
546 58a6c894 Antony Chazapis
            sql = 'select name, version_id from (%s) where name like ? order by name'
547 58a6c894 Antony Chazapis
            sql = sql % self._sql_until(until)
548 22dab079 Antony Chazapis
            param = (cont_prefix + prefix + '%',)
549 22dab079 Antony Chazapis
        c = self.con.execute(sql, param)
550 58a6c894 Antony Chazapis
        objects = [(x[0][len(cont_prefix):], x[1]) for x in c.fetchall()]
551 22dab079 Antony Chazapis
        if delimiter:
552 22dab079 Antony Chazapis
            pseudo_objects = []
553 22dab079 Antony Chazapis
            for x in objects:
554 58a6c894 Antony Chazapis
                pseudo_name = x[0]
555 22dab079 Antony Chazapis
                i = pseudo_name.find(delimiter, len(prefix))
556 22dab079 Antony Chazapis
                if not virtual:
557 22dab079 Antony Chazapis
                    # If the delimiter is not found, or the name ends
558 22dab079 Antony Chazapis
                    # with the delimiter's first occurence.
559 22dab079 Antony Chazapis
                    if i == -1 or len(pseudo_name) == i + len(delimiter):
560 58a6c894 Antony Chazapis
                        pseudo_objects.append(x)
561 22dab079 Antony Chazapis
                else:
562 22dab079 Antony Chazapis
                    # If the delimiter is found, keep up to (and including) the delimiter.
563 22dab079 Antony Chazapis
                    if i != -1:
564 22dab079 Antony Chazapis
                        pseudo_name = pseudo_name[:i + len(delimiter)]
565 58a6c894 Antony Chazapis
                    if pseudo_name not in [y[0] for y in pseudo_objects]:
566 83dd59c5 Antony Chazapis
                        if pseudo_name == x[0]:
567 83dd59c5 Antony Chazapis
                            pseudo_objects.append(x)
568 83dd59c5 Antony Chazapis
                        else:
569 83dd59c5 Antony Chazapis
                            pseudo_objects.append((pseudo_name, None))
570 22dab079 Antony Chazapis
            objects = pseudo_objects
571 22dab079 Antony Chazapis
        
572 22dab079 Antony Chazapis
        start = 0
573 22dab079 Antony Chazapis
        if marker:
574 22dab079 Antony Chazapis
            try:
575 58a6c894 Antony Chazapis
                start = [x[0] for x in objects].index(marker) + 1
576 22dab079 Antony Chazapis
            except ValueError:
577 22dab079 Antony Chazapis
                pass
578 22dab079 Antony Chazapis
        if not limit or limit > 10000:
579 22dab079 Antony Chazapis
            limit = 10000
580 22dab079 Antony Chazapis
        return objects[start:start + limit]
581 22dab079 Antony Chazapis
    
582 58a6c894 Antony Chazapis
    def _del_path(self, path):
583 22dab079 Antony Chazapis
        sql = '''delete from hashmaps where version_id in
584 58a6c894 Antony Chazapis
                    (select version_id from versions where name = ?)'''
585 58a6c894 Antony Chazapis
        self.con.execute(sql, (path,))
586 58a6c894 Antony Chazapis
        sql = '''delete from metadata where version_id in
587 58a6c894 Antony Chazapis
                    (select version_id from versions where name = ?)'''
588 22dab079 Antony Chazapis
        self.con.execute(sql, (path,))
589 58a6c894 Antony Chazapis
        sql = '''delete from versions where name = ?'''
590 b956618e Antony Chazapis
        self.con.execute(sql, (path,))
591 3436eeb0 Antony Chazapis
        sql = '''delete from permissions where name like ?'''
592 3436eeb0 Antony Chazapis
        self.con.execute(sql, (path + '%',)) # Redundant.
593 b956618e Antony Chazapis
        self.con.commit()