Files
cgrates/utils/coreutils.go
2015-01-20 18:16:02 +02:00

362 lines
9.0 KiB
Go

/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can Storagetribute 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 WITH*out 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 <http://www.gnu.org/licenses/>
*/
package utils
import (
"archive/zip"
"bytes"
"crypto/rand"
"crypto/sha1"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"math"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
// Returns first non empty string out of vals. Useful to extract defaults
func FirstNonEmpty(vals ...string) string {
for _, val := range vals {
if len(val) != 0 {
return val
}
}
return ""
}
func Sha1(attrs ...string) string {
hasher := sha1.New()
for _, attr := range attrs {
hasher.Write([]byte(attr))
}
return fmt.Sprintf("%x", hasher.Sum(nil))
}
func NewTPid() string {
return Sha1(GenUUID())
}
/*func GenUUID() string {
uuid := make([]byte, 16)
n, err := rand.Read(uuid)
if n != len(uuid) || err != nil {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}
// TODO: verify the two lines implement RFC 4122 correctly
uuid[8] = 0x80 // variant bits see page 5
uuid[4] = 0x40 // version 4 Pseudo Random, see page 7
return hex.EncodeToString(uuid)
}*/
// helper function for uuid generation
func GenUUID() string {
b := make([]byte, 16)
_, err := io.ReadFull(rand.Reader, b)
if err != nil {
log.Fatal(err)
}
b[6] = (b[6] & 0x0F) | 0x40
b[8] = (b[8] &^ 0x40) | 0x80
return fmt.Sprintf("%x-%x-%x-%x-%x", b[:4], b[4:6], b[6:8], b[8:10],
b[10:])
}
// Round return rounded version of x with prec precision.
//
// Special cases are:
// Round(±0) = ±0
// Round(±Inf) = ±Inf
// Round(NaN) = NaN
func Round(x float64, prec int, method string) float64 {
var rounder float64
maxPrec := 7 // define a max precison to cut float errors
if maxPrec < prec {
maxPrec = prec
}
pow := math.Pow(10, float64(prec))
intermed := x * pow
_, frac := math.Modf(intermed)
switch method {
case ROUNDING_UP:
if frac >= math.Pow10(-maxPrec) { // Max precision we go, rest is float chaos
rounder = math.Ceil(intermed)
} else {
rounder = math.Floor(intermed)
}
case ROUNDING_DOWN:
rounder = math.Floor(intermed)
case ROUNDING_MIDDLE:
if frac >= 0.5 {
rounder = math.Ceil(intermed)
} else {
rounder = math.Floor(intermed)
}
default:
rounder = intermed
}
return rounder / pow
}
func ParseTimeDetectLayout(tmStr string) (time.Time, error) {
var nilTime time.Time
rfc3339Rule := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.+$`)
sqlRule := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$`)
gotimeRule := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.?\d*\s[+,-]\d+\s\w+$`)
fsTimestamp := regexp.MustCompile(`^\d{16}$`)
unixTimestampRule := regexp.MustCompile(`^\d{10}$`)
oneLineTimestampRule := regexp.MustCompile(`^\d{14}$`)
oneSpaceTimestampRule := regexp.MustCompile(`^\d{2}\.\d{2}.\d{4}\s{1}\d{2}:\d{2}:\d{2}$`)
switch {
case rfc3339Rule.MatchString(tmStr):
return time.Parse(time.RFC3339, tmStr)
case gotimeRule.MatchString(tmStr):
return time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", tmStr)
case sqlRule.MatchString(tmStr):
return time.Parse("2006-01-02 15:04:05", tmStr)
case fsTimestamp.MatchString(tmStr):
if tmstmp, err := strconv.ParseInt(tmStr+"000", 10, 64); err != nil {
return nilTime, err
} else {
return time.Unix(0, tmstmp), nil
}
case unixTimestampRule.MatchString(tmStr):
if tmstmp, err := strconv.ParseInt(tmStr, 10, 64); err != nil {
return nilTime, err
} else {
return time.Unix(tmstmp, 0), nil
}
case tmStr == "0" || len(tmStr) == 0: // Time probably missing from request
return nilTime, nil
case oneLineTimestampRule.MatchString(tmStr):
return time.Parse("20060102150405", tmStr)
case oneSpaceTimestampRule.MatchString(tmStr):
return time.Parse("02.01.2006 15:04:05", tmStr)
}
return nilTime, errors.New("Unsupported time format")
}
func ParseDate(date string) (expDate time.Time, err error) {
date = strings.TrimSpace(date)
switch {
case date == "*unlimited" || date == "":
// leave it at zero
case strings.HasPrefix(date, "+"):
d, err := time.ParseDuration(date[1:])
if err != nil {
return expDate, err
}
expDate = time.Now().Add(d)
case date == "*daily":
expDate = time.Now().AddDate(0, 0, 1) // add one day
case date == "*monthly":
expDate = time.Now().AddDate(0, 1, 0) // add one month
case date == "*yearly":
expDate = time.Now().AddDate(1, 0, 0) // add one year
case strings.HasSuffix(date, "Z"):
expDate, err = time.Parse(time.RFC3339, date)
default:
unix, err := strconv.ParseInt(date, 10, 64)
if err != nil {
return expDate, err
}
expDate = time.Unix(unix, 0)
}
return expDate, err
}
// returns a number equeal or larger than the amount that exactly
// is divisible to whole
func RoundDuration(whole, amount time.Duration) time.Duration {
a, w := float64(amount), float64(whole)
if math.Mod(a, w) == 0 {
return amount
}
return time.Duration((w - math.Mod(a, w)) + a)
}
func SplitPrefix(prefix string, minLength int) []string {
length := int(math.Max(float64(len(prefix)-(minLength-1)), 0))
subs := make([]string, length)
max := len(prefix)
for i := 0; i < length; i++ {
subs[i] = prefix[:max-i]
}
return subs
}
func CopyHour(src, dest time.Time) time.Time {
if src.Hour() == 0 && src.Minute() == 0 && src.Second() == 0 {
return src
}
return time.Date(dest.Year(), dest.Month(), dest.Day(), src.Hour(), src.Minute(), src.Second(), src.Nanosecond(), src.Location())
}
// Parses duration, considers s as time unit if not provided, seconds as float to specify subunits
func ParseDurationWithSecs(durStr string) (time.Duration, error) {
if durSecs, err := strconv.ParseFloat(durStr, 64); err == nil { // Seconds format considered
durNanosecs := int(durSecs * NANO_MULTIPLIER)
return time.Duration(durNanosecs), nil
} else {
return time.ParseDuration(durStr)
}
}
func AccountKey(tenant, account, direction string) string {
return fmt.Sprintf("%s:%s:%s", direction, tenant, account)
}
// returns the minimum duration between the two
func MinDuration(d1, d2 time.Duration) time.Duration {
if d1 < d2 {
return d1
}
return d2
}
func ParseZeroRatingSubject(rateSubj string) (time.Duration, error) {
if rateSubj == "" {
rateSubj = ZERO_RATING_SUBJECT_PREFIX + "1s"
}
if !strings.HasPrefix(rateSubj, ZERO_RATING_SUBJECT_PREFIX) {
return 0, errors.New("malformed rating subject: " + rateSubj)
}
durStr := rateSubj[len(ZERO_RATING_SUBJECT_PREFIX):]
return time.ParseDuration(durStr)
}
func ConcatenatedKey(keyVals ...string) string {
resKey := ""
for idx, key := range keyVals {
if idx != 0 {
resKey += CONCATENATED_KEY_SEP
}
resKey += key
}
return resKey
}
func RatingSubjectAliasKey(tenant, subject string) string {
return ConcatenatedKey(tenant, subject)
}
func AccountAliasKey(tenant, account string) string {
return ConcatenatedKey(tenant, account)
}
func HttpJsonPost(url string, skipTlsVerify bool, content interface{}) ([]byte, error) {
body, err := json.Marshal(content)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerify},
}
client := &http.Client{Transport: tr}
resp, err := client.Post(url, "application/json", bytes.NewBuffer(body))
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode > 299 {
return respBody, fmt.Errorf("Unexpected status code received: %d", resp.StatusCode)
}
return respBody, nil
}
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
path := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(path, f.Mode())
} else {
f, err := os.OpenFile(
path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
}
return nil
}
// Utilities to provide pointers where we need to define ad-hoc
func StringPointer(str string) *string {
return &str
}
func IntPointer(i int) *int {
return &i
}
func Int64Pointer(i int64) *int64 {
return &i
}
func Float64Pointer(f float64) *float64 {
return &f
}
func BoolPointer(b bool) *bool {
return &b
}
func StringSlicePointer(slc []string) *[]string {
return &slc
}
func Float64SlicePointer(slc []float64) *[]float64 {
return &slc
}