gricer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +84 -0
  3. data/Rakefile +49 -0
  4. data/app/assets/images/gricer/fluid/breadcrumb.png +0 -0
  5. data/app/assets/javascripts/gricer.js.coffee +85 -0
  6. data/app/assets/javascripts/gricer_backend_jquery.js.coffee +352 -0
  7. data/app/assets/javascripts/jquery.flot.js +2599 -0
  8. data/app/assets/javascripts/jquery.flot.pie.js +750 -0
  9. data/app/assets/javascripts/jquery.flot.resize.js +60 -0
  10. data/app/assets/javascripts/jquery.flot.symbol.js +70 -0
  11. data/app/assets/javascripts/worldmap.js +146 -0
  12. data/app/assets/stylesheets/gricer/fluid-jquery-ui.css.scss +1298 -0
  13. data/app/assets/stylesheets/gricer/fluid.css.scss +240 -0
  14. data/app/assets/stylesheets/gricer/helpers/css3.css.scss +21 -0
  15. data/app/controllers/gricer/base_controller.rb +141 -0
  16. data/app/controllers/gricer/capture_controller.rb +42 -0
  17. data/app/controllers/gricer/dashboard_controller.rb +18 -0
  18. data/app/controllers/gricer/requests_controller.rb +42 -0
  19. data/app/controllers/gricer/sessions_controller.rb +24 -0
  20. data/app/helpers/gricer/base_helper.rb +22 -0
  21. data/app/models/gricer/agent.rb +789 -0
  22. data/app/models/gricer/request.rb +239 -0
  23. data/app/models/gricer/session.rb +433 -0
  24. data/app/views/gricer/capture/index.html.erb +1 -0
  25. data/app/views/gricer/dashboard/_menu.html.erb +10 -0
  26. data/app/views/gricer/dashboard/_overview.html.erb +33 -0
  27. data/app/views/gricer/dashboard/index.html.erb +19 -0
  28. data/config/routes.rb +51 -0
  29. data/lib/gricer.rb +36 -0
  30. data/lib/gricer/action_controller/base.rb +28 -0
  31. data/lib/gricer/action_controller/track.rb +132 -0
  32. data/lib/gricer/active_model/statistics.rb +167 -0
  33. data/lib/gricer/config.rb +125 -0
  34. data/lib/gricer/engine.rb +9 -0
  35. data/lib/gricer/localization.rb +3 -0
  36. data/lib/tasks/gricer_tasks.rake +92 -0
  37. data/spec/controllers/gricer/base_controller_spec.rb +207 -0
  38. data/spec/controllers/gricer/capture_controller_spec.rb +44 -0
  39. data/spec/controllers/gricer/dashboard_controller_spec.rb +44 -0
  40. data/spec/controllers/gricer/requests_controller_spec.rb +36 -0
  41. data/spec/controllers/gricer/sessions_controller_spec.rb +37 -0
  42. data/spec/dummy/Rakefile +7 -0
  43. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  44. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  45. data/spec/dummy/app/assets/stylesheets/dashboard.css +4 -0
  46. data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
  47. data/spec/dummy/app/assets/stylesheets/sessions.css +4 -0
  48. data/spec/dummy/app/controllers/application_controller.rb +23 -0
  49. data/spec/dummy/app/controllers/dashboard_controller.rb +19 -0
  50. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  51. data/spec/dummy/app/helpers/dashboard_helper.rb +2 -0
  52. data/spec/dummy/app/views/dashboard/index.html.erb +236 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +16 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +48 -0
  56. data/spec/dummy/config/boot.rb +10 -0
  57. data/spec/dummy/config/cucumber.yml +9 -0
  58. data/spec/dummy/config/database.yml +25 -0
  59. data/spec/dummy/config/environment.rb +5 -0
  60. data/spec/dummy/config/environments/development.rb +27 -0
  61. data/spec/dummy/config/environments/production.rb +51 -0
  62. data/spec/dummy/config/environments/test.rb +39 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/inflections.rb +10 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  67. data/spec/dummy/config/initializers/session_store.rb +8 -0
  68. data/spec/dummy/config/initializers/wrap_parameters.rb +12 -0
  69. data/spec/dummy/config/locales/en.yml +5 -0
  70. data/spec/dummy/config/routes.rb +11 -0
  71. data/spec/dummy/db/schema.rb +241 -0
  72. data/spec/dummy/log/development.log +0 -0
  73. data/spec/dummy/public/404.html +26 -0
  74. data/spec/dummy/public/422.html +26 -0
  75. data/spec/dummy/public/500.html +26 -0
  76. data/spec/dummy/public/favicon.ico +0 -0
  77. data/spec/dummy/script/rails +6 -0
  78. data/spec/helpers/gricer/base_helper_spec.rb +28 -0
  79. data/spec/lib/gricer/action_controller/track_spec.rb +63 -0
  80. data/spec/models/gricer/agent_spec.rb +829 -0
  81. data/spec/models/gricer/request_spec.rb +145 -0
  82. data/spec/models/gricer/session_spec.rb +209 -0
  83. data/spec/routing/capture_routes_spec.rb +6 -0
  84. data/spec/routing/dashboard_routes_spec.rb +9 -0
  85. data/spec/routing/requests_routes_spec.rb +90 -0
  86. data/spec/routing/sessions_routes_spec.rb +115 -0
  87. data/spec/spec_helper.rb +23 -0
  88. metadata +185 -0
@@ -0,0 +1,42 @@
1
+ module Gricer
2
+ # This controller handles the request based statistics
3
+ class RequestsController < BaseController
4
+
5
+ private
6
+ # Set the basic collection to requests from browsers.
7
+ def basic_collection
8
+ Request.browsers
9
+ end
10
+
11
+ # Handle special fields
12
+ def handle_special_fields
13
+ if ['referer_host', 'referer_path', 'referer_params', 'search_engine', 'search_query'].include?(params[:field])
14
+ @items = @items.only_first_in_session
15
+ end
16
+
17
+ if ['search_engine', 'search_query'].include?(params[:field])
18
+ @items = @items.where("#{params[:field]} IS NOT NULL")
19
+ end
20
+
21
+ if params[:field] == 'entry_path'
22
+ params[:field] = 'path'
23
+ @items = @items.only_first_in_session
24
+ end
25
+
26
+ super
27
+ end
28
+
29
+ # Offer links to further details on some attributes
30
+ def further_details
31
+ {
32
+ 'referer_host' => 'referer_path',
33
+ 'referer_path' => 'referer_params',
34
+
35
+ 'agent.name' => 'agent.major_version',
36
+ 'agent.os' => 'agent.name',
37
+ 'agent.major_version' => 'agent.full_version',
38
+ 'agent.engine_name' => 'agent.engine_version',
39
+ }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ module Gricer
2
+ # This controller handles the session based statistics
3
+ class SessionsController < BaseController
4
+ private
5
+ # Set the basic collection to sessions from browsers.
6
+ def basic_collection
7
+ Session.browsers
8
+ end
9
+
10
+ # Offer links to further details on some attributes
11
+ def further_details
12
+ {
13
+ 'agent.name' => 'agent.major_version',
14
+ 'agent.os' => 'agent.name',
15
+ 'agent.major_version' => 'agent.full_version',
16
+ 'agent.engine_name' => 'agent.engine_version',
17
+ 'silverlight_major_version' => 'silverlight_version',
18
+ 'flash_major_version' => 'flash_version',
19
+ 'country' => 'city',
20
+ 'requested_locale_major' => 'requested_locale_minor',
21
+ }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module Gricer
2
+ # A helper with some basic features for formating Gricer's data
3
+ module BaseHelper
4
+ # Format a value as percentage
5
+ # @example
6
+ # ruby> format_percentage 0.4223
7
+ #
8
+ # => "42.23 %"
9
+ def format_percentage value
10
+ '%3.2f %' % (value * 100)
11
+ end
12
+
13
+ # Format a value of seconds as time range
14
+ # @example
15
+ # ruby> format_seconds 102
16
+ #
17
+ # => "00:01:42"
18
+ def format_seconds value
19
+ '%02d:%02d:%02d' % [(value/3600).floor,((value/60)%60).floor,(value%60)]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,789 @@
1
+ module Gricer
2
+ # ActiveRecord Model for User Agent Statistics
3
+ # @attr [String] request_header
4
+ # The current value of user agent string as within the HTTP request.
5
+ #
6
+ # @attr [String] name
7
+ # The current value of user agent's name.
8
+ # (e.g. 'Firefox', 'Chrome', 'Internet Explorer')
9
+ #
10
+ # @attr [String] full_version
11
+ # The current value of user agent's full version.
12
+ # This means it does include all sub version numbers.
13
+ #
14
+ # @attr [String] major_version
15
+ # The current value of user agent's major version.
16
+ # This means it does include only the first number after a dot.
17
+ #
18
+ # @attr [String] engine_name
19
+ # The current value of the name of the engine used by the user agent
20
+ #
21
+ # @attr [String] engine_version
22
+ # The current value of the version of the engine used by the user agent
23
+ #
24
+ # @attr [String] os
25
+ # The current value of the OS the user agent is hosted on.
26
+ #
27
+ # @attr [String] agent_class
28
+ # The current value of the classification of the user agent.
29
+ #
30
+ # See {AGENT_CLASSES} for possible values.
31
+ class Agent < ::ActiveRecord::Base
32
+ set_table_name "#{::Gricer::config.table_name_prefix}agents"
33
+ include ActiveModel::Statistics
34
+
35
+ has_many :requests, class_name: 'Gricer::Request', foreign_key: :agent_id, order: 'created_at ASC'
36
+ has_many :sessions, class_name: 'Gricer::Session', foreign_key: :session_id, order: 'created_at ASC'
37
+
38
+ before_create :guess_agent_type
39
+
40
+ # The agent class constant defines numeric values for the different agent types to be stored in a database.
41
+ AGENT_CLASSES = {
42
+ 0x1000 => :browser,
43
+ 0x2000 => :mobile_browser,
44
+ 0x0001 => :bot
45
+ }
46
+
47
+ # Filter out anything that is not a Browser or MobileBrowser
48
+ # @return [ActiveRecord::Relation]
49
+ def self.browsers
50
+ self.where("\"#{self.table_name}\".\"agent_class_id\" IN (?)", [0x1000, 0x2000])
51
+ end
52
+
53
+
54
+ def agent_class
55
+ AGENT_CLASSES[agent_class_id]
56
+ end
57
+
58
+ def agent_class= class_name
59
+ self.agent_class_id = nil
60
+ AGENT_CLASSES.each do |key, value|
61
+ if value == class_name
62
+ self.agent_class_id = key
63
+ break
64
+ end
65
+ end
66
+ end
67
+
68
+ # Guess the user agent data from the given request_header attribute and fill out the fields.
69
+ def guess_agent_type
70
+ return if request_header.blank?
71
+
72
+ agent_string = request_header.gsub(/[UI];[ ]*/, '')
73
+
74
+ void, self.os, x11_os, comp_os = agent_string.match(/\(([A-Za-z0-9]*)(?:[^;]*)[; ]*([A-Za-z]*)(?:[^;]*)[; ]*([A-Za-z]*)/).to_a
75
+
76
+ # if os is only a number it is probably a wrong found
77
+ void, self.os, x11_os, comp_os = agent_string.match(/\).*\(([A-Za-z0-9]*)(?:[^;]*)[; ]*([A-Za-z]*)(?:[^;]*)[; ]*([A-Za-z]*)/).to_a if os =~ /^[0-9]*$/
78
+
79
+ self.os = comp_os if os == 'compatible'
80
+ self.os = x11_os if %w(Linux).include? os and ['Android'].include? x11_os
81
+ self.os = x11_os if %w(X11 Unknown PDA).include? os and not x11_os.blank?
82
+ self.os = 'Linux' if %w(Arch Gentoo).include? os and agent_string =~ /#{os} Linux/
83
+ self.os = 'Windows CE' if agent_string =~ /Windows CE/ or agent_string =~ /WindowsCE/
84
+
85
+ if agent_string =~ /Opera[\/ ]/ or agent_string =~ /Opera$/
86
+ self.name = "Opera"
87
+
88
+ void, self.full_version = agent_string.match(/Version\/([0-9\.]*)/).to_a if agent_string =~ /Version\//
89
+ void, self.full_version = agent_string.match(/Opera[\/ ]([0-9\.]*)/).to_a if full_version.blank? or full_version =~ /^[0-9]+$/
90
+
91
+ if agent_string =~ /^[^\(]*\(compatible; MSIE/
92
+ matches = agent_string.match /\(compatible; MSIE [0-9\.]*; ([a-zA-Z0-9]*)([a-zA-z0-9\ \/\._-]*)(?:; )?([a-zA-Z0-9]*)/
93
+
94
+ if matches
95
+ self.os = matches[1]
96
+ self.os = matches[3] if %w(X11 MSIE).include? os
97
+ end
98
+ end
99
+
100
+ self.os = "Nintendo Wii" if os=='Nintendo'
101
+
102
+ void, self.full_version = agent_string.match(/Opera ([0-9]+\.[0-9\.]+)/).to_a if full_version.blank?
103
+
104
+ if agent_string =~ /Opera Mobi[;\/]/
105
+ self.name = 'Opera Mobile'
106
+ elsif agent_string =~ /Opera Mini\//
107
+ self.name = 'Opera Mini'
108
+ end
109
+
110
+ void, mini_version = agent_string.match(/Opera Mini\/([0-9]+\.[0-9\.]+)/).to_a
111
+ self.full_version = mini_version unless mini_version.blank?
112
+
113
+ self.agent_class = :browser
114
+
115
+ if ['Opera Mini', 'Opera Mobile'].include? name
116
+ self.agent_class = :mobile_browser
117
+
118
+ if agent_string =~ /BlackBerry/
119
+ self.os = 'BlackBerry'
120
+ elsif agent_string =~ /Android/
121
+ self.os = 'Android'
122
+ elsif agent_string =~ /Series 60/ or agent_string =~ /SymbOS/ or agent_string =~ /SymbianOS/
123
+ self.os = 'Symbian OS'
124
+ elsif agent_string =~ /Windows Mobile; WCE;/
125
+ self.os = 'Windows CE'
126
+ elsif agent_string =~ /Windows Mobile;/ or agent_string =~ /Microsoft Windows;/
127
+ self.os = 'Windows Mobile'
128
+ elsif os == 'Linux' and agent_string =~ /Maemo/
129
+ self.os = 'Maemo'
130
+ elsif agent_string =~ /iPhone/ or agent_string =~ /iPad/ or agent_string =~ /iPod/
131
+ self.os = 'iOS'
132
+ end
133
+ elsif os == 'Symbian'
134
+ self.name = 'Opera Mobile'
135
+ self.agent_class = :mobile_browser
136
+ end
137
+
138
+ if agent_string =~ /Presto\//
139
+ void, self.engine_version = agent_string.match(/Presto\/([0-9]+\.[0-9\.]+)/).to_a
140
+ self.engine_name = 'Presto'
141
+ elsif (version = self.full_version.to_f) >= 7.0
142
+ self.engine_name = 'Presto'
143
+
144
+ if version > 11.6
145
+ self.engine_version = nil
146
+ elsif version >= 11.5
147
+ self.engine_version = '2.9.168'
148
+ elsif version >= 11.1
149
+ self.engine_version = '2.8.131'
150
+ elsif version >= 11.0
151
+ self.engine_version = '2.7.62'
152
+ elsif name == 'Opera'
153
+ if version >= 10.6
154
+ self.engine_version = '2.6.30'
155
+ elsif version >= 10.5
156
+ self.engine_version = '2.5.24'
157
+ elsif version >= 10.0
158
+ self.engine_version = '2.2.15'
159
+ elsif version >= 9.6
160
+ self.engine_version = '2.1.1'
161
+ elsif version >= 9.5
162
+ self.engine_version = '2.1'
163
+ elsif version >= 9.0
164
+ self.engine_version = '2.0'
165
+ else
166
+ self.engine_version = '1.0'
167
+ end
168
+ elsif name == 'Opera Mobile'
169
+ if version >= 10.1
170
+ self.engine_version = '2.5.24'
171
+ elsif version >= 10
172
+ self.engine_version = '2.4'
173
+ elsif version >= 9.8
174
+ self.engine_version = '2.2.15'
175
+ elsif version >= 9.7
176
+ self.engine_version = '2.2'
177
+ else
178
+ self.engine_version = '2.1'
179
+ end
180
+ end
181
+ end
182
+ elsif agent_string =~ /AppleWeb[kK]it/
183
+ self.name = "WebKit Browser"
184
+ self.engine_name = 'WebKit'
185
+ void, self.engine_version = agent_string.match(/AppleWeb[kK]it\/([0-9\.]*)/).to_a
186
+
187
+
188
+ if agent_string =~ /Mobile/ || agent_string =~ /Android/ || agent_string =~ / Pre\// and not agent_string =~ /Series60\//
189
+ self.name = "Mobile Safari"
190
+
191
+ void, self.full_version = agent_string.match(/Version\/([0-9\.]*)/).to_a
192
+
193
+ if ['iPod', 'iPhone', 'iPad'].include? os
194
+ self.os = 'iOS'
195
+ elsif os =~ /webOS/
196
+ self.os='Palm webOS'
197
+ elsif os == 'BlackBerry'
198
+ self.name = 'BlackBerry Browser'
199
+ end
200
+
201
+ if agent_string =~ /Dorothy/
202
+ self.name = 'Dorothy'
203
+ self.os = 'Windows CE' if os == 'Windows'
204
+ end
205
+
206
+ self.agent_class = :mobile_browser
207
+ elsif agent_string =~ /ABrowse/
208
+ self.name = "ABrowse"
209
+ void, self.full_version = agent_string.match(/ABrowse ([0-9\.]*)/).to_a
210
+ self.agent_class = :browser
211
+ elsif agent_string =~ /[Ee]piphany/
212
+ self.name = "Epiphany"
213
+ void, self.full_version = agent_string.match(/Epiphany\/([0-9\.]*)/).to_a
214
+ self.agent_class = :browser
215
+ elsif agent_string =~ /OmniWeb/
216
+ self.name = "OmniWeb"
217
+ void, self.full_version = agent_string.match(/OmniWeb\/v([0-9\.]*)/).to_a
218
+ self.os = 'OS X'
219
+ self.agent_class = :browser
220
+ else
221
+ chrome_aliases = %w(Chromium Iron)
222
+ webkit_browsers = ['QtWeb Internet Browser'] + %w(Arora Flock Fluid Hana RealPlayer Shiira Stainless SunriseBrowser Sunrise Skyfire TeaShark BOLT Series60 webOS Chrome) + chrome_aliases
223
+
224
+ self.agent_class = :browser
225
+
226
+ webkit_browsers.each do |agent|
227
+ if agent_string =~ /#{agent}/
228
+ if chrome_aliases.include?(agent)
229
+ self.name = 'Chrome'
230
+ else
231
+ self.name = agent
232
+ end
233
+ void, self.full_version = agent_string.match(/#{agent}\/([0-9\.]*)/).to_a
234
+ void, self.full_version = agent_string.match(/Version\/([0-9\.]*)/).to_a if full_version.blank?
235
+ break
236
+ end
237
+ end
238
+ end
239
+
240
+ self.name = 'Sunrise' if name == 'SunriseBrowser'
241
+ self.name = 'Bolt' if name == 'BOLT'
242
+ if name == 'Series60' or os == 'SymbianOS'
243
+ self.name = 'Browser for S60'
244
+ self.agent_class = :mobile_browser
245
+ end
246
+
247
+ if name == 'webOS'
248
+ self.name = 'webOS Browser'
249
+ self.agent_class = :mobile_browser
250
+ end
251
+
252
+ if %w(Skyfire).include? self.name
253
+ self.os = 'Android'
254
+ self.agent_class = :mobile_browser
255
+ elsif %w(TeaShark Bolt).include? self.name
256
+ self.os = 'J2ME'
257
+ self.agent_class = :mobile_browser
258
+ end
259
+
260
+ if agent_string =~ /Safari/ and self.name == "WebKit Browser"
261
+ self.name = "Safari"
262
+
263
+ void, self.full_version = agent_string.match(/Version\/([0-9\.]*)/).to_a
264
+
265
+ unless self.full_version
266
+ void, webkit_version = agent_string.match(/Safari\/([0-9\.]*)/).to_a
267
+
268
+ self.full_version = case webkit_version
269
+ when /^85\.8(?:\.1)?$/ then '1.0.3'
270
+ when /^85(?:\.[567])?$/ then '1.0'
271
+ when /^125(?:\.1)?$/ then '1.2'
272
+ when /^125\.[278]$/ then '1.2.2'
273
+ when /^125\.5\.5$/ then '1.2.2'
274
+ when /^125\.9$/ then '1.2.3'
275
+ when /^125\.1[12]$/ then '1.2.4'
276
+ when /^312$/ then '1.3'
277
+ when /^312\.3(?:\.[13])?$/ then '1.3.1'
278
+ when /^312\.[56]$/ then '1.3.2'
279
+ when /^412(?:\.[2])*$/ then '2.0'
280
+ when /^412\.[567]$/ then '2.0.1'
281
+ when /^413$/ then '2.0.1'
282
+ when /^416\.1[23]$/ then '2.0.2'
283
+ when /^417\.8$/ then '2.0.3'
284
+ when /^417\.9\.[23]$/ then '2.0.3'
285
+ when /^419.3$/ then '2.0.4'
286
+ end
287
+ end
288
+ end
289
+
290
+ elsif agent_string =~ /Konqueror/
291
+ self.name = "Konqueror"
292
+ void, self.full_version = agent_string.match(/Konqueror[\/ ]([0-9\.]*)/).to_a
293
+ void, self.os = agent_string.match(/Konqueror[^;]*; (?:i[36]86 )?([A-Za-z0-9]*)/).to_a
294
+ self.os = nil if os == 'X11'
295
+ self.engine_name = 'KHTML'
296
+ void, self.engine_version = agent_string.match(/KHTML[\/ ]([0-9\.]*)/).to_a
297
+ self.engine_version = full_version if engine_version.blank?
298
+ self.agent_class = :browser
299
+
300
+
301
+ elsif agent_string =~ /Gecko/
302
+ self.name = "Gecko Browser";
303
+
304
+ real_agent = nil
305
+ self.agent_class = :browser
306
+
307
+ firefox_aliases = %w(Firebird Minefield BonEcho Iceweasel Shiretoko GranParadiso IceCat Iceweasel IceWeasel Namoroka Phoenix)
308
+ gecko_browsers = %w(BeZillaBrowser Beonex Camino Chimera Conkeror Epiphany Flock Galeon Iceape K-Meleon K-Ninja Kapiko Kazehakase KMLite MultiZilla SeaMonkey Netscape Navigator Prism Fennec Minimo Firefox) + firefox_aliases
309
+
310
+ agent_string = agent_string.gsub('prism', 'Prism')
311
+
312
+ gecko_browsers.each do |agent|
313
+ if agent_string =~ /#{agent}/
314
+ self.name = agent
315
+ break
316
+ end
317
+ end
318
+
319
+ self.engine_name = 'Gecko'
320
+ void, self.engine_version = agent_string.match(/rv:([0-9\.]*)/).to_a
321
+
322
+ if self.name == "Gecko Browser"
323
+ self.full_version = engine_version
324
+ else
325
+ void, self.full_version = agent_string.match(/#{self.name}\/([0-9\.]*)/).to_a
326
+ end
327
+
328
+ self.name = 'Netscape Navigator' if %w(Netscape Navigator).include? name
329
+ self.name = 'Firefox' if firefox_aliases.include? name
330
+
331
+ if agent_string =~ /Maemo Browser[\/ ][0-9]/
332
+ self.name = 'Maemo Browser'
333
+ void, self.full_version = agent_string.match(/Maemo Browser[\/ ]([0-9\.]*)/).to_a
334
+ self.os = 'Maemo'
335
+ self.agent_class = :mobile_browser
336
+ end
337
+
338
+ if %w(Fennec Minimo).include? name
339
+ self.agent_class = :mobile_browser
340
+ end
341
+
342
+ elsif agent_string =~ /Series60\//
343
+ self.name = 'Browser for S60'
344
+ self.os = 'Symbian OS'
345
+ void, self.full_version = agent_string.match(/Series60\/([0-9\.]*)/).to_a
346
+ self.agent_class = :mobile_browser
347
+ elsif agent_string =~ /Series80\//
348
+ self.name = 'Browser for S80'
349
+ self.os = 'Symbian OS'
350
+ void, self.full_version = agent_string.match(/Series80\/([0-9\.]*)/).to_a
351
+ self.agent_class = :mobile_browser
352
+
353
+ elsif agent_string =~ /compatible; MSIE/
354
+ self.name = "Internet Explorer"
355
+
356
+ void, self.full_version, self.os = agent_string.match(/MSIE ([0-9\.]*)(?:b[0-9]*)?; ([A-Za-z]*)/).to_a
357
+
358
+ if os=='Mac'
359
+ self.os = 'Macintosh'
360
+ elsif os != 'Windows' and agent_string =~ /Windows/
361
+ self.os = 'Windows'
362
+ end
363
+ self.agent_class = :browser
364
+
365
+ if agent_string =~ /IEMobile[\/ ][0-9]/
366
+ self.name = 'IE Mobile'
367
+ self.agent_class = :mobile_browser
368
+ void, self.full_version= agent_string.match(/IEMobile[\/ ]([0-9\.]*)/).to_a
369
+ void, self.os = agent_string.match(/(Windows [A-Za-z0-9]*)/).to_a
370
+ end
371
+
372
+ if agent_string =~ /Trident\//
373
+ void, self.engine_version = agent_string.match(/Trident\/([0-9]+\.[0-9\.]+)/).to_a
374
+ self.engine_name = 'Trident'
375
+ elsif (version = self.full_version.to_f) >= 4
376
+ logger.debug "#{self.full_version}: #{version}"
377
+ self.engine_name = 'Trident'
378
+ if version > 10.0
379
+ self.engine_version = nil
380
+ elsif version >= 10.0
381
+ self.engine_version = '6.0'
382
+ elsif version >= 9.0
383
+ self.engine_version = '5.0'
384
+ elsif version >= 8.0
385
+ self.engine_version = '4.0'
386
+ elsif version >= 7.0
387
+ self.engine_version = '3.1'
388
+ elsif version >= 6.0
389
+ self.engine_version = '3.0'
390
+ elsif version >= 5.5
391
+ self.engine_version = '2.2'
392
+ elsif version >= 5.1
393
+ self.engine_version = '2.1'
394
+ elsif version >= 5.0
395
+ self.engine_version = '2.0'
396
+ else
397
+ self.engine_version = '1.0'
398
+ end
399
+
400
+ if agent_string =~ /Windows CE;/ and agent_string =~ /[pP]rofile\/MIDP/
401
+ self.name = 'IE Mobile'
402
+ self.os = 'Windows CE'
403
+ self.agent_class = :mobile_browser
404
+ end
405
+
406
+ end
407
+
408
+ if agent_string =~ /Avant Browser/
409
+ self.name = 'Avant Browser'
410
+ self.full_version = nil
411
+ elsif agent_string =~ /PalmSource[^;]*; Blazer/
412
+ self.name = 'Blazer'
413
+ void, self.full_version = agent_string.match(/Blazer[\/ ]([0-9\.]*)/).to_a
414
+ self.engine_name = nil
415
+ self.engine_version = nil
416
+ self.os = 'Palm OS'
417
+ self.agent_class = :mobile_browser
418
+ elsif agent_string =~ /EudoraWeb/
419
+ self.name = 'EudoraWeb'
420
+ void, self.full_version = agent_string.match(/EudoraWeb ([0-9\.]*)/).to_a
421
+ self.engine_name = nil
422
+ self.engine_version = nil
423
+ self.agent_class = :mobile_browser
424
+ elsif agent_string =~ /uZard(?:Web)?[\/ ][0-9]/
425
+ self.name = 'uZard'
426
+ void, self.full_version= agent_string.match(/uZard(?:Web)?[\/ ]([0-9\.]*)/).to_a
427
+ self.os = nil
428
+ self.agent_class = :mobile_browser
429
+ end
430
+
431
+
432
+ # Rare Browsers
433
+
434
+ elsif agent_string =~ /ABrowse/
435
+ # Old version of ABrowse don't include AppleWebKit in agent_string
436
+ self.name = "ABrowse"
437
+ void, self.full_version, self.os = agent_string.match(/ABrowse ([0-9\.]*)(?:[a-zA-z0-9\ \/\._-]*); ([a-zA-Z0-9]*)/).to_a
438
+ self.agent_class = :browser
439
+ elsif agent_string =~ /amaya\//
440
+ self.name = "Amaya"
441
+ void, self.full_version = agent_string.match(/amaya\/([0-9\.]*)/).to_a
442
+ self.agent_class = :browser
443
+ elsif agent_string =~ /AmigaVoyager/
444
+ self.name = "AmigaVoyager"
445
+ self.os = "AmigaOS"
446
+ void, self.full_version = agent_string.match(/AmigaVoyager\/([0-9\.]*)/).to_a
447
+ self.agent_class = :browser
448
+ elsif agent_string =~ /^Avant Browser/
449
+ self.name = "Avant Browser"
450
+ self.os = 'Windows'
451
+ void, self.full_version = agent_string.match(/Avant Browser\/([0-9\.]*)/).to_a
452
+ self.agent_class = :browser
453
+ elsif agent_string =~ /\(Charon;/
454
+ self.name = "Charon"
455
+ void, self.os = agent_string.match(/\(Charon; (.*)\)/).to_a
456
+ self.agent_class = :browser
457
+ elsif agent_string =~ /Cyberdog/
458
+ self.name = "Cyberdog"
459
+ self.os = "Classic Macintosh"
460
+ void, self.full_version = agent_string.match(/Cyberdog\/([0-9\.]*)/).to_a
461
+ self.agent_class = :browser
462
+ elsif agent_string =~ /Dillo\//
463
+ self.name = "Dillo"
464
+ void, self.full_version = agent_string.match(/Dillo\/([0-9\.]*)/).to_a
465
+ self.agent_class = :browser
466
+ elsif agent_string =~ /E[lL]inks/
467
+ self.name = "ELinks"
468
+ void, self.os = agent_string.match(/;\s?([A-Za-z0-9]*)(?:[\+a-zA-z0-9\s\/\._-]*);\s?[0-9]+x[0-9]+/).to_a
469
+ void, self.full_version = agent_string.match(/E[lL]inks\/([0-9\.]*)/).to_a
470
+ void, self.full_version = agent_string.match(/E[lL]inks \(([0-9\.]*)/).to_a if full_version.blank?
471
+ self.agent_class = :browser
472
+ elsif agent_string =~ /Galaxy/
473
+ self.name = "Galaxy"
474
+ void, self.os = agent_string.match(/\(([A-Za-z0-9]*)/).to_a
475
+ void, self.full_version = agent_string.match(/Galaxy[\/ ]([0-9\.]*)/).to_a
476
+ self.agent_class = :browser
477
+ elsif agent_string =~ /HotJava/
478
+ self.name = "HotJava"
479
+ void, self.full_version = agent_string.match(/HotJava[\/ ]([0-9\.]*)/).to_a
480
+ self.agent_class = :browser
481
+ elsif agent_string =~ /IBM WebExplorer/
482
+ self.name = "IBM WebExplorer"
483
+ self.os = 'OS/2'
484
+ void, self.full_version = agent_string.match(/\/v([0-9\.]*)/).to_a
485
+ self.agent_class = :browser
486
+ elsif agent_string =~ /IBrowse/
487
+ self.name = "IBrowse"
488
+ self.os = 'AmigaOS'
489
+ void, self.full_version = agent_string.match(/IBrowse[\/ ]([0-9\.]*)/).to_a
490
+ self.agent_class = :browser
491
+ elsif agent_string =~ /iCab/
492
+ self.name = "iCab"
493
+ self.os = 'OS X'
494
+ void, self.full_version = agent_string.match(/iCab[\/ ]([0-9\.]*)/).to_a
495
+ self.agent_class = :browser
496
+ elsif agent_string =~ /^Links/
497
+ self.name = "Links"
498
+ void, self.full_version = agent_string.match(/Links (?:\()?([0-9\.]*)/).to_a
499
+ void, self.os = agent_string.match(/Links[^;]*; ([A-Za-z0-9]*)/).to_a
500
+ self.os = nil if os == 'Unix'
501
+ self.os = 'Macintosh' if os == 'Darwin'
502
+ self.agent_class = :browser
503
+ elsif agent_string =~ /^Lynx/
504
+ self.name = "Lynx"
505
+ self.os = nil
506
+ void, self.full_version = agent_string.match(/Lynx\/([0-9\.]*)/).to_a
507
+ self.agent_class = :browser
508
+ elsif agent_string =~ /Midori\/[0-9]/
509
+ self.name = "Midori"
510
+ void, self.full_version = agent_string.match(/Midori\/([0-9\.]*)/).to_a
511
+ self.engine_name = 'WebKit'
512
+ void, self.engine_version = agent_string.match(/WebKit\/\(([0-9\.]*)/).to_a
513
+ self.agent_class = :browser
514
+ elsif agent_string =~ /^NCSA[_ ]Mosaic\//
515
+ self.name = "Mosaic"
516
+ void, self.full_version = agent_string.match(/Mosaic\/([0-9\.]*)/).to_a
517
+ void, self.os = agent_string.match(/\((?:X11;[ ]?)?([A-Za-z0-9]*)/).to_a
518
+ self.os = nil if os == 'Unix'
519
+ self.os = 'Macintosh' if os == 'Darwin'
520
+ self.agent_class = :browser
521
+ elsif agent_string =~ /neon\//
522
+ self.name = "Neon"
523
+ void, self.full_version = agent_string.match(/neon\/([0-9\.]*)/).to_a
524
+ self.agent_class = :browser
525
+ elsif agent_string =~ /NetPositive\//
526
+ self.name = "NetPositive"
527
+ void, self.full_version = agent_string.match(/NetPositive\/([0-9\.]*)/).to_a
528
+ self.agent_class = :browser
529
+ elsif agent_string =~ /NetSurf\//
530
+ self.name = "NetSurf"
531
+ void, self.full_version = agent_string.match(/NetSurf\/([0-9\.]*)/).to_a
532
+ self.agent_class = :browser
533
+ elsif agent_string =~ /OmniWeb\//
534
+ self.name = "OmniWeb"
535
+ void, self.full_version = agent_string.match(/OmniWeb\/([0-9\.]*)/).to_a
536
+ self.os = 'OS X'
537
+ self.agent_class = :browser
538
+ elsif agent_string =~ /^w3m[\/ ]?/
539
+ self.name = 'w3m'
540
+ void, self.full_version = agent_string.match(/w3m[\/ ]([0-9\.]*)/).to_a
541
+ self.full_version = '0.5.2' if full_version == '0.52'
542
+ void, self.os = agent_string.match(/\(([A-Za-z0-9]*)/).to_a
543
+ self.os = 'Linux' if os == 'Debian'
544
+ self.agent_class = :browser
545
+ elsif agent_string == 'WorldWideweb (NEXT)'
546
+ self.name = 'WorldWideweb'
547
+ self.os = 'NeXT OS'
548
+ self.agent_class = :browser
549
+ elsif agent_string =~ /Yandex\//
550
+ self.name = "Yandex"
551
+ void, self.full_version = agent_string.match(/Yandex\/([0-9\.]*)/).to_a
552
+ self.os = 'Windows'
553
+ self.agent_class = :browser
554
+
555
+ # Console Browsers
556
+
557
+ elsif agent_string =~ /Bunjalloo\//
558
+ self.name = "Bunjalloo"
559
+ void, self.full_version = agent_string.match(/Bunjalloo\/([0-9\.]*)/).to_a
560
+ void, self.os = agent_string.match(/\(([A-Za-z0-9 ]*)/).to_a
561
+ self.agent_class = :browser
562
+ elsif agent_string =~ /\(PLAYSTATION 3/
563
+ self.name = "Playstation 3"
564
+ void, self.full_version = agent_string.match(/PLAYSTATION 3; ([0-9\.]*)/).to_a
565
+ self.os = "Playstation 3"
566
+ self.agent_class = :browser
567
+ elsif agent_string =~ /\(PS3 \(PlayStation 3\)/
568
+ self.name = "Playstation 3"
569
+ void, self.full_version = agent_string.match(/\(PS3 \(PlayStation 3\); ([0-9\.]*)/).to_a
570
+ self.os = "Playstation 3"
571
+ self.agent_class = :browser
572
+ elsif agent_string =~ /PSP \(PlayStation Portable\)/
573
+ self.name = "PlayStation Portable"
574
+ void, self.full_version = agent_string.match(/PSP \(PlayStation Portable\); ([0-9\.]*)/).to_a
575
+ self.os = "PlayStation Portable"
576
+ self.agent_class = :browser
577
+ elsif agent_string =~ /wii libnup/
578
+ self.name = "Wii Browser"
579
+ void, self.full_version = agent_string.match(/wii libnup\/([0-9\.]*)/).to_a
580
+ self.os = "Nintendo Wii"
581
+ self.agent_class = :browser
582
+
583
+ # Mobile Browsers
584
+
585
+ elsif agent_string =~ /^BlackBerry/
586
+ self.name = "BlackBerry Browser"
587
+ void, self.full_version = agent_string.match(/^BlackBerry[0-9]*[i]*\/([0-9\.]*)/).to_a
588
+ self.os = "BlackBerry"
589
+ self.agent_class = :mobile_browser
590
+ elsif agent_string =~ /portalmmm\//
591
+ self.name = "IMode mobile browser"
592
+ void, self.full_version = agent_string.match(/portalmmm\/([0-9\.]*)/).to_a
593
+ self.os = nil
594
+ self.agent_class = :mobile_browser
595
+ elsif agent_string =~ /^Doris/
596
+ self.name = "Doris"
597
+ void, self.full_version = agent_string.match(/^Doris\/([0-9\.]*)/).to_a
598
+ self.os = "SymbianOS"
599
+ self.agent_class = :mobile_browser
600
+ elsif agent_string =~ /\/GoBrowser/
601
+ self.name = "GoBrowser"
602
+ void, self.full_version = agent_string.match(/\/GoBrowser\/([0-9\.]*)/).to_a
603
+ void, self.os = agent_string.match(/\(([A-Za-z0-9]*)/).to_a
604
+ self.agent_class = :mobile_browser
605
+ elsif agent_string =~ /MIB\/[0-9]/
606
+ self.name = "Motorola Internet Browser"
607
+ void, self.full_version = agent_string.match(/MIB\/([0-9\.]*)/).to_a
608
+ self.os = 'J2ME'
609
+ self.agent_class = :mobile_browser
610
+ elsif agent_string =~ /UP.Browser\//
611
+ self.name = "UP Browser"
612
+ void, self.full_version = agent_string.match(/UP.Browser\/([0-9\.]*)/).to_a
613
+ self.os = 'J2ME'
614
+ self.agent_class = :mobile_browser
615
+ elsif agent_string =~ /NetFront\/[0-9]/
616
+ self.name = "NetFront"
617
+ void, self.full_version = agent_string.match(/NetFront\/([0-9\.]*)/).to_a
618
+ self.os = 'Kindle' if agent_string =~ /Kindle\/[0-9]/
619
+ self.agent_class = :mobile_browser
620
+ elsif agent_string =~ /POLARIS\/[0-9]/
621
+ self.name = "Polaris"
622
+ void, self.full_version = agent_string.match(/POLARIS\/([0-9\.]*)/).to_a
623
+ self.agent_class = :mobile_browser
624
+ elsif agent_string =~ /SEMC-Browser\//
625
+ self.name = "SonyEricsson Browser"
626
+ self.os = 'J2ME'
627
+ void, self.full_version = agent_string.match(/SEMC-Browser\/([0-9\.]*)/).to_a
628
+ self.agent_class = :mobile_browser
629
+ elsif agent_string =~ /Teleca/
630
+ self.name = "Teleca-Obigo"
631
+ void, self.full_version, self.os = agent_string.match(/Teleca[\/ ]([A-Z0-9\.]*)(?:; )?([A-Za-z0-9]*)/).to_a
632
+ self.agent_class = :mobile_browser
633
+
634
+ elsif agent_string =~ /^SonyEricsson/
635
+ self.name = "SonyEricsson Browser"
636
+ self.os = 'J2ME'
637
+ self.agent_class = :mobile_browser
638
+ elsif agent_string =~ /[pP]rofile\/MIDP-/
639
+ self.name = "MIDP Browser"
640
+ self.os = 'J2ME'
641
+ void, self.full_version = agent_string.match(/[pP]rofile\/MIDP-([A-Za-z0-9\.]*)/).to_a
642
+ self.agent_class = :mobile_browser
643
+
644
+ # Known Bots
645
+ elsif agent_string =~ /Yahoo! Slurp/
646
+ self.name = "Yahoo! Bot"
647
+ self.agent_class = :bot
648
+ elsif agent_string =~ /Googlebot/ || agent_string =~ /Google-Sitemaps/
649
+ self.name = "Google Bot"
650
+ self.agent_class = :bot
651
+ elsif agent_string =~ /ia_archiver/
652
+ self.name = "Internet Archive Bot"
653
+ self.agent_class = :bot
654
+ elsif agent_string =~ /msnbot/
655
+ self.name = "MSN Live Search Bot"
656
+ self.agent_class = :bot
657
+ elsif agent_string =~ /ScoutJet/
658
+ self.name = "ScoutJet Bot"
659
+ self.agent_class = :bot
660
+ elsif agent_string =~ /Ask Jeeves/
661
+ self.name = "Ask Jeeves Bot"
662
+ self.agent_class = :bot
663
+ elsif agent_string =~ /AboutUsBot/
664
+ self.name = "AboutUsBot"
665
+ self.agent_class = :bot
666
+ elsif agent_string =~ /NaverBot/
667
+ self.name = "NaverBot"
668
+ self.agent_class = :bot
669
+ elsif agent_string =~ /Gigabot/
670
+ self.name = "Gigabot"
671
+ self.agent_class = :bot
672
+ elsif agent_string =~ /CyberPatrol SiteCat/
673
+ self.name = "CyberPatrol Bot"
674
+ self.agent_class = :bot
675
+ elsif agent_string =~ /BLT/
676
+ self.name = "BLT"
677
+ self.agent_class = :bot
678
+ elsif agent_string =~ /Netluchs/
679
+ self.name = "Netluchs"
680
+ self.agent_class = :bot
681
+ elsif agent_string =~ /blackspider/
682
+ self.name = "Blackspider"
683
+ self.agent_class = :bot
684
+ elsif agent_string =~ /Yanga WorldSearch Bot/
685
+ self.name = "Yanga WorldSearch Bot"
686
+ void, self.full_version = agent_string.match(/v([0-9\.]*)/).to_a
687
+ self.agent_class = :bot
688
+ elsif agent_string=~ /Twiceler/
689
+ self.name = "Twiceler"
690
+ void, self.full_version = agent_string.match(/Twiceler-([0-9\.]*)/).to_a
691
+ self.agent_class = :bot
692
+ elsif agent_string =~ /WebDataCentreBot/
693
+ self.name = "WebDataCentreBot"
694
+ void, self.full_version = agent_string.match(/WebDataCentreBot\/([0-9\.]*)/).to_a
695
+ self.agent_class = :bot
696
+ elsif agent_string =~ /DomainCrawler/
697
+ self.name = "DomainCrawler"
698
+ void, self.full_version = agent_string.match(/DomainCrawler\/([0-9\.]*)/).to_a
699
+ self.agent_class = :bot
700
+ elsif agent_string =~ /NetcraftSurveyAgent/
701
+ self.name = "NetcraftSurveyAgent"
702
+ void, self.full_version = agent_string.match(/NetcraftSurveyAgent\/([0-9\.]*)/).to_a
703
+ self.agent_class = :bot
704
+ elsif agent_string =~ /Snapbot/
705
+ self.name = "Snapbot"
706
+ void, self.full_version = agent_string.match(/Snapbot\/([0-9\.]*)/).to_a
707
+ self.agent_class = :bot
708
+ elsif agent_string =~ /Jyxobot/
709
+ self.name = "Jyxobot"
710
+ void, self.full_version = agent_string.match(/Jyxobot\/([0-9\.]*)/).to_a
711
+ self.agent_class = :bot
712
+ elsif agent_string =~ /Speedy Spider/
713
+ self.name = "Speedy Spider"
714
+ self.agent_class = :bot
715
+ elsif agent_string =~ /iPhoto/
716
+ self.name = "iPhoto"
717
+ void, self.full_version, self.os = agent_string.match(/iPhoto\/([0-9\.]*) \(([A-Za-z1-9]*);/).to_a
718
+ self.agent_class = :browser
719
+ elsif agent_string =~ /mylinkcheck/
720
+ self.name = "MyLinkCheck"
721
+ void, self.full_version = agent_string.match(/mylinkcheck\/([0-9\.]*)/).to_a
722
+ self.agent_class = :bot
723
+ elsif agent_string =~ /W3C-checklink/
724
+ self.name = "W3C-checklink"
725
+ void, self.full_version = agent_string.match(/W3C-checklink\/([0-9\.]*)/).to_a
726
+ self.agent_class = :bot
727
+ elsif agent_string =~ /Eurobot/
728
+ self.name = "Eurobot"
729
+ void, self.full_version = agent_string.match(/Eurobot\/([0-9\.]*)/).to_a
730
+ self.agent_class = :bot
731
+ elsif agent_string =~ /JoeDog\//
732
+ self.name = "JoeDog"
733
+ void, self.full_version = agent_string.match(/JoeDog\/([0-9\.]*)/).to_a
734
+ self.agent_class = :bot
735
+ elsif agent_string =~ /W3C_Validator\//
736
+ self.name = "W3C_Validator"
737
+ void, self.full_version = agent_string.match(/W3C_Validator\/([0-9\.]*)/).to_a
738
+ self.agent_class = :bot
739
+ elsif agent_string =~ /Wget\//
740
+ self.name = "Wget"
741
+ void, self.full_version = agent_string.match(/Wget\/([0-9\.]*)/).to_a
742
+ self.agent_class = :bot
743
+
744
+ elsif agent_string =~ /WebAlta Crawler\//
745
+ self.name = "WebAlta Crawler"
746
+ void, self.full_version = agent_string.match(/WebAlta Crawler\/([0-9\.]*)/).to_a
747
+ self.agent_class = :bot
748
+ elsif agent_string =~ /WebAlta Crawler\//
749
+ self.name = "WebAlta Crawler"
750
+ void, self.full_version = agent_string.match(/WebAlta Crawler\/([0-9\.]*)/).to_a
751
+ self.agent_class = :bot
752
+ elsif agent_string =~ /WWW-Mechanize\//
753
+ self.name = "WWW-Mechanize";
754
+ void, self.full_version = agent_string.match(/WWW-Mechanize\/([0-9\.]*)/).to_a
755
+ self.agent_class = :bot
756
+ elsif agent_string =~ /Serverstress Analysis Software/
757
+ self.name = 'Serverstress Analysis Software'
758
+ self.agent_class = :bot
759
+
760
+ elsif agent_string =~ /Java\//
761
+ self.name = 'Java Client';
762
+ self.os = nil
763
+ void, self.full_version = agent_string.match(/Java\/([0-9\.]*)/).to_a
764
+ self.agent_class = :bot
765
+ elsif agent_string.strip == 'http://www.uni-koblenz.de/~flocke/robot-info.txt'
766
+ self.name = 'http://www.uni-koblenz.de/~flocke/robot-info.txt'
767
+ self.agent_class = :bot
768
+ elsif agent_string =~ /^Shelob/
769
+ self.name = 'Shelob';
770
+ self.agent_class = :bot
771
+ end
772
+
773
+ # Cleanup
774
+
775
+ self.full_version = "#{full_version}0" if full_version =~ /^[0-9]+\.$/
776
+ self.full_version = "#{full_version}.0" if full_version =~ /^[0-9]+$/
777
+ self.os = 'OS X' if ['Macintosh', 'Mac'].include? os
778
+ self.os = 'Windows' if os == 'Win'
779
+ self.os = 'Symbian OS' if %w(Symbian SymbianOS).include? os
780
+ self.os = 'J2ME' if os == 'J2ME/MIDP'
781
+ self.os = 'Brew' if os == 'BREW'
782
+
783
+ self.full_version = nil if self.full_version.blank?
784
+ self.full_version.sub!(/\.$/,'') if self.full_version
785
+ self.major_version = full_version.match(/^([0-9]*\.[0-9]*)/).to_a.last if full_version
786
+ self.major_version = full_version if major_version.blank?
787
+ end
788
+ end
789
+ end