StatsCollect 0.1.0.20101220

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,46 @@
1
+ #--
2
+ # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module StatsCollect
7
+
8
+ module Locations
9
+
10
+ class Twitter
11
+
12
+ # Execute the plugin.
13
+ # This method has to add the stats and errors to the proxy.
14
+ # It can filter only objects and categories given.
15
+ # It has access to its configuration.
16
+ #
17
+ # Parameters:
18
+ # * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
19
+ # * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
20
+ # * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
21
+ # * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
22
+ def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
23
+ if ((oStatsProxy.isObjectIncluded?('Global')) and
24
+ ((oStatsProxy.isCategoryIncluded?('Following')) or
25
+ (oStatsProxy.isCategoryIncluded?('Followers')) or
26
+ (oStatsProxy.isCategoryIncluded?('Lists followers')) or
27
+ (oStatsProxy.isCategoryIncluded?('Tweets'))))
28
+ require 'mechanize'
29
+ lMechanizeAgent = Mechanize.new
30
+ lProfilePage = lMechanizeAgent.get("http://twitter.com/#{iConf[:Name]}")
31
+ lNbrFollowing = Integer(lProfilePage.root.css('span#following_count').first.content.strip)
32
+ lNbrFollowers = Integer(lProfilePage.root.css('span#follower_count').first.content.strip)
33
+ lNbrLists = Integer(lProfilePage.root.css('span#lists_count').first.content.strip)
34
+ lNbrTweets = Integer(lProfilePage.root.css('span#update_count').first.content.strip)
35
+ oStatsProxy.addStat('Global', 'Following', lNbrFollowing)
36
+ oStatsProxy.addStat('Global', 'Followers', lNbrFollowers)
37
+ oStatsProxy.addStat('Global', 'Lists followers', lNbrLists)
38
+ oStatsProxy.addStat('Global', 'Tweets', lNbrTweets)
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,69 @@
1
+ #--
2
+ # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module StatsCollect
7
+
8
+ module Locations
9
+
10
+ class Youtube
11
+
12
+ # Execute the plugin.
13
+ # This method has to add the stats and errors to the proxy.
14
+ # It can filter only objects and categories given.
15
+ # It has access to its configuration.
16
+ #
17
+ # Parameters:
18
+ # * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
19
+ # * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
20
+ # * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
21
+ # * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
22
+ def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
23
+ require 'mechanize'
24
+ lMechanizeAgent = Mechanize.new
25
+ lLoginForm = lMechanizeAgent.get('http://www.youtube.com').link_with(:text => 'Sign In').click.forms[1]
26
+ lLoginForm.Email = 'Muriel.Esteban@GMail.com'
27
+ lLoginForm.Passwd = 'M[K2LBVu0}xT|b<[<Q")'
28
+ lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first).meta.first.click
29
+ if ((oStatsProxy.isCategoryIncluded?('Video plays')) or
30
+ (oStatsProxy.isCategoryIncluded?('Video likes')) or
31
+ (oStatsProxy.isCategoryIncluded?('Video dislikes')) or
32
+ (oStatsProxy.isCategoryIncluded?('Video comments')) or
33
+ (oStatsProxy.isCategoryIncluded?('Video responses')))
34
+ getVideos(oStatsProxy, lMechanizeAgent)
35
+ end
36
+ end
37
+
38
+ # Get the videos statistics
39
+ #
40
+ # Parameters:
41
+ # * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
42
+ # * *iMechanizeAgent* (_Mechanize_): The agent reading pages
43
+ def getVideos(oStatsProxy, iMechanizeAgent)
44
+ lVideosPage = iMechanizeAgent.get('http://www.youtube.com/my_videos')
45
+ # List of videos read (used for display)
46
+ lLstVideosRead = []
47
+ lVideosPage.root.css('li.vm-video-item').each do |iVideoNode|
48
+ lVideoTitle = iVideoNode.css('div.vm-video-title a').first.content
49
+ lMetricNodes = iVideoNode.css('div.vm-video-metrics dl dd')
50
+ lNbrPlays = Integer(lMetricNodes[0].css('a').first.content.strip)
51
+ lNbrComments = Integer(lMetricNodes[1].content.strip)
52
+ lNbrResponses = Integer(lMetricNodes[2].content.strip)
53
+ lNbrLikes = Integer(lMetricNodes[3].content[0..-2].strip)
54
+ lNbrDislikes = Integer(lMetricNodes[4].content.strip)
55
+ oStatsProxy.addStat(lVideoTitle, 'Video plays', lNbrPlays)
56
+ oStatsProxy.addStat(lVideoTitle, 'Video comments', lNbrComments)
57
+ oStatsProxy.addStat(lVideoTitle, 'Video responses', lNbrResponses)
58
+ oStatsProxy.addStat(lVideoTitle, 'Video likes', lNbrLikes)
59
+ oStatsProxy.addStat(lVideoTitle, 'Video dislikes', lNbrDislikes)
60
+ lLstVideosRead << lVideoTitle
61
+ end
62
+ logDebug "#{lLstVideosRead.size} videos read: #{lLstVideosRead.join(', ')}"
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,24 @@
1
+ #--
2
+ # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module StatsCollect
7
+
8
+ module Notifiers
9
+
10
+ class None
11
+
12
+ # Send a given notification
13
+ #
14
+ # Parameters:
15
+ # * *iConf* (<em>map<Symbol,Object></em>): The notifier config
16
+ # * *iMessage* (_String_): Message to send
17
+ def sendNotification(iConf, iMessage)
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,34 @@
1
+ #--
2
+ # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module StatsCollect
7
+
8
+ module Notifiers
9
+
10
+ class SendMail
11
+
12
+ # Send a given notification
13
+ #
14
+ # Parameters:
15
+ # * *iConf* (<em>map<Symbol,Object></em>): The notifier config
16
+ # * *iMessage* (_String_): Message to send
17
+ def sendNotification(iConf, iMessage)
18
+ require 'mail'
19
+ Mail.defaults do
20
+ delivery_method(:smtp, iConf[:SMTP])
21
+ end
22
+ Mail.deliver do
23
+ from iConf[:From]
24
+ to iConf[:To]
25
+ subject "Report of stats collection - #{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}"
26
+ body iMessage
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,447 @@
1
+ #--
2
+ # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ require 'date'
7
+ require 'optparse'
8
+ require 'StatsCollect/StatsProxy'
9
+
10
+ module StatsCollect
11
+
12
+ # Stats orders statuses
13
+ # !!! Those constants are also defined in the RoR website.
14
+ STATS_ORDER_STATUS_TOBEPROCESSED = 0
15
+ STATS_ORDER_STATUS_RECOVERABLE_ERROR = 1
16
+ STATS_ORDER_STATUS_UNRECOVERABLE_ERROR = 2
17
+ # Value types
18
+ # !!! Those constants are also defined in the RoR website.
19
+ STATS_VALUE_TYPE_INTEGER = 0
20
+ STATS_VALUE_TYPE_FLOAT = 1
21
+ STATS_VALUE_TYPE_PERCENTAGE = 2
22
+ STATS_VALUE_TYPE_UNKNOWN = 3
23
+
24
+ class Stats
25
+
26
+ # Constructor
27
+ def initialize
28
+ # Parse for available Locations
29
+ require 'rUtilAnts/Plugins'
30
+ RUtilAnts::Plugins::initializePlugins
31
+ parsePluginsFromDir('Locations', "#{File.expand_path(File.dirname(__FILE__))}/Locations", 'StatsCollect::Locations')
32
+ parsePluginsFromDir('Backends', "#{File.expand_path(File.dirname(__FILE__))}/Backends", 'StatsCollect::Backends')
33
+ parsePluginsFromDir('Notifiers', "#{File.expand_path(File.dirname(__FILE__))}/Notifiers", 'StatsCollect::Notifiers')
34
+
35
+ @Backend = nil
36
+ @Notifier = nil
37
+ @ConfigFile = nil
38
+ @DisplayHelp = false
39
+
40
+ # The command line parser
41
+ @Options = OptionParser.new
42
+ @Options.banner = 'StatsCollect.rb [--help] [--debug] --backend <Backend> --notifier <Notifier> --config <ConfigFile>'
43
+ @Options.on( '--backend <Backend>', String,
44
+ "<Backend>: Backend to be used. Available backends are: #{getPluginNames('Backends').join(', ')}",
45
+ 'Specify the backend to be used') do |iArg|
46
+ @Backend = iArg
47
+ end
48
+ @Options.on( '--notifier <Notifier>', String,
49
+ "<Notifier>: Notifier used to send notifications. Available notifiers are: #{getPluginNames('Notifiers').join(', ')}",
50
+ 'Specify the notifier to be used') do |iArg|
51
+ @Notifier = iArg
52
+ end
53
+ @Options.on( '--config <ConfigFile>', String,
54
+ '<ConfigFile>: The configuration file',
55
+ 'Specify the configuration file') do |iArg|
56
+ @ConfigFile = iArg
57
+ end
58
+ @Options.on( '--help',
59
+ 'Display help') do
60
+ @DisplayHelp = true
61
+ end
62
+ @Options.on( '--debug',
63
+ 'Activate debug logs') do
64
+ activateLogDebug(true)
65
+ end
66
+
67
+ end
68
+
69
+ # Check that the environment is correctly set.
70
+ # This has to be called prior to calling collect, and exit should be made if error code is not 0.
71
+ #
72
+ # Parameters:
73
+ # * *iParams* (<em>list<String></em>): The parameters, as given in the command line
74
+ # Return:
75
+ # * _Integer_: Error code (given to exit) (0 = no error)
76
+ def setup(iParams)
77
+ rErrorCode = 0
78
+
79
+ lRemainingArgs = nil
80
+ begin
81
+ lRemainingArgs = @Options.parse(iParams)
82
+ if (!lRemainingArgs.empty?)
83
+ logErr "Unknown arguments: #{lRemainingArgs.join(' ')}"
84
+ logErr @Options
85
+ rErrorCode = 1
86
+ end
87
+ rescue Exception
88
+ logErr "Exception: #{$!}.\n#{$!.backtrace.join("\n")}"
89
+ rErrorCode = 2
90
+ end
91
+ if (rErrorCode == 0)
92
+ if (@DisplayHelp)
93
+ logMsg @Options
94
+ rErrorCode = 3
95
+ elsif (@Backend == nil)
96
+ logErr 'You must specify a backend.'
97
+ logErr @Options
98
+ rErrorCode = 4
99
+ elsif (@Notifier == nil)
100
+ logErr 'You must specify a notifier.'
101
+ logErr @Options
102
+ rErrorCode = 5
103
+ elsif (@ConfigFile == nil)
104
+ logErr 'You must specify a config file.'
105
+ logErr @Options
106
+ rErrorCode = 6
107
+ else
108
+ @Conf = nil
109
+ # Read the configuration file
110
+ begin
111
+ File.open(@ConfigFile, 'r') do |iFile|
112
+ @Conf = eval(iFile.read)
113
+ end
114
+ rescue
115
+ logErr "Invalid configuration file: #{@ConfigFile}"
116
+ rErrorCode = 7
117
+ end
118
+ if (rErrorCode == 0)
119
+ # Get the corresponding notifier
120
+ if (@Conf[:Notifiers][@Notifier] == nil)
121
+ logErr "Notifier #{@Notifier} has no configuration set up in configuration file #{@ConfigFile}"
122
+ rErrorCode = 8
123
+ else
124
+ @NotifierInstance, lError = getPluginInstance('Notifiers', @Notifier)
125
+ if (@NotifierInstance == nil)
126
+ logErr "Unable to instantiate notifier #{@Notifier}: #{lError}"
127
+ rErrorCode = 9
128
+ else
129
+ # Will we notify the user of the script execution ?
130
+ @NotifyUser = true
131
+ # Get the corresponding backend
132
+ if (@Conf[:Backends][@Backend] == nil)
133
+ logErr "Backend #{@Backend} has no configuration set up in configuration file #{@ConfigFile}"
134
+ rErrorCode = 10
135
+ else
136
+ @BackendInstance, lError = getPluginInstance('Backends', @Backend)
137
+ if (@BackendInstance == nil)
138
+ logErr "Unable to instantiate backend #{@Backend}: #{lError}"
139
+ rErrorCode = 11
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ return rErrorCode
149
+ end
150
+
151
+ # Execute the stats collection
152
+ #
153
+ # Return:
154
+ # * _Integer_: Error code (given to exit)
155
+ def collect
156
+ rErrorCode = 0
157
+
158
+ # Prevent concurrent execution
159
+ require 'tmpdir'
160
+ lLockFile = "#{Dir.tmpdir}/StatsCollect.lock"
161
+ if (File.exists?(lLockFile))
162
+ logErr "Another instance of stats collection is already running. Delete file #{lLockFile} if it is not."
163
+ begin
164
+ lDetails = nil
165
+ File.open(lLockFile, 'r') do |iFile|
166
+ lDetails = eval(iFile.read)
167
+ end
168
+ logErr "Details of the running instance: #{lDetails.inspect}"
169
+ rErrorCode = 12
170
+ rescue Exception
171
+ logErr "Invalid lock file #{lLockFile}: #{$!}."
172
+ rErrorCode = 13
173
+ end
174
+ else
175
+ File.open(lLockFile, 'w') do |oFile|
176
+ oFile << "
177
+ {
178
+ :ExecutionTime => '#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}',
179
+ :PID => '#{Process.pid}'
180
+ }
181
+ "
182
+ end
183
+ begin
184
+ # Collect statistics
185
+ logInfo "[#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}] - Begin collecting stats..."
186
+ @BackendInstance.initSession(@Conf[:Backends][@Backend])
187
+ # Get the stats orders to process
188
+ lFoundOrder = false
189
+ lTimeStamp, lLstLocations, lLstObjects, lLstCategories, lStatus = @BackendInstance.getNextStatsOrder
190
+ while (lTimeStamp != nil)
191
+ lFoundOrder = true
192
+ logInfo "Dequeued stats order: Time: #{lTimeStamp}, Locations: #{lLstLocations.join('|')}, Objects: #{lLstObjects.join('|')}, Categories: #{lLstCategories.join('|')}, Status: #{lStatus}"
193
+ begin
194
+ lRecoverableOrders, lUnrecoverableOrders = processOrder(lLstObjects, lLstCategories, lLstLocations)
195
+ # Add recoverable orders back
196
+ lRecoverableOrders.each do |iOrderInfo|
197
+ iLstRecoverableObjects, iLstRecoverableCategories, iLstRecoverableLocations = iOrderInfo
198
+ logInfo "Enqueue recoverable order: Locations: #{iLstRecoverableLocations.join('|')}, Objects: #{iLstRecoverableObjects.join('|')}, Categories: #{iLstRecoverableCategories.join('|')}"
199
+ @BackendInstance.putNewStatsOrder(DateTime.now, iLstRecoverableLocations, iLstRecoverableObjects, iLstRecoverableCategories, STATS_ORDER_STATUS_RECOVERABLE_ERROR)
200
+ end
201
+ # Add unrecoverable orders back
202
+ lUnrecoverableOrders.each do |iOrderInfo|
203
+ iLstUnrecoverableObjects, iLstUnrecoverableCategories, iLstUnrecoverableLocations = iOrderInfo
204
+ logInfo "Enqueue unrecoverable order: Locations: #{iLstUnrecoverableLocations.join('|')}, Objects: #{iLstUnrecoverableObjects.join('|')}, Categories: #{iLstUnrecoverableCategories.join('|')}"
205
+ @BackendInstance.putNewStatsOrder(lTimeStamp, iLstUnrecoverableLocations, iLstUnrecoverableObjects, iLstUnrecoverableCategories, STATS_ORDER_STATUS_UNRECOVERABLE_ERROR)
206
+ end
207
+ @BackendInstance.commit
208
+ rescue Exception
209
+ @BackendInstance.rollback
210
+ logErr "Exception while processing order #{lTimeStamp}, Locations: #{lLstLocations.join('|')}, Objects: #{lLstObjects.join('|')}, Categories: #{lLstCategories.join('|')}, Status: #{lStatus}: #{$!}.\n#{$!.backtrace.join("\n")}\n"
211
+ rErrorCode = 14
212
+ end
213
+ lTimeStamp, lLstLocations, lLstObjects, lLstCategories, lStatus = @BackendInstance.getNextStatsOrder
214
+ end
215
+ if (!lFoundOrder)
216
+ @NotifyUser = false
217
+ end
218
+ logInfo "[#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}] - Stats collection finished."
219
+ File.unlink(lLockFile)
220
+ rescue Exception
221
+ logErr "Exception thrown while collecting stats: #{$!}.\n#{$!.backtrace.join("\n")}"
222
+ rErrorCode = 15
223
+ @NotifyUser = true
224
+ end
225
+ end
226
+
227
+ return rErrorCode
228
+ end
229
+
230
+ # Send notifications of a file content if necessary
231
+ #
232
+ # Parameters:
233
+ # * *iFileName* (_String_): The file containing notifications to be sent
234
+ def notify(iFileName)
235
+ if (@NotifyUser)
236
+ lMessage = nil
237
+ begin
238
+ File.open(iFileName, 'r') do |iFile|
239
+ lMessage = iFile.read
240
+ end
241
+ rescue Exception
242
+ lMessage = "Error while reading log file #{iFileName}: #{$!}"
243
+ end
244
+ @NotifierInstance.sendNotification(@Conf[:Notifiers][@Notifier], lMessage)
245
+ end
246
+ end
247
+
248
+ private
249
+
250
+ # Process an order
251
+ #
252
+ # Parameters:
253
+ # * *iObjectsList* (<em>list<String></em>): List of objects to filter (can be empty for all)
254
+ # * *iCategoriesList* (<em>list<String></em>): List of categories to filter (can be empty for all)
255
+ # * *iLocationsList* (<em>list<String></em>): List of locations to filter (can be empty for all)
256
+ # Return:
257
+ # * <em>list<[list<String>,list<String>,list<String>]></em>: The list of orders (objects, categories, locations) that could not be performed due to recoverable errors
258
+ # * <em>list<[list<String>,list<String>,list<String>]></em>: The list of orders (objects, categories, locations) that could not be performed due to unrecoverable errors
259
+ def processOrder(iObjectsList, iCategoriesList, iLocationsList)
260
+ rRecoverableOrders = []
261
+ rUnrecoverableOrders = []
262
+
263
+ # For each location, call the relevant plugin
264
+ lPlugins = []
265
+ if (iLocationsList.empty?)
266
+ lPlugins = getPluginNames('Locations')
267
+ else
268
+ lPlugins = iLocationsList
269
+ end
270
+ lErrorPlugins = []
271
+ lPlugins.each do |iPluginName|
272
+ lPluginConf = nil
273
+ if (@Conf[:Locations] != nil)
274
+ lPluginConf = @Conf[:Locations][iPluginName]
275
+ end
276
+ lPlugin, lError = getPluginInstance('Locations', iPluginName)
277
+ if (lError == nil)
278
+ # Ask the plugin to perform the order
279
+ lStatsProxy = StatsProxy.new(iObjectsList, iCategoriesList)
280
+ logInfo "===== Call Location plugin #{iPluginName} to perform order..."
281
+ begin
282
+ lPlugin.execute(lStatsProxy, lPluginConf, iObjectsList, iCategoriesList)
283
+ rescue Exception
284
+ logErr "Exception thrown during plugin #{iPluginName} execution: #{$!}.\n#{$!.backtrace.join("\n")}"
285
+ lErrorPlugins << iPluginName
286
+ lError = true
287
+ end
288
+ if (lError == nil)
289
+ # Write the stats into the database
290
+ writeStats(lStatsProxy.StatsToAdd, iPluginName, iObjectsList, iCategoriesList)
291
+ # If the plugin failed on recoverable errors, note them
292
+ lStatsProxy.RecoverableOrders.each do |iOrderInfo|
293
+ iLstObjects, iLstCategories = iOrderInfo
294
+ # Filter them: if we did not want them, do not add them back
295
+ lObjectsToAdd = intersectLists(iLstObjects, iObjectsList)
296
+ lCategoriesToAdd = intersectLists(iLstCategories, iCategoriesList)
297
+ if ((lObjectsToAdd != nil) and
298
+ (lCategoriesToAdd != nil))
299
+ rRecoverableOrders << [ lObjectsToAdd, lCategoriesToAdd, [iPluginName] ]
300
+ end
301
+ end
302
+ # If the plugin failed on unrecoverable errors, note them
303
+ lStatsProxy.UnrecoverableOrders.each do |iOrderInfo|
304
+ iLstObjects, iLstCategories = iOrderInfo
305
+ # Filter them: if we did not want them, do not add them back
306
+ lObjectsToAdd = intersectLists(iLstObjects, iObjectsList)
307
+ lCategoriesToAdd = intersectLists(iLstCategories, iCategoriesList)
308
+ if ((lObjectsToAdd != nil) and
309
+ (lCategoriesToAdd != nil))
310
+ rUnrecoverableOrders << [ lObjectsToAdd, lCategoriesToAdd, [iPluginName] ]
311
+ end
312
+ end
313
+ end
314
+ logInfo ''
315
+ else
316
+ logErr "Error while instantiating Location plugin #{iPluginName}: #{$!}."
317
+ lErrorPlugins << iPluginName
318
+ end
319
+ end
320
+ if (!lErrorPlugins.empty?)
321
+ rUnrecoverableOrders << [ iObjectsList, iCategoriesList, lErrorPlugins ]
322
+ end
323
+
324
+ return rRecoverableOrders, rUnrecoverableOrders
325
+ end
326
+
327
+ # Find names common to 2 different lists.
328
+ # Both lists can be empty to indicate all possible names.
329
+ # This method returns the intersection of both lists.
330
+ #
331
+ # Parameters:
332
+ # * *iLst1* (<em>list<String></em>): The first list
333
+ # * *iLst2* (<em>list<String></em>): The second list
334
+ # Return:
335
+ # * <em>list<String></em>: The resulting list. Can be empty for all possible names, or nil for no name.
336
+ def intersectLists(iLst1, iLst2)
337
+ rLstIntersection = nil
338
+
339
+ if (iLst1.empty?)
340
+ if (iLst2.empty?)
341
+ # Add all
342
+ rLstIntersection = []
343
+ else
344
+ rLstIntersection = iLst2
345
+ end
346
+ else
347
+ if (iLst2.empty?)
348
+ rLstIntersection = iLst1
349
+ else
350
+ # Filter
351
+ rLstIntersection = []
352
+ iLst1.each do |iObject|
353
+ if (iLst2.include?(iObject))
354
+ rLstIntersection << iObject
355
+ end
356
+ end
357
+ if (rLstIntersection.empty?)
358
+ rLstIntersection = nil
359
+ end
360
+ end
361
+ end
362
+
363
+ return rLstIntersection
364
+ end
365
+
366
+ # Write stats in the database.
367
+ # Apply some filter before.
368
+ #
369
+ # Parameters:
370
+ # * *iStatsToAdd* (<em>list<[TimeStamp,Object,Category,Value]></em>): The stats to write in the DB
371
+ # * *iLocation* (_String_): The location of these stats
372
+ # * *iLstObjects* (<em>list<String></em>): The filtering objects to write (can be empty for all)
373
+ # * *iLstCategories* (<em>list<String></em>): The filtering categories to write (can be empty for all)
374
+ def writeStats(iStatsToAdd, iLocation, iLstObjects, iLstCategories)
375
+ # Filter the stats we will really add
376
+ lStatsToBeCommitted = nil
377
+ if ((iLstObjects.empty?) and
378
+ (iLstCategories.empty?))
379
+ lStatsToBeCommitted = iStatsToAdd
380
+ else
381
+ # Filter
382
+ lStatsToBeCommitted = []
383
+ iStatsToAdd.each do |iStatsInfo|
384
+ iTimeStamp, iObject, iCategory, iValue = iStatsInfo
385
+ lOK = true
386
+ if (!iLstObjects.empty?)
387
+ lOK = iLstObjects.include?(iObject)
388
+ end
389
+ if ((lOK) and
390
+ (!iLstCategories.empty?))
391
+ lOK = iLstCategories.include?(iCategory)
392
+ end
393
+ if (lOK)
394
+ lStatsToBeCommitted << iStatsInfo
395
+ end
396
+ end
397
+ end
398
+
399
+ # Write stats if there are some
400
+ if (lStatsToBeCommitted.empty?)
401
+ logInfo 'No stats to be written after filtering.'
402
+ else
403
+ # Get the current locations from the DB to know if our location exists
404
+ lKnownLocations = @BackendInstance.getKnownLocations
405
+ lLocationID = lKnownLocations[iLocation]
406
+ if (lLocationID == nil)
407
+ # First create the new location and get its ID
408
+ logInfo "Creating new location: #{iLocation}"
409
+ lLocationID = @BackendInstance.addLocation(iLocation)
410
+ end
411
+ logDebug "Location used for those stats: #{iLocation} (#{lLocationID})"
412
+ # Get the list of categories, sorted by category name
413
+ lKnownCategories = @BackendInstance.getKnownCategories
414
+ # Get the list of objects, sorted by object name
415
+ # This map will eventually be completed if new objects are found among the stats to write.
416
+ lKnownObjects = @BackendInstance.getKnownObjects
417
+ # Add statistics
418
+ lStatsToBeCommitted.each do |iStatsInfo|
419
+ iTimeStamp, iObject, iCategory, iValue = iStatsInfo
420
+ # Check that the category exists
421
+ lValueType = nil
422
+ lCategoryID = nil
423
+ if (lKnownCategories[iCategory] == nil)
424
+ logWarn "Unknown stats category given by location #{iLocation}: #{iCategory}. It will be created with an Unknown value type."
425
+ lValueType = STATS_VALUE_TYPE_UNKNOWN
426
+ lCategoryID = @BackendInstance.addCategory(iCategory, lValueType)
427
+ lKnownCategories[iCategory] = [ lCategoryID, lValueType ]
428
+ else
429
+ lCategoryID, lValueType = lKnownCategories[iCategory]
430
+ end
431
+ # Check if we need to create the corresponding object
432
+ lObjectID = lKnownObjects[iObject]
433
+ if (lObjectID == nil)
434
+ logInfo "Creating new object: #{iObject}"
435
+ lObjectID = @BackendInstance.addObject(iObject)
436
+ lKnownObjects[iObject] = lObjectID
437
+ end
438
+ # Add the stat
439
+ @BackendInstance.addStat(iTimeStamp, lLocationID, lObjectID, lCategoryID, iValue, lValueType)
440
+ logDebug "Added stat: Time: #{iTimeStamp}, Location: #{iLocation} (#{lLocationID}), Object: #{iObject} (#{lObjectID}), Category: #{iCategory} (#{lCategoryID}), Value: #{iValue}"
441
+ end
442
+ end
443
+ end
444
+
445
+ end
446
+
447
+ end