ClickSpotter 0.1.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.
@@ -0,0 +1,53 @@
1
+ #
2
+ # DNSResolver.rb - ClickSpotter
3
+ #
4
+ # Copyright (c) 2005, 2006 by Chris Schlaeger <cs@kde.org>
5
+ # This program is free software; you can redistribute it and/or modify
6
+ # it under the terms of version 2 of the GNU General Public License as
7
+ # published by the Free Software Foundation.
8
+ #
9
+ # $Id: DNSResolver.rb 8 2006-01-22 15:19:51Z cs $
10
+ #
11
+
12
+ require 'socket'
13
+ require 'thread'
14
+
15
+ $dnsCache = "DNSResolverCache"
16
+
17
+ class DNSRecord
18
+ attr_reader :host
19
+ attr_accessor :timeStamp
20
+
21
+ def initialize
22
+ @host = nil
23
+ end
24
+
25
+ def resolve(ip)
26
+ begin
27
+ a = Socket.gethostbyname(ip)
28
+ @host = Socket.gethostbyaddr(a[3], a[2])[0]
29
+ rescue
30
+ @host = ip
31
+ end
32
+ @timeStamp = Time.now()
33
+ end
34
+
35
+ end
36
+
37
+ class DNSResolver < AsyncResolverWithCache
38
+
39
+ def initialize
40
+ super('DNSCache', $globals.getSetting('DNSTimeout') * 60 * 60 * 24, 5)
41
+ @enabled = $globals.getSetting('DNSResolver')
42
+ end
43
+
44
+ def newRecord
45
+ DNSRecord.new
46
+ end
47
+
48
+ def hostName(ip)
49
+ (rec = resolve(ip)) ? rec.host : ip
50
+ end
51
+
52
+ end
53
+
@@ -0,0 +1,24 @@
1
+ #
2
+ # GSServerLogSettings.rb - ClickSpotter
3
+ #
4
+ # Copyright (c) 2005, 2006 by Chris Schlaeger <cs@kde.org>
5
+ # This program is free software; you can redistribute it and/or modify
6
+ # it under the terms of version 2 of the GNU General Public License as
7
+ # published by the Free Software Foundation.
8
+ #
9
+ # $Id: GSServerLogSettings.rb 8 2006-01-22 15:19:51Z cs $
10
+ #
11
+
12
+ class GSServerLogSettings
13
+
14
+ attr_accessor :name, :logFile, :default, :hostNames
15
+
16
+ def initialize
17
+ @name = nil
18
+ @logFile = nil
19
+ @default = nil
20
+ @hostNames = []
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,351 @@
1
+ #
2
+ # GeoDistView.rb - ClickSpotter
3
+ #
4
+ # Copyright (c) 2005, 2006 by Chris Schlaeger <cs@kde.org>
5
+ # This program is free software; you can redistribute it and/or modify
6
+ # it under the terms of version 2 of the GNU General Public License as
7
+ # published by the Free Software Foundation.
8
+ #
9
+ # $Id: GeoDistView.rb 8 2006-01-22 15:19:51Z cs $
10
+ #
11
+
12
+ require 'GeoLocator'
13
+ require 'BrowserLauncher'
14
+ require 'Qt'
15
+ require 'ClickableCanvasView'
16
+
17
+ # This class shows the geographical distribution of visitors on a
18
+ # world map and as numbers per country in a list.
19
+
20
+ class GeoDistView < Qt::Object
21
+
22
+ include BrowserLauncher
23
+
24
+ slots 'routerListClicked(QListViewItem*)', 'popUpInfo()', 'mapClicked()'
25
+
26
+ attr_writer :showRouters, :showRoutes, :showIPs,
27
+ :showHostNames, :showCities, :showAll,
28
+ :showLast, :lastMinutes
29
+
30
+ # The mapFrame widget will hold the world map and the countryList will
31
+ # show the distribution by country.
32
+ def initialize(mapFrame, countryList = nil, routeList = nil, large = false)
33
+ super(nil)
34
+ @mapFrame = mapFrame
35
+ @countryList = countryList
36
+ @routeList = routeList
37
+
38
+ if @countryList
39
+ @countryListCSLV = CSListView.new(@countryList)
40
+ @countryList.setSorting(1, false)
41
+ end
42
+ if @routeList
43
+ @routeListCSLV = CSListView.new(@routeList)
44
+ @routeList.setSorting(0, true)
45
+
46
+ connect(@routeList, SIGNAL('clicked(QListViewItem*)'),
47
+ SLOT('routerListClicked(QListViewItem*)'))
48
+ end
49
+
50
+ # Canvas that shows the map and the location crosses
51
+ @canvas = Qt::Canvas.new
52
+ # The canvas view will be a child of the mapFrame widget
53
+ @mapFrame.setColumnLayout(0, Qt::Vertical)
54
+ @mapFrame.layout().setSpacing(6)
55
+ @mapFrame.layout().setMargin(11)
56
+ lm = Qt::GridLayout.new(mapFrame.layout)
57
+ # Create the CanvasView that shows the map canvas
58
+ @mapView = ClickableCanvasView.new(@canvas, @mapFrame)
59
+ lm.addWidget(@mapView, 0, 0)
60
+ # The world map is a background image of the canvas
61
+ pm = Qt::Pixmap.new(large ? AppConfig.dataFile('worldmap-large.jpg') :
62
+ AppConfig.dataFile('worldmap.png'))
63
+ if large
64
+ @mapView.setMaximumSize(pm.width + 2 * @mapView.frameWidth ,
65
+ pm.height + 2 * @mapView.frameWidth)
66
+ else
67
+ @mapView.setFixedSize(pm.width + 2 * @mapView.frameWidth ,
68
+ pm.height + 2 * @mapView.frameWidth)
69
+ @mapView.setVScrollBarMode(Qt::ScrollView.AlwaysOff)
70
+ @mapView.setHScrollBarMode(Qt::ScrollView.AlwaysOff)
71
+ end
72
+ connect(@mapView, SIGNAL('clicked()'), self, SLOT('mapClicked()'))
73
+
74
+ @canvas.resize(pm.width, pm.height)
75
+ @canvas.setBackgroundPixmap(pm)
76
+
77
+ @showAll = false
78
+ @showRoutes = false
79
+ @showRouters = false
80
+ @showIPs = false
81
+ @showHostNames = false
82
+ @showCities = false
83
+ @showLast = false
84
+ @lastMinutes = 1
85
+
86
+ @layer = 0
87
+
88
+ @highlightedVisitor = nil
89
+ @highlightedRouter = nil
90
+ end
91
+
92
+ # Update the map and the country distribution list.
93
+ def update(visitors)
94
+ # Remove all crosses from the map canvas
95
+ @canvas.allItems().each { |i| i.dispose }
96
+ @layer = 10
97
+
98
+ @highlightedVisitor = nil
99
+ @highlightedRouter = nil
100
+
101
+ # Determine a little square on the map the mouse cursor is over.
102
+ mouseRect = nil
103
+ pos = @mapView.mapFromGlobal(Qt::Cursor.new.pos);
104
+ if pos.x >= 0 && pos.y >= 0 &&
105
+ pos.x < @mapView.width && pos.y < @mapView.height
106
+ radius = 4
107
+ mouseRect = Qt::Rect.new(pos.x - radius, pos.y - radius,
108
+ 2 * radius, 2 * radius)
109
+ end
110
+
111
+ # Count the number of visitors for each country.
112
+ countries = {}
113
+ showSurfers = $globals.getSetting('ShowSurfers') != 0
114
+ showRobots = $globals.getSetting('ShowRobots') != 0
115
+ deadline = Time.now - @lastMinutes * 60
116
+ visitors.each do |key, visitor|
117
+ if (!@showAll &&
118
+ ((visitor.robot != nil && !showRobots) ||
119
+ (visitor.robot == nil && !showSurfers))) ||
120
+ (@showLast && visitor.lastHit.timeStamp < deadline)
121
+ next
122
+ end
123
+ markRouteOnMap(visitor.ip, mouseRect)
124
+ updateRouterList(visitor.ip) if @routeList
125
+ geoloc = $geoLocator.resolve(visitor.ip)
126
+ if geoloc == nil || geoloc.country == nil
127
+ label = '* Unknown *'
128
+ else
129
+ label = geoloc.country
130
+ # Mark all visitors with known locations on the map
131
+ if markOnMap(geoloc, "red", 8, visitor.ip, mouseRect)
132
+ @highlightedVisitor = visitor
133
+ end
134
+ end
135
+ countries[label] = 0 if !countries.has_key?(label)
136
+ countries[label] += 1
137
+ end
138
+ @canvas.update
139
+ if @countryList
140
+ @countryListCSLV.startUpdate
141
+ countries.each do |country, count|
142
+ @countryListCSLV.insertItem(country, country, count,
143
+ count * 100.0 / visitors.size)
144
+ end
145
+ @countryListCSLV.finishUpdate
146
+ end
147
+
148
+ end
149
+
150
+ def markRouteOnMap(ip, mouseRect)
151
+ return unless @showRouters || @showRoutes
152
+
153
+ rec = $traceRouter.resolve(ip)
154
+ return unless rec
155
+
156
+ last = nil
157
+ rec.routers.each do |r|
158
+ loc = $geoLocator.resolve(r)
159
+ next unless loc && loc.latitude && loc.longitude
160
+ drawConnection(last, loc) if @showRoutes
161
+ last = loc
162
+ if @showRouters && markOnMap(loc, 'yellow', 6, ip, mouseRect)
163
+ @highlightedRouter = ip
164
+ end
165
+ end
166
+ # Make sure that we always connect to the destination if we have found
167
+ # at least one router along the way.
168
+ if last
169
+ loc = $geoLocator.resolve(ip)
170
+ drawConnection(last, loc) if @showRoutes
171
+ end
172
+ end
173
+
174
+ def updateRouterList(ip)
175
+ rec = $traceRouter.resolve(ip)
176
+ return unless rec
177
+
178
+ i = 1
179
+ @routeListCSLV.startUpdate
180
+ rec.routers.each do |r|
181
+ loc = $geoLocator.resolve(r)
182
+ if loc
183
+ @routeListCSLV.insertItem(r, i, r, $resolver.hostName(r),
184
+ loc.country ? loc.country : '',
185
+ loc.city ? loc.city : '',
186
+ loc.latitude ? loc.latitude : 0,
187
+ loc.longitude ? loc.longitude : 0)
188
+ else
189
+ @routeListCSLV.insertItem(r, i, '*', '*', '', '', 0, 0)
190
+ end
191
+ i += 1
192
+ end
193
+ @routeListCSLV.finishUpdate
194
+ end
195
+
196
+ def drawConnection(loc1, loc2)
197
+ x1, y1 = loc2xy(loc1)
198
+ return unless x1 > 0 && y1 > 0
199
+ x2, y2 = loc2xy(loc2)
200
+ return unless x2 > 0 && y2 > 0
201
+
202
+ horizRollover = (x1 - x2).abs > (@canvas.width / 2)
203
+ vertRollover = (y1 - y2).abs > (@canvas.height / 2)
204
+
205
+ if !horizRollover && !vertRollover
206
+ l = Qt::CanvasLine.new(@canvas)
207
+ l.setPen(Qt::Pen.new(Qt::Color.new('gray'), 1))
208
+ l.setPoints(x1, y1, x2, y2)
209
+ l.z = 2
210
+ l.show
211
+ elsif horizRollover
212
+ xl, yl = x1 < x2 ? [ x1, y1 ] : [ x2, y2 ]
213
+ xr, yr = x1 > x2 ? [ x1, y1 ] : [ x2, y2 ]
214
+ l1 = Qt::CanvasLine.new(@canvas)
215
+ l1.setPen(Qt::Pen.new(Qt::Color.new('gray'), 1))
216
+ l1.setPoints(xr - @canvas.width, yr, xl, yl)
217
+ l1.z = 2
218
+ l1.show
219
+
220
+ l2 = Qt::CanvasLine.new(@canvas)
221
+ l2.setPen(Qt::Pen.new(Qt::Color.new('gray'), 1))
222
+ l2.setPoints(xl + @canvas.width, yl, xr, yr)
223
+ l2.z = 2
224
+ l2.show
225
+ else
226
+ xt, yt = y1 < y2 ? [ x1, y1 ] : [ x2, y2 ]
227
+ xb, yb = y1 > y2 ? [ x1, y1 ] : [ x2, y2 ]
228
+ l1 = Qt::CanvasLine.new(@canvas)
229
+ l1.setPen(Qt::Pen.new(Qt::Color.new('gray'), 1))
230
+ l1.setPoints(xt, yt - @canvas.height, xb, yb)
231
+ l1.z = 2
232
+ l1.show
233
+
234
+ l2 = Qt::CanvasLine.new(@canvas)
235
+ l2.setPen(Qt::Pen.new(Qt::Color.new('gray'), 1))
236
+ l2.setPoints(xb, yb + @canvas.height, xt, yt)
237
+ l2.z = 2
238
+ l2.show
239
+ end
240
+ end
241
+
242
+ # Mark the specified location with a small red cross on the map
243
+ def markOnMap(geoloc, color, pLayer, ip, mouseRect)
244
+ return false unless geoloc
245
+ x , y = loc2xy(geoloc)
246
+ return false unless x > 0 && y > 0
247
+
248
+ # In case this IP is under the mouse cursor we will show full details
249
+ # no matter that the show flags say.
250
+ showFullInfo = mouseRect && mouseRect.contains(x, y)
251
+
252
+ # Horizontal line
253
+ l1 = Qt::CanvasLine.new(@canvas)
254
+ l1.setPen(Qt::Pen.new(Qt::Color.new(color), 1))
255
+ l1.setPoints(x - 1, y, x + 2, y)
256
+ l1.z = pLayer
257
+ l1.show
258
+ # Vertical line
259
+ l2 = Qt::CanvasLine.new(@canvas)
260
+ l2.setPen(Qt::Pen.new(Qt::Color.new(color), 1))
261
+ l2.setPoints(x, y - 1, x, y + 2)
262
+ l2.z = pLayer
263
+ l2.show
264
+
265
+ label = ''
266
+ label += ip + "\n" if @showIPs || showFullInfo
267
+ label += $resolver.hostName(ip) + "\n" if @showHostNames || showFullInfo
268
+ label += geoloc.city + "\n" if (@showCities || showFullInfo) && geoloc.city
269
+ label += geoloc.country + "\n" if showFullInfo
270
+ drawLabel(x, y, color, label, showFullInfo) unless label.empty?
271
+
272
+ return showFullInfo
273
+ end
274
+
275
+ def loc2xy(geoloc)
276
+ return [ -1, -1 ] unless geoloc
277
+ # Check that location is valid
278
+ latitude = geoloc.latitude
279
+ longitude = geoloc.longitude
280
+ return [ -1, -1 ] if latitude == nil || longitude == nil
281
+ mapW = @canvas.width
282
+ mapH = @canvas.height
283
+ # Compute the center of the cross
284
+ x = (mapW / 2) + longitude * mapW / 360
285
+ y = (mapH / 2) - latitude * mapH / 180
286
+ [ x, y ]
287
+ end
288
+
289
+ def drawLabel(px, py, frameColor, text, large)
290
+
291
+ x = px + 5
292
+ y = py - 5
293
+
294
+ tl = Qt::CanvasText.new(text, @canvas)
295
+ tl.setColor(Qt::Color.new('black'))
296
+ font = Qt::Font.new
297
+ font.setPixelSize(large ? 11 : 9)
298
+ tl.setFont(font)
299
+ tl.x = x
300
+ tl.y = y
301
+ tl.z = @layer + 1
302
+ tl.show
303
+
304
+ bg = Qt::CanvasRectangle.new(tl.boundingRect, @canvas)
305
+ bg.setPen(Qt::Pen.new(Qt::Color.new(frameColor)))
306
+ bg.setBrush(Qt::Brush.new(Qt::Color.new('white')))
307
+ bg.z = @layer
308
+ bg.show
309
+
310
+ l = Qt::CanvasLine.new(@canvas)
311
+ l.setPen(Qt::Pen.new(Qt::Color.new(frameColor), 1))
312
+ l.setPoints(px, py, x, y + bg.boundingRect.height / 3)
313
+ l.show
314
+ l.z = 8
315
+
316
+ @layer += 2
317
+ end
318
+
319
+ def selectedItems
320
+ return [] unless @countryList
321
+ @countryListCSLV.selectedItems
322
+ end
323
+
324
+ def mapClicked()
325
+ if @highlightedVisitor
326
+ correctIP(@highlightedVisitor.ip)
327
+ elsif @highlightedRouter
328
+ correctIP(@highlightedRouter)
329
+ end
330
+ end
331
+
332
+ def routerListClicked(item)
333
+ return unless item
334
+
335
+ ip = @routeListCSLV.item(item)
336
+ return unless ip
337
+
338
+ correctIP(ip)
339
+ end
340
+
341
+ def correctIP(ip)
342
+ browseURL("http://www.hostip.info/correct.html?spip=#{ip}")
343
+
344
+ # I don't know how long it takes for the hostip server to provide the
345
+ # changed information but there seems to be a noticable delay. We set the
346
+ # record to timeout in 60 minutes to that it will be fetched again then.
347
+ $geoLocator.expireInMinutes(ip, 60)
348
+ end
349
+
350
+ end
351
+
@@ -0,0 +1,91 @@
1
+ #
2
+ # GeoLocator.rb - ClickSpotter
3
+ #
4
+ # Copyright (c) 2005, 2006 by Chris Schlaeger <cs@kde.org>
5
+ # This program is free software; you can redistribute it and/or modify
6
+ # it under the terms of version 2 of the GNU General Public License as
7
+ # published by the Free Software Foundation.
8
+ #
9
+ # $Id: GeoLocator.rb 8 2006-01-22 15:19:51Z cs $
10
+ #
11
+
12
+ require 'net/http'
13
+ require 'time'
14
+ require 'AsyncResolverWithCache'
15
+
16
+ class GeoLocatorRecord
17
+ attr_reader :country, :countryCode, :city, :latitude, :longitude
18
+ attr_accessor :timeStamp
19
+
20
+ def initialize
21
+ @country = nil
22
+ @countryCode = nil
23
+ @city = nil
24
+ @latitude = nil
25
+ @longitude = nil
26
+ @timeStamp = nil
27
+ end
28
+
29
+ def resolve(ip)
30
+ begin
31
+ http = Net::HTTP.new($globals.getSetting('GeolocHost'),
32
+ $globals.getSetting('GeolocPort'))
33
+ resp = http.get("/rough.php?ip=#{ip}&position=true", nil)
34
+ # This is how the data string should look like:
35
+ # data = <<EOS
36
+ # Country: GERMANY
37
+ # Country Code: DE
38
+ # City: Albstadt
39
+ # Latitude: 48.25
40
+ # Longitude: 9.0167
41
+ # Guessed: false
42
+ # EOS
43
+ resp.body.scan(/(.*):\s+(.*)\n/) do |tokens|
44
+ tokens[1].gsub!(/\|/, '')
45
+ case tokens[0]
46
+ when 'Country'
47
+ @country = tokens[1]
48
+ when 'Country Code'
49
+ @countryCode = tokens[1]
50
+ when 'City'
51
+ @city = tokens[1]
52
+ when 'Latitude'
53
+ val = tokens[1].to_f
54
+ @latitude = val if val != 0 && val > -90 && val < 90
55
+ when 'Longitude'
56
+ val = tokens[1].to_f
57
+ @longitude = val if val != 0 && val >= -180 && val <= 180
58
+ else
59
+ end
60
+ end
61
+ rescue
62
+ return false
63
+ end
64
+ @timeStamp = Time.now()
65
+ return true
66
+ end
67
+
68
+ end
69
+
70
+ class GeoLocator < AsyncResolverWithCache
71
+
72
+ def initialize
73
+ super('GeoLocatorCache',
74
+ $globals.getSetting('GeolocTimeout') * 60 * 60 * 24, 2)
75
+ @enabled = $globals.getSetting('Geolocator')
76
+ end
77
+
78
+ def newRecord
79
+ GeoLocatorRecord.new
80
+ end
81
+
82
+ def country(ip)
83
+ (rec = resolve(ip)) ? rec.country : nil
84
+ end
85
+
86
+ def city(ip)
87
+ (rec = resolve(ip)) ? rec.city : nil
88
+ end
89
+
90
+ end
91
+