# # 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) <#{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