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,))
|