Revision c915d3bf

b/pithos/backends/lib/node.py
98 98
       Attributes store metadata.
99 99
    """
100 100
    
101
    # TODO: Keep size of object in one place.
102
    
101 103
    def __init__(self, **params):
102 104
        execute = self.execute
103 105
        
......
226 228
            return (0, 0, 0)
227 229
        return r
228 230
    
229
    def node_children(self, node):
231
    def node_count_children(self, node):
230 232
        """Return node's child count."""
231 233
        
232
        q = "select count(node) from nodes where parent = ?"
234
        q = "select count(node) from nodes where parent = ? and node != 0"
233 235
        self.execute(q, (node,))
234 236
        r = fetchone()
235 237
        if r is None:
236 238
            return 0
237 239
        return r
238 240
    
241
    def node_purge_children(self, parent, before=inf, cluster=0):
242
        """Delete all versions with the specified
243
           parent and cluster, and return
244
           the serials of versions deleted.
245
           Clears out nodes with no remaining versions.
246
        """
247
        
248
        execute = self.execute
249
        q = ("select count(serial), sum(size) from versions "
250
             "where node in (select node "
251
                            "from nodes "
252
                            "where parent = ?) "
253
             "and cluster = ? "
254
             "and mtime <= ?")
255
        args = (parent, cluster, before)
256
        execute(q, args)
257
        nr, size = self.fetchone()
258
        if not nr:
259
            return ()
260
        # TODO: Statistics for nodes (children) will be wrong.
261
        self.node_update_ancestors(parent, -nr, -size, cluster)
262
        
263
        q = ("select serial from versions "
264
             "where node in (select node "
265
                            "from nodes "
266
                            "where parent = ?) "
267
             "and cluster = ? "
268
             "and mtime <= ?")
269
        execute(q, args)
270
        serials = [r[SERIAL] for r in self.fetchall()]
271
        q = ("delete from versions "
272
             "where node in (select node "
273
                            "from nodes "
274
                            "where parent = ?) "
275
             "and cluster = ? "
276
             "and mtime <= ?")
277
        execute(q, args)
278
        q = ("delete from nodes n "
279
             "where (select count(serial) "
280
                    "from versions "
281
                    "where node = n.node) = 0 "
282
             "and parent = ?")
283
        execute(q, parent)
284
        return serials
285
    
286
    def node_purge(self, node, before=inf, cluster=0):
287
        """Delete all versions with the specified
288
           node and cluster, and return
289
           the serials of versions deleted.
290
           Clears out the node if it has no remaining versions.
291
        """
292
        
293
        execute = self.execute
294
        q = ("select count(serial), sum(size) from versions "
295
             "where node = ? "
296
             "and cluster = ? "
297
             "and mtime <= ?")
298
        args = (node, cluster, before)
299
        execute(q, args)
300
        nr, size = self.fetchone()
301
        if not nr:
302
            return ()
303
        self.node_update_ancestors(node, -nr, -size, cluster)
304
        
305
        q = ("select serial from versions "
306
             "where node = ? "
307
             "and cluster = ? "
308
             "and mtime <= ?")
309
        execute(q, args)
310
        serials = [r[SERIAL] for r in self.fetchall()]
311
        q = ("delete from versions "
312
             "where node = ? "
313
             "and cluster = ? "
314
             "and mtime <= ?")
315
        execute(q, args)
316
        q = ("delete from nodes n "
317
             "where (select count(serial) "
318
                    "from versions "
319
                    "where node = n.node) = 0 "
320
             "and node = ?")
321
        execute(q, node)
322
        return serials
323
    
324
    def node_remove(self, node):
325
        """Remove the node specified.
326
           Return false if the node has children or is not found.
327
        """
328
        
329
        if self.node_children(node):
330
            return False
331
        
332
        q = "select parent from node where node = ?"
333
        self.execute(q, (node,))
334
        r = self.fetchone()
335
        if r is None:
336
            return False
337
        parent = r[0]
338
        
339
        mtime = time()
340
        q = "select population, size, cluster from statistics where node = ?"
341
        self.execute(q, (node,))
342
        for population, size, cluster in self.fetchall():
343
            self.node_update_ancestors(parent, -population, -size, mtime, cluster)
344
        
345
        q = "delete from nodes where node = ?"
346
        self.execute(q, (node,))
347
        return True
348
    
239 349
#     def node_remove(self, serial, recursive=0):
240 350
#         """Remove the node specified by serial.
241 351
#            Return false if the node is not found,
......
340 450
        self.node_update_ancestors(node, -1, -size, mtime, oldcluster)
341 451
        self.node_update_ancestors(node, 1, size, mtime, cluster)
342 452

  
343
        q = "update nodes set parent = ?, path = ? where serial = ?"
344
        self.execute(q, (parent, path, source))
453
        q = "update nodes set cluster = ? where serial = ?"
454
        self.execute(q, (cluster, serial))
345 455
    
346 456
#     def version_copy(self, serial, node, muser, copy_attr=True):
347 457
#         """Copy the version specified by serial into
......
373 483
             "and cluster != ? "
374 484
             "and node in (select node "
375 485
                          "from nodes "
376
                          "where path like ?")
486
                          "where path like ?)")
377 487
        self.execute(q, (before, except_cluster, prefix + '%'))
378 488
        r = fetchone()
379 489
        if r is None:
......
550 660
#             execute(q, args)
551 661
# 
552 662
#         return matches, prefixes
553

  
554
#     def node_delete(self, parent, prefix,
555
#                     start='', delimiter=None,
556
#                     after=0.0, before=inf,
557
#                     filterq=None, versions=0,
558
#                     cluster=0, limit=10000):
559
#         """Delete the matching version for each
560
#            of the matching paths in the parent's namespace.
561
#            Return empty if nothing is deleted, else return matches.
562
#            The paths matching are those that would
563
#            be returned by .node_list() with the same arguments.
564
#            Note that only paths are deleted, not prefixes.
565
# 
566
#         """
567
#         r = self.node_list(parent, prefix,
568
#                            start=start, delimiter=delimiter,
569
#                            after=after, before=before,
570
#                            filterq=filterq, versions=versions,
571
#                            cluster=cluster, limit=limit)
572
#         matches, prefixes = r
573
#         if not matches:
574
#             return ()
575
# 
576
#         q = "delete from nodes where serial = ?"
577
#         self.executemany(q, ((props[SERIAL],) for props in matches))
578
#         # TODO: Update sizes.
579
#         return matches
580

  
581
#     def node_purge(self, parent, path, after=0, before=inf, cluster=0):
582
#         """Delete all nodes with the specified
583
#            parent, cluster and path, and return
584
#            the serials of nodes deleted.
585
#         """
586
#         execute = self.execute
587
#         q = ("select count(serial), total(size), "
588
#                     "total(population), total(popsize) "
589
#              "from nodes "
590
#              "where parent = ? and cluster = ? "
591
#              "and path = ? and mtime between ? and ?")
592
#         args = (parent, cluster, path, after, before)
593
#         execute(q, args)
594
#         nr, size, pop, popsize = self.fetchone()
595
#         if not nr:
596
#             return ()
597
#         self.node_update_ancestors(parent, -pop-nr, -size-popsize)
598
#         q = ("select serial from nodes "
599
#              "where parent = ? and cluster = ? "
600
#              "and path = ? and mtime between ? and ?")
601
#         execute(q, args)
602
#         serials = [r[SERIAL] for r in self.fetchall()]
603
#         q = ("delete from nodes where "
604
#              "parent = ? and cluster = ? "
605
#              "and path = ? and mtime between ? and ?")
606
#         execute(q, args)
607
#         return serials
608 663
    
609 664
    def attribute_get(self, serial, keys=()):
610 665
        """Return a list of (key, value) pairs of the version specified by serial.
b/pithos/backends/modular.py
123 123
        """Return a dictionary with the account metadata."""
124 124
        
125 125
        logger.debug("get_account_meta: %s %s", account, until)
126
        node = self._lookup_account(account, user == account)
126
        path, node = self._lookup_account(account, user == account)
127 127
        if user != account:
128 128
            if until or node is None or account not in self._allowed_accounts(user):
129 129
                raise NotAllowedError
......
135 135
            # Account does not exist before until.
136 136
            version_id = None
137 137
            mtime = until
138
        object_count, bytes, tstamp = self._get_statistics(node, account, until)
139
        if mtime > tstamp:
140
            tstamp = mtime
138
        object_count, bytes, tstamp = self._get_statistics(node, path, until)
139
        tstamp = max(mtime, tstamp)
141 140
        if until is None:
142 141
            modified = tstamp
143 142
        else:
144
            modified = self._get_statistics(node, account)[2] # Overall last modification.
145
            if mtime > modified:
146
                modified = mtime
143
            modified = self._get_statistics(node, path)[2] # Overall last modification.
144
            modified = max(mtime, modified)
147 145
        
148 146
        # Proper count.
149
        count = self.node.node_children(node)
147
        # TODO: Fix this to count until.
148
        count = self.node.node_count_children(node)
150 149
        object_count -= count
151 150
        
152 151
        if user != account:
......
168 167
        logger.debug("update_account_meta: %s %s %s", account, meta, replace)
169 168
        if user != account:
170 169
            raise NotAllowedError
171
        node = self._lookup_account(account, True)
170
        path, node = self._lookup_account(account, True)
172 171
        self._put_metadata(user, node, meta, replace, False)
173 172
    
174 173
    @backend_method
......
210 209
        node = self.node.node_lookup(account)
211 210
        if node is not None:
212 211
            raise NameError('Account already exists')
213
        node = self.node.node_create(ROOTNODE, account)
214
        self.node.version_create(node, 0, None, account, CLUSTER_NORMAL)
215
    
216
    
217
    
218
    
219
    
220
    
221
    
222
    
223
    
224
    
225
    
226
    
212
        self._put_path(ROOTNODE, account)
227 213
    
228 214
    @backend_method
229 215
    def delete_account(self, user, account):
......
232 218
        logger.debug("delete_account: %s", account)
233 219
        if user != account:
234 220
            raise NotAllowedError
235
        count = self._get_pathstats(account)[0]
236
        if count > 0:
221
        node = self.node.node_lookup(account)
222
        if node is None:
223
            return
224
        if not self.node.node_remove(node):
237 225
            raise IndexError('Account is not empty')
238
        sql = 'delete from versions where name = ?'
239
        self.con.execute(sql, (account,))
240 226
        self.permissions.group_destroy(account)
241 227
    
242
    @backend_method
243
    def list_containers(self, user, account, marker=None, limit=10000, shared=False, until=None):
244
        """Return a list of containers existing under an account."""
245
        
246
        logger.debug("list_containers: %s %s %s %s", account, marker, limit, until)
247
        if user != account:
248
            if until or account not in self._allowed_accounts(user):
249
                raise NotAllowedError
250
            allowed = self._allowed_containers(user, account)
251
            start, limit = self._list_limits(allowed, marker, limit)
252
            return allowed[start:start + limit]
253
        else:
254
            if shared:
255
                allowed = [x.split('/', 2)[1] for x in self.permissions.access_list_shared(account)]
256
                start, limit = self._list_limits(allowed, marker, limit)
257
                return allowed[start:start + limit]
258
        return [x[0] for x in self._list_objects(account, '', '/', marker, limit, False, [], until)]
228
#     @backend_method
229
#     def list_containers(self, user, account, marker=None, limit=10000, shared=False, until=None):
230
#         """Return a list of containers existing under an account."""
231
#         
232
#         logger.debug("list_containers: %s %s %s %s", account, marker, limit, until)
233
#         if user != account:
234
#             if until or account not in self._allowed_accounts(user):
235
#                 raise NotAllowedError
236
#             allowed = self._allowed_containers(user, account)
237
#             start, limit = self._list_limits(allowed, marker, limit)
238
#             return allowed[start:start + limit]
239
#         else:
240
#             if shared:
241
#                 allowed = [x.split('/', 2)[1] for x in self.permissions.access_list_shared(account)]
242
#                 start, limit = self._list_limits(allowed, marker, limit)
243
#                 return allowed[start:start + limit]
244
#         return [x[0] for x in self._list_objects(account, '', '/', marker, limit, False, [], until)]
259 245
    
260 246
    @backend_method
261 247
    def get_container_meta(self, user, account, container, until=None):
......
265 251
        if user != account:
266 252
            if until or container not in self._allowed_containers(user, account):
267 253
                raise NotAllowedError
268
        path, version_id, mtime = self._get_containerinfo(account, container, until)
269
        count, bytes, tstamp = self._get_pathstats(path, until)
270
        if mtime > tstamp:
271
            tstamp = mtime
254
        path, node = self._lookup_container(account, container)
255
        props = self._get_properties(node, until)
256
        count, bytes, tstamp = self._get_statistics(node, path, until)
257
        tstamp = max(mtime, tstamp)
272 258
        if until is None:
273 259
            modified = tstamp
274 260
        else:
275
            modified = self._get_pathstats(path)[2] # Overall last modification
276
            if mtime > modified:
277
                modified = mtime
261
            modified = self._get_statistics(node, path)[2] # Overall last modification.
262
            modified = max(mtime, modified)
278 263
        
279 264
        if user != account:
280
            meta = {'name': container, 'modified': modified}
265
            meta = {'name': container}
281 266
        else:
282
            meta = self._get_metadata(path, version_id)
283
            meta.update({'name': container, 'count': count, 'bytes': bytes, 'modified': modified})
267
            meta = dict(self.node.attribute_get(props[SERIAL]))
284 268
            if until is not None:
285 269
                meta.update({'until_timestamp': tstamp})
270
            meta.update({'name': container, 'count': count, 'bytes': bytes})
271
        meta.update({'modified': modified})
286 272
        return meta
287 273
    
288 274
    @backend_method
......
292 278
        logger.debug("update_container_meta: %s %s %s %s", account, container, meta, replace)
293 279
        if user != account:
294 280
            raise NotAllowedError
295
        path, version_id, mtime = self._get_containerinfo(account, container)
296
        self._put_metadata(user, path, meta, replace, False)
281
        path, node = self._lookup_container(account, container)
282
        self._put_metadata(user, node, meta, replace, False)
297 283
    
298 284
    @backend_method
299 285
    def get_container_policy(self, user, account, container):
......
304 290
            if container not in self._allowed_containers(user, account):
305 291
                raise NotAllowedError
306 292
            return {}
307
        path = self._get_containerinfo(account, container)[0]
293
        path = self._lookup_container(account, container)[0]
308 294
        return self.policy.policy_get(path)
309 295
    
310 296
    @backend_method
......
314 300
        logger.debug("update_container_policy: %s %s %s %s", account, container, policy, replace)
315 301
        if user != account:
316 302
            raise NotAllowedError
317
        path = self._get_containerinfo(account, container)[0]
303
        path = self._lookup_container(account, container)[0]
318 304
        self._check_policy(policy)
319 305
        if replace:
320 306
            for k, v in self.default_policy.iteritems():
......
330 316
        if user != account:
331 317
            raise NotAllowedError
332 318
        try:
333
            path, version_id, mtime = self._get_containerinfo(account, container)
319
            path, node = self._lookup_container(account, container)
334 320
        except NameError:
335 321
            pass
336 322
        else:
......
338 324
        if policy:
339 325
            self._check_policy(policy)
340 326
        path = '/'.join((account, container))
341
        version_id = self._put_version(path, user)[0]
327
        self._put_path(self._lookup_account(account, True)[1], path)
342 328
        for k, v in self.default_policy.iteritems():
343 329
            if k not in policy:
344 330
                policy[k] = v
......
351 337
        logger.debug("delete_container: %s %s %s", account, container, until)
352 338
        if user != account:
353 339
            raise NotAllowedError
354
        path, version_id, mtime = self._get_containerinfo(account, container)
340
        path, node = self._lookup_container(account, container)
355 341
        
356 342
        if until is not None:
357
            sql = '''select version_id from versions where name like ? and tstamp <= ?
358
                        and version_id not in (select version_id from (%s))'''
359
            sql = sql % self._sql_until() # Do not delete current versions.
360
            c = self.con.execute(sql, (path + '/%', until))
361
            for v in [x[0] for x in c.fetchall()]:
362
                self._del_version(v)
343
            versions = self.node.node_purge_children(node, until, CLUSTER_HISTORY)
344
            for v in versions:
345
                self.mapper.map_remv(v)
346
            self.node.node_purge_children(node, until, CLUSTER_DELETED)
363 347
            return
364 348
        
365
        count = self._get_pathstats(path)[0]
366
        if count > 0:
349
        if self._get_statistics(node, path)[0] > 0:
367 350
            raise IndexError('Container is not empty')
368
        sql = 'delete from versions where name = ? or name like ?' # May contain hidden items.
369
        self.con.execute(sql, (path, path + '/%',))
351
        versions = self.node.node_purge_children(node, until, CLUSTER_HISTORY)
352
        for v in versions:
353
            self.mapper.map_remv(v)
354
        self.node.node_purge_children(node, until, CLUSTER_DELETED)
355
        self.node.node_remove(node)
370 356
        self.policy.policy_unset(path)
371
        self._copy_version(user, account, account, True, False) # New account version (for timestamp update).
372 357
    
373
    @backend_method
374
    def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], shared=False, until=None):
375
        """Return a list of objects existing under a container."""
376
        
377
        logger.debug("list_objects: %s %s %s %s %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit, virtual, keys, shared, until)
378
        allowed = []
379
        if user != account:
380
            if until:
381
                raise NotAllowedError
382
            allowed = self.permissions.access_list_paths(user, '/'.join((account, container)))
383
            if not allowed:
384
                raise NotAllowedError
385
        else:
386
            if shared:
387
                allowed = self.permissions.access_list_shared('/'.join((account, container)))
388
        path, version_id, mtime = self._get_containerinfo(account, container, until)
389
        return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, until, allowed)
390
    
391
    @backend_method
392
    def list_object_meta(self, user, account, container, until=None):
393
        """Return a list with all the container's object meta keys."""
394
        
395
        logger.debug("list_object_meta: %s %s %s", account, container, until)
396
        allowed = []
397
        if user != account:
398
            if until:
399
                raise NotAllowedError
400
            allowed = self.permissions.access_list_paths(user, '/'.join((account, container)))
401
            if not allowed:
402
                raise NotAllowedError
403
        path, version_id, mtime = self._get_containerinfo(account, container, until)
404
        sql = '''select distinct m.key from (%s) o, metadata m
405
                    where m.version_id = o.version_id and o.name like ?'''
406
        sql = sql % self._sql_until(until)
407
        param = (path + '/%',)
408
        if allowed:
409
            for x in allowed:
410
                sql += ' and o.name like ?'
411
                param += (x,)
412
        c = self.con.execute(sql, param)
413
        return [x[0] for x in c.fetchall()]
358
#     @backend_method
359
#     def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], shared=False, until=None):
360
#         """Return a list of objects existing under a container."""
361
#         
362
#         logger.debug("list_objects: %s %s %s %s %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit, virtual, keys, shared, until)
363
#         allowed = []
364
#         if user != account:
365
#             if until:
366
#                 raise NotAllowedError
367
#             allowed = self.permissions.access_list_paths(user, '/'.join((account, container)))
368
#             if not allowed:
369
#                 raise NotAllowedError
370
#         else:
371
#             if shared:
372
#                 allowed = self.permissions.access_list_shared('/'.join((account, container)))
373
#         path, version_id, mtime = self._get_containerinfo(account, container, until)
374
#         return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, until, allowed)
375
    
376
#     @backend_method
377
#     def list_object_meta(self, user, account, container, until=None):
378
#         """Return a list with all the container's object meta keys."""
379
#         
380
#         logger.debug("list_object_meta: %s %s %s", account, container, until)
381
#         allowed = []
382
#         if user != account:
383
#             if until:
384
#                 raise NotAllowedError
385
#             allowed = self.permissions.access_list_paths(user, '/'.join((account, container)))
386
#             if not allowed:
387
#                 raise NotAllowedError
388
#         path, version_id, mtime = self._get_containerinfo(account, container, until)
389
#         sql = '''select distinct m.key from (%s) o, metadata m
390
#                     where m.version_id = o.version_id and o.name like ?'''
391
#         sql = sql % self._sql_until(until)
392
#         param = (path + '/%',)
393
#         if allowed:
394
#             for x in allowed:
395
#                 sql += ' and o.name like ?'
396
#                 param += (x,)
397
#         c = self.con.execute(sql, param)
398
#         return [x[0] for x in c.fetchall()]
414 399
    
415 400
    @backend_method
416 401
    def get_object_meta(self, user, account, container, name, version=None):
......
418 403
        
419 404
        logger.debug("get_object_meta: %s %s %s %s", account, container, name, version)
420 405
        self._can_read(user, account, container, name)
421
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name, version)
406
        path, node = self._lookup_object(account, container, name)
407
        props = self._get_version(node, version)
422 408
        if version is None:
423
            modified = mtime
409
            modified = props[MTIME]
424 410
        else:
425
            modified = self._get_version(path, version)[2] # Overall last modification
411
            # TODO: Use latest version if stop keeping statistics for leaves.
412
            modified = self._get_statistics(node, path)[2] # Overall last modification.
426 413
        
427
        meta = self._get_metadata(path, version_id)
428
        meta.update({'name': name, 'bytes': size})
429
        meta.update({'version': version_id, 'version_timestamp': mtime})
430
        meta.update({'modified': modified, 'modified_by': muser})
414
        meta = dict(self.node.attribute_get(props[SERIAL]))
415
        meta.update({'name': name, 'bytes': props[SIZE]})
416
        meta.update({'version': props[SERIAL], 'version_timestamp': props[MTIME]})
417
        meta.update({'modified': modified, 'modified_by': props[MUSER]})
431 418
        return meta
432 419
    
433 420
    @backend_method
......
436 423
        
437 424
        logger.debug("update_object_meta: %s %s %s %s %s", account, container, name, meta, replace)
438 425
        self._can_write(user, account, container, name)
439
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name)
440
        self._put_metadata(user, path, meta, replace)
426
        path, node = self._lookup_object(account, container, name)
427
        self._put_metadata(user, node, meta, replace)
441 428
    
442 429
    @backend_method
443 430
    def get_object_permissions(self, user, account, container, name):
......
446 433
        
447 434
        logger.debug("get_object_permissions: %s %s %s", account, container, name)
448 435
        self._can_read(user, account, container, name)
449
        path = self._get_objectinfo(account, container, name)[0]
436
        path = self._lookup_object(account, container, name)[0]
450 437
        return self.permissions.access_inherit(path)
451 438
    
452 439
    @backend_method
......
456 443
        logger.debug("update_object_permissions: %s %s %s %s", account, container, name, permissions)
457 444
        if user != account:
458 445
            raise NotAllowedError
459
        path = self._get_objectinfo(account, container, name)[0]
446
        path = self._lookup_object(account, container, name)[0]
460 447
        self._check_permissions(path, permissions)
461 448
        self.permissions.access_set(path, permissions)
462 449
    
......
466 453
        
467 454
        logger.debug("get_object_public: %s %s %s", account, container, name)
468 455
        self._can_read(user, account, container, name)
469
        path = self._get_objectinfo(account, container, name)[0]
456
        path = self._lookup_object(account, container, name)[0]
470 457
        if self.permissions.public_check(path):
471 458
            return '/public/' + path
472 459
        return None
......
477 464
        
478 465
        logger.debug("update_object_public: %s %s %s %s", account, container, name, public)
479 466
        self._can_write(user, account, container, name)
480
        path = self._get_objectinfo(account, container, name)[0]
467
        path = self._lookup_object(account, container, name)[0]
481 468
        if not public:
482 469
            self.permissions.public_unset(path)
483 470
        else:
......
489 476
        
490 477
        logger.debug("get_object_hashmap: %s %s %s %s", account, container, name, version)
491 478
        self._can_read(user, account, container, name)
492
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name, version)
493
        hashmap = self.mapper.map_retr(version_id)
494
        return size, [binascii.hexlify(x) for x in hashmap]
479
        path, node = self._lookup_object(account, container, name)
480
        props = self._get_version(node, version)
481
        hashmap = self.mapper.map_retr(props[SERIAL])
482
        return props[SIZE], [binascii.hexlify(x) for x in hashmap]
495 483
    
496 484
    @backend_method
497 485
    def update_object_hashmap(self, user, account, container, name, size, hashmap, meta={}, replace_meta=False, permissions=None):
......
506 494
            ie = IndexError()
507 495
            ie.data = missing
508 496
            raise ie
509
        path = self._get_containerinfo(account, container)[0]
510
        path = '/'.join((path, name))
511 497
        if permissions is not None:
512 498
            self._check_permissions(path, permissions)
513
        src_version_id, dest_version_id = self._copy_version(user, path, path, not replace_meta, False)
514
        sql = 'update versions set size = ? where version_id = ?'
515
        self.con.execute(sql, (size, dest_version_id))
499
        path, node = self._put_object_path(account, container, name)
500
        src_version_id, dest_version_id = self._copy_version(user, node, node, not replace_meta, False)
501
        # TODO: Set size.
502
#         sql = 'update versions set size = ? where version_id = ?'
503
#         self.con.execute(sql, (size, dest_version_id))
516 504
        self.mapper.map_stor(dest_version_id, [binascii.unhexlify(x) for x in hashmap])
517
        for k, v in meta.iteritems():
518
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
519
            self.con.execute(sql, (dest_version_id, k, v))
505
        # TODO: Check if can update meta with empty values.
506
        self.node.attribute_set(dest_version_id, ((k, v) for k, v in meta.iteritems()))
520 507
        if permissions is not None:
521 508
            self.permissions.access_set(path, permissions)
522 509
    
......
529 516
            raise NotAllowedError
530 517
        self._can_read(user, account, src_container, src_name)
531 518
        self._can_write(user, account, dest_container, dest_name)
532
        self._get_containerinfo(account, src_container)
533
        if src_version is None:
534
            src_path = self._get_objectinfo(account, src_container, src_name)[0]
535
        else:
536
            src_path = '/'.join((account, src_container, src_name))
537
        dest_path = self._get_containerinfo(account, dest_container)[0]
538
        dest_path = '/'.join((dest_path, dest_name))
519
        src_path, src_node = self._lookup_object(account, src_container, src_name)
520
        src_props = self._get_version(src_node)
539 521
        if permissions is not None:
540 522
            self._check_permissions(dest_path, permissions)
541
        src_version_id, dest_version_id = self._copy_version(user, src_path, dest_path, not replace_meta, True, src_version)
542
        for k, v in dest_meta.iteritems():
543
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
544
            self.con.execute(sql, (dest_version_id, k, v))
523
        dest_path, dest_node = self._put_object_path(account, dest_container, dest_name)
524
        src_version_id, dest_version_id = self._copy_version(user, src_node, dest_node, not replace_meta, True, src_version)
525
        # TODO: Check if can update meta with empty values.
526
        self.node.attribute_set(dest_version_id, ((k, v) for k, v in meta.iteritems()))
545 527
        if permissions is not None:
546 528
            self.permissions.access_set(dest_path, permissions)
547 529
    
......
563 545
        
564 546
        if until is not None:
565 547
            path = '/'.join((account, container, name))
566
            sql = '''select version_id from versions where name = ? and tstamp <= ?'''
567
            c = self.con.execute(sql, (path, until))
568
            for v in [x[0] in c.fetchall()]:
569
                self._del_version(v)
548
            node = self.node.node_lookup(path)
549
            if node is None:
550
                return
551
            versions = self.node.node_purge(node, until, CLUSTER_NORMAL)
552
            versions += self.node.node_purge(node, until, CLUSTER_HISTORY)
553
            for v in versions:
554
                self.mapper.map_remv(v)
555
            self.node.node_purge_children(node, until, CLUSTER_DELETED)
570 556
            try:
571
                version_id = self._get_version(path)[0]
557
                props = self._get_version(node)
572 558
            except NameError:
573 559
                pass
574 560
            else:
575 561
                self.permissions.access_clear(path)
576 562
            return
577 563
        
578
        path = self._get_objectinfo(account, container, name)[0]
579
        self._put_version(path, user, 0, 1)
564
        path, node = self._lookup_object(account, container, name)
565
        self._copy_version(user, node, node, False, False, None, CLUSTER_DELETED)
580 566
        self.permissions.access_clear(path)
581 567
    
582
    @backend_method
583
    def list_versions(self, user, account, container, name):
584
        """Return a list of all (version, version_timestamp) tuples for an object."""
585
        
586
        logger.debug("list_versions: %s %s %s", account, container, name)
587
        self._can_read(user, account, container, name)
588
        # This will even show deleted versions.
589
        path = '/'.join((account, container, name))
590
        sql = '''select distinct version_id, tstamp from versions where name = ? and hide = 0'''
591
        c = self.con.execute(sql, (path,))
592
        return [(int(x[0]), int(x[1])) for x in c.fetchall()]
568
#     @backend_method
569
#     def list_versions(self, user, account, container, name):
570
#         """Return a list of all (version, version_timestamp) tuples for an object."""
571
#         
572
#         logger.debug("list_versions: %s %s %s", account, container, name)
573
#         self._can_read(user, account, container, name)
574
#         # This will even show deleted versions.
575
#         path = '/'.join((account, container, name))
576
#         sql = '''select distinct version_id, tstamp from versions where name = ? and hide = 0'''
577
#         c = self.con.execute(sql, (path,))
578
#         return [(int(x[0]), int(x[1])) for x in c.fetchall()]
593 579
    
594 580
    @backend_method(autocommit=0)
595 581
    def get_block(self, hash):
......
619 605
        h, e = self.blocker.block_delta(binascii.unhexlify(hash), ((offset, data),))
620 606
        return binascii.hexlify(h)
621 607
    
622
    
623
    
624
    
625
    
626
    
627
    
628
    
629
    
630
    
631
    
632
    
633
    
634
    
635
    
636
    
637
    
638
    def _sql_until(self, until=None):
639
        """Return the sql to get the latest versions until the timestamp given."""
640
        if until is None:
641
            until = int(time.time())
642
        sql = '''select version_id, name, tstamp, size from versions v
643
                    where version_id = (select max(version_id) from versions
644
                                        where v.name = name and tstamp <= %s)
645
                    and hide = 0'''
646
        return sql % (until,)
647
    
648
    def _put_version(self, path, user, size=0, hide=0):
649
        tstamp = int(time.time())
650
        sql = 'insert into versions (name, user, tstamp, size, hide) values (?, ?, ?, ?, ?)'
651
        id = self.con.execute(sql, (path, user, tstamp, size, hide)).lastrowid
652
        return str(id), tstamp
653
    
654
    def _get_versioninfo(self, account, container, name, until=None):
655
        """Return path, latest version, associated timestamp and size until the timestamp given."""
656
        
657
        p = (account, container, name)
658
        try:
659
            p = p[:p.index(None)]
660
        except ValueError:
661
            pass
662
        path = '/'.join(p)
663
        node = self.node.node_lookup(path)
664
        if node is not None:
665
            props = self.node.version_lookup(node, until, CLUSTER_NORMAL)
666
            # TODO: Do one lookup.
667
            if props is None and until is not None:
668
                props = self.node.version_lookup(node, until, CLUSTER_HISTORY)
669
            if props is not None:
670
                return path, props[SERIAL], props[MTIME], props[SIZE]
671
        raise NameError('Path does not exist')
672
    
673
    def _get_accountinfo(self, account, until=None):
674
        try:
675
            path, version_id, mtime, size = self._get_versioninfo(account, None, None, until)
676
            return version_id, mtime
677
        except:
678
            raise NameError('Account does not exist')
679
    
680
    def _get_containerinfo(self, account, container, until=None):
681
        try:
682
            path, version_id, mtime, size = self._get_versioninfo(account, container, None, until)
683
            return path, version_id, mtime
684
        except:
685
            raise NameError('Container does not exist')
686
    
687
    def _get_objectinfo(self, account, container, name, version=None):
688
        path = '/'.join((account, container, name))
689
        version_id, muser, mtime, size = self._get_version(path, version)
690
        return path, version_id, muser, mtime, size
691
    
692
    def _create_account(self, user, account):
693
        try:
694
            self._get_accountinfo(account)
695
        except NameError:
696
            self._put_version(account, user)
697
    
698 608
    def _check_policy(self, policy):
699 609
        for k in policy.keys():
700 610
            if policy[k] == '':
......
721 631
            limit = 10000
722 632
        return start, limit
723 633
    
724
    def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None, allowed=[]):
725
        cont_prefix = path + '/'
726
        if keys and len(keys) > 0:
727
            sql = '''select distinct o.name, o.version_id from (%s) o, metadata m where o.name like ? and
728
                        m.version_id = o.version_id and m.key in (%s)'''
729
            sql = sql % (self._sql_until(until), ', '.join('?' * len(keys)))
730
            param = (cont_prefix + prefix + '%',) + tuple(keys)
731
            if allowed:
732
                sql += ' and (' + ' or '.join(('o.name like ?',) * len(allowed)) + ')'
733
                param += tuple([x + '%' for x in allowed])
734
            sql += ' order by o.name'
735
        else:
736
            sql = 'select name, version_id from (%s) where name like ?'
737
            sql = sql % self._sql_until(until)
738
            param = (cont_prefix + prefix + '%',)
739
            if allowed:
740
                sql += ' and (' + ' or '.join(('name like ?',) * len(allowed)) + ')'
741
                param += tuple([x + '%' for x in allowed])
742
            sql += ' order by name'
743
        c = self.con.execute(sql, param)
744
        objects = [(x[0][len(cont_prefix):], x[1]) for x in c.fetchall()]
745
        if delimiter:
746
            pseudo_objects = []
747
            for x in objects:
748
                pseudo_name = x[0]
749
                i = pseudo_name.find(delimiter, len(prefix))
750
                if not virtual:
751
                    # If the delimiter is not found, or the name ends
752
                    # with the delimiter's first occurence.
753
                    if i == -1 or len(pseudo_name) == i + len(delimiter):
754
                        pseudo_objects.append(x)
755
                else:
756
                    # If the delimiter is found, keep up to (and including) the delimiter.
757
                    if i != -1:
758
                        pseudo_name = pseudo_name[:i + len(delimiter)]
759
                    if pseudo_name not in [y[0] for y in pseudo_objects]:
760
                        if pseudo_name == x[0]:
761
                            pseudo_objects.append(x)
762
                        else:
763
                            pseudo_objects.append((pseudo_name, None))
764
            objects = pseudo_objects
765
        
766
        start, limit = self._list_limits([x[0] for x in objects], marker, limit)
767
        return objects[start:start + limit]
768
    
769
    def _del_version(self, version):
770
        self.mapper.map_remv(version)
771
        sql = 'delete from versions where version_id = ?'
772
        self.con.execute(sql, (version,))
634
#     def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None, allowed=[]):
635
#         cont_prefix = path + '/'
636
#         if keys and len(keys) > 0:
637
#             sql = '''select distinct o.name, o.version_id from (%s) o, metadata m where o.name like ? and
638
#                         m.version_id = o.version_id and m.key in (%s)'''
639
#             sql = sql % (self._sql_until(until), ', '.join('?' * len(keys)))
640
#             param = (cont_prefix + prefix + '%',) + tuple(keys)
641
#             if allowed:
642
#                 sql += ' and (' + ' or '.join(('o.name like ?',) * len(allowed)) + ')'
643
#                 param += tuple([x + '%' for x in allowed])
644
#             sql += ' order by o.name'
645
#         else:
646
#             sql = 'select name, version_id from (%s) where name like ?'
647
#             sql = sql % self._sql_until(until)
648
#             param = (cont_prefix + prefix + '%',)
649
#             if allowed:
650
#                 sql += ' and (' + ' or '.join(('name like ?',) * len(allowed)) + ')'
651
#                 param += tuple([x + '%' for x in allowed])
652
#             sql += ' order by name'
653
#         c = self.con.execute(sql, param)
654
#         objects = [(x[0][len(cont_prefix):], x[1]) for x in c.fetchall()]
655
#         if delimiter:
656
#             pseudo_objects = []
657
#             for x in objects:
658
#                 pseudo_name = x[0]
659
#                 i = pseudo_name.find(delimiter, len(prefix))
660
#                 if not virtual:
661
#                     # If the delimiter is not found, or the name ends
662
#                     # with the delimiter's first occurence.
663
#                     if i == -1 or len(pseudo_name) == i + len(delimiter):
664
#                         pseudo_objects.append(x)
665
#                 else:
666
#                     # If the delimiter is found, keep up to (and including) the delimiter.
667
#                     if i != -1:
668
#                         pseudo_name = pseudo_name[:i + len(delimiter)]
669
#                     if pseudo_name not in [y[0] for y in pseudo_objects]:
670
#                         if pseudo_name == x[0]:
671
#                             pseudo_objects.append(x)
672
#                         else:
673
#                             pseudo_objects.append((pseudo_name, None))
674
#             objects = pseudo_objects
675
#         
676
#         start, limit = self._list_limits([x[0] for x in objects], marker, limit)
677
#         return objects[start:start + limit]
773 678
    
774 679
    # Path functions.
775 680
    
681
    def _put_path(self, parent, path):
682
        node = self.node.node_create(parent, path)
683
        self.node.version_create(node, 0, None, path, CLUSTER_NORMAL)
684
    
685
    def _put_object_path(self, account, container, name):
686
        path, parent = self._lookup_container(account, container)
687
        path = '/'.join((path, name))
688
        node = self.node.node_lookup(path)
689
        if node is None:
690
            node = self.node.node_create(parent, path)
691
        return path, node
692
    
776 693
    def _lookup_account(self, account, create=True):
777 694
        node = self.node.node_lookup(account)
778 695
        if node is None and create:
779
            node = self.node.node_create(ROOTNODE, account)
780
            self.node.version_create(node, 0, None, account, CLUSTER_NORMAL)
781
        return node
696
            self._put_path(ROOTNODE, account)
697
        return account, node
782 698
    
783 699
    def _lookup_container(self, account, container):
784
        node = self.node.node_lookup('/'.join((account, container)))
700
        path = '/'.join((account, container))
701
        node = self.node.node_lookup(path)
785 702
        if node is None:
786 703
            raise NameError('Container does not exist')
787
        return node
704
        return path, node
788 705
    
789 706
    def _lookup_object(self, account, container, name):
790
        node = self.node.node_lookup('/'.join((account, container, name)))
707
        path = '/'.join((account, container, name))
708
        node = self.node.node_lookup(path)
791 709
        if node is None:
792 710
            raise NameError('Object does not exist')
793
        return node
711
        return path, node
794 712
    
795 713
    def _get_properties(self, node, until=None):
796 714
        """Return properties until the timestamp given."""
......
807 725
    def _get_statistics(self, node, path, until=None):
808 726
        """Return count, sum of size and latest timestamp of everything under node/path."""
809 727
        
728
        # TODO: Remove this function.
729
        
810 730
        if until is None:
811 731
            return self.node.node_statistics(node, CLUSTER_NORMAL)
812 732
        else:
......
823 743
                raise IndexError('Version does not exist')
824 744
        return props
825 745
    
826
    def _copy_version(self, user, src_node, dest_node, copy_meta=True, copy_data=True, src_version=None):
746
    def _copy_version(self, user, src_node, dest_node, copy_meta=True, copy_data=True, src_version=None, dest_cluster=CLUSTER_NORMAL):
747
        
748
        # TODO: Break this into two functions - one that creates and one that copies.
749
        
827 750
        # Get source serial and size.
828 751
        if src_version is not None:
829 752
            src_props = self._get_version(src_node, src_version)
......
848 771
            dest_props = self.node.version_lookup(dest_node, inf, CLUSTER_NORMAL)
849 772
            if dest_props is not None:
850 773
                self.node.version_recluster(dest_props[SERIAL], CLUSTER_HISTORY)
851
        dest_version_id, mtime = self.node.version_create(dest_node, size, src_version_id, user, CLUSTER_NORMAL)
774
        dest_version_id, mtime = self.node.version_create(dest_node, size, src_version_id, user, dest_cluster)
852 775
        
853 776
        # Copy meta and data.
854 777
        if copy_meta and src_version_id is not None:

Also available in: Unified diff