Module: OBF::Utils

Defined in:
lib/obf/utils.rb

Defined Under Namespace

Classes: Zipper

Class Method Summary collapse

Class Method Details

.as_progress_percent(a, b, &block) ⇒ Object



498
499
500
501
502
503
504
# 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) ⇒ Object



481
482
483
484
485
486
487
488
489
490
# 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) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
# 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') ⇒ Object



291
292
293
294
295
296
297
298
299
# 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) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 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

.hydraObject



162
163
164
# File 'lib/obf/utils.rb', line 162

def self.hydra
  Typhoeus::Hydra.new(max_concurrency: 10)
end

.identify_file(path) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# 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 = '') ⇒ Object



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/obf/utils.rb', line 378

def self.image_attrs(path, extension='')
  res = {}
  if 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) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# 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
  str = "data:" + image['content_type']
  str += ";base64," + Base64.strict_encode64(image['data'])
  str
end

.image_raw(url) ⇒ Object



135
136
137
138
139
# 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) ⇒ Object



473
474
475
476
477
478
479
# 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) ⇒ Object



465
466
467
468
469
470
471
# File 'lib/obf/utils.rb', line 465

def self.log(str)
  if defined?(Rails)
    Rails.logger.info(str)
  else
    puts str
  end
end

.obf_shellObject



276
277
278
279
280
281
282
283
284
285
286
287
288
289
# 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) ⇒ Object



360
361
362
363
364
365
366
367
368
369
# 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) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
# 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) ⇒ Object



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# 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) ⇒ Object



60
61
62
63
64
65
66
67
# 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) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# 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) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# 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) ⇒ Object



253
254
255
256
257
# 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) ⇒ Object



371
372
373
374
375
376
# 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) ⇒ Object



492
493
494
495
496
# 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