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