133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
|
package fs
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
// BwTimeSlot represents a bandwidth configuration at a point in time.
|
||
|
type BwTimeSlot struct {
|
||
|
HHMM int
|
||
|
Bandwidth SizeSuffix
|
||
|
}
|
||
|
|
||
|
// BwTimetable contains all configured time slots.
|
||
|
type BwTimetable []BwTimeSlot
|
||
|
|
||
|
// String returns a printable representation of BwTimetable.
|
||
|
func (x BwTimetable) String() string {
|
||
|
ret := []string{}
|
||
|
for _, ts := range x {
|
||
|
ret = append(ret, fmt.Sprintf("%04.4d,%s", ts.HHMM, ts.Bandwidth.String()))
|
||
|
}
|
||
|
return strings.Join(ret, " ")
|
||
|
}
|
||
|
|
||
|
// Set the bandwidth timetable.
|
||
|
func (x *BwTimetable) Set(s string) error {
|
||
|
// The timetable is formatted as:
|
||
|
// "hh:mm,bandwidth hh:mm,banwidth..." ex: "10:00,10G 11:30,1G 18:00,off"
|
||
|
// If only a single bandwidth identifier is provided, we assume constant bandwidth.
|
||
|
|
||
|
if len(s) == 0 {
|
||
|
return errors.New("empty string")
|
||
|
}
|
||
|
// Single value without time specification.
|
||
|
if !strings.Contains(s, " ") && !strings.Contains(s, ",") {
|
||
|
ts := BwTimeSlot{}
|
||
|
if err := ts.Bandwidth.Set(s); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
ts.HHMM = 0
|
||
|
*x = BwTimetable{ts}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
for _, tok := range strings.Split(s, " ") {
|
||
|
tv := strings.Split(tok, ",")
|
||
|
|
||
|
// Format must be HH:MM,BW
|
||
|
if len(tv) != 2 {
|
||
|
return errors.Errorf("invalid time/bandwidth specification: %q", tok)
|
||
|
}
|
||
|
|
||
|
// Basic timespec sanity checking
|
||
|
HHMM := tv[0]
|
||
|
if len(HHMM) != 5 {
|
||
|
return errors.Errorf("invalid time specification (hh:mm): %q", HHMM)
|
||
|
}
|
||
|
hh, err := strconv.Atoi(HHMM[0:2])
|
||
|
if err != nil {
|
||
|
return errors.Errorf("invalid hour in time specification %q: %v", HHMM, err)
|
||
|
}
|
||
|
if hh < 0 || hh > 23 {
|
||
|
return errors.Errorf("invalid hour (must be between 00 and 23): %q", hh)
|
||
|
}
|
||
|
mm, err := strconv.Atoi(HHMM[3:])
|
||
|
if err != nil {
|
||
|
return errors.Errorf("invalid minute in time specification: %q: %v", HHMM, err)
|
||
|
}
|
||
|
if mm < 0 || mm > 59 {
|
||
|
return errors.Errorf("invalid minute (must be between 00 and 59): %q", hh)
|
||
|
}
|
||
|
|
||
|
ts := BwTimeSlot{
|
||
|
HHMM: (hh * 100) + mm,
|
||
|
}
|
||
|
// Bandwidth limit for this time slot.
|
||
|
if err := ts.Bandwidth.Set(tv[1]); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
*x = append(*x, ts)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// LimitAt returns a BwTimeSlot for the time requested.
|
||
|
func (x BwTimetable) LimitAt(tt time.Time) BwTimeSlot {
|
||
|
// If the timetable is empty, we return an unlimited BwTimeSlot starting at midnight.
|
||
|
if len(x) == 0 {
|
||
|
return BwTimeSlot{HHMM: 0, Bandwidth: -1}
|
||
|
}
|
||
|
|
||
|
HHMM := tt.Hour()*100 + tt.Minute()
|
||
|
|
||
|
// By default, we return the last element in the timetable. This
|
||
|
// satisfies two conditions: 1) If there's only one element it
|
||
|
// will always be selected, and 2) The last element of the table
|
||
|
// will "wrap around" until overriden by an earlier time slot.
|
||
|
// there's only one time slot in the timetable.
|
||
|
ret := x[len(x)-1]
|
||
|
|
||
|
mindif := 0
|
||
|
first := true
|
||
|
|
||
|
// Look for most recent time slot.
|
||
|
for _, ts := range x {
|
||
|
// Ignore the past
|
||
|
if HHMM < ts.HHMM {
|
||
|
continue
|
||
|
}
|
||
|
dif := ((HHMM / 100 * 60) + (HHMM % 100)) - ((ts.HHMM / 100 * 60) + (ts.HHMM % 100))
|
||
|
if first {
|
||
|
mindif = dif
|
||
|
first = false
|
||
|
}
|
||
|
if dif <= mindif {
|
||
|
mindif = dif
|
||
|
ret = ts
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
// Type of the value
|
||
|
func (x BwTimetable) Type() string {
|
||
|
return "BwTimetable"
|
||
|
}
|