lib/folder.py
author terom
Fri, 21 Dec 2007 20:45:03 +0000
changeset 13 c229bcb1de41
parent 12 c2d8e9a754a1
child 14 4b5478da5850
permissions -rw-r--r--
image breadcrumb links back to page that said image is on
import os, os.path

import settings, image, utils, helpers
from log import index, render
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))
    
def isImage (fname) :
    """
        Is the given filename likely to be an image file?
    """

    fname = fname.lower()
    base, ext = os.path.splitext(fname)
    ext = ext.lstrip('.')

    return ext in settings.IMAGE_EXTS

class Folder (object) :
    def __init__ (self, name='.', parent=None) :
        # the directory name, no trailing /
        self.name = 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.
        """

        index.info("Indexing %s", self.path)

        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('.') :
                index.debug("Skipping dotfile %s", fname)
                continue
            
            # apply filters
            if filters :
                if fname in filters :
                    next_filter = filters[fname]
                else :
                    index.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)
            ) :
                index.debug("Found subdir %s", fpath)
                f = self.subdirs[fname] = Folder(fname, self)
                if f.index(next_filter) :   # recursion
                    # if a subdir is alive, we are alive as well
                    self.alive = True

            # handle images
            elif os.path.isfile(fpath) and isImage(fname) :
                index.debug("Found image %s", fname)
                self.images[fname] = image.Image(self, fname)

            # ignore everything else
            else :
                index.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 :
                self.alive = True
                pass # use what was in the title file
                
            elif not self.parent :
                self.title = 'Index'

            else :
                self.title = self.name
            
            if self.descr :
                self.alive = True

        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 :
            render.info("Skipping dir %s (no images)", self.path)
            return
        
        # 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 :
            render.warning("Dir `%s' contents were filtered, so we won't render the gallery index again", self.path)

        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) :
                    render.info("Creating dir %s", path)
                    os.mkdir(path)

            # sort the subdirs
            subdirs = self.subdirs.values()
            subdirs.sort(key=lambda d: d.name)
            
            render.info("Rendering %s", self.path)

            # 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 :
                render.info("Index split into %d pages of %d images each", len(pages), settings.IMAGE_COUNT)
            
            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
        for img in self.images.itervalues() :
            img.render()
        
        # recurse into subdirs
        for dir in self.subdirs.itervalues() :
            dir.render()