StatsCollect 0.1.0.20101220

Sign up to get free protection for your applications and to get access to all the features.
@@ -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