degal/folder.py
author Tero Marttila <terom@fixme.fi>
Fri, 05 Jun 2009 19:30:15 +0300
changeset 57 8d06e0283b88
parent 44 533b7e8b5d3b
child 64 4ebd563214d2
permissions -rw-r--r--
start implementing new Image stuff, tie in RenderMachine into the new Image class, assoicated config stuff
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()