StatsCollect 0.1.1.20101220 → 0.2.0.20110830

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
2
+ # Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
3
  # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
4
  #++
5
5
 
@@ -31,12 +31,14 @@ module StatsCollect
31
31
  lUserID = lHomePage.uri.to_s.match(/^http:\/\/www\.reverbnation\.com\/artist\/control_room\/(\d*)$/)[1]
32
32
  if ((oStatsProxy.isCategoryIncluded?('Song plays')) or
33
33
  (oStatsProxy.isCategoryIncluded?('Song downloads')) or
34
- (oStatsProxy.isCategoryIncluded?('Video plays')) or
35
34
  (oStatsProxy.isCategoryIncluded?('Song play ratio')) or
36
35
  (oStatsProxy.isCategoryIncluded?('Song likes')) or
37
36
  (oStatsProxy.isCategoryIncluded?('Song dislikes')))
38
37
  getPlays(oStatsProxy, lMechanizeAgent, lUserID)
39
38
  end
39
+ if (oStatsProxy.isCategoryIncluded?('Video plays'))
40
+ getVideos(oStatsProxy, lMechanizeAgent, lUserID)
41
+ end
40
42
  if ((oStatsProxy.isObjectIncluded?('Global')) and
41
43
  ((oStatsProxy.isCategoryIncluded?('Chart position genre')) or
42
44
  (oStatsProxy.isCategoryIncluded?('Chart position global')) or
@@ -54,8 +56,8 @@ module StatsCollect
54
56
  # * *iUserID* (_String_): The ReverbNation user ID
55
57
  def getPlays(oStatsProxy, iMechanizeAgent, iUserID)
56
58
  # Get the Ajax stats table
57
- lStatsTable = iMechanizeAgent.get("http://www.reverbnation.com/artist/new_stats_plays_table/#{iUserID}?all_time=true")
58
- lStatsTableNode = Nokogiri::HTML(lStatsTable.content[31..-4].gsub(/\\"/,'"').gsub(/\\n/,"\n").gsub(/\\r/,''))
59
+ lStatsTable = iMechanizeAgent.get("http://www.reverbnation.com/artist/new_stats_song_plays_table/#{iUserID}?all_time=true")
60
+ lStatsTableNode = Nokogiri::HTML(lStatsTable.content[36..-4].gsub(/\\"/,'"').gsub(/\\n/,"\n").gsub(/\\r/,''))
59
61
  # Screen scrap it
60
62
  lLstSongsRead = []
61
63
  lStatsTableNode.css('table.statstable_full tr')[1..-3].each do |iSongNode|
@@ -63,14 +65,12 @@ module StatsCollect
63
65
  lSongTitle = lNodeContents[1].content
64
66
  lNbrSongPlays = Integer(lNodeContents[3].content)
65
67
  lNbrSongDownloads = Integer(lNodeContents[4].content)
66
- lNbrVideoPlays = Integer(lNodeContents[5].content)
67
- lPlayRatio = Integer(lNodeContents[6].content.match(/^(\d*)%$/)[1])
68
- lMatch = lNodeContents[7].content.match(/^(\d*)\/(\d*)$/)
68
+ lPlayRatio = Integer(lNodeContents[5].content.match(/^(\d*)%$/)[1])
69
+ lMatch = lNodeContents[6].content.match(/^(\d*)\/(\d*)$/)
69
70
  lNbrLikes = Integer(lMatch[1])
70
71
  lNbrDislikes = Integer(lMatch[2])
71
72
  oStatsProxy.addStat(lSongTitle, 'Song plays', lNbrSongPlays)
72
73
  oStatsProxy.addStat(lSongTitle, 'Song downloads', lNbrSongDownloads)
73
- oStatsProxy.addStat(lSongTitle, 'Video plays', lNbrVideoPlays)
74
74
  oStatsProxy.addStat(lSongTitle, 'Song play ratio', lPlayRatio)
75
75
  oStatsProxy.addStat(lSongTitle, 'Song likes', lNbrLikes)
76
76
  oStatsProxy.addStat(lSongTitle, 'Song dislikes', lNbrDislikes)
@@ -79,6 +79,28 @@ module StatsCollect
79
79
  logDebug "#{lLstSongsRead.size} songs read: #{lLstSongsRead.join(', ')}"
80
80
  end
81
81
 
82
+ # Get the videos statistics
83
+ #
84
+ # Parameters:
85
+ # * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
86
+ # * *iMechanizeAgent* (_Mechanize_): The agent reading pages
87
+ # * *iUserID* (_String_): The ReverbNation user ID
88
+ def getVideos(oStatsProxy, iMechanizeAgent, iUserID)
89
+ # Get the Ajax stats table
90
+ lStatsTable = iMechanizeAgent.get("http://www.reverbnation.com/artist/new_stats_video_plays_table/#{iUserID}?all_time=true")
91
+ lStatsTableNode = Nokogiri::HTML(lStatsTable.content[37..-4].gsub(/\\"/,'"').gsub(/\\n/,"\n").gsub(/\\r/,''))
92
+ # Screen scrap it
93
+ lLstVideosRead = []
94
+ lStatsTableNode.css('table.statstable_full tr')[1..-3].each do |iSongNode|
95
+ lNodeContents = iSongNode.css('td')
96
+ lVideoTitle = lNodeContents[1].children[0].content
97
+ lNbrVideoPlays = Integer(lNodeContents[3].content)
98
+ oStatsProxy.addStat(lVideoTitle, 'Video plays', lNbrVideoPlays)
99
+ lLstVideosRead << lVideoTitle
100
+ end
101
+ logDebug "#{lLstVideosRead.size} videos read: #{lLstVideosRead.join(', ')}"
102
+ end
103
+
82
104
  # Get the report statistics
83
105
  #
84
106
  # Parameters:
@@ -94,9 +116,9 @@ module StatsCollect
94
116
  lChildrenNodes = iStatsSectionNode.children
95
117
  lChildrenNodes.each_with_index do |iNode, iIdx|
96
118
  if (iNode.content == 'Genre:')
97
- lChartPositionGenre = Integer(lChildrenNodes[iIdx+1].content.strip)
119
+ lChartPositionGenre = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(',',''))
98
120
  elsif (iNode.content == 'Global:')
99
- lChartPositionGlobal = Integer(lChildrenNodes[iIdx+1].content.strip)
121
+ lChartPositionGlobal = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(',',''))
100
122
  end
101
123
  end
102
124
  if ((lChartPositionGenre != nil) and
@@ -113,9 +135,9 @@ module StatsCollect
113
135
  lChildrenNodes = iPaneNode.children
114
136
  lChildrenNodes.each_with_index do |iNode, iIdx|
115
137
  if (iNode.content == 'Band Equity Score: ')
116
- lBandEquityScore = Integer(lChildrenNodes[iIdx+1].content.strip)
138
+ lBandEquityScore = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(',',''))
117
139
  elsif (iNode.content == 'Total Fans: ')
118
- lNbrFriends = Integer(lChildrenNodes[iIdx+1].content.strip)
140
+ lNbrFriends = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(',',''))
119
141
  end
120
142
  end
121
143
  if ((lBandEquityScore != nil) and
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
2
+ # Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
3
  # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
4
  #++
5
5
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
2
+ # Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
3
  # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
4
  #++
5
5
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
2
+ # Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
3
  # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
4
  #++
5
5
 
@@ -22,10 +22,14 @@ module StatsCollect
22
22
  def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
23
23
  require 'mechanize'
24
24
  lMechanizeAgent = Mechanize.new
25
- lLoginForm = lMechanizeAgent.get('http://www.youtube.com').link_with(:text => 'Sign In').click.forms[1]
25
+ lLoginForm = lMechanizeAgent.get('http://www.youtube.com').link_with(:text => 'Sign In').click.forms[0]
26
26
  lLoginForm.Email = iConf[:LoginEMail]
27
27
  lLoginForm.Passwd = iConf[:LoginPassword]
28
- lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first).meta.first.click
28
+ if (Mechanize::VERSION > '1.0.0')
29
+ lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first).meta_refresh.first.click
30
+ else
31
+ lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first).meta.first.click
32
+ end
29
33
  if ((oStatsProxy.isCategoryIncluded?('Video plays')) or
30
34
  (oStatsProxy.isCategoryIncluded?('Video likes')) or
31
35
  (oStatsProxy.isCategoryIncluded?('Video dislikes')) or
@@ -84,13 +88,13 @@ module StatsCollect
84
88
  lOverviewPage = iMechanizeAgent.get('http://www.youtube.com/account_overview')
85
89
  lNbrVisits = nil
86
90
  lNbrFollowers = nil
87
- lOverviewPage.root.css('div.statBlock').each do |iStatsSectionNode|
91
+ lOverviewPage.root.css('div.statBlock div').each do |iStatsSectionNode|
88
92
  lChildrenNodes = iStatsSectionNode.children
89
93
  lChildrenNodes.each_with_index do |iNode, iIdx|
90
94
  if (iNode.content == 'Channel Views:')
91
- lNbrVisits = Integer(lChildrenNodes[iIdx+1].content.strip)
95
+ lNbrVisits = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(/,/,''))
92
96
  elsif (iNode.content == 'Subscribers:')
93
- lNbrFollowers = Integer(lChildrenNodes[iIdx+1].content.strip)
97
+ lNbrFollowers = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(/,/,''))
94
98
  end
95
99
  end
96
100
  if ((lNbrVisits != nil) and
@@ -99,9 +103,9 @@ module StatsCollect
99
103
  end
100
104
  end
101
105
  if (lNbrVisits == nil)
102
- logErr "Unable to get number of visits: #{lOverviewPage}"
106
+ logErr "Unable to get number of visits: #{lOverviewPage.content}"
103
107
  elsif (lNbrFollowers == nil)
104
- logErr "Unable to get number of followers: #{lOverviewPage}"
108
+ logErr "Unable to get number of followers: #{lOverviewPage.content}"
105
109
  else
106
110
  oStatsProxy.addStat('Global', 'Visits', lNbrVisits)
107
111
  oStatsProxy.addStat('Global', 'Followers', lNbrFollowers)
@@ -0,0 +1,25 @@
1
+ #--
2
+ # Copyright (c) 2010 - 2011 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 Custom
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
+ iConf[:SendCode].call(iMessage)
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,27 @@
1
+ #--
2
+ # Copyright (c) 2010 - 2011 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 LogFile
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
+ File.open(iConf[:LogFile], (iConf[:Append] == true) ? 'a' : 'w') do |oFile|
19
+ oFile.write(iMessage)
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
2
+ # Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
3
  # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
4
  #++
5
5
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
2
+ # Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
3
  # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
4
  #++
5
5
 
@@ -1,11 +1,12 @@
1
1
  #--
2
- # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
2
+ # Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
3
  # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
4
  #++
5
5
 
6
6
  require 'date'
7
7
  require 'optparse'
8
8
  require 'StatsCollect/StatsProxy'
9
+ require 'StatsCollect/StatsOrdersProxy'
9
10
 
10
11
  module StatsCollect
11
12
 
@@ -18,6 +19,8 @@ module StatsCollect
18
19
  STATS_VALUE_TYPE_FLOAT = 1
19
20
  STATS_VALUE_TYPE_PERCENTAGE = 2
20
21
  STATS_VALUE_TYPE_UNKNOWN = 3
22
+ STATS_VALUE_TYPE_MAP = 4
23
+ STATS_VALUE_TYPE_STRING = 5
21
24
 
22
25
  class Stats
23
26
 
@@ -154,77 +157,73 @@ module StatsCollect
154
157
  def collect
155
158
  rErrorCode = 0
156
159
 
157
- # Prevent concurrent execution
158
- require 'tmpdir'
159
- lLockFile = "#{Dir.tmpdir}/StatsCollect.lock"
160
- if (File.exists?(lLockFile))
161
- logErr "Another instance of stats collection is already running. Delete file #{lLockFile} if it is not."
162
- begin
163
- lDetails = nil
164
- File.open(lLockFile, 'r') do |iFile|
165
- lDetails = eval(iFile.read)
166
- end
167
- logErr "Details of the running instance: #{lDetails.inspect}"
168
- rErrorCode = 12
169
- rescue Exception
170
- logErr "Invalid lock file #{lLockFile}: #{$!}."
171
- rErrorCode = 13
172
- end
173
- else
174
- File.open(lLockFile, 'w') do |oFile|
175
- oFile << "
176
- {
177
- :ExecutionTime => '#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}',
178
- :PID => '#{Process.pid}'
179
- }
180
- "
181
- end
160
+ require 'rUtilAnts/Misc'
161
+ RUtilAnts::Misc::initializeMisc
162
+ lMutexErrorCode = fileMutex('StatsCollect') do
182
163
  begin
164
+ # The list of errors
165
+ lLstErrors = []
166
+ setLogErrorsStack(lLstErrors)
183
167
  # Collect statistics
184
- logInfo "[#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}] - Begin collecting stats..."
185
- if (!@BackendInit)
186
- @BackendInstance.initSession(@Conf[:Backends][@Backend])
187
- @BackendInit = true
188
- end
189
- # Get the stats orders to process
168
+ logInfo "[#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}] - Begin collecting stats (PID #{Process.pid})..."
190
169
  lFoundOrder = false
191
- lTimeStamp, lLstLocations, lLstObjects, lLstCategories, lStatus = @BackendInstance.getNextStatsOrder
192
- while (lTimeStamp != nil)
193
- lFoundOrder = true
194
- logInfo "Dequeued stats order: Time: #{lTimeStamp}, Locations: #{lLstLocations.join('|')}, Objects: #{lLstObjects.join('|')}, Categories: #{lLstCategories.join('|')}, Status: #{lStatus}"
195
- begin
196
- lRecoverableOrders, lUnrecoverableOrders = processOrder(lLstObjects, lLstCategories, lLstLocations)
197
- # Add recoverable orders back
198
- lRecoverableOrders.each do |iOrderInfo|
199
- iLstRecoverableObjects, iLstRecoverableCategories, iLstRecoverableLocations = iOrderInfo
200
- logInfo "Enqueue recoverable order: Locations: #{iLstRecoverableLocations.join('|')}, Objects: #{iLstRecoverableObjects.join('|')}, Categories: #{iLstRecoverableCategories.join('|')}"
201
- @BackendInstance.putNewStatsOrder(DateTime.now, iLstRecoverableLocations, iLstRecoverableObjects, iLstRecoverableCategories, STATS_ORDER_STATUS_RECOVERABLE_ERROR)
202
- end
203
- # Add unrecoverable orders back
204
- lUnrecoverableOrders.each do |iOrderInfo|
205
- iLstUnrecoverableObjects, iLstUnrecoverableCategories, iLstUnrecoverableLocations = iOrderInfo
206
- logInfo "Enqueue unrecoverable order: Locations: #{iLstUnrecoverableLocations.join('|')}, Objects: #{iLstUnrecoverableObjects.join('|')}, Categories: #{iLstUnrecoverableCategories.join('|')}"
207
- @BackendInstance.putNewStatsOrder(lTimeStamp, iLstUnrecoverableLocations, iLstUnrecoverableObjects, iLstUnrecoverableCategories, STATS_ORDER_STATUS_UNRECOVERABLE_ERROR)
170
+ lNbrErrors = 0
171
+ setupBackend do
172
+ # Get the stats orders to process
173
+ lStatsOrdersProxy = StatsOrdersProxy.new
174
+ @BackendInstance.getStatsOrders(lStatsOrdersProxy)
175
+ lFoundOrder = (!lStatsOrdersProxy.StatsOrders.empty?)
176
+ # Process each stats order
177
+ lStatsOrdersProxy.StatsOrders.each do |iLstIDs, iStatsOrderInfo|
178
+ iTimeStamp, iLstLocations, iLstObjects, iLstCategories, iStatus = iStatsOrderInfo
179
+ @BackendInstance.dequeueStatsOrders(iLstIDs)
180
+ logInfo "Dequeued stats order: IDs: #{iLstIDs.join('|')}, Time: #{iTimeStamp}, Locations: #{iLstLocations.join('|')}, Objects: #{iLstObjects.join('|')}, Categories: #{iLstCategories.join('|')}, Status: #{iStatus}"
181
+ begin
182
+ lRecoverableOrders, lUnrecoverableOrders = processOrder(iLstObjects, iLstCategories, iLstLocations)
183
+ # Add recoverable orders back
184
+ lRecoverableOrders.each do |iOrderInfo|
185
+ lNbrErrors += 1
186
+ iLstRecoverableObjects, iLstRecoverableCategories, iLstRecoverableLocations = iOrderInfo
187
+ logInfo "Enqueue recoverable order: Locations: #{iLstRecoverableLocations.join('|')}, Objects: #{iLstRecoverableObjects.join('|')}, Categories: #{iLstRecoverableCategories.join('|')}"
188
+ @BackendInstance.putNewStatsOrder(DateTime.now + @Conf[:RecoverableErrorsRetryDelay]/86400.0, iLstRecoverableLocations, iLstRecoverableObjects, iLstRecoverableCategories, STATS_ORDER_STATUS_RECOVERABLE_ERROR)
189
+ end
190
+ # Add unrecoverable orders back
191
+ lUnrecoverableOrders.each do |iOrderInfo|
192
+ lNbrErrors += 1
193
+ iLstUnrecoverableObjects, iLstUnrecoverableCategories, iLstUnrecoverableLocations = iOrderInfo
194
+ logInfo "Enqueue unrecoverable order: Locations: #{iLstUnrecoverableLocations.join('|')}, Objects: #{iLstUnrecoverableObjects.join('|')}, Categories: #{iLstUnrecoverableCategories.join('|')}"
195
+ @BackendInstance.putNewStatsOrder(iTimeStamp, iLstUnrecoverableLocations, iLstUnrecoverableObjects, iLstUnrecoverableCategories, STATS_ORDER_STATUS_UNRECOVERABLE_ERROR)
196
+ end
197
+ @BackendInstance.commit
198
+ rescue Exception
199
+ lNbrErrors += 1
200
+ @BackendInstance.rollback
201
+ logErr "Exception while processing order #{iTimeStamp}, Locations: #{iLstLocations.join('|')}, Objects: #{iLstObjects.join('|')}, Categories: #{iLstCategories.join('|')}, Status: #{iStatus}: #{$!}.\n#{$!.backtrace.join("\n")}\n"
202
+ rErrorCode = 14
208
203
  end
209
- @BackendInstance.commit
210
- rescue Exception
211
- @BackendInstance.rollback
212
- logErr "Exception while processing order #{lTimeStamp}, Locations: #{lLstLocations.join('|')}, Objects: #{lLstObjects.join('|')}, Categories: #{lLstCategories.join('|')}, Status: #{lStatus}: #{$!}.\n#{$!.backtrace.join("\n")}\n"
213
- rErrorCode = 14
214
204
  end
215
- lTimeStamp, lLstLocations, lLstObjects, lLstCategories, lStatus = @BackendInstance.getNextStatsOrder
216
205
  end
217
206
  if (!lFoundOrder)
218
207
  @NotifyUser = false
219
208
  end
209
+ setLogErrorsStack(nil)
210
+ if (lNbrErrors > 0)
211
+ logErr "#{lNbrErrors} orders were put in error during processing. Please check logs."
212
+ end
213
+ if (!lLstErrors.empty?)
214
+ logErr "#{lLstErrors.size} errors were reported. Check log for exact errors."
215
+ end
220
216
  logInfo "[#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}] - Stats collection finished."
221
- File.unlink(lLockFile)
222
217
  rescue Exception
223
218
  logErr "Exception thrown while collecting stats: #{$!}.\n#{$!.backtrace.join("\n")}"
224
219
  rErrorCode = 15
225
220
  @NotifyUser = true
226
221
  end
227
222
  end
223
+ if ((lMutexErrorCode != RUtilAnts::Misc::FILEMUTEX_NO_LOCK) and
224
+ (lMutexErrorCode != RUtilAnts::Misc::FILEMUTEX_ZOMBIE_LOCK))
225
+ rErrorCode = 12
226
+ end
228
227
 
229
228
  return rErrorCode
230
229
  end
@@ -254,15 +253,32 @@ module StatsCollect
254
253
  # * *iLstObjects* (<em>list<String></em>): Objects list (can be empty for all objects)
255
254
  # * *iLstCategories* (<em>list<String></em>): Categories list (can be empty for all categories)
256
255
  def pushStatsOrder(iLstLocations, iLstObjects, iLstCategories)
256
+ setupBackend do
257
+ @BackendInstance.putNewStatsOrder(DateTime.now, iLstLocations, iLstObjects, iLstCategories, STATS_ORDER_STATUS_TOBEPROCESSED)
258
+ end
259
+ end
260
+
261
+ private
262
+
263
+ # Call some code initializing backend before and ensuring it will be closed after.
264
+ # This method is re-entrant.
265
+ #
266
+ # Parameters:
267
+ # * _CodeBlock_: the code to be called
268
+ def setupBackend
269
+ lBackendInitHere = false
257
270
  if (!@BackendInit)
258
271
  @BackendInstance.initSession(@Conf[:Backends][@Backend])
259
272
  @BackendInit = true
273
+ lBackendInitHere = true
274
+ end
275
+ yield
276
+ if (lBackendInitHere)
277
+ @BackendInstance.closeSession
278
+ @BackendInit = false
260
279
  end
261
- @BackendInstance.putNewStatsOrder(DateTime.now, iLstLocations, iLstObjects, iLstCategories, STATS_ORDER_STATUS_TOBEPROCESSED)
262
280
  end
263
281
 
264
- private
265
-
266
282
  # Process an order
267
283
  #
268
284
  # Parameters:
@@ -292,7 +308,7 @@ module StatsCollect
292
308
  lPlugin, lError = getPluginInstance('Locations', iPluginName)
293
309
  if (lError == nil)
294
310
  # Ask the plugin to perform the order
295
- lStatsProxy = StatsProxy.new(iObjectsList, iCategoriesList)
311
+ lStatsProxy = StatsProxy.new(iObjectsList, iCategoriesList, @BackendInstance, iPluginName)
296
312
  logInfo "===== Call Location plugin #{iPluginName} to perform order..."
297
313
  begin
298
314
  lPlugin.execute(lStatsProxy, lPluginConf, iObjectsList, iCategoriesList)
@@ -303,7 +319,7 @@ module StatsCollect
303
319
  end
304
320
  if (lError == nil)
305
321
  # Write the stats into the database
306
- writeStats(lStatsProxy.StatsToAdd, iPluginName, iObjectsList, iCategoriesList)
322
+ writeStats(lStatsProxy.StatsToAdd, iObjectsList, iCategoriesList)
307
323
  # If the plugin failed on recoverable errors, note them
308
324
  lStatsProxy.RecoverableOrders.each do |iOrderInfo|
309
325
  iLstObjects, iLstCategories = iOrderInfo
@@ -384,10 +400,9 @@ module StatsCollect
384
400
  #
385
401
  # Parameters:
386
402
  # * *iStatsToAdd* (<em>list<[TimeStamp,Object,Category,Value]></em>): The stats to write in the DB
387
- # * *iLocation* (_String_): The location of these stats
388
403
  # * *iLstObjects* (<em>list<String></em>): The filtering objects to write (can be empty for all)
389
404
  # * *iLstCategories* (<em>list<String></em>): The filtering categories to write (can be empty for all)
390
- def writeStats(iStatsToAdd, iLocation, iLstObjects, iLstCategories)
405
+ def writeStats(iStatsToAdd, iLstObjects, iLstCategories)
391
406
  # Filter the stats we will really add
392
407
  lStatsToBeCommitted = nil
393
408
  if ((iLstObjects.empty?) and
@@ -418,21 +433,33 @@ module StatsCollect
418
433
  else
419
434
  # Get the current locations from the DB to know if our location exists
420
435
  lKnownLocations = @BackendInstance.getKnownLocations
421
- lLocationID = lKnownLocations[iLocation]
422
- if (lLocationID == nil)
423
- # First create the new location and get its ID
424
- logInfo "Creating new location: #{iLocation}"
425
- lLocationID = @BackendInstance.addLocation(iLocation)
426
- end
427
- logDebug "Location used for those stats: #{iLocation} (#{lLocationID})"
428
436
  # Get the list of categories, sorted by category name
429
437
  lKnownCategories = @BackendInstance.getKnownCategories
430
438
  # Get the list of objects, sorted by object name
431
439
  # This map will eventually be completed if new objects are found among the stats to write.
432
440
  lKnownObjects = @BackendInstance.getKnownObjects
441
+ # Use the following to generate a RB file that can be used with RB plugin.
442
+ if false
443
+ lStrStats = []
444
+ lStatsToBeCommitted.each do |iStatsInfo|
445
+ lCheckExistence, iTimeStamp, iLocation, iObject, iCategory, iValue = iStatsInfo
446
+ lStrStats << [ lCheckExistence, iTimeStamp.strftime('%Y-%m-%d %H:%M:%S'), iLocation, iObject, iCategory, iValue ].inspect
447
+ end
448
+ File.open('__StatsToBeWritten.rb', 'w') do |oFile|
449
+ oFile.write("[\n#{lStrStats.join(",\n")}\n]")
450
+ end
451
+ end
433
452
  # Add statistics
434
453
  lStatsToBeCommitted.each do |iStatsInfo|
435
- iTimeStamp, iObject, iCategory, iValue = iStatsInfo
454
+ lCheckExistence, iTimeStamp, iLocation, iObject, iCategory, iValue = iStatsInfo
455
+ lLocationID = lKnownLocations[iLocation]
456
+ if (lLocationID == nil)
457
+ # First create the new location and get its ID
458
+ logInfo "Creating new location: #{iLocation}"
459
+ lLocationID = @BackendInstance.addLocation(iLocation)
460
+ lKnownLocations[iLocation] = lLocationID
461
+ lCheckExistence = false
462
+ end
436
463
  # Check that the category exists
437
464
  lValueType = nil
438
465
  lCategoryID = nil
@@ -441,6 +468,7 @@ module StatsCollect
441
468
  lValueType = STATS_VALUE_TYPE_UNKNOWN
442
469
  lCategoryID = @BackendInstance.addCategory(iCategory, lValueType)
443
470
  lKnownCategories[iCategory] = [ lCategoryID, lValueType ]
471
+ lCheckExistence = false
444
472
  else
445
473
  lCategoryID, lValueType = lKnownCategories[iCategory]
446
474
  end
@@ -450,11 +478,24 @@ module StatsCollect
450
478
  logInfo "Creating new object: #{iObject}"
451
479
  lObjectID = @BackendInstance.addObject(iObject)
452
480
  lKnownObjects[iObject] = lObjectID
481
+ lCheckExistence = false
482
+ end
483
+ # First, we ensure that this stats does not exist if we don't want duplicates
484
+ lAdd = true
485
+ if (lCheckExistence)
486
+ lExistingValue = @BackendInstance.getStat(iTimeStamp, lLocationID, lObjectID, lCategoryID, lValueType)
487
+ if (lExistingValue != nil)
488
+ logWarn "Stat value for #{iTimeStamp.strftime('%Y-%m-%d %H:%M:%S')}, Location: #{lLocationID}, Object: #{lObjectID}, Category: #{lCategoryID} already exists with value #{lExistingValue}. Will not duplicate it."
489
+ lAdd = false
490
+ end
491
+ end
492
+ if (lAdd)
493
+ # Add the stat
494
+ @BackendInstance.addStat(iTimeStamp, lLocationID, lObjectID, lCategoryID, iValue, lValueType)
495
+ logDebug "Added stat: Time: #{iTimeStamp}, Location: #{iLocation} (#{lLocationID}), Object: #{iObject} (#{lObjectID}), Category: #{iCategory} (#{lCategoryID}), Value: #{iValue}"
453
496
  end
454
- # Add the stat
455
- @BackendInstance.addStat(iTimeStamp, lLocationID, lObjectID, lCategoryID, iValue, lValueType)
456
- logDebug "Added stat: Time: #{iTimeStamp}, Location: #{iLocation} (#{lLocationID}), Object: #{iObject} (#{lObjectID}), Category: #{iCategory} (#{lCategoryID}), Value: #{iValue}"
457
497
  end
498
+ logInfo "#{lStatsToBeCommitted.size} stats added."
458
499
  end
459
500
  end
460
501
 
@@ -0,0 +1,54 @@
1
+ #--
2
+ # Copyright (c) 2010 - 2011 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
+ # The stats orders proxy, used by plugins to populate stats
9
+ class StatsOrdersProxy
10
+
11
+ # List of categories among the stats orders stats orders, by IDs
12
+ # map< list< Integer >, [ DateTime, list<String>, list<String>, list<String>, Integer ] >
13
+ attr_reader :StatsOrders
14
+
15
+ # Constructor
16
+ def initialize
17
+ # List of stats orders IDs
18
+ @StatsOrders = {}
19
+ end
20
+
21
+ # Add a new stats order
22
+ #
23
+ # Parameters:
24
+ # * *iID* (_Integer_): An ID identifying uniquely this stats order. It will be used to tell which stats orders have to be dequeued later.
25
+ # * *iTimeStamp* (_DateTime_): Time stamp
26
+ # * *iLstLocations* (<em>list<String></em>): List of locations
27
+ # * *iLstObjects* (<em>list<String></em>): List of objects
28
+ # * *iLstCategories* (<em>list<String></em>): List of categories
29
+ # * *iStatus* (_Integer_): Status
30
+ def addStatsOrder(iID, iTimeStamp, iLstLocations, iLstObjects, iLstCategories, iStatus)
31
+ lLstSortedLocations = iLstLocations.sort.uniq
32
+ lLstSortedObjects = iLstObjects.sort.uniq
33
+ lLstSortedCategories = iLstCategories.sort.uniq
34
+ # First, check if this stats order is not already present
35
+ lFound = false
36
+ @StatsOrders.each do |iIDs, iStatsOrderInfo|
37
+ iExistingTimeStamp, iLstExistingLocations, iLstExistingObjects, iLstExistingCategories, iExistingStatus = iStatsOrderInfo
38
+ if ((iLstExistingLocations == lLstSortedLocations) and
39
+ (iLstExistingObjects == lLstSortedObjects) and
40
+ (iLstExistingCategories == lLstSortedCategories))
41
+ # Found already here
42
+ iIDs << iID
43
+ lFound = true
44
+ break
45
+ end
46
+ end
47
+ if (!lFound)
48
+ @StatsOrders[[iID]] = [ iTimeStamp, lLstSortedLocations, lLstSortedObjects, lLstSortedCategories, iStatus ]
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end