gricer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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