[Zope-CVS] CVS: Products/AdaptableStorage/gateway_fs - ShortLivedCache.py:1.1 FSConnection.py:1.12

Shane Hathaway shane@zope.com
Wed, 5 Feb 2003 10:08:04 -0500


Update of /cvs-repository/Products/AdaptableStorage/gateway_fs
In directory cvs.zope.org:/tmp/cvs-serv13924/gateway_fs

Modified Files:
	FSConnection.py 
Added Files:
	ShortLivedCache.py 
Log Message:
Added short-lived caching (1 second) to FSConnection to offset the cost of
property files and automatic filename extensions.


=== Added File Products/AdaptableStorage/gateway_fs/ShortLivedCache.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Simple short-lived object cache.

$Id: ShortLivedCache.py,v 1.1 2003/02/05 15:08:01 shane Exp $
"""

from time import time


class ShortLivedCache:

    def __init__(self, lifetime=1):
        # The default lifetime is 1 second.
        self.lifetime = lifetime
        self.data = {}
        self.expiration = time() + lifetime

    def get(self, key, default=None):
        now = time()
        if now >= self.expiration:
            self.data.clear()
            return default
        res = self.data.get(key, default)
        return res

    def set(self, key, value):
        now = time()
        if now >= self.expiration:
            self.data.clear()
            self.expiration = now + self.lifetime
        self.data[key] = value

    def invalidate(self, key):
        try:
            del self.data[key]
        except KeyError:
            pass

    def clear(self):
        self.data.clear()



=== Products/AdaptableStorage/gateway_fs/FSConnection.py 1.11 => 1.12 ===
--- Products/AdaptableStorage/gateway_fs/FSConnection.py:1.11	Tue Feb  4 23:59:16 2003
+++ Products/AdaptableStorage/gateway_fs/FSConnection.py	Wed Feb  5 10:08:01 2003
@@ -24,6 +24,7 @@
 from interfaces.public import IFSConnection
 from exceptions import FSWriteError
 from mapper_public import ITPCConnection, NoStateFoundError
+from ShortLivedCache import ShortLivedCache
 
 
 # Try to decipher this regular expression ;-)
@@ -59,6 +60,8 @@
         # _pending holds the data to be written.
         # _pending: { subpath string -> { section_name -> data } }
         self._pending = {}
+        self._props_cache = ShortLivedCache()
+        self._dir_cache = ShortLivedCache()
 
 
     def computeDirectoryContents(self, path, ignore_error=0):
@@ -66,6 +69,10 @@
 
         Returns (filenames, object_names, translations).
         """
+        res = self._dir_cache.get(path)
+        if res is not None:
+            return res
+
         filenames = []
         obj_names = []
         trans = {}     # { base name -> filename with extension or None }
@@ -73,7 +80,7 @@
             fns = os.listdir(path)
         except OSError:
             if ignore_error:
-                return filenames, obj_names, trans
+                return (filenames, obj_names, trans)
         hidden_filename_prefix = self.hidden_filename_prefix
         for fn in fns:
             if fn.startswith(hidden_filename_prefix):
@@ -100,7 +107,10 @@
                 if '.' in obj_name:
                     base, ext = obj_name.split('.', 1)
                     trans[base] = None
-        return filenames, obj_names, trans
+
+        res = (filenames, obj_names, trans)
+        self._dir_cache.set(path, res)
+        return res
         
 
     def listDirectoryAsMapping(self, path, ignore_error=0):
@@ -136,7 +146,6 @@
             dir_path, obj_name = os.path.split(path)
             if '.' not in obj_name:
                 # This object might have an automatic filename extension.
-                # XXX This is expensive.
                 filenames, obj_names, trans = self.computeDirectoryContents(
                     dir_path, 1)
                 fn = trans.get(obj_name)
@@ -222,13 +231,19 @@
 
     def getPropertiesFromFile(self, path):
         """Reads a properties file next to path."""
+        res = self._props_cache.get(path)
+        if res is not None:
+            return res
+
         props_fn = self.getPropertiesPath(path)
 
         try:
             f = open(props_fn, 'rb')
         except IOError:
             # The file is presumably nonexistent
-            return {}
+            res = {}
+            self._props_cache.set(path, res)
+            return res
         try:
             data = f.read()
         finally:
@@ -252,6 +267,7 @@
                 prev_section_name = match.group(1)
                 pos = match.end()
 
+        self._props_cache.set(path, res)
         return res
 
 
@@ -291,6 +307,7 @@
                         writeSection(props_f, OBJECT_NAMES_SECTION,
                                      '\n'.join(data))
                         self.disableConflictingExtensions(subpath, data)
+                        self._dir_cache.invalidate(path)
                     else:
                         # Change the file contents.
                         f = open(path, 'wb')
@@ -305,6 +322,9 @@
                     writeSection(props_f, name, data)
         finally:
             props_f.close()
+            self._props_cache.invalidate(path)
+            # The file might be new, so invalidate the directory.
+            self._dir_cache.invalidate(os.path.dirname(path))
 
 
     def removeUnlinkedItems(self, path, names):
@@ -344,8 +364,7 @@
             subpath = subpath[:-1]
         for obj_name in obj_names:
             if reserved.has_key(obj_name):
-                # obj_name has no extension and must not have an
-                # automatic extension.
+                # Prevent obj_name from using an automatic extension.
                 child_subpath = '%s/%s' % (subpath, obj_name)
                 self.queue(child_subpath, SUGGESTED_EXTENSION_SECTION,
                            '', force=1)
@@ -429,7 +448,8 @@
             os.makedirs(self.basepath)
 
     def begin(self):
-        pass
+        self._props_cache.clear()
+        self._dir_cache.clear()
 
     def vote(self):
         """Do some early verification
@@ -444,6 +464,8 @@
     def reset(self):
         self._final = 0
         self._pending.clear()
+        self._props_cache.clear()
+        self._dir_cache.clear()
 
     def abort(self):
         self.reset()