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



427
428
429
430
431
432
433
# File 'lib/obf/utils.rb', line 427

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



410
411
412
413
414
415
416
417
418
419
# File 'lib/obf/utils.rb', line 410

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

.fix_color(str, type = 'hex') ⇒ Object



229
230
231
232
233
234
235
236
237
# File 'lib/obf/utils.rb', line 229

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) ⇒ 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
# File 'lib/obf/utils.rb', line 3

def self.get_url(url)
  return nil unless url
  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)
    res = Typhoeus.get(uri)
    content_type = res.headers['Content-Type']
    data = res.body
  end
  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
  res = {
    'content_type' => content_type,
    'data' => data,
    'extension' => extension
  }
  res
end

.identify_file(path) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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
# File 'lib/obf/utils.rb', line 42

def self.identify_file(path)
  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



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
346
347
348
349
350
351
352
353
354
# File 'lib/obf/utils.rb', line 316

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
  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



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/obf/utils.rb', line 112

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



106
107
108
109
110
# File 'lib/obf/utils.rb', line 106

def self.image_raw(url)
  image = get_url(url)
  return nil unless image
  image
end

.load_zip(path, &block) ⇒ Object



402
403
404
405
406
407
408
# File 'lib/obf/utils.rb', line 402

def self.load_zip(path, &block)
  require 'zip'

  Zip::File.open(path) do |zipfile|
    block.call(Zipper.new(zipfile))
  end
end

.obf_shellObject



214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/obf/utils.rb', line 214

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



298
299
300
301
302
303
304
305
306
307
# File 'lib/obf/utils.rb', line 298

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



285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/obf/utils.rb', line 285

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



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/obf/utils.rb', line 239

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



33
34
35
36
37
38
39
40
# File 'lib/obf/utils.rb', line 33

def self.sanitize_url(url)
  uri = URI.parse(url) rescue nil
  return nil unless uri
  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



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/obf/utils.rb', line 133

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['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']
    url_data = get_url(image['url'])
    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}`
    "#{file.path}#{extension}"
  else
    background ||= 'white'
    size = 400
    path = file.path
    if image['content_type'] && image['content_type'].match(/svg/)
      `convert -background none -density 300 -resize #{size}x#{size} -gravity center -extent #{size}x#{size} #{file.path} #{file.path}.png`
#        `rsvg-convert -w #{size} -h #{size} -a #{file.path} > #{file.path}.png`
      path = "#{file.path}.png"
    end
    `convert #{path} -density 300 -resize #{size}x#{size} -background "#{background}" -gravity center -extent #{size}x#{size} #{path}.jpg`

    "#{path}.jpg"
  end
end

.sound_base64(url) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/obf/utils.rb', line 197

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



191
192
193
194
195
# File 'lib/obf/utils.rb', line 191

def self.sound_raw(url)
  sound = get_url(url)
  return nil unless sound
  sound
end

.temp_path(*args) ⇒ Object



309
310
311
312
313
314
# File 'lib/obf/utils.rb', line 309

def self.temp_path(*args)
  file = Tempfile.new(*args)
  res = file.path
  file.unlink
  res
end

.update_current_progress(*args) ⇒ Object



421
422
423
424
425
# File 'lib/obf/utils.rb', line 421

def self.update_current_progress(*args)
  if Object.const_defined?('Progress') && Progress.respond_to?(:update_current_progress)
    Progress.update_current_progress(*args)
  end
end