Statistics
| Branch: | Tag: | Revision:

root / pithos / backends / simple.py @ e3f9bbaa

History | View | Annotate | Download (19.5 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 22dab079 Antony Chazapis
    def __init__(self, db):
56 22dab079 Antony Chazapis
        self.hash_algorithm = 'sha1'
57 22dab079 Antony Chazapis
        self.block_size = 128 * 1024 # 128KB
58 b956618e Antony Chazapis
        
59 22dab079 Antony Chazapis
        basepath = os.path.split(db)[0]
60 22dab079 Antony Chazapis
        if basepath and not os.path.exists(basepath):
61 b956618e Antony Chazapis
            os.makedirs(basepath)
62 22dab079 Antony Chazapis
        
63 b956618e Antony Chazapis
        self.con = sqlite3.connect(db)
64 22dab079 Antony Chazapis
        sql = '''create table if not exists objects (
65 22dab079 Antony Chazapis
                    name text, tstamp text, primary key (name))'''
66 b956618e Antony Chazapis
        self.con.execute(sql)
67 b956618e Antony Chazapis
        sql = '''create table if not exists metadata (
68 22dab079 Antony Chazapis
                    name text, key text, value text, primary key (name, key))'''
69 22dab079 Antony Chazapis
        self.con.execute(sql)
70 22dab079 Antony Chazapis
        sql = '''create table if not exists versions (
71 22dab079 Antony Chazapis
                    object_id int, version int, size int, primary key (object_id, version))'''
72 22dab079 Antony Chazapis
        self.con.execute(sql)
73 22dab079 Antony Chazapis
        sql = '''create table if not exists blocks (
74 22dab079 Antony Chazapis
                    block_id text, data blob, primary key (block_id))'''
75 22dab079 Antony Chazapis
        self.con.execute(sql)
76 22dab079 Antony Chazapis
        sql = '''create table if not exists hashmaps (
77 22dab079 Antony Chazapis
                    version_id int, pos int, block_id text, primary key (version_id, pos))'''
78 b956618e Antony Chazapis
        self.con.execute(sql)
79 b956618e Antony Chazapis
        self.con.commit()
80 b956618e Antony Chazapis
    
81 b956618e Antony Chazapis
    def get_account_meta(self, account):
82 b956618e Antony Chazapis
        """Return a dictionary with the account metadata."""
83 b956618e Antony Chazapis
        
84 b956618e Antony Chazapis
        logger.debug("get_account_meta: %s", account)
85 22dab079 Antony Chazapis
        count, bytes = self._get_pathstats(account)
86 22dab079 Antony Chazapis
        
87 22dab079 Antony Chazapis
        # Proper count.
88 22dab079 Antony Chazapis
        sql = 'select count(name) from objects where name glob ? and not name glob ?'
89 22dab079 Antony Chazapis
        c = self.con.execute(sql, (account + '/*', account + '/*/*'))
90 22dab079 Antony Chazapis
        row = c.fetchone()
91 22dab079 Antony Chazapis
        count = row[0]
92 22dab079 Antony Chazapis
        
93 b956618e Antony Chazapis
        meta = self._get_metadata(account)
94 22dab079 Antony Chazapis
        meta.update({'name': account, 'count': count, 'bytes': bytes})
95 b956618e Antony Chazapis
        return meta
96 b956618e Antony Chazapis
    
97 22dab079 Antony Chazapis
    def update_account_meta(self, account, meta, replace=False):
98 b956618e Antony Chazapis
        """Update the metadata associated with the account."""
99 b956618e Antony Chazapis
        
100 22dab079 Antony Chazapis
        logger.debug("update_account_meta: %s %s %s", account, meta, replace)
101 22dab079 Antony Chazapis
        self._update_metadata(account, None, None, meta, replace)
102 b956618e Antony Chazapis
    
103 22dab079 Antony Chazapis
    def put_container(self, account, name):
104 b956618e Antony Chazapis
        """Create a new container with the given name."""
105 b956618e Antony Chazapis
        
106 22dab079 Antony Chazapis
        logger.debug("put_container: %s %s", account, name)
107 22dab079 Antony Chazapis
        try:
108 22dab079 Antony Chazapis
            path, link, tstamp = self._get_containerinfo(account, name)
109 22dab079 Antony Chazapis
        except NameError:
110 22dab079 Antony Chazapis
            path = os.path.join(account, name)
111 22dab079 Antony Chazapis
            link = self._put_linkinfo(path)
112 b956618e Antony Chazapis
        else:
113 b956618e Antony Chazapis
            raise NameError('Container already exists')
114 b956618e Antony Chazapis
        self._update_metadata(account, name, None, None)
115 b956618e Antony Chazapis
    
116 b956618e Antony Chazapis
    def delete_container(self, account, name):
117 b956618e Antony Chazapis
        """Delete the container with the given name."""
118 b956618e Antony Chazapis
        
119 b956618e Antony Chazapis
        logger.debug("delete_container: %s %s", account, name)
120 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, name)
121 22dab079 Antony Chazapis
        count, bytes = self._get_pathstats(path)
122 22dab079 Antony Chazapis
        if count > 0:
123 b956618e Antony Chazapis
            raise IndexError('Container is not empty')
124 22dab079 Antony Chazapis
        self._del_path(path)
125 b956618e Antony Chazapis
        self._update_metadata(account, None, None, None)
126 b956618e Antony Chazapis
    
127 b956618e Antony Chazapis
    def get_container_meta(self, account, name):
128 b956618e Antony Chazapis
        """Return a dictionary with the container metadata."""
129 b956618e Antony Chazapis
        
130 b956618e Antony Chazapis
        logger.debug("get_container_meta: %s %s", account, name)
131 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, name)
132 22dab079 Antony Chazapis
        count, bytes = self._get_pathstats(path)
133 22dab079 Antony Chazapis
        meta = self._get_metadata(path)
134 22dab079 Antony Chazapis
        meta.update({'name': name, 'count': count, 'bytes': bytes, 'created': tstamp})
135 b956618e Antony Chazapis
        return meta
136 b956618e Antony Chazapis
    
137 22dab079 Antony Chazapis
    def update_container_meta(self, account, name, meta, replace=False):
138 b956618e Antony Chazapis
        """Update the metadata associated with the container."""
139 b956618e Antony Chazapis
        
140 22dab079 Antony Chazapis
        logger.debug("update_container_meta: %s %s %s %s", account, name, meta, replace)
141 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, name)
142 22dab079 Antony Chazapis
        self._update_metadata(account, name, None, meta, replace)
143 b956618e Antony Chazapis
    
144 b956618e Antony Chazapis
    def list_containers(self, account, marker=None, limit=10000):
145 b956618e Antony Chazapis
        """Return a list of containers existing under an account."""
146 b956618e Antony Chazapis
        
147 b956618e Antony Chazapis
        logger.debug("list_containers: %s %s %s", account, marker, limit)
148 22dab079 Antony Chazapis
        return self._list_objects(account, '', '/', marker, limit, False, [])
149 b956618e Antony Chazapis
    
150 22dab079 Antony Chazapis
    def list_objects(self, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[]):
151 b956618e Antony Chazapis
        """Return a list of objects existing under a container."""
152 b956618e Antony Chazapis
        
153 b956618e Antony Chazapis
        logger.debug("list_objects: %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit)
154 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, container)
155 22dab079 Antony Chazapis
        return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys)
156 22dab079 Antony Chazapis
    
157 22dab079 Antony Chazapis
    def list_object_meta(self, account, name):
158 22dab079 Antony Chazapis
        """Return a list with all the container's object meta keys."""
159 b956618e Antony Chazapis
        
160 22dab079 Antony Chazapis
        logger.debug("list_object_meta: %s %s", account, name)
161 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, name)
162 22dab079 Antony Chazapis
        sql = 'select distinct key from metadata where name like ?'
163 22dab079 Antony Chazapis
        c = self.con.execute(sql, (path + '/%',))
164 22dab079 Antony Chazapis
        return [x[0] for x in c.fetchall()]
165 b956618e Antony Chazapis
    
166 b956618e Antony Chazapis
    def get_object_meta(self, account, container, name):
167 b956618e Antony Chazapis
        """Return a dictionary with the object metadata."""
168 b956618e Antony Chazapis
        
169 b956618e Antony Chazapis
        logger.debug("get_object_meta: %s %s %s", account, container, name)
170 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, container)
171 22dab079 Antony Chazapis
        path, link, tstamp, version, size = self._get_objectinfo(account, container, name)
172 22dab079 Antony Chazapis
        meta = self._get_metadata(path)
173 22dab079 Antony Chazapis
        meta.update({'name': name, 'bytes': size, 'version': version, 'created': tstamp})
174 b956618e Antony Chazapis
        return meta
175 b956618e Antony Chazapis
    
176 22dab079 Antony Chazapis
    def update_object_meta(self, account, container, name, meta, replace=False):
177 b956618e Antony Chazapis
        """Update the metadata associated with the object."""
178 b956618e Antony Chazapis
        
179 22dab079 Antony Chazapis
        logger.debug("update_object_meta: %s %s %s %s %s", account, container, name, meta, replace)
180 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, container)
181 22dab079 Antony Chazapis
        path, link, tstamp, version, size = self._get_objectinfo(account, container, name)
182 22dab079 Antony Chazapis
        if 'versioned' in meta:
183 22dab079 Antony Chazapis
            if meta['versioned']:
184 22dab079 Antony Chazapis
                if version == 0:
185 22dab079 Antony Chazapis
                    sql = 'update versions set version = 1 where object_id = ?'
186 22dab079 Antony Chazapis
                    self.con.execute(sql, (link,))
187 22dab079 Antony Chazapis
                    self.con.commit()
188 22dab079 Antony Chazapis
            else:
189 22dab079 Antony Chazapis
                if version > 0:
190 22dab079 Antony Chazapis
                    self._del_uptoversion(link, version)
191 22dab079 Antony Chazapis
                    sql = 'update versions set version = 0 where object_id = ?'
192 22dab079 Antony Chazapis
                    self.con.execute(sql, (link,))
193 22dab079 Antony Chazapis
                    self.con.commit()
194 22dab079 Antony Chazapis
            del(meta['versioned'])
195 22dab079 Antony Chazapis
        self._update_metadata(account, container, name, meta, replace)
196 b956618e Antony Chazapis
    
197 22dab079 Antony Chazapis
    def get_object_hashmap(self, account, container, name, version=None):
198 22dab079 Antony Chazapis
        """Return the object's size and a list with partial hashes."""
199 b956618e Antony Chazapis
        
200 22dab079 Antony Chazapis
        logger.debug("get_object_hashmap: %s %s %s %s", account, container, name, version)
201 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, container)
202 22dab079 Antony Chazapis
        path, link, tstamp, version, size = self._get_objectinfo(account, container, name, version)
203 b956618e Antony Chazapis
        
204 22dab079 Antony Chazapis
        sql = '''select block_id from hashmaps where version_id =
205 22dab079 Antony Chazapis
                    (select rowid from versions where object_id = ? and version = ?)
206 22dab079 Antony Chazapis
                    order by pos'''
207 22dab079 Antony Chazapis
        c = self.con.execute(sql, (link, version))
208 22dab079 Antony Chazapis
        hashmap = [x[0] for x in c.fetchall()]
209 22dab079 Antony Chazapis
        return size, hashmap
210 22dab079 Antony Chazapis
    
211 22dab079 Antony Chazapis
    def update_object_hashmap(self, account, container, name, size, hashmap):
212 22dab079 Antony Chazapis
        """Create/update an object with the specified size and partial hashes."""
213 b956618e Antony Chazapis
        
214 cfe6939d Antony Chazapis
        logger.debug("update_object_hashmap: %s %s %s %s %s", account, container, name, size, hashmap)
215 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, container)
216 b956618e Antony Chazapis
        try:
217 22dab079 Antony Chazapis
            path, link, tstamp, version, s = self._get_objectinfo(account, container, name)
218 b956618e Antony Chazapis
        except NameError:
219 22dab079 Antony Chazapis
            version = 0
220 b956618e Antony Chazapis
        
221 22dab079 Antony Chazapis
        if version == 0:
222 22dab079 Antony Chazapis
            path = os.path.join(account, container, name)
223 22dab079 Antony Chazapis
            
224 22dab079 Antony Chazapis
            self._del_path(path, delmeta=False)
225 22dab079 Antony Chazapis
            link = self._put_linkinfo(path)
226 22dab079 Antony Chazapis
        else:
227 cfe6939d Antony Chazapis
            version += 1
228 b956618e Antony Chazapis
        
229 22dab079 Antony Chazapis
        sql = 'insert or replace into versions (object_id, version, size) values (?, ?, ?)'
230 22dab079 Antony Chazapis
        version_id = self.con.execute(sql, (link, version, size)).lastrowid
231 22dab079 Antony Chazapis
        for i in range(len(hashmap)):
232 22dab079 Antony Chazapis
            sql = 'insert or replace into hashmaps (version_id, pos, block_id) values (?, ?, ?)'
233 22dab079 Antony Chazapis
            self.con.execute(sql, (version_id, i, hashmap[i]))
234 22dab079 Antony Chazapis
        self.con.commit()
235 22dab079 Antony Chazapis
    
236 22dab079 Antony Chazapis
    def copy_object(self, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False):
237 22dab079 Antony Chazapis
        """Copy an object's data and metadata."""
238 b956618e Antony Chazapis
        
239 22dab079 Antony Chazapis
        logger.debug("copy_object: %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta)
240 22dab079 Antony Chazapis
        size, hashmap = self.get_object_hashmap(account, src_container, src_name)
241 22dab079 Antony Chazapis
        self.update_object_hashmap(account, dest_container, dest_name, size, hashmap)
242 22dab079 Antony Chazapis
        if not replace_meta:
243 22dab079 Antony Chazapis
            meta = self._get_metadata(os.path.join(account, src_container, src_name))
244 22dab079 Antony Chazapis
            meta.update(dest_meta)
245 22dab079 Antony Chazapis
        else:
246 22dab079 Antony Chazapis
            meta = dest_meta
247 22dab079 Antony Chazapis
        self._update_metadata(account, dest_container, dest_name, meta, replace_meta)
248 b956618e Antony Chazapis
    
249 22dab079 Antony Chazapis
    def move_object(self, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False):
250 b956618e Antony Chazapis
        """Move an object's data and metadata."""
251 b956618e Antony Chazapis
        
252 22dab079 Antony Chazapis
        logger.debug("move_object: %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta)
253 22dab079 Antony Chazapis
        self.copy_object(account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta)
254 b956618e Antony Chazapis
        self.delete_object(account, src_container, src_name)
255 b956618e Antony Chazapis
    
256 b956618e Antony Chazapis
    def delete_object(self, account, container, name):
257 b956618e Antony Chazapis
        """Delete an object."""
258 b956618e Antony Chazapis
        
259 b956618e Antony Chazapis
        logger.debug("delete_object: %s %s %s", account, container, name)
260 22dab079 Antony Chazapis
        path, link, tstamp = self._get_containerinfo(account, container)
261 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
262 22dab079 Antony Chazapis
        link, tstamp = self._get_linkinfo(path)
263 22dab079 Antony Chazapis
        self._del_path(path)
264 b956618e Antony Chazapis
        self._update_metadata(account, container, None, None)
265 b956618e Antony Chazapis
    
266 22dab079 Antony Chazapis
    def get_block(self, hash):
267 22dab079 Antony Chazapis
        """Return a block's data."""
268 22dab079 Antony Chazapis
        
269 cfe6939d Antony Chazapis
        logger.debug("get_block: %s", hash)
270 22dab079 Antony Chazapis
        c = self.con.execute('select data from blocks where block_id = ?', (hash,))
271 22dab079 Antony Chazapis
        row = c.fetchone()
272 22dab079 Antony Chazapis
        if row:
273 22dab079 Antony Chazapis
            return str(row[0])
274 22dab079 Antony Chazapis
        else:
275 22dab079 Antony Chazapis
            raise NameError('Block does not exist')
276 b956618e Antony Chazapis
    
277 22dab079 Antony Chazapis
    def put_block(self, data):
278 22dab079 Antony Chazapis
        """Create a block and return the hash."""
279 22dab079 Antony Chazapis
        
280 cfe6939d Antony Chazapis
        logger.debug("put_block: %s", len(data))
281 22dab079 Antony Chazapis
        h = hashlib.new(self.hash_algorithm)
282 22dab079 Antony Chazapis
        h.update(data.rstrip('\x00'))
283 22dab079 Antony Chazapis
        hash = h.hexdigest()
284 22dab079 Antony Chazapis
        sql = 'insert or ignore into blocks (block_id, data) values (?, ?)'
285 22dab079 Antony Chazapis
        self.con.execute(sql, (hash, buffer(data)))
286 22dab079 Antony Chazapis
        self.con.commit()
287 22dab079 Antony Chazapis
        return hash
288 22dab079 Antony Chazapis
    
289 22dab079 Antony Chazapis
    def update_block(self, hash, data, offset=0):
290 22dab079 Antony Chazapis
        """Update a known block and return the hash."""
291 22dab079 Antony Chazapis
        
292 cfe6939d Antony Chazapis
        logger.debug("update_block: %s %s %s", hash, len(data), offset)
293 cfe6939d Antony Chazapis
        if offset == 0 and len(data) == self.block_size:
294 cfe6939d Antony Chazapis
            return self.put_block(data)
295 22dab079 Antony Chazapis
        src_data = self.get_block(hash)
296 22dab079 Antony Chazapis
        bs = self.block_size
297 22dab079 Antony Chazapis
        if offset < 0 or offset > bs or offset + len(data) > bs:
298 22dab079 Antony Chazapis
            raise IndexError('Offset or data outside block limits')
299 22dab079 Antony Chazapis
        dest_data = src_data[:offset] + data + src_data[offset + len(data):]
300 22dab079 Antony Chazapis
        return self.put_block(dest_data)
301 b956618e Antony Chazapis
    
302 b956618e Antony Chazapis
    def _get_linkinfo(self, path):
303 22dab079 Antony Chazapis
        c = self.con.execute('select rowid, tstamp from objects where name = ?', (path,))
304 b956618e Antony Chazapis
        row = c.fetchone()
305 b956618e Antony Chazapis
        if row:
306 22dab079 Antony Chazapis
            return str(row[0]), str(row[1])
307 b956618e Antony Chazapis
        else:
308 b956618e Antony Chazapis
            raise NameError('Object does not exist')
309 b956618e Antony Chazapis
    
310 b956618e Antony Chazapis
    def _put_linkinfo(self, path):
311 22dab079 Antony Chazapis
        sql = 'insert into objects (name, tstamp) values (?, ?)'
312 22dab079 Antony Chazapis
        id = self.con.execute(sql, (path, int(time.time()))).lastrowid
313 b956618e Antony Chazapis
        self.con.commit()
314 b956618e Antony Chazapis
        return str(id)
315 b956618e Antony Chazapis
    
316 22dab079 Antony Chazapis
    def _get_containerinfo(self, account, container):
317 22dab079 Antony Chazapis
        path = os.path.join(account, container)
318 22dab079 Antony Chazapis
        try:
319 22dab079 Antony Chazapis
            link, tstamp = self._get_linkinfo(path)
320 22dab079 Antony Chazapis
        except NameError:
321 22dab079 Antony Chazapis
            raise NameError('Container does not exist')
322 22dab079 Antony Chazapis
        return path, link, tstamp
323 22dab079 Antony Chazapis
    
324 22dab079 Antony Chazapis
    def _get_objectinfo(self, account, container, name, version=None):
325 22dab079 Antony Chazapis
        path = os.path.join(account, container, name)
326 22dab079 Antony Chazapis
        link, tstamp = self._get_linkinfo(path)
327 22dab079 Antony Chazapis
        if not version: # If zero or None.
328 22dab079 Antony Chazapis
            sql = '''select version, size from versions v,
329 22dab079 Antony Chazapis
                        (select object_id, max(version) as m from versions
330 22dab079 Antony Chazapis
                            where object_id = ? group by object_id) as g
331 22dab079 Antony Chazapis
                        where v.object_id = g.object_id and v.version = g.m'''
332 22dab079 Antony Chazapis
            c = self.con.execute(sql, (link,))
333 22dab079 Antony Chazapis
        else:
334 22dab079 Antony Chazapis
            sql = 'select version, size from versions where object_id = ? and version = ?'
335 22dab079 Antony Chazapis
            c = self.con.execute(sql, (link, version))
336 22dab079 Antony Chazapis
        row = c.fetchone()
337 22dab079 Antony Chazapis
        if not row:
338 22dab079 Antony Chazapis
            raise IndexError('Version does not exist')
339 22dab079 Antony Chazapis
        
340 22dab079 Antony Chazapis
        return path, link, tstamp, int(row[0]), int(row[1])
341 22dab079 Antony Chazapis
    
342 22dab079 Antony Chazapis
    def _get_pathstats(self, path):
343 22dab079 Antony Chazapis
        """Return count and sum of size of all objects under path."""
344 22dab079 Antony Chazapis
        
345 22dab079 Antony Chazapis
        sql = '''select count(o), total(size) from (
346 22dab079 Antony Chazapis
                    select v.object_id as o, v.size from versions v,
347 22dab079 Antony Chazapis
                        (select object_id, max(version) as m from versions where object_id in
348 22dab079 Antony Chazapis
                            (select rowid from objects where name like ?) group by object_id) as g
349 22dab079 Antony Chazapis
                        where v.object_id = g.object_id and v.version = g.m
350 22dab079 Antony Chazapis
                    union
351 22dab079 Antony Chazapis
                    select rowid as o, 0 as size from objects where name like ?
352 22dab079 Antony Chazapis
                        and rowid not in (select object_id from versions))'''
353 22dab079 Antony Chazapis
        c = self.con.execute(sql, (path + '/%', path + '/%'))
354 22dab079 Antony Chazapis
        row = c.fetchone()
355 22dab079 Antony Chazapis
        return int(row[0]), int(row[1])
356 22dab079 Antony Chazapis
    
357 22dab079 Antony Chazapis
    def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[]):
358 22dab079 Antony Chazapis
        cont_prefix = path + '/'
359 22dab079 Antony Chazapis
        if keys and len(keys) > 0:
360 e3f9bbaa Sofia Papagiannaki
            sql = '''select distinct o.name from objects o, metadata m where o.name like ? and
361 22dab079 Antony Chazapis
                        m.name = o.name and m.key in (%s) order by o.name'''
362 22dab079 Antony Chazapis
            sql = sql % ', '.join('?' * len(keys))
363 22dab079 Antony Chazapis
            param = (cont_prefix + prefix + '%',) + tuple(keys)
364 22dab079 Antony Chazapis
        else:
365 22dab079 Antony Chazapis
            sql = 'select name from objects where name like ? order by name'
366 22dab079 Antony Chazapis
            param = (cont_prefix + prefix + '%',)
367 22dab079 Antony Chazapis
        c = self.con.execute(sql, param)
368 22dab079 Antony Chazapis
        objects = [x[0][len(cont_prefix):] for x in c.fetchall()]
369 22dab079 Antony Chazapis
        if delimiter:
370 22dab079 Antony Chazapis
            pseudo_objects = []
371 22dab079 Antony Chazapis
            for x in objects:
372 22dab079 Antony Chazapis
                pseudo_name = x
373 22dab079 Antony Chazapis
                i = pseudo_name.find(delimiter, len(prefix))
374 22dab079 Antony Chazapis
                if not virtual:
375 22dab079 Antony Chazapis
                    # If the delimiter is not found, or the name ends
376 22dab079 Antony Chazapis
                    # with the delimiter's first occurence.
377 22dab079 Antony Chazapis
                    if i == -1 or len(pseudo_name) == i + len(delimiter):
378 22dab079 Antony Chazapis
                        pseudo_objects.append(pseudo_name)
379 22dab079 Antony Chazapis
                else:
380 22dab079 Antony Chazapis
                    # If the delimiter is found, keep up to (and including) the delimiter.
381 22dab079 Antony Chazapis
                    if i != -1:
382 22dab079 Antony Chazapis
                        pseudo_name = pseudo_name[:i + len(delimiter)]
383 22dab079 Antony Chazapis
                    if pseudo_name not in pseudo_objects:
384 22dab079 Antony Chazapis
                        pseudo_objects.append(pseudo_name)
385 22dab079 Antony Chazapis
            objects = pseudo_objects
386 22dab079 Antony Chazapis
        
387 22dab079 Antony Chazapis
        start = 0
388 22dab079 Antony Chazapis
        if marker:
389 22dab079 Antony Chazapis
            try:
390 22dab079 Antony Chazapis
                start = objects.index(marker) + 1
391 22dab079 Antony Chazapis
            except ValueError:
392 22dab079 Antony Chazapis
                pass
393 22dab079 Antony Chazapis
        if not limit or limit > 10000:
394 22dab079 Antony Chazapis
            limit = 10000
395 22dab079 Antony Chazapis
        return objects[start:start + limit]
396 22dab079 Antony Chazapis
    
397 b956618e Antony Chazapis
    def _get_metadata(self, path):
398 22dab079 Antony Chazapis
        sql = 'select key, value from metadata where name = ?'
399 b956618e Antony Chazapis
        c = self.con.execute(sql, (path,))
400 b956618e Antony Chazapis
        return dict(c.fetchall())
401 b956618e Antony Chazapis
    
402 22dab079 Antony Chazapis
    def _put_metadata(self, path, meta, replace=False):
403 22dab079 Antony Chazapis
        if replace:
404 22dab079 Antony Chazapis
            sql = 'delete from metadata where name = ?'
405 22dab079 Antony Chazapis
            self.con.execute(sql, (path,))
406 b956618e Antony Chazapis
        for k, v in meta.iteritems():
407 22dab079 Antony Chazapis
            sql = 'insert or replace into metadata (name, key, value) values (?, ?, ?)'
408 22dab079 Antony Chazapis
            self.con.execute(sql, (path, k, v))
409 b956618e Antony Chazapis
        self.con.commit()
410 b956618e Antony Chazapis
    
411 22dab079 Antony Chazapis
    def _update_metadata(self, account, container, name, meta, replace=False):
412 b956618e Antony Chazapis
        """Recursively update metadata and set modification time."""
413 b956618e Antony Chazapis
        
414 b956618e Antony Chazapis
        modified = {'modified': int(time.time())}
415 b956618e Antony Chazapis
        if not meta:
416 b956618e Antony Chazapis
            meta = {}
417 b956618e Antony Chazapis
        meta.update(modified)
418 b956618e Antony Chazapis
        path = (account, container, name)
419 b956618e Antony Chazapis
        for x in reversed(range(3)):
420 b956618e Antony Chazapis
            if not path[x]:
421 b956618e Antony Chazapis
                continue
422 22dab079 Antony Chazapis
            self._put_metadata(os.path.join(*path[:x+1]), meta, replace)
423 b956618e Antony Chazapis
            break
424 b956618e Antony Chazapis
        for y in reversed(range(x)):
425 b956618e Antony Chazapis
            self._put_metadata(os.path.join(*path[:y+1]), modified)
426 b956618e Antony Chazapis
    
427 22dab079 Antony Chazapis
    def _del_uptoversion(self, link, version):
428 22dab079 Antony Chazapis
        sql = '''delete from hashmaps where version_id
429 22dab079 Antony Chazapis
                    (select rowid from versions where object_id = ? and version < ?)'''
430 22dab079 Antony Chazapis
        self.con.execute(sql, (link, version))
431 22dab079 Antony Chazapis
        self.con.execute('delete from versions where object_id = ?', (link,))
432 22dab079 Antony Chazapis
        self.con.commit()
433 22dab079 Antony Chazapis
    
434 22dab079 Antony Chazapis
    def _del_path(self, path, delmeta=True):
435 22dab079 Antony Chazapis
        sql = '''delete from hashmaps where version_id in
436 22dab079 Antony Chazapis
                    (select rowid from versions where object_id in
437 22dab079 Antony Chazapis
                    (select rowid from objects where name = ?))'''
438 22dab079 Antony Chazapis
        self.con.execute(sql, (path,))
439 22dab079 Antony Chazapis
        sql = '''delete from versions where object_id in
440 22dab079 Antony Chazapis
                    (select rowid from objects where name = ?)'''
441 b956618e Antony Chazapis
        self.con.execute(sql, (path,))
442 b956618e Antony Chazapis
        self.con.execute('delete from objects where name = ?', (path,))
443 22dab079 Antony Chazapis
        if delmeta:
444 22dab079 Antony Chazapis
            self.con.execute('delete from metadata where name = ?', (path,))
445 b956618e Antony Chazapis
        self.con.commit()