StatsCollect 0.1.0.20101220
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +2 -0
- data/ChangeLog +5 -0
- data/Credits +31 -0
- data/LICENSE +31 -0
- data/README +18 -0
- data/ReleaseInfo +8 -0
- data/StatsCollect.conf.rb.example +91 -0
- data/TODO +1 -0
- data/bin/StatsCollect.rb +30 -0
- data/lib/StatsCollect/Backends/MySQL.rb +190 -0
- data/lib/StatsCollect/Backends/Terminal.rb +157 -0
- data/lib/StatsCollect/Locations/AddThis.rb +65 -0
- data/lib/StatsCollect/Locations/Facebook.rb +62 -0
- data/lib/StatsCollect/Locations/FacebookArtist.rb +63 -0
- data/lib/StatsCollect/Locations/FacebookLike.rb +51 -0
- data/lib/StatsCollect/Locations/GoogleSearch.rb +39 -0
- data/lib/StatsCollect/Locations/MySpace.rb +224 -0
- data/lib/StatsCollect/Locations/ReverbNation.rb +143 -0
- data/lib/StatsCollect/Locations/Tweets.rb +51 -0
- data/lib/StatsCollect/Locations/Twitter.rb +46 -0
- data/lib/StatsCollect/Locations/Youtube.rb +69 -0
- data/lib/StatsCollect/Notifiers/None.rb +24 -0
- data/lib/StatsCollect/Notifiers/SendMail.rb +34 -0
- data/lib/StatsCollect/Stats.rb +447 -0
- data/lib/StatsCollect/StatsProxy.rb +106 -0
- metadata +89 -0
@@ -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
|