diff -r d9cf1e272e90 -r 92c20f8b297f 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