Code

fixed export/import of retired nodes (sf bug 685273)
[roundup.git] / roundup / backends / back_anydbm.py
index 2e1bd9b8fa5c399f5d1d8b215e14d89cd0dd59a7..dfacf2f48465a0aa76ab3800d683ff81e8194cb5 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_anydbm.py,v 1.97 2003-01-15 22:17:19 kedder Exp $
+#$Id: back_anydbm.py,v 1.106 2003-02-26 23:42:50 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in a database
 chosen by anydbm. It is guaranteed to always be available in python
@@ -26,11 +26,11 @@ serious bugs, and is not available)
 import whichdb, anydbm, os, marshal, re, weakref, string, copy
 from roundup import hyperdb, date, password, roundupdb, security
 from blobfiles import FileStorage
-from sessions import Sessions
+from sessions import Sessions, OneTimeKeys
 from roundup.indexer import Indexer
 from roundup.backends import locking
 from roundup.hyperdb import String, Password, Date, Interval, Link, \
-    Multilink, DatabaseError, Boolean, Number
+    Multilink, DatabaseError, Boolean, Number, Node
 
 #
 # Now the database
@@ -68,6 +68,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
         self.transactions = []
         self.indexer = Indexer(self.dir)
         self.sessions = Sessions(self.config)
+        self.otks = OneTimeKeys(self.config)
         self.security = security.Security(self)
         # ensure files are group readable and writable
         os.umask(0002)
@@ -299,6 +300,13 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
         if db is None:
             db = self.getclassdb(classname)
         if not db.has_key(nodeid):
+            # try the cache - might be a brand-new node
+            cache_dict = self.cache.setdefault(classname, {})
+            if cache_dict.has_key(nodeid):
+                if __debug__:
+                    print >>hyperdb.TRACE, 'get %s %s cached'%(classname,
+                        nodeid)
+                return cache_dict[nodeid]
             raise IndexError, "no such %s %s"%(classname, nodeid)
 
         # check the uncommitted, destroyed nodes
@@ -555,7 +563,6 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
         '''
         if __debug__:
             print >>hyperdb.DEBUG, 'commit', (self,)
-        # TODO: lock the DB
 
         # keep a handle to all the database files opened
         self.databases = {}
@@ -569,7 +576,6 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
         for db in self.databases.values():
             db.close()
         del self.databases
-        # TODO: unlock the DB
 
         # reindex the nodes that request it
         for classname, nodeid in filter(None, reindex.keys()):
@@ -760,6 +766,14 @@ class Class(hyperdb.Class):
         These operations trigger detectors and can be vetoed.  Attempts
         to modify the "creation" or "activity" properties cause a KeyError.
         '''
+        self.fireAuditors('create', None, propvalues)
+        newid = self.create_inner(**propvalues)
+        self.fireReactors('create', newid, None)
+        return newid
+
+    def create_inner(self, **propvalues):
+        ''' Called by create, in-between the audit and react calls.
+        '''
         if propvalues.has_key('id'):
             raise KeyError, '"id" is reserved'
 
@@ -768,9 +782,6 @@ class Class(hyperdb.Class):
 
         if propvalues.has_key('creation') or propvalues.has_key('activity'):
             raise KeyError, '"creation" and "activity" are reserved'
-
-        self.fireAuditors('create', None, propvalues)
-
         # new node's id
         newid = self.db.newid(self.classname)
 
@@ -890,8 +901,6 @@ class Class(hyperdb.Class):
         if self.do_journal:
             self.db.addjournal(self.classname, newid, 'create', {})
 
-        self.fireReactors('create', newid, None)
-
         return newid
 
     def export_list(self, propnames, nodeid):
@@ -913,6 +922,10 @@ class Class(hyperdb.Class):
             elif isinstance(proptype, hyperdb.Password):
                 value = str(value)
             l.append(repr(value))
+
+        # append retired flag
+        l.append(self.is_retired(nodeid))
+
         return l
 
     def import_list(self, propnames, proplist):
@@ -954,6 +967,10 @@ class Class(hyperdb.Class):
                 value = pwd
             d[propname] = value
 
+        # check retired flag
+        if int(proplist[-1]):
+            d[self.db.RETIRED_FLAG] = 1
+
         # add the node and journal
         self.db.addnode(self.classname, newid, d)
 
@@ -1316,10 +1333,10 @@ class Class(hyperdb.Class):
 
         self.fireReactors('retire', nodeid, None)
 
-    def is_retired(self, nodeid):
+    def is_retired(self, nodeid, cldb=None):
         '''Return true if the node is retired.
         '''
-        node = self.db.getnode(cn, nodeid, cldb)
+        node = self.db.getnode(self.classname, nodeid, cldb)
         if node.has_key(self.db.RETIRED_FLAG):
             return 1
         return 0
@@ -1349,7 +1366,7 @@ class Class(hyperdb.Class):
 
         The returned list contains tuples of the form
 
-            (date, tag, action, params)
+            (nodeid, date, tag, action, params)
 
         'date' is a Timestamp object specifying the time of the change and
         'tag' is the journaltag specified when the database was opened.
@@ -1762,12 +1779,15 @@ class Class(hyperdb.Class):
                 # Multilink properties are sorted according to how many
                 # links are present.
                 elif isinstance(propclass, Multilink):
+                    r = cmp(len(av), len(bv))
+                    if r == 0:
+                        # Compare contents of multilink property if lenghts is
+                        # equal
+                        r = cmp ('.'.join(av), '.'.join(bv))
                     if dir == '+':
-                        r = cmp(len(av), len(bv))
-                        if r != 0: return r
+                        return r
                     elif dir == '-':
-                        r = cmp(len(bv), len(av))
-                        if r != 0: return r
+                        return -r
                 elif isinstance(propclass, Number) or isinstance(propclass, Boolean):
                     if dir == '+':
                         r = cmp(av, bv)
@@ -1868,7 +1888,7 @@ class Class(hyperdb.Class):
         for react in self.reactors[action]:
             react(self.db, self, nodeid, oldvalues)
 
-class FileClass(Class):
+class FileClass(Class, hyperdb.FileClass):
     '''This class defines a large chunk of data. To support this, it has a
        mandatory String property "content" which is typically saved off
        externally to the hyperdb.
@@ -1880,11 +1900,23 @@ class FileClass(Class):
     default_mime_type = 'text/plain'
 
     def create(self, **propvalues):
-        ''' snaffle the file propvalue and store in a file
+        ''' Snarf the "content" propvalue and store in a file
         '''
+        # we need to fire the auditors now, or the content property won't
+        # be in propvalues for the auditors to play with
+        self.fireAuditors('create', None, propvalues)
+
+        # now remove the content property so it's not stored in the db
         content = propvalues['content']
         del propvalues['content']
-        newid = Class.create(self, **propvalues)
+
+        # do the database create
+        newid = Class.create_inner(self, **propvalues)
+
+        # fire reactors
+        self.fireReactors('create', newid, None)
+
+        # store off the content as a file
         self.db.storefile(self.classname, newid, None, content)
         return newid