Revision f77b150f pithos/backends/simple.py

b/pithos/backends/simple.py
73 73
                    version_id integer primary key,
74 74
                    name text,
75 75
                    user text,
76
                    tstamp datetime default current_timestamp,
76
                    tstamp integer not null,
77 77
                    size integer default 0,
78
                    hide integer default 0)'''
78
                    trash integer default 0,
79
                    until integer default null)'''
79 80
        self.con.execute(sql)
80 81
        sql = '''create table if not exists metadata (
81 82
                    version_id integer,
......
229 230
        logger.debug("get_container_meta: %s %s %s", account, container, until)
230 231
        if user != account:
231 232
            raise NotAllowedError
233
        
234
        # TODO: Container meta for trash.
232 235
        path, version_id, mtime = self._get_containerinfo(account, container, until)
233 236
        count, bytes, tstamp = self._get_pathstats(path, until)
234 237
        if mtime > tstamp:
......
314 317
        path, version_id, mtime = self._get_containerinfo(account, container)
315 318
        
316 319
        if until is not None:
317
            sql = '''select version_id from versions where name like ? and tstamp <= datetime(%s, 'unixepoch')'''
320
            sql = '''select version_id from versions where name like ? and tstamp <= ?'''
318 321
            c = self.con.execute(sql, (path + '/%', until))
319 322
            versions = [x[0] for x in c.fetchall()]
320 323
            for v in versions:
......
327 330
        
328 331
        if self._get_pathcount(path) > 0:
329 332
            raise IndexError('Container is not empty')
330
        sql = 'delete from versions where name = ?'
331
        self.con.execute(sql, (path,))
333
        sql = 'delete from versions where name like ?' # May contain hidden trash items.
334
        self.con.execute(sql, (path + '/%',))
332 335
        sql = 'delete from policy where name = ?'
333 336
        self.con.execute(sql, (path,))
334 337
        self._copy_version(user, account, account, True, True) # New account version (for timestamp update).
......
342 345
        path, version_id, mtime = self._get_containerinfo(account, container, until)
343 346
        return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, trash, until)
344 347
    
345
    def list_object_meta(self, user, account, container, until=None):
348
    def list_object_meta(self, user, account, container, trash=False, until=None):
346 349
        """Return a list with all the container's object meta keys."""
347 350
        
348 351
        logger.debug("list_object_meta: %s %s %s", account, container, until)
......
351 354
        path, version_id, mtime = self._get_containerinfo(account, container, until)
352 355
        sql = '''select distinct m.key from (%s) o, metadata m
353 356
                    where m.version_id = o.version_id and o.name like ?'''
354
        sql = sql % self._sql_until(until)
357
        sql = sql % self._sql_until(until, trash)
355 358
        c = self.con.execute(sql, (path + '/%',))
356 359
        return [x[0] for x in c.fetchall()]
357 360
    
......
509 512
            c = self.con.execute(sql, (path,))
510 513
        else:
511 514
            path = os.path.join(account, container, name)
512
            sql = '''select version_id from versions where name = ? and tstamp <= datetime(%s, 'unixepoch')'''
515
            sql = '''select version_id from versions where name = ? and tstamp <= ?'''
513 516
            c = self.con.execute(sql, (path, until))
514 517
        versions = [x[0] for x in c.fetchall()]
515 518
        for v in versions:
......
518 521
            sql = 'delete from versions where version_id = ?'
519 522
            self.con.execute(sql, (v,))
520 523
        
521
        # If no more versions exist, delete permissions/public.
522
        sql = 'select version_id from versions where name = ?'
524
        # If no more normal versions exist, delete permissions/public.
525
        sql = 'select version_id from versions where name = ? and trash = 0'
523 526
        row = self.con.execute(sql, (path,)).fetchone()
524 527
        if row is None:
525 528
            self._del_sharing(path)
526 529
        self.con.commit()
527 530
    
528
    def update_object_trash(self, user, account, container, name, trash=True):
529
        """Trash/untrash an object."""
531
    def trash_object(self, user, account, container, name):
532
        """Trash an object."""
530 533
        
531 534
        logger.debug("trash_object: %s %s %s", account, container, name)
532 535
        if user != account:
533 536
            raise NotAllowedError
534
        if trash:
535
            path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name)
536
            self._del_sharing(path)
537
        else:
538
            path = os.path.join(account, container, name)
539
            sql = '''select version_id, hide from versions where name = ?
540
                        order by version_id desc limit 1'''
541
            c = self.con.execute(sql, (path,))
542
            row = c.fetchone()
543
            if not row or not int(row[1]):
544
                raise NameError('Object not in trash')
545
            version_id = row[0]
546
        hide = 1 if trash else 0
547
        sql = 'update versions set hide = ? where version_id = ?'
548
        self.con.execute(sql, (hide, version_id,))
549
        seld.con.commit()
537
        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name)
538
        src_version_id, dest_version_id = self._copy_version(user, path, path, True, True, version_id)
539
        sql = 'update versions set trash = 1 where version_id = ?'
540
        self.con.execute(sql, (dest_version_id,))
541
        self._del_sharing(path)
542
        self.con.commit()
543
    
544
    def untrash_object(self, user, account, container, name, version):
545
        """Untrash an object."""
546
        
547
        logger.debug("untrash_object: %s %s %s %s", account, container, name, version)
548
        if user != account:
549
            raise NotAllowedError
550
        
551
        path = os.path.join(account, container, name)
552
        sql = '''select version_id from versions where name = ? and version_id = ? and trash = 1'''
553
        c = self.con.execute(sql, (path, version))
554
        row = c.fetchone()
555
        if not row or not int(row[1]):
556
            raise NameError('Object not in trash')
557
        sql = 'update versions set until = ? where version_id = ?'
558
        self.con.execute(sql, (int(time.time()), version))
559
        self.con.commit()
550 560
    
551 561
    def list_versions(self, user, account, container, name):
552 562
        """Return a list of all (version, version_timestamp) tuples for an object."""
553 563
        
554 564
        logger.debug("list_versions: %s %s %s", account, container, name)
555 565
        self._can_read(user, account, container, name)
556
        # This will even show deleted versions.
557 566
        path = os.path.join(account, container, name)
558
        sql = '''select distinct version_id, strftime('%s', tstamp) from versions where name = ?'''
567
        sql = '''select distinct version_id, tstamp from versions where name = ? and trash = 0'''
559 568
        c = self.con.execute(sql, (path,))
560 569
        return [(int(x[0]), int(x[1])) for x in c.fetchall()]
561 570
    
......
595 604
        dest_data = src_data[:offset] + data + src_data[offset + len(data):]
596 605
        return self.put_block(dest_data)
597 606
    
598
    def _sql_until(self, until=None):
607
    def _sql_until(self, until=None, trash=False):
599 608
        """Return the sql to get the latest versions until the timestamp given."""
609
        
600 610
        if until is None:
601 611
            until = int(time.time())
602
        sql = '''select version_id, name, strftime('%s', tstamp) as tstamp, size from versions v
603
                    where version_id = (select max(version_id) from versions
604
                                        where v.name = name and tstamp <= datetime(%s, 'unixepoch'))
605
                    and hide = 0'''
606
        return sql % ('%s', until)
612
        if not trash:
613
            sql = '''select version_id, name, tstamp, size from versions v
614
                        where version_id = (select max(version_id) from versions
615
                                            where v.name = name and tstamp <= ?)
616
                        and trash = 0'''
617
            return sql % (until,)
618
        else:
619
            sql = '''select version_id, name, tstamp, size from versions v
620
                        where trash = 1 and tstamp <= ? and (until is null or until > ?)'''
621
            return sql % (until, until)
607 622
    
608 623
    def _get_pathstats(self, path, until=None):
609 624
        """Return count, sum of size and latest timestamp of everything under path (latest versions/no trash)."""
610 625
        
611
        sql = 'select count(version_id), total(size), max(tstamp) from (%s) where name like ?'
626
        sql = 'select count(version_id), total(size) from (%s) where name like ?'
612 627
        sql = sql % self._sql_until(until)
613 628
        c = self.con.execute(sql, (path + '/%',))
629
        total_count, total_size = c.fetchone()
630
        sql = 'select max(tstamp) from versions where name like ? and tstamp <= ?' # Include trash actions.
631
        c = self.con.execute(sql, (path + '/%', until))
614 632
        row = c.fetchone()
615
        tstamp = row[2] if row[2] is not None else 0
616
        return int(row[0]), int(row[1]), int(tstamp)
633
        tstamp = row[0] if row[0] is not None else 0
634
        return int(total_count), int(total_size), int(tstamp)
617 635
    
618 636
    def _get_pathcount(self, path):
619 637
        """Return count of everything under path (including versions/trash)."""
620 638
        
621
        sql = 'select count(version_id) from versions where name like ?'
639
        sql = 'select count(version_id) from versions where name like ? and until is null'
622 640
        c = self.con.execute(sql, (path + '/%',))
623 641
        row = c.fetchone()
624 642
        return int(row[0])
......
642 660
        return str(row[0]), str(row[1]), int(row[2]), int(row[3])
643 661
    
644 662
    def _put_version(self, path, user, size=0):
645
        sql = 'insert into versions (name, user, size) values (?, ?, ?)'
646
        id = self.con.execute(sql, (path, user, size)).lastrowid
663
        tstamp = int(time.time())
664
        sql = 'insert into versions (name, user, tstamp, size) values (?, ?, ?, ?)'
665
        id = self.con.execute(sql, (path, user, tstamp, size)).lastrowid
647 666
        self.con.commit()
648 667
        return str(id)
649 668
    
......
857 876
    def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], trash=False, until=None):
858 877
        cont_prefix = path + '/'
859 878
        
860
        if trash:
861
            table = '''select version_id, name, strftime('%s', tstamp) as tstamp, size from versions v
862
                        where hide = 1''' % ('%s',)
863
            if until is not None:
864
                table += ''' and tstamp <= datetime(%s, 'unixepoch')''' % (until,)
865
        else:
866
            table = self._sql_until(until)
867 879
        if keys and len(keys) > 0:
868 880
            sql = '''select distinct o.name, o.version_id from (%s) o, metadata m where o.name like ? and
869 881
                        m.version_id = o.version_id and m.key in (%s) order by o.name'''
870
            sql = sql % (table, ', '.join('?' * len(keys)))
882
            sql = sql % (self._sql_until(until, trash), ', '.join('?' * len(keys)))
871 883
            param = (cont_prefix + prefix + '%',) + tuple(keys)
872 884
        else:
873 885
            sql = 'select name, version_id from (%s) where name like ? order by name'
874
            sql = sql % (table,)
886
            sql = sql % (self._sql_until(until, trash),)
875 887
            param = (cont_prefix + prefix + '%',)
876 888
        c = self.con.execute(sql, param)
877 889
        objects = [(x[0][len(cont_prefix):], x[1]) for x in c.fetchall()]

Also available in: Unified diff