"""
Per-directory gallery state
"""
import filesystem, image, html
from utils import lazy_load
class Folder (filesyste.Directory) :
"""
A Folder is a filesystem Directory that contains any number of other Folders and Images.
"""
def __init__ (self, node) :
super(Folder, self).__init__(node)
# info
self.title = None
self.description = None
@lazy_load
def preview_dir (self) :
"""
Load and return the Directory for previews
"""
return self.subdir(self.config.preview_dir, create=True)
@lazy_load
def thumb_dir (self) :
"""
Load and return the Directory for thumbs
"""
return self.subdir(self.config.thumb_dir, create=True)
@lazy_load_iter
def subnodes (self) :
"""
Load and return an ordered list of child-Nodes
"""
return super(Folder, self).subnodes(skip_dotfiles=True, sorted=True)
@lazy_load_iter
def subfolders (self) :
"""
Load and return an ordered list of sub-Folders
"""
return (Folder(node) for node in self.subnodes if isinstance(node, filesystem.Directory))
@lazy_load_iter
def images (self) :
"""
Load and return an ordered/linked list of sub-Images
"""
prev = None
for node in self.subnodes :
# skip non-relevant ones
# XXX: node should need to be a File
if not isinstance(node, filesystem.File) or not self.config.is_image(node) :
continue
# create new
img = image.Image(node, prev)
# link up
if prev :
prev.next = img
# yield the linked-up prev
yield prev
# continue
prev = img
# and the last one
if prev :
yield prev
@property
def page_count (self) :
"""
Returns the number of pages needed to show this folder's images
"""
return math.ceil(len(self.images) / float(self.config.images_per_page))
def images_for_page (self, page) :
"""
Returns the list of Images to be displayed for the given page, if any
"""
# offset to first image
offset = page * self.config.images_per_page
# slice
return self.images[offset : offset + self.config.images_per_page]
def html_file (self, page=0) :
"""
Returns the File representing the .html for the given page
"""
if page :
return self.subfile("index_%d.html" % page)
else :
return self.subfile("index.html")
def index (self) :
"""
Recursively index this Folder, yielding a series of all Folder objects inside.
"""
# and subfolders
for subfolder in self.subfolders :
yield subfolder
for item in subfolder.index_subfolders() :
yield item
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()