wurfl_device 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage/
4
+ doc/
5
+ pkg/
6
+ *.gem
7
+ .bundle/
8
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE ADDED
File without changes
File without changes
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'bundler'
5
+ Bundler::GemHelper.install_tasks
6
+ rescue LoadError
7
+ $stderr.puts "Bundler not installed. You should install it with: gem install bundler"
8
+ end
9
+
10
+ $LOAD_PATH << File.expand_path('./lib', File.dirname(__FILE__))
11
+ require 'wurfl_device/version'
12
+
13
+ begin
14
+ require 'rspec/core/rake_task'
15
+
16
+ RSpec::Core::RakeTask.new
17
+
18
+ if RUBY_VERSION >= '1.9'
19
+ RSpec::Core::RakeTask.new(:cov) do |t|
20
+ ENV['ENABLE_SIMPLECOV'] = '1'
21
+ t.ruby_opts = '-w'
22
+ t.rcov_opts = %q[-Ilib --exclude "spec/*,gems/*"]
23
+ end
24
+ end
25
+ rescue LoadError
26
+ $stderr.puts "RSpec not available. Install it with: gem install rspec-core rspec-expectations rr faker"
27
+ end
28
+
29
+ task :default => :spec
30
+
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
4
+
5
+ require 'wurfl_device/cli'
6
+
7
+ begin
8
+ WurflDevice::CLI.start
9
+ rescue WurflDevice::WurflDeviceError => e
10
+ WurflDevice.ui.error e.message
11
+ WurflDevice.ui.debug e.backtrace.join("\n")
12
+ exit e.status_code
13
+ rescue Interrupt => e
14
+ WurflDevice.ui.error "\nQuitting..."
15
+ WurflDevice.ui.debug e.backtrace.join("\n")
16
+ exit 1
17
+ end
@@ -0,0 +1,244 @@
1
+ require 'etc'
2
+ require 'redis'
3
+
4
+ require 'wurfl_device/version'
5
+
6
+ module WurflDevice
7
+ autoload :Capability, 'wurfl_device/capability'
8
+ autoload :Constants, 'wurfl_device/constants'
9
+ autoload :Device, 'wurfl_device/device'
10
+ autoload :Handset, 'wurfl_device/handset'
11
+ autoload :UI, 'wurfl_device/ui'
12
+ autoload :CLI, 'wurfl_device/cli'
13
+ autoload :UserAgentMatcher, 'wurfl_device/user_agent_matcher'
14
+ autoload :XmlLoader, 'wurfl_device/xml_loader'
15
+
16
+ class WurflDeviceError < StandardError
17
+ def self.status_code(code = nil)
18
+ define_method(:status_code) { code }
19
+ end
20
+ end
21
+
22
+ class CacheError < WurflDeviceError; status_code(10); end
23
+
24
+ class << self
25
+ attr_writer :ui, :db
26
+
27
+ def ui
28
+ @ui ||= UI.new
29
+ end
30
+
31
+ def db
32
+ @db ||= Redis.new(:db => Constants::DB_INDEX)
33
+ end
34
+
35
+ def tmp_dir
36
+ File.join(Etc.systmpdir, 'wurfl_device')
37
+ end
38
+
39
+ def get_actual_device(device_id)
40
+ capabilities = Capability.new
41
+ actual_device = get_actual_device_raw(device_id)
42
+ return nil if actual_device.nil?
43
+ actual_device.each_pair do |key, value|
44
+ if key =~ /^(.+)\:(.+)$/i
45
+ capabilities[$1] ||= Capability.new
46
+ capabilities[$1][$2] = parse_string_value(value)
47
+ else
48
+ capabilities[key] = parse_string_value(value)
49
+ end
50
+ end
51
+ capabilities
52
+ end
53
+
54
+ def get_device_from_id(device_id)
55
+ device = Device.new(device_id)
56
+ return device if device.is_valid?
57
+ return nil
58
+ end
59
+
60
+ def get_device_from_ua(user_agent)
61
+ matcher = UserAgentMatcher.new.match(user_agent)
62
+ return matcher.device
63
+ end
64
+
65
+ def get_device_from_ua_cache(user_agent)
66
+ cached_device = db.hget(Constants::WURFL_USER_AGENTS_CACHED, user_agent)
67
+ return Marshal::load(cached_device) unless cached_device.nil?
68
+ cached_device = db.hget(Constants::WURFL_USER_AGENTS, user_agent)
69
+ return Marshal::load(cached_device) unless cached_device.nil?
70
+ return nil
71
+ end
72
+
73
+ def save_device_in_ua_cache(user_agent, device)
74
+ db.hset(Constants::WURFL_USER_AGENTS_CACHED, user_agent, Marshal::dump(device))
75
+ end
76
+
77
+ def get_info
78
+ db.hgetall(Constants::WURFL_INFO)
79
+ end
80
+
81
+ # cache related
82
+ def clear_user_agent_cache
83
+ db.del(Constants::WURFL_USER_AGENTS_CACHED)
84
+ end
85
+
86
+ def clear_cache
87
+ db.keys("#{Constants::WURFL}*").each { |k| db.del k }
88
+ end
89
+
90
+ def clear_devices
91
+ db.keys("#{Constants::WURFL_DEVICES}*").each { |k| db.del k }
92
+ db.del(Contants::WURFL_INITIALIZED)
93
+ db.del(Contants::WURFL_INFO)
94
+ end
95
+
96
+ def get_user_agents
97
+ db.hkeys(Constants::WURFL_USER_AGENTS)
98
+ end
99
+
100
+ def get_user_agents_in_cache
101
+ db.hkeys(Constants::WURFL_USER_AGENTS_CACHED)
102
+ end
103
+
104
+ def get_user_agents_in_index(matcher)
105
+ db.hgetall("#{Constants::WURFL_DEVICES_INDEX}#{matcher}")
106
+ end
107
+
108
+ def get_actual_device_raw(device_id)
109
+ db.hgetall("#{Constants::WURFL_DEVICES}#{device_id}")
110
+ end
111
+
112
+ def get_devices
113
+ db.keys("#{Constants::WURFL_DEVICES}*")
114
+ end
115
+
116
+ def get_indexes
117
+ db.keys("#{Constants::WURFL_DEVICES_INDEX}*")
118
+ end
119
+
120
+ def rebuild_user_agent_cache
121
+ # update the cache's
122
+ get_indexes.each { |k| db.del k }
123
+ db.del(Constants::WURFL_USER_AGENTS)
124
+ db.del(Constants::WURFL_DEVICES_INDEX)
125
+
126
+ get_devices.each do |device_id|
127
+ device_id.gsub!(Constants::WURFL_DEVICES, '')
128
+ actual_device = get_actual_device(device_id)
129
+ next if actual_device.nil?
130
+ user_agent = actual_device.user_agent
131
+ next if user_agent.nil? || user_agent.empty?
132
+ db.hset(Constants::WURFL_USER_AGENTS, user_agent, Marshal::dump(Device.new(device_id)))
133
+
134
+ matcher = UserAgentMatcher.get_index(user_agent)
135
+ db.hset("#{Constants::WURFL_DEVICES_INDEX}#{matcher}", user_agent, device_id)
136
+ end
137
+
138
+ get_user_agents_in_cache.each do |user_agent|
139
+ db.hdel(Constants::WURFL_USER_AGENTS_CACHED, user_agent)
140
+ UserAgentMatcher.new.match(user_agent)
141
+ end
142
+ end
143
+
144
+ def is_initialized?
145
+ status = parse_string_value(db.get(Constants::WURFL_INITIALIZED))
146
+ return false if status.nil?
147
+ return false unless status.is_a?(TrueClass)
148
+ true
149
+ end
150
+
151
+ def initialize_cache
152
+ # make sure only process can initialize at a time
153
+ # don't initialize if there is another initializing
154
+ lock_the_cache_for_initializing do
155
+ db.set(Constants::WURFL_INITIALIZED, false)
156
+
157
+ # download & parse the wurfl xml
158
+ (devices, version, last_updated) = XmlLoader.load_xml_file(download_wurfl_xml_file) do |capabilities|
159
+ device_id = capabilities.delete('id')
160
+ next if device_id.nil? || device_id.empty?
161
+ db.del("#{Constants::WURFL_DEVICES}#{device_id}")
162
+ user_agent = capabilities.delete('user_agent')
163
+ fall_back = capabilities.delete('fall_back')
164
+
165
+ device_id.strip! unless device_id.nil?
166
+ user_agent.strip! unless user_agent.nil?
167
+ fall_back.strip! unless fall_back.nil?
168
+
169
+ db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "id", device_id)
170
+ db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "user_agent", user_agent)
171
+ db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "fall_back", fall_back)
172
+
173
+ capabilities.each_pair do |key, value|
174
+ if value.is_a?(Hash)
175
+ value.each_pair do |k, v|
176
+ db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "#{key.to_s}:#{k.to_s}", v)
177
+ end
178
+ else
179
+ db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "#{key.to_s}", value)
180
+ end
181
+ end
182
+ end
183
+
184
+ db.set(Constants::WURFL_INITIALIZED, true)
185
+ db.hset(Constants::WURFL_INFO, "version", version)
186
+ db.hset(Constants::WURFL_INFO, "last_updated", Time.now)
187
+ end
188
+ end
189
+
190
+ def parse_string_value(value)
191
+ return value unless value.is_a?(String)
192
+ # convert to utf-8 (wurfl.xml encoding format)
193
+ value.force_encoding('UTF-8')
194
+ return false if value =~ /^false/i
195
+ return true if value =~ /^true/i
196
+ return value.to_i if (value == value.to_i.to_s)
197
+ return value.to_f if (value == value.to_f.to_s)
198
+ value
199
+ end
200
+ protected
201
+ def download_wurfl_xml_file
202
+ wurfl_xml_source = "http://sourceforge.net/projects/wurfl/files/WURFL/2.2/wurfl-2.2.xml.gz"
203
+ FileUtils.mkdir_p(WurflDevice.tmp_dir)
204
+ FileUtils.cd(WurflDevice.tmp_dir)
205
+ `wget --timeout=60 -qN -- #{wurfl_xml_source} > /dev/null`
206
+ raise "Failed to download wurfl-latest.xml.gz" unless $? == 0
207
+
208
+ wurfl_xml_filename = File.basename(wurfl_xml_source)
209
+ `gunzip -qc #{wurfl_xml_filename} > wurfl.xml`
210
+ raise 'Failed to unzip wurfl-latest.xml.gz' unless $? == 0
211
+
212
+ wurfl_xml_file_extracted = File.join(WurflDevice.tmp_dir, 'wurfl.xml')
213
+ raise "wurfl.xml does not exists!" unless File.exists?(wurfl_xml_file_extracted)
214
+
215
+ wurfl_xml_file_extracted
216
+ end
217
+
218
+ def lock_the_cache_for_initializing
219
+ start_at = Time.now
220
+ success = false
221
+ while Time.now - start_at < Constants::LOCK_TIMEOUT
222
+ success = true and break if try_lock
223
+ sleep Constants::LOCK_SLEEP
224
+ end
225
+ if block_given? and success
226
+ yield
227
+ unlock
228
+ end
229
+ end
230
+
231
+ def try_lock
232
+ now = Time.now.to_f
233
+ @expires_at = now + Constants::LOCK_EXPIRE
234
+ return true if db.setnx(Constants::WURFL_INITIALIZING, @expires_at)
235
+ return false if db.get(Constants::WURFL_INITIALIZING).to_f > now
236
+ return true if db.getset(Constants::WURFL_INITIALIZING, @expires_at).to_f <= now
237
+ return false
238
+ end
239
+
240
+ def unlock(force=false)
241
+ db.del(Constants::WURFL_INITIALIZING) if db.get(Constants::WURFL_INITIALIZING).to_f == @expires_at or force
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,61 @@
1
+ module WurflDevice
2
+ class Capability < ::Hash
3
+ def initialize(hash={})
4
+ super()
5
+ hash.each do |key, value|
6
+ self[convert_key(key)] = value
7
+ end
8
+ end
9
+
10
+ def [](key)
11
+ super(convert_key(key))
12
+ end
13
+
14
+ def []=(key, value)
15
+ super(convert_key(key), value)
16
+ end
17
+
18
+ def delete(key)
19
+ super(convert_key(key))
20
+ end
21
+
22
+ def values_at(*indices)
23
+ indices.collect { |key| self[convert_key(key)] }
24
+ end
25
+
26
+ def merge(other)
27
+ dup.merge!(other)
28
+ end
29
+
30
+ def merge!(other)
31
+ other.each do |key, value|
32
+ self[convert_key(key)] = value
33
+ end
34
+ self
35
+ end
36
+
37
+ protected
38
+ def convert_key(key)
39
+ key.is_a?(Symbol) ? key.to_s : key
40
+ end
41
+
42
+ # Magic predicates. For instance:
43
+ #
44
+ # capability.force? # => !!options['force']
45
+ # capability.shebang # => "/usr/lib/local/ruby"
46
+ # capability.test_framework?(:rspec) # => options[:test_framework] == :rspec
47
+ #
48
+ def method_missing(method, *args, &block)
49
+ method = method.to_s
50
+ if method =~ /^(\w+)\?$/
51
+ if args.empty?
52
+ !!self[$1]
53
+ else
54
+ self[$1] == args.first
55
+ end
56
+ else
57
+ self[method]
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,124 @@
1
+ require 'thor'
2
+ require 'wurfl_device'
3
+
4
+ module WurflDevice
5
+ class CLI < Thor
6
+ include Thor::Actions
7
+
8
+ def initialize(*)
9
+ super
10
+ the_shell = (options["no-color"] ? Thor::Shell::Basic.new : shell)
11
+ WurflDevice.ui = UI::Shell.new(the_shell)
12
+ WurflDevice.ui.debug! if options["verbose"]
13
+ end
14
+
15
+ check_unknown_options!
16
+
17
+ default_task :help
18
+
19
+ desc "help", "Show this message"
20
+ def help(cli=nil)
21
+ version
22
+ WurflDevice.ui.info "http://github.com/aputs/wurfl_device for README"
23
+ WurflDevice.ui.info ""
24
+ super
25
+ end
26
+
27
+ desc "dump DEVICE_ID|USER_AGENT", "display capabilities DEVICE_ID|USER_AGENT"
28
+ method_option :json, :type => :boolean, :banner => "show the dump in json format", :aliases => "-j"
29
+ method_option :yaml, :type => :boolean, :banner => "show the dump in yaml format", :aliases => "-y"
30
+ def dump(device_id)
31
+ device = WurflDevice.get_device_from_id(device_id)
32
+ device = WurflDevice.get_device_from_ua(device_id, use_cache) if device.nil?
33
+
34
+ if options.json?
35
+ WurflDevice.ui.info device.capabilities.to_json
36
+ else
37
+ WurflDevice.ui.info device.capabilities.to_yaml
38
+ end
39
+ end
40
+
41
+ desc "list", "list user agent cache list"
42
+ def list
43
+ WurflDevice.get_user_agents_in_cache.each do |user_agent|
44
+ device = WurflDevice.get_device_from_ua_cache(user_agent)
45
+ device_id = ''
46
+ device_id = device.id unless device.nil?
47
+ WurflDevice.ui.info user_agent + ":" + device_id
48
+ end
49
+ end
50
+
51
+ desc "update", "update the wurfl cache"
52
+ method_option "clear-all", :type => :boolean, :banner => "remove all wurfl cache related"
53
+ method_option "clear-dev", :type => :boolean, :banner => "clear the device cache first before updating"
54
+ method_option "clear-ua", :type => :boolean, :banner => "remove all wurfl user agents cache"
55
+ def update
56
+ opts = options.dup
57
+ if opts['clear-all']
58
+ WurflDevice.ui.info "clearing all cache entries."
59
+ WurflDevice.clear_devices
60
+ WurflDevice.clear_cache
61
+ end
62
+ if opts['clear-ua']
63
+ WurflDevice.ui.info "clearing user agent cache."
64
+ WurflDevice.clear_user_agent_cache
65
+ end
66
+ if opts['clear-dev']
67
+ WurflDevice.ui.info "clearing device cache."
68
+ WurflDevice.clear_devices
69
+ end
70
+ WurflDevice.ui.info "updating wurfl devices cache."
71
+ WurflDevice.initialize_cache
72
+ WurflDevice.ui.info "rebuilding cache."
73
+ WurflDevice.rebuild_user_agent_cache
74
+ WurflDevice.ui.info "done."
75
+ WurflDevice.ui.info ""
76
+ status true
77
+ end
78
+
79
+ desc "rebuild", "rebuild the existing user_agents cache"
80
+ def rebuild
81
+ WurflDevice.ui.info "rebuilding the user_agents cache."
82
+ WurflDevice.rebuild_user_agent_cache
83
+ WurflDevice.ui.info "done."
84
+ end
85
+
86
+ desc "status", "show wurfl cache information"
87
+ def status(skip_version=false)
88
+ version unless skip_version
89
+ unless WurflDevice.is_initialized?
90
+ WurflDevice.ui.info "cache is not initialized"
91
+ return
92
+ end
93
+ info = WurflDevice.get_info
94
+ version = info['version'] || 'none'
95
+ last_update = info['last_updated'] || 'unknown'
96
+ WurflDevice.ui.info "cache info:"
97
+ WurflDevice.ui.info " wurfl-xml version: " + version
98
+ WurflDevice.ui.info " cache last updated: " + last_update
99
+ devices = WurflDevice.get_devices
100
+ user_agents = WurflDevice.get_user_agents
101
+ user_agents_message = ''
102
+ user_agents_message = " (warning count should be equal to devices count)" if devices.length != user_agents.length
103
+ WurflDevice.ui.info " " + commify(devices.length) + " device id's"
104
+ WurflDevice.ui.info " " + commify(user_agents.length) + " exact user agents" + user_agents_message
105
+ WurflDevice.ui.info " " + commify(WurflDevice.get_user_agents_in_cache.length) + " user agents found in cache"
106
+ WurflDevice.ui.info ""
107
+ end
108
+ map %w(stats stat info) => :status
109
+
110
+ desc "version", "show the wurfl_device version information"
111
+ def version
112
+ WurflDevice.ui.info "wurfl_device version #{WurflDevice::VERSION.freeze}"
113
+ end
114
+ map %w(-v --version) => :version
115
+ private
116
+ def commify(n)
117
+ n.to_s =~ /([^\.]*)(\..*)?/
118
+ int, dec = $1.reverse, $2 ? $2 : ""
119
+ while int.gsub!(/(,|\.|^)(\d{3})(\d)/, '\1\2,\3')
120
+ end
121
+ int.reverse + dec
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module WurflDevice
3
+ class Constants
4
+ LOCK_TIMEOUT = 3
5
+ LOCK_EXPIRE = 10
6
+ LOCK_SLEEP = 0.1
7
+
8
+ DB_INDEX = "7".freeze
9
+ GENERIC = 'generic'
10
+ WURFL = "wurfl:"
11
+ WURFL_INFO = "wurfl:info"
12
+ WURFL_DEVICES = "wurfl:devices:"
13
+ WURFL_DEVICES_INDEX = "wurfl:index:"
14
+ WURFL_INITIALIZED = "wurfl:is_initialized"
15
+ WURFL_INITIALIZING = "wurfl:is_initializing"
16
+ WURFL_USER_AGENTS = "wurfl:user_agents"
17
+ WURFL_USER_AGENTS_CACHED = "wurfl:user_agents_cached"
18
+
19
+ USER_AGENT_MATCHERS =
20
+ [
21
+ "Alcatel", "Android", "AOL", "Apple", "BenQ", "BlackBerry", "Bot", "CatchAll", "Chrome", "DoCoMo",
22
+ "Firefox", "Grundig", "HTC", "Kddi", "Konqueror", "Kyocera", "LG", "Mitsubishi", "Motorola", "MSIE",
23
+ "Nec", "Nintendo", "Nokia", "Opera", "OperaMini", "Panasonic", "Pantech", "Philips", "Portalmmm", "Qtek",
24
+ "Safari", "Sagem", "Samsung", "Sanyo", "Sharp", "Siemens", "SonyEricsson", "SPV", "Toshiba", "Vodafone", "WindowsCE"
25
+ ]
26
+
27
+ MOBILE_BROWSERS = [
28
+ 'cldc', 'symbian', 'midp', 'j2me', 'mobile', 'wireless', 'palm', 'phone', 'pocket pc', 'pocketpc', 'netfront',
29
+ 'bolt', 'iris', 'brew', 'openwave', 'windows ce', 'wap2.', 'android', 'opera mini', 'opera mobi', 'maemo', 'fennec',
30
+ 'blazer', 'vodafone', 'wp7', 'armv'
31
+ ]
32
+
33
+ ROBOTS = [ 'bot', 'crawler', 'spider', 'novarra', 'transcoder', 'yahoo! searchmonkey', 'yahoo! slurp', 'feedfetcher-google', 'toolbar', 'mowser' ]
34
+ DESKTOP_BROWSERS = [ 'slcc1', '.net clr', 'wow64', 'media center pc', 'funwebproducts', 'macintosh', 'aol 9.', 'america online browser', 'googletoolbar' ]
35
+ end
36
+ end
@@ -0,0 +1,82 @@
1
+ require 'yaml'
2
+ require 'redis'
3
+
4
+ module WurflDevice
5
+ class Device
6
+ attr_accessor :capabilities
7
+
8
+ def initialize(device_id=nil)
9
+ raise WurflDevice::CacheError, "wurfl cache is not initialized" unless WurflDevice.is_initialized?
10
+ if device_id.nil?
11
+ @capabilities = WurflDevice.get_actual_device(WurflDevice::Constants::GENERIC)
12
+ else
13
+ @capabilities = build_device(device_id)
14
+ end
15
+ end
16
+
17
+ def is_valid?
18
+ return false if @capabilities.nil?
19
+ return false if @capabilities.id.nil?
20
+ return false if @capabilities.id.empty?
21
+ return true
22
+ end
23
+
24
+ def is_generic?
25
+ is_valid? && @capabilities.id !~ Regexp.new(WurflDevice::Constants::GENERIC, Regexp::IGNORECASE)
26
+ end
27
+
28
+ def build_device(device_id)
29
+ capabilities = Capability.new
30
+ device = WurflDevice.get_actual_device_raw(device_id)
31
+ return nil if device.nil?
32
+
33
+ capabilities['fall_back_tree'] ||= Array.new
34
+
35
+ if !device['fall_back'].nil? && !device['fall_back'].empty? && device['id'] != WurflDevice::Constants::GENERIC
36
+ fall_back = build_device(device['fall_back'])
37
+ unless fall_back.nil?
38
+ capabilities['fall_back_tree'].unshift(fall_back['id'])
39
+ fall_back.each_pair do |key, value|
40
+ if value.kind_of?(Hash)
41
+ capabilities[key] ||= Capability.new
42
+ value.each_pair do |k, v|
43
+ capabilities[key][k] = v
44
+ end
45
+ elsif value.is_a?(Array)
46
+ capabilities[key] ||= Array.new
47
+ capabilities[key] |= value
48
+ else
49
+ capabilities[key] = value
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ device.each_pair do |key, value|
56
+ if key =~ /^(.+)\:(.+)$/i
57
+ capabilities[$1] ||= Capability.new
58
+ capabilities[$1][$2] = WurflDevice.parse_string_value(value)
59
+ else
60
+ capabilities[key] = WurflDevice.parse_string_value(value)
61
+ end
62
+ end
63
+
64
+ capabilities
65
+ end
66
+
67
+ # Magic predicates
68
+ # slower than going directly to capabilities
69
+ def method_missing(method, *args, &block)
70
+ return nil if @capabilities.nil?
71
+ meth = method.to_s
72
+ meth.gsub!(/\?/, '')
73
+ return @capabilities.send(method, args, block) if @capabilities.has_key?(meth)
74
+ @capabilities.each_pair do |key, value|
75
+ if value.is_a?(Hash)
76
+ return value.send(method, args, block) if value.has_key?(meth)
77
+ end
78
+ end
79
+ return nil
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,73 @@
1
+ require 'rubygems/user_interaction'
2
+
3
+ module WurflDevice
4
+ class UI
5
+ def warn(message)
6
+ end
7
+
8
+ def debug(message)
9
+ end
10
+
11
+ def error(message)
12
+ end
13
+
14
+ def info(message)
15
+ end
16
+
17
+ def confirm(message)
18
+ end
19
+
20
+ class Shell < UI
21
+ attr_writer :shell
22
+
23
+ def initialize(shell)
24
+ @shell = shell
25
+ @quiet = false
26
+ @debug = ENV['DEBUG']
27
+ end
28
+
29
+ def debug(msg)
30
+ @shell.say(msg) if @debug && !@quiet
31
+ end
32
+
33
+ def info(msg)
34
+ @shell.say(msg) if !@quiet
35
+ end
36
+
37
+ def confirm(msg)
38
+ @shell.say(msg, :green) if !@quiet
39
+ end
40
+
41
+ def warn(msg)
42
+ @shell.say(msg, :yellow)
43
+ end
44
+
45
+ def error(msg)
46
+ @shell.say(msg, :red)
47
+ end
48
+
49
+ def be_quiet!
50
+ @quiet = true
51
+ end
52
+
53
+ def debug!
54
+ @debug = true
55
+ end
56
+ end
57
+
58
+ class RGProxy < ::Gem::SilentUI
59
+ def initialize(ui)
60
+ @ui = ui
61
+ super()
62
+ end
63
+
64
+ def say(message)
65
+ if message =~ /native extensions/
66
+ @ui.info "with native extensions "
67
+ else
68
+ @ui.debug(message)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,156 @@
1
+ require 'text'
2
+
3
+ module WurflDevice
4
+ class UserAgentMatcher
5
+ attr_accessor :user_agent, :user_agent_cleaned, :device
6
+
7
+ def match(user_agent)
8
+ @user_agent = user_agent
9
+ @user_agent_cleaned = UserAgentMatcher.clean_user_agent(user_agent)
10
+ @device = nil
11
+
12
+ # exact match
13
+ @device = WurflDevice.get_device_from_ua_cache(@user_agent)
14
+ @device = WurflDevice.get_device_from_ua_cache(@user_agent_cleaned) if @device.nil?
15
+
16
+ # already in cache so return immediately
17
+ return self if !@device.nil? && @device.is_valid?
18
+
19
+ # ris match
20
+ if @device.nil?
21
+ user_agent = @user_agent
22
+ user_agent_list = WurflDevice.get_user_agents_in_index(UserAgentMatcher.get_index(user_agent)).sort { |a, b| a[0] <=> b[0] }
23
+ tolerance = UserAgentMatcher.first_slash(user_agent)-1
24
+ curlen = user_agent.length
25
+ while curlen >= tolerance
26
+ user_agent_list.map do |ua, device_id|
27
+ if ua.index(user_agent) == 0
28
+ @device = Device.new(device_id)
29
+ break
30
+ end
31
+ end
32
+ break unless @device.nil?
33
+ user_agent = user_agent.slice(0, curlen-1)
34
+ curlen = user_agent.length
35
+ end
36
+ end
37
+
38
+ # last attempts
39
+ if @device.nil?
40
+ user_agent = @user_agent
41
+ device_id = WurflDevice::Constants::GENERIC
42
+ device_id = 'opwv_v7_generic' if user_agent.index('UP.Browser/7')
43
+ device_id = 'opwv_v6_generic' if user_agent.index('UP.Browser/6')
44
+ device_id = 'upgui_generic' if user_agent.index('UP.Browser/5')
45
+ device_id = 'uptext_generic' if user_agent.index('UP.Browser/4')
46
+ device_id = 'uptext_generic' if user_agent.index('UP.Browser/3')
47
+ device_id = 'nokia_generic_series60' if user_agent.index('Series60')
48
+ device_id = 'generic_web_browser' if user_agent.index('Mozilla/4.0')
49
+ device_id = 'generic_web_browser' if user_agent.index('Mozilla/5.0')
50
+ device_id = 'generic_web_browser' if user_agent.index('Mozilla/6.0')
51
+ @device = WurflDevice.get_device_from_id(device_id)
52
+ end
53
+
54
+ WurflDevice.save_device_in_ua_cache(@user_agent, @device)
55
+
56
+ return self
57
+ end
58
+
59
+ class << self
60
+ def get_index(user_agent)
61
+ # create device index
62
+ matcher = 'Generic'
63
+ WurflDevice::Constants::USER_AGENT_MATCHERS.each do |m|
64
+ if Regexp.new(m, Regexp::IGNORECASE) =~ user_agent
65
+ matcher = m
66
+ break
67
+ end
68
+ end
69
+ return matcher
70
+ end
71
+
72
+ def clean_user_agent(user_agent)
73
+ user_agent = remove_up_link_from_ua(user_agent)
74
+
75
+ # remove nokia-msisdn header
76
+ user_agent = remove_nokia_msisdn(user_agent)
77
+
78
+ # clean up myphone id's
79
+ user_agent = user_agent.sub("'M', 'Y' 'P', 'H', 'O', 'N', 'E'", "MyPhone")
80
+
81
+ # remove serial numbers
82
+ user_agent = user_agent.sub(/\/SN\d{15}/, '/SNXXXXXXXXXXXXXXX')
83
+ user_agent = user_agent.sub(/\[(ST|TF|NT)\d+\]/, '')
84
+
85
+ # remove locale identifiers
86
+ user_agent = user_agent.sub(/([ ;])[a-zA-Z]{2}-[a-zA-Z]{2}([ ;\)])/, '\1xx-xx\2')
87
+
88
+ user_agent = normalize_blackberry(user_agent)
89
+ user_agent = normalize_android(user_agent)
90
+
91
+ return user_agent.strip
92
+ end
93
+
94
+ def first_slash(user_agent)
95
+ pos = user_agent.index('/')
96
+ return user_agent.length if pos.nil?
97
+ return pos
98
+ end
99
+
100
+ def second_slash(user_agent)
101
+ first = user_agent.index('/')
102
+ return user_agent.length if first.nil?
103
+ first = first + 1
104
+ second = user_agent.index('/', first)
105
+ return user_agent.length if second.nil?
106
+ return second
107
+ end
108
+
109
+ def first_space(user_agent)
110
+ pos = user_agent.index(' ')
111
+ return user_agent.length if pos.nil?
112
+ return pos
113
+ end
114
+
115
+ def first_open_paren(user_agent)
116
+ pos = user_agent.index('(')
117
+ return user_agent.length if pos.nil?
118
+ return pos
119
+ end
120
+
121
+ def remove_nokia_msisdn(user_agent)
122
+ if user_agent =~ /^(.+)NOKIA-MSISDN\:\ (.+)$/i
123
+ user_agent = $1
124
+ end
125
+ return user_agent
126
+ end
127
+
128
+ def remove_up_link_from_ua(user_agent)
129
+ pos = user_agent.index('UP.Link')
130
+ return user_agent unless pos
131
+ return user_agent.slice(0..(pos-1))
132
+ end
133
+
134
+ def normalize_blackberry(user_agent)
135
+ pos = user_agent.index('BlackBerry')
136
+ return user_agent if pos.nil?
137
+ return user_agent.slice(pos, user_agent.length - pos)
138
+ end
139
+
140
+ def normalize_android(user_agent)
141
+ return user_agent.sub(/(Android \d\.\d)([^; \/\)]+)/, '\1')
142
+ end
143
+
144
+ def parse(user_agent)
145
+ # grab all Agent/version strings as 'agents'
146
+ agents = Array.new
147
+ user_agent.split(/\s+/).each do |string|
148
+ if string =~ /\//
149
+ agents << string
150
+ end
151
+ end
152
+ return agents
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module WurflDevice
4
+ VERSION = "0.0.10".freeze
5
+ end
@@ -0,0 +1,49 @@
1
+ require 'etc'
2
+ require 'nokogiri'
3
+
4
+ module WurflDevice
5
+ class XmlLoader
6
+ def self.load_xml_file(wurfl_file, &blk)
7
+ devices = Hash.new
8
+
9
+ xml = Nokogiri::XML File.open(wurfl_file)
10
+
11
+ version = xml.xpath('//version/ver')[0].children.to_s rescue nil
12
+ last_updated = DateTime.parse(xml.xpath('//version/last_updated')[0].children.to_s) rescue nil
13
+
14
+ xml.xpath('//devices/*').each do |element|
15
+ wurfl_id = 'generic'
16
+ user_agent = 'generic'
17
+ fall_back = nil
18
+ if element.attributes["id"].to_s != "generic"
19
+ wurfl_id = element.attributes["id"].to_s
20
+ user_agent = element.attributes["user_agent"].to_s
21
+ user_agent = 'generic' if user_agent.empty?
22
+ fall_back = element.attributes["fall_back"].to_s
23
+ end
24
+
25
+ device = Hash.new
26
+ device['id'] = wurfl_id
27
+ device['user_agent'] = user_agent
28
+ device['fall_back'] = fall_back
29
+
30
+ element.xpath('.//*').each do |group|
31
+ group_id = group.attributes["id"].to_s
32
+ next if group_id.empty?
33
+ group_capa = Hash.new
34
+ group.xpath('.//*').each do |capability|
35
+ name = capability.attributes["name"].to_s
36
+ next if name.empty?
37
+ group_capa[name] = WurflDevice.parse_string_value(capability.attributes["value"].to_s)
38
+ end
39
+ device[group_id] = group_capa
40
+ end
41
+
42
+ devices[wurfl_id] = device
43
+ yield device if block_given?
44
+ end
45
+
46
+ [devices, version, last_updated]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+
4
+ describe WurflDevice do
5
+ describe :devices do
6
+ it "cache should be available" do
7
+ WurflDevice.is_initialized?.should == true
8
+ end
9
+
10
+ it "generic device should exists" do
11
+ device = WurflDevice.get_actual_device(WurflDevice::Constants::GENERIC)
12
+ device.id.should == WurflDevice::Constants::GENERIC
13
+ end
14
+
15
+ it "check for user agent matcher" do
16
+ user_agent = 'Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 Nokia6120c/6.01; Profile/MIDP-2.0 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413'
17
+ device = WurflDevice.get_device_from_ua(user_agent)
18
+ device.id.should == 'nokia_6120c_ver1_sub601'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ if RUBY_VERSION >= '1.9'
2
+ if ENV['ENABLE_SIMPLECOV']
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+ end
6
+ else
7
+ require 'rubygems'
8
+ end
9
+
10
+ require 'faker'
11
+ require 'rspec/core'
12
+
13
+ $LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
14
+
15
+ RSpec.configure do |config|
16
+ config.mock_with :rr
17
+ config.color_enabled = true
18
+ end
19
+
20
+ require 'wurfl_device'
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $LOAD_PATH.push File.expand_path("../lib", __FILE__)
4
+
5
+ require "wurfl_device/version"
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "wurfl_device"
9
+ s.version = WurflDevice::VERSION.dup
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ["Allan Ralph Hutalla"]
12
+ s.email = ["ahutalla@gmail.com"]
13
+ s.homepage = "http://github.com/aputs/wurfl_device"
14
+ s.summary = %q{Ruby client library for mobile handset detection}
15
+ s.description = %q{Ruby client library for mobile handset detection}
16
+
17
+ s.add_dependency 'thor'
18
+ s.add_dependency 'nokogiri'
19
+ s.add_dependency 'redis'
20
+ s.add_dependency 'text'
21
+
22
+ s.add_development_dependency 'bundler', '>= 1.0.10'
23
+ s.add_development_dependency 'rake', '>= 0.9.2'
24
+ s.add_development_dependency 'rspec-core', '~> 2.0'
25
+ s.add_development_dependency 'rspec-expectations', '~> 2.0'
26
+ s.add_development_dependency 'rr', '~> 1.0'
27
+ s.add_development_dependency 'faker', '~> 0.9'
28
+ s.add_development_dependency 'simplecov', '~> 0.5.3'
29
+
30
+ s.files = `git ls-files`.split("\n")
31
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
32
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
33
+ s.require_paths = ["lib"]
34
+ s.extra_rdoc_files = ["LICENSE", "README.md"]
35
+ end
metadata ADDED
@@ -0,0 +1,189 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wurfl_device
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.10
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Allan Ralph Hutalla
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: &66803460 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *66803460
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ requirement: &66802960 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *66802960
36
+ - !ruby/object:Gem::Dependency
37
+ name: redis
38
+ requirement: &66802500 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *66802500
47
+ - !ruby/object:Gem::Dependency
48
+ name: text
49
+ requirement: &66802060 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *66802060
58
+ - !ruby/object:Gem::Dependency
59
+ name: bundler
60
+ requirement: &66801540 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 1.0.10
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *66801540
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: &66801020 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 0.9.2
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *66801020
80
+ - !ruby/object:Gem::Dependency
81
+ name: rspec-core
82
+ requirement: &66800540 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: '2.0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *66800540
91
+ - !ruby/object:Gem::Dependency
92
+ name: rspec-expectations
93
+ requirement: &66800060 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ~>
97
+ - !ruby/object:Gem::Version
98
+ version: '2.0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *66800060
102
+ - !ruby/object:Gem::Dependency
103
+ name: rr
104
+ requirement: &66799560 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '1.0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *66799560
113
+ - !ruby/object:Gem::Dependency
114
+ name: faker
115
+ requirement: &66799000 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ~>
119
+ - !ruby/object:Gem::Version
120
+ version: '0.9'
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: *66799000
124
+ - !ruby/object:Gem::Dependency
125
+ name: simplecov
126
+ requirement: &66798340 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 0.5.3
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: *66798340
135
+ description: Ruby client library for mobile handset detection
136
+ email:
137
+ - ahutalla@gmail.com
138
+ executables:
139
+ - wurfldevice
140
+ extensions: []
141
+ extra_rdoc_files:
142
+ - LICENSE
143
+ - README.md
144
+ files:
145
+ - .gitignore
146
+ - Gemfile
147
+ - LICENSE
148
+ - README.md
149
+ - Rakefile
150
+ - bin/wurfldevice
151
+ - lib/wurfl_device.rb
152
+ - lib/wurfl_device/capability.rb
153
+ - lib/wurfl_device/cli.rb
154
+ - lib/wurfl_device/constants.rb
155
+ - lib/wurfl_device/device.rb
156
+ - lib/wurfl_device/ui.rb
157
+ - lib/wurfl_device/user_agent_matcher.rb
158
+ - lib/wurfl_device/version.rb
159
+ - lib/wurfl_device/xml_loader.rb
160
+ - spec/cache/device_spec.rb
161
+ - spec/spec_helper.rb
162
+ - wurfl_device.gemspec
163
+ homepage: http://github.com/aputs/wurfl_device
164
+ licenses: []
165
+ post_install_message:
166
+ rdoc_options: []
167
+ require_paths:
168
+ - lib
169
+ required_ruby_version: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ required_rubygems_version: !ruby/object:Gem::Requirement
176
+ none: false
177
+ requirements:
178
+ - - ! '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ requirements: []
182
+ rubyforge_project:
183
+ rubygems_version: 1.8.6
184
+ signing_key:
185
+ specification_version: 3
186
+ summary: Ruby client library for mobile handset detection
187
+ test_files:
188
+ - spec/cache/device_spec.rb
189
+ - spec/spec_helper.rb