lib/httpimagestore/configuration/handler.rb in httpimagestore-1.2.0 vs lib/httpimagestore/configuration/handler.rb in httpimagestore-1.3.0

- old
+ new

@@ -1,6 +1,8 @@ require 'mime/types' +require 'digest/sha2' +require 'securerandom' module Configuration class ImageNotLoadedError < ConfigurationError def initialize(image_name) super "image '#{image_name}' not loaded" @@ -11,11 +13,35 @@ def initialize super 'empty body - expected image data' end end - class RequestState + class VariableNotDefinedError < ConfigurationError + def initialize(name) + super "variable '#{name}' not defined" + end + end + + class NoRequestBodyToGenerateMetaVariableError < ConfigurationError + def initialize(meta_value) + super "need not empty request body to generate value for '#{meta_value}'" + end + end + + class NoVariableToGenerateMetaVariableError < ConfigurationError + def initialize(value_name, meta_value) + super "need '#{value_name}' variable to generate value for '#{meta_value}'" + end + end + + class NoImageDataForVariableError < ConfigurationError + def initialize(image_name, meta_value) + super "image '#{image_name}' does not have data for variable '#{meta_value}'" + end + end + + class RequestState < Hash include ClassLogging class Images < Hash def initialize(memory_limit) @memory_limit = memory_limit @@ -33,35 +59,100 @@ fetch(name){|image_name| raise ImageNotLoadedError.new(image_name)} end end def initialize(body = '', matches = {}, path = '', query_string = {}, memory_limit = MemoryLimit.new) - @locals = {} - @locals.merge! query_string - @locals[:path] = path - @locals.merge! matches - @locals[:query_string_options] = query_string.sort.map{|kv| kv.join(':')}.join(',') - log.debug "processing request with body length: #{body.bytesize} bytes and locals: #{@locals} " + super() do |request_state, name| + # note that request_state may be different object when useing with_locals that creates duplicate + request_state[name] = request_state.generate_meta_variable(name) or raise VariableNotDefinedError.new(name) + end - @locals[:_body] = body + merge! query_string + self[:path] = path + merge! matches + self[:query_string_options] = query_string.sort.map{|kv| kv.join(':')}.join(',') + log.debug "processing request with body length: #{body.bytesize} bytes and variables: #{self} " + + @body = body @images = Images.new(memory_limit) @memory_limit = memory_limit @output_callback = nil end + attr_reader :body attr_reader :images - attr_reader :locals attr_reader :memory_limit + def with_locals(locals) + log.debug "using additional local variables: #{locals}" + self.dup.merge!(locals) + end + def output(&callback) @output_callback = callback end def output_callback @output_callback or fail 'no output callback' end + + def fetch_base_variable(name, base_name) + fetch(base_name, nil) or generate_meta_variable(base_name) or raise NoVariableToGenerateMetaVariableError.new(base_name, name) + end + + def generate_meta_variable(name) + log.debug "generating meta variable: #{name}" + val = case name + when :basename + path = Pathname.new(fetch_base_variable(name, :path)) + path.basename(path.extname).to_s + when :dirname + Pathname.new(fetch_base_variable(name, :path)).dirname.to_s + when :extension + Pathname.new(fetch_base_variable(name, :path)).extname.delete('.') + when :digest # deprecated + @body.empty? and raise NoRequestBodyToGenerateMetaVariableError.new(name) + Digest::SHA2.new.update(@body).to_s[0,16] + when :input_digest + @body.empty? and raise NoRequestBodyToGenerateMetaVariableError.new(name) + Digest::SHA2.new.update(@body).to_s[0,16] + when :input_sha256 + @body.empty? and raise NoRequestBodyToGenerateMetaVariableError.new(name) + Digest::SHA2.new.update(@body).to_s + when :input_image_width + @images['input'].width or raise NoImageDataForVariableError.new('input', name) + when :input_image_height + @images['input'].height or raise NoImageDataForVariableError.new('input', name) + when :input_image_mime_extension + @images['input'].mime_extension or raise NoImageDataForVariableError.new('input', name) + when :image_digest + Digest::SHA2.new.update(@images[fetch_base_variable(name, :image_name)].data).to_s[0,16] + when :image_sha256 + Digest::SHA2.new.update(@images[fetch_base_variable(name, :image_name)].data).to_s + when :mimeextension # deprecated + image_name = fetch_base_variable(name, :image_name) + @images[image_name].mime_extension or raise NoImageDataForVariableError.new(image_name, name) + when :image_mime_extension + image_name = fetch_base_variable(name, :image_name) + @images[image_name].mime_extension or raise NoImageDataForVariableError.new(image_name, name) + when :image_width + image_name = fetch_base_variable(name, :image_name) + @images[image_name].width or raise NoImageDataForVariableError.new(image_name, name) + when :image_height + image_name = fetch_base_variable(name, :image_name) + @images[image_name].height or raise NoImageDataForVariableError.new(image_name, name) + when :uuid + SecureRandom.uuid + end + if val + log.debug "generated meta variable '#{name}': #{val}" + else + log.debug "could not generated meta variable '#{name}'" + end + val + end end module ImageMetaData attr_accessor :source_path attr_accessor :source_url @@ -73,18 +164,18 @@ mime = MIME::Types[mime_type].first mime.extensions.select{|e| e.length == 3}.first or mime.extensions.first end end - class Image < Struct.new(:data, :mime_type) + class Image < Struct.new(:data, :mime_type, :width, :height) include ImageMetaData end class InputSource def realize(request_state) - request_state.locals[:_body].empty? and raise ZeroBodyLengthError - request_state.images['input'] = Image.new(request_state.locals[:_body]) + request_state.body.empty? and raise ZeroBodyLengthError + request_state.images['input'] = Image.new(request_state.body) end end class OutputOK def realize(request_state) @@ -100,11 +191,11 @@ @template = RubyStringTemplate.new(template) if template end def included?(request_state) return true if not @template - @template.render(request_state.locals).split(',').include? @value + @template.render(request_state).split(',').include? @value end end module ConditionalInclusion def inclusion_matcher(matcher) @@ -127,12 +218,15 @@ include ConditionalInclusion def initialize(global, image_name, matcher) @global = global @image_name = image_name - @locals = {imagename: @image_name} + @locals = {} + inclusion_matcher matcher + local :imagename, @image_name # deprecated + local :image_name, @image_name end private attr_accessor :image_name @@ -141,11 +235,11 @@ @locals[name] = value end def rendered_path(request_state) path = @global.paths[@path_spec] - Pathname.new(path.render(@locals.merge(request_state.locals))).cleanpath.to_s + Pathname.new(path.render(request_state.with_locals(@locals))).cleanpath.to_s end def put_sourced_named_image(request_state) rendered_path = rendered_path(request_state) @@ -155,12 +249,10 @@ request_state.images[@image_name] = image end def get_named_image_for_storage(request_state) image = request_state.images[@image_name] - local :mimeextension, image.mime_extension - rendered_path = rendered_path(request_state) image.store_path = rendered_path yield @image_name, image, rendered_path end @@ -191,11 +283,12 @@ handler_configuration = Struct.new( :global, :http_method, :uri_matchers, - :image_sources, + :sources, + :processors, :stores, :output ).new handler_configuration.global = configuration @@ -243,17 +336,18 @@ Matcher.new(nil) do Regexp.escape(matcher) end end end - handler_configuration.image_sources = [] + handler_configuration.sources = [] + handler_configuration.processors = [] handler_configuration.stores = [] handler_configuration.output = nil node.grab_attributes if handler_configuration.http_method != 'get' - handler_configuration.image_sources << InputSource.new + handler_configuration.sources << InputSource.new end configuration.handlers << handler_configuration self.new(handler_configuration).parse(node)