wurfl_device 0.0.10

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