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.
- data/ChangeLog +98 -0
- data/LICENSE +1 -1
- data/ReleaseInfo +8 -8
- data/StatsCollect.conf.rb.example +43 -2
- data/bin/StatsCollect.rb +1 -1
- data/lib/StatsCollect/Backends/MySQL.rb +253 -41
- data/lib/StatsCollect/Backends/Terminal.rb +39 -27
- data/lib/StatsCollect/Locations/AddThis.rb +2 -2
- data/lib/StatsCollect/Locations/CSV.rb +106 -0
- data/lib/StatsCollect/Locations/Facebook.rb +9 -5
- data/lib/StatsCollect/Locations/FacebookArtist.rb +4 -2
- data/lib/StatsCollect/Locations/FacebookLike.rb +1 -1
- data/lib/StatsCollect/Locations/GoogleGroup.rb +103 -0
- data/lib/StatsCollect/Locations/GoogleSearch.rb +1 -1
- data/lib/StatsCollect/Locations/MySpace.rb +63 -12
- data/lib/StatsCollect/Locations/RB.rb +64 -0
- data/lib/StatsCollect/Locations/ReverbNation.rb +34 -12
- data/lib/StatsCollect/Locations/Tweets.rb +1 -1
- data/lib/StatsCollect/Locations/Twitter.rb +1 -1
- data/lib/StatsCollect/Locations/Youtube.rb +12 -8
- data/lib/StatsCollect/Notifiers/Custom.rb +25 -0
- data/lib/StatsCollect/Notifiers/LogFile.rb +27 -0
- data/lib/StatsCollect/Notifiers/None.rb +1 -1
- data/lib/StatsCollect/Notifiers/SendMail.rb +1 -1
- data/lib/StatsCollect/Stats.rb +115 -74
- data/lib/StatsCollect/StatsOrdersProxy.rb +54 -0
- data/lib/StatsCollect/StatsProxy.rb +47 -6
- metadata +16 -10
- data/TODO +0 -2
data/ChangeLog
CHANGED
@@ -1,5 +1,103 @@
|
|
1
1
|
= StatsCollect Release History
|
2
2
|
|
3
|
+
== 0.2.0.20110830 (Beta)
|
4
|
+
|
5
|
+
=== General
|
6
|
+
|
7
|
+
* Migrated TODO list to Trackers.
|
8
|
+
* Updated copyright information.
|
9
|
+
* Migrated source repository to git.
|
10
|
+
* Used fileMutex from last rUtilAnts version.
|
11
|
+
* Added useful log info.
|
12
|
+
* Completed the conf example file.
|
13
|
+
* New API: Location plugins can now add a complete list of stats at once.
|
14
|
+
* New API: Backends have to be able to return a given stat.
|
15
|
+
* When stats are added with a given timestamp, their existence will be checked before add.
|
16
|
+
* Added RB location plugin to add stats from a Ruby list defined in a file.
|
17
|
+
* Collection plugins can now add stats for a given timestamp.
|
18
|
+
* Collection plugins can now add stats for a location different than they.
|
19
|
+
* Collection plugins can now ask for the list of known categories, locations and objects.
|
20
|
+
* Removed the logging of errors details a second time at the end of logs.
|
21
|
+
* Added CSV plugin to collect stats from CSV files.
|
22
|
+
* Added a string type value
|
23
|
+
* Added a way to recap all encountered errors at the end of the log.
|
24
|
+
* Added PID info in logs.
|
25
|
+
* Lock file is removed when the process it monitors has disappeared.
|
26
|
+
* Added MAP stats value type.
|
27
|
+
* Added new error logging: no more needed to parse the whole logs to check for errors presence.
|
28
|
+
* Changed API for Backends: Implemented a simplified and more flexible way to specify Stats Orders.
|
29
|
+
* Add a configurable delay when re-enqueuing recoverable stats orders.
|
30
|
+
* Added a closeSession method to finalize Backend sessions.
|
31
|
+
* Bug correction: StatsCollect did not execute due to new rUtilAnts lib.
|
32
|
+
* Bug correction: Lock file was not deleted when exception occurred.
|
33
|
+
|
34
|
+
=== Backends/MySQL
|
35
|
+
|
36
|
+
* Uses MySQL connection and prepared statements pools
|
37
|
+
* Implemented a way to store Ruby maps as values in a differential storage (encode differences only).
|
38
|
+
* Process only stats orders with past times tamps
|
39
|
+
* Adapted to the new API.
|
40
|
+
* Now uses prepared queries for performance and security.
|
41
|
+
* Bug correction: Stats orders were not read correctly from DB.
|
42
|
+
|
43
|
+
=== Backends/Terminal
|
44
|
+
|
45
|
+
* Adapted to the new API.
|
46
|
+
* Bug correction: Enqueued order was invalid.
|
47
|
+
|
48
|
+
=== Locations/Facebook
|
49
|
+
|
50
|
+
* Updated to the new URL scheme. Configuration files need to add :URLID attribute.
|
51
|
+
* Facebook changed pages code.
|
52
|
+
* Changed the way number of friends were retrieved, as Facebook code changed
|
53
|
+
* Changed UserAgent as Facebook considered it as mobile
|
54
|
+
|
55
|
+
=== Locations/FacebookArtist
|
56
|
+
|
57
|
+
* Facebook changed pages code.
|
58
|
+
* Changed UserAgent as Facebook considered it as mobile
|
59
|
+
|
60
|
+
=== Locations/GoogleGroup
|
61
|
+
|
62
|
+
* Added compatibility between Mechanize 1.0.0 and 2.0.1.
|
63
|
+
* Updated to Mechanize 2.0.1 API.
|
64
|
+
* Added a new Location plugin to parse GoogleGroups.
|
65
|
+
|
66
|
+
=== Locations/MySpace
|
67
|
+
|
68
|
+
* Corrected MySpace code as it has changed.
|
69
|
+
* Changed the way comments are parsed.
|
70
|
+
* Added a stat to get complete Friends list with IDs and names.
|
71
|
+
* Bug correction: Visits and Friends where not counted correctly when exceeding 1000.
|
72
|
+
* Bug correction: Friends lists did not match names on IDs sometimes.
|
73
|
+
|
74
|
+
=== Locations/ReverbNation
|
75
|
+
|
76
|
+
* Changed way to parse songs and videos plays
|
77
|
+
* Changed way to parse integers with thousand separators.
|
78
|
+
|
79
|
+
=== Locations/Youtube
|
80
|
+
|
81
|
+
* Added compatibility between Mechanize 1.0.0 and 2.0.1.
|
82
|
+
* Updated to Mechanize 2.0.1 API.
|
83
|
+
* Adapted to new code.
|
84
|
+
* Bug correction: Counters were not parsed correctly when greater than 1000.
|
85
|
+
|
86
|
+
=== Notifiers/Custom
|
87
|
+
|
88
|
+
* Added a customizable notifier
|
89
|
+
|
90
|
+
=== Notifiers/LogFile
|
91
|
+
|
92
|
+
* Added a log file notifier
|
93
|
+
|
94
|
+
== 0.1.1.20101220 (Beta)
|
95
|
+
|
96
|
+
* Facebook: Adapted code to new profiles.
|
97
|
+
* Youtube: Added Following, Followers, Visits and Friends stats.
|
98
|
+
* Added a way to push a new stats order using the API.
|
99
|
+
* Bug correction: MySQL: Did not work.
|
100
|
+
|
3
101
|
== 0.1.0.20101220 (Beta)
|
4
102
|
|
5
103
|
* Initial version
|
data/LICENSE
CHANGED
@@ -6,7 +6,7 @@ This list is found in the file named AUTHORS.
|
|
6
6
|
The AUTHORS and LICENSE files have to be included in any release of software
|
7
7
|
embedding source code of this package, or using it as a derivative software.
|
8
8
|
|
9
|
-
Copyright (c)
|
9
|
+
Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
10
10
|
|
11
11
|
Redistribution and use in source and binary forms, with or without
|
12
12
|
modification, are permitted provided that the following conditions are met:
|
data/ReleaseInfo
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
2
|
-
# This file has been generated by RubyPackager during a delivery.
|
3
|
-
# More info about RubyPackager: http://rubypackager.sourceforge.net
|
4
|
-
{
|
5
|
-
:Version => '0.
|
6
|
-
:Tags => [ 'Beta' ],
|
7
|
-
:DevStatus => 'Beta'
|
8
|
-
}
|
1
|
+
|
2
|
+
# This file has been generated by RubyPackager during a delivery.
|
3
|
+
# More info about RubyPackager: http://rubypackager.sourceforge.net
|
4
|
+
{
|
5
|
+
:Version => '0.2.0.20110830',
|
6
|
+
:Tags => [ 'Beta' ],
|
7
|
+
:DevStatus => 'Beta'
|
8
|
+
}
|
@@ -1,4 +1,7 @@
|
|
1
1
|
{
|
2
|
+
# Delay to apply before retrying recoverable stats orders (number of seconds)
|
3
|
+
:RecoverableErrorsRetryDelay => 300,
|
4
|
+
|
2
5
|
# Configuration for Backends
|
3
6
|
:Backends => {
|
4
7
|
'MySQL' => {
|
@@ -27,7 +30,16 @@
|
|
27
30
|
# To who the notifications are sent
|
28
31
|
:To => 'mail@host.com'
|
29
32
|
},
|
30
|
-
'None' => {}
|
33
|
+
'None' => {},
|
34
|
+
'LogFile' => {
|
35
|
+
:FileName => '/log/MyLogFile.log',
|
36
|
+
:Append => true
|
37
|
+
},
|
38
|
+
'Custom' => {
|
39
|
+
:SendCode => Proc.new do |iMessage|
|
40
|
+
puts iMessage
|
41
|
+
end
|
42
|
+
}
|
31
43
|
},
|
32
44
|
|
33
45
|
# Configuration for Locations
|
@@ -46,7 +58,8 @@
|
|
46
58
|
},
|
47
59
|
'Facebook' => {
|
48
60
|
:LoginEMail => 'FacebookLogin',
|
49
|
-
:LoginPassword => '*****'
|
61
|
+
:LoginPassword => '*****',
|
62
|
+
:URLID => 'facebookid'
|
50
63
|
},
|
51
64
|
'FacebookArtist' => {
|
52
65
|
:LoginEMail => 'FacebookLogin',
|
@@ -66,6 +79,10 @@
|
|
66
79
|
'www.myhost.com'
|
67
80
|
]
|
68
81
|
},
|
82
|
+
'Youtube' => {
|
83
|
+
:LoginEMail => 'YoutubeLogin',
|
84
|
+
:LoginPassword => '*****'
|
85
|
+
},
|
69
86
|
'FacebookLike' => {
|
70
87
|
# List of objects for which we retrieve the Facebook likes
|
71
88
|
:Objects => [
|
@@ -87,5 +104,29 @@
|
|
87
104
|
'GoogleSearchString'
|
88
105
|
]
|
89
106
|
},
|
107
|
+
'GoogleGroup' => {
|
108
|
+
:LoginEMail => 'GoogleGroupLogin',
|
109
|
+
:LoginPassword => '*****',
|
110
|
+
:Objects => [
|
111
|
+
'GoogleGroupName'
|
112
|
+
]
|
113
|
+
},
|
114
|
+
'CSV' => {
|
115
|
+
:ColumnSeparator => ',',
|
116
|
+
:RowSeparator => "\n",
|
117
|
+
:QuoteChar => '"',
|
118
|
+
:DateTimeFormat => '%d/%m/%y %H:%M',
|
119
|
+
:IDsMustExist => false,
|
120
|
+
:Files => [
|
121
|
+
'C:\\Temp\\Stats.csv'
|
122
|
+
]
|
123
|
+
},
|
124
|
+
'RB' => {
|
125
|
+
:DateTimeFormat => '%Y-%m-%d %H:%M:%S',
|
126
|
+
:IDsMustExist => false,
|
127
|
+
:Files => [
|
128
|
+
'C:\\Temp\\Stats.rb'
|
129
|
+
]
|
130
|
+
}
|
90
131
|
}
|
91
132
|
}
|
data/bin/StatsCollect.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/bin/env ruby
|
2
2
|
#--
|
3
|
-
# Copyright (c)
|
3
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
4
4
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
5
5
|
#++
|
6
6
|
|
@@ -1,56 +1,100 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
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
|
+
require 'zlib'
|
7
|
+
|
8
|
+
class DateTime
|
9
|
+
|
10
|
+
# Convert this date time to a MySQL time
|
11
|
+
def to_MySQLTime
|
12
|
+
return Mysql::Time.new(
|
13
|
+
self.year,
|
14
|
+
self.month,
|
15
|
+
self.day,
|
16
|
+
self.hour,
|
17
|
+
self.min,
|
18
|
+
self.sec)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
6
23
|
module StatsCollect
|
7
24
|
|
8
25
|
module Backends
|
9
26
|
|
10
27
|
class MySQL
|
11
28
|
|
29
|
+
# Constants used to represent differencial data storage
|
30
|
+
DIFFDATA_KEY = 'K'
|
31
|
+
DIFFDATA_MERGE = 'M'
|
32
|
+
DIFFDATA_DELETE = 'D'
|
33
|
+
DIFFDATA_MODIFY = 'O'
|
34
|
+
DIFFDATA_SAME = 'S'
|
35
|
+
# Number of maximal rows to store data differences before creating a new data key
|
36
|
+
DIFFDATA_MAX_NUMBER_OF_ROWS = 20
|
37
|
+
|
12
38
|
# Initialize a session of this backend
|
13
39
|
#
|
14
40
|
# Parameters:
|
15
41
|
# * *iConf* (<em>map<Symbol,Object></em>): Configuration of this backend
|
16
42
|
def initSession(iConf)
|
17
|
-
require '
|
18
|
-
|
19
|
-
|
43
|
+
require 'rUtilAnts/MySQLPool'
|
44
|
+
RUtilAnts::MySQLPool::initializeMySQLPool
|
45
|
+
lError, @MySQLConnection = connectToMySQL(iConf[:DBHost], iConf[:DBName], iConf[:DBUser], iConf[:DBPassword])
|
46
|
+
if (lError != nil)
|
47
|
+
raise lError
|
48
|
+
end
|
49
|
+
@StatementSelectFromStatsOrders = getPreparedStatement(@MySQLConnection, 'SELECT id, timestamp, locations_list, objects_list, categories_list, status FROM stats_orders WHERE (status = 0 OR status = 1) AND timestamp < ? ORDER BY timestamp DESC', :LeaveOpen => true)
|
50
|
+
@StatementDeleteFromStatsOrders = getPreparedStatement(@MySQLConnection, 'DELETE FROM stats_orders WHERE id=?', :LeaveOpen => true)
|
51
|
+
@StatementInsertIntoStatsLocations = getPreparedStatement(@MySQLConnection, 'INSERT INTO stats_locations (name) VALUES (?)', :LeaveOpen => true)
|
52
|
+
@StatementInsertIntoStatsCategories = getPreparedStatement(@MySQLConnection, 'INSERT INTO stats_categories (name, value_type) VALUES (?, ?)', :LeaveOpen => true)
|
53
|
+
@StatementInsertIntoStatsObjects = getPreparedStatement(@MySQLConnection, 'INSERT INTO stats_objects (name) VALUES (?)', :LeaveOpen => true)
|
54
|
+
@StatementInsertIntoStatsValues = getPreparedStatement(@MySQLConnection, 'INSERT INTO stats_values (timestamp, stats_location_id, stats_object_id, stats_category_id, value) VALUES (?, ?, ?, ?, ?)', :LeaveOpen => true)
|
55
|
+
@StatementSelectFromStatsValues = getPreparedStatement(@MySQLConnection, 'SELECT value FROM stats_values WHERE timestamp = ? AND stats_location_id = ? AND stats_object_id = ? AND stats_category_id = ?', :LeaveOpen => true)
|
56
|
+
@StatementInsertIntoStatsBinaryValues = getPreparedStatement(@MySQLConnection, 'INSERT INTO stats_binary_values (timestamp, stats_location_id, stats_object_id, stats_category_id, value) VALUES (?, ?, ?, ?, ?)', :LeaveOpen => true)
|
57
|
+
@StatementInsertIntoStatsOrders = getPreparedStatement(@MySQLConnection, 'INSERT INTO stats_orders (timestamp, locations_list, objects_list, categories_list, status) VALUES (?, ?, ?, ?, ?)', :LeaveOpen => true)
|
58
|
+
@StatementSelectFromStatsLastKeys = getPreparedStatement(@MySQLConnection, 'SELECT stats_value_id FROM stats_last_keys WHERE stats_location_id = ? AND stats_object_id = ? AND stats_category_id = ?', :LeaveOpen => true)
|
59
|
+
@StatementSelectFromStatsBinaryValues = getPreparedStatement(@MySQLConnection, 'SELECT id, value FROM stats_binary_values WHERE stats_location_id = ? AND stats_object_id = ? AND stats_category_id = ? AND id >= ? ORDER BY id', :LeaveOpen => true)
|
60
|
+
@StatementInsertIntoStatsLastKeys = getPreparedStatement(@MySQLConnection, 'INSERT INTO stats_last_keys (stats_location_id, stats_object_id, stats_category_id, stats_value_id) VALUES (?, ?, ?, ?)', :LeaveOpen => true)
|
61
|
+
@StatementUpdateStatsLastKeys = getPreparedStatement(@MySQLConnection, 'UPDATE stats_last_keys SET stats_value_id = ? WHERE stats_location_id = ? AND stats_object_id = ? AND stats_category_id = ?', :LeaveOpen => true)
|
20
62
|
end
|
21
63
|
|
22
|
-
# Get the next stats
|
23
|
-
# Code to begin a new transaction can be set in this method too.
|
64
|
+
# Get the next stats orders.
|
24
65
|
#
|
25
|
-
#
|
26
|
-
# *
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
if (!@LstStatsOrders.empty?)
|
45
|
-
lID, rTimeStamp, lStrLocations, lStrObjects, lStrCategories, rStatus = @LstStatsOrders.pop
|
46
|
-
rLstLocations = lStrLocations.split('|')
|
47
|
-
rLstObjects = lStrObjects.split('|')
|
48
|
-
rLstCategories = lStrCategories.split('|')
|
49
|
-
@MySQLConnection.query('start transaction')
|
50
|
-
@MySQLConnection.query("DELETE FROM stats_orders WHERE id=#{lID}")
|
66
|
+
# Parameters:
|
67
|
+
# * *oStatsOrdersProxy* (_StatsOrdersProxy_): The stats orders proxy to be used to give stats orders
|
68
|
+
def getStatsOrders(oStatsOrdersProxy)
|
69
|
+
@StatementSelectFromStatsOrders.execute(DateTime.now.to_MySQLTime)
|
70
|
+
@StatementSelectFromStatsOrders.each do |iRow|
|
71
|
+
iID, iMySQLTimeStamp, iStrLocations, iStrObjects, iStrCategories, iStatus = iRow
|
72
|
+
oStatsOrdersProxy.addStatsOrder(
|
73
|
+
iID,
|
74
|
+
DateTime.civil(
|
75
|
+
iMySQLTimeStamp.year,
|
76
|
+
iMySQLTimeStamp.month,
|
77
|
+
iMySQLTimeStamp.day,
|
78
|
+
iMySQLTimeStamp.hour,
|
79
|
+
iMySQLTimeStamp.minute,
|
80
|
+
iMySQLTimeStamp.second),
|
81
|
+
iStrLocations.split('|'),
|
82
|
+
iStrObjects.split('|'),
|
83
|
+
iStrCategories.split('|'),
|
84
|
+
iStatus)
|
51
85
|
end
|
86
|
+
end
|
52
87
|
|
53
|
-
|
88
|
+
# Dequeue the given stat orders IDs.
|
89
|
+
# Code to begin a new transaction can be set in this method too. In this case, the dequeue should be part of the transaction, or it will have to be re-enqueued during rollback method call (otherwise orders will be lost).
|
90
|
+
#
|
91
|
+
# Parameters:
|
92
|
+
# * *iLstStatsOrderIDs* (<em>list<Integer></em>): The list of stats order IDs to dequeue
|
93
|
+
def dequeueStatsOrders(iLstStatsOrderIDs)
|
94
|
+
@MySQLConnection.query('start transaction')
|
95
|
+
iLstStatsOrderIDs.each do |iStatsOrderID|
|
96
|
+
@StatementDeleteFromStatsOrders.execute(iStatsOrderID)
|
97
|
+
end
|
54
98
|
end
|
55
99
|
|
56
100
|
# Get the list of known locations
|
@@ -105,9 +149,9 @@ module StatsCollect
|
|
105
149
|
# Return:
|
106
150
|
# * _Integer_: Its resulting ID
|
107
151
|
def addLocation(iLocation)
|
108
|
-
@
|
152
|
+
@StatementInsertIntoStatsLocations.execute(iLocation)
|
109
153
|
|
110
|
-
return @
|
154
|
+
return @StatementInsertIntoStatsLocations.insert_id
|
111
155
|
end
|
112
156
|
|
113
157
|
# Add a new category
|
@@ -118,9 +162,9 @@ module StatsCollect
|
|
118
162
|
# Return:
|
119
163
|
# * _Integer_: Its resulting ID
|
120
164
|
def addCategory(iCategory, iValueType)
|
121
|
-
@
|
165
|
+
@StatementInsertIntoStatsCategories.execute(iCategory, iValueType)
|
122
166
|
|
123
|
-
return @
|
167
|
+
return @StatementInsertIntoStatsCategories.insert_id
|
124
168
|
end
|
125
169
|
|
126
170
|
# Add a new object
|
@@ -130,9 +174,9 @@ module StatsCollect
|
|
130
174
|
# Return:
|
131
175
|
# * _Integer_: Its resulting ID
|
132
176
|
def addObject(iObject)
|
133
|
-
@
|
177
|
+
@StatementInsertIntoStatsObjects.execute(iObject)
|
134
178
|
|
135
|
-
return @
|
179
|
+
return @StatementInsertIntoStatsObjects.insert_id
|
136
180
|
end
|
137
181
|
|
138
182
|
# Add a new stat
|
@@ -145,6 +189,10 @@ module StatsCollect
|
|
145
189
|
# * *iValue* (_Object_): The value to store
|
146
190
|
# * *iValueType* (_Integer_): The value type
|
147
191
|
def addStat(iTimeStamp, iLocationID, iObjectID, iCategoryID, iValue, iValueType)
|
192
|
+
# Do we need to store this value ID in the last keys ?
|
193
|
+
lStoreInLastKeys = false
|
194
|
+
lExistingLastKey = false
|
195
|
+
lInsertStatement = @StatementInsertIntoStatsValues
|
148
196
|
# Convert the value to its internal representation
|
149
197
|
lStrValue = nil
|
150
198
|
case iValueType
|
@@ -156,12 +204,151 @@ module StatsCollect
|
|
156
204
|
lStrValue = iValue.to_s
|
157
205
|
when STATS_VALUE_TYPE_UNKNOWN
|
158
206
|
lStrValue = iValue.to_s
|
207
|
+
when STATS_VALUE_TYPE_MAP
|
208
|
+
lInsertStatement = @StatementInsertIntoStatsBinaryValues
|
209
|
+
# This is a special case:
|
210
|
+
# We retrieve the last value of this statistic, and decide if we write it completely, or just write a diff.
|
211
|
+
lLastKeyStatsValueID = nil
|
212
|
+
@StatementSelectFromStatsLastKeys.execute(iLocationID, iObjectID, iCategoryID)
|
213
|
+
# Should be only 1 row, or none
|
214
|
+
@StatementSelectFromStatsLastKeys.each do |iRow|
|
215
|
+
lLastKeyStatsValueID = iRow[0]
|
216
|
+
end
|
217
|
+
if (lLastKeyStatsValueID == nil)
|
218
|
+
# First value to store: store a key
|
219
|
+
lStrValue = "#{DIFFDATA_KEY}#{Zlib::Deflate.new.deflate(Marshal.dump(iValue), Zlib::FINISH)}"
|
220
|
+
lStoreInLastKeys = true
|
221
|
+
else
|
222
|
+
# There was already a previous key for this value.
|
223
|
+
lExistingLastKey = true
|
224
|
+
# Reconstruct the value by getting all the stats_values since this key.
|
225
|
+
lExistingValue = nil
|
226
|
+
@StatementSelectFromStatsBinaryValues.execute(iLocationID, iObjectID, iCategoryID, lLastKeyStatsValueID)
|
227
|
+
# If too much rows, we just create a new key
|
228
|
+
lNbrRows = @StatementSelectFromStatsBinaryValues.num_rows
|
229
|
+
if ((lNbrRows == 0) or
|
230
|
+
(lNbrRows >= DIFFDATA_MAX_NUMBER_OF_ROWS))
|
231
|
+
lStrValue = "#{DIFFDATA_KEY}#{Zlib::Deflate.new.deflate(Marshal.dump(iValue), Zlib::FINISH)}"
|
232
|
+
lStoreInLastKeys = true
|
233
|
+
else
|
234
|
+
@StatementSelectFromStatsBinaryValues.each do |iRow|
|
235
|
+
iID, iRowValue = iRow
|
236
|
+
# Read the type of this diff data
|
237
|
+
case iRowValue[0..0]
|
238
|
+
when DIFFDATA_KEY
|
239
|
+
lExistingValue = Marshal.load(Zlib::Inflate.new.inflate(iRowValue[1..-1]))
|
240
|
+
when DIFFDATA_MERGE
|
241
|
+
lExistingValue.merge!(Marshal.load(iRowValue[1..-1]))
|
242
|
+
when DIFFDATA_DELETE
|
243
|
+
lValuesToDelete = Marshal.load(iRowValue[1..-1])
|
244
|
+
lExistingValue.delete_if do |iKey, iExistingValue|
|
245
|
+
next (lValuesToDelete.include?(iKey))
|
246
|
+
end
|
247
|
+
when DIFFDATA_MODIFY
|
248
|
+
lValuesToDelete, lValuesToModify = Marshal.load(iRowValue[1..-1])
|
249
|
+
lExistingValue.delete_if do |iKey, iExistingValue|
|
250
|
+
next (lValuesToDelete.include?(iKey))
|
251
|
+
end
|
252
|
+
lExistingValue.merge!(lValuesToModify)
|
253
|
+
when DIFFDATA_SAME
|
254
|
+
# Nothing to do
|
255
|
+
else
|
256
|
+
logErr "Unknown diff value type: #{iRowValue[0..0]}"
|
257
|
+
raise RuntimeError.new("Unknown diff value type: #{iRowValue[0..0]}")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
# Now compute the difference between the existing value and the new one
|
261
|
+
lValuesToDelete = []
|
262
|
+
lValuesToMerge = {}
|
263
|
+
iValue.each do |iKey, iNewValue|
|
264
|
+
if (lExistingValue.has_key?(iKey))
|
265
|
+
if (iNewValue != lExistingValue[iKey])
|
266
|
+
# A modified value: add it
|
267
|
+
lValuesToMerge[iKey] = iNewValue
|
268
|
+
end
|
269
|
+
else
|
270
|
+
# A new value: add it
|
271
|
+
lValuesToMerge[iKey] = iNewValue
|
272
|
+
end
|
273
|
+
end
|
274
|
+
lExistingValue.each do |iKey, iExistingValue|
|
275
|
+
if (!iValue.has_key?(iKey))
|
276
|
+
# A missing value: delete it
|
277
|
+
lValuesToDelete << iKey
|
278
|
+
end
|
279
|
+
end
|
280
|
+
if (lValuesToDelete.empty?)
|
281
|
+
if (lValuesToMerge.empty?)
|
282
|
+
lStrValue = DIFFDATA_SAME
|
283
|
+
else
|
284
|
+
lStrValue = "#{DIFFDATA_MERGE}#{Marshal.dump(lValuesToMerge)}"
|
285
|
+
end
|
286
|
+
elsif (lValuesToMerge.empty?)
|
287
|
+
lStrValue = "#{DIFFDATA_DELETE}#{Marshal.dump(lValuesToDelete)}"
|
288
|
+
else
|
289
|
+
lStrValue = "#{DIFFDATA_MODIFY}#{Marshal.dump([lValuesToDelete,lValuesToMerge])}"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
when STATS_VALUE_TYPE_STRING
|
294
|
+
lStrValue = iValue
|
159
295
|
else
|
160
296
|
logErr "Unknown category value type: #{iValueType}. It will be treated as Unknown."
|
161
297
|
lStrValue = iValue.to_s
|
162
298
|
end
|
163
299
|
# Add the new stat in the DB for real
|
164
|
-
|
300
|
+
lInsertStatement.execute(iTimeStamp.to_MySQLTime, iLocationID, iObjectID, iCategoryID, lStrValue)
|
301
|
+
# Store the last key idf needed
|
302
|
+
if (lStoreInLastKeys)
|
303
|
+
lNewStatValueID = lInsertStatement.insert_id
|
304
|
+
if (lExistingLastKey)
|
305
|
+
@StatementUpdateStatsLastKeys.execute(lNewStatValueID, iLocationID, iObjectID, iCategoryID)
|
306
|
+
else
|
307
|
+
@StatementInsertIntoStatsLastKeys.execute(iLocationID, iObjectID, iCategoryID, lNewStatValueID)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Get an existing stat value
|
313
|
+
#
|
314
|
+
# Parameters:
|
315
|
+
# * *iTimeStamp* (_DateTime_): The timestamp
|
316
|
+
# * *iLocationID* (_Integer_): The location ID
|
317
|
+
# * *iObjectID* (_Integer_): The object ID
|
318
|
+
# * *iCategoryID* (_Integer_): The category ID
|
319
|
+
# * *iValueType* (_Integer_): The value type
|
320
|
+
# Return:
|
321
|
+
# * _Object_: The corresponding value, or nil if none
|
322
|
+
def getStat(iTimeStamp, iLocationID, iObjectID, iCategoryID, iValueType)
|
323
|
+
rValue = nil
|
324
|
+
|
325
|
+
@StatementSelectFromStatsValues.execute(iTimeStamp.to_MySQLTime, iLocationID, iObjectID, iCategoryID)
|
326
|
+
if (@StatementSelectFromStatsValues.num_rows > 0)
|
327
|
+
@StatementSelectFromStatsValues.each do |iRow|
|
328
|
+
lStrValue = iRow[0]
|
329
|
+
case iValueType
|
330
|
+
when STATS_VALUE_TYPE_INTEGER
|
331
|
+
rValue = Integer(lStrValue)
|
332
|
+
when STATS_VALUE_TYPE_FLOAT
|
333
|
+
rValue = Float(lStrValue)
|
334
|
+
when STATS_VALUE_TYPE_PERCENTAGE
|
335
|
+
rValue = Float(lStrValue)
|
336
|
+
when STATS_VALUE_TYPE_UNKNOWN
|
337
|
+
rValue = lStrValue
|
338
|
+
when STATS_VALUE_TYPE_MAP
|
339
|
+
# TODO
|
340
|
+
rValue = lStrValue
|
341
|
+
when STATS_VALUE_TYPE_STRING
|
342
|
+
rValue = lStrValue
|
343
|
+
else
|
344
|
+
logErr "Unknown category value type: #{iValueType}. It will be treated as Unknown."
|
345
|
+
rValue = lStrValue
|
346
|
+
end
|
347
|
+
break
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
return rValue
|
165
352
|
end
|
166
353
|
|
167
354
|
# Add a new stats order
|
@@ -173,7 +360,14 @@ module StatsCollect
|
|
173
360
|
# * *iLstCategories* (<em>list<String></em>): List of categories
|
174
361
|
# * *iStatus* (_Integer_): The order status
|
175
362
|
def putNewStatsOrder(iTimeStamp, iLstLocations, iLstObjects, iLstCategories, iStatus)
|
176
|
-
|
363
|
+
lMySQLTime = Mysql::Time.new(
|
364
|
+
iTimeStamp.year,
|
365
|
+
iTimeStamp.month,
|
366
|
+
iTimeStamp.day,
|
367
|
+
iTimeStamp.hour,
|
368
|
+
iTimeStamp.min,
|
369
|
+
iTimeStamp.sec)
|
370
|
+
@StatementInsertIntoStatsOrders.execute(lMySQLTime, iLstLocations.join('|'), iLstObjects.join('|'), iLstCategories.join('|'), iStatus)
|
177
371
|
end
|
178
372
|
|
179
373
|
# Commit the current stats order transaction
|
@@ -186,6 +380,24 @@ module StatsCollect
|
|
186
380
|
@MySQLConnection.query('rollback')
|
187
381
|
end
|
188
382
|
|
383
|
+
# Close a session of this backend
|
384
|
+
def closeSession
|
385
|
+
closePreparedStatement(@MySQLConnection, @StatementSelectFromStatsOrders)
|
386
|
+
closePreparedStatement(@MySQLConnection, @StatementDeleteFromStatsOrders)
|
387
|
+
closePreparedStatement(@MySQLConnection, @StatementInsertIntoStatsLocations)
|
388
|
+
closePreparedStatement(@MySQLConnection, @StatementInsertIntoStatsCategories)
|
389
|
+
closePreparedStatement(@MySQLConnection, @StatementInsertIntoStatsObjects)
|
390
|
+
closePreparedStatement(@MySQLConnection, @StatementInsertIntoStatsValues)
|
391
|
+
closePreparedStatement(@MySQLConnection, @StatementSelectFromStatsValues)
|
392
|
+
closePreparedStatement(@MySQLConnection, @StatementInsertIntoStatsBinaryValues)
|
393
|
+
closePreparedStatement(@MySQLConnection, @StatementInsertIntoStatsOrders)
|
394
|
+
closePreparedStatement(@MySQLConnection, @StatementSelectFromStatsLastKeys)
|
395
|
+
closePreparedStatement(@MySQLConnection, @StatementSelectFromStatsBinaryValues)
|
396
|
+
closePreparedStatement(@MySQLConnection, @StatementInsertIntoStatsLastKeys)
|
397
|
+
closePreparedStatement(@MySQLConnection, @StatementUpdateStatsLastKeys)
|
398
|
+
closeMySQL(@MySQLConnection)
|
399
|
+
end
|
400
|
+
|
189
401
|
end
|
190
402
|
|
191
403
|
end
|