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,239 @@
1
+ module Gricer
2
+ # ActiveRecord Model for Request Statistics
3
+ # @attr [Gricer::Session] session
4
+ # The current value of the associated session.
5
+ #
6
+ # @attr [Gricer::Agent] agent
7
+ # The current value of the associated agent.
8
+ #
9
+ # @attr [String] host
10
+ # The current value of the host requested.
11
+ #
12
+ # @attr [String] path
13
+ # The current value of the path requested.
14
+ #
15
+ # @attr [String] method
16
+ # The current value of the method requested.
17
+ #
18
+ # @attr [String] protocol
19
+ # The current value of the protocol requested.
20
+ #
21
+ # @attr [String] controller
22
+ # The current value of the controller requested.
23
+ #
24
+ # @attr [String] action
25
+ # The current value of the action requested.
26
+ #
27
+ # @attr [String] format
28
+ # The current value of the format requested.
29
+ #
30
+ # @attr [String] param_id
31
+ # The current value of the http GET/POST id parameter requested.
32
+ #
33
+ # @attr [Integer] user_id
34
+ # The current id of the user logged in.
35
+ #
36
+ # @see Gricer::ActionController::Base#gricer_user_id
37
+ #
38
+ # @attr [Integer] status_code
39
+ # The current value of the HTTP status returned.
40
+ #
41
+ # @attr [String] content_type
42
+ # The current value of the content type returned.
43
+ #
44
+ # @attr [String] body_size
45
+ # The current size of the body returned.
46
+ #
47
+ # @attr [String] system_time
48
+ # The current value of the system time elapsed processing this request.
49
+ #
50
+ # @attr [String] user_time
51
+ # The current value of the user time elapsed processing this request.
52
+ #
53
+ # @attr [String] total_time
54
+ # The current value of the total time elapsed processing this request.
55
+ #
56
+ # @attr [String] real_time
57
+ # The current value of the real time elapsed processing this request.
58
+ #
59
+ # @attr [Boolean] javascript
60
+ # The current value of the javascript capability of the requesting agent.
61
+ #
62
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
63
+ #
64
+ # @attr [Integer] window_width
65
+ # The current value of the width in pixels of the requesting agent's window.
66
+ #
67
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
68
+ #
69
+ # @attr [Integer] window_height
70
+ # The current value of the height in pixels of the requesting agent's window.
71
+ #
72
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
73
+ #
74
+ # @attr [String] referer_protocol
75
+ # The current value of the protocol of the referering page.
76
+ #
77
+ # @attr [String] referer_host
78
+ # The current value of the host of the referering page.
79
+ #
80
+ # @attr [String] referer_path
81
+ # The current value of the path of the referering page.
82
+ #
83
+ # @attr [String] referer_params
84
+ # The current value of the params of the referering page.
85
+ #
86
+ # @attr [String] search_engine
87
+ # The current value of the search engine name refering to get to this request.
88
+ #
89
+ # @attr [String] search_query
90
+ # The current value of the search query refering to get to this request.
91
+ #
92
+ # @attr [Boolean] is_first_in_session
93
+ # The current value of the first in session flag.
94
+ # This is true if it is the first request within a Gricer::Session.
95
+ #
96
+ # @attr [String] locale_major
97
+ # The current value of the locale responded (major locale only)
98
+ #
99
+ # @attr [String] locale_minor
100
+ # The current value of the locale responded (major locale only)
101
+ #
102
+ # @attr [String] locale
103
+ # The current value of the locale responded
104
+ #
105
+ class Request < ::ActiveRecord::Base
106
+ set_table_name "#{::Gricer::config.table_name_prefix}requests"
107
+ include ActiveModel::Statistics
108
+
109
+ belongs_to :session, class_name: 'Gricer::Session', counter_cache: true
110
+ belongs_to :agent, class_name: 'Gricer::Agent', counter_cache: true
111
+
112
+ before_create :init_session
113
+
114
+ # Filter out anything that is not a Browser or MobileBrowser
115
+ # @return [ActiveRecord::Relation]
116
+ def self.browsers
117
+ includes("agent")
118
+ .where("\"#{Agent.table_name}\".\"agent_class_id\" IN (?)", [0x1000, 0x2000])
119
+ end
120
+
121
+ # Filter out anything that has not the first_in_session flag
122
+ # @return [ActiveRecord::Relation]
123
+ def self.only_first_in_session
124
+ where('is_first_in_session = ?', true)
125
+ end
126
+
127
+ # Store the ip address from which the request was sent for init_session
128
+ # @see #init_session
129
+ def ip_address=(ip)
130
+ @ip_address = ip
131
+ end
132
+
133
+ # Find or Create Gricer::Agent corrosponding to the given user agent string as given in the HTTP header
134
+ #
135
+ # @param agent_header [String] A user agent string as in a HTTP header
136
+ # @return [Gricer::Agent]
137
+ def agent_header=(agent_header)
138
+ self.agent = Agent.find_or_create_by_request_header agent_header
139
+ end
140
+
141
+ # Parse the Ruby on Rails request to fill attributes
142
+ def request=(request)
143
+ self.ip_address = request.remote_ip
144
+ self.agent_header = request.headers['HTTP_USER_AGENT']
145
+ self.referer = request.headers['HTTP_X_FORWARDED_REFERER'] || request.headers['HTTP_REFERER']
146
+
147
+ self.host = request.host
148
+ self.path = request.path
149
+ self.method = request.request_method
150
+ self.protocol = request.protocol.sub(/[:\/]*$/, '').upcase
151
+ self.locale = I18n.locale
152
+
153
+ @request_locale = request.headers['HTTP_ACCEPT_LANGUAGE'].try(:split, ',').try(:first)
154
+
155
+ logger.debug I18n.locale
156
+ end
157
+
158
+ def locale=(locale)
159
+ self.locale_major, self.locale_minor = locale.try(:to_s).try(:downcase).try(:split, '-')
160
+ end
161
+
162
+ def locale
163
+ if locale_minor
164
+ "#{locale_major}-#{locale_minor}"
165
+ else
166
+ locale_major
167
+ end
168
+ end
169
+
170
+ # Parse the referer to fill referer and search engine attributes
171
+ #
172
+ # @param referer [String] The Referer as given in HTTP request
173
+ def referer=(referer)
174
+ if referer
175
+ void, self.referer_protocol, self.referer_host, self.referer_path, self.referer_params = referer.match(/([A-Za-z0-9]*):\/\/([^\/]*)([^\?]*)[\?]?(.*)/).to_a
176
+
177
+ # Sanatize/Normalize referer values
178
+ self.referer_protocol = referer_protocol.try(:upcase)
179
+ self.referer_host = referer_host.try(:downcase)
180
+ self.referer_path = '/' if referer_path.blank?
181
+ self.referer_params = nil if referer_params.blank?
182
+ params = CGI::parse(referer_params) unless referer_params.blank?
183
+
184
+ # Detect Search Engines
185
+ if referer_host =~ /^www\.google(?:\.com?)?(?:\.[a-z]{2})?$/ and ['/search', '/url'].include? referer_path
186
+ self.search_engine = 'Google'
187
+ self.search_query = params['q'].try(:first)
188
+ elsif referer_host == 'www.bing.com' and referer_path == '/search'
189
+ self.search_engine = 'Bing'
190
+ self.search_query = params['q'].try(:first)
191
+ elsif referer_host =~ /search\.yahoo\.com$/ and referer_path =~ /\/search;/
192
+ self.search_engine = 'Yahoo'
193
+ self.search_query = params['p'].try(:first)
194
+ elsif referer_host == 'www.baidu.com' and referer_path == '/s'
195
+ self.search_engine = 'Baidu'
196
+ self.search_query = params['wd'].try(:first)
197
+ elsif referer_host =~ /ask\.com$/ and referer_path =~ /^\/web/
198
+ self.search_engine = 'Ask'
199
+ self.search_query = params['q'].try(:first)
200
+ elsif referer_host == 'search.aol.com' and referer_path == '/aol/search'
201
+ self.search_engine = 'AOL'
202
+ self.search_query = params['q'].try(:first)
203
+ elsif referer_host =~ /metacrawler\.com$/ and referer_path =~ /search\/web/
204
+ self.search_engine = 'MetaCrawler'
205
+ self.search_query = params['q'].try(:first)
206
+ elsif referer_host =~ /dogpile\.com/ and referer_path =~ /dogpile\/ws\/results\/Web\//
207
+ self.search_engine = 'Dogpile'
208
+ void, self.search_query = referer_path.match(/ws\/results\/Web\/([^\/]*)\//).to_a
209
+ self.search_query = CGI::unescape(self.search_query).gsub('!FE', '.')
210
+ end
211
+ end
212
+ end
213
+
214
+ # Parse the params to fill param_id and format attributes
215
+ def params=(params)
216
+ self.param_id = params[:id]
217
+ self.format = params[:format]
218
+ end
219
+
220
+ # Init the corrosponding Gricer::Session (called before create)
221
+ #
222
+ # @return [Gricer::Session]
223
+ def init_session
224
+ if session
225
+ if session.updated_at < Time.now - ::Gricer.config.max_session_duration
226
+ self.session = Session.create previous_session: session, ip_address: @ip_address, agent: agent, requested_locale: @request_locale
227
+ else
228
+ self.session.touch
229
+ end
230
+ else
231
+ self.is_first_in_session = true
232
+ self.session = Session.create ip_address: @ip_address, agent: agent, requested_locale: @request_locale
233
+ self.session.touch
234
+ end
235
+
236
+ session
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,433 @@
1
+ module Gricer
2
+ # ActiveRecord Model for Session Statistics
3
+ # @attr [Gricer::Session] previous_session
4
+ # The current value of the associated previous session.
5
+ #
6
+ # @attr [Gricer::Agent] agent
7
+ # The current value of the associated agent.
8
+ #
9
+ # @attr [String] ip_address_hash
10
+ # The current value of the ip address in an anonyminized hash
11
+ #
12
+ # @attr [String] domain
13
+ # The current value of the domain (only major info) from which the session was requested.
14
+ #
15
+ # @attr [String] country
16
+ # The current value of the country from which the session was requested.
17
+ #
18
+ # You need to configure a GeoIP in the {Gricer::Config} instance.
19
+ #
20
+ # @attr [String] region
21
+ # The current value of the region from which the session was requested.
22
+ #
23
+ # You need to configure a GeoIP in the {Gricer::Config} instance.
24
+ #
25
+ # @attr [String] city
26
+ # The current value of the city from which the session was requested.
27
+ #
28
+ # You need to configure a GeoIP in the {Gricer::Config} instance.
29
+ #
30
+ # @attr [String] postal_code
31
+ # The current value of the region from which the session was requested.
32
+ #
33
+ # You need to configure a GeoIP in the {Gricer::Config} instance.
34
+ #
35
+ # @attr [Float] longitude
36
+ # The current value of the longitude of the city/region from which the session was requested.
37
+ #
38
+ # You need to configure a GeoIP in the {Gricer::Config} instance.
39
+ #
40
+ # @attr [Float] latitude
41
+ # The current value of the latitude of the city/region from which the session was requested.
42
+ #
43
+ # You need to configure a GeoIP in the {Gricer::Config} instance.
44
+ #
45
+ # @attr [Boolean] javascript
46
+ # The current value of the javascript capability of the requesting agent.
47
+ #
48
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
49
+ #
50
+ # @attr [Boolean] java
51
+ # The current value of the java capability of the requesting agent.
52
+ #
53
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
54
+ #
55
+ # @attr [String] flash_version
56
+ # The current value of the version of flash installed on the requesting agent.
57
+ #
58
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
59
+ #
60
+ # @attr [String] flash_major_version
61
+ # The current value of the major version of flash installed on the requesting agent.
62
+ #
63
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
64
+ #
65
+ # @attr [String] silverlight_version
66
+ # The current value of the version of silverlight installed on the requesting agent.
67
+ #
68
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
69
+ #
70
+ # @attr [String] silverlight_major_version
71
+ # The current value of the major version of silverlight installed on the requesting agent.
72
+ #
73
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
74
+ #
75
+ # @attr [Integer] screen_width
76
+ # The current value of the width in pixels of the screen the requesting agent's window is on.
77
+ #
78
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
79
+ #
80
+ # @attr [Integer] screen_height
81
+ # The current value of the height in pixels of the screen the requesting agent's window is on.
82
+ #
83
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
84
+ #
85
+ # @attr [Integer] screen_depth
86
+ # The current value of the depth in bits of the screen the requesting agent's window is on.
87
+ #
88
+ # This feature needs the usage of {Gricer::TrackHelper#gricer_track_tag}.
89
+ #
90
+ # @attr [String] requested_locale_major
91
+ # The current value of the locale requested (major locale only)
92
+ #
93
+ # @attr [String] requested_locale_minor
94
+ # The current value of the locale requested (major locale only)
95
+ #
96
+ # @attr [String] requested_locale
97
+ # The current value of the locale requested
98
+ #
99
+ class Session < ::ActiveRecord::Base
100
+ set_table_name "#{::Gricer::config.table_name_prefix}sessions"
101
+ include ActiveModel::Statistics
102
+
103
+ has_many :requests, class_name: 'Gricer::Request', foreign_key: :session_id, order: 'created_at ASC'
104
+ belongs_to :agent, class_name: 'Gricer::Agent', foreign_key: :agent_id, counter_cache: true
105
+ belongs_to :previous_session, class_name: 'Gricer::Session', foreign_key: :previous_session_id
106
+
107
+ # Filter out anything that is not a Browser or MobileBrowser
108
+ # @return [ActiveRecord::Relation]
109
+ def self.browsers
110
+ self.includes("agent")
111
+ .where("\"#{Agent.table_name}\".\"agent_class_id\" IN (?)", [0x1000, 0x2000])
112
+ end
113
+
114
+ # Filter out only bounce sessions (sessions with just one request)
115
+ # @return [ActiveRecord::Relation]
116
+ def self.bounce_sessions
117
+ self.where("\"#{self.table_name}\".\"requests_count\" = ?", 1)
118
+ end
119
+
120
+ # Filter out only new visits (which does not have a previous_session)
121
+ # @return [ActiveRecord::Relation]
122
+ def self.new_visits
123
+ where("\"#{self.table_name}\".\"previous_session_id\" IS NULL")
124
+ end
125
+
126
+ # This is just a list of two-part domain endings.
127
+ # It is used to get the domain from the request.
128
+ # return [Array]
129
+ def two_part_domain_endings
130
+ [
131
+ 'com.ac','edu.ac','gov.ac','net.ac','mil.ac',
132
+ 'org.ac','com.ae','net.ae','org.ae','gov.ae','ac.ae',
133
+ 'co.ae','sch.ae','pro.ae','com.ai','org.ai','edu.ai',
134
+ 'gov.ai','com.ar','net.ar','org.ar','gov.ar','mil.ar',
135
+ 'edu.ar','int.ar','co.at','ac.at','or.at','gv.at','priv.at',
136
+ 'com.au','gov.au','org.au','edu.au','id.au','oz.au',
137
+ 'info.au','net.au','asn.au','csiro.au','telememo.au',
138
+ 'conf.au','otc.au','com.az','net.az','org.az','com.bb',
139
+ 'net.bb','org.bb','ac.be','belgie.be','dns.be','fgov.be',
140
+ 'com.bh','gov.bh','net.bh','edu.bh','org.bh','com.bm',
141
+ 'edu.bm','gov.bm','org.bm','net.bm','adm.br','adv.br',
142
+ 'agr.br','am.br','arq.br','art.br','ato.br','bio.br',
143
+ 'bmd.br','cim.br','cng.br','cnt.br','com.br','coop.br',
144
+ 'ecn.br','edu.br','eng.br','esp.br','etc.br','eti.br',
145
+ 'far.br','fm.br','fnd.br','fot.br','fst.br','g12.br',
146
+ 'ggf.br','gov.br','imb.br','ind.br','inf.br','jor.br',
147
+ 'lel.br','mat.br','med.br','mil.br','mus.br','net.br',
148
+ 'nom.br','not.br','ntr.br','odo.br','org.br','ppg.br',
149
+ 'pro.br','psc.br','psi.br','qsl.br','rec.br','slg.br',
150
+ 'srv.br','tmp.br','trd.br','tur.br','tv.br','vet.br',
151
+ 'zlg.br','com.bs','net.bs','org.bs','ab.ca','bc.ca',
152
+ 'mb.ca','nb.ca','nf.ca','nl.ca','ns.ca','nt.ca','nu.ca',
153
+ 'on.ca','pe.ca','qc.ca','sk.ca','yk.ca','gc.ca','co.ck',
154
+ 'net.ck','org.ck','edu.ck','gov.ck','com.cn','edu.cn',
155
+ 'gov.cn','net.cn','org.cn','ac.cn','ah.cn','bj.cn','cq.cn',
156
+ 'gd.cn','gs.cn','gx.cn','gz.cn','hb.cn','he.cn','hi.cn',
157
+ 'hk.cn','hl.cn','hn.cn','jl.cn','js.cn','ln.cn','mo.cn',
158
+ 'nm.cn','nx.cn','qh.cn','sc.cn','sn.cn','sh.cn','sx.cn',
159
+ 'tj.cn','tw.cn','xj.cn','xz.cn','yn.cn','zj.cn','arts.co',
160
+ 'com.co','edu.co','firm.co','gov.co','info.co','int.co',
161
+ 'nom.co','mil.co','org.co','rec.co','store.co','web.co',
162
+ 'ac.cr','co.cr','ed.cr','fi.cr','go.cr','or.cr','sa.cr',
163
+ 'com.cu','net.cu','org.cu','ac.cy','com.cy','gov.cy',
164
+ 'net.cy','org.cy','co.dk','art.do','com.do','edu.do',
165
+ 'gov.do','gob.do','org.do','mil.do','net.do','sld.do',
166
+ 'web.do','com.dz','org.dz','net.dz','gov.dz','edu.dz',
167
+ 'ass.dz','pol.dz','art.dz','com.ec','k12.ec','edu.ec',
168
+ 'fin.ec','med.ec','gov.ec','mil.ec','org.ec','net.ec',
169
+ 'com.ee','pri.ee','fie.ee','org.ee','med.ee','com.eg',
170
+ 'edu.eg','eun.eg','gov.eg','net.eg','org.eg','sci.eg',
171
+ 'com.er','net.er','org.er','edu.er','mil.er','gov.er',
172
+ 'ind.er','com.es','org.es','gob.es','edu.es','nom.es',
173
+ 'com.et','gov.et','org.et','edu.et','net.et','biz.et',
174
+ 'name.et','info.et','ac.fj','com.fj','gov.fj','id.fj',
175
+ 'org.fj','school.fj','com.fk','ac.fk','gov.fk','net.fk',
176
+ 'nom.fk','org.fk','asso.fr','nom.fr','barreau.fr',
177
+ 'com.fr','prd.fr','presse.fr','tm.fr','aeroport.fr',
178
+ 'assedic.fr','avocat.fr','avoues.fr','cci.fr',
179
+ 'chambagri.fr','chirurgiens-dentistes.fr',
180
+ 'experts-comptables.fr','geometre-expert.fr',
181
+ 'gouv.fr','greta.fr','huissier-justice.fr','medecin.fr',
182
+ 'notaires.fr','pharmacien.fr','port.fr','veterinaire.fr',
183
+ 'com.ge','edu.ge','gov.ge','mil.ge','net.ge','org.ge',
184
+ 'pvt.ge','co.gg','org.gg','sch.gg','ac.gg','gov.gg',
185
+ 'ltd.gg','ind.gg','net.gg','alderney.gg','guernsey.gg',
186
+ 'sark.gg','com.gt','edu.gt','net.gt','gob.gt','org.gt',
187
+ 'mil.gt','ind.gt','com.gu','edu.gu','net.gu','org.gu',
188
+ 'gov.gu','mil.gu','com.hk','net.hk','org.hk','idv.hk',
189
+ 'gov.hk','edu.hk','co.hu','2000.hu','erotika.hu',
190
+ 'jogasz.hu','sex.hu','video.hu','info.hu','agrar.hu',
191
+ 'film.hu','konyvelo.hu','shop.hu','org.hu','bolt.hu',
192
+ 'forum.hu','lakas.hu','suli.hu','priv.hu','casino.hu',
193
+ 'games.hu','media.hu','szex.hu','sport.hu','city.hu',
194
+ 'hotel.hu','news.hu','tozsde.hu','tm.hu','erotica.hu',
195
+ 'ingatlan.hu','reklam.hu','utazas.hu','ac.id','co.id',
196
+ 'go.id','mil.id','net.id','or.id','co.il','net.il',
197
+ 'org.il','ac.il','gov.il','k12.il','muni.il','idf.il',
198
+ 'co.im','net.im','org.im','ac.im','lkd.co.im','gov.im',
199
+ 'nic.im','plc.co.im','co.in','net.in','ac.in','ernet.in',
200
+ 'gov.in','nic.in','res.in','gen.in','firm.in','mil.in',
201
+ 'org.in','ind.in','ac.je','co.je','net.je','org.je',
202
+ 'gov.je','ind.je','jersey.je','ltd.je','sch.je','com.jo',
203
+ 'org.jo','net.jo','gov.jo','edu.jo','mil.jo','ad.jp',
204
+ 'ac.jp','co.jp','go.jp','or.jp','ne.jp','gr.jp','ed.jp',
205
+ 'lg.jp','net.jp','org.jp','gov.jp','hokkaido.jp',
206
+ 'aomori.jp','iwate.jp','miyagi.jp','akita.jp',
207
+ 'yamagata.jp','fukushima.jp','ibaraki.jp','tochigi.jp',
208
+ 'gunma.jp','saitama.jp','chiba.jp','tokyo.jp',
209
+ 'kanagawa.jp','niigata.jp','toyama.jp','ishikawa.jp',
210
+ 'fukui.jp','yamanashi.jp','nagano.jp','gifu.jp',
211
+ 'shizuoka.jp','aichi.jp','mie.jp','shiga.jp','kyoto.jp',
212
+ 'osaka.jp','hyogo.jp','nara.jp','wakayama.jp','tottori.jp',
213
+ 'shimane.jp','okayama.jp','hiroshima.jp','yamaguchi.jp',
214
+ 'tokushima.jp','kagawa.jp','ehime.jp','kochi.jp',
215
+ 'fukuoka.jp','saga.jp','nagasaki.jp','kumamoto.jp',
216
+ 'oita.jp','miyazaki.jp','kagoshima.jp','okinawa.jp',
217
+ 'sapporo.jp','sendai.jp','yokohama.jp','kawasaki.jp',
218
+ 'nagoya.jp','kobe.jp','kitakyushu.jp','utsunomiya.jp',
219
+ 'kanazawa.jp','takamatsu.jp','matsuyama.jp','com.kh',
220
+ 'net.kh','org.kh','per.kh','edu.kh','gov.kh','mil.kh',
221
+ 'ac.kr','co.kr','go.kr','ne.kr','or.kr','pe.kr','re.kr',
222
+ 'seoul.kr','kyonggi.kr','com.kw','net.kw','org.kw',
223
+ 'edu.kw','gov.kw','com.la','net.la','org.la','com.lb',
224
+ 'org.lb','net.lb','edu.lb','gov.lb','mil.lb','com.lc',
225
+ 'edu.lc','gov.lc','net.lc','org.lc','com.lv','net.lv',
226
+ 'org.lv','edu.lv','gov.lv','mil.lv','id.lv','asn.lv',
227
+ 'conf.lv','com.ly','net.ly','org.ly','co.ma','net.ma',
228
+ 'org.ma','press.ma','ac.ma','com.mk','com.mm','net.mm',
229
+ 'org.mm','edu.mm','gov.mm','com.mo','net.mo','org.mo',
230
+ 'edu.mo','gov.mo','com.mt','net.mt','org.mt','edu.mt',
231
+ 'tm.mt','uu.mt','com.mx','net.mx','org.mx','com.my',
232
+ 'org.my','gov.my','edu.my','net.my','com.na','org.na',
233
+ 'net.na','alt.na','edu.na','cul.na','unam.na','telecom.na',
234
+ 'com.nc','net.nc','org.nc','ac.ng','edu.ng','sch.ng',
235
+ 'com.ng','gov.ng','org.ng','net.ng','gob.ni','com.ni',
236
+ 'net.ni','edu.ni','nom.ni','org.ni','com.np','net.np',
237
+ 'org.np','gov.np','edu.np','ac.nz','co.nz','cri.nz',
238
+ 'gen.nz','geek.nz','govt.nz','iwi.nz','maori.nz','mil.nz',
239
+ 'net.nz','org.nz','school.nz','com.om','co.om','edu.om',
240
+ 'ac.om','gov.om','net.om','org.om','mod.om','museum.om',
241
+ 'biz.om','pro.om','med.om','com.pa','net.pa','org.pa',
242
+ 'edu.pa','ac.pa','gob.pa','sld.pa','edu.pe','gob.pe',
243
+ 'nom.pe','mil.pe','org.pe','com.pe','net.pe','com.pg',
244
+ 'net.pg','ac.pg','com.ph','net.ph','org.ph','mil.ph',
245
+ 'ngo.ph','aid.pl','agro.pl','atm.pl','auto.pl','biz.pl',
246
+ 'com.pl','edu.pl','gmina.pl','gsm.pl','info.pl','mail.pl',
247
+ 'miasta.pl','media.pl','mil.pl','net.pl',
248
+ 'nieruchomosci.pl','nom.pl','org.pl','pc.pl','powiat.pl',
249
+ 'priv.pl','realestate.pl','rel.pl','sex.pl','shop.pl',
250
+ 'sklep.pl','sos.pl','szkola.pl','targi.pl','tm.pl',
251
+ 'tourism.pl','travel.pl','turystyka.pl','com.pk','net.pk',
252
+ 'edu.pk','org.pk','fam.pk','biz.pk','web.pk','gov.pk',
253
+ 'gob.pk','gok.pk','gon.pk','gop.pk','gos.pk','edu.ps',
254
+ 'gov.ps','plo.ps','sec.ps','com.py','net.py','org.py',
255
+ 'edu.py','com.qa','net.qa','org.qa','edu.qa','gov.qa',
256
+ 'asso.re','com.re','nom.re','com.ro','org.ro','tm.ro',
257
+ 'nt.ro','nom.ro','info.ro','rec.ro','arts.ro','firm.ro',
258
+ 'store.ro','www.ro','com.ru','net.ru','org.ru','gov.ru',
259
+ 'pp.ru','com.sa','edu.sa','sch.sa','med.sa','gov.sa',
260
+ 'net.sa','org.sa','pub.sa','com.sb','net.sb','org.sb',
261
+ 'edu.sb','gov.sb','com.sd','net.sd','org.sd','edu.sd',
262
+ 'sch.sd','med.sd','gov.sd','tm.se','press.se','parti.se',
263
+ 'brand.se','fh.se','fhsk.se','fhv.se','komforb.se',
264
+ 'kommunalforbund.se','komvux.se','lanarb.se','lanbib.se',
265
+ 'naturbruksgymn.se','sshn.se','org.se','pp.se','com.sg',
266
+ 'net.sg','org.sg','edu.sg','gov.sg','per.sg','com.sh',
267
+ 'net.sh','org.sh','edu.sh','gov.sh','mil.sh','gov.st',
268
+ 'saotome.st','principe.st','consulado.st','embaixada.st',
269
+ 'org.st','edu.st','net.st','com.st','store.st','mil.st',
270
+ 'co.st','com.sv','org.sv','edu.sv','gob.sv','red.sv',
271
+ 'com.sy','net.sy','org.sy','gov.sy','ac.th','co.th',
272
+ 'go.th','net.th','or.th','com.tn','net.tn','org.tn',
273
+ 'edunet.tn','gov.tn','ens.tn','fin.tn','nat.tn','ind.tn',
274
+ 'info.tn','intl.tn','rnrt.tn','rnu.tn','rns.tn',
275
+ 'tourism.tn','com.tr','net.tr','org.tr','edu.tr','gov.tr',
276
+ 'mil.tr','bbs.tr','k12.tr','gen.tr','co.tt','com.tt',
277
+ 'org.tt','net.tt','biz.tt','info.tt','pro.tt','name.tt',
278
+ 'gov.tt','edu.tt','nic.tt','us.tt','uk.tt','ca.tt','eu.tt',
279
+ 'es.tt','fr.tt','it.tt','se.tt','dk.tt','be.tt','de.tt',
280
+ 'at.tt','au.tt','co.tv','com.tw','net.tw','org.tw',
281
+ 'edu.tw','idv.tw','gove.tw','com.ua','net.ua','org.ua',
282
+ 'edu.ua','gov.ua','ac.ug','co.ug','or.ug','go.ug','co.uk',
283
+ 'me.uk','org.uk','edu.uk','ltd.uk','plc.uk','net.uk',
284
+ 'sch.uk','nic.uk','ac.uk','gov.uk','nhs.uk','police.uk',
285
+ 'mod.uk','dni.us','fed.us','com.uy','edu.uy','net.uy',
286
+ 'org.uy','gub.uy','mil.uy','com.ve','net.ve','org.ve',
287
+ 'co.ve','edu.ve','gov.ve','mil.ve','arts.ve','bib.ve',
288
+ 'firm.ve','info.ve','int.ve','nom.ve','rec.ve','store.ve',
289
+ 'tec.ve','web.ve','co.vi','net.vi','org.vi','com.vn',
290
+ 'biz.vn','edu.vn','gov.vn','net.vn','org.vn','int.vn',
291
+ 'ac.vn','pro.vn','info.vn','health.vn','name.vn','com.vu',
292
+ 'edu.vu','net.vu','org.vu','de.vu','ch.vu','fr.vu',
293
+ 'com.ws','net.ws','org.ws','gov.ws','edu.ws','ac.yu',
294
+ 'co.yu','edu.yu','org.yu','com.ye','net.ye','org.ye',
295
+ 'gov.ye','edu.ye','mil.ye','ac.za','alt.za','bourse.za',
296
+ 'city.za','co.za','edu.za','gov.za','law.za','mil.za',
297
+ 'net.za','ngo.za','nom.za','org.za','school.za','tm.za',
298
+ 'web.za','co.zw','ac.zw','org.zw','gov.zw','eu.org',
299
+ 'au.com','br.com','cn.com','de.com','de.net','eu.com',
300
+ 'gb.com','gb.net','hu.com','no.com','qc.com','ru.com',
301
+ 'sa.com','se.com','uk.com','uk.net','us.com','uy.com',
302
+ 'za.com','dk.org','tel.no','fax.nr','mob.nr','mobil.nr',
303
+ 'mobile.nr','tel.nr','tlf.nr','e164.arpa'
304
+ ]
305
+ end
306
+
307
+ # Parse the requesting IP address and fill corrosponding attributes
308
+ # @param ip [String] An IP address (IPv4 only at the moment)
309
+ def ip_address=(ip)
310
+ require 'digest/sha1'
311
+ require 'resolv'
312
+
313
+ logger.debug("IP: #{ip}")
314
+
315
+ unless ip.blank?
316
+ self.ip_address_hash = Digest::SHA1.hexdigest(ip)
317
+
318
+ # IP
319
+ begin
320
+ domain_parts = Resolv.getname(ip).split('.')
321
+
322
+ self.domain = "#{domain_parts[-1]}"
323
+
324
+ self.domain = "#{domain_parts[-2]}.#{domain}" if domain_parts[-2]
325
+
326
+ if two_part_domain_endings.include? self.domain
327
+ self.domain = "#{domain_parts[-3]}.#{self.domain}"
328
+ end
329
+ rescue
330
+ ip_parts = ip.split('.')
331
+ self.domain = "#{ip_parts[0]}.#{ip_parts[1]}.x.x"
332
+ end
333
+
334
+ # GeoIP
335
+ if ::Gricer.config.geoip_db
336
+ if Rails.env.development?
337
+ geoip = {:country_code=>"DE", :country_code3=>"DEU", :country_name=>"Germany", :latitude=>51.0, :longitude=>9.0}
338
+ else
339
+ geoip = ::Gricer.config.geoip_db.look_up(ip)
340
+ end
341
+
342
+
343
+ if geoip
344
+ self.country = geoip[:country_code].try(:downcase)
345
+ self.region = geoip[:region].try(:downcase)
346
+ self.city = geoip[:city]
347
+ self.postal_code = geoip[:postal_code]
348
+ self.longitude = geoip[:longitude]
349
+ self.latitude = geoip[:latitude]
350
+ end
351
+ end
352
+ end
353
+ end
354
+
355
+ def requested_locale=(locale)
356
+ self.requested_locale_major, self.requested_locale_minor = locale.try(:downcase).try(:split, '-')
357
+ end
358
+
359
+ def requested_locale
360
+ if requested_locale_minor
361
+ "#{requested_locale_major}-#{requested_locale_minor}"
362
+ else
363
+ requested_locale_major
364
+ end
365
+ end
366
+
367
+ def silverlight_version=(version)
368
+ self[:silverlight_version] = version
369
+ self.silverlight_major_version = silverlight_version.match(/^([0-9]*\.[0-9]*)/).to_a.last if silverlight_version
370
+ end
371
+
372
+ def flash_version=(version)
373
+ self[:flash_version] = version
374
+ self.flash_major_version = flash_version.match(/^([0-9]*\.[0-9]*)/).to_a.last if flash_version
375
+ end
376
+
377
+ # Get the bounce rate
378
+ #
379
+ # This is the rate of sessions with one request to sessions with multiple requests.
380
+ # @return [Float]
381
+ def self.bounce_rate
382
+ if (c = self.count) > 0
383
+ self.bounce_sessions.count / c.to_f
384
+ else
385
+ 0
386
+ end
387
+ end
388
+
389
+ # Get the average count of requests per session.
390
+ #
391
+ # @return [Float]
392
+ def self.requests_per_session
393
+ if (c = self.count) > 0
394
+ self.sum(:requests_count) / c.to_f
395
+ else
396
+ 0
397
+ end
398
+ end
399
+
400
+ # Get the average duration of sessions in seconds.
401
+ #
402
+ # @return [Float]
403
+ def self.avg_duration
404
+ if (c = self.count) > 0
405
+ logger.debug ActiveRecord::Base.connection.class
406
+
407
+ if ActiveRecord::Base.connection.class.to_s == 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
408
+ self.sum("date_part('epoch', \"#{self.table_name}\".\"updated_at\") - date_part('epoch', \"#{self.table_name}\".\"created_at\")").to_f / c.to_f
409
+ else
410
+ self.sum("strftime('%s', \"#{self.table_name}\".\"updated_at\") - strftime('%s', \"#{self.table_name}\".\"created_at\")") / c.to_f
411
+ end
412
+ else
413
+ 0
414
+ end
415
+ end
416
+
417
+ # Get the duration of the current session instance
418
+ def duration
419
+ updated_at - created_at
420
+ end
421
+
422
+ # Get the new visitor rate
423
+ #
424
+ # This is the rate of new sessions to all sessions
425
+ def self.new_visitors
426
+ if (c = self.count) > 0
427
+ self.new_visits.count / c.to_f
428
+ else
429
+ 0
430
+ end
431
+ end
432
+ end
433
+ end