module OBF::Utils

Public Class Methods

as_progress_percent(a, b, &block) click to toggle source
# File lib/obf/utils.rb, line 498
def self.as_progress_percent(a, b, &block)
  if Object.const_defined?('Progress') && Progress.respond_to?(:as_percent)
    Progress.as_percent(a, b, &block)
  else
    block.call
  end
end
build_zip(dest_path=nil, &block) click to toggle source
# File lib/obf/utils.rb, line 481
def self.build_zip(dest_path=nil, &block)
  require 'zip'
  
  if !dest_path
    dest_path = OBF::Utils.temp_path(['archive', '.obz'])
  end
  Zip::File.open(dest_path, Zip::File::CREATE) do |zipfile|
    block.call(Zipper.new(zipfile))
  end
end
extension_for(content_type) click to toggle source
# File lib/obf/utils.rb, line 48
def self.extension_for(content_type)
  type = MIME::Types[content_type]
  type = type && type[0]
  extension = ""
  if type.respond_to?(:preferred_extension)
    extension = ("." + type.preferred_extension) if type.preferred_extension
  elsif type.respond_to?(:extensions)
    extension = ("." + type.extensions[0]) if type && type.extensions && type.extensions.length > 0
  end
  extension
end
fix_color(str, type='hex') click to toggle source
# File lib/obf/utils.rb, line 291
def self.fix_color(str, type='hex')
  lookup = str + "::" + type
  @@colors ||= {}
  return @@colors[lookup] if @@colors[lookup]
  path = File.dirname(File.dirname(__FILE__)) + '/tinycolor_convert.js'
  color = `node #{path} "#{str}" #{type}`.strip
  @@colors[lookup] = color
  color
end
get_url(url, hydra_wait=false) click to toggle source
# File lib/obf/utils.rb, line 3
def self.get_url(url, hydra_wait=false)
  return nil unless url
  res = {}
  content_type = nil
  data = nil
  if url.match(/^data:/)
    content_type = url.split(/;/)[0].split(/:/)[1]
    data = Base64.strict_decode64(url.split(/\,/, 2)[1])
  else
    uri = url.match(/\%/) ? url : URI.escape(url)
    uri = self.sanitize_url(uri)
    if hydra_wait
      req = Typhoeus::Request.new(uri, followlocation: true)
      req.on_complete do |response|
        if response.success?
          res.delete('request')
          res['content_type'] = response.headers['Content-Type']
          res['data'] = response.body
          res['extension'] = extension_for(res['content_type'])
        else
          OBF::Utils.log("  FAILED TO RETRIEVE #{uri.to_s} #{response.code}")
        end
      end
      res['request'] = req
    else
      req = Typhoeus.get(uri, followlocation: true)
      content_type = req.headers['Content-Type']
      OBF::Utils.log("  FAILED TO RETRIEVE #{uri.to_s} #{req.code}") unless req.success?
      data = req.body if req.success?
    end
  end
  res['content_type'] = content_type
  res['data'] = data
  res['extension'] = extension_for(content_type) if content_type
  if res['request']
    if hydra_wait
      # do nothing
    else
      res['request'].run
      res.delete('request')
    end
  end
  res
end
hydra() click to toggle source
# File lib/obf/utils.rb, line 162
def self.hydra
  Typhoeus::Hydra.new(max_concurrency: 10)
end
identify_file(path) click to toggle source
# File lib/obf/utils.rb, line 69
def self.identify_file(path)
  # TODO: .c4v files are sqlite databases that can be converted
  # based on your munger.rb code (text-only)
  name = File.basename(path) rescue nil
  if name.match(/\.obf$/)
    return :obf
  elsif name.match(/\.obz$/)
    return :obz
  elsif name.match(/\.avz$/)
    return :avz
  else
    json = JSON.parse(File.read(path)) rescue nil
    if json
      if json.is_a?(Hash)
        if json['format'] && json['format'].match(/^open-board-/)
          return :obf
        end
        return :json_not_obf
      else
        return :json_not_object
      end
    end
    
    begin
      plist = CFPropertyList::List.new(:file => path) rescue nil
      plist_data = CFPropertyList.native_types(plist.value) rescue nil
      if plist_data
        if plist_data['$objects'] && plist_data['$objects'].any?{|o| o['$classname'] == 'SYWord' }
          return :sfy
        end
        return :unknown
      end
    rescue CFFormatError => e
    end
    
    xml = Nokogiri::XML(File.open(path)) rescue nil
    if xml && xml.children.length > 0
      if xml.children[0].name == 'sensorygrid'
        return :sgrid
      end
      return :unknown
    end

    begin
      type = nil
      load_zip(path) do |zipper|
        if zipper.glob('manifest.json').length > 0
          json = JSON.parse(zipper.read('manifest.json')) rescue nil
          if json['root'] && json['format'] && json['format'].match(/^open-board-/)
            type = :obz
          end
        end
        if !type && zipper.glob('*.js').length > 0
          json = JSON.parse(zipper.read('*.js')) rescue nil
          if json['locale'] && json['sheets']
            type = :picto4me
          end
        end
      end
      return type if type
    rescue Zip::Error => e
    end
  end
  return :unknown
end
image_attrs(path, extension='') click to toggle source
# File lib/obf/utils.rb, line 378
def self.image_attrs(path, extension='')
  res = {}
  if path && path.match(/^data:/)
    res['content_type'] = path.split(/;/)[0].split(/:/)[1]
    raw = Base64.strict_decode64(path.split(/\,/, 2)[1])
    file = Tempfile.new(['file', extension])
    path = file.path
    file.binmode
    file.write raw
    file.close
  else
    is_file = File.exist?(path) rescue false
    if !is_file
      file = Tempfile.new(['file', extension])
      file.binmode
      file.write path
      path = file.path
      file.close
    end
  end
  OBF::Utils.log "file not found, #{path}" if !File.exist?(path)
  data = `identify -verbose #{path}`
  data.split(/\n/).each do |line|
    pre, post = line.sub(/^\s+/, '').split(/:\s/, 2)
    if pre == 'Geometry'
      match = post.match(/(\d+)x(\d+)/)
      if match && match[1] && match[2]
        res['width'] = match[1].to_i
        res['height'] = match[2].to_i
      end
    elsif pre == 'Mime type'
      res['content_type'] = post
    end
  end
  if res['content_type'] && res['content_type'].match(/^image\/svg/)
    res['width'] ||= 300
    res['height'] ||= 300
  end
  res
end
image_base64(url) click to toggle source
# File lib/obf/utils.rb, line 141
def self.image_base64(url)
  image = nil
  if url.match(/:\/\//)
    image = get_url(url)
  else
    types = MIME::Types.type_for(url)
    image = {
      'data' => File.read(url),
      'content_type' => types[0] && types[0].to_s
    }
    if !image['content_type']
      attrs = image_attrs(url)
      image['content_type'] ||= attrs['content_type']
    end
  end
  return nil unless image && image['data']
  str = "data:" + image['content_type']
  str += ";base64," + Base64.strict_encode64(image['data'])
  str
end
image_raw(url) click to toggle source
# File lib/obf/utils.rb, line 135
def self.image_raw(url)
  image = get_url(url)
  return nil unless image
  image
end
load_zip(path, &block) click to toggle source
# File lib/obf/utils.rb, line 473
def self.load_zip(path, &block)
  require 'zip'

  Zip::File.open(path) do |zipfile|
    block.call(Zipper.new(zipfile))
  end
end
log(str) click to toggle source
# File lib/obf/utils.rb, line 465
def self.log(str)
  if defined?(Rails)
    Rails.logger.info(str)
  else
    puts str
  end
end
obf_shell() click to toggle source
# File lib/obf/utils.rb, line 276
def self.obf_shell
  {
    'format' => 'open-board-0.1',
    'license' => {'type' => 'private'},
    'buttons' => [],
    'grid' => {
      'rows' => 0,
      'columns' => 0,
      'order' => [[]]
    },
    'images' => [],
    'sounds' => []
  }
end
parse_grid(pre_grid) click to toggle source
# File lib/obf/utils.rb, line 360
def self.parse_grid(pre_grid)
  pre_grid ||= {}
  grid = {
    'rows' => pre_grid['rows'] || 1,
    'columns' => pre_grid['columns'] || 1,
    'order' => pre_grid['order'] || [[nil]]
  }
  # TODO: parse order better
  grid
end
parse_license(pre_license) click to toggle source
# File lib/obf/utils.rb, line 347
def self.parse_license(pre_license)
  pre_license = {} unless pre_license.is_a?(Hash)
  license = {}
  ['type', 'copyright_notice_url', 'source_url', 'author_name', 'author_url', 'author_email', 'uneditable'].each do |attr|
    license[attr] = pre_license[attr] if pre_license[attr] != nil
  end
  license['type'] ||= 'private'
  license['copyright_notice_url'] ||= license['copyright_notice_link'] if license.key?('copyright_notice_link')
  license['source_url'] ||= license['source_link'] if license.key?('source_link')
  license['author_url'] ||= license['author_link'] if license.key?('author_link')
  license
end
parse_obf(obj, opts=nil) click to toggle source
# File lib/obf/utils.rb, line 301
def self.parse_obf(obj, opts=nil)
  opts ||= {}
  json = obj
  if obj.is_a?(String)
    json = JSON.parse(obj)
  end
  if opts['manifest']
    (json['buttons'] || []).each do |button|
      if button['image_id']
        # find image in list, if it has an id but no path, use the path from the manifest
        image = (json['images'] || []).detect{|i| i['id'] == button['image_id'] }
        if image && !image['path'] && !image['data'] && opts['manifest'] && opts['manifest']['paths'] && opts['manifest']['paths']['images'] && opts['manifest']['paths']['images'][button['image_id']]
          image['path'] = opts['manifest']['paths']['images'][button['image_id']]
        end
      end
      if button['sound_id']
        # find sound in list, if it has an id but no path, use the path from the manifest
        sound = (json['sounds'] || []).detect{|s| s['id'] == button['sound_id'] }
        if sound && !sound['path'] && !sound['data'] && opts['manifest'] && opts['manifest']['paths'] && opts['manifest']['paths']['sounds'] && opts['manifest']['paths']['sounds'][button['sound_id']]
          sound['path'] = opts['manifest']['paths']['sounds'][button['sound_id']]
        end
      end
    end
  end
  ['images', 'sounds', 'buttons'].each do |key|
    json["#{key}_hash"] = json[key]
    if json[key].is_a?(Array)
      hash = {}
      json[key].compact.each do |item|
        hash[item['id']] = item
      end
      json["#{key}_hash"] = hash
    elsif json["#{key}_hash"]
      array = []
      json["#{key}_hash"].each do |id, item|
        if item
          item['id'] ||= id
          array << item
        end
      end
      json[key] = array
    end
  end
  json
end
sanitize_url(url) click to toggle source
# File lib/obf/utils.rb, line 60
def self.sanitize_url(url)
  uri = URI.parse(url) rescue nil
  return nil unless uri && uri.host
  return nil if (!defined?(Rails) || !Rails.env.development?) && (uri.host.match(/^127/) || uri.host.match(/localhost/) || uri.host.match(/^0/) || uri.host.to_s == uri.host.to_i.to_s)
  port_suffix = ""
  port_suffix = ":#{uri.port}" if (uri.scheme == 'http' && uri.port != 80)
  "#{uri.scheme}://#{uri.host}#{port_suffix}#{uri.path}#{uri.query && "?#{uri.query}"}"
end
save_image(image, zipper=nil, background=nil) click to toggle source
# File lib/obf/utils.rb, line 166
  def self.save_image(image, zipper=nil, background=nil)
    if image['data']
      if !image['content_type']
        image['content_type'] = image['data'].split(/;/)[0].split(/:/)[1]
      end
    elsif image['raw_data']
      # already processed
    elsif image['path'] && zipper
      image['raw_data'] = zipper.read(image['path'])
      if !image['content_type']
        types = MIME::Types.type_for(image['path'])
        image['content_type'] = types[0] && types[0].to_s
      end
    elsif image['url']
      OBF::Utils.log "  retrieving #{image['url']}"
      url_data = get_url(image['url'])
      OBF::Utils.log "  done!"
      image['raw_data'] = url_data['data']
      image['content_type'] = url_data['content_type']
    elsif image['symbol']
      # not supported
    end
    type = MIME::Types[image['content_type']]
    type = type && type[0]
    extension = nil
    if type.respond_to?(:preferred_extension)
      extension = type && ("." + type.preferred_extension)
    elsif type.respond_to?(:extensions)
      extension = type && ("." + type.extensions.first)
    end
    file = Tempfile.new(["image_stash", extension.to_s])
    file.binmode
    if image['data']
      str = Base64.strict_decode64(image['data'].split(/\,/, 2)[1])
      file.write str
    elsif image['raw_data']
      file.write image['raw_data']
    else
      file.close
      return nil
    end
    file.close
    if extension && ['image/jpeg', 'image/jpg'].include?(image['content_type']) && image['width'] && image['width'] < 1000 && image['width'] == image['height']
      # png files need to be converted to make sure they don't have a transparent bg, or
      # else performance takes a huge hit.
      `cp #{file.path} #{file.path}#{extension}`
      image['local_path'] = "#{file.path}#{extension}"
    else
      background ||= 'white'
      size = 400
      path = file.path
      if image['content_type'] && image['content_type'].match(/svg/)
        cmd = "convert -background \"#{background}\" -density 300 -resize #{size}x#{size} -gravity center -extent #{size}x#{size} #{file.path} -flatten #{file.path}.jpg"
        OBF::Utils.log "    #{cmd}"
        image['local_path'] = "#{file.path}.jpg"
        if image['threadable']
          pid = Process.spawn(cmd)
          thr = Process.detach(pid)
          OBF::Utils.log "    scheduled image"
          return {thread: thr, image: image, type: 'svg', pid: pid}
        else
          `#{cmd}`
          OBF::Utils.log "    finished image #{File.exist?(image['local_path']) && File.size(image['local_path'])}"
        end
#        `convert -background "#{background}" -density 300 -resize #{size}x#{size} -gravity center -extent #{size}x#{size} #{file.path} -flatten #{file.path}.jpg`
#        `rsvg-convert -w #{size} -h #{size} -a #{file.path} > #{file.path}.png`
      else
        cmd = "convert #{path} -density 300 -resize #{size}x#{size} -background \"#{background}\" -gravity center -extent #{size}x#{size} -flatten #{path}.jpg"
        OBF::Utils.log "    #{cmd}"
        image['local_path'] = "#{path}.jpg"
        if image['threadable']
          pid = Process.spawn(cmd)
          thr = Process.detach(pid)
          OBF::Utils.log "    scheduled image"
          return {thread: thr, image: image, type: 'not_svg', pid: pid}
        else
          `#{cmd}`
          OBF::Utils.log "    finished image #{File.exist?(image['local_path']) && File.size(image['local_path'])}"
        end
        # `convert #{path} -density 300 -resize #{size}x#{size} -background "#{background}" -gravity center -extent #{size}x#{size} -flatten #{path}.jpg`
      end

      image['local_path']
    end
    image['local_path']
  end
sound_base64(url) click to toggle source
# File lib/obf/utils.rb, line 259
def self.sound_base64(url)
  sound = nil
  if url.match(/:\/\//)
    sound = get_url(url)
  else
    types = MIME::Types.type_for(url)
    sound = {
      'data' => File.read(url),
      'content_type' => types[0] && types[0].to_s
    }
  end
  return nil unless sound
  str = "data:" + sound['content_type']
  str += ";base64," + Base64.strict_encode64(sound['data'])
  str
end
sound_raw(url) click to toggle source
# File lib/obf/utils.rb, line 253
def self.sound_raw(url)
  sound = get_url(url)
  return nil unless sound
  sound
end
temp_path(*args) click to toggle source
# File lib/obf/utils.rb, line 371
def self.temp_path(*args)
  file = Tempfile.new(*args)
  res = file.path
  file.unlink
  res
end
update_current_progress(*args) click to toggle source
# File lib/obf/utils.rb, line 492
def self.update_current_progress(*args)
  if Object.const_defined?('Progress') && Progress.respond_to?(:update_current_progress)
    Progress.update_current_progress(*args)
  end
end