diff --git a/cmd/cgr-scheduler/cgr-scheduler.go b/cmd/cgr-scheduler/cgr-scheduler.go
index c0c4d529d..a9dbbcdde 100644
--- a/cmd/cgr-scheduler/cgr-scheduler.go
+++ b/cmd/cgr-scheduler/cgr-scheduler.go
@@ -22,6 +22,8 @@ import (
"log"
"github.com/cgrates/cgrates/timespans"
"flag"
+ "time"
+ "sort"
)
var (
@@ -31,6 +33,43 @@ var (
redispass = flag.String("pass", "", "redis database password")
)
+/*
+Structure to store action timings according to next activation time.
+*/
+type actiontimingqueue []*timespans.ActionTiming
+
+func (atq actiontimingqueue) Len() int {
+ return len(atq)
+}
+
+func (atq actiontimingqueue) Swap(i, j int) {
+ atq[i], atq[j] = atq[j], atq[i]
+}
+
+func (atq actiontimingqueue) Less(j, i int) bool {
+ return atq[j].GetNextStartTime().Before(atq[i].GetNextStartTime())
+}
+
+type scheduler struct {
+ queue actiontimingqueue
+}
+
+func (s scheduler) loop() {
+ for {
+ a0 := s.queue[0]
+ now := time.Now()
+ if a0.GetNextStartTime().Equal(now) || a0.GetNextStartTime().Before(now) {
+ a0.Execute()
+ s.queue = append(s.queue, a0)
+ s.queue = s.queue[1:]
+ sort.Sort(s.queue)
+ } else {
+ d := a0.GetNextStartTime().Sub(now)
+ time.Sleep(d)
+ }
+ }
+}
+
func main() {
flag.Parse()
storage, err := timespans.NewRedisStorage(*redisserver, *redisdb)
@@ -41,7 +80,7 @@ func main() {
if err != nil {
log.Fatalf("Cannot get action timings:", err)
}
- for _, at := range actionTimings {
- log.Print(at)
- }
+ s := scheduler{}
+ s.queue = append(s.queue, actionTimings...)
+ s.loop()
}
diff --git a/timespans/actions.go b/timespans/actions.go
index da6de89f0..3eff6f3cb 100644
--- a/timespans/actions.go
+++ b/timespans/actions.go
@@ -18,7 +18,16 @@ along with this program. If not, see
package timespans
-import ()
+import (
+ "time"
+ "log"
+ "fmt"
+ "sort"
+)
+
+const (
+ FORMAT = "2006-1-2 15:04:05 MST"
+)
// Amount of a trafic of a certain type (TOR)
type UnitsCounter struct {
@@ -95,3 +104,86 @@ type ActionTiming struct {
ActionsId string
actions []*Action
}
+
+func (at *ActionTiming) getActions() (a []*Action, err error) {
+ if at.actions == nil {
+ a, err = storageGetter.GetActions(at.ActionsId)
+ at.actions = a
+ }
+ return
+}
+
+func (at *ActionTiming) GetNextStartTime() (t time.Time, err error) {
+ i := at.Timing
+ if i == nil {
+ return
+ }
+ now := time.Now()
+ y, m, d := now.Date()
+ z, _ := now.Zone()
+ if i.StartTime != "" {
+ l := fmt.Sprintf("%d-%d-%d %s %s", y, m, d, i.StartTime, z)
+ t, err = time.Parse(FORMAT, l)
+ }
+
+ if i.WeekDays != nil && len(i.WeekDays) > 0 {
+ sort.Sort(i.WeekDays)
+ }
+
+ if i.MonthDays != nil && len(i.MonthDays) > 0 {
+ sort.Sort(i.MonthDays)
+ now := time.Now()
+ x := sort.SearchInts(i.MonthDays, now.Day())
+ d = i.MonthDays[0]
+ if x < len(i.MonthDays) {
+ if i.MonthDays[x] == now.Day() {
+ if now.Before(t) {
+ h, m, s := t.Clock()
+ t = time.Date(now.Year(), now.Month(), now.Day(), h, m, s, 0, time.Local)
+ return
+ }
+ if x+1 < len(i.MonthDays) { // today was found in the list, jump to the next grater day
+ d = i.MonthDays[x+1]
+ }
+ } else { // today was not found in the list, x is the first greater day
+ d = i.MonthDays[x]
+ }
+ }
+ h, m, s := t.Clock()
+ t = time.Date(now.Year(), now.Month(), d, h, m, s, 0, time.Local)
+ }
+
+ if i.Months != nil && len(i.Months) > 0 {
+ sort.Sort(i.Months)
+ now := time.Now()
+ x := sort.Search(len(i.Months), func(x int) bool { return i.Months[x] >= now.Month() })
+ m = i.Months[0]
+ if x < len(i.Months) {
+ if i.Months[x] == now.Month() {
+ if now.Before(t) {
+ h, m, s := t.Clock()
+ t = time.Date(now.Year(), now.Month(), t.Day(), h, m, s, 0, time.Local)
+ return
+ }
+ if x+1 < len(i.Months) { // today was found in the list, jump to the next grater day
+ m = i.Months[x+1]
+ }
+ } else { // today was not found in the list, x is the first greater day
+ m = i.Months[x]
+ }
+ }
+ h, min, s := t.Clock()
+ t = time.Date(now.Year(), m, t.Day(), h, min, s, 0, time.Local)
+ }
+ return
+}
+
+func (at *ActionTiming) Execute() {
+ aac, err := at.getActions()
+ if err != nil {
+ return
+ }
+ for _, a := range aac {
+ log.Print(a)
+ }
+}
diff --git a/timespans/actions_test.go b/timespans/actions_test.go
new file mode 100644
index 000000000..e0cfc3c5e
--- /dev/null
+++ b/timespans/actions_test.go
@@ -0,0 +1,177 @@
+/*
+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 (
+ "testing"
+ "time"
+)
+
+func TestActionTimingNothing(t *testing.T) {
+ at := &ActionTiming{}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ expected := time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}
+
+func TestActionTimingOnlyHour(t *testing.T) {
+ at := &ActionTiming{Timing: &Interval{StartTime: "10:01:00"}}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ now := time.Now()
+ y, m, d := now.Date()
+ expected := time.Date(y, m, d, 10, 1, 0, 0, time.Local)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}
+
+/*func TestActionTimingOnlyWeekdays(t *testing.T) {
+ at := &ActionTiming{Timing: &Interval{WeekDays: []time.Weekday{time.Monday}}}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ now := time.Now()
+ y, m, d := now.Date()
+ expected := time.Date(y, m, d, 10, 1, 0, 0, time.Local)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}
+
+func TestActionTimingWeekdaysHour(t *testing.T) {
+ at := &ActionTiming{Timing: &Interval{WeekDays: []time.Weekday{time.Monday}, StartTime: "10:01:00"}}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ now := time.Now()
+ y, m, d := now.Date()
+ expected := time.Date(y, m, d, 10, 1, 0, 0, time.Local)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}*/
+
+func TestActionTimingOnlyMonthdays(t *testing.T) {
+ now := time.Now()
+ y, m, d := now.Date()
+ tomorrow := time.Date(y, m, d+1, 0, 0, 0, 0, time.Local)
+ at := &ActionTiming{Timing: &Interval{MonthDays: MonthDays{1, 25, 2, tomorrow.Day()}}}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ expected := time.Date(y, m, tomorrow.Day(), 0, 0, 0, 0, time.Local)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}
+
+func TestActionTimingHourMonthdays(t *testing.T) {
+ now := time.Now()
+ y, m, d := now.Date()
+ testTime := time.Date(y, m, d, 10, 1, 0, 0, time.Local)
+ tomorrow := time.Date(y, m, d+1, 0, 0, 0, 0, time.Local)
+ day := now.Day()
+ if now.After(testTime) {
+ day = tomorrow.Day()
+ }
+ at := &ActionTiming{Timing: &Interval{MonthDays: MonthDays{now.Day(), tomorrow.Day()}, StartTime: "10:01:00"}}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ expected := time.Date(y, m, day, 10, 1, 0, 0, time.Local)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}
+
+func TestActionTimingOnlyMonths(t *testing.T) {
+ now := time.Now()
+ y, m, d := now.Date()
+ nextMonth := time.Date(y, m+1, d, 0, 0, 0, 0, time.Local)
+ at := &ActionTiming{Timing: &Interval{Months: Months{time.February, time.May, nextMonth.Month()}}}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ expected := time.Date(y, nextMonth.Month(), 1, 0, 0, 0, 0, time.Local)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}
+
+func TestActionTimingHourMonths(t *testing.T) {
+ now := time.Now()
+ y, m, d := now.Date()
+ testTime := time.Date(y, m, d, 10, 1, 0, 0, time.Local)
+ nextMonth := time.Date(y, m+1, d, 0, 0, 0, 0, time.Local)
+ month := now.Month()
+ if now.After(testTime) {
+ month = nextMonth.Month()
+ }
+ at := &ActionTiming{Timing: &Interval{Months: Months{now.Month(), nextMonth.Month()}, StartTime: "10:01:00"}}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ expected := time.Date(y, month, d, 10, 1, 0, 0, time.Local)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}
+
+func TestActionTimingHourMonthdaysMonths(t *testing.T) {
+ now := time.Now()
+ y, m, d := now.Date()
+ testTime := time.Date(y, m, d, 10, 1, 0, 0, time.Local)
+ nextMonth := time.Date(y, m+1, d, 0, 0, 0, 0, time.Local)
+ tomorrow := time.Date(y, m, d+1, 0, 0, 0, 0, time.Local)
+ day := now.Day()
+ if now.After(testTime) {
+ day = tomorrow.Day()
+ }
+ month := now.Month()
+ if now.After(testTime) {
+ month = nextMonth.Month()
+ }
+ at := &ActionTiming{Timing: &Interval{
+ Months: Months{now.Month(), nextMonth.Month()},
+ MonthDays: MonthDays{now.Day(), tomorrow.Day()},
+ StartTime: "10:01:00",
+ }}
+ st, err := at.GetNextStartTime()
+ if err != nil {
+ t.Error(err)
+ }
+ expected := time.Date(y, month, day, 10, 1, 0, 0, time.Local)
+ if !st.Equal(expected) {
+ t.Errorf("Expected %v was %v", expected, st)
+ }
+}
diff --git a/timespans/dateseries.go b/timespans/dateseries.go
index 1ebe0703d..7c0d2d850 100644
--- a/timespans/dateseries.go
+++ b/timespans/dateseries.go
@@ -28,9 +28,20 @@ import (
// Defines months series
type Months []time.Month
+func (m Months) Len() int {
+ return len(m)
+}
+
+func (m Months) Swap(i, j int) {
+ m[i], m[j] = m[j], m[i]
+}
+
+func (m Months) Less(j, i int) bool {
+ return m[j] < m[i]
+}
+
// Return true if the specified date is inside the series
func (m Months) Contains(month time.Month) (result bool) {
- result = false
for _, ms := range m {
if ms == month {
result = true
@@ -61,6 +72,18 @@ func (m *Months) Parse(input, sep string) {
// Defines month days series
type MonthDays []int
+func (md MonthDays) Len() int {
+ return len(md)
+}
+
+func (md MonthDays) Swap(i, j int) {
+ md[i], md[j] = md[j], md[i]
+}
+
+func (md MonthDays) Less(j, i int) bool {
+ return md[j] < md[i]
+}
+
// Return true if the specified date is inside the series
func (md MonthDays) Contains(monthDay int) (result bool) {
result = false
@@ -93,6 +116,18 @@ func (md *MonthDays) Parse(input, sep string) {
// Defines week days series
type WeekDays []time.Weekday
+func (wd WeekDays) Len() int {
+ return len(wd)
+}
+
+func (wd WeekDays) Swap(i, j int) {
+ wd[i], wd[j] = wd[j], wd[i]
+}
+
+func (wd WeekDays) Less(j, i int) bool {
+ return wd[j] < wd[i]
+}
+
// Return true if the specified date is inside the series
func (wd WeekDays) Contains(weekDay time.Weekday) (result bool) {
result = false
diff --git a/timespans/storage_redis.go b/timespans/storage_redis.go
index 9a1b42e62..4690adb0c 100644
--- a/timespans/storage_redis.go
+++ b/timespans/storage_redis.go
@@ -20,7 +20,7 @@ package timespans
import (
"github.com/simonz05/godis"
- "encoding/json"
+ "encoding/json"
)
const (
@@ -134,7 +134,7 @@ func (rs *RedisStorage) GetAllActionTimings() (ats []*ActionTiming, err error) {
return
}
for _, v := range values.BytesArray() {
- var tempAts []*ActionTiming
+ var tempAts []*ActionTiming
err = json.Unmarshal(v, &tempAts)
ats = append(ats, tempAts...)
}
diff --git a/timespans/userbalance.go b/timespans/userbalance.go
index 06f42649a..c8ea605c6 100644
--- a/timespans/userbalance.go
+++ b/timespans/userbalance.go
@@ -47,18 +47,19 @@ var (
Structure containing information about user's credit (minutes, cents, sms...).'
*/
type UserBalance struct {
- Id string
- Type string // prepaid-postpaid
- BalanceMap map[string]float64
- MinuteBuckets []*MinuteBucket
- UnitsCounters []*UnitsCounter
- ActionTriggers []*ActionTrigger
+ Id string
+ Type string // prepaid-postpaid
+ BalanceMap map[string]float64
+ MinuteBuckets []*MinuteBucket
+ UnitsCounters []*UnitsCounter
+ ActionTriggers []*ActionTrigger
+ usedActionTriggers []*ActionTrigger
}
/*
Error type for overflowed debit methods.
*/
-type AmountTooBig byte
+type AmountTooBig struct{}
func (a AmountTooBig) Error() string {
return "Amount excedes balance!"
@@ -83,6 +84,11 @@ func (bs bucketsorter) Less(j, i int) bool {
bs[i].Price > bs[j].Price
}
+func (ub *UserBalance) ResetActionTriggers() {
+ ub.ActionTriggers = append(ub.ActionTriggers, ub.usedActionTriggers...)
+ ub.usedActionTriggers = make([]*ActionTrigger, 0)
+}
+
/*
Returns user's available minutes for the specified destination
*/