StatsCollect 0.1.1.20101220 → 0.2.0.20110830

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.
@@ -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