implement simple stat caching using lazy_load for filesystem
authorTero Marttila <terom@fixme.fi>
Thu, 11 Jun 2009 22:50:44 +0300
changeset 97 92c20f8b297f
parent 96 d9cf1e272e90
child 98 d7d98c4479ab
implement simple stat caching using lazy_load for filesystem
degal/filesystem.py
--- a/degal/filesystem.py	Thu Jun 11 22:50:21 2009 +0300
+++ b/degal/filesystem.py	Thu Jun 11 22:50:44 2009 +0300
@@ -2,12 +2,43 @@
     Filesystem path handling
 """
 
-import os, os.path, errno
+import os, os.path, errno, stat
 import codecs, shutil
 import itertools
 
 from utils import lazy_load
 
+class NodeError (Exception) :
+    """
+        General exception class for errors associated with some specific node
+    """
+
+    def __init__ (self, node, message) :
+        super(NodeError, self).__init__(message)
+        
+        self.node = node
+
+    def __str__ (self) :
+        return "%s: %s" % (self.node, self.message)
+        
+    def __unicode__ (self) :
+        return u"%s: %s" % (self.node, self.message)
+
+    def __repr__ (self) :
+        return "NodeError(%r, %r)" % (self.node, self.message)
+
+class NodeErrno (NodeError) :
+    """
+        OSError/errno errors for nodes
+    """
+
+    def __init__ (self, node, errno) :
+        """
+            Converts the given errno into an error message and uses that as the exception message
+        """
+
+        super(NodeErrno, self).__init__(node, os.strerror(errno))
+
 class Node (object) :
     """
         A filesystem object is basically just complicated representation of a path.
@@ -176,6 +207,52 @@
             yield segment
 
         yield self.name if unicode else self.fsname
+    
+    @lazy_load
+    def _stat (self) :
+        """
+            Cached OS-level stats.
+
+            Returns None on ENOENT (node doesn't exist).
+        """
+        
+        try :
+            # syscall
+            return os.stat(self.path)
+
+        except OSError, e :
+            # trap ENOENT for soft
+            if soft and e.errno == errno.ENOENT :
+                return None
+
+            else :
+                raise
+
+    def stat (self, soft=False) :
+        """
+            Returns the os.stat struct for this node.
+            
+            If `soft` is given, returns None if this node doesn't exist.
+
+            These stats are not cached.
+
+            >>> Root('/').stat() is not None
+            True
+            >>> Root('/nonexistant').stat(soft=True) is None
+            True
+        """
+        
+        if self._stat :
+            # got it
+            return self._stat
+
+        elif soft :
+            # doesn't exist
+            return None
+
+        else :
+            # not found
+            raise NodeErrno(self, errno.ENOENT)
 
     def exists (self) :
         """
@@ -187,31 +264,35 @@
             False
         """
 
-        return os.path.exists(self.path)
+        return self._stat is not None
 
     def is_dir (self) :
         """
-            Tests if this node represents a directory on the physical filesystem
+            Tests if this node represents a directory on the physical filesystem.
+
+            Returns False for non-existant files.
 
             >>> Node(Root('/'), '.').is_dir()
             True
             >>> Root('/').subnode('dev').subnode('null').is_dir()
             False
         """
-
-        return os.path.isdir(self.path)
+        
+        return stat.S_ISDIR(self._stat.st_mode) if self._stat else False
 
     def is_file (self) :
         """
             Tests if this node represents a normal file on the physical filesystem
 
+            Returns False for non-existant files.
+
             >>> Node(Root('/'), '.').is_file()
             False
             >>> Root('/').subnode('dev').subnode('null').is_file()
             False
         """
 
-        return os.path.isfile(self.path)
+        return stat.S_ISREG(self._stat.st_mode) if self._stat else False
 
     def test (self) :
         """
@@ -254,31 +335,6 @@
         """
         
         return node.path_to(self)
-
-    def stat (self, soft=False) :
-        """
-            Returns the os.stat struct for this node.
-            
-            If `soft` is given, returns None if this node doesn't exist.
-
-            These stats are not cached.
-
-            >>> Root('/').stat() is not None
-            True
-            >>> Root('/nonexistant').stat(soft=True) is None
-            True
-        """
-
-        try :
-            return os.stat(self.path)
-
-        except OSError, e :
-            # trap ENOENT for soft
-            if soft and e.errno == errno.ENOENT :
-                return None
-
-            else :
-                raise
     
     def __str__ (self) :
         return self.path
@@ -538,11 +594,8 @@
             Returns True if both files exist, and this file is newer than the given file.
         """
 
-        self_stat = self.stat(soft=True)
-        file_stat = file.stat(soft=True)
-
-        if self_stat and file_stat :
-            return self_stat.st_mtime > file_stat.st_mtime
+        if self._stat and file._stat :
+            return self._stat.st_mtime > file._stat.st_mtime
 
         else :
             return None