Keep trash history.
authorAntony Chazapis <chazapis@gmail.com>
Sat, 16 Jul 2011 14:02:59 +0000 (17:02 +0300)
committerAntony Chazapis <chazapis@gmail.com>
Sat, 16 Jul 2011 14:02:59 +0000 (17:02 +0300)
pithos/backends/base.py
pithos/backends/simple.py

index b11bb04..559b264 100644 (file)
@@ -217,7 +217,7 @@ class BaseBackend(object):
         """
         return []
     
-    def list_object_meta(self, user, account, container, until=None):
+    def list_object_meta(self, user, account, container, trash=False, until=None):
         """Return a list with all the container's object meta keys.
         
         Raises:
@@ -236,7 +236,6 @@ class BaseBackend(object):
             'modified_by': The user that committed the object (version requested)
             'version': The version identifier
             'version_timestamp': The version's modification timestamp
-            'trash': Set if the version has been trashed
         
         Raises:
             NotAllowedError: Operation not permitted
@@ -379,12 +378,21 @@ class BaseBackend(object):
         """
         return
     
-    def update_object_trash(self, user, account, container, name, trash=True):
-        """Trash/untrash an object.
+    def trash_object(self, user, account, container, name):
+        """Trash an object.
         
         Raises:
             NotAllowedError: Operation not permitted
-            NameError: Container/object does not exist or object not in trash
+            NameError: Container/object does not exist
+        """
+        return
+    
+    def untrash_object(self, user, account, container, name):
+        """Untrash an object.
+        
+        Raises:
+            NotAllowedError: Operation not permitted
+            NameError: Object not in trash
         """
         return
     
index 1ba4367..0aecb68 100644 (file)
@@ -73,9 +73,10 @@ class SimpleBackend(BaseBackend):
                     version_id integer primary key,
                     name text,
                     user text,
-                    tstamp datetime default current_timestamp,
+                    tstamp integer not null,
                     size integer default 0,
-                    hide integer default 0)'''
+                    trash integer default 0,
+                    until integer default null)'''
         self.con.execute(sql)
         sql = '''create table if not exists metadata (
                     version_id integer,
@@ -229,6 +230,8 @@ class SimpleBackend(BaseBackend):
         logger.debug("get_container_meta: %s %s %s", account, container, until)
         if user != account:
             raise NotAllowedError
+        
+        # TODO: Container meta for trash.
         path, version_id, mtime = self._get_containerinfo(account, container, until)
         count, bytes, tstamp = self._get_pathstats(path, until)
         if mtime > tstamp:
@@ -314,7 +317,7 @@ class SimpleBackend(BaseBackend):
         path, version_id, mtime = self._get_containerinfo(account, container)
         
         if until is not None:
-            sql = '''select version_id from versions where name like ? and tstamp <= datetime(%s, 'unixepoch')'''
+            sql = '''select version_id from versions where name like ? and tstamp <= ?'''
             c = self.con.execute(sql, (path + '/%', until))
             versions = [x[0] for x in c.fetchall()]
             for v in versions:
@@ -327,8 +330,8 @@ class SimpleBackend(BaseBackend):
         
         if self._get_pathcount(path) > 0:
             raise IndexError('Container is not empty')
-        sql = 'delete from versions where name = ?'
-        self.con.execute(sql, (path,))
+        sql = 'delete from versions where name like ?' # May contain hidden trash items.
+        self.con.execute(sql, (path + '/%',))
         sql = 'delete from policy where name = ?'
         self.con.execute(sql, (path,))
         self._copy_version(user, account, account, True, True) # New account version (for timestamp update).
@@ -342,7 +345,7 @@ class SimpleBackend(BaseBackend):
         path, version_id, mtime = self._get_containerinfo(account, container, until)
         return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, trash, until)
     
-    def list_object_meta(self, user, account, container, until=None):
+    def list_object_meta(self, user, account, container, trash=False, until=None):
         """Return a list with all the container's object meta keys."""
         
         logger.debug("list_object_meta: %s %s %s", account, container, until)
@@ -351,7 +354,7 @@ class SimpleBackend(BaseBackend):
         path, version_id, mtime = self._get_containerinfo(account, container, until)
         sql = '''select distinct m.key from (%s) o, metadata m
                     where m.version_id = o.version_id and o.name like ?'''
-        sql = sql % self._sql_until(until)
+        sql = sql % self._sql_until(until, trash)
         c = self.con.execute(sql, (path + '/%',))
         return [x[0] for x in c.fetchall()]
     
@@ -509,7 +512,7 @@ class SimpleBackend(BaseBackend):
             c = self.con.execute(sql, (path,))
         else:
             path = os.path.join(account, container, name)
-            sql = '''select version_id from versions where name = ? and tstamp <= datetime(%s, 'unixepoch')'''
+            sql = '''select version_id from versions where name = ? and tstamp <= ?'''
             c = self.con.execute(sql, (path, until))
         versions = [x[0] for x in c.fetchall()]
         for v in versions:
@@ -518,44 +521,50 @@ class SimpleBackend(BaseBackend):
             sql = 'delete from versions where version_id = ?'
             self.con.execute(sql, (v,))
         
-        # If no more versions exist, delete permissions/public.
-        sql = 'select version_id from versions where name = ?'
+        # If no more normal versions exist, delete permissions/public.
+        sql = 'select version_id from versions where name = ? and trash = 0'
         row = self.con.execute(sql, (path,)).fetchone()
         if row is None:
             self._del_sharing(path)
         self.con.commit()
     
-    def update_object_trash(self, user, account, container, name, trash=True):
-        """Trash/untrash an object."""
+    def trash_object(self, user, account, container, name):
+        """Trash an object."""
         
         logger.debug("trash_object: %s %s %s", account, container, name)
         if user != account:
             raise NotAllowedError
-        if trash:
-            path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name)
-            self._del_sharing(path)
-        else:
-            path = os.path.join(account, container, name)
-            sql = '''select version_id, hide from versions where name = ?
-                        order by version_id desc limit 1'''
-            c = self.con.execute(sql, (path,))
-            row = c.fetchone()
-            if not row or not int(row[1]):
-                raise NameError('Object not in trash')
-            version_id = row[0]
-        hide = 1 if trash else 0
-        sql = 'update versions set hide = ? where version_id = ?'
-        self.con.execute(sql, (hide, version_id,))
-        seld.con.commit()
+        path, version_id, muser, mtime, size = self._get_objectinfo(account, container, name)
+        src_version_id, dest_version_id = self._copy_version(user, path, path, True, True, version_id)
+        sql = 'update versions set trash = 1 where version_id = ?'
+        self.con.execute(sql, (dest_version_id,))
+        self._del_sharing(path)
+        self.con.commit()
+    
+    def untrash_object(self, user, account, container, name, version):
+        """Untrash an object."""
+        
+        logger.debug("untrash_object: %s %s %s %s", account, container, name, version)
+        if user != account:
+            raise NotAllowedError
+        
+        path = os.path.join(account, container, name)
+        sql = '''select version_id from versions where name = ? and version_id = ? and trash = 1'''
+        c = self.con.execute(sql, (path, version))
+        row = c.fetchone()
+        if not row or not int(row[1]):
+            raise NameError('Object not in trash')
+        sql = 'update versions set until = ? where version_id = ?'
+        self.con.execute(sql, (int(time.time()), version))
+        self.con.commit()
     
     def list_versions(self, user, account, container, name):
         """Return a list of all (version, version_timestamp) tuples for an object."""
         
         logger.debug("list_versions: %s %s %s", account, container, name)
         self._can_read(user, account, container, name)
-        # This will even show deleted versions.
         path = os.path.join(account, container, name)
-        sql = '''select distinct version_id, strftime('%s', tstamp) from versions where name = ?'''
+        sql = '''select distinct version_id, tstamp from versions where name = ? and trash = 0'''
         c = self.con.execute(sql, (path,))
         return [(int(x[0]), int(x[1])) for x in c.fetchall()]
     
@@ -595,30 +604,39 @@ class SimpleBackend(BaseBackend):
         dest_data = src_data[:offset] + data + src_data[offset + len(data):]
         return self.put_block(dest_data)
     
-    def _sql_until(self, until=None):
+    def _sql_until(self, until=None, trash=False):
         """Return the sql to get the latest versions until the timestamp given."""
+        
         if until is None:
             until = int(time.time())
-        sql = '''select version_id, name, strftime('%s', tstamp) as tstamp, size from versions v
-                    where version_id = (select max(version_id) from versions
-                                        where v.name = name and tstamp <= datetime(%s, 'unixepoch'))
-                    and hide = 0'''
-        return sql % ('%s', until)
+        if not trash:
+            sql = '''select version_id, name, tstamp, size from versions v
+                        where version_id = (select max(version_id) from versions
+                                            where v.name = name and tstamp <= ?)
+                        and trash = 0'''
+            return sql % (until,)
+        else:
+            sql = '''select version_id, name, tstamp, size from versions v
+                        where trash = 1 and tstamp <= ? and (until is null or until > ?)'''
+            return sql % (until, until)
     
     def _get_pathstats(self, path, until=None):
         """Return count, sum of size and latest timestamp of everything under path (latest versions/no trash)."""
         
-        sql = 'select count(version_id), total(size), max(tstamp) from (%s) where name like ?'
+        sql = 'select count(version_id), total(size) from (%s) where name like ?'
         sql = sql % self._sql_until(until)
         c = self.con.execute(sql, (path + '/%',))
+        total_count, total_size = c.fetchone()
+        sql = 'select max(tstamp) from versions where name like ? and tstamp <= ?' # Include trash actions.
+        c = self.con.execute(sql, (path + '/%', until))
         row = c.fetchone()
-        tstamp = row[2] if row[2] is not None else 0
-        return int(row[0]), int(row[1]), int(tstamp)
+        tstamp = row[0] if row[0] is not None else 0
+        return int(total_count), int(total_size), int(tstamp)
     
     def _get_pathcount(self, path):
         """Return count of everything under path (including versions/trash)."""
         
-        sql = 'select count(version_id) from versions where name like ?'
+        sql = 'select count(version_id) from versions where name like ? and until is null'
         c = self.con.execute(sql, (path + '/%',))
         row = c.fetchone()
         return int(row[0])
@@ -642,8 +660,9 @@ class SimpleBackend(BaseBackend):
         return str(row[0]), str(row[1]), int(row[2]), int(row[3])
     
     def _put_version(self, path, user, size=0):
-        sql = 'insert into versions (name, user, size) values (?, ?, ?)'
-        id = self.con.execute(sql, (path, user, size)).lastrowid
+        tstamp = int(time.time())
+        sql = 'insert into versions (name, user, tstamp, size) values (?, ?, ?, ?)'
+        id = self.con.execute(sql, (path, user, tstamp, size)).lastrowid
         self.con.commit()
         return str(id)
     
@@ -857,21 +876,14 @@ class SimpleBackend(BaseBackend):
     def _list_objects(self, path, prefix='', delimiter=None, marker=None, limit=10000, virtual=True, keys=[], trash=False, until=None):
         cont_prefix = path + '/'
         
-        if trash:
-            table = '''select version_id, name, strftime('%s', tstamp) as tstamp, size from versions v
-                        where hide = 1''' % ('%s',)
-            if until is not None:
-                table += ''' and tstamp <= datetime(%s, 'unixepoch')''' % (until,)
-        else:
-            table = self._sql_until(until)
         if keys and len(keys) > 0:
             sql = '''select distinct o.name, o.version_id from (%s) o, metadata m where o.name like ? and
                         m.version_id = o.version_id and m.key in (%s) order by o.name'''
-            sql = sql % (table, ', '.join('?' * len(keys)))
+            sql = sql % (self._sql_until(until, trash), ', '.join('?' * len(keys)))
             param = (cont_prefix + prefix + '%',) + tuple(keys)
         else:
             sql = 'select name, version_id from (%s) where name like ? order by name'
-            sql = sql % (table,)
+            sql = sql % (self._sql_until(until, trash),)
             param = (cont_prefix + prefix + '%',)
         c = self.con.execute(sql, param)
         objects = [(x[0][len(cont_prefix):], x[1]) for x in c.fetchall()]