diff --git a/timespans/action_timing.go b/timespans/action_timing.go
index 75c69a2aa..0fd7062ed 100644
--- a/timespans/action_timing.go
+++ b/timespans/action_timing.go
@@ -204,9 +204,9 @@ Serializes the action timing for the storage. Used for key-value storages.
func (at *ActionTiming) store() (result string) {
result += at.Tag + "|"
for _, ubi := range at.UserBalanceIds {
- result += ubi + ";"
+ result += ubi + ","
}
- result = strings.TrimRight(result, ";") + "|"
+ result = strings.TrimRight(result, ",") + "|"
result += at.Timing.store() + "|"
result += strconv.FormatFloat(at.Weight, 'f', -1, 64) + "|"
result += at.ActionsId
@@ -219,9 +219,10 @@ De-serializes the action timing for the storage. Used for key-value storages.
func (at *ActionTiming) restore(input string) {
elements := strings.Split(input, "|")
at.Tag = elements[0]
- for _, ubi := range strings.Split(elements[1], ";") {
+ for _, ubi := range strings.Split(elements[1], ",") {
at.UserBalanceIds = append(at.UserBalanceIds, ubi)
}
+
at.Timing = &Interval{}
at.Timing.restore(elements[2])
at.Weight, _ = strconv.ParseFloat(elements[3], 64)
diff --git a/timespans/action_trigger.go b/timespans/action_trigger.go
index ee7bcff00..72a03a1a4 100644
--- a/timespans/action_trigger.go
+++ b/timespans/action_trigger.go
@@ -21,6 +21,8 @@ package timespans
import (
"log"
"sort"
+ "strconv"
+ "strings"
)
type ActionTrigger struct {
@@ -73,3 +75,30 @@ func (atpl ActionTriggerPriotityList) Less(i, j int) bool {
func (atpl ActionTriggerPriotityList) Sort() {
sort.Sort(atpl)
}
+
+/*
+Serializes the action trigger for the storage. Used for key-value storages.
+*/
+func (at *ActionTrigger) store() (result string) {
+ result += at.BalanceId + ";"
+ result += at.DestinationId + ";"
+ result += at.ActionsId + ";"
+ result += strconv.FormatFloat(at.ThresholdValue, 'f', -1, 64) + ";"
+ result += strconv.FormatFloat(at.Weight, 'f', -1, 64)
+ return
+}
+
+/*
+De-serializes the action timing for the storage. Used for key-value storages.
+*/
+func (at *ActionTrigger) restore(input string) {
+ elements := strings.Split(input, ";")
+ if len(elements) != 5 {
+ return
+ }
+ at.BalanceId = elements[0]
+ at.DestinationId = elements[1]
+ at.ActionsId = elements[2]
+ at.ThresholdValue, _ = strconv.ParseFloat(elements[3], 64)
+ at.Weight, _ = strconv.ParseFloat(elements[4], 64)
+}
diff --git a/timespans/actions_test.go b/timespans/actions_test.go
index e513d9e37..4ebec5e23 100644
--- a/timespans/actions_test.go
+++ b/timespans/actions_test.go
@@ -44,7 +44,7 @@ func TestActionTimingStoreRestore(t *testing.T) {
ActionsId: "Commando",
}
r := at.store()
- if string(r) != "test|one;two;three|1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;1|10|Commando" {
+ if string(r) != "test|one,two,three|1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;1|10|Commando" {
t.Errorf("Error serializing action timing: %v", string(r))
}
o := &ActionTiming{}
@@ -54,6 +54,25 @@ func TestActionTimingStoreRestore(t *testing.T) {
}
}
+func TestActionTriggerStoreRestore(t *testing.T) {
+ at := &ActionTrigger{
+ BalanceId: CREDIT,
+ ThresholdValue: 100.0,
+ DestinationId: "NAT",
+ Weight: 10.0,
+ ActionsId: "Commando",
+ }
+ r := at.store()
+ if string(r) != "MONETARY;NAT;Commando;100;10" {
+ t.Errorf("Error serializing action trigger: %v", string(r))
+ }
+ o := &ActionTrigger{}
+ o.restore(r)
+ if !reflect.DeepEqual(o, at) {
+ t.Errorf("Expected %v was %v", at, o)
+ }
+}
+
func TestActionTimingNothing(t *testing.T) {
at := &ActionTiming{}
st := at.GetNextStartTime()
diff --git a/timespans/activationperiod.go b/timespans/activationperiod.go
index 1865becdd..be58db0c8 100644
--- a/timespans/activationperiod.go
+++ b/timespans/activationperiod.go
@@ -69,6 +69,7 @@ func (ap *ActivationPeriod) store() (result string) {
for _, i := range ap.Intervals {
result += i.store() + "|"
}
+ result = strings.TrimRight(result, "|")
return
}
diff --git a/timespans/activationperiod_test.go b/timespans/activationperiod_test.go
index 40bd60284..acd0e44b6 100644
--- a/timespans/activationperiod_test.go
+++ b/timespans/activationperiod_test.go
@@ -42,7 +42,7 @@ func TestApStoreRestore(t *testing.T) {
ap := &ActivationPeriod{ActivationTime: d}
ap.AddInterval(i)
result := ap.store()
- expected := "1328106601000000000|2;1;3,4;14:30:00;15:00:00;0;0;0;0|"
+ expected := "1328106601000000000|2;1;3,4;14:30:00;15:00:00;0;0;0;0"
if result != expected {
t.Errorf("Expected %q was %q", expected, result)
}
@@ -166,14 +166,7 @@ func TestApAddIntervalIfNotPresent(t *testing.T) {
/**************************** Benchmarks *************************************/
-func BenchmarkActivationPeriodRestore(b *testing.B) {
- ap := ActivationPeriod{}
- for i := 0; i < b.N; i++ {
- json.Unmarshal([]byte("1328106601;2|1|3,4|14:30:00|15:00:00|0|0|0|0;"), &ap)
- }
-}
-
-func BenchmarkActivationPeriodStoreRestore(b *testing.B) {
+func BenchmarkActivationPeriodStoreRestoreJson(b *testing.B) {
b.StopTimer()
d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC)
i := &Interval{Months: []time.Month{time.February},
@@ -191,3 +184,29 @@ func BenchmarkActivationPeriodStoreRestore(b *testing.B) {
json.Unmarshal(result, &ap1)
}
}
+
+func BenchmarkActivationPeriodRestore(b *testing.B) {
+ ap := ActivationPeriod{}
+ for i := 0; i < b.N; i++ {
+ ap.restore("1328106601000000000|2;1;3,4;14:30:00;15:00:00;0;0;0;0")
+ }
+}
+
+func BenchmarkActivationPeriodStoreRestore(b *testing.B) {
+ b.StopTimer()
+ d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC)
+ i := &Interval{Months: []time.Month{time.February},
+ MonthDays: []int{1},
+ WeekDays: []time.Weekday{time.Wednesday, time.Thursday},
+ StartTime: "14:30:00",
+ EndTime: "15:00:00"}
+ ap := &ActivationPeriod{ActivationTime: d}
+ ap.AddInterval(i)
+
+ ap1 := ActivationPeriod{}
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ result := ap.store()
+ ap1.restore(result)
+ }
+}
diff --git a/timespans/storage_interface.go b/timespans/storage_interface.go
index 577417b32..70e7058c1 100644
--- a/timespans/storage_interface.go
+++ b/timespans/storage_interface.go
@@ -23,6 +23,7 @@ import (
"encoding/gob"
"encoding/json"
"errors"
+ "log"
"strings"
)
@@ -194,6 +195,7 @@ func (mm *MyMarshaler) Unmarshal(data []byte, v interface{}) (err error) {
return nil
}
+ log.Print("Using default gob marshalling!")
mm.buf.Reset()
mm.buf.Write(data)
return gob.NewDecoder(&mm.buf).Decode(v)
diff --git a/timespans/units_counter.go b/timespans/units_counter.go
new file mode 100644
index 000000000..27f165f27
--- /dev/null
+++ b/timespans/units_counter.go
@@ -0,0 +1,82 @@
+/*
+Rating system designed to be used in VoIP Carriers World
+Copyright (C) 2012 Radu Ioan Fericean
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+
+package timespans
+
+import (
+ "strconv"
+ "strings"
+)
+
+// Amount of a trafic of a certain type
+type UnitsCounter struct {
+ Direction string
+ BalanceId string
+ Units float64
+ Weight float64
+ MinuteBuckets []*MinuteBucket
+}
+
+// Structure to store actions according to weight
+type countersorter []*UnitsCounter
+
+func (s countersorter) Len() int {
+ return len(s)
+}
+
+func (s countersorter) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
+func (s countersorter) Less(i, j int) bool {
+ return s[i].Weight < s[j].Weight
+}
+
+/*
+Serializes the unit counter for the storage. Used for key-value storages.
+*/
+func (uc *UnitsCounter) store() (result string) {
+ result += uc.Direction + "/"
+ result += uc.BalanceId + "/"
+ result += strconv.FormatFloat(uc.Units, 'f', -1, 64) + "/"
+ result += strconv.FormatFloat(uc.Weight, 'f', -1, 64) + "/"
+ for _, mb := range uc.MinuteBuckets {
+ result += mb.store() + ","
+ }
+ result = strings.TrimRight(result, ",")
+ return
+}
+
+/*
+De-serializes the unit counter for the storage. Used for key-value storages.
+*/
+func (uc *UnitsCounter) restore(input string) {
+ elements := strings.Split(input, "/")
+ if len(elements) != 5 {
+ return
+ }
+ uc.Direction = elements[0]
+ uc.BalanceId = elements[1]
+ uc.Units, _ = strconv.ParseFloat(elements[2], 64)
+ uc.Weight, _ = strconv.ParseFloat(elements[3], 64)
+ for _, mbs := range strings.Split(elements[4], ",") {
+ mb := &MinuteBucket{}
+ mb.restore(mbs)
+ uc.MinuteBuckets = append(uc.MinuteBuckets, mb)
+ }
+}
diff --git a/timespans/units_counter_test.go b/timespans/units_counter_test.go
new file mode 100644
index 000000000..23fcc8c80
--- /dev/null
+++ b/timespans/units_counter_test.go
@@ -0,0 +1,43 @@
+/*
+Rating system designed to be used in VoIP Carriers World
+Copyright (C) 2012 Radu Ioan Fericean
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+
+package timespans
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestUnitsCounterStoreRestore(t *testing.T) {
+ uc := &UnitsCounter{
+ Direction: OUTBOUND,
+ BalanceId: SMS,
+ Units: 100,
+ Weight: 10,
+ MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
+ }
+ r := uc.store()
+ if string(r) != "OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET" {
+ t.Errorf("Error serializing units counter: %v", string(r))
+ }
+ o := &UnitsCounter{}
+ o.restore(r)
+ if !reflect.DeepEqual(o, uc) {
+ t.Errorf("Expected %v was %v", uc, o)
+ }
+}
diff --git a/timespans/userbalance.go b/timespans/userbalance.go
index 2b9a21592..327c77a9f 100644
--- a/timespans/userbalance.go
+++ b/timespans/userbalance.go
@@ -19,7 +19,8 @@ along with this program. If not, see
package timespans
import (
- // "log"
+ "strconv"
+ "strings"
"sync"
)
@@ -291,26 +292,72 @@ Resets the user balance items to their tariff plan values.
return
}
*/
-// Amount of a trafic of a certain type
-type UnitsCounter struct {
- Direction string
- BalanceId string
- Units float64
- Weight float64
- MinuteBuckets []*MinuteBucket
+
+/*
+Serializes the user balance for the storage. Used for key-value storages.
+*/
+func (ub *UserBalance) store() (result string) {
+ result += ub.Id + "|"
+ result += ub.Type + "|"
+ for k, v := range ub.BalanceMap {
+ result += k + ":" + strconv.FormatFloat(v, 'f', -1, 64) + "#"
+ }
+ result = strings.TrimRight(result, "#") + "|"
+ for _, mb := range ub.MinuteBuckets {
+ result += mb.store() + "#"
+ }
+ result = strings.TrimRight(result, "#") + "|"
+ for _, uc := range ub.UnitCounters {
+ result += uc.store() + "#"
+ }
+ result = strings.TrimRight(result, "#") + "|"
+ for _, at := range ub.ActionTriggers {
+ result += at.store() + "#"
+ }
+ result = strings.TrimRight(result, "#")
+ return
}
-// Structure to store actions according to weight
-type countersorter []*UnitsCounter
-
-func (s countersorter) Len() int {
- return len(s)
-}
-
-func (s countersorter) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
-}
-
-func (s countersorter) Less(i, j int) bool {
- return s[i].Weight < s[j].Weight
+/*
+De-serializes the user balance for the storage. Used for key-value storages.
+*/
+func (ub *UserBalance) restore(input string) {
+ elements := strings.Split(input, "|")
+ ub.Id = elements[0]
+ ub.Type = elements[1]
+ if ub.BalanceMap == nil {
+ ub.BalanceMap = make(map[string]float64, 0)
+ }
+ for _, maps := range strings.Split(elements[2], "#") {
+ kv := strings.Split(maps, ":")
+ if len(kv) != 2 {
+ continue
+ }
+ value, _ := strconv.ParseFloat(kv[1], 64)
+ ub.BalanceMap[kv[0]] = value
+ }
+ for _, mbs := range strings.Split(elements[3], "#") {
+ if mbs == "" {
+ continue
+ }
+ mb := &MinuteBucket{}
+ mb.restore(mbs)
+ ub.MinuteBuckets = append(ub.MinuteBuckets, mb)
+ }
+ for _, ucs := range strings.Split(elements[4], "#") {
+ if ucs == "" {
+ continue
+ }
+ uc := &UnitsCounter{}
+ uc.restore(ucs)
+ ub.UnitCounters = append(ub.UnitCounters, uc)
+ }
+ for _, ats := range strings.Split(elements[5], "#") {
+ if ats == "" {
+ continue
+ }
+ at := &ActionTrigger{}
+ at.restore(ats)
+ ub.ActionTriggers = append(ub.ActionTriggers, at)
+ }
}
diff --git a/timespans/userbalance_test.go b/timespans/userbalance_test.go
index e18fda960..da84eea7c 100644
--- a/timespans/userbalance_test.go
+++ b/timespans/userbalance_test.go
@@ -35,6 +35,41 @@ func init() {
}
func TestUserBalanceStoreRestore(t *testing.T) {
+ uc := &UnitsCounter{
+ Direction: OUTBOUND,
+ BalanceId: SMS,
+ Units: 100,
+ Weight: 10,
+ MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
+ }
+ at := &ActionTrigger{
+ BalanceId: CREDIT,
+ ThresholdValue: 100.0,
+ DestinationId: "NAT",
+ Weight: 10.0,
+ ActionsId: "Commando",
+ }
+ ub := &UserBalance{
+ Id: "rif",
+ Type: UB_TYPE_POSTPAID,
+ BalanceMap: map[string]float64{SMS: 14, TRAFFIC: 1024},
+ MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}},
+ UnitCounters: []*UnitsCounter{uc, uc},
+ ActionTriggers: ActionTriggerPriotityList{at, at, at},
+ }
+ r := ub.store()
+ if string(r) != "rif|postpaid|SMS:14#INTERNET:1024|0;20;1;0;NAT#0;10;10;0;RET|OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET#OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET|MONETARY;NAT;Commando;100;10#MONETARY;NAT;Commando;100;10#MONETARY;NAT;Commando;100;10" &&
+ string(r) != "rif|postpaid|INTERNET:1024#SMS:14|0;20;1;0;NAT#0;10;10;0;RET|OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET#OUT/SMS/100/10/0;20;1;0;NAT,0;10;10;0;RET|MONETARY;NAT;Commando;100;10#MONETARY;NAT;Commando;100;10#MONETARY;NAT;Commando;100;10" {
+ t.Errorf("Error serializing action timing: %v", string(r))
+ }
+ o := &UserBalance{}
+ o.restore(r)
+ if !reflect.DeepEqual(o, ub) {
+ t.Errorf("Expected %v was %v", ub, o)
+ }
+}
+
+func TestUserBalanceStorageStoreRestore(t *testing.T) {
b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"}
b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"}
rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT: 21}}