#
# nysagallery class/function library
# Part of nysagallery
#
# Copyright (c) 2006, 2007 Stephen Williams, all rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the copyright holder may not be used to endorse or
# promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
require 'date'
require 'rexml/document'
module GalleryLib
RCS_ID = "$Id: gallery_lib.rb,v 1.6 2007/01/02 14:23:38 stephen Exp $"
URL_BASE = "/gallery/"
# Exception with HTTP status code
class NysaGalleryException < Exception
attr_reader :status
def initialize(msg, status)
super(msg)
@status = status
end
end
# Produces HTML formatting of a picture:
#
# *Title* (dimensions)
# /Added: date/
#
# Description
#
# "added" and "description" are omitted if absent
def GalleryLib.format_picture(gallery, section, picture)
url = format_url(gallery, section, picture)
heading = "
#{picture.title}"
heading << " (#{picture.width} x #{picture.height})"
heading << "
Added: #{picture.added}" if picture.added
heading << "
"
picture.description ? "#{heading}#{picture.description}
" : heading
end
# Produces HTML formatting of a thumbnail, linking to its
# picture or to the supplied URL
def GalleryLib.format_thumbnail(gallery, section, picture,
alt_text = picture.title,
url = nil)
gdir = gallery.directory ? gallery.directory + "/" : ""
sdir = section.directory ? section.directory + "/" : ""
url = format_url(gallery, section, picture) unless url
alt_text = CGI::escapeHTML(alt_text)
<
END
end
# Constructs a picture URL, including gallery and section
# directories if defined, suitable for use in an
# element
def GalleryLib.format_imgsrc_url(gallery, section, picture)
gdir = gallery.directory ? gallery.directory + "/" : ""
sdir = section.directory ? section.directory + "/" : ""
"#{Picture::RAW_URL_BASE}#{gdir}#{sdir}#{picture.filename}." +
"#{picture.format}"
end
# Constructs a picture URL, including gallery and section
# directories if defined.
#
# Picture URLs are of the form:
# URL_BASE/gallery_name,section_name,picture_filename
# so the web server needs to rewrite incoming requests thusly:
# URL_BASE/gallery_picture.rbx?gallery=gallery_name§ion=section_name& \
# picture=picture_filename
def GalleryLib.format_url(gallery, section, picture)
"#{URL_BASE}#{gallery.name},#{section.name},#{picture.filename}"
end
# Mangle RCS ID string to include linked filename
def GalleryLib.rcs_mangle(rcsid)
rcsmatch = /^(.Id: )([^,]*)(,v.*)$/.match(rcsid)
if rcsmatch
filename = rcsmatch[2]
filename2 = filename.tr('.', '_') + '.txt'
"#{rcsmatch[1]}#{filename}#{rcsmatch[3]}"
else
rcsid
end
end
# Hash subclass that preserves insertion order
class OHash < Hash
attr_reader :keys
def initialize
super
@keys = []
end
def []=(k, v)
super(k, v)
@keys.delete(k) if @keys.include?(k)
@keys << k
end
def delete(k)
@keys.delete(k) if @keys.include?(k)
super(k)
end
def each_key
@keys.each do |key|
yield key
end
end
def each
@keys.each do |key|
yield [key, self[key]]
end
end
def each_value
@keys.each do |key|
yield self[key]
end
end
def store(k, v)
self[k] = v
end
end
# Encapsulation of a gallery picture
class Picture
public
# Constants
RAW_URL_BASE = "http://nysa.cx/img/"
THUMBNAIL_URL_BASE = RAW_URL_BASE
URL_BASE = "http://nysa.cx/localimg,"
attr_reader :added, :description, :filename, :format, :height,
:thumbnail_filename, :thumbnail_height, :thumbnail_width,
:title, :width
# Constructor
def initialize(node)
parse(node)
end
private
# Constants
DEFAULT_FORMAT = "jpg"
DEFAULT_WIDTH = 640
DEFAULT_HEIGHT = 427
THUMBNAIL_WIDTH = 80
THUMBNAIL_HEIGHT = 53
def die(status, message)
raise NysaGalleryException.new(message, status)
end
# Parses picture information from an XML node
def parse(node)
@title = node.attributes["title"]
die("SERVER_ERROR", "Picture has no title") unless @title
@filename = node.attributes["filename"]
die("SERVER_ERROR", "Picture has no filename") unless @filename
@thumbnail_filename = @filename + ".jpg"
i = @thumbnail_filename.rindex('/')
if i
@thumbnail_filename.insert(i + 1, "thumb_")
else
@thumbnail_filename.insert(0, "thumb_")
end
element = node.elements["added"]
if element
@added = element.text
begin
@added = Date.parse(@added)
rescue
die("SERVER_ERROR", "Bad picture date: #{@added}")
end
end
attr = node.attributes["height"]
@height = attr ? attr.to_i : DEFAULT_HEIGHT
attr = node.attributes["width"]
@width = attr ? attr.to_i : DEFAULT_WIDTH
if @width >= @height
@thumbnail_height = THUMBNAIL_HEIGHT
@thumbnail_width = THUMBNAIL_WIDTH
else
@thumbnail_height = THUMBNAIL_WIDTH
@thumbnail_width = THUMBNAIL_HEIGHT
end
attr = node.attributes["format"]
@format = attr ? attr : DEFAULT_FORMAT
element = node.elements["desc"]
@description = element.text if element
end
end
# Encapsulation of a gallery section
class Section
attr_reader :description, :directory, :name, :pictures, :title
public
# Constructor
def initialize(node)
@node = node
parse(node)
end
# Picks a random picture from the section
def random_picture
elements = @node.get_elements("picture")
Picture.new(elements[rand(elements.size)])
end
private
def die(status, message)
raise NysaGalleryException.new(message, status)
end
# Parses section information from an XML node
def parse(node)
@name = node.attributes["name"]
die("SERVER_ERROR", "No section name") unless @name
@title = node.attributes["title"]
die("SERVER_ERROR", "No section title") unless @title
element = node.elements["desc"]
@description = element.text if element
element = node.elements["directory"]
@directory = element.text if element
@pictures = []
node.elements.each("picture") do |pic_element|
picture = Picture.new(pic_element)
@pictures << picture
end
end
end
# Encapsulation of a complete gallery
class Gallery
public
attr_reader :title, :created, :description, :directory, :name, :sections
# Constructor
def initialize(filename)
die("NOT_FOUND", "No such gallery") unless File.exists?(filename)
@name = /^(.*)\.xml$/.match(filename)[1]
File.open(filename) do |file|
@doc = REXML::Document.new(file)
end
@updated = nil
parse_basic
end
# Parses complete information about the gallery
def parse_sections
@sections = OHash.new
@doc.elements.each("gallery/section") do |sec_element|
section = Section.new(sec_element)
@sections[section.name] = section
end
end
# The number of pictures in the gallery
def picture_count
@doc.get_elements("gallery/section/picture").size
end
# Returns a random section from the gallery
def random_section
sections = @doc.get_elements("gallery/section")
Section.new(sections[rand(sections.size)])
end
# The date when the gallery was updated
def updated
unless @updated
@updated = @created
@doc.get_elements("gallery/section/picture/added").each do |element|
added = element.text
begin
added = Date.parse(added)
rescue
die("SERVER_ERROR", "Bad picture date: #{added}")
end
@updated = added if added > @updated
end
end
@updated
end
private
def die(status, message)
raise NysaGalleryException.new(message, status)
end
# Parses basic information about the gallery
def parse_basic
gal_element = @doc.elements["gallery"]
die("SERVER_ERROR", "Invalid gallery") unless gal_element
@title = gal_element.attributes["title"]
die("SERVER_ERROR", "Gallery has no title") unless @title
die("SERVER_ERROR", "Gallery has no sections") unless
gal_element.elements["section"]
element = gal_element.elements["created"]
@created = element.text if element
die("SERVER_ERROR", "Gallery has no creation date") unless @created
begin
@created = Date.parse(@created)
rescue
die("SERVER_ERROR", "Bad gallery date: #{@created}")
end
element = gal_element.elements["desc"]
@description = element.text if element
element = gal_element.elements["directory"]
@directory = element.text if element
end
end
end