diff --git a/timespans/activationperiod.go b/timespans/activationperiod.go index 2dd6fdf47..5ff23fd77 100644 --- a/timespans/activationperiod.go +++ b/timespans/activationperiod.go @@ -20,6 +20,8 @@ package timespans import ( "time" //"log" + "strconv" + "strings" ) /* @@ -40,3 +42,57 @@ func (ap *ActivationPeriod) AddInterval(is ...*Interval) { } } +/* +Serializes the activation periods for the storage. Used for key-value storages. +*/ +func (ap *ActivationPeriod) store() (result string) { + result += strconv.FormatInt(ap.ActivationTime.UnixNano(), 10) + ";" + for _, i := range ap.Intervals { + var is string + is = strconv.Itoa(int(i.Month)) + "|" + is += strconv.Itoa(i.MonthDay) + "|" + for _, wd := range i.WeekDays { + is += strconv.Itoa(int(wd)) + "," + } + is = strings.TrimRight(is, ",") + "|" + is += i.StartTime + "|" + is += i.EndTime + "|" + is += strconv.FormatFloat(i.Ponder, 'f', -1, 64) + "|" + is += strconv.FormatFloat(i.ConnectFee, 'f', -1, 64) + "|" + is += strconv.FormatFloat(i.Price, 'f', -1, 64) + "|" + is += strconv.FormatFloat(i.BillingUnit, 'f', -1, 64) + result += is + ";" + } + return +} + +/* +De-serializes the activation periods for the storage. Used for key-value storages. +*/ +func (ap *ActivationPeriod) restore(input string) { + elements := strings.Split(input, ";") + unixNano, _ := strconv.ParseInt(elements[0], 10, 64) + ap.ActivationTime = time.Unix(0, unixNano).In(time.UTC) + ap.Intervals = make([]*Interval, 0) + for _, is := range elements[1 : len(elements)-1] { + i := &Interval{} + ise := strings.Split(is, "|") + month, _ := strconv.Atoi(ise[0]) + i.Month = time.Month(month) + i.MonthDay, _ = strconv.Atoi(ise[1]) + for _, d := range strings.Split(ise[2], ",") { + if d != "" { + wd, _ := strconv.Atoi(d) + i.WeekDays = append(i.WeekDays, time.Weekday(wd)) + } + } + i.StartTime = ise[3] + i.EndTime = ise[4] + i.Ponder, _ = strconv.ParseFloat(ise[5], 64) + i.ConnectFee, _ = strconv.ParseFloat(ise[6], 64) + i.Price, _ = strconv.ParseFloat(ise[7], 64) + i.BillingUnit, _ = strconv.ParseFloat(ise[8], 64) + + ap.Intervals = append(ap.Intervals, i) + } +} diff --git a/timespans/activationperiod_test.go b/timespans/activationperiod_test.go index 8f5b62244..24cc2947a 100644 --- a/timespans/activationperiod_test.go +++ b/timespans/activationperiod_test.go @@ -47,8 +47,6 @@ func TestApRestoreRedis(t *testing.T) { } func TestApStoreRestore(t *testing.T) { - getter, _ := NewKyotoStorage("test.kch") - defer getter.Close() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &Interval{Month: time.February, MonthDay: 1, @@ -57,22 +55,29 @@ func TestApStoreRestore(t *testing.T) { EndTime: "15:00:00"} ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) - - getter.SetActivationPeriods("storerestore", []*ActivationPeriod{ap}) - aps, err := getter.GetActivationPeriods("storerestore") - if err != nil || len(aps) != 1 || !reflect.DeepEqual(ap, aps[0]) { - t.Log(aps) - t.Errorf("Expected %v was %v ", ap, aps) + result := ap.store() + 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) + } + ap1 := ActivationPeriod{} + ap1.restore(result) + if reflect.DeepEqual(ap, ap1) { + t.Errorf("Expected %v was %v", ap, ap1) } - } /**************************** Benchmarks *************************************/ +func BenchmarkActivationPeriodRestore(b *testing.B) { + ap := ActivationPeriod{} + for i := 0; i < b.N; i++ { + ap.restore("1328106601;2|1|3,4|14:30:00|15:00:00|0|0|0|0;") + } +} + func BenchmarkActivationPeriodStoreRestore(b *testing.B) { b.StopTimer() - getter, _ := NewKyotoStorage("test.kch") - defer getter.Close() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &Interval{Month: time.February, MonthDay: 1, @@ -82,9 +87,10 @@ func BenchmarkActivationPeriodStoreRestore(b *testing.B) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) + ap1 := ActivationPeriod{} b.StartTimer() for i := 0; i < b.N; i++ { - getter.SetActivationPeriods("storerestore", []*ActivationPeriod{ap}) - getter.GetActivationPeriods("storerestore") + result := ap.store() + ap1.restore(result) } } diff --git a/timespans/calldesc_test.go b/timespans/calldesc_test.go index 5b59604e1..be3db32e7 100644 --- a/timespans/calldesc_test.go +++ b/timespans/calldesc_test.go @@ -221,7 +221,7 @@ func TestMaxSessionTimeNoUserBudget(t *testing.T) { defer getter.Close() cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0723", storageGetter: getter, Amount: 1000} result, err := cd.GetMaxSessionTime() - if result != 1000 || err == nil { + if result != 1000 || err != nil { t.Errorf("Expected %v was %v", 1000, result) } } diff --git a/timespans/destinations.go b/timespans/destinations.go index b0f4ea2e5..556924f38 100644 --- a/timespans/destinations.go +++ b/timespans/destinations.go @@ -17,6 +17,10 @@ along with this program. If not, see */ package timespans +import ( + "strings" +) + /* Structure that gathers multiple destination prefixes under a common id. */ @@ -25,6 +29,21 @@ type Destination struct { Prefixes []string } +/* +Serializes the destination for the storage. Used for key-value storages. +*/ +func (d *Destination) store() (result string) { + for _, p := range d.Prefixes { + result += p + "," + } + result = strings.TrimRight(result, ",") + return +} + +func (d *Destination) restore(input string) { + d.Prefixes = strings.Split(input, ",") +} + /* De-serializes the destination for the storage. Used for key-value storages. */ diff --git a/timespans/kyoto_storage.go b/timespans/kyoto_storage.go index d88f2f0a2..68a638fb1 100644 --- a/timespans/kyoto_storage.go +++ b/timespans/kyoto_storage.go @@ -15,123 +15,83 @@ 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 ( - "bitbucket.org/ww/cabinet" - "bytes" - "encoding/json" + "github.com/fsouza/gokabinet/kc" //"log" - "sync" + "strings" ) type KyotoStorage struct { - //db *kc.DB - db *cabinet.KCDB - buf bytes.Buffer - dec *json.Decoder - enc *json.Encoder - mux sync.Mutex // we need norma lock because we reset the buf variable + db *kc.DB } func NewKyotoStorage(filaName string) (*KyotoStorage, error) { - ndb := cabinet.New() - err := ndb.Open(filaName, cabinet.KCOWRITER|cabinet.KCOCREATE) - ks := &KyotoStorage{db: ndb} - - ks.dec = json.NewDecoder(&ks.buf) - ks.enc = json.NewEncoder(&ks.buf) - return ks, err + ndb, err := kc.Open(filaName, kc.WRITE) + return &KyotoStorage{db: ndb}, err } func (ks *KyotoStorage) Close() { ks.db.Close() } -func (ks *KyotoStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { - ks.mux.Lock() - defer ks.mux.Unlock() +func (ks *KyotoStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { + values, err := ks.db.Get(key) - ks.buf.Reset() - ks.enc.Encode(aps) - return ks.db.Set([]byte(key), ks.buf.Bytes()) + if err == nil { + for _, ap_string := range strings.Split(values, "\n") { + if len(ap_string) > 0 { + ap := &ActivationPeriod{} + ap.restore(ap_string) + aps = append(aps, ap) + } + } + } + return aps, err } -func (ks *KyotoStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { - ks.mux.Lock() - defer ks.mux.Unlock() +func (ks *KyotoStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { + result := "" + for _, ap := range aps { + result += ap.store() + "\n" + } + return ks.db.Set(key, result) +} - values, err := ks.db.Get([]byte(key)) - if err == nil { - ks.buf.Reset() - ks.buf.Write(values) - err = ks.dec.Decode(&aps) +func (ks *KyotoStorage) GetDestination(key string) (dest *Destination, err error) { + if values, err := ks.db.Get(key); err == nil { + dest = &Destination{Id: key} + dest.restore(values) } return } func (ks *KyotoStorage) SetDestination(dest *Destination) error { - ks.mux.Lock() - defer ks.mux.Unlock() - - ks.buf.Reset() - ks.enc.Encode(dest) - return ks.db.Set([]byte(dest.Id), ks.buf.Bytes()) + return ks.db.Set(dest.Id, dest.store()) } -func (ks *KyotoStorage) GetDestination(key string) (dest *Destination, err error) { - ks.mux.Lock() - defer ks.mux.Unlock() - - values, err := ks.db.Get([]byte(key)) - if err == nil { - ks.buf.Reset() - ks.buf.Write(values) - err = ks.dec.Decode(&dest) +func (ks *KyotoStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) { + if values, err := ks.db.Get(key); err == nil { + tp = &TariffPlan{Id: key} + tp.restore(values) } return } func (ks *KyotoStorage) SetTariffPlan(tp *TariffPlan) error { - ks.mux.Lock() - defer ks.mux.Unlock() - - ks.buf.Reset() - ks.enc.Encode(tp) - return ks.db.Set([]byte(tp.Id), ks.buf.Bytes()) + return ks.db.Set(tp.Id, tp.store()) } -func (ks *KyotoStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) { - ks.mux.Lock() - defer ks.mux.Unlock() - - values, err := ks.db.Get([]byte(key)) - if err == nil { - ks.buf.Reset() - ks.buf.Write(values) - err = ks.dec.Decode(&tp) +func (ks *KyotoStorage) GetUserBudget(key string) (ub *UserBudget, err error) { + if values, err := ks.db.Get(key); err == nil { + ub = &UserBudget{Id: key} + ub.restore(values) } return } func (ks *KyotoStorage) SetUserBudget(ub *UserBudget) error { - ks.mux.Lock() - defer ks.mux.Unlock() - - ks.buf.Reset() - ks.enc.Encode(ub) - return ks.db.Set([]byte(ub.Id), ks.buf.Bytes()) -} - -func (ks *KyotoStorage) GetUserBudget(key string) (ub *UserBudget, err error) { - ks.mux.Lock() - defer ks.mux.Unlock() - - values, err := ks.db.Get([]byte(key)) - if err == nil { - ks.buf.Reset() - ks.buf.Write(values) - ks.dec.Decode(&ub) - } - return + return ks.db.Set(ub.Id, ub.store()) } diff --git a/timespans/minute_buckets.go b/timespans/minute_buckets.go index c24821e2f..908c7fcc1 100644 --- a/timespans/minute_buckets.go +++ b/timespans/minute_buckets.go @@ -20,6 +20,8 @@ package timespans import ( // "log" "math" + "strconv" + "strings" ) type MinuteBucket struct { @@ -31,6 +33,28 @@ type MinuteBucket struct { precision int } +/* +Serializes the minute bucket for the storage. Used for key-value storages. +*/ +func (mb *MinuteBucket) store() (result string) { + result += strconv.Itoa(int(mb.Seconds)) + "|" + result += strconv.Itoa(int(mb.Priority)) + "|" + result += strconv.FormatFloat(mb.Price, 'f', -1, 64) + "|" + result += mb.DestinationId + return +} + +/* +De-serializes the minute bucket for the storage. Used for key-value storages. +*/ +func (mb *MinuteBucket) restore(input string) { + elements := strings.Split(input, "|") + mb.Seconds, _ = strconv.ParseFloat(elements[0], 64) + mb.Priority, _ = strconv.Atoi(elements[1]) + mb.Price, _ = strconv.ParseFloat(elements[2], 64) + mb.DestinationId = elements[3] +} + /* Returns the destination loading it from the storage if necessary. */ diff --git a/timespans/redis_storage.go b/timespans/redis_storage.go index 93bd64b24..66c225793 100644 --- a/timespans/redis_storage.go +++ b/timespans/redis_storage.go @@ -18,126 +18,86 @@ along with this program. If not, see package timespans import ( - "bytes" - "encoding/json" "github.com/simonz05/godis" - // "log" - "sync" + "strings" ) type RedisStorage struct { dbNb int db *godis.Client - buf bytes.Buffer - dec *json.Decoder - enc *json.Encoder - mux sync.Mutex } func NewRedisStorage(address string, db int) (*RedisStorage, error) { ndb := godis.New(address, db, "") - rs := &RedisStorage{db: ndb, dbNb: db} - rs.dec = json.NewDecoder(&rs.buf) - rs.enc = json.NewEncoder(&rs.buf) - return rs, nil + return &RedisStorage{db: ndb, dbNb: db}, nil } func (rs *RedisStorage) Close() { rs.db.Quit() } -func (rs *RedisStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { - //.db.Select(rs.dbNb) - rs.mux.Lock() - defer rs.mux.Unlock() - - rs.buf.Reset() - rs.enc.Encode(aps) - return rs.db.Set(key, rs.buf.Bytes()) -} - func (rs *RedisStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { //rs.db.Select(rs.dbNb) - rs.mux.Lock() - defer rs.mux.Unlock() elem, err := rs.db.Get(key) + values := elem.String() if err == nil { - rs.buf.Reset() - rs.buf.Write(elem.Bytes()) - - err = rs.dec.Decode(&aps) + for _, ap_string := range strings.Split(values, "\n") { + if len(ap_string) > 0 { + ap := &ActivationPeriod{} + ap.restore(ap_string) + aps = append(aps, ap) + } + } } - return + return aps, err } -func (rs *RedisStorage) SetDestination(dest *Destination) error { - //rs.db.Select(rs.dbNb + 1) - rs.mux.Lock() - defer rs.mux.Unlock() - - rs.buf.Reset() - rs.enc.Encode(dest) - return rs.db.Set(dest.Id, rs.buf.Bytes()) +func (rs *RedisStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { + //.db.Select(rs.dbNb) + result := "" + for _, ap := range aps { + result += ap.store() + "\n" + } + return rs.db.Set(key, result) } func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error) { //rs.db.Select(rs.dbNb + 1) - rs.mux.Lock() - defer rs.mux.Unlock() + if values, err := rs.db.Get(key); err == nil { + dest = &Destination{Id: key} + dest.restore(values.String()) + } + return +} +func (rs *RedisStorage) SetDestination(dest *Destination) error { + //rs.db.Select(rs.dbNb + 1) + return rs.db.Set(dest.Id, dest.store()) +} - elem, err := rs.db.Get(key) - if err == nil { - rs.buf.Reset() - rs.buf.Write(elem.Bytes()) - err = rs.dec.Decode(&dest) +func (rs *RedisStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) { + //rs.db.Select(rs.dbNb + 2) + if values, err := rs.db.Get(key); err == nil { + tp = &TariffPlan{Id: key} + tp.restore(values.String()) } return } func (rs *RedisStorage) SetTariffPlan(tp *TariffPlan) error { //rs.db.Select(rs.dbNb + 2) - rs.mux.Lock() - defer rs.mux.Unlock() - - rs.buf.Reset() - rs.enc.Encode(tp) - return rs.db.Set(tp.Id, rs.buf.Bytes()) + return rs.db.Set(tp.Id, tp.store()) } -func (rs *RedisStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) { - //rs.db.Select(rs.dbNb + 2) - rs.mux.Lock() - defer rs.mux.Unlock() - - elem, err := rs.db.Get(key) - if err == nil { - rs.buf.Reset() - rs.buf.Write(elem.Bytes()) - err = rs.dec.Decode(&tp) +func (rs *RedisStorage) GetUserBudget(key string) (ub *UserBudget, err error) { + //rs.db.Select(rs.dbNb + 3) + if values, err := rs.db.Get(key); err == nil { + ub = &UserBudget{Id: key} + ub.restore(values.String()) } return } func (rs *RedisStorage) SetUserBudget(ub *UserBudget) error { //rs.db.Select(rs.dbNb + 3) - rs.mux.Lock() - defer rs.mux.Unlock() - - rs.buf.Reset() - rs.enc.Encode(ub) - return rs.db.Set(ub.Id, rs.buf.Bytes()) -} - -func (rs *RedisStorage) GetUserBudget(key string) (ub *UserBudget, err error) { - //rs.db.Select(rs.dbNb + 3) - rs.mux.Lock() - defer rs.mux.Unlock() - - elem, err := rs.db.Get(key) - if err == nil { - rs.buf.Reset() - rs.buf.Write(elem.Bytes()) - err = rs.dec.Decode(&ub) - } - return + return rs.db.Set(ub.Id, ub.store()) } diff --git a/timespans/tariff_plans.go b/timespans/tariff_plans.go index fb642f5a8..8872deceb 100644 --- a/timespans/tariff_plans.go +++ b/timespans/tariff_plans.go @@ -19,6 +19,8 @@ package timespans import ( // "log" + "strings" + "strconv" ) /* @@ -35,6 +37,62 @@ type TariffPlan struct { VolumeDiscountThresholds []*VolumeDiscount } +/* +Serializes the tariff plan for the storage. Used for key-value storages. +*/ +func (tp *TariffPlan) store() (result string) { + result += strconv.FormatFloat(tp.SmsCredit, 'f', -1, 64) + ";" + result += strconv.FormatFloat(tp.Traffic, 'f', -1, 64) + ";" + result += strconv.FormatFloat(tp.ReceivedCallSecondsLimit, 'f', -1, 64) + ";" + if tp.RecivedCallBonus == nil { + tp.RecivedCallBonus = &RecivedCallBonus{} + } + result += tp.RecivedCallBonus.store() + ";" + for i, mb := range tp.MinuteBuckets { + if i > 0 { + result += "," + } + result += mb.store() + } + if tp.VolumeDiscountThresholds != nil { + result += ";" + } + for i, vd := range tp.VolumeDiscountThresholds { + if i > 0 { + result += "," + } + result += strconv.FormatFloat(vd.Volume, 'f', -1, 64) + "|" + strconv.FormatFloat(vd.Discount, 'f', -1, 64) + } + result = strings.TrimRight(result, ";") + return +} + +/* +De-serializes the tariff plan for the storage. Used for key-value storages. +*/ +func (tp *TariffPlan) restore(input string) { + elements := strings.Split(input, ";") + tp.SmsCredit, _ = strconv.ParseFloat(elements[0], 64) + tp.Traffic, _ = strconv.ParseFloat(elements[1], 64) + tp.ReceivedCallSecondsLimit, _ = strconv.ParseFloat(elements[2], 64) + tp.RecivedCallBonus = &RecivedCallBonus{} + tp.RecivedCallBonus.restore(elements[3]) + for _, mbs := range strings.Split(elements[4], ",") { + mb := &MinuteBucket{} + mb.restore(mbs) + tp.MinuteBuckets = append(tp.MinuteBuckets, mb) + } + if len(elements) > 5 { + for _, vdss := range strings.Split(elements[5], ",") { + vd := &VolumeDiscount{} + vds := strings.Split(vdss, "|") + vd.Volume, _ = strconv.ParseFloat(vds[0], 64) + vd.Discount, _ = strconv.ParseFloat(vds[1], 64) + tp.VolumeDiscountThresholds = append(tp.VolumeDiscountThresholds, vd) + } + } +} + /* Structure that holds the thresholds and for which */ @@ -52,3 +110,31 @@ type RecivedCallBonus struct { Traffic float64 MinuteBucket *MinuteBucket } + +/* +Serializes the tariff plan for the storage. Used for key-value storages. +*/ +func (rcb *RecivedCallBonus) store() (result string) { + result += strconv.FormatFloat(rcb.Credit, 'f', -1, 64) + "," + result += strconv.FormatFloat(rcb.SmsCredit, 'f', -1, 64) + "," + result += strconv.FormatFloat(rcb.Traffic, 'f', -1, 64) + if rcb.MinuteBucket != nil { + result += "," + result += rcb.MinuteBucket.store() + } + return +} + +/* +De-serializes the tariff plan for the storage. Used for key-value storages. +*/ +func (rcb *RecivedCallBonus) restore(input string) { + elements := strings.Split(input, ",") + rcb.Credit, _ = strconv.ParseFloat(elements[0], 64) + rcb.SmsCredit, _ = strconv.ParseFloat(elements[1], 64) + rcb.Traffic, _ = strconv.ParseFloat(elements[2], 64) + if len(elements) > 3 { + rcb.MinuteBucket = &MinuteBucket{} + rcb.MinuteBucket.restore(elements[3]) + } +} diff --git a/timespans/test.kch b/timespans/test.kch index 5fd8c3b57..d7a0230fd 100644 Binary files a/timespans/test.kch and b/timespans/test.kch differ diff --git a/timespans/userbudget.go b/timespans/userbudget.go index 36de8884c..8de90f29f 100644 --- a/timespans/userbudget.go +++ b/timespans/userbudget.go @@ -21,6 +21,8 @@ import ( // "log" "sort" "sync" + "strconv" + "strings" ) /* @@ -68,6 +70,50 @@ func (bs bucketsorter) Less(j, i int) bool { bs[i].Price > bs[j].Price } +/* +Serializes the user budget for the storage. Used for key-value storages. +*/ +func (ub *UserBudget) store() (result string) { + result += strconv.FormatFloat(ub.Credit, 'f', -1, 64) + ";" + result += strconv.FormatFloat(ub.SmsCredit, 'f', -1, 64) + ";" + result += strconv.FormatFloat(ub.Traffic, 'f', -1, 64) + ";" + result += strconv.FormatFloat(ub.VolumeDiscountSeconds, 'f', -1, 64) + ";" + result += strconv.FormatFloat(ub.ReceivedCallSeconds, 'f', -1, 64) + ";" + result += strconv.Itoa(ub.ResetDayOfTheMonth) + ";" + result += ub.TariffPlanId + if ub.MinuteBuckets != nil { + result += ";" + } + for i, mb := range ub.MinuteBuckets { + if i > 0 { + result += "," + } + result += mb.store() + } + return +} + +/* +De-serializes the user budget for the storage. Used for key-value storages. +*/ +func (ub *UserBudget) restore(input string) { + elements := strings.Split(input, ";") + ub.Credit, _ = strconv.ParseFloat(elements[0], 64) + ub.SmsCredit, _ = strconv.ParseFloat(elements[1], 64) + ub.Traffic, _ = strconv.ParseFloat(elements[2], 64) + ub.VolumeDiscountSeconds, _ = strconv.ParseFloat(elements[3], 64) + ub.ReceivedCallSeconds, _ = strconv.ParseFloat(elements[4], 64) + ub.ResetDayOfTheMonth, _ = strconv.Atoi(elements[5]) + ub.TariffPlanId = elements[6] + if len(elements) > 7 { + for _, mbs := range strings.Split(elements[7], ",") { + mb := &MinuteBucket{} + mb.restore(mbs) + ub.MinuteBuckets = append(ub.MinuteBuckets, mb) + } + } +} + /* Returns the tariff plan loading it from the storage if necessary. */