"
- shorturl = "%s/%s" % (self.shorturl_code, cur_page+1)
- else :
- pagination = ''
- shorturl = self.shorturl_code
-
- # render to index.html
- gallery_tpl.renderTo(self.pathFor(self._page_fname(cur_page)),
- STYLE_URL=self.inRoot('style.css'),
- BREADCRUMB=" » ".join([link(u, t) for (u, t) in self.breadcrumb()]),
- TITLE=self.title,
- DIRECTORIES=directories,
- PAGINATION=pagination,
- CONTENT="".join([i.thumbImgTag() for i in page]),
- DESCR=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()
-
-class Image (object) :
- def __init__ (self, dir, name) :
- # the image filename, e.g. DSC3948.JPG
- self.name = name
-
- # the Folder object that we are in
- self.dir = dir
-
- # the relative path from the root to us
- self.path = dir.pathFor(name)
-
- # the basename+ext, e.g. DSCR3948, .JPG
- self.base_name, self.ext = os.path.splitext(name)
-
- # the root-relative paths to the thumb and preview images
- self.thumb_path = self.dir.pathFor(THUMB_DIR, self.name)
- self.preview_path = self.dir.pathFor(PREVIEW_DIR, self.name)
-
- # our user-friendly title
- self.title = name
-
- # our long-winded description
- self.descr = ''
-
- # the image before and after us, both may be None
- self.prev = self.next = None
-
- # the name of the .html gallery view thing for this image, *always* self.name + ".html"
- self.html_name = self.name + ".html"
-
- # the root-relative path to the gallery view
- self.html_path = self.dir.pathFor(self.html_name)
-
- #
- # Figured out after prepare
- #
-
- # (w, h) tuple
- self.img_size = None
-
- # the ShortURL code for this image
- self.shorturl_code = None
-
- # what to use in the rendered templates, intended to be overridden by subclasses
- self.series_act = "add"
- self.series_verb = "Add to"
-
- def getObjInfo (self) :
- """
- Metadata for shorturl2.db
- """
- return 'img', self.dir.path, self.name
-
- def thumbImgTag (self) :
- """
- a of this image's thumbnail. Path relative to directory we are in
- """
- return link(self.html_name, "" % (os.path.join(THUMB_DIR, self.name), self.descr, self.title))
+from lib import folder, shorturl
- def previewImgTag (self) :
- """
- a of this image's preview. Path relative to directory we are in
- """
- return link(self.name, "" % (os.path.join(PREVIEW_DIR, self.name), self.descr, self.title))
-
- def linkTag (self) :
- """
- a text-link to this image
- """
- return link(self.html_name, self.title)
-
- def breadcrumb (self) :
- """
- Returns a [(fname, title)] list of this image's parents
- """
-
- f = self.dir
- b = [(self.html_name, self.title)]
- d = 0
-
- while f :
- b.insert(0, (dirUp(d), f.title))
-
- d += 1
- f = f.parent
-
- return b
-
- def render (self) :
- """
- Write out the .html file
- """
-
- # stat the image file to get the filesize and mtime
- st = os.stat(self.path)
-
- self.filesize = st.st_size
- self.timestamp = st.st_mtime
-
- # open the image in PIL to get image attributes + generate thumbnails
- img = PIL.Image.open(self.path)
-
- self.img_size = img.size
-
- for out_path, geom in ((self.thumb_path, THUMB_GEOM), (self.preview_path, PREVIEW_GEOM)) :
- # if it doesn't exist, or it's older than the image itself, generate
- if not (os.path.exists(out_path) and os.stat(out_path).st_mtime > self.timestamp) :
- render.info("Create thumbnailed image at %s with geom %s", out_path, geom)
-
- # XXX: is this the most efficient way to do this?
- out_img = img.copy()
- out_img.thumbnail(geom, resample=True)
- out_img.save(out_path)
-
- # look for the metadata file
- title_path = self.dir.pathFor(self.base_name + '.txt')
-
- title, descr = readTitleDescr(title_path)
-
- if title :
- self.title = title
- self.descr = descr
-
- render.info("Rendering image %s", self.path)
-
- image_tpl.renderTo(self.html_path,
- STYLE_URL=self.dir.inRoot('style.css'),
- UP_URL=('.'),
- PREV_URL=(self.prev and self.prev.html_name or ''),
- NEXT_URL=(self.next and self.next.html_name or ''),
- FILE=self.name,
- BREADCRUMB=" » ".join([link(u, t) for u, t in self.breadcrumb()]),
- TITLE=self.title,
- PREVIOUS_THUMB=(self.prev and self.prev.thumbImgTag() or ''),
- IMAGE=self.previewImgTag(),
- NEXT_THUMB=(self.next and self.next.thumbImgTag() or ''),
- DESCRIPTION=self.descr,
- IMGSIZE="%dx%d" % self.img_size,
- FILESIZE=fmtFilesize(self.filesize),
- TIMESTAMP=fmtTimestamp(self.timestamp),
- SHORTURL=self.dir.inRoot('s', self.shorturl_code),
- SHORTURL_CODE=self.shorturl_code,
- SERIES_URL=self.dir.inRoot('series/%s/%s' % (self.series_act, self.shorturl_code)),
- SERIES_VERB=self.series_verb,
- )
-
- def __str__ (self) :
- return "Image `%s' in `%s'" % (self.name, self.dir.path)
-
-def int2key (id) :
- """
- Turn an integer into a short-as-possible url-safe string
- """
- for type in ('B', 'H', 'I') :
- try :
- return base64.b64encode(struct.pack(type, id), '-_').rstrip('=')
- except struct.error :
- continue
-
- raise Exception("ID overflow: %s" % id)
-
-def updateShorturlDb (root) :
- """
- DeGAL <= 0.2 used a simple key => path mapping, but now we use
- something more structured, key => (type, dirpath, fname), where
-
- type - one of 'img', 'dir'
- dirpath - the path to the directory, e.g. '.', './foobar', './foobar/quux'
- fname - the filename, one of '', 'DSC9839.JPG', 'this.png', etc.
- """
-
- db = shelve.open('shorturls2', 'c', writeback=True)
-
- id = db.get('_id', 1)
-
- dirqueue = [root]
-
- # dict of path -> obj
- paths = {}
-
- index.info("Processing ShortURLs...")
-
- while dirqueue :
- dir = dirqueue.pop(0)
-
- dirqueue.extend(dir.subdirs.itervalues())
-
- if dir.alive :
- paths[dir.path] = dir
-
- for img in dir.images.itervalues() :
- paths[img.path] = img
-
- for key in db.keys() :
- if key.startswith('_') :
- continue
-
- type, dirpath, fname = db[key]
-
- path = os.path.join(dirpath, fname).rstrip('/')
-
- try :
- paths.pop(path).shorturl_code = key
- index.debug("Code for `%s' is %s", path, key)
-
- except KeyError :
- index.debug("Path `%s' in DB does not exist?", path)
-
- for obj in paths.itervalues() :
- key = int2key(id)
- id += 1
-
- index.info("Alloc code `%s' for `%s'", key, obj.html_path)
-
- obj.shorturl_code = key
-
- db[key] = obj.getObjInfo()
-
- db['_id'] = id
- db.close()
-
-def main (targets=()) :
+def main (dir='.', targets=()) :
root_filter = {}
for target in targets :
@@ -670,24 +34,19 @@
if path_part :
if path_part not in f :
f[path_part] = {}
+
f = f[path_part]
-
- index.debug('Filter: %s', root_filter)
- root = Folder()
+ root = folder.Folder(dir)
root.index(root_filter)
- updateShorturlDb(root)
+ shorturl.updateDB(root)
root.render()
-def fmtFilesize (size) :
- return utils.formatbytes(size, forcekb=False, largestonly=True, kiloname='KiB', meganame='MiB', bytename='B', nospace=False)
-
-def fmtTimestamp (ts) :
- return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
-
if __name__ == '__main__' :
- from sys import argv
- argv.pop(0)
-
- main(argv)
-
+ parser = OptionParser(usage="usage: %prog [options] ... [target ...]")
+
+ parser.add_option("-d", "--dir", dest="dir", help="look for images in DIR and write the HTML there", metavar="DIR", default=".")
+
+ options, filter_targets = parser.parse_args()
+
+ main(options.dir, filter_targets)
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/folder.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/folder.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,287 @@
+import os, os.path
+
+import settings, image, utils
+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) :
+ """
+ Returns a [(fname, title)] list of this dir's parent dirs
+ """
+
+ f = self
+ b = []
+ d = 0
+
+ while f :
+ b.insert(0, (dirUp(d), f.title))
+
+ d += 1
+ f = f.parent
+
+ return b
+
+ 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()
+
\ No newline at end of file
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/formatbytes.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/formatbytes.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,116 @@
+###############################################################
+# Functions taken from pathutils.py Version 0.2.5 (2005/12/06), http://www.voidspace.org.uk/python/recipebook.shtml#utils
+# Copyright Michael Foord 2004
+# Released subject to the BSD License
+# Please see http://www.voidspace.org.uk/python/license.shtml
+
+###############################################################
+# formatbytes takes a filesize (as returned by os.getsize() )
+# and formats it for display in one of two ways !!
+
+# For information about bugfixes, updates and support, please join the Pythonutils mailing list.
+# http://groups.google.com/group/pythonutils/
+# Comments, suggestions and bug reports welcome.
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# E-mail fuzzyman@voidspace.org.uk
+
+def formatbytes(sizeint, configdict=None, **configs):
+ """
+ Given a file size as an integer, return a nicely formatted string that
+ represents the size. Has various options to control it's output.
+
+ You can pass in a dictionary of arguments or keyword arguments. Keyword
+ arguments override the dictionary and there are sensible defaults for options
+ you don't set.
+
+ Options and defaults are as follows :
+
+ * ``forcekb = False`` - If set this forces the output to be in terms
+ of kilobytes and bytes only.
+
+ * ``largestonly = True`` - If set, instead of outputting
+ ``1 Mbytes, 307 Kbytes, 478 bytes`` it outputs using only the largest
+ denominator - e.g. ``1.3 Mbytes`` or ``17.2 Kbytes``
+
+ * ``kiloname = 'Kbytes'`` - The string to use for kilobytes
+
+ * ``meganame = 'Mbytes'`` - The string to use for Megabytes
+
+ * ``bytename = 'bytes'`` - The string to use for bytes
+
+ * ``nospace = True`` - If set it outputs ``1Mbytes, 307Kbytes``,
+ notice there is no space.
+
+ Example outputs : ::
+
+ 19Mbytes, 75Kbytes, 255bytes
+ 2Kbytes, 0bytes
+ 23.8Mbytes
+
+ .. note::
+
+ It currently uses the plural form even for singular.
+ """
+ defaultconfigs = { 'forcekb' : False,
+ 'largestonly' : True,
+ 'kiloname' : 'Kbytes',
+ 'meganame' : 'Mbytes',
+ 'bytename' : 'bytes',
+ 'nospace' : True}
+ if configdict is None:
+ configdict = {}
+ for entry in configs:
+ # keyword parameters override the dictionary passed in
+ configdict[entry] = configs[entry]
+ #
+ for keyword in defaultconfigs:
+ if not configdict.has_key(keyword):
+ configdict[keyword] = defaultconfigs[keyword]
+ #
+ if configdict['nospace']:
+ space = ''
+ else:
+ space = ' '
+ #
+ mb, kb, rb = bytedivider(sizeint)
+ if configdict['largestonly']:
+ if mb and not configdict['forcekb']:
+ return stringround(mb, kb)+ space + configdict['meganame']
+ elif kb or configdict['forcekb']:
+ if mb and configdict['forcekb']:
+ kb += 1024*mb
+ return stringround(kb, rb) + space+ configdict['kiloname']
+ else:
+ return str(rb) + space + configdict['bytename']
+ else:
+ outstr = ''
+ if mb and not configdict['forcekb']:
+ outstr = str(mb) + space + configdict['meganame'] +', '
+ if kb or configdict['forcekb'] or mb:
+ if configdict['forcekb']:
+ kb += 1024*mb
+ outstr += str(kb) + space + configdict['kiloname'] +', '
+ return outstr + str(rb) + space + configdict['bytename']
+
+def stringround(main, rest):
+ """
+ Given a file size in either (mb, kb) or (kb, bytes) - round it
+ appropriately.
+ """
+ # divide an int by a float... get a float
+ value = main + rest/1024.0
+ return str(round(value, 1))
+
+def bytedivider(nbytes):
+ """
+ Given an integer (probably a long integer returned by os.getsize() )
+ it returns a tuple of (megabytes, kilobytes, bytes).
+
+ This can be more easily converted into a formatted string to display the
+ size of the file.
+ """
+ mb, remainder = divmod(nbytes, 1048576)
+ kb, rb = divmod(remainder, 1024)
+ return (mb, kb, rb)
+
+
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/helpers.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/helpers.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,31 @@
+# template helper functions
+import urllib
+from formatbytes import formatbytes
+from datetime import datetime
+
+def iter_is_first (seq) :
+ flag = True
+
+ for item in seq :
+ yield item, flag
+ flag = False
+
+def url_for_page (page) :
+ assert page >= 0
+
+ if page > 0 :
+ return 'index_%d.html' % page
+ else :
+ return 'index.html'
+
+def tag_for_img (page, img) :
+ return """""" % (page, img)
+
+def format_filesize (size) :
+ return formatbytes(size, forcekb=False, largestonly=True, kiloname='KiB', meganame='MiB', bytename='B', nospace=False)
+
+def format_timestamp (ts) :
+ return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
+
+def format_imgsize (size) :
+ return "%dx%d" % size
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/image.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/image.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,129 @@
+import os, os.path
+
+import PIL.Image
+
+import settings, utils
+from log import index, render
+from template import image as image_tpl
+
+class Image (object) :
+ def __init__ (self, dir, name) :
+ # the image filename, e.g. DSC3948.JPG
+ self.name = name
+
+ # the Folder object that we are in
+ self.dir = dir
+
+ # the relative path from the root to us
+ self.path = dir.pathFor(name)
+
+ # the basename+ext, e.g. DSCR3948, .JPG
+ self.base_name, self.ext = os.path.splitext(name)
+
+ # our user-friendly title
+ self.title = name
+
+ # our long-winded description
+ self.descr = ''
+
+ # the image before and after us, both may be None
+ self.prev = self.next = None
+
+ # the image-relative names for the html page, thumb and preview images
+ self.html_name = self.name + ".html"
+ self.thumb_name = utils.url_join(settings.THUMB_DIR, self.name)
+ self.preview_name = utils.url_join(settings.PREVIEW_DIR, self.name)
+
+ # the root-relative paths to the html page, thumb and preview images
+ self.html_path = self.dir.pathFor(self.html_name)
+ self.thumb_path = self.dir.pathFor(settings.THUMB_DIR, self.name)
+ self.preview_path = self.dir.pathFor(settings.PREVIEW_DIR, self.name)
+
+ #
+ # Figured out after prepare
+ #
+
+ # (w, h) tuple
+ self.img_size = None
+
+ # the ShortURL code for this image
+ self.shorturl_code = None
+
+ # what to use in the rendered templates, intended to be overridden by subclasses
+ self.series_act = "add"
+ self.series_verb = "Add to"
+
+ def getObjInfo (self) :
+ """
+ Metadata for shorturl2.db
+ """
+ return 'img', self.dir.path, self.name
+
+ def breadcrumb (self) :
+ """
+ Returns a [(fname, title)] list of this image's parents
+ """
+
+ return self.dir.breadcrumb() + [(self.html_name, self.title)]
+
+ def render (self) :
+ """
+ Write out the .html file
+ """
+
+ # stat the image file to get the filesize and mtime
+ st = os.stat(self.path)
+
+ self.filesize = st.st_size
+ self.timestamp = st.st_mtime
+
+ # open the image in PIL to get image attributes + generate thumbnails
+ img = PIL.Image.open(self.path)
+
+ self.img_size = img.size
+
+ for out_path, geom in ((self.thumb_path, settings.THUMB_GEOM), (self.preview_path, settings.PREVIEW_GEOM)) :
+ # if it doesn't exist, or it's older than the image itself, generate
+ if not (os.path.exists(out_path) and os.stat(out_path).st_mtime > self.timestamp) :
+ render.info("Create thumbnailed image at %s with geom %s", out_path, geom)
+
+ # XXX: is this the most efficient way to do this? It seems slow
+ out_img = img.copy()
+ out_img.thumbnail(geom, resample=True)
+ out_img.save(out_path)
+
+ # look for the metadata file
+ title_path = self.dir.pathFor(self.base_name + '.txt')
+
+ self.title, self.descr = utils.readTitleDescr(title_path)
+
+ if not self.title :
+ self.title = self.name
+
+ render.info("Rendering image %s", self.path)
+
+ image_tpl.render_to(self.html_path,
+ stylesheet_url = self.dir.inRoot('style.css'),
+ title = self.title,
+ breadcrumb = self.breadcrumb(),
+
+ prev = self.prev,
+ next = self.next,
+ img = self,
+
+ description = self.descr,
+
+ filename = self.name,
+ img_size = self.img_size,
+ file_size = self.filesize,
+ timestamp = self.timestamp,
+
+ shorturl = self.dir.inRoot('s', self.shorturl_code),
+ shorturl_code = self.shorturl_code,
+
+ series_url = self.dir.inRoot('series/%s/%s' % (self.series_act, self.shorturl_code)),
+ series_verb = self.series_verb,
+ )
+
+ def __str__ (self) :
+ return "Image `%s' in `%s'" % (self.name, self.dir.path)
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/log.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/log.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,14 @@
+import logging
+
+logging.basicConfig(
+ level=logging.DEBUG,
+ format="%(message)s",
+# format="%(name)8s %(levelname)8s %(lineno)3d %(message)s",
+
+)
+
+index = logging.getLogger('index')
+render = logging.getLogger('render')
+template = logging.getLogger('template')
+
+template.setLevel(logging.ERROR)
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/settings.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/settings.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,19 @@
+TEMPLATE_DIR = './templates'
+TEMPLATE_EXT = 'html'
+
+IMAGE_EXTS = ('jpg', 'jpeg', 'png', 'gif', 'bmp')
+
+THUMB_DIR = 'thumbs'
+PREVIEW_DIR = 'previews'
+TITLE_FILE = 'title.txt'
+
+THUMB_GEOM = (160, 120)
+PREVIEW_GEOM = (640, 480)
+
+DEFAULT_TITLE = 'Image gallery'
+
+# how many image/page
+IMAGE_COUNT = 50
+
+VERSION = "0.5"
+ROOT_IGNORE = ('lib', 'templates')
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/shorturl.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/shorturl.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,78 @@
+import struct
+import base64
+import shelve
+import os.path
+
+from log import index
+
+def int2key (id) :
+ """
+ Turn an integer into a short-as-possible url-safe string
+ """
+ for type in ('B', 'H', 'I') :
+ try :
+ return base64.b64encode(struct.pack(type, id), '-_').rstrip('=')
+ except struct.error :
+ continue
+
+ raise Exception("ID overflow: %s" % id)
+
+def updateDB (root) :
+ """
+ DeGAL <= 0.2 used a simple key => path mapping, but now we use
+ something more structured, key => (type, dirpath, fname), where
+
+ type - one of 'img', 'dir'
+ dirpath - the path to the directory, e.g. '.', './foobar', './foobar/quux'
+ fname - the filename, one of '', 'DSC9839.JPG', 'this.png', etc.
+ """
+
+ db = shelve.open('shorturls2', 'c', writeback=True)
+
+ id = db.get('_id', 1)
+
+ dirqueue = [root]
+
+ # dict of path -> obj
+ paths = {}
+
+ index.info("Processing ShortURLs...")
+
+ while dirqueue :
+ dir = dirqueue.pop(0)
+
+ dirqueue.extend(dir.subdirs.itervalues())
+
+ if dir.alive :
+ paths[dir.path] = dir
+
+ for img in dir.images.itervalues() :
+ paths[img.path] = img
+
+ for key in db.keys() :
+ if key.startswith('_') :
+ continue
+
+ type, dirpath, fname = db[key]
+
+ path = os.path.join(dirpath, fname).rstrip(os.sep)
+
+ try :
+ paths.pop(path).shorturl_code = key
+ index.debug("Code for `%s' is %s", path, key)
+
+ except KeyError :
+ index.debug("Path `%s' in DB does not exist?", path)
+
+ for obj in paths.itervalues() :
+ key = int2key(id)
+ id += 1
+
+ index.info("Alloc code `%s' for `%s'", key, obj.html_path)
+
+ obj.shorturl_code = key
+
+ db[key] = obj.getObjInfo()
+
+ db['_id'] = id
+ db.close()
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/template.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/template.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,45 @@
+from mako import exceptions
+from mako.lookup import TemplateLookup
+
+import settings, helpers
+
+import log
+
+_lookup = TemplateLookup(
+ directories=[settings.TEMPLATE_DIR],
+ module_directory='%s/cache' % settings.TEMPLATE_DIR,
+ output_encoding='utf-8',
+ filesystem_checks=False, # this may need to be changed if used in a long-term process
+)
+
+TEMPLATE_GLOBALS = dict(
+ h = helpers,
+ version = settings.VERSION,
+)
+
+class Template (object) :
+ def __init__ (self, name) :
+ self.name = name
+ self.tpl = _lookup.get_template("%s.%s" % (name, settings.TEMPLATE_EXT))
+
+ def render (self, **data) :
+ data.update(TEMPLATE_GLOBALS)
+
+ try :
+ log.template.debug("render %s with %s", self.name, data)
+ return self.tpl.render(**data)
+ except :
+ data = exceptions.text_error_template().render()
+ log.template.error(data)
+
+ raise
+
+ def render_to (self, file, **data) :
+ fh = open(file, "w")
+ fh.write(self.render(**data))
+ fh.close()
+
+# templates
+gallery = Template("gallery")
+image = Template("image")
+
diff -r 27dac27d1a58 -r c2d8e9a754a1 lib/utils.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/utils.py Fri Dec 21 20:36:03 2007 +0000
@@ -0,0 +1,38 @@
+import os.path
+
+def readFile (path) :
+ fo = open(path, 'r')
+ data = fo.read()
+ fo.close()
+
+ return data
+
+def readTitleDescr (path) :
+ """
+ Read a title.txt or .txt file
+ """
+
+ if os.path.exists(path) :
+ content = readFile(path)
+
+ if '---' in content :
+ title, descr = content.split('---', 1)
+ else :
+ title, descr = content, ''
+
+ return title.strip(), descr.strip()
+
+ return "", ""
+
+def url_join (*parts) :
+ return '/'.join(parts)
+
+from optparse import OptionParser
+
+def optparse (options) :
+ parser = OptionParser()
+
+
+ parser.add_option
+
+
\ No newline at end of file
diff -r 27dac27d1a58 -r c2d8e9a754a1 series.cgi
--- a/series.cgi Thu Dec 20 17:42:04 2007 +0000
+++ b/series.cgi Fri Dec 21 20:36:03 2007 +0000
@@ -1,4 +1,24 @@
#!/usr/bin/env python2.4
+#
+# 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 shelve
import cgi
diff -r 27dac27d1a58 -r c2d8e9a754a1 shorturl.cgi
--- a/shorturl.cgi Thu Dec 20 17:42:04 2007 +0000
+++ b/shorturl.cgi Fri Dec 21 20:36:03 2007 +0000
@@ -1,4 +1,25 @@
-#!/usr/bin/env python2.5
+#!/usr/bin/env python2.4
+#
+# 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 shelve
import cgi
diff -r 27dac27d1a58 -r c2d8e9a754a1 taggr.cgi
--- a/taggr.cgi Thu Dec 20 17:42:04 2007 +0000
+++ b/taggr.cgi Fri Dec 21 20:36:03 2007 +0000
@@ -1,4 +1,25 @@
#!/usr/bin/env python2.4
+#
+# 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 cgi
import shelve
diff -r 27dac27d1a58 -r c2d8e9a754a1 templates/gallery.html
--- a/templates/gallery.html Thu Dec 20 17:42:04 2007 +0000
+++ b/templates/gallery.html Fri Dec 21 20:36:03 2007 +0000
@@ -1,25 +1,56 @@
-
-
+<%inherit file="master.html" />
-
-
-
-
-
-
-
-
-
-
+<%def name="pagination(num_pages, cur_page)">
+% if num_pages > 1 :
+