# HG changeset patch # User Tero Marttila # Date 1244103772 -10800 # Node ID 0f39cb5e4b11b28f9611c006c68a0b373e1cbf75 # Parent 724121253d348c74b3cecce3f53d3609eb0573a3 start writing new structure, with config, render, filesystem modules diff -r 724121253d34 -r 0f39cb5e4b11 degal/config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/degal/config.py Thu Jun 04 11:22:52 2009 +0300 @@ -0,0 +1,23 @@ +""" + Configuration +""" + +class Configuration (object) : + """ + Various configuration settings + """ + + # the name of the gallery + gallery_title = "Image Gallery" + + # recognized image extensions + image_exts = ('jpg', 'jpeg', 'png', 'gif', 'bmp') + + # subdirectory used for generated thumbnails/previews + thumb_dir = 'thumbs' + preview_dir = 'previews' + + # size of generated thumbnails/previews + thumb_size = (160, 120) + preview_size = (640, 480) + diff -r 724121253d34 -r 0f39cb5e4b11 degal/filesystem.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/degal/filesystem.py Thu Jun 04 11:22:52 2009 +0300 @@ -0,0 +1,322 @@ +""" + Filesystem path handling +""" + +import os, os.path + +class Node (object) : + """ + A filesystem object is basically just complicated representation of a path. + + On the plus side, it has a parent node and can handle unicode/binary paths. + """ + + # the binary name + fsname = None + + # the unicode name + name = None + + def decode_fsname (self, fsname) : + """ + Decode the given raw byte string representing a filesystem name into an user-readable unicode name. + + XXX: currently just hardcoded as utf-8 + """ + + return fsname.decode('utf-8', 'replace') + + def encode_name (self, name) : + """ + Returns a suitable fsname for the given unicode name or strict ASCII str + + XXX: currently just hardcoded as utf-8 + """ + + # this should fail for non-ASCII str + return name.encode('utf-8') + + @classmethod + def from_node (cls, node) : + """ + Construct from a Node object + """ + + return cls(node.parent, node.fsname, node.name, node.config) + + def __init__ (self, parent, fsname=None, name=None, config=None) : + """ + Initialize the node with a parent and both name/fsname. + + If not given, fsname is encoded from name, or name decoded from fsname, using encode/decode_name. + + If parent is given, but both fsname and name are None, then this node will be equal to the parent. + """ + + assert not fsname or isinstance(fsname, str) + + if parent and not fsname and not name : + # no name given -> we're the same as parent + self.parent, self.config, self.fsname, self.name = parent.parent, parent.config, parent.fsname, parent.name + + else : + # store + self.parent = parent + + # config, either as given, or copy from parent + if config : + self.config = config + + else : + self.config = parent.config + + # fsname + if fsname : + self.fsname = fsname + + else : + self.fsname = self.encode_name(name) + + # name + if name : + self.name = name + + else : + self.name = self.decode_fsname(fsname) + + def subnode (self, name) : + """ + Returns a Node object representing the given name behind this node. + + The name should either be a plain ASCII string or unicode object. + """ + + return Node(self, name=name) + + @property + def path (self) : + """ + Build and return the real filesystem path for this node + """ + + # build using parent path and our fsname + return os.path.join(self.parent.path, self.fsname) + + @property + def unicodepath (self) : + """ + Build and return the fake unicode filesystem path for this node + """ + + # build using parent unicodepath and our name + return os.path.join(self.parent.path, self.name) + + def exists (self) : + """ + Tests if this node exists on the physical filesystem + """ + + return os.path.exists(self.path) + + def is_dir (self) : + """ + Tests if this node represents a directory on the filesystem + """ + + return os.path.isdir(self.path) + + def is_file (self) : + """ + Tests if this node represents a normal file on the filesystem + """ + + return os.path.isfile(self.path) + + def __str__ (self) : + """ + Returns the raw filesystem path + """ + + return self.path + + def __unicode__ (self) : + """ + Returns the human-readable path + """ + + return self.unicodepath + + def __repr__ (self) : + """ + Returns a str representing this dir + """ + + return "Node(%r, %r)" % (self.parent.path, self.fsname) + + def __cmp__ (self) : + """ + Comparisons between Nodes + """ + + return cmp((self.parent, self.name), (other.parent, other.name)) + +class File (Node) : + """ + A file. Simple, eh? + """ + + @property + def basename (self) : + """ + Returns the "base" part of this file's name, i.e. the filename without the extension + """ + + basename, _ = os.path.splitext(self.name) + + return basename + + @property + def fileext (self) : + """ + Returns the file extension part of the file's name, without any leading dot + """ + + _, fileext = os.path.splitext(self.name) + + return fileext.rstrip('.') + + def matchext (self, ext_list) : + """ + Tests if this file's extension is part of the recognized list of extensions + """ + + return (self.fileext.lower() in ext_list) + +class Directory (Node) : + """ + A directory is a node that contains other nodes. + """ + + # a list of (test_func, node_type) tuples for use by children() to build subnodes with + NODE_TYPES = None + + def subdir (self, name) : + """ + Returns a Directory object representing the name underneath this dir + """ + + return Directory(self, name=name) + + def subfile (self, name) : + """ + Returns a File object representing the name underneath this dir + """ + + return Directory(self, name=name) + + def listdir (self, skip_dotfiles=True) : + """ + Yield a series of raw fsnames for nodes in this dir + """ + + # expressed + return (fsname for fsname in os.listdir(self.path) if not (skip_dotfiles and fsname.startswith('.'))) + + def child_nodes (self, skip_dotfiles=True) : + """ + Yield a series of nodes contained in this dir + """ + + return (Node(self, fsname) for fsname in self.listdir(skip_dotfiles)) + + def __iter__ (self) : + """ + Iterating over a Directory yields sub-Nodes. + + Dotfiles are skipped. + """ + + return self.childnodes() + + @property + def root_path (self) : + """ + Build and return a relative path to the root of this dir tree + """ + + # build using parent root_path + return os.path.join('..', self.parent.root_path) + + def children (self) : + """ + Yield a series of Node subclasses representing the items in this dir. + + This uses self.NODE_TYPES to figure out what kind of sub-node object to build. This should be a list of + (test_func, node_type) + + tuples, of which the first is a function that takes a Node as it's sole argument, and returns a boolean. + For the first test_func which returns True, a Node-subclass object is constructed using node_type.from_node. + """ + + for node in self : + # figure out what type to use + for test_func, node_type in self.NODE_TYPES : + if test_func(node) : + # matches, build + yield node_type.from_node(node) + + else : + # unknown file type! + raise Exception("unrecongized type of file: %s" % node); + +# assign default Directory.NODE_TYPES +Directory.NODE_TYPES = [ + (Node.is_dir, Directory), + (Node.is_file, File), +] + + +class Root (Directory) : + """ + A special Directory that overrides the Node methods to anchor the recursion/etc at some 'real' filesystem path. + """ + + def __init__ (self, fspath, config) : + """ + Construct the directory tree root at the given 'real' path, which must be a raw str + """ + + # abuse Node's concept of a "name" a bit + super(Root, self).__init__(None, fspath, config=config) + + @property + def path (self) : + """ + Returns the raw path + """ + + return self.fsname + + @property + def unicodepath (self) : + """ + Returns the raw decoded path + """ + + return self.name + + @property + def root_path (self) : + """ + Returns an empty string representing this dir + """ + + return '' + + def __repr__ (self) : + """ + Override Node.__repr__ to not use self.parent.path + """ + + return "Root(%r)" % self.fsname + diff -r 724121253d34 -r 0f39cb5e4b11 degal/render.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/degal/render.py Thu Jun 04 11:22:52 2009 +0300 @@ -0,0 +1,51 @@ +""" + Rendering images as thumbnails/previews. +""" + +import PIL.Image + +import os.path + +class RenderMachine (object) : + """ + RAWR! I'm the render machine! + + TODO: use multithreaded rendering (PIL supports it) + """ + + def __init__ (self, config) : + """ + Use the given Configuration object's settings for rendering + """ + + self.config = config + + def render_out (self, img, size, out_path) : + """ + Creates a thumbnail from the given PIL.Image of the given size, and saves it at out_path. + """ + + # we need to create a copy, as .thumbnail mutates the Image + img_out = img.copy() + + # then resample to given size + img_out.thumbnail(size, resample=True) + + # and write out + img_out.save(out_path) + + def render_img (self, img, dirname, filename) : + """ + Renders new thumbnails/previews from the given loaded image and filename based on the current configuration. + + Note: this does not check for the existance of the thumbs/previews subdirs! + """ + + # once for each size + for subdir, size in ( + (self.config.thumb_dir, self.config.thumb_size), + (self.config.preview_dir, self.config.preview_size) + ) : + # resize and write out + self.render_out(img, size, os.path.join(dirname, subdir, filename) +