# -*- coding: iso-8859-1 -*-
"""
    Gallery.py  Version 0.87
    This macro creates dynamic tabulated displays based on attachment contents

    @copyright: 2004,2005 by Simon Ryan <simon<at>smartblackbox.com>  http://smartblackbox.com/simon
    @license: GPL

    Special thanks go to: 
	My beautiful wife Jenny: For keeping the kids at bay long enough for me to code it :-)
        Adam Shand: For his GallerySoftware feature wish list, support, ideas and suggestions.

    Usage: [[Gallery(key1=value1,key2=value2....)]]

      where the following keys are valid:
	  thumbnailwidth     = no of pixels wide to make the thumbnails
	  webnailwidth       = width in pixels of the web sized images
	  numberofcolumns    = no of columns used in the thumbnail table

    Bugs:

      Continued rotation will degrade the tmp images (but they can be forced to regen)

    Features:

      Simple usage, just put [[Gallery]] on any page and upload some pictures as attachments
      Rotate buttons
      Annotation
      Should work with MoinMoin versions 1.2.x and 1.3.x
      Support for Python Imaging Library
      Works with FastCGI mode


    Not yet implemented:
      Handling of video formats 

    Speed up:
	# When you get really sick of how slow the moinmoin image system is, 
	# you can either reconfigure your setup for FastCGI (highly recommended)
	# or: set the following variables in your wikiconfig.py
	gallerytempdir (the path to a writable directory)
	gallerytempurl (the path in your webservers url space where this directory can be read from)
        eg:
	    gallerytempdir='/var/www/html/nails'
	    gallerytempurl='/nails'
	  or maybe:
	    gallerytempurl=url_prefix+'/nails'
	# There are other ways of getting speedups for attachments, but these methods are the safest (IMHO)

"""

__author__ = "Simon D. Ryan"
__version__ = "0.87"

from MoinMoin import config, wikiutil
import string, cStringIO, os
import commands, shutil
from random import randint
try:
    import Image
except:
    pass

class Globs:
    # A quick place to plonk those shared variables
    thumbnailwidth='200'
    webnailwidth='600'
    numberofcolumns=4
    adminmsg=''
    debuglevel=0
    originals={}
    convertbin=''
    annotated={}
    attachmentdir=''
    gallerytempdirroot=''
    gallerytempdir=''
    gallerytempurl=''
    galleryrotchar=''
    pagename=''
    admin=''
    bcomp=''
    baseurl=''
    timeout=40
    allowedextensions=['jpg','jpeg','png','bmp','tiff','gif']

def message(astring,level=1):
    if level<=Globs.debuglevel:
        Globs.adminmsg=Globs.adminmsg+'<font color="#FF0000"><strong>Gallery</strong></font>:&nbsp;&nbsp;'+astring+'<br>\n'

def version():
        return(' version <b>'+Globs.version+'</b> by Simon D. Ryan.'+\
	'<br>Copyright 2004,2005 Simon D. Ryan<br>Gallery is a MoinMoin macro and is released under the '+\
	'<a href="http://www.gnu.org/licenses/gpl.txt">GPL</a>\n'+\
	'<p>Upload some images as attachments to <a href="'+Globs.baseurl+Globs.pagename+'?action=AttachFile"><b>'+Globs.pagename+'</b></a> and I will generate a gallery for you.')

# Thanks to denny<at>ece.arizona.edu
# This can be replaced with a static translation table to speed things up (later)
def mktrans():
        # Allow only letters and digits and a few other valid file characters
	alphanumeric=string.letters+string.digits+'.,-_\'!"'
	source_string=""
	destination_string=""
	for i in range(256):
		source_string=source_string+chr(i)
		if chr(i) in alphanumeric:
			destination_string=destination_string+chr(i)
		else:
			destination_string=destination_string+' '
	return string.maketrans(source_string,destination_string)

def qlink(pagename, querystring, query, description=''):
    # Returns a hyperlink constructed as a form query on pagename
    if not description:
        description=query
    return '<a href="'+Globs.baseurl+pagename+'?'+querystring+'='+query+Globs.bcomp+'">'+description+'</a>'

def navibar(target,querystring):
    # Returns a navigational bar with PREV,THUMBS,NEXT
    positions=Globs.originals.keys()
    positions.sort()
    # Append the action to the end of the URLS. This allows us to keep modes such as action=print
    thumbs='<a href="'+Globs.subname+'?'+Globs.bcomp+'">THUMBS</a>'
    index=positions.index(target)
    back,forward='',''
    if not index==0:
        # We are not the first so we can provide a back link
	back=qlink(Globs.pagename, querystring, positions[index-1], 'PREV')
    if not index==len(positions)-1:
        # We are not the last so we can provide a forward link
	forward=qlink(Globs.pagename, querystring, positions[index+1], 'NEXT')
    return '<table><tr><td>'+back+'</td><td>'+thumbs+'</td><td>'+forward+'</td></tr></table>'

def toolbar(target,naillevel):
    if Globs.admin:
	rotateleft='<input type="submit" name="rotate" value="rotate left">'
	rotateright='<input type="submit" name="rotate" value="rotate right">'
	deleteitem='<input type="submit" name="delete" value="delete">'
	htarget='<input type=hidden value="'+target+'" name="'+naillevel+'">'
	compat='<input type=hidden value="show" name="action">'
	return '<form METHOD=POST><table><tr><td>'+rotateleft+'</td><td>'+rotateright+'</td><td>'+deleteitem+'</td></tr></table>\n'+htarget+compat+'</form>'
    else:
        return ''

def buildnails(items):
    # For now we use commands.getoutput to do our dirty work
    # Later we can build a batch job and fork it off.

    # Make sure our temp directory is writable and generate a message if it isn't
    try:
	if not os.path.isfile(Globs.gallerytempdir+'/tmp.writetest'):
	    # There is probably a less ugly was to do this using stat (later)
	    open(Globs.gallerytempdir+'/tmp.writetest','w').close()
    except IOError:
        message('I had some trouble writing to the temp directory. Is it owned by me and writable?',0)

    # Don't go further if there is a lock in place
    if os.path.isfile(Globs.attachmentdir+'/tmp.lock'):
        message("I'm currently busy generating thumbnails and webnails, please try again later.",0)
	return ''

    # Find the convert binary in standard locations
    if not globals().has_key('Image'):
	if not os.path.isfile('/usr/bin/convert'):
	    if not os.path.isfile('/usr/X11R6/bin/convert'):
		message('<b>Please install ImageMagick or PIL so I can build thumbnails and webnails</b><p>',0)
		return
	    else:
		Globs.convertbin='/usr/X11R6/bin/convert'
	else:
	    Globs.convertbin='/usr/bin/convert'
    else:
        # Use Python Imaging Library
        Globs.convertbin='Image'

    # Create a lock file in the attachments dir so we can always remotely remove it if there is a problem
    open(Globs.attachmentdir+'/tmp.lock','w').close()
    
    import time
    tstart=time.time()
    pid,pid2='',''

    # For each original file, check for the existance of a nail
    for item in items:
        basename,prefix,width=item

	# Check to see if we tarry too long on the road
	if tstart and (time.time()-tstart) > Globs.timeout:
	    # This is taking waaay too long let us fork and detach else the browser will time out or worse, the webserver may kill us
	    pid = os.fork()
	    if pid != 0:
	        # We are in the parent so we break out
		message('The thumbnail generation process was taking too long so it has been backgrounded. Please try again later to see the full set of thumbnails',0)
		break
	    else:
		# Once we are forked we want to ignore the time
		tstart=''
	        # Break away from the controlling terminal, so that the web server cannot kill us by killing our parent
	        os.setsid()
		# Fork again so we can get away without a controlling terminal
		pid2 = os.fork()
		if (pid2 != 0):
		    os._exit(0)
		else:
		    # Close all open file descriptors
		    try:
		        max_fd = os.sysconf("SC_OPEN_MAX")
		    except (AttributeError, ValueError):
		        max_fd = 256
		    for fd in range(0, max_fd):
                        try:
			    os.close(fd)
			except OSError:
			    pass
		    # Redirect the standard file descriptors to /dev/null
		    os.open("/dev/null", os.O_RDONLY)    # stdin
		    os.open("/dev/null", os.O_RDWR)      # stdout
		    os.open("/dev/null", os.O_RDWR)      # stderr

		    # Now we are finally free to continue the conversions as a daemon
		    # If you would like to know more about the above, see:
		    #   Advanced Programming in the Unix Environment: W. Richard Stevens
		    # It is also explained in:
		    #   Unix Network Programming (Volume 1): W. Richard Stevens

	#pathtooriginal='"'+Globs.attachmentdir+'/'+Globs.originals[basename]+'"'
	pathtooriginal='"'+os.path.join(Globs.attachmentdir,Globs.originals[basename])+'"'
	# Warning:
	# Take care if modifying the following line, 
	# you may inadvertantly overwrite your original images!
	if not Globs.convertbin == 'Image':
	    #print 'building nail for '+pathtooriginal
	    #convout=commands.getoutput('%s -geometry %s \"%s\" "\"%s/%s.%s.jpg\""' % (Globs.convertbin,width+'x'+width,pathtooriginal,Globs.gallerytempdir,prefix,basename))
	    convout=commands.getoutput('%s -geometry %s %s "%s/%s.%s.jpg"' % (Globs.convertbin,width+'x'+width,pathtooriginal,Globs.gallerytempdir,prefix,basename))
	    convout=''
	    convout=string.strip(convout)
	    if convout:
		message(convout)
	else:
	    # Use PIL  (strip off the "")
	    im = Image.open(pathtooriginal[1:-1])
	    # Use the integer version for PIL
	    width=string.atoi(width)
	    im.thumbnail((width,width), Image.ANTIALIAS)
	    im.save(os.path.join(Globs.gallerytempdir,prefix)+'.'+basename+'.jpg','JPEG')

    if (not pid) and (not pid2):
	# Release the lock file when finished
	os.unlink(Globs.attachmentdir+'/tmp.lock')

    # We have built thumbnails so we can deposit an indicator file to prevent rebuilding next time
    if not os.path.isfile(Globs.attachmentdir+'/delete.me.to.regenerate.thumbnails.and.webnails'):
        open(Globs.attachmentdir+'/delete.me.to.regenerate.thumbnails.and.webnails','w').close()


def deletefromdisk(target):
    # Rotate the images
    # Don't go further if there is a lock in place
    if os.path.isfile(Globs.attachmentdir+'/tmp.lock'):
        message("I'm currently busy generating thumbnails and webnails. Please try your delete request again later.",0)
	return ''
    # Ok do the actual delete 
    if 1:
        # Delete first the temp dir webnail and thumbnail
	try:
	    os.unlink(Globs.gallerytempdir+'/tmp.webnail.'+target+'.jpg')
	except:
	    pass
	try:
	    os.unlink(Globs.gallerytempdir+'/tmp.thumbnail.'+target+'.jpg')
	except:
	    pass
	try:
	    os.unlink(Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg')
	except:
	    pass

	# Now delete the original (except we actually just move it to /tmp) 
	# TODO: insert a random number in the destination filename to cope with deleting files with same name
	origfn=Globs.originals[target]
	try:
	    #shutil.copy(Globs.attachmentdir+'/'+origfn,'/tmp/deleted.'+origfn)
	    shutil.copy(Globs.attachmentdir+'/'+origfn,'/tmp/'+origfn)
	    os.unlink(Globs.attachmentdir+'/'+origfn)
	except:
	    pass
	try:
	    #shutil.copy(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt','/tmp/deleted.tmp.annotation.'+target+'.txt')
	    shutil.copy(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt','/tmp/tmp.annotation.'+target+'.txt')
	    os.unlink(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt')
	except:
	    pass

def rotate(target,direction):
    # Rotate the images
    # Don't go further if there is a lock in place
    if os.path.isfile(Globs.attachmentdir+'/tmp.lock'):
        message("I'm currently busy generating thumbnails and webnails. Please try your rotate request again later.",0)
	return ''

    # Find the correct binary
    if not globals().has_key('Image'):
	if not os.path.isfile('/usr/bin/mogrify'):
	    if not os.path.isfile('/usr/X11R6/bin/mogrify'):
		message('<b>Please install ImageMagick so I can build thumbnails and webnails</b><p>',0)
		return
	    else:
		Globs.convertbin='/usr/X11R6/bin/mogrify'
	else:
	    Globs.convertbin='/usr/bin/mogrify'
    else:
        Globs.convertbin = 'Image'

    # Do the actual rotations
    if direction=='rotate right':
	degs='90'
    else:
	degs='270'
    if not Globs.convertbin == 'Image':
	convout=commands.getoutput(Globs.convertbin+' -rotate '+degs+' "'+Globs.gallerytempdir+'/tmp.webnail.'+target+'.jpg"')
	convout=commands.getoutput(Globs.convertbin+' -rotate '+degs+' "'+Globs.gallerytempdir+'/tmp.thumbnail.'+target+'.jpg"')
	# Don't bother rotating the original. Since we don't want to reduce its quality incase it is used for printing
	#if not os.path.isfile(Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg'):
	#    # Generate from original
	#    pathtooriginal=Globs.attachmentdir+'/'+Globs.originals[target]
	#    shutil.copy(pathtooriginal,Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg')
	#convout=commands.getoutput(Globs.convertbin+' -rotate '+degs+' "'+Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg"')
    else:
	# Use PIL  (strip off the "")
	if direction=='rotate right':
	    degs=270.0
	else:
	    degs=90.0
	im = os.path.join(Globs.gallerytempdir,'tmp.webnail.')+target+'.jpg'
	imw = Image.open(im)
	imw.rotate(degs).save(im,'JPEG')
	im = os.path.join(Globs.gallerytempdir,'tmp.thumbnail.')+target+'.jpg'
	imw = Image.open(im)
	imw.rotate(degs).save(im,'JPEG')
	imo = os.path.join(Globs.gallerytempdir,'tmp.rotated.')+target+'.jpg'
	if not os.path.isfile(Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg'):
	    # Generate from original
	    im=Globs.attachmentdir+'/'+Globs.originals[target]
	else:
	    im = imo
	imw = Image.open(im)
	imw.rotate(degs).save(imo,'JPEG')

def getannotation(target):
    # Annotations are stored as a file for now (later to be stored in images)
    atext=''
    if Globs.annotated.has_key(target):
        try:
	    atext=open(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt').readline()
	except:
	    atext=''
	message('was annotated')
    else:
	message('was not annotated')
    # replace double quotes with the html escape so quoted annotations appear
    return string.replace(atext,'"','&quot;')

def execute(macro, args):

    Globs.version=__version__

    # Containers
    formvals={}
    thumbnails={}
    webnails={}
    rotated={}
    try:
        import wikiconfig
    except:
        wikiconfig=''

    # Class variables need to be specifically set 
    # (except for the case where a value is to be shared with another Gallery macro on the same wiki page)
    Globs.originals={}
    Globs.annotated={}
    Globs.attachmentdir=''
    Globs.admin=''
    Globs.adminmsg=''
    Globs.pagename=''

    # process arguments
    if args:
	# Arguments are comma delimited key=value pairs
	sargs=string.split(args,',')
	for item in sargs:
	    sitem=string.split(item,'=')
	    if len(sitem)==2:
		key,value=sitem[0],sitem[1]
		if key=='thumbnailwidth':
		    Globs.thumbnailwidth=value
		elif key=='webnailwidth':
		    Globs.webnailwidth=value
		elif key=='numberofcolumns':
		    try:
			Globs.numberofcolumns=string.atoi(value)
		    except TypeError:
		        pass
		# Experimental, uncomment at own risk
		#elif key=='pagename':
		#    Globs.pagename=value

    transtable=mktrans()

    # Useful variables
    dontregen=''
    annotationmessage=''
    Globs.baseurl=macro.request.getBaseURL()+'/'
    if not Globs.pagename:
	#Globs.pagename = string.replace(macro.formatter.page.page_name,'/','_2f')
	Globs.pagename = macro.formatter.page.page_name
	# This fixes the subpages bug. subname is now used instead of pagename when creating certain urls
	Globs.subname = string.split(Globs.pagename,'/')[-1]
    # Hmmm. A bug in moinmoin? underscores are getting escaped. These doubly escaped pagenames are even appearing in data/pages
    try:
        # Try the old MoinMoin-1.2.x way first
        textdir=config.text_dir
        pagepath = string.replace(wikiutil.getPagePath(Globs.pagename),'_5f','_')
    except:
        pagepath = macro.formatter.page.getPagePath()
    Globs.attachmentdir = pagepath+'/attachments'
    Globs.galleryrotchar='?'
    if hasattr(macro,'cfg') and hasattr(macro.cfg,'gallerytempdir') and hasattr(macro.cfg,'gallerytempurl'):
	Globs.gallerytempdirroot=macro.cfg.gallerytempdir
	Globs.gallerytempdir=macro.cfg.gallerytempdir+'/'+Globs.pagename+'/'
	Globs.gallerytempurl=macro.cfg.gallerytempurl+'/'+Globs.pagename+'/'
    elif hasattr(wikiconfig,'gallerytempdir') and hasattr(wikiconfig,'gallerytempurl'):
        message('gallerytempdir and gallerytempurl found')
	Globs.gallerytempdirroot=wikiconfig.gallerytempdir
	Globs.gallerytempdir=wikiconfig.gallerytempdir+'/'+Globs.pagename+'/'
	Globs.gallerytempurl=wikiconfig.gallerytempurl+'/'+Globs.pagename+'/'
    elif hasattr(wikiconfig,'attachments'):
	Globs.gallerytempdirroot=wikiconfig.attachments['dir']
	Globs.gallerytempdir=wikiconfig.attachments['dir']+'/'+Globs.pagename+'/attachments/'
	Globs.gallerytempurl=wikiconfig.attachments['url']+'/'+Globs.pagename+'/attachments/'
	Globs.attachmentdir = Globs.gallerytempdir
    else:
	Globs.gallerytempdir=Globs.attachmentdir
	Globs.gallerytempurl=Globs.subname+'?action=AttachFile&amp;do=get&amp;target='
	# MoinMoin no longer allows us to use a ? to trigger a refetch, so we pass it a &
	Globs.galleryrotchar='&'
    if args:
        args=macro.request.getText(args)

    # HTML Constants
    tleft='<table><tr><td><center>'
    tmidd='</center></td><td><center>'
    trigh='</center></td></tr></table>\n'

    # Process any form items into a dictionary (values become unique)
    for item in macro.form.items():
        if not formvals.has_key(item[0]):
            # Here is where we clean the untrusted web input
	    # (sometimes we get foreign keys from moinmoin when the page is edited)
	    try:
		formvals[item[0]]=string.translate(item[1][0],transtable)
	    except AttributeError:
	        pass

    # Add this to the end of each URL to keep some versions of moinmoin happy 
    if formvals.has_key('action'):
        if formvals['action']=='content':
	    # translate content action to print action for now
	    Globs.bcomp='&action=print'
	else:
	    Globs.bcomp='&action='+formvals['action']
    else:
	Globs.bcomp='&action=show'

    # Figure out if we have delete privs
    try:
        # If a user can delete the page containing the Gallery, then they are considered a Gallery administrator
	# This probably should be configurable via a wikiconfig variable eg: galleryadminreq = <admin|delete|any>
        if macro.request.user.may.delete(macro.formatter.page.page_name):
            Globs.admin='true'
    except AttributeError:
        pass
    
    out=cStringIO.StringIO()

    # Grab a list of the files in the attachment directory
    if os.path.isdir(Globs.attachmentdir):
        if Globs.gallerytempdir==Globs.attachmentdir:
	    afiles=os.listdir(Globs.attachmentdir)
	else:
	    if not os.path.isdir(Globs.gallerytempdirroot):
	        message('You need to create the temp dir first:'+Globs.gallerytempdirroot,0)
		return macro.formatter.rawHTML(
		    Globs.adminmsg+'<p>')
	    if not os.path.isdir(Globs.gallerytempdir):
	        # Try to create it if it is absent
		spagename=string.split(Globs.pagename,'/')
		compbit=''
		for component in spagename:
		    compbit=compbit+'/'+component
                    try:
			# The following line stops an exception being raised when trying
			# to create a directory that already exists (thanks Magnus Wahrenberg)
			if not os.access(Globs.gallerytempdirroot+compbit, os.F_OK):
			    os.mkdir(Globs.gallerytempdirroot+compbit)

                    except:
			message('Please check permissions on temp dir:'+Globs.gallerytempdirroot,0)
			return macro.formatter.rawHTML(
			    Globs.adminmsg+'<p>')
                        
	    if os.path.isdir(Globs.gallerytempdir):
		afiles=os.listdir(Globs.attachmentdir)+os.listdir(Globs.gallerytempdir)
	    else:
	        message('You need to create the temp dir first:'+Globs.gallerytempdir,0)
		return macro.formatter.rawHTML(
		    Globs.adminmsg+'<p>')

	# Split out the thumbnails and webnails
	for item in afiles:
	    if item.startswith('tmp.thumbnail.'):
	        origname=item[14:-4]
		thumbnails[origname]=''
	    elif item.startswith('tmp.webnail.'):
	        origname=item[12:-4]
		webnails[origname]=''
	    elif item.startswith('tmp.rotated.'):
	        origname=item[12:-4]
		rotated[origname]=''
	    elif item.startswith('tmp.annotation.'):
	        origname=item[15:-4]
		Globs.annotated[origname]=''
	    elif item == 'delete.me.to.regenerate.thumbnails.and.webnails':
	        dontregen='true'
	    elif item == 'tmp.writetest' or item == 'tmp.lock':
	        pass
	    else:
	        # This must be one of the original images
		lastdot=string.rfind(item,'.')
		origname=item[:lastdot]
                ext = item[lastdot+1:]
                if string.lower(ext) not in Globs.allowedextensions:
                    continue
		Globs.originals[origname]=item
    else:
        message(version(),0)
	return macro.formatter.rawHTML( Globs.adminmsg )

    if not Globs.gallerytempdir==Globs.attachmentdir and os.path.isfile(Globs.attachmentdir+'/tmp.writetest'):
	    # If we are using the new gallerytempdir and we were using the old system then make sure there are no 
	    # remnant files from the old system in the attachment dir to confuse us
	    message('You have changed to using a gallerytempdir so I am cleaning old tmp files from your attachment dir.',0)
	    for item in webnails.keys():
	        try:
		    os.unlink(Globs.attachmentdir+'/tmp.webnail.'+item+'.jpg')
		except:
		    pass
	    # Try deleting any old thumbnails which may be in the attachment directory
            for item in thumbnails.keys():	
	        try:
		    os.unlink(Globs.attachmentdir+'/tmp.thumbnail.'+item+'.jpg')
		except:
		    pass
	    # Try deleting any old rotated originals which may be in the attachment directory
            for item in rotated.keys():	
	        try:
		    os.unlink(Globs.attachmentdir+'/tmp.rotated.'+item+'.jpg')
		except:
		    pass
	    os.unlink(Globs.attachmentdir+'/tmp.writetest')

    newnails=[]
    # Any thumbnails need to be built?
    for key in Globs.originals.keys():
        if (not thumbnails.has_key(key)) or (not dontregen):
	    # Create a thumbnail for this original
	    newnails.append((key,'tmp.thumbnail',Globs.thumbnailwidth))
    # Any webnails need to be built?
    for key in Globs.originals.keys():
        if (not webnails.has_key(key)) or (not dontregen):
	    # Create a webnail for this original
	    newnails.append((key,'tmp.webnail',Globs.webnailwidth))
    # Ok, lets build them all at once
    if not len(newnails)==0:
	buildnails(newnails)

    # If a regen of thumbnails and webnails has occurred, then we should also delete any tmp.rotated files.
    if not dontregen:
        for key in rotated.keys():
	    # Wrapped in a try except since child processes may try to unlink a second time
	    try:
		os.unlink(Globs.gallerytempdir+'/tmp.rotated.'+key+'.jpg')
	    except:
		pass

    if formvals.has_key('annotate'):
        if Globs.admin and formvals.has_key('target'):
	    target=formvals['target']
	    # Write an annotation file
	    atext=string.replace(formvals['annotate'],'"','&quot;')
	    ouf=open(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt','w')
	    ouf.write(atext)
	    ouf.close()
	    message('Annotation updated to <i>'+atext+'</i>',0)
	    # Now update the annotated dictionary
            if not Globs.annotated.has_key(target):
	        Globs.annotated[target]=''

    if formvals.has_key('webnail'):
	# Does the webnail exist?
        message('webnail requested')
	target=formvals['webnail']
	rotend=''
	if Globs.originals.has_key(target):
	    out.write(navibar(target,'webnail'))
	    out.write(toolbar(target,'webnail'))
	    if formvals.has_key('rotate'):
		direction=formvals['rotate']
		message(direction)
		rotate(target,direction)
		rotend=Globs.galleryrotchar+'rot='+repr(randint(1,10000))
	    if formvals.has_key('delete'):
	        message('Deleted <i>'+target+'</i>',0)
	        deletefromdisk(target)
	    # Put things in a table
	    out.write(tleft)
	    # Lets build up an image tag
	    out.write('<a href="'+Globs.baseurl+Globs.pagename+'?original='+target+'&action=content"><img src="'+Globs.gallerytempurl+'tmp.webnail.'+target+'.jpg'+rotend+'"></a>\n')
	    out.write(trigh)
	    out.write(tleft)

	    atext=getannotation(target)

	    # Are we an administrator?
	    if Globs.admin:
	        # We always provide an annotation text field
		out.write('<form action='+Globs.subname+' name=annotate METHOD=POST>')
		out.write('<input maxLength=256 size=55 name=annotate value="'+atext+'">')
		out.write('<input type=hidden value="'+target+'" name="target">')
		out.write('<input type=hidden value="show" name="action">')
		out.write('<input type=hidden value="'+target+'" name="webnail">')
		out.write('</form>')
	    else:
		out.write(atext)
	    out.write(trigh)
	    #out.write(toolbar(target,'webnail'))

	else:
	    message('I do not have file: '+target,0)
    elif formvals.has_key('original'):
	# Now we just construct a single item rather than a table
	# Does the webnail exist?
        message('original requested')
	target=formvals['original']
	rotend=''
	if not Globs.originals.has_key(target):
	    message('I do not have file: '+target,0)
	else:
	    if formvals.has_key('rotate'):
		direction=formvals['rotate']
		message(direction)
		rotate(target,direction)
		rotend=Globs.galleryrotchar+'rot='+repr(randint(1,10000))
		rotated[target]=''
	    # Lets build up an image tag
	    out.write(navibar(target,'original'))
	    out.write(tleft)
	    originalfilename=Globs.originals[target]
	    # If there is a rotated version, show that instead
	    if rotated.has_key(target):
		out.write('<a href="'+Globs.baseurl+Globs.pagename+'?webnail='+target+Globs.bcomp+'"><img src="'+Globs.gallerytempurl+'tmp.rotated.'+target+'.jpg'+rotend+'"></a>\n')
	    else:
		out.write('<a href="'+Globs.baseurl+Globs.pagename+'?webnail='+target+Globs.bcomp+'"><img src="'+Globs.subname+'?action=AttachFile&amp;do=get&amp;target='+originalfilename+'"></a>\n')
	    out.write(trigh)
	    out.write(tleft)

	    atext=getannotation(target)

	    # Are we an administrator?
	    if Globs.admin:
	        # We always provide an annotation text field
		out.write('<form action='+Globs.subname+' name=annotate METHOD=POST>')
		out.write('<input maxLength=256 size=55 name=annotate value="'+atext+'">')
		out.write('<input type=hidden value="'+target+'" name="target">')
		out.write('<input type=hidden value="show" name="action">')
		out.write('<input type=hidden value="'+target+'" name="original">')
		out.write('</form>')
	    else:
		out.write(atext)
	    out.write(trigh)
	    out.write(toolbar(target,'original'))

    elif formvals.has_key('rotate'):
        # We rotate all sizes of this image to the left or right
	message('rotate requested')
	target=formvals['target']
	direction=formvals['rotate']
	if not Globs.originals.has_key(target):
	    message('I do not have file: '+target,0)
	else:
	    # Do the rotation
	    rotate(target,direction)
            # Display the new image in webnail mode 
	    # We may need a way of forcing the browser to reload the newly rotated image here (later)
	    out.write(tleft)
	    out.write('<a href="'+Globs.baseurl+Globs.pagename+'?webnail='+target+Globs.bcomp+'"><img src="'+Globs.gallerytempurl+'tmp.webnail.'+target+'.jpg"></a>\n')
	    out.write(trigh)

    else:
	# Finally lets build a table of thumbnails
	thumbs=Globs.originals.keys()
	thumbs.sort()
	thumbs.reverse()
	# If version number is requested (append a ?version=tellme&action=show to the page request)
	# or if there are no original images, just give help message and return
	if formvals.has_key('version') or len(thumbs)==0:
	    message(version(),0)
	    return macro.formatter.rawHTML( Globs.adminmsg )
	out.write('\n<table>')
	cease=''
	rollover=''
	while 1:
	    out.write('<tr>')
	    for i in range(Globs.numberofcolumns):
		try:
		    item=thumbs.pop()
		except IndexError:
		    cease='true'
		    break

		# Alt text
		atext=getannotation(item)
		rollover='alt="'+atext+'" title="'+atext+'"'

		# Table entry for thumbnail image
		out.write('<td><a href="'+Globs.baseurl+Globs.pagename+'?webnail='+item+Globs.bcomp+'"><center><img src="'+Globs.gallerytempurl+'tmp.thumbnail.'+item+'.jpg" '+rollover+'></a></center></td>')
	    out.write('</tr>\n')
	    if cease:
		out.write('</table>')
		break
	
    out.seek(0)
    # Finally output any administrative messages at the top followed by any generated content
    return macro.formatter.rawHTML(
        Globs.adminmsg+'<p>'
        +out.read()	
    )

