degal/folder.py
author Tero Marttila <terom@fixme.fi>
Wed, 03 Jun 2009 19:03:28 +0300
branchuse-distutils
changeset 41 3b1579a7bffb
parent 31 lib/folder.py@09776792a91c
child 44 533b7e8b5d3b
permissions -rw-r--r--
reorganize files to move lib, templates, www into 'degal' package, keep separate 'cgi-bin' for now
# 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()