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.
- checksums.yaml +7 -0
- data/lib/handset_detection/base.rb +554 -0
- data/lib/handset_detection/cache/filesystem.rb +87 -0
- data/lib/handset_detection/cache/memcached.rb +70 -0
- data/lib/handset_detection/cache/none.rb +60 -0
- data/lib/handset_detection/cache/rails.rb +59 -0
- data/lib/handset_detection/cache.rb +105 -0
- data/lib/handset_detection/device.rb +667 -0
- data/lib/handset_detection/extra.rb +221 -0
- data/lib/handset_detection/hd4.rb +458 -0
- data/lib/handset_detection/store.rb +178 -0
- data/lib/handset_detection.rb +27 -0
- metadata +54 -0
@@ -0,0 +1,221 @@
|
|
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
|
+
|
29
|
+
class Extra < Base
|
30
|
+
def initialize(config={})
|
31
|
+
@data = nil
|
32
|
+
@store = nil
|
33
|
+
super()
|
34
|
+
set_config config
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set Config variables
|
38
|
+
#
|
39
|
+
# +param+ hash config A config hash
|
40
|
+
# +return+ boolean true on success, false otherwise
|
41
|
+
#
|
42
|
+
def set_config(config)
|
43
|
+
@store = Store::get_instance
|
44
|
+
@store.set_config config
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def set(data)
|
49
|
+
@data = data
|
50
|
+
end
|
51
|
+
|
52
|
+
# Matches all HTTP header extras - platform, browser and app
|
53
|
+
#
|
54
|
+
# +param+ string cls Is 'platform','browser' or 'app'
|
55
|
+
# +return+ an Extra on success, false otherwise
|
56
|
+
#
|
57
|
+
def match_extra(cls, headers)
|
58
|
+
headers.delete 'profile'
|
59
|
+
order = @detection_config["#{cls}-ua-order"]
|
60
|
+
|
61
|
+
headers.keys.each do |key|
|
62
|
+
# Append any x- headers to the list of headers to check
|
63
|
+
if (not order.include?(key)) and /^x-/i.match(key)
|
64
|
+
order << key
|
65
|
+
end
|
66
|
+
end
|
67
|
+
order.each do |field|
|
68
|
+
unless headers[field].blank?
|
69
|
+
_id = get_match 'user-agent', headers[field], cls, field, cls
|
70
|
+
if _id
|
71
|
+
extra = find_by_id _id
|
72
|
+
return extra
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
# Find a device by its id
|
80
|
+
#
|
81
|
+
# +param+ string _id
|
82
|
+
# +return+ hash device on success, false otherwise
|
83
|
+
#
|
84
|
+
def find_by_id(_id)
|
85
|
+
@store.read "Extra_#{_id}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Can learn language from language header or agent
|
89
|
+
#
|
90
|
+
# +param+ hash headers A key => value hash of sanitized http headers
|
91
|
+
# +return+ hash Extra on success, false otherwise
|
92
|
+
#
|
93
|
+
def match_language(headers)
|
94
|
+
extra = { 'Extra' => { 'hd_specs' => {}} }
|
95
|
+
|
96
|
+
# Mock up a fake Extra for merge into detection reply.
|
97
|
+
extra['_id'] = 0.to_int
|
98
|
+
extra['Extra']['hd_specs']['general_language'] = ''
|
99
|
+
extra['Extra']['hd_specs']['general_language_full'] = ''
|
100
|
+
|
101
|
+
# Try directly from http header first
|
102
|
+
unless headers['language'].blank?
|
103
|
+
candidate = headers['language']
|
104
|
+
if @detection_languages.include?(candidate) and @detection_languages[candidate]
|
105
|
+
extra['Extra']['hd_specs']['general_language'] = candidate
|
106
|
+
extra['Extra']['hd_specs']['general_language_full'] = @detection_languages[candidate]
|
107
|
+
return extra
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
check_order = @detection_config['language-ua-order'] + headers.keys
|
112
|
+
language_list = @detection_languages
|
113
|
+
check_order.each do |header|
|
114
|
+
if headers.include?(header) and not headers[header].blank?
|
115
|
+
agent = headers[header]
|
116
|
+
language_list.each do |code, full|
|
117
|
+
if /[; \(]#{code}[; \)]/i.match(agent)
|
118
|
+
extra['Extra']['hd_specs']['general_language'] = code
|
119
|
+
extra['Extra']['hd_specs']['general_language_full'] = full
|
120
|
+
return extra
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
false
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns false if this device definitively cannot run this platform and platform version.
|
129
|
+
# Returns true if its possible of if there is any doubt.
|
130
|
+
#
|
131
|
+
# Note : The detected platform must match the device platform. This is the stock OS as shipped
|
132
|
+
# on the device. If someone is running a variant (eg CyanogenMod) then all bets are off.
|
133
|
+
#
|
134
|
+
# +param+ string specs The specs we want to check.
|
135
|
+
# +return+ boolean false if these specs can not run the detected OS, true otherwise.
|
136
|
+
#
|
137
|
+
def verify_platform(specs=nil)
|
138
|
+
platform = @data
|
139
|
+
platform.default = ''
|
140
|
+
platform_name = platform['Extra']['hd_specs']['general_platform'].downcase.strip
|
141
|
+
platform_version = platform['Extra']['hd_specs']['general_platform_version'].downcase.strip
|
142
|
+
device_platform_name = specs['general_platform'].downcase.strip
|
143
|
+
device_platform_version_min = specs['general_platform_version'].downcase.strip
|
144
|
+
device_platform_version_max = specs['general_platform_version_max'].downcase.strip
|
145
|
+
|
146
|
+
# Its possible that we didnt pickup the platform correctly or the device has no platform info
|
147
|
+
# Return true in this case because we cant give a concrete false (it might run this version).
|
148
|
+
if platform.blank? or platform_name.blank? or device_platform_name.blank?
|
149
|
+
return true
|
150
|
+
end
|
151
|
+
|
152
|
+
# Make sure device is running stock OS / Platform
|
153
|
+
# Return true in this case because its possible the device can run a different OS (mods / hacks etc..)
|
154
|
+
if platform_name != device_platform_name
|
155
|
+
return true
|
156
|
+
end
|
157
|
+
|
158
|
+
# Detected version is lower than the min version - so definetly false.
|
159
|
+
if not platform_version.blank? and not device_platform_version_min.blank? and compare_platform_versions(platform_version, device_platform_version_min) <= -1
|
160
|
+
return false
|
161
|
+
end
|
162
|
+
|
163
|
+
# Detected version is greater than the max version - so definetly false.
|
164
|
+
if not platform_version.blank? and not device_platform_version_max.blank? and compare_platform_versions(platform_version, device_platform_version_max) >= 1
|
165
|
+
return false
|
166
|
+
end
|
167
|
+
|
168
|
+
# Maybe Ok ..
|
169
|
+
true
|
170
|
+
end
|
171
|
+
|
172
|
+
# Breaks a version number apart into its Major, minor and point release numbers for comparison.
|
173
|
+
#
|
174
|
+
# Big Assumption : That version numbers separate their release bits by '.' !!!
|
175
|
+
# might need to do some analysis on the string to rip it up right.
|
176
|
+
#
|
177
|
+
# +param+ string version_number
|
178
|
+
# +return+ hash of ('major' => x, 'minor' => y and 'point' => z) on success, null otherwise
|
179
|
+
#
|
180
|
+
def break_version_apart(version_number)
|
181
|
+
tmp = "#{version_number}.0.0.0".split('.', 4)
|
182
|
+
reply = {}
|
183
|
+
reply['major'] = tmp[0].blank? ? '0' : tmp[0]
|
184
|
+
reply['minor'] = tmp[1].blank? ? '0' : tmp[1]
|
185
|
+
reply['point'] = tmp[2].blank? ? '0' : tmp[2]
|
186
|
+
reply
|
187
|
+
end
|
188
|
+
|
189
|
+
# Helper for comparing two strings (numerically if possible)
|
190
|
+
#
|
191
|
+
# +param+ string a Generally a number, but might be a string
|
192
|
+
# +param+ string b Generally a number, but might be a string
|
193
|
+
# +return+ int
|
194
|
+
#
|
195
|
+
def compare_smartly(a, b)
|
196
|
+
begin
|
197
|
+
return Integer(a) - Integer(b)
|
198
|
+
rescue
|
199
|
+
return a <=> b
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Compares two platform version numbers
|
204
|
+
#
|
205
|
+
# +param+ string va Version A
|
206
|
+
# +param+ string vb Version B
|
207
|
+
# +return+ < 0 if a < b, 0 if a == b and > 0 if a > b : Also returns 0 if data is absent from either.
|
208
|
+
#
|
209
|
+
def compare_platform_versions(va, vb)
|
210
|
+
return 0 if va.blank? or vb.blank?
|
211
|
+
version_a = break_version_apart va
|
212
|
+
version_b = break_version_apart vb
|
213
|
+
major = compare_smartly version_a['major'], version_b['major']
|
214
|
+
minor = compare_smartly version_a['minor'], version_b['minor']
|
215
|
+
point = compare_smartly version_a['point'], version_b['point']
|
216
|
+
return major if major != 0
|
217
|
+
return minor if minor != 0
|
218
|
+
return point if point != 0
|
219
|
+
return 0
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,458 @@
|
|
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 'fileutils'
|
28
|
+
require 'handset_detection/base'
|
29
|
+
require 'handset_detection/device'
|
30
|
+
require 'yaml'
|
31
|
+
require 'zip'
|
32
|
+
|
33
|
+
# HD4 Class
|
34
|
+
#
|
35
|
+
class HD4 < Base
|
36
|
+
attr_reader :cache
|
37
|
+
|
38
|
+
# This is the main constructor for the class HD4
|
39
|
+
#
|
40
|
+
# +param+ mixed config can be a hash of config options or a fully qualified path to an alternate config file.
|
41
|
+
# +return+ void
|
42
|
+
#
|
43
|
+
def initialize(config=nil)
|
44
|
+
@realm = 'APIv4'
|
45
|
+
@reply = nil
|
46
|
+
@raw_reply = nil
|
47
|
+
@detect_request = {}
|
48
|
+
@error = ''
|
49
|
+
@logger = nil
|
50
|
+
@debug = false
|
51
|
+
@config = {
|
52
|
+
'username' => '',
|
53
|
+
'secret' => '',
|
54
|
+
'site_id' => '',
|
55
|
+
'use_proxy' => 0,
|
56
|
+
'proxy_server' => '',
|
57
|
+
'proxy_port' => '',
|
58
|
+
'proxy_user' => '',
|
59
|
+
'proxy_pass' => '',
|
60
|
+
'use_local' => false,
|
61
|
+
'api_server' => 'api.handsetdetection.com',
|
62
|
+
'timeout' => 10,
|
63
|
+
'debug' => false,
|
64
|
+
'filesdir' => '',
|
65
|
+
'retries' => 3,
|
66
|
+
'cache_requests' => false,
|
67
|
+
'geoip' => false,
|
68
|
+
'log_unknown' => true
|
69
|
+
}
|
70
|
+
@tree = {}
|
71
|
+
if config.blank?
|
72
|
+
if defined? Rails
|
73
|
+
config = File.join Rails.root, 'config', 'hdconfig.yml'
|
74
|
+
else
|
75
|
+
config = 'hdconfig.yml'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
super()
|
79
|
+
set_config config
|
80
|
+
if @config['username'].blank?
|
81
|
+
raise 'Error : API username not set. Download a premade config from your Site Settings.'
|
82
|
+
elsif @config['secret'].blank?
|
83
|
+
raise 'Error : API secret not set. Download a premade config from your Site Settings.'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_local_detection(enable)
|
88
|
+
@config['use_local'] = enable
|
89
|
+
end
|
90
|
+
|
91
|
+
def set_proxy_user(user)
|
92
|
+
@config['proxy_user'] = user
|
93
|
+
end
|
94
|
+
|
95
|
+
def set_proxy_pass(pass)
|
96
|
+
@config['proxy_pass'] = pass
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_use_proxy(proxy)
|
100
|
+
@config['use_proxy'] = proxy
|
101
|
+
end
|
102
|
+
|
103
|
+
def set_proxy_server(name)
|
104
|
+
@config['proxy_server'] = name
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_proxy_port(number)
|
108
|
+
@config['proxy_port'] = number
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_secret(secret)
|
112
|
+
@config['secret'] = secret
|
113
|
+
end
|
114
|
+
|
115
|
+
def set_username(user)
|
116
|
+
@config['username'] = user
|
117
|
+
end
|
118
|
+
|
119
|
+
def set_timeout(timeout)
|
120
|
+
@config['timeout'] = timeout
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_detect_var(key, value)
|
124
|
+
@detect_request[key.downcase] = value
|
125
|
+
end
|
126
|
+
|
127
|
+
def set_site_id(siteid)
|
128
|
+
@config['site_id'] = siteid.to_i
|
129
|
+
end
|
130
|
+
|
131
|
+
def set_use_local(value)
|
132
|
+
@config['use_local'] = value
|
133
|
+
end
|
134
|
+
|
135
|
+
def set_api_server(value)
|
136
|
+
@config['api_server'] = value
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_logger(function)
|
140
|
+
@config['logger'] = function
|
141
|
+
end
|
142
|
+
|
143
|
+
def set_files_dir(directory)
|
144
|
+
@config['filesdir'] = directory
|
145
|
+
unless @store.set_directory(directory)
|
146
|
+
raise "Error : Failed to create cache directory in #{directory}. Set your filesdir config setting or check directory permissions."
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_local_detection
|
151
|
+
@config['use_local']
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_proxy_user
|
155
|
+
@config['proxy_user']
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_proxy_pass
|
159
|
+
@config['proxy_pass']
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_use_proxy
|
163
|
+
@config['use_proxy']
|
164
|
+
end
|
165
|
+
|
166
|
+
def get_proxy_server
|
167
|
+
@config['proxy_server']
|
168
|
+
end
|
169
|
+
|
170
|
+
def get_proxy_port
|
171
|
+
@config['proxy_port']
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_error
|
175
|
+
@error
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_error_msg
|
179
|
+
@error
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_secret
|
183
|
+
@config['secret']
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_username
|
187
|
+
@config['username']
|
188
|
+
end
|
189
|
+
|
190
|
+
def get_timeout
|
191
|
+
@config['timeout']
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_reply
|
195
|
+
@reply
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_raw_reply
|
199
|
+
@raw_reply
|
200
|
+
end
|
201
|
+
|
202
|
+
def get_site_id
|
203
|
+
@config['site_id']
|
204
|
+
end
|
205
|
+
|
206
|
+
def get_use_local
|
207
|
+
@config['use_local']
|
208
|
+
end
|
209
|
+
|
210
|
+
def get_api_server
|
211
|
+
@config['api_server']
|
212
|
+
end
|
213
|
+
|
214
|
+
def get_detect_request
|
215
|
+
@detect_request
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_files_dir
|
219
|
+
@config['filesdir']
|
220
|
+
end
|
221
|
+
|
222
|
+
# Set config file
|
223
|
+
#
|
224
|
+
# +param+ hash config An assoc array of config data
|
225
|
+
# +return+ true on success, false otherwise
|
226
|
+
#
|
227
|
+
def set_config(cfg)
|
228
|
+
if cfg.is_a? Hash
|
229
|
+
@config = cfg
|
230
|
+
elsif cfg.is_a? String
|
231
|
+
@config = YAML::load_file cfg
|
232
|
+
end
|
233
|
+
|
234
|
+
if @config['filesdir'].blank?
|
235
|
+
@config['filesdir'] = File.dirname __FILE__
|
236
|
+
end
|
237
|
+
|
238
|
+
@store = Store::get_instance
|
239
|
+
@store.set_config @config, true
|
240
|
+
@cache = Cache.new(@config)
|
241
|
+
@device = Device.new(@config)
|
242
|
+
true
|
243
|
+
end
|
244
|
+
|
245
|
+
# Setup the api kit with HTTP headers from the web server environment.
|
246
|
+
#
|
247
|
+
# This is likely what you want to send if you're invoking this class for each new website visitor.
|
248
|
+
# You can override or add to these with set_detect_var(key, value)
|
249
|
+
#
|
250
|
+
# +param+ env A hash holding web server environment variables. In Rails: request.env or request.headers
|
251
|
+
# +return+ void
|
252
|
+
#
|
253
|
+
def set_server_headers(env)
|
254
|
+
env.each do |k, v|
|
255
|
+
next unless k.match(/^HTTP_/)
|
256
|
+
name = k.sub(/^HTTP_/, '').split(/_/).map { |x| x.capitalize }.join('-')
|
257
|
+
@detect_request[name] = v
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# List all known vendors
|
262
|
+
#
|
263
|
+
# +param+ void
|
264
|
+
# +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
|
265
|
+
#
|
266
|
+
def device_vendors
|
267
|
+
return remote("device/vendors", nil) unless @config['use_local']
|
268
|
+
@reply = @device.get_reply if @device.local_vendors
|
269
|
+
set_error @device.get_status, @device.get_message
|
270
|
+
end
|
271
|
+
|
272
|
+
# List all models for a given vendor
|
273
|
+
#
|
274
|
+
# +param+ string vendor The device vendor eg Apple
|
275
|
+
# +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
|
276
|
+
#
|
277
|
+
def device_models(vendor)
|
278
|
+
unless @config['use_local']
|
279
|
+
return remote "device/models/#{vendor}", nil
|
280
|
+
end
|
281
|
+
|
282
|
+
if @device.local_models vendor
|
283
|
+
@reply = @device.get_reply
|
284
|
+
end
|
285
|
+
|
286
|
+
set_error @device.get_status, @device.get_message
|
287
|
+
end
|
288
|
+
|
289
|
+
# Find properties for a specific device
|
290
|
+
#
|
291
|
+
# +param+ string vendor The device vendor eg. Nokia
|
292
|
+
# +param+ string model The deviec model eg. N95
|
293
|
+
# +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
|
294
|
+
#
|
295
|
+
def device_view(vendor, model)
|
296
|
+
unless @config['use_local']
|
297
|
+
return remote "device/view/#{vendor}/#{model}", nil
|
298
|
+
end
|
299
|
+
if @device.local_view vendor, model
|
300
|
+
@reply = @device.get_reply
|
301
|
+
end
|
302
|
+
set_error @device.get_status, @device.get_message
|
303
|
+
end
|
304
|
+
|
305
|
+
# Find which devices have property 'X'.
|
306
|
+
#
|
307
|
+
# +param+ string key Property to inquire about eg 'network', 'connectors' etc...
|
308
|
+
# +param+ string value Value to inquire about eg 'CDMA', 'USB' etc ...
|
309
|
+
# +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
|
310
|
+
#
|
311
|
+
def device_what_has(key, value)
|
312
|
+
unless @config['use_local']
|
313
|
+
return remote("device/whathas/#{key}/#{value}", nil)
|
314
|
+
end
|
315
|
+
|
316
|
+
if @device.local_what_has key, value
|
317
|
+
@reply = @device.get_reply
|
318
|
+
end
|
319
|
+
|
320
|
+
set_error @device.get_status, @device.get_message
|
321
|
+
end
|
322
|
+
|
323
|
+
# Device Detect
|
324
|
+
#
|
325
|
+
# +param+ array data : Data for device detection : HTTP Headers usually
|
326
|
+
# +return+ bool true on success, false otherwise. Use getReply to inspect results on success.
|
327
|
+
#
|
328
|
+
def device_detect(data={})
|
329
|
+
id = data['id'].blank? ? @config['site_id'] : data['id']
|
330
|
+
request_body = @detect_request.merge data
|
331
|
+
fast_key = ''
|
332
|
+
|
333
|
+
# If caching enabled then check cache
|
334
|
+
unless @config['cache_requests'].blank?
|
335
|
+
request_body.each { |k, v| headers[k.downcase] = v }
|
336
|
+
headers = headers.sort.to_h
|
337
|
+
fast_key = JSON.generate(headers).gsub(/ /, '')
|
338
|
+
if reply = @cache.read(fast_key)
|
339
|
+
@reply = reply
|
340
|
+
@raw_reply = ''
|
341
|
+
return set_error 0, "OK"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
if @config['use_local']
|
346
|
+
# log("Starting Local Detection")
|
347
|
+
result = @device.local_detect(request_body)
|
348
|
+
set_error @device.get_status, @device.get_message
|
349
|
+
set_reply @device.get_reply
|
350
|
+
# log"Finishing Local Detection : result is #{result}"
|
351
|
+
# Log unknown headers if enabled
|
352
|
+
if @config['log_unknown'] == true and not result
|
353
|
+
send_remote_syslog request_body
|
354
|
+
end
|
355
|
+
else
|
356
|
+
# log "Starting API Detection"
|
357
|
+
result = remote "device/detect/#{id}", request_body
|
358
|
+
# log "Finishing API Detection : result is #{result}"
|
359
|
+
end
|
360
|
+
|
361
|
+
# If we got a result then cache it
|
362
|
+
if result and not @config['cache_requests'].blank?
|
363
|
+
@cache.write fast_key, reply
|
364
|
+
end
|
365
|
+
result
|
366
|
+
end
|
367
|
+
|
368
|
+
# Fetch an archive from handset detection which contains all the device specs and matching trees as individual json files.
|
369
|
+
#
|
370
|
+
# +param+ void
|
371
|
+
# +return+ hd_specs data on success, false otherwise
|
372
|
+
#
|
373
|
+
def device_fetch_archive
|
374
|
+
path = File.join(@config['filesdir'], "ultimate.zip")
|
375
|
+
unless @config['local_archive_source'].blank?
|
376
|
+
FileUtils.cp File.join(@config['local_archive_source'], "ultimate.zip"), path
|
377
|
+
return install_archive path
|
378
|
+
end
|
379
|
+
|
380
|
+
return false unless remote("device/fetcharchive", '', 'zip')
|
381
|
+
data = get_raw_reply
|
382
|
+
if data.blank?
|
383
|
+
return set_error 299, 'Error : FetchArchive failed. Bad Download. File is zero length'
|
384
|
+
elsif data.length < 9000000
|
385
|
+
trythis = JSON.parse data
|
386
|
+
if trythis and trythis.include?('status') and trythis.include('message')
|
387
|
+
return set_error trythis['status'].to_i, trythis['message']
|
388
|
+
end
|
389
|
+
set_error 299, "Error : FetchArchive failed. Bad Download. File too short at #{data.length} bytes."
|
390
|
+
end
|
391
|
+
|
392
|
+
begin
|
393
|
+
File.open(path, 'w') { |f| f.write(get_raw_reply) }
|
394
|
+
rescue
|
395
|
+
return set_error 299, "Error : FetchArchive failed. Could not write #{path}"
|
396
|
+
end
|
397
|
+
return install_archive path
|
398
|
+
end
|
399
|
+
|
400
|
+
# Community Fetch Archive - Fetch the community archive version
|
401
|
+
#
|
402
|
+
# +param+ void
|
403
|
+
# +return+ hd_specs data on success, false otherwise
|
404
|
+
#
|
405
|
+
def community_fetch_archive
|
406
|
+
path = File.join(@config['filesdir'], "communityultimate.zip")
|
407
|
+
unless @config['local_archive_source'].blank?
|
408
|
+
FileUtils.cp File.join(@config['local_archive_source'], "communityultimate.zip"), path
|
409
|
+
return install_archive path
|
410
|
+
end
|
411
|
+
|
412
|
+
return false unless remote "community/fetcharchive", '', 'zip', false
|
413
|
+
data = get_raw_reply
|
414
|
+
if data.blank?
|
415
|
+
return set_error 299, 'Error : FetchArchive failed. Bad Download. File is zero length'
|
416
|
+
elsif data.length < 900000
|
417
|
+
trythis = JSON.parse data
|
418
|
+
if not trythis.blank? and trythis.include?('status') and trythis.include?('message')
|
419
|
+
return set_error trythis['status'].to_int, trythis['message']
|
420
|
+
end
|
421
|
+
return set_error 299, "Error : FetchArchive failed. Bad Download. File too short at #{data.length} bytes."
|
422
|
+
end
|
423
|
+
|
424
|
+
begin
|
425
|
+
File.open(path, 'w') { |f| f.write data }
|
426
|
+
rescue
|
427
|
+
return set_error 299, "Error : FetchArchive failed. Could not write #{path}"
|
428
|
+
end
|
429
|
+
return install_archive path
|
430
|
+
end
|
431
|
+
|
432
|
+
# Install an ultimate archive file
|
433
|
+
#
|
434
|
+
# +param+ string $file Fully qualified path to file
|
435
|
+
# +return+ boolean true on success, false otherwise
|
436
|
+
#
|
437
|
+
def install_archive(file)
|
438
|
+
# Unzip the archive and cache the individual files
|
439
|
+
Zip::File.open(file) do |zip_file|
|
440
|
+
# return $this->setError(299, "Error : Failed to open ". $file)
|
441
|
+
zip_file.each do |entry|
|
442
|
+
filename = File.join Dir.tmpdir, entry.name
|
443
|
+
entry.extract(filename) {true}
|
444
|
+
@store.move_in filename, entry.name
|
445
|
+
end
|
446
|
+
end
|
447
|
+
true
|
448
|
+
end
|
449
|
+
|
450
|
+
# This method can indicate if using the js Helper would yeild more accurate results.
|
451
|
+
#
|
452
|
+
# +param+ hash headers
|
453
|
+
# +return+ true if helpful, false otherwise.
|
454
|
+
#
|
455
|
+
def is_helper_useful(headers)
|
456
|
+
@device.is_helper_useful headers
|
457
|
+
end
|
458
|
+
end
|