Revision 84846143

b/pithos/backends/base.py
176 176
        """
177 177
        return
178 178
    
179
    def delete_container(self, user, account, container):
180
        """Delete the container with the given name.
179
    def delete_container(self, user, account, container, until=None):
180
        """Delete/purge the container with the given name.
181 181
        
182 182
        Raises:
183 183
            NotAllowedError: Operation not permitted
......
359 359
        """
360 360
        return
361 361
    
362
    def delete_object(self, user, account, container, name):
363
        """Delete an object.
362
    def delete_object(self, user, account, container, name, until=None):
363
        """Delete/purge an object.
364 364
        
365 365
        Raises:
366 366
            NotAllowedError: Operation not permitted
b/pithos/backends/simple.py
65 65
            os.makedirs(basepath)
66 66
        
67 67
        self.con = sqlite3.connect(db, check_same_thread=False)
68
        
69
        sql = '''pragma foreign_keys = on'''
70
        self.con.execute(sql)
71
        
68 72
        sql = '''create table if not exists versions (
69 73
                    version_id integer primary key,
70 74
                    name text,
71 75
                    user text,
72
                    tstamp datetime default current_timestamp,
76
                    tstamp integer not null,
73 77
                    size integer default 0,
74 78
                    hide integer default 0)'''
75 79
        self.con.execute(sql)
76 80
        sql = '''create table if not exists metadata (
77
                    version_id integer, key text, value text, primary key (version_id, key))'''
81
                    version_id integer,
82
                    key text,
83
                    value text,
84
                    primary key (version_id, key)
85
                    foreign key (version_id) references versions(version_id)
86
                    on delete cascade)'''
87
        self.con.execute(sql)
88
        sql = '''create table if not exists hashmaps (
89
                    version_id integer,
90
                    pos integer,
91
                    block_id text,
92
                    primary key (version_id, pos)
93
                    foreign key (version_id) references versions(version_id)
94
                    on delete cascade)'''
78 95
        self.con.execute(sql)
79 96
        sql = '''create table if not exists blocks (
80 97
                    block_id text, data blob, primary key (block_id))'''
81 98
        self.con.execute(sql)
82
        sql = '''create table if not exists hashmaps (
83
                    version_id integer, pos integer, block_id text, primary key (version_id, pos))'''
99
        
100
        sql = '''create table if not exists policy (
101
                    name text, key text, value text, primary key (name, key))'''
84 102
        self.con.execute(sql)
103
        
85 104
        sql = '''create table if not exists groups (
86 105
                    account text, name text, users text, primary key (account, name))'''
87 106
        self.con.execute(sql)
88
        sql = '''create table if not exists policy (
89
                    name text, key text, value text, primary key (name, key))'''
90
        self.con.execute(sql)
91 107
        sql = '''create table if not exists permissions (
92 108
                    name text, read text, write text, primary key (name))'''
93 109
        self.con.execute(sql)
......
170 186
                self.con.execute(sql, (account, k, ','.join(v)))
171 187
        self.con.commit()
172 188
    
189
    def put_account(self, user, account):
190
        """Create a new account with the given name."""
191
        
192
        logger.debug("put_account: %s", account)
193
        if user != account:
194
            raise NotAllowedError
195
        try:
196
            version_id, mtime = self._get_accountinfo(account)
197
        except NameError:
198
            pass
199
        else:
200
            raise NameError('Account already exists')
201
        version_id = self._put_version(account, user)
202
        self.con.commit()
203
    
173 204
    def delete_account(self, user, account):
174 205
        """Delete the account with the given name."""
175 206
        
176 207
        logger.debug("delete_account: %s", account)
177 208
        if user != account:
178 209
            raise NotAllowedError
179
        count, bytes, tstamp = self._get_pathstats(account)
210
        count = self._get_pathstats(account)[0]
180 211
        if count > 0:
181 212
            raise IndexError('Account is not empty')
182
        self._del_path(account) # Point of no return.
213
        sql = 'delete from versions where name = ?'
214
        self.con.execute(sql, (account,))
215
        sql = 'delete from groups where name = ?'
216
        self.con.execute(sql, (account,))
217
        self.con.commit()
183 218
    
184 219
    def list_containers(self, user, account, marker=None, limit=10000, until=None):
185 220
        """Return a list of containers existing under an account."""
......
271 306
            self.con.execute(sql, (path, k, v))
272 307
        self.con.commit()
273 308
    
274
    def delete_container(self, user, account, container):
275
        """Delete the container with the given name."""
309
    def delete_container(self, user, account, container, until=None):
310
        """Delete/purge the container with the given name."""
276 311
        
277
        logger.debug("delete_container: %s %s", account, container)
312
        logger.debug("delete_container: %s %s %s", account, container, until)
278 313
        if user != account:
279 314
            raise NotAllowedError
280 315
        path, version_id, mtime = self._get_containerinfo(account, container)
281
        count, bytes, tstamp = self._get_pathstats(path)
316
        
317
        if until is not None:
318
            sql = '''select version_id from versions where name like ? and tstamp <= ?'''
319
            c = self.con.execute(sql, (path + '/%', until))
320
            for v in [x[0] for x in c.fetchall()]:
321
                self._del_version(v)
322
            self.con.commit()
323
            return
324
        
325
        count = self._get_pathstats(path)[0]
282 326
        if count > 0:
283 327
            raise IndexError('Container is not empty')
284
        self._del_path(path) # Point of no return.
328
        sql = 'delete from versions where name like ?' # May contain hidden items.
329
        self.con.execute(sql, (path + '/%',))
330
        sql = 'delete from policy where name = ?'
331
        self.con.execute(sql, (path,))
285 332
        self._copy_version(user, account, account, True, True) # New account version (for timestamp update).
286 333
    
287 334
    def list_objects(self, user, account, container, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None):
288 335
        """Return a list of objects existing under a container."""
289 336
        
290
        logger.debug("list_objects: %s %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit, until)
337
        logger.debug("list_objects: %s %s %s %s %s %s %s %s %s", account, container, prefix, delimiter, marker, limit, virtual, keys, until)
291 338
        if user != account:
292 339
            raise NotAllowedError
293 340
        path, version_id, mtime = self._get_containerinfo(account, container, until)
......
448 495
        self.copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
449 496
        self.delete_object(user, account, src_container, src_name)
450 497
    
451
    def delete_object(self, user, account, container, name):
452
        """Delete an object."""
498
    def delete_object(self, user, account, container, name, until=None):
499
        """Delete/purge an object."""
453 500
        
454
        logger.debug("delete_object: %s %s %s", account, container, name)
501
        logger.debug("delete_object: %s %s %s %s", account, container, name, until)
455 502
        if user != account:
456 503
            raise NotAllowedError
504
        
505
        if until is not None:
506
            path = os.path.join(account, container, name)
507
            sql = '''select version_id from versions where name = ? and tstamp <= ?'''
508
            c = self.con.execute(sql, (path, until))
509
            for v in [x[0] in c.fetchall()]:
510
                self._del_version(v)
511
            try:
512
                version_id = self._get_version(path)[0]
513
            except NameError:
514
                pass
515
            else:
516
                self._del_sharing(path)
517
            self.con.commit()
518
            return
519
        
457 520
        path = self._get_objectinfo(account, container, name)[0]
458 521
        self._put_version(path, user, 0, 1)
459
        sql = 'delete from permissions where name = ?'
460
        self.con.execute(sql, (path,))
461
        sql = 'delete from public where name = ?'
462
        self.con.execute(sql, (path,))
463
        self.con.commit()
522
        self._del_sharing(path)
464 523
    
465 524
    def list_versions(self, user, account, container, name):
466 525
        """Return a list of all (version, version_timestamp) tuples for an object."""
......
469 528
        self._can_read(user, account, container, name)
470 529
        # This will even show deleted versions.
471 530
        path = os.path.join(account, container, name)
472
        sql = '''select distinct version_id, strftime('%s', tstamp) from versions where name = ? and hide = 0'''
531
        sql = '''select distinct version_id, tstamp from versions where name = ? and hide = 0'''
473 532
        c = self.con.execute(sql, (path,))
474 533
        return [(int(x[0]), int(x[1])) for x in c.fetchall()]
475 534
    
......
513 572
        """Return the sql to get the latest versions until the timestamp given."""
514 573
        if until is None:
515 574
            until = int(time.time())
516
        sql = '''select version_id, name, strftime('%s', tstamp) as tstamp, size from versions v
575
        sql = '''select version_id, name, tstamp, size from versions v
517 576
                    where version_id = (select max(version_id) from versions
518
                                        where v.name = name and tstamp <= datetime(%s, 'unixepoch'))
577
                                        where v.name = name and tstamp <= ?)
519 578
                    and hide = 0'''
520
        return sql % ('%s', until)
579
        return sql % (until,)
521 580
    
522 581
    def _get_pathstats(self, path, until=None):
523 582
        """Return count and sum of size of everything under path and latest timestamp."""
......
531 590
    
532 591
    def _get_version(self, path, version=None):
533 592
        if version is None:
534
            sql = '''select version_id, user, strftime('%s', tstamp), size, hide from versions where name = ?
593
            sql = '''select version_id, user, tstamp, size, hide from versions where name = ?
535 594
                        order by version_id desc limit 1'''
536 595
            c = self.con.execute(sql, (path,))
537 596
            row = c.fetchone()
......
539 598
                raise NameError('Object does not exist')
540 599
        else:
541 600
            # The database (sqlite) will not complain if the version is not an integer.
542
            sql = '''select version_id, user, strftime('%s', tstamp), size from versions where name = ?
601
            sql = '''select version_id, user, tstamp, size from versions where name = ?
543 602
                        and version_id = ?'''
544 603
            c = self.con.execute(sql, (path, version))
545 604
            row = c.fetchone()
......
548 607
        return str(row[0]), str(row[1]), int(row[2]), int(row[3])
549 608
    
550 609
    def _put_version(self, path, user, size=0, hide=0):
551
        sql = 'insert into versions (name, user, size, hide) values (?, ?, ?, ?)'
552
        id = self.con.execute(sql, (path, user, size, hide)).lastrowid
610
        tstamp = int(time.time())
611
        sql = 'insert into versions (name, user, tstamp, size, hide) values (?, ?, ?, ?, ?)'
612
        id = self.con.execute(sql, (path, user, tstamp, size, hide)).lastrowid
553 613
        self.con.commit()
554 614
        return str(id)
555 615
    
......
753 813
        self.con.execute(sql, (path,))
754 814
        self.con.commit()
755 815
    
816
    def _del_sharing(self, path):
817
        sql = 'delete from permissions where name = ?'
818
        self.con.execute(sql, (path,))
819
        sql = 'delete from public where name = ?'
820
        self.con.execute(sql, (path,))
821
        self.con.commit()
822
    
756 823
    def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], until=None):
757 824
        cont_prefix = path + '/'
758 825
        if keys and len(keys) > 0:
......
797 864
            limit = 10000
798 865
        return objects[start:start + limit]
799 866
    
800
    def _del_path(self, path):
801
        sql = '''delete from hashmaps where version_id in
802
                    (select version_id from versions where name = ?)'''
803
        self.con.execute(sql, (path,))
804
        sql = '''delete from metadata where version_id in
805
                    (select version_id from versions where name = ?)'''
806
        self.con.execute(sql, (path,))
807
        sql = '''delete from versions where name = ?'''
808
        self.con.execute(sql, (path,))
809
        self.con.commit()
867
    def _del_version(self, version):
868
        sql = 'delete from hashmaps where version_id = ?'
869
        self.con.execute(sql, (version,))
870
        sql = 'delete from versions where version_id = ?'
871
        self.con.execute(sql, (version,))

Also available in: Unified diff