lib/fiona7/scrivito_patches/binary.rb in infopark_fiona7-0.30.0.2 vs lib/fiona7/scrivito_patches/binary.rb in infopark_fiona7-0.70.0.1
- old
+ new
@@ -1,55 +1,474 @@
require 'scrivito/binary'
-module Scrivito
- # This class had been reworked to support uploads directly
- # into CMS and not into S3
- class Binary
- # new implementation
- def content_type
- if shadow_obj
- shadow_obj.mime_type
+require 'mini_magick'
+
+
+module Fiona7
+ module BinaryHandling
+ class ParamEncoder
+ def initialize
+ self.verifier = ActiveSupport::MessageVerifier.new(
+ Rails.application.secrets.secret_key_base, serializer: ::JSON)
end
- end
- def content_length
- if shadow_obj
- shadow_obj.body_length
- else
- 0
+ def encode(params)
+ self.verifier.generate(params)
end
- end
- def filename
- if shadow_obj
- shadow_obj.name + '.' + shadow_obj.file_extension
+ def decode(string)
+ self.verifier.verify(string)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature => e
+ {}
end
+
+ protected
+ attr_accessor :verifier
end
- # New API call. Allows to read the extension of the stored file.
- def file_extension
- if shadow_obj
- shadow_obj.file_extension
+ class MetaBinary
+ class ActualBinary
+ def initialize(binary_id, transformation=false)
+ self.binary_id = binary_id.to_i
+ self.transformation = transformation
+ self.obj = self.load_obj
+ end
+ protected
+ attr_accessor :binary_id, :transformation, :obj
+
+ def load_obj
+ if Fiona7.mode == :legacy
+ Fiona7::WriteObj.find(self.binary_id)
+ else
+ Fiona7::InternalReleasedObj.find(self.binary_id)
+ end
+ rescue ActiveRecord::RecordNotFound
+ nil
+ end
end
+
+ class UnmodifiedBinary < ActualBinary
+ def valid?
+ self.binary_id >= 0 && self.obj.try(:binary?)
+ end
+
+ def present?
+ !self.obj.nil?
+ end
+
+ def filename
+ self.obj.filename
+ end
+
+ def filepath
+ self.obj.body_data_path
+ end
+
+ def length
+ self.obj.body_length
+ end
+
+ def width
+ if self.image
+ self.image[:width]
+ else
+ 0
+ end
+ end
+
+ def height
+ if self.image
+ self.image[:height]
+ else
+ 0
+ end
+ end
+
+ def mime_type
+ self.obj.mime_type
+ end
+
+ def last_changed
+ self.obj.last_changed.utc
+ end
+
+ protected
+ def image
+ return @image if defined?(@image)
+ if self.filepath
+ @image = MiniMagick::Image.new(self.filepath)
+ else
+ @image = nil
+ end
+ end
+ end
+
+ class TransformedBinary < UnmodifiedBinary
+ def valid?
+ super && valid_transformation?
+ end
+
+ def filename
+ ::File.basename(self.transformed_filepath)
+ end
+
+ alias_method :original_filepath, :filepath
+ def filepath
+ self.transformed_filepath
+ end
+
+ def legnth
+ ::File.size(self.transformed_filepath)
+ end
+
+ def last_changed
+ ::File.mtime(self.transformed_filepath).utc
+ end
+
+ protected
+ def valid_transformation?
+ true &&
+ (self.mime_type =~ /image\//) &&
+ (self.width.present? || self.height.present?) &&
+ (!self.width.present? || (1..4096).include?(self.width.to_i)) &&
+ (!self.height.present? || (1..4096).include?(self.height.to_i)) &&
+ ((1..75).include?(self.quality.to_i)) &&
+ (self.fit == 'clip' || self.fit == 'crop') &&
+ (self.width.to_i + self.height.to_i < 4096) &&
+ (self.fit == 'clip' || (self.width.present? && self.height.present?))
+ end
+
+ def transformed_filepath
+ return @transformed_filepath if @transformed_filepath
+ return nil if self.original_filepath.nil?
+
+ output_filepath = original_filepath + self.transformed_filename
+
+ if ::File.exists?(output_filepath)
+ Rails.logger.debug("Transformed image #{output_filepath} already generated, serving")
+ return @transformed_filepath = output_filepath
+ else
+ Rails.logger.debug("Transforming image")
+ end
+
+ image = MiniMagick::Image.open(original_filepath)
+
+ if self.fit == 'clip'
+ image.combine_options do |b|
+ b.resize "#{self.width}x#{self.height}>"
+ end
+ elsif self.fit == 'crop'
+ image.combine_options do |b|
+ b.resize "#{self.width}x#{self.height}>"
+ b.gravity "center"
+ b.extent "#{self.width}x#{self.height}>"
+ end
+ else
+ raise 'invalid fit'
+ end
+
+ image.write(output_filepath)
+ @transformed_filepath = output_filepath
+ end
+
+ def transformed_filename
+ ext = ::File.extname(self.original_filepath)
+ "#{self.fit}_#{self.width}_#{self.height}_#{self.quality}#{ext}"
+ end
+
+ def transformation_with_fallback
+ @transformation_with_fallback ||= (self.transformation || {}).with_indifferent_access
+ end
+
+ def width
+ self.transformation_with_fallback[:width].to_s
+ end
+
+ def height
+ self.transformation_with_fallback[:height].to_s
+ end
+
+ def quality
+ self.transformation_with_fallback[:quality] || '75'
+ end
+
+ def fit
+ self.transformation_with_fallback[:fit] || 'clip'
+ end
+ end
+
+ delegate :valid?, :present?,
+ :filename, :filepath,
+ :mime_type, :length, :last_changed,
+ :width, :height,
+ :to => :implementation
+
+ def initialize(binary_id, transformation=false)
+ if transformation
+ self.implementation = TransformedBinary.new(binary_id, transformation)
+ else
+ self.implementation = UnmodifiedBinary.new(binary_id, transformation)
+ end
+ end
+
+ protected
+ attr_accessor :implementation
+
end
- # New API call. Allows to access the contents of the blob.
- def content
- if shadow_obj
- shadow_obj.body
+ module DeliveryMixin
+ # GET
+ def show
+ binary_id = binary_id_from_params
+ transformation = transformation_from_params
+ meta_binary = MetaBinary.new(binary_id, transformation)
+
+ if !meta_binary.present?
+ not_found
+ elsif !meta_binary.valid?
+ bad_request
+ elsif stale?(:last_modified => meta_binary.last_changed) && true
+ filename = meta_binary.filename
+ filepath = meta_binary.filepath
+ mime_type = meta_binary.mime_type
+
+ send_file(File.expand_path(filepath), {
+ :type => mime_type,
+ :filename => filename,
+ :disposition => 'inline'
+ })
+ end
end
+
+ # HEAD
+ def query
+ binary_id = binary_id_from_params
+ transformation = transformation_from_params
+ meta_binary = BinaryHandling::MetaBinary.new(binary_id, transformation)
+
+ if !meta_binary.valid?
+ bad_request
+ elsif !meta_binary.present?
+ not_found
+ else
+ set_header('Content-Type', meta_binary.mime_type)
+ set_header('Content-Length', meta_binary.length)
+ set_header('Cache-Control', 'no-transform,public,max-age=300,s-maxage=900')
+ head_ok
+ end
+ end
+
+ protected
+ def bad_request
+ head 400
+ end
+
+ def not_found
+ head 404
+ end
+
+ def head_ok
+ head 200
+ end
end
- def url
- ActiveSupport::Deprecation.warn("This method should never be called. Use scrivito_path or scrvito_url whenever possible")
- if shadow_obj
- Rails.application.routes.url_helpers.fiona7_blob_path(id: @id, name: self.filename)
+
+ class EmbeddedServer
+ class << self
+ attr_accessor :enable
end
+
+ self.enable = false
+
+ HOST = 'localhost'
+ PORT = 7104
+ extend MonitorMixin
+
+ require 'webrick'
+
+ class BinaryServerlet < ::WEBrick::HTTPServlet::AbstractServlet
+ class RequestHandler
+
+ include Fiona7::BinaryHandling::DeliveryMixin
+
+ def initialize(request, response)
+ self.request = request
+ self.response = response
+ end
+
+ protected
+ attr_accessor :request, :response
+ def binary_id_from_params
+ # TODO: handle malformed input
+ match = /\/_b\/([0-9]+)(\/(.*)(\.[a-z0-9A-Z]+)?)?/.match(self.request.request_uri.to_s)
+ if match
+ match[1]
+ else
+ 0
+ end
+ end
+
+ def transformation_from_params
+ if self.request.query_string
+ ParamEncoder.new.decode(
+ ::CGI.parse(self.request.query_string)["t"]
+ )
+ end
+ end
+
+ def set_header(name, value)
+ self.response[name] = value
+ end
+
+ # minimal stubs for Rails API below
+
+ def stale?(*args)
+ true
+ end
+
+ def head(status)
+ self.response.status = status
+ end
+
+ def send_file(filepath, options={})
+ self.response['Content-Type'] = options[:type]
+ self.response['Content-Length'] = ::File.size(filepath)
+ self.response['Content-Disposition'] = "#{options[:disposition]}; filename=\"#{options[:filename]}\""
+ self.response.body = File.read(filepath)
+ self.response.status = 200
+ end
+ end
+
+ def do_GET(request, response)
+ RequestHandler.new(request, response).show
+ end
+
+ def do_HEAD(request, response)
+ RequestHandler.new(request, response).query
+ end
+ end
+
+ def self.running_instance
+ if !self.enable
+ return HOST, PORT
+ end
+
+ self.synchronize do
+ @server_thread ||= self.wait_for_server
+
+ Kernel.at_exit do
+ Process.kill("INT", @server_thread)
+ Process.kill("KILL", @server_thread)
+ end
+
+ return HOST, PORT
+ end
+ end
+
+ def self.wait_for_server
+ pid = Process.fork do
+ self.run_server
+ end
+ # wait for webrick!
+ sleep 1
+ pid
+ end
+
+ def self.run_server
+
+ server = ::WEBrick::HTTPServer.new({
+ :Port => PORT, :DocumentRoot => '/dev/null',
+ #Logger: WEBrick::Log.new(Logger.new(nil)), AccessLog: []
+ })
+
+ server.mount '/', BinaryServerlet
+
+ Signal.trap('INT') do
+ server.shutdown
+ exit!
+ end
+
+ server.start
+ end
end
- private
- def shadow_obj
- @shadow_obj ||= Fiona7::InternalReleasedObj.find(@id.to_i) if @id.to_i != 0
+ class UrlGenerator
+ attr_reader :blob_id, :access_type, :verb, :transformation
+ def initialize(blob_id, access_type, verb, transformation)
+ self.blob_id = blob_id
+ self.access_type = access_type
+ self.verb = verb
+ self.transformation = transformation
+ end
+
+ def generate
+ if server_detected?
+ hosted_server_url
+ else
+ embedded_server_url
+ end
+ end
+
+ protected
+ attr_writer :blob_id, :access_type, :verb, :transformation
+ def blob
+ if Fiona7.mode == :legacy
+ @blob ||= Fiona7::WriteObj.find(blob_id.to_i)
+ else
+ @blob ||= Fiona7::InternalReleasedObj.find(blob_id.to_i)
+ end
+ end
+
+ def filename
+ self.blob.filename
+ rescue ActiveRecord::RecordNotFound
+ "null.null"
+ end
+
+ def server_detected?
+ Fiona7::Middleware::ServerDetectionMiddleware.server_detected?
+ end
+
+ def hosted_server_url
+ # TODO: use middleware for storing url
+ host = Fiona7::Middleware::ServerDetectionMiddleware.server_name
+ port = Fiona7::Middleware::ServerDetectionMiddleware.server_port
+
+ generate_url(host, port)
+ end
+
+ def embedded_server_url
+ host, port = EmbeddedServer.running_instance
+ generate_url(host, port)
+ end
+
+ def generate_url(host, port=80)
+ options = {}
+ options[:host] = host
+ options[:port] = port
+ options[:path] = "/_b/#{self.blob_id}/#{self.filename}"
+ if self.transformation.present?
+ self.validate_transformation!
+ options[:query] = self.encode_query_string
+ end
+ URI::HTTP.build(options).to_s
+ end
+
+ def encode_query_string
+ token = ParamEncoder.new.encode(self.transformation)
+ return "t=#{token}"
+ end
+
+ def validate_transformation!
+ MetaBinary.new(self.blob_id, self.transformation).valid? || raise(Scrivito::TransformationDefinitionError.new("Invalid transformation", "binary.unprocessable.image.transform.invalid_config"))
+ end
end
+ end
+end
+
+module Scrivito
+ # This class had been reworked to support uploads directly
+ # into CMS and not into S3
+ class Binary
end
end