diff -r 373392025533 -r 3b1579a7bffb degal/folder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/degal/folder.py Wed Jun 03 19:03:28 2009 +0300 @@ -0,0 +1,329 @@ +# DeGAL - A pretty simple web image gallery +# Copyright (C) 2007 Tero Marttila +# http://marttila.de/~terom/degal/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +import os, os.path + +import settings, image, utils, helpers, log +from template import gallery as gallery_tpl +from helpers import url_for_page + +def dirUp (count=1) : + """ + Returns a relative path to the directly count levels above the current one + """ + + if not count : + return '.' + + return os.path.join(*(['..']*count)) + +class Folder (object) : + def __init__ (self, name='.', parent=None) : + # the directory name, no trailing / + self.name = unicode(name.rstrip(os.sep)) + + # our parent Folder, or None + self.parent = parent + + # the path to this dir, as a relative path to the root of the image gallery, always starts with . + if parent and name : + self.path = parent.pathFor(self.name) + else : + self.path = self.name + + # the url-path to the index.html file + self.html_path = self.path + + # dict of fname -> Folder + self.subdirs = {} + + # dict of fname -> Image + self.images = {} + + # our human-friendly title + self.title = None + + # our long-winded description + self.descr = '' + + # is this folder non-empty? + self.alive = None + + # self.images.values(), but sorted by filename + self.sorted_images = [] + + # the ShortURL key to this dir + self.shorturl_code = None + + # were we filtered out? + self.filtered = False + + def pathFor (self, *fnames) : + """ + Return a root-relative path to the given path inside this dir + """ + return os.path.join(self.path, *fnames) + + def index (self, filters=None) : + """ + Look for other dirs and images inside this dir. Filters must be either None, + whereupon all files will be included, or a dict of {filename -> next_filter}. + If given, only filenames that are present in the dict will be indexed, and in + the case of dirs, the next_filter will be passed on to that Folder's index + method. + """ + + if filters : + self.filtered = True + + # iterate through listdir + for fname in os.listdir(self.path) : + # the full filesystem path to it + fpath = self.pathFor(fname) + + # ignore dotfiles + if fname.startswith('.') : + log.debug("Skipping dotfile %s", fname) + continue + + # apply filters + if filters : + if fname in filters : + next_filter = filters[fname] + else : + log.debug("Skip `%s' as we have a filter", fname) + continue + else : + next_filter = None + + # recurse into subdirs, but not thumbs/previews + if (os.path.isdir(fpath) + and (fname not in (settings.THUMB_DIR, settings.PREVIEW_DIR)) + and (self.parent or fname not in settings.ROOT_IGNORE) + ) : + log.down(fname) + + f = Folder(fname, self) + + try : + if f.index(next_filter) : # recursion + # if a subdir is alive, we are alive as well + self.subdirs[fname] = f + self.alive = True + except Exception, e : + log.warning("skip - %s: %s" % (type(e), e)) + + log.up() + + # handle images + elif os.path.isfile(fpath) and utils.isImage(fname) : + log.next(fname) + self.images[fname] = image.Image(self, fname) + + # ignore everything else + else : + log.debug("Ignoring file %s", fname) + + # sort and link the images + if self.images : + self.alive = True + + # sort the images + fnames = self.images.keys() + fnames.sort() + + prev = None + + # link + for fname in fnames : + img = self.images[fname] + + img.prev = prev + + if prev : + prev.next = img + + prev = img + + # add to the sorted images list + self.sorted_images.append(img) + + # figure out our title/ descr. Must be done before our parent dir is rendered (self.title) + title_path = self.pathFor(settings.TITLE_FILE) + + self.title, self.descr = utils.readTitleDescr(title_path) + + # default title for the root dir + if self.title or self.descr : + self.alive = True + pass # use what was in the title file + + elif not self.parent : + self.title = 'Index' + + else : + self.title = self.name + + if not self.alive : + log.debug("Dir %s isn't alive" % self.path) + + return self.alive + + def getObjInfo (self) : + """ + Metadata for shorturls2.db + """ + return 'dir', self.path, '' + + def breadcrumb (self, forImg=None) : + """ + Returns a [(fname, title)] list of this dir's parent dirs + """ + + f = self + b = [] + d = 0 + + while f : + # functionality of the slightly-hacked-in variety + if f is self and forImg is not None : + url = helpers.url_for_page(self.getPageNumber(forImg)) + else : + url = dirUp(d) + + b.insert(0, (url, f.title)) + + d += 1 + f = f.parent + + return b + + def getPageNumber (self, img) : + """ + Get the page number that the given image is on + """ + + return self.sorted_images.index(img) // settings.IMAGE_COUNT + + def countParents (self, acc=0) : + if self.parent : + return self.parent.countParents(acc+1) + else : + return acc + + def inRoot (self, *fnames) : + """ + Return a relative URL from this dir to the given path in the root dir + """ + + c = self.countParents() + + return utils.url_join(*((['..']*c) + list(fnames))) + + def render (self) : + """ + Render the index.html, Images, and recurse into subdirs + """ + + # ded folders are skipped + if not self.alive : + # dead, skip, no output + return + + index_mtime = utils.mtime(self.pathFor("index.html")) + dir_mtime = utils.mtime(self.path) + + # if this dir's contents were filtered out, then we can't render the index.html, as we aren't aware of all the images in here + if self.filtered : + log.warning("Dir `%s' contents were filtered, so we won't render the gallery index again", self.path) + + elif index_mtime > dir_mtime : + # no changes, pass, ignored + pass + + else : + # create the thumb/preview dirs if needed + for dir in (settings.THUMB_DIR, settings.PREVIEW_DIR) : + path = self.pathFor(dir) + + if not os.path.isdir(path) : + log.info("mkdir %s", dir) + os.mkdir(path) + + # sort the subdirs + subdirs = self.subdirs.values() + subdirs.sort(key=lambda d: d.name) + + # paginate! + images = self.sorted_images + image_count = len(images) + pages = [] + + while images : + pages.append(images[:settings.IMAGE_COUNT]) + images = images[settings.IMAGE_COUNT:] + + pagination_required = len(pages) > 1 + + if pagination_required : + log.info("%d pages @ %d images", len(pages), settings.IMAGE_COUNT) + elif not pages : + log.info("no images, render for subdirs") + pages = [[]] + + for cur_page, images in enumerate(pages) : + if pagination_required and cur_page > 0 : + shorturl = "%s/%s" % (self.shorturl_code, cur_page+1) + else : + shorturl = self.shorturl_code + + # render to index.html + gallery_tpl.render_to(self.pathFor(url_for_page(cur_page)), + stylesheet_url = self.inRoot('style.css'), + title = self.title, + breadcrumb = self.breadcrumb(), + + dirs = subdirs, + images = images, + + num_pages = len(pages), + cur_page = cur_page, + + description = self.descr, + + shorturl = self.inRoot('s', shorturl), + shorturl_code = shorturl, + ) + + # render images + image_count = len(self.sorted_images) + for i, img in enumerate(self.images.itervalues()) : + log.next("[%-4d/%4d] %s", i + 1, image_count, img.name) + + img.render() + + # recurse into subdirs + for dir in self.subdirs.itervalues() : + log.down(dir.name) + + dir.render() + + log.up() +