--- /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()
+