diff --git a/timespans/activationperiod.go b/timespans/activationperiod.go index 6e356df61..7cbaed80a 100644 --- a/timespans/activationperiod.go +++ b/timespans/activationperiod.go @@ -28,7 +28,7 @@ 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) + ";" + result += strconv.FormatInt(ap.ActivationTime.UnixNano(), 10) + ";" for _, i := range ap.Intervals { var is string is = strconv.Itoa(int(i.Month)) + "|" diff --git a/timespans/destinations.go b/timespans/destinations.go index 86c4fb11d..f929407ed 100644 --- a/timespans/destinations.go +++ b/timespans/destinations.go @@ -4,15 +4,17 @@ import ( "strings" ) +/* +Structure that gathers multiple destination prefixes under a common id. +*/ type Destination struct { Id string Prefixes []string } -func (d *Destination) GetKey() (result string) { - return d.Id -} - +/* +Serializes the destination for the storage. Used for key-value storages. +*/ func (d *Destination) store() (result string) { for _, p := range d.Prefixes { result += p + "," @@ -26,7 +28,7 @@ func (d *Destination) restore(input string) { } /* -Returns true if the bucket contains specified prefix. +De-serializes the destination for the storage. Used for key-value storages. */ func (d *Destination) containsPrefix(prefix string) bool { for _, p := range d.Prefixes { diff --git a/timespans/minute_buckets.go b/timespans/minute_buckets.go index 55a22aac4..8e20e2dec 100644 --- a/timespans/minute_buckets.go +++ b/timespans/minute_buckets.go @@ -1,16 +1,19 @@ package timespans type MinuteBucket struct { - Seconds int - Priority int - Price float64 + Seconds int + Priority int + Price float64 DestinationId string - destination *Destination + destination *Destination } +/* +Returns the destination loading it from the storage if necessary. +*/ func (mb *MinuteBucket) getDestination(storage StorageGetter) (dest *Destination) { if mb.destination == nil { - mb.destination,_ = storage.GetDestination(mb.DestinationId) + mb.destination, _ = storage.GetDestination(mb.DestinationId) } return mb.destination } diff --git a/timespans/tariff_plans.go b/timespans/tariff_plans.go index ce3221e55..1eb9f84fe 100644 --- a/timespans/tariff_plans.go +++ b/timespans/tariff_plans.go @@ -11,12 +11,12 @@ these numbers to the user budget every month. */ type TariffPlan struct { Id string - SmsCredit int + SmsCredit int MinuteBuckets []*MinuteBucket } /* -Serializes the activation periods for the storage. Used for key-value storages. +Serializes the tariff plan for the storage. Used for key-value storages. */ func (tp *TariffPlan) store() (result string) { result += strconv.Itoa(tp.SmsCredit) + ";" @@ -32,17 +32,17 @@ func (tp *TariffPlan) store() (result string) { } /* -De-serializes the activation periods for the storage. Used for key-value storages. +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.Atoi(elements[0]) + tp.SmsCredit, _ = strconv.Atoi(elements[0]) for _, mbs := range elements[1 : len(elements)-1] { mb := &MinuteBucket{} mbse := strings.Split(mbs, "|") - mb.Seconds,_ = strconv.Atoi(mbse[0]) - mb.Priority,_ = strconv.Atoi(mbse[1]) - mb.Price,_ = strconv.ParseFloat(mbse[2], 64) + mb.Seconds, _ = strconv.Atoi(mbse[0]) + mb.Priority, _ = strconv.Atoi(mbse[1]) + mb.Price, _ = strconv.ParseFloat(mbse[2], 64) mb.DestinationId = mbse[3] tp.MinuteBuckets = append(tp.MinuteBuckets, mb) diff --git a/timespans/tariff_plans_test.go b/timespans/tariff_plans_test.go index 29113cdff..71db69ce0 100644 --- a/timespans/tariff_plans_test.go +++ b/timespans/tariff_plans_test.go @@ -7,7 +7,7 @@ import ( func TestTariffPlanStoreRestore(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} - seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} s := seara.store() tp1 := &TariffPlan{Id: "seara"} tp1.restore(s) @@ -21,7 +21,7 @@ func TestTariffPlanKyotoStore(t *testing.T) { defer getter.Close() b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} - seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} getter.SetTariffPlan(seara) result, _ := getter.GetTariffPlan(seara.Id) if result.SmsCredit != seara.SmsCredit || len(result.MinuteBuckets) != len(seara.MinuteBuckets) { @@ -34,7 +34,7 @@ func TestTariffPlanRedisStore(t *testing.T) { defer getter.Close() b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} - seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} getter.SetTariffPlan(seara) result, _ := getter.GetTariffPlan(seara.Id) if result.SmsCredit != seara.SmsCredit || len(result.MinuteBuckets) != len(seara.MinuteBuckets) { @@ -47,7 +47,7 @@ func TestTariffPlanMongoStore(t *testing.T) { defer getter.Close() b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} - seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} getter.SetTariffPlan(seara) result, _ := getter.GetTariffPlan(seara.Id) if result.SmsCredit != seara.SmsCredit || len(result.MinuteBuckets) != len(seara.MinuteBuckets) { @@ -62,7 +62,7 @@ func BenchmarkTariffPlanKyotoStoreRestore(b *testing.B) { defer getter.Close() b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} - seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} for i := 0; i < b.N; i++ { getter.SetTariffPlan(seara) getter.GetTariffPlan(seara.Id) @@ -74,7 +74,7 @@ func BenchmarkTariffPlanRedisStoreRestore(b *testing.B) { defer getter.Close() b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} - seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} for i := 0; i < b.N; i++ { getter.SetTariffPlan(seara) getter.GetTariffPlan(seara.Id) @@ -86,7 +86,7 @@ func BenchmarkTariffPlanMongoStoreRestore(b *testing.B) { defer getter.Close() b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} - seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} for i := 0; i < b.N; i++ { getter.SetTariffPlan(seara) getter.GetTariffPlan(seara.Id) diff --git a/timespans/userbudget.go b/timespans/userbudget.go index b88782d09..69f8a5b81 100644 --- a/timespans/userbudget.go +++ b/timespans/userbudget.go @@ -3,31 +3,85 @@ package timespans import ( "log" "math" + "strconv" + "strings" ) /* Structure conatining information about user's credit (minutes, cents, sms...).' */ type UserBudget struct { - id string - minuteBuckets []*MinuteBucket - credit float64 - smsCredit int + Id string + Credit float64 + SmsCredit int + ResetDayOfTheMonth int + TariffPlanId string tariffPlan *TariffPlan - resetDayOfTheMonth int + MinuteBuckets []*MinuteBucket +} + +/* +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.Itoa(ub.SmsCredit) + ";" + result += strconv.Itoa(ub.ResetDayOfTheMonth) + ";" + result += ub.TariffPlanId + ";" + for _, mb := range ub.MinuteBuckets { + var mbs string + mbs += strconv.Itoa(int(mb.Seconds)) + "|" + mbs += strconv.Itoa(int(mb.Priority)) + "|" + mbs += strconv.FormatFloat(mb.Price, 'f', -1, 64) + "|" + mbs += mb.DestinationId + result += mbs + ";" + } + 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.Atoi(elements[1]) + ub.ResetDayOfTheMonth, _ = strconv.Atoi(elements[2]) + ub.TariffPlanId = elements[3] + for _, mbs := range elements[4 : len(elements)-1] { + mb := &MinuteBucket{} + mbse := strings.Split(mbs, "|") + mb.Seconds, _ = strconv.Atoi(mbse[0]) + mb.Priority, _ = strconv.Atoi(mbse[1]) + mb.Price, _ = strconv.ParseFloat(mbse[2], 64) + mb.DestinationId = mbse[3] + + ub.MinuteBuckets = append(ub.MinuteBuckets, mb) + } +} + + +/* +Returns the tariff plan loading it from the storage if necessary. +*/ +func (ub *UserBudget) getTariffPlan(storage StorageGetter) (tp *TariffPlan) { + if ub.tariffPlan == nil { + ub.tariffPlan, _ = storage.GetTariffPlan(ub.TariffPlanId) + } + return ub.tariffPlan } /* Returns user's avaliable minutes for the specified destination */ func (ub *UserBudget) GetSecondsForPrefix(storage StorageGetter, prefix string) (seconds int) { - if len(ub.minuteBuckets) == 0 { - log.Print("There are no minute buckets to check for user", ub.id) + if len(ub.MinuteBuckets) == 0 { + log.Print("There are no minute buckets to check for user", ub.Id) return } - bestBucket := ub.minuteBuckets[0] + bestBucket := ub.MinuteBuckets[0] - for _, mb := range ub.minuteBuckets { + for _, mb := range ub.MinuteBuckets { d := mb.getDestination(storage) if d.containsPrefix(prefix) && mb.Priority > bestBucket.Priority { bestBucket = mb @@ -35,7 +89,7 @@ func (ub *UserBudget) GetSecondsForPrefix(storage StorageGetter, prefix string) } seconds = bestBucket.Seconds if bestBucket.Price > 0 { - seconds = int(math.Min(ub.credit/bestBucket.Price, float64(seconds))) + seconds = int(math.Min(ub.Credit/bestBucket.Price, float64(seconds))) } return } diff --git a/timespans/userbudget_test.go b/timespans/userbudget_test.go index 9c87507f8..1d4da5e74 100644 --- a/timespans/userbudget_test.go +++ b/timespans/userbudget_test.go @@ -2,6 +2,7 @@ package timespans import ( "testing" + //"log" ) var ( @@ -14,7 +15,7 @@ func TestGetSeconds(t *testing.T) { b2 := &MinuteBucket{Seconds: 100, Priority: 20, destination: retea} tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} - ub1 := &UserBudget{id: "rif", minuteBuckets: []*MinuteBucket{b1, b2}, credit: 200, tariffPlan: tf1, resetDayOfTheMonth: 10} + ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10} seconds := ub1.GetSecondsForPrefix(nil, "0723") expected := 100 if seconds != expected { @@ -27,7 +28,7 @@ func TestGetPricedSeconds(t *testing.T) { b2 := &MinuteBucket{Seconds: 100, Price: 1, Priority: 20, destination: retea} tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} - ub1 := &UserBudget{id: "rif", minuteBuckets: []*MinuteBucket{b1, b2}, credit: 21, tariffPlan: tf1, resetDayOfTheMonth: 10} + ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10} seconds := ub1.GetSecondsForPrefix(nil, "0723") expected := 21 if seconds != expected { @@ -35,6 +36,20 @@ func TestGetPricedSeconds(t *testing.T) { } } +func TestUserBudgetStoreRestore(t *testing.T) { + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} + rifsBudget := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} + s := rifsBudget.store() + ub1 := &UserBudget{Id: "rif"} + ub1.restore(s) + if ub1.store() != s { + t.Errorf("Expected %q was %q", s, ub1.store()) + } +} + + /*********************************** Benchmarks *******************************/ func BenchmarkGetSecondForPrefix(b *testing.B) { @@ -43,9 +58,9 @@ func BenchmarkGetSecondForPrefix(b *testing.B) { b2 := &MinuteBucket{Seconds: 100, Price: 1, Priority: 20, destination: retea} tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} - ub1 := &UserBudget{id: "rif", minuteBuckets: []*MinuteBucket{b1, b2}, credit: 21, tariffPlan: tf1, resetDayOfTheMonth: 10} + ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10} b.StartTimer() for i := 0; i < b.N; i++ { - ub1.GetSecondsForPrefix(nil,"0723") + ub1.GetSecondsForPrefix(nil, "0723") } }