do some filesystem.Path stuff, and read_only mode
authorTero Marttila <terom@fixme.fi>
Fri, 05 Jun 2009 21:46:43 +0300
changeset 60 406da27a4be2
parent 59 fbbe956229cc
child 61 fad360dd01da
do some filesystem.Path stuff, and read_only mode
degal/config.py
degal/filesystem.py
--- a/degal/config.py	Fri Jun 05 19:30:53 2009 +0300
+++ b/degal/config.py	Fri Jun 05 21:46:43 2009 +0300
@@ -2,13 +2,14 @@
     Configuration
 """
 
-import render
-
 class Configuration (object) :
     """
         Various configuration settings
     """
 
+    # the path to the gallery
+    gallery_path        = "."
+
     # the name of the gallery
     gallery_title       = "Image Gallery"
 
@@ -44,6 +45,13 @@
         ("FocalLength",             "Focal length"              )
     ]
     
+    ## runtime settings
+
+    # do not modify anything
+    read_only           = False
+
+
+    ### functions
     def is_image (self, file) :
         """
             Tests if the given File is an image, based on its file extension
--- a/degal/filesystem.py	Fri Jun 05 19:30:53 2009 +0300
+++ b/degal/filesystem.py	Fri Jun 05 21:46:43 2009 +0300
@@ -3,7 +3,10 @@
 """
 
 import os, os.path, errno
-import codecs
+import codecs, shutil
+import itertools
+
+from utils import lazy_load
 
 class Node (object) :
     """
@@ -94,24 +97,38 @@
         
         return Node(self, name=name)
  
-    @property
+    def nodepath (self) :
+        """
+            Returns the path of nodes from this node to the root node, inclusive
+        """
+
+        return self.parent.nodepath() + [self]
+
+    @lazy_load
     def path (self) :
         """
-            Build and return the real filesystem path for this node
+            Return the machine-readable root-path for this node
         """
         
         # build using parent path and our fsname
         return os.path.join(self.parent.path, self.fsname)
     
-    @property
+    @lazy_load
     def unicodepath (self) :
         """
-            Build and return the fake unicode filesystem path for this node
+            Return the human-readable root-path for this node
         """
         
         # build using parent unicodepath and our name
         return os.path.join(self.parent.path, self.name)
    
+    def path_segments (self, unicode=True) :
+        """
+            Return a series of single-level names describing the path from the root to this node
+        """
+
+        return self.parent.path_segments(unicode=unicode) + [self.name if unicode else self.fsname]
+
     def exists (self) :
         """
             Tests if this node exists on the physical filesystem
@@ -132,13 +149,43 @@
         """
 
         return os.path.isfile(self.path)
+
+    def test (self) :
+        """
+            Tests that this node exists. Raises an error it not, otherwise, returns the node itself
+        """
+
+        if not self.exists() :
+            raise Exception("Filesystem node does not exist: %s" % self)
+
+        return self
     
     def path_from (self, node) :
         """
-            Returns a relative path to this node from the given node
+            Returns a relative path to this node from the given node.
+
+            This is the same as path_to, but just reversed.
+        """
+        
+        return node.path_to(self)
+
+    def path_to (self, node) :
+        """
+            Returns a relative path from this node to the given node
         """
 
-        XXX
+        # get real paths for both
+        from_path = self.nodepath()
+        to_path = node.nodepath()
+        pivot = None
+
+        # reduce common prefix
+        while from_path[0] == to_path[0] :
+            from_path.pop(0)
+            pivot = to_path.pop(0)
+
+        # build path
+        return Path(*itertools.chain(reversed(from_path), [pivot], to_path))
 
     def stat (self, soft=False) :
         """
@@ -157,20 +204,10 @@
 
             else :
                 raise
-
-    def __str__ (self) :
-        """
-            Returns the raw filesystem path
-        """
-
-        return self.path
-
-    def __unicode__ (self) :
-        """
-            Returns the human-readable path
-        """
-
-        return self.unicodepath
+    
+    # alias str/unicode
+    str = path
+    unicode = unicodepath
     
     def __repr__ (self) :
         """
@@ -179,12 +216,80 @@
 
         return "Node(%r, %r)" % (self.parent.path, self.fsname)
     
-    def __cmp__ (self) :
+    def __cmp__ (self, other) :
         """
             Comparisons between Nodes
         """
 
-        return cmp((self.parent, self.name), (other.parent, other.name))
+        return cmp((self.parent, self.name), (other.parent if self.parent else None, other.name))
+
+class Path (object) :
+    """
+        A Path is a sequence of Nodes that form a path through the node tree.
+
+        Each node must either be the parent or the child of the following node.
+    """
+
+    def __init__ (self, *nodes) :
+        """
+            Initialize with the given node path
+        """
+
+        self.nodes = nodes
+    
+    def subpath (self, *nodes) :
+        """
+            Returns a new path with the given node(s) appended
+        """
+
+        return Path(*itertools.chain(self.nodes, nodes))
+    
+    def path_segments (self, unicode=True) :
+        """
+            Yields a series of physical path segments for this path
+        """
+
+        prev = None
+
+        for node in self.nodes :
+            if not prev :
+                # ignore
+                pass
+
+            elif prev.parent and prev.parent == node :
+                # up a level
+                yield u'..' if unicode else '..'
+            
+            else :
+                # down a level
+                yield node.name if unicode else node.fsname
+            
+            # chained together
+            prev = node
+
+    def __iter__ (self) :
+        """
+            Iterate over the nodes
+        """
+
+        return iter(self.nodes)
+    
+    def __unicode__ (self) :
+        """
+            Returns the unicode human-readable path
+        """
+
+        return os.path.join(*self.path_segments(unicode=True))
+    
+    def __str__ (self) :
+        """
+            Returns the binary machine-readable path
+        """
+
+        return os.path.join(*self.path_segments(unicode=False))
+    
+    def __repr__ (self) :
+        return "Path(%s)" % ', '.join(repr(segment) for segment in self.path_segments(unicode=False))
 
 class File (Node) :
     """
@@ -218,17 +323,47 @@
         
         return (self.fileext.lower() in ext_list)
     
+    def test (self) :
+        """
+            Tests that this file exists as a file. Raises an error it not, otherwise, returns itself
+        """
+
+        if not self.is_file() :
+            raise Exception("File does not exist: %s" % self)
+
+        return self
+
     def open (self, mode='r', encoding=None, errors=None, bufsize=None) :
         """
-            Wrapper for open/codecs.open
+            Wrapper for open/codecs.open.
+
+            Raises an error if read_only mode is set and mode contains any of 'wa+'
         """
 
+        if self.config.read_only and any((c in mode) for c in 'wa+') :
+            raise Exception("Unable to open file for %s due to read_only mode: %s" % (mode, self))
+
         if encoding :
             return codecs.open(self.path, mode, encoding, errors, bufsize)
 
         else :
             return open(self.path, mode, bufsize)
 
+    def copy_from (self, file) :
+        """
+            Replace this file with a copy of the given file with default permissions.
+
+            Raises an error if read_only mode is set.
+
+            XXX: accept mode
+        """
+
+        if self.config.read_only :
+            raise Exception("Not copying file as read_only mode is set: %s -> %s" % (file, self))
+        
+        # perform the copy
+        shutil.copyfile(file.path, self.path)
+
 class Directory (Node) :
     """
         A directory is a node that contains other nodes.
@@ -241,14 +376,18 @@
         """
             Returns a Directory object representing the name underneath this dir.
 
-            If the create option is given, the directory will be created if it does not exist.
+            If the create option is given, the directory will be created if it does not exist. Note that this will
+            raise an error if read_only mode is set
         """
 
-        dir = Directory(self, name=name)
+        subdir = Directory(self, name=name)
         
         # try and mkdir?
-        if create and not dir.is_dir() :
-            dir.mkdir()
+        if create and not subdir.is_dir() :
+            # create it!
+            subdir.mkdir()
+
+        return subdir
     
     def subfile (self, name) :
         """
@@ -257,13 +396,29 @@
 
         return Directory(self, name=name)
 
+    def test (self) :
+        """
+            Tests that this dir exists as a dir. Raises an error it not, otherwise, returns itself
+        """
+
+        if not self.is_dir() :
+            raise Exception("Directory does not exist: %s" % self)
+
+        return self
+
     def mkdir (self) :
         """
-            Create this directory with default permissions
+            Create this directory with default permissions.
+
+            This will fail if read_only mode is set
             
             XXX: mode argument
         """
         
+        if self.config.read_only :
+            # forbidden
+            raise Exception("Unable to create dir due to read_only mode: %s" % self)
+        
         # do it
         os.mkdir(self.path)
 
@@ -348,7 +503,8 @@
         A special Directory that overrides the Node methods to anchor the recursion/etc at some 'real' filesystem path.
     """
 
-    def __init__ (self, fspath, config) :
+    # XXX: config needs a default
+    def __init__ (self, fspath, config=None) :
         """
             Construct the directory tree root at the given 'real' path, which must be a raw str
         """
@@ -356,6 +512,13 @@
         # abuse Node's concept of a "name" a bit
         super(Root, self).__init__(None, fspath, config=config)
 
+    def nodepath (self) :
+        """
+            Just return ourself
+        """
+        
+        return [self]
+
     @property
     def path (self) :
         """
@@ -380,6 +543,13 @@
 
         return ''
 
+    def path_segments (self, unicode=True) :
+        """
+            No path segments
+        """
+
+        return []
+
     def __repr__ (self) :
         """
             Override Node.__repr__ to not use self.parent.path