handset_detection 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,667 @@
1
+ #--
2
+ # Copyright (c) Richard Uren 2016 <richard@teleport.com.au>
3
+ # All Rights Reserved
4
+ #
5
+ # LICENSE: Redistribution and use in source and binary forms, with or
6
+ # without modification, are permitted provided that the following
7
+ # conditions are met: Redistributions of source code must retain the
8
+ # above copyright notice, this list of conditions and the following
9
+ # disclaimer. Redistributions in binary form must reproduce the above
10
+ # copyright notice, this list of conditions and the following disclaimer
11
+ # in the documentation and/or other materials provided with the
12
+ # distribution.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17
+ # NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
18
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
20
+ # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
22
+ # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23
+ # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
24
+ # DAMAGE.
25
+ #++
26
+
27
+ require 'handset_detection/base'
28
+ require 'handset_detection/store'
29
+ require 'handset_detection/extra'
30
+
31
+ class Device < Base
32
+ def initialize(config={})
33
+ @device = nil
34
+ @platform = nil
35
+ @browser = nil
36
+ @app = nil
37
+ @rating_result = nil
38
+ @store = nil
39
+ @extra = nil
40
+ @config = nil
41
+ super()
42
+ set_config config
43
+ @device_headers = {}
44
+ @extra_headers = {}
45
+ end
46
+
47
+ # Set Config sets config vars
48
+ #
49
+ # +param+ hash config A config assoc array.
50
+ # +return+ true on success, false otherwise
51
+ #
52
+ def set_config(config)
53
+ config.each do |key, value|
54
+ @config[key] = value
55
+ end
56
+ @store = Store::get_instance
57
+ @store.set_config @config
58
+ @extra = Extra.new config
59
+ end
60
+
61
+ # Find all device vendors
62
+ #
63
+ # +param+ void
64
+ # +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
65
+ #
66
+ def local_vendors
67
+ @reply = {}
68
+ data = fetch_devices
69
+ return false if data.blank?
70
+
71
+ tmp = []
72
+ data['devices'].each do |item|
73
+ tmp << item['Device']['hd_specs']['general_vendor']
74
+ end
75
+ @reply['vendor'] = tmp.uniq
76
+ @reply['vendor'].sort!
77
+ set_error 0, 'OK'
78
+ end
79
+
80
+ # Find all models for the sepecified vendor
81
+ #
82
+ # +param+ string vendor The device vendor
83
+ # +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
84
+ #
85
+ def local_models(vendor)
86
+ @reply = {}
87
+ data = fetch_devices
88
+ return false if data.blank?
89
+
90
+ vendor = vendor.downcase
91
+ tmp = []
92
+ data['devices'].each do |item|
93
+ if vendor == item['Device']['hd_specs']['general_vendor'].downcase
94
+ tmp << item['Device']['hd_specs']['general_model']
95
+ end
96
+ key = vendor + " "
97
+ unless item['Device']['hd_specs']['general_aliases'].blank?
98
+ item['Device']['hd_specs']['general_aliases'].each do |alias_item|
99
+ result = alias_item.downcase.index(key.downcase)
100
+ tmp << alias_item.sub(key, '') if result === 0
101
+ end
102
+ end
103
+ end
104
+ tmp.sort!
105
+ @reply['model'] = tmp.uniq
106
+ set_error 0, 'OK'
107
+ end
108
+
109
+ # Finds all the specs for a specific device
110
+ #
111
+ # +param+ string vendor The device vendor
112
+ # +param+ string model The device model
113
+ # +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
114
+ #
115
+ def local_view(vendor, model)
116
+ @reply = {}
117
+ data = fetch_devices
118
+ return false if data.blank?
119
+
120
+ vendor = vendor.downcase
121
+ model = model.downcase
122
+ data['devices'].each do |item|
123
+ if vendor == item['Device']['hd_specs']['general_vendor'].downcase and model == item['Device']['hd_specs']['general_model'].downcase
124
+ @reply['device'] = item['Device']['hd_specs']
125
+ return set_error 0, 'OK'
126
+ end
127
+ end
128
+ set_error 301, 'Nothing found'
129
+ end
130
+
131
+ # Finds all devices that have a specific property
132
+ #
133
+ # +param+ string key
134
+ # +param+ string value
135
+ # +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
136
+ #
137
+ def local_what_has(key, value)
138
+ data = fetch_devices
139
+ return false if data.blank?
140
+
141
+ tmp = []
142
+ value = value.downcase
143
+ data['devices'].each do |item|
144
+ next if item['Device']['hd_specs'][key].blank?
145
+ match = false
146
+ if item['Device']['hd_specs'][key].is_a? Array
147
+ item['Device']['hd_specs'][key].each do |check|
148
+ if check.downcase.include? value.downcase
149
+ match = true
150
+ end
151
+ end
152
+ elsif item['Device']['hd_specs'][key].downcase.include? value.downcase
153
+ match = true
154
+ end
155
+
156
+ if match
157
+ tmp << { 'id' => item['Device']['_id'],
158
+ 'general_vendor' => item['Device']['hd_specs']['general_vendor'],
159
+ 'general_model' => item['Device']['hd_specs']['general_model'] }
160
+ end
161
+ end
162
+ @reply['devices'] = tmp
163
+ set_error 0, 'OK'
164
+ end
165
+
166
+ # Perform a local detection
167
+ #
168
+ # +param+ array headers HTTP headers as an assoc array. keys are standard http header names eg user-agent, x-wap-profile
169
+ # +return+ bool true on success, false otherwise
170
+ #
171
+ def local_detect(h)
172
+ headers = {}
173
+ # lowercase headers on the way in.
174
+ h.each {|k, v| headers[k.downcase] = v}
175
+ hardware_info = headers['x-local-hardwareinfo']
176
+ headers.delete('x-local-hardwareinfo')
177
+
178
+ # Is this a native detection or a HTTP detection ?
179
+ if has_bi_keys headers
180
+ return v4_match_build_info headers
181
+ end
182
+ v4_match_http_headers headers, hardware_info
183
+ end
184
+
185
+ # Returns the rating score for a device based on the passed values
186
+ #
187
+ # +param+ string deviceId : The ID of the device to check.
188
+ # +param+ hash props Properties extracted from the device (display_x, display_y etc .. )
189
+ # +return+ array of rating information. (which includes 'score' which is an int value that is a percentage.)
190
+ #
191
+ def find_rating(device_id, props)
192
+ device = find_by_id(device_id)
193
+ return nil if device['Device']['hd_specs'].blank?
194
+
195
+ specs = device['Device']['hd_specs']
196
+
197
+ total = 70
198
+ result = {}
199
+
200
+ # Display Resolution - Worth 40 points if correct
201
+ result['resolution'] = 0
202
+ unless props['display_x'].blank? or props['display_y'].blank?
203
+ p_max_res = [props['display_x'], props['display_y']].max.to_i
204
+ p_min_res = [props['display_x'], props['display_y']].min.to_i
205
+ s_max_res = [specs['display_x'], specs['display_y']].max.to_i
206
+ s_min_res = [specs['display_x'], specs['display_y']].min.to_i
207
+ if p_max_res == s_max_res and p_min_res == s_min_res
208
+ # Check for native match first
209
+ result['resolution'] = 40
210
+ else
211
+ # Check for css dimensions match.
212
+ # css dimensions should be display_[xy] / display_pixel_ratio or others in other modes.
213
+ # Devices can have multiple css display modes (eg. iPhone 6, iPhone 6+ Zoom mode)
214
+ css_screen_sizes = specs['display_css_screen_sizes'].blank? ? [] : specs['display_css_screen_sizes']
215
+ css_screen_sizes.each do |size|
216
+ dimensions = size.split('x')
217
+ tmp_max_res = dimensions.max.to_i
218
+ tmp_min_res = dimensions.min.to_i
219
+ if p_max_res == tmp_max_res and p_min_res == tmp_min_res
220
+ result['resolution'] = 40
221
+ break
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ # Display pixel ratio - 20 points
228
+ result['display_pixel_ratio'] = 20
229
+ unless props['display_pixel_ratio'].blank?
230
+ # Note : display_pixel_ratio will be a string stored as 1.33 or 1.5 or 2, perhaps 2.0 ..
231
+ if specs['display_pixel_ratio'].to_f.round(2) == (props['display_pixel_ratio'] / 100.to_f).round(2)
232
+ result['display_pixel_ratio'] = 40
233
+ end
234
+ end
235
+
236
+ # Benchmark - 10 points - Enough to tie break but not enough to overrule display or pixel ratio.
237
+ result['benchmark'] = 0
238
+ unless props['benchmark'].blank?
239
+ unless specs['benchmark_min'].blank? or specs['benchmark_max'].blank?
240
+ if props['benchmark'].to_i >= specs['benchmark_min'].to_i and props['benchmark'].to_i <= specs['benchmark_max'].to_i
241
+ # Inside range
242
+ result['benchmark'] = 10
243
+ end
244
+ end
245
+ end
246
+
247
+ result['score'] = result.values.inject(0){ |sum, x| sum + x }.to_i
248
+ result['possible'] = total
249
+ result['_id'] = device_id
250
+
251
+ # Distance from mean used in tie breaking situations if two devices have the same score.
252
+ result['distance'] = 100000
253
+ unless specs['benchmark_min'].blank? or specs['benchmark_max'].blank? or props['benchmark'].blank?
254
+ result['distance'] = ((specs['benchmark_min'] + specs['benchmark_max']) / 2 - props['benchmark']).abs.to_i
255
+ end
256
+ result
257
+ end
258
+
259
+ # Overlays specs onto a device
260
+ #
261
+ # +param+ string specsField : Either 'platform', 'browser', 'language'
262
+ # +return+ void
263
+ #
264
+ def specs_overlay(specs_field, device, specs)
265
+ if specs.include? 'hd_specs'
266
+ if specs_field == 'platform'
267
+ unless specs['hd_specs']['general_platform'].blank?
268
+ device['Device']['hd_specs']['general_platform'] = specs['hd_specs']['general_platform']
269
+ device['Device']['hd_specs']['general_platform_version'] = specs['hd_specs']['general_platform_version']
270
+ end
271
+ elsif specs_field == 'browser'
272
+ unless specs['hd_specs']['general_browser'].blank?
273
+ device['Device']['hd_specs']['general_browser'] = specs['hd_specs']['general_browser']
274
+ device['Device']['hd_specs']['general_browser_version'] = specs['hd_specs']['general_browser_version']
275
+ end
276
+ elsif specs_field == 'app'
277
+ unless specs['hd_specs']['general_app'].blank?
278
+ device['Device']['hd_specs']['general_app'] = specs['hd_specs']['general_app']
279
+ device['Device']['hd_specs']['general_app_version'] = specs['hd_specs']['general_app_version']
280
+ device['Device']['hd_specs']['general_app_category'] = specs['hd_specs']['general_app_category']
281
+ end
282
+ elsif specs_field == 'language'
283
+ unless specs['hd_specs']['general_language'].blank?
284
+ device['Device']['hd_specs']['general_language'] = specs['hd_specs']['general_language']
285
+ device['Device']['hd_specs']['general_language_full'] = specs['hd_specs']['general_language_full']
286
+ end
287
+ end
288
+ end
289
+ device
290
+ end
291
+
292
+ # Takes a string of onDeviceInformation and turns it into something that can be used for high accuracy checking.
293
+ #
294
+ # Strings a usually generated from cookies, but may also be supplied in headers.
295
+ # The format is w;h;r;b where w is the display width, h is the display height, r is the pixel ratio and b is the benchmark.
296
+ # display_x, display_y, display_pixel_ratio, general_benchmark
297
+ #
298
+ # +param+ string hardwareInfo String of light weight device property information, separated by ':'
299
+ # +return+ array partial specs array of information we can use to improve detection accuracy
300
+ #
301
+ def info_string_to_hash(hardware_info)
302
+ # Remove the header or cookie name from the string 'x-specs1a='
303
+ if hardware_info.include?('=')
304
+ tmp = hardware_info.split('=')
305
+ if tmp[1].blank?
306
+ return {}
307
+ else
308
+ hardware_info = tmp[1]
309
+ end
310
+ end
311
+ reply = {}
312
+ info = hardware_info.split(':')
313
+ return {} if info.length != 4
314
+ reply['display_x'] = info[0].strip.to_i
315
+ reply['display_y'] = info[1].strip.to_i
316
+ reply['display_pixel_ratio'] = info[2].strip.to_i
317
+ reply['benchmark'] = info[3].strip.to_i
318
+ reply
319
+ end
320
+
321
+ # Overlays hardware info onto a device - Used in generic replys
322
+ #
323
+ # +param+ hash device
324
+ # +param+ hardwareInfo
325
+ # +return+ void
326
+ #
327
+ def hardware_info_overlay(device, info)
328
+ unless info.blank?
329
+ device['Device']['hd_specs']['display_x'] = info['display_x'] unless info['display_x'].blank?
330
+ device['Device']['hd_specs']['display_y'] = info['display_y'] unless info['display_y'].blank?
331
+ device['Device']['hd_specs']['display_pixel_ratio'] = info['display_pixel_ratio'] unless info['display_pixel_ratio'].blank?
332
+ end
333
+ device
334
+ end
335
+
336
+ # Device matching
337
+ #
338
+ # Plan of attack :
339
+ # 1) Look for opera headers first - as they're definitive
340
+ # 2) Try profile match - only devices which have unique profiles will match.
341
+ # 3) Try user-agent match
342
+ # 4) Try other x-headers
343
+ # 5) Try all remaining headers
344
+ #
345
+ # +param+ void
346
+ # +return+ array The matched device or null if not found
347
+ #
348
+ def match_device(headers)
349
+ # Remember the agent for generic matching later.
350
+ agent = ""
351
+ # Opera mini sometimes puts the vendor # model in the header - nice! ... sometimes it puts ? # ? in as well
352
+ if (not headers['x-operamini-phone'].blank?) and headers['x-operamini-phone'].strip != "? # ?"
353
+ _id = get_match 'x-operamini-phone', headers['x-operamini-phone'], DETECTIONV4_STANDARD, 'x-operamini-phone', 'device'
354
+ return find_by_id(_id) if _id
355
+ agent = headers['x-operamini-phone']
356
+ headers.delete('x-operamini-phone')
357
+ end
358
+
359
+ # Profile header matching
360
+ unless headers['profile'].blank?
361
+ _id = get_match 'profile', headers['profile'], DETECTIONV4_STANDARD, 'profile', 'device'
362
+ return find_by_id _id if _id
363
+ headers.delete('profile')
364
+ end
365
+
366
+ # Profile header matching
367
+ unless headers['x-wap-profile'].blank?
368
+ _id = get_match 'profile', headers['x-wap-profile'], DETECTIONV4_STANDARD, 'x-wap-profile', 'device'
369
+ return find_by_id _id if _id
370
+ headers.delete('x-wap-profile')
371
+ end
372
+
373
+ # Match nominated headers ahead of x- headers
374
+ order = @detection_config['device-ua-order']
375
+ headers.each do |key, value|
376
+ order << key if (not order.include? key) and /^x-/i.match key
377
+ end
378
+
379
+ order.each do |item|
380
+ unless headers[item].blank?
381
+ # log "Trying user-agent match on header #{item}"
382
+ _id = get_match 'user-agent', headers[item], DETECTIONV4_STANDARD, item, 'device'
383
+ return find_by_id _id if _id
384
+ end
385
+ end
386
+
387
+ # Generic matching - Match of last resort
388
+ # log('Trying Generic Match')
389
+
390
+ if headers.include? 'x-operamini-phone-ua'
391
+ _id = get_match 'user-agent', headers['x-operamini-phone-ua'], DETECTIONV4_GENERIC, 'agent', 'device'
392
+ end
393
+ if _id.blank? and headers.include? 'agent'
394
+ _id =get_match 'user-agent', headers['agent'], DETECTIONV4_GENERIC, 'agent', 'device'
395
+ end
396
+ if _id.blank? and headers.include? 'user-agent'
397
+ _id = get_match 'user-agent', headers['user-agent'], DETECTIONV4_GENERIC, 'agent', 'device'
398
+ end
399
+
400
+ return find_by_id _id unless _id.blank?
401
+ false
402
+ end
403
+
404
+ # Find a device by its id
405
+ #
406
+ # +param+ string _id
407
+ # +return+ hash device on success, false otherwise
408
+ #
409
+ def find_by_id(_id)
410
+ @store.read("Device_#{_id}")
411
+ end
412
+
413
+ # Internal helper for building a list of all devices.
414
+ #
415
+ # +param+ void
416
+ # +return+ array List of all devices.
417
+ #
418
+ def fetch_devices
419
+ result = @store.fetch_devices
420
+ unless result
421
+ return set_error 299, "Error : fetchDevices cannot read files from store."
422
+ end
423
+ result
424
+ end
425
+
426
+ # BuildInfo Matching
427
+ #
428
+ # Takes a set of buildInfo key/value pairs & works out what the device is
429
+ #
430
+ # +param+ hash buildInfo - Buildinfo key/value array
431
+ # +return+ mixed device array on success, false otherwise
432
+ #
433
+ def v4_match_build_info(build_info)
434
+ @device = nil
435
+ @platform = nil
436
+ @browser = nil
437
+ @app = nil
438
+ @detected_rule_key = nil
439
+ @rating_result = nil
440
+ @reply = {}
441
+
442
+ # Nothing to check
443
+ return false if build_info.blank?
444
+
445
+ @build_info = build_info
446
+
447
+ # Device Detection
448
+ @device = v4_match_bi_helper build_info, 'device'
449
+ return false if @device.blank?
450
+
451
+ # Platform Detection
452
+ @platform = v4_match_bi_helper build_info, 'platform'
453
+ unless @platform.blank?
454
+ @device = specs_overlay 'platform', @device, @platform
455
+ end
456
+
457
+ @reply['hd_specs'] = @device['Device']['hd_specs']
458
+ set_error 0, "OK"
459
+ end
460
+
461
+ # buildInfo Match helper - Does the build info match heavy lifting
462
+ #
463
+ # +param+ hash buildInfo A buildInfo key/value array
464
+ # +param+ string category - 'device' or 'platform' (cant match browser or app with buildinfo)
465
+ # +return+ device or extra on success, false otherwise
466
+ #
467
+ def v4_match_bi_helper(build_info, category='device')
468
+ # ***** Device Detection *****
469
+ conf_bi_keys = @detection_config["#{category}-bi-order"]
470
+ return nil if conf_bi_keys.blank? or build_info.blank?
471
+
472
+ hints = []
473
+ conf_bi_keys.each do |platform, set|
474
+ value = ''
475
+ set.each do |tuple|
476
+ checking = true
477
+ tuple.each do |item|
478
+ unless build_info.include? item
479
+ checking = false
480
+ break
481
+ else
482
+ value += '|' + build_info[item]
483
+ end
484
+ end
485
+ if checking
486
+ value.gsub!(/^[| \t\n\r\0\x0B]*/, '')
487
+ value.gsub!(/[| \t\n\r\0\x0B]*$/, '')
488
+ hints << value
489
+ subtree = (category == 'device') ? DETECTIONV4_STANDARD : category
490
+ _id = get_match 'buildinfo', value, subtree, 'buildinfo', category
491
+ unless _id.blank?
492
+ return (category == 'device') ? (find_by_id _id) : (@extra.find_by_id _id)
493
+ end
494
+ end
495
+ end
496
+ end
497
+ # If we get this far then not found, so try generic.
498
+ platform = has_bi_keys build_info
499
+ unless platform.blank?
500
+ try = ["generic|#{platform}", "#{platform}|generic"]
501
+ try.each do |value|
502
+ subtree = (category == 'device') ? DETECTIONV4_GENERIC : category
503
+ _id = get_match 'buildinfo', value, subtree, 'buildinfo', category
504
+ unless _id.blank?
505
+ return (category == 'device') ? (find_by_id _id) : (@extra.find_by_id _id)
506
+ end
507
+ end
508
+ end
509
+ nil
510
+ end
511
+
512
+ # Find the best device match for a given set of headers and optional device properties.
513
+ #
514
+ # In 'all' mode all conflicted devces will be returned as a list.
515
+ # In 'default' mode if there is a conflict then the detected device is returned only (backwards compatible with v3).
516
+ #
517
+ # +param+ hash headers Set of sanitized http headers
518
+ # +param+ string hardwareInfo Information about the hardware
519
+ # +return+ array device specs. (device.hd_specs)
520
+ #
521
+ def v4_match_http_headers(headers, hardware_info=nil)
522
+ @device = nil
523
+ @platform = nil
524
+ @browser = nil
525
+ @app = nil
526
+ @rating_result = nil
527
+ @detected_rule_key = {}
528
+ @reply = {}
529
+ hw_props = nil
530
+
531
+ # Nothing to check
532
+ return false if headers.blank?
533
+
534
+ headers.delete('ip')
535
+ headers.delete('host')
536
+
537
+ # Sanitize headers & cleanup language
538
+ headers.each do |key, value|
539
+ if key == 'accept-language' or key == 'content-language'
540
+ key = 'language'
541
+ tmp = value.downcase.gsub(/ /, '').split(/[,;]/)
542
+ unless tmp[0].blank?
543
+ value = tmp[0]
544
+ else
545
+ next
546
+ end
547
+ end
548
+ @device_headers[key.downcase] = clean_str value
549
+ @extra_headers[key.downcase] = @extra.extra_clean_str value
550
+ end
551
+
552
+ @device = match_device @device_headers
553
+ return set_error 301, "Not Found" if @device.blank?
554
+
555
+ unless hardware_info.blank?
556
+ hw_props = info_string_to_hash hardware_info
557
+ end
558
+
559
+ # Stop on detect set - Tidy up and return
560
+ if @device['Device']['hd_ops']['stop_on_detect'] == 1
561
+ # Check for hardwareInfo overlay
562
+ unless @device['Device']['hd_ops']['overlay_result_specs'].blank?
563
+ @device = hardware_info_overlay(@device, hw_props)
564
+ end
565
+ @reply['hd_specs'] = @device['Device']['hd_specs']
566
+ return set_error 0, "OK"
567
+ end
568
+
569
+ # Get extra info
570
+ @platform = @extra.match_extra 'platform', @extra_headers
571
+ @browser = @extra.match_extra 'browser', @extra_headers
572
+ @app = @extra.match_extra 'app', @extra_headers
573
+ @language = @extra.match_language @extra_headers
574
+
575
+ # Find out if there is any contention on the detected rule.
576
+ device_list = get_high_accuracy_candidates
577
+ unless device_list.blank?
578
+
579
+ # Resolve contention with OS check
580
+ @extra.set @platform
581
+ pass1_list = []
582
+ device_list.each do |_id|
583
+ try_device = find_by_id _id
584
+ if @extra.verify_platform try_device['Device']['hd_specs']
585
+ pass1_list << _id
586
+ end
587
+ end
588
+ # Contention still not resolved .. check hardware
589
+ if pass1_list.length >= 2 and (not hw_props.blank?)
590
+
591
+ # Score the list based on hardware
592
+ result = []
593
+ pass1_list.each do |_id|
594
+ tmp = find_rating _id, hw_props
595
+ unless tmp.blank?
596
+ tmp['_id'] = _id
597
+ result << tmp
598
+ end
599
+ end
600
+
601
+ # Sort the results
602
+ result.sort! do |d1, d2| case
603
+ when d2['score'].to_i - d1['score'].to_i != 0
604
+ d2['score'].to_i - d1['score'].to_i
605
+ else
606
+ d1['distance'].to_i - d2['distance'].to_i
607
+ end
608
+ end
609
+ @rating_result = result
610
+ # Take the first one
611
+ if @rating_result[0]['score'] != 0
612
+ device = find_by_id result[0]['_id']
613
+ unless device.blank?
614
+ @device = device
615
+ end
616
+ end
617
+ end
618
+ end
619
+ # Overlay specs
620
+ @device = specs_overlay 'platform', @device, @platform['Extra'] if @platform
621
+ @device = specs_overlay 'browser', @device, @browser['Extra'] if @browser
622
+ @device = specs_overlay 'app', @device, @app['Extra'] if @app
623
+ @device = specs_overlay 'language', @device, @language['Extra'] if @language
624
+
625
+ # Overlay hardware info result if required
626
+ unless @device['Device']['hd_ops']['overlay_result_specs'].blank? or hardware_info.blank?
627
+ @device = hardware_info_overlay @device, hw_props
628
+ end
629
+
630
+ @reply['hd_specs'] = @device['Device']['hd_specs']
631
+ set_error 0, "OK"
632
+ end
633
+
634
+ # Determines if high accuracy checks are available on the device which was just detected
635
+ #
636
+ # +param+ void
637
+ # +return+ array, a list of candidate devices which have this detection rule or false otherwise.
638
+ #
639
+ def get_high_accuracy_candidates
640
+ branch = get_branch 'hachecks'
641
+ rule_key = @detected_rule_key['device']
642
+ unless branch[rule_key].blank?
643
+ return branch[rule_key]
644
+ end
645
+ false
646
+ end
647
+
648
+ # Determines if hd4Helper would provide more accurate results.
649
+ #
650
+ # +param+ hash headers HTTP Headers
651
+ # +return+ true if required, false otherwise
652
+ #
653
+ def is_helper_useful(headers)
654
+ return false if headers.blank?
655
+
656
+ headers.delete('ip')
657
+ headers.delete('host')
658
+
659
+ tmp = local_detect(headers)
660
+ return false if tmp.blank?
661
+
662
+ tmp = get_high_accuracy_candidates
663
+ return false if tmp.blank?
664
+
665
+ true
666
+ end
667
+ end