mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
491 lines
18 KiB
Go
491 lines
18 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package agents
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cgrates/cgrates/config"
|
|
"github.com/cgrates/cgrates/sessions"
|
|
"github.com/cgrates/cgrates/utils"
|
|
"github.com/cgrates/fsock"
|
|
)
|
|
|
|
// ToDo: Introduce support for RSRFields
|
|
|
|
const (
|
|
varPrefix = "variable_"
|
|
// Freswitch event proprities names
|
|
SUBJECT = varPrefix + utils.CGRSubject
|
|
ACCOUNT = varPrefix + utils.CGRAccount
|
|
DESTINATION = varPrefix + utils.CGRDestination
|
|
REQTYPE = varPrefix + utils.CGRReqType //prepaid or postpaid
|
|
CATEGORY = varPrefix + utils.CGRCategory
|
|
VAR_CGR_ROUTE = varPrefix + utils.CGRRoute
|
|
UUID = "Unique-ID" // -Unique ID for this call leg
|
|
CSTMID = varPrefix + utils.CGRTenant
|
|
CALL_DEST_NR = "Caller-Destination-Number"
|
|
SIP_REQ_USER = "variable_sip_req_user"
|
|
PARK_TIME = "Caller-Profile-Created-Time"
|
|
SETUP_TIME = "Caller-Channel-Created-Time"
|
|
ANSWER_TIME = "Caller-Channel-Answered-Time"
|
|
END_TIME = "Caller-Channel-Hangup-Time"
|
|
DURATION = "variable_billsec"
|
|
NAME = "Event-Name"
|
|
HEARTBEAT = "HEARTBEAT"
|
|
ANSWER = "CHANNEL_ANSWER"
|
|
HANGUP = "CHANNEL_HANGUP_COMPLETE"
|
|
PARK = "CHANNEL_PARK"
|
|
AUTH_OK = "AUTH_OK"
|
|
DISCONNECT = "SWITCH DISCONNECT"
|
|
MANAGER_REQUEST = "MANAGER_REQUEST"
|
|
USERNAME = "Caller-Username"
|
|
FS_IPv4 = "FreeSWITCH-IPv4"
|
|
HANGUP_CAUSE = "Hangup-Cause"
|
|
PDD_MEDIA_MS = "variable_progress_mediamsec"
|
|
PDD_NOMEDIA_MS = "variable_progressmsec"
|
|
IGNOREPARK = "variable_cgr_ignorepark"
|
|
FS_VARPREFIX = "variable_"
|
|
VarCGRFlags = varPrefix + utils.CGRFlags
|
|
VarCGROpts = varPrefix + utils.CGROpts
|
|
CGRResourceAllocation = "cgr_resource_allocation"
|
|
VAR_CGR_DISCONNECT_CAUSE = varPrefix + utils.CGRDisconnectCause
|
|
VAR_CGR_CMPUTELCR = varPrefix + utils.CGRComputeLCR
|
|
FsConnID = "FsConnID" // used to share connID info in event for remote disconnects
|
|
VarAnswerEpoch = "variable_answer_epoch"
|
|
VarCGRACD = varPrefix + utils.CgrAcd
|
|
VarCGROriginHost = varPrefix + utils.CGROriginHost
|
|
VarCGRMaxUsage = "cgr_max_usage"
|
|
)
|
|
|
|
func NewFSEvent(strEv string) (fsev FSEvent) {
|
|
return fsock.FSEventStrToMap(strEv, nil)
|
|
}
|
|
|
|
// Event type holding a mapping of all event's proprieties
|
|
type FSEvent map[string]string
|
|
|
|
// Nice printing for the event object.
|
|
func (fsev FSEvent) String() (result string) {
|
|
for k, v := range fsev {
|
|
result += fmt.Sprintf("%s = %s\n", k, v)
|
|
}
|
|
result += "=============================================================="
|
|
return
|
|
}
|
|
|
|
func (fsev FSEvent) GetName() string {
|
|
return fsev[NAME]
|
|
}
|
|
|
|
// Account calling
|
|
func (fsev FSEvent) GetAccount(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[ACCOUNT], fsev[USERNAME])
|
|
}
|
|
|
|
// Rating subject being charged
|
|
func (fsev FSEvent) GetSubject(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[SUBJECT], fsev.GetAccount(fieldName))
|
|
}
|
|
|
|
// Charging destination number
|
|
func (fsev FSEvent) GetDestination(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[DESTINATION],
|
|
fsev[CALL_DEST_NR], fsev[SIP_REQ_USER])
|
|
}
|
|
|
|
// Original dialed destination number, useful in case of unpark
|
|
func (fsev FSEvent) GetCallDestNr(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[CALL_DEST_NR], fsev[SIP_REQ_USER])
|
|
}
|
|
func (fsev FSEvent) GetCategory(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[CATEGORY],
|
|
config.CgrConfig().GeneralCfg().DefaultCategory)
|
|
}
|
|
|
|
func (fsev FSEvent) GetUUID() string {
|
|
return fsev[UUID]
|
|
}
|
|
|
|
func (fsev FSEvent) GetSessionIds() []string {
|
|
return []string{fsev.GetUUID()}
|
|
}
|
|
|
|
func (fsev FSEvent) GetTenant(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[CSTMID],
|
|
config.CgrConfig().GeneralCfg().DefaultTenant)
|
|
}
|
|
|
|
func (fsev FSEvent) GetReqType(fieldName string) string {
|
|
var reqTypeDetected = "" // Used to automatically disable processing of the request
|
|
if fsev["variable_process_cdr"] == "false" { // FS will not generated CDR here
|
|
reqTypeDetected = utils.MetaNone
|
|
} else if fsev["Caller-Dialplan"] == "inline" { // Used for internally generated dialplan, eg refer coming from another box, not in our control
|
|
reqTypeDetected = utils.MetaNone
|
|
}
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[REQTYPE],
|
|
reqTypeDetected, config.CgrConfig().GeneralCfg().DefaultReqType)
|
|
}
|
|
|
|
func (fsev FSEvent) MissingParameter(timezone string) string {
|
|
if strings.TrimSpace(fsev.GetAccount(utils.MetaDefault)) == "" {
|
|
return utils.AccountField
|
|
}
|
|
if strings.TrimSpace(fsev.GetSubject(utils.MetaDefault)) == "" {
|
|
return utils.Subject
|
|
}
|
|
if strings.TrimSpace(fsev.GetDestination(utils.MetaDefault)) == "" {
|
|
return utils.Destination
|
|
}
|
|
if strings.TrimSpace(fsev.GetCategory(utils.MetaDefault)) == "" {
|
|
return utils.Category
|
|
}
|
|
if strings.TrimSpace(fsev.GetUUID()) == "" {
|
|
return utils.OriginID
|
|
}
|
|
if strings.TrimSpace(fsev.GetTenant(utils.MetaDefault)) == "" {
|
|
return utils.Tenant
|
|
}
|
|
if strings.TrimSpace(fsev.GetCallDestNr(utils.MetaDefault)) == "" {
|
|
return CALL_DEST_NR
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (fsev FSEvent) GetSetupTime(fieldName, timezone string) (t time.Time, err error) {
|
|
fsSTimeStr, hasKey := fsev[SETUP_TIME]
|
|
if hasKey && fsSTimeStr != "0" {
|
|
// Discard the nanoseconds information since MySQL cannot store them in early versions and csv uses default seconds so CGRID will not corelate
|
|
fsSTimeStr = fsSTimeStr[:len(fsSTimeStr)-6]
|
|
}
|
|
sTimeStr := utils.FirstNonEmpty(fsev[fieldName], fsSTimeStr)
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
sTimeStr = fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.ParseTimeDetectLayout(sTimeStr, timezone)
|
|
}
|
|
|
|
func (fsev FSEvent) GetAnswerTime(fieldName, timezone string) (t time.Time, err error) {
|
|
fsATimeStr, hasKey := fsev[ANSWER_TIME]
|
|
if hasKey && fsATimeStr != "0" {
|
|
// Discard the nanoseconds information since MySQL cannot store them in early versions and csv uses default seconds so CGRID will not corelate
|
|
fsATimeStr = fsATimeStr[:len(fsATimeStr)-6]
|
|
}
|
|
aTimeStr := utils.FirstNonEmpty(fsev[fieldName], fsATimeStr)
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
aTimeStr = fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.ParseTimeDetectLayout(aTimeStr, timezone)
|
|
}
|
|
|
|
func (fsev FSEvent) GetEndTime(fieldName, timezone string) (t time.Time, err error) {
|
|
return utils.ParseTimeDetectLayout(fsev[END_TIME], timezone)
|
|
}
|
|
|
|
func (fsev FSEvent) GetDuration(fieldName string) (time.Duration, error) {
|
|
durStr := utils.FirstNonEmpty(fsev[fieldName], fsev[DURATION])
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
durStr = fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.ParseDurationWithSecs(durStr)
|
|
}
|
|
|
|
func (fsev FSEvent) GetPdd(fieldName string) (time.Duration, error) {
|
|
var PDDStr string
|
|
if slices.Contains([]string{utils.MetaDefault, utils.PDD}, fieldName) {
|
|
PDDStr = utils.FirstNonEmpty(fsev[PDD_MEDIA_MS], fsev[PDD_NOMEDIA_MS])
|
|
if len(PDDStr) != 0 {
|
|
PDDStr = PDDStr + "ms" // PDD is in milliseconds and CGR expects it in seconds
|
|
}
|
|
} else if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
PDDStr = fieldName[len(utils.StaticValuePrefix):]
|
|
} else {
|
|
PDDStr = fsev[fieldName]
|
|
}
|
|
return utils.ParseDurationWithSecs(PDDStr)
|
|
}
|
|
|
|
func (fsev FSEvent) GetADC(fieldName string) (time.Duration, error) {
|
|
var ACDStr string
|
|
if slices.Contains([]string{utils.MetaDefault, utils.ACD}, fieldName) {
|
|
ACDStr = utils.FirstNonEmpty(fsev[VarCGRACD])
|
|
if len(ACDStr) != 0 {
|
|
ACDStr = ACDStr + "s" // ACD is in seconds and CGR expects it in seconds
|
|
}
|
|
} else if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
ACDStr = fieldName[len(utils.StaticValuePrefix):]
|
|
} else {
|
|
ACDStr = fsev[fieldName]
|
|
}
|
|
return utils.ParseDurationWithSecs(ACDStr)
|
|
}
|
|
|
|
func (fsev FSEvent) GetRoute(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[VAR_CGR_ROUTE])
|
|
}
|
|
|
|
func (fsev FSEvent) GetDisconnectCause(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[VAR_CGR_DISCONNECT_CAUSE], fsev[HANGUP_CAUSE])
|
|
}
|
|
|
|
func (fsev FSEvent) GetOriginatorIP(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.StaticValuePrefix) { // Static value
|
|
return fieldName[len(utils.StaticValuePrefix):]
|
|
}
|
|
return utils.FirstNonEmpty(fsev[fieldName], fsev[VarCGROriginHost], fsev[FS_IPv4])
|
|
}
|
|
|
|
// GetOriginHost returns the first non empty between: fsev[VarCGROriginHost], conns[connId].cfg.Alias and fsev[FS_IPv4]
|
|
func (fsev FSEvent) GetOriginHost() string {
|
|
return utils.FirstNonEmpty(fsev[VarCGROriginHost], fsev[FS_IPv4])
|
|
}
|
|
|
|
func (fsev FSEvent) GetExtraFields() map[string]string {
|
|
extraFields := make(map[string]string)
|
|
const dynprefix string = utils.MetaDynReq + utils.NestingSep
|
|
for _, fldRule := range config.CgrConfig().FsAgentCfg().ExtraFields {
|
|
if !strings.HasPrefix(fldRule.Rules, dynprefix) {
|
|
continue
|
|
}
|
|
attrName := fldRule.AttrName()[5:]
|
|
if parsed, err := fsev.ParseEventValue(attrName, fldRule,
|
|
config.CgrConfig().GeneralCfg().DefaultTimezone); err != nil {
|
|
utils.Logger.Warning(fmt.Sprintf("<%s> error: %s parsing event rule: %+v", utils.FreeSWITCHAgent, err.Error(), fldRule))
|
|
} else {
|
|
extraFields[attrName] = parsed
|
|
}
|
|
}
|
|
return extraFields
|
|
}
|
|
|
|
// Used in derived charging and sittuations when we need to run regexp on fields
|
|
func (fsev FSEvent) ParseEventValue(attrName string, rsrFld *config.RSRParser, timezone string) (parsed string, err error) {
|
|
switch attrName {
|
|
case utils.ToR:
|
|
return rsrFld.ParseValue(utils.MetaVoice)
|
|
case utils.OriginID:
|
|
return rsrFld.ParseValue(fsev.GetUUID())
|
|
case utils.OriginHost:
|
|
return rsrFld.ParseValue(fsev.GetOriginHost())
|
|
case utils.Source:
|
|
return rsrFld.ParseValue("FS_EVENT")
|
|
case utils.RequestType:
|
|
return rsrFld.ParseValue(fsev.GetReqType(""))
|
|
case utils.Tenant:
|
|
return rsrFld.ParseValue(fsev.GetTenant(""))
|
|
case utils.Category:
|
|
return rsrFld.ParseValue(fsev.GetCategory(""))
|
|
case utils.AccountField:
|
|
return rsrFld.ParseValue(fsev.GetAccount(""))
|
|
case utils.Subject:
|
|
return rsrFld.ParseValue(fsev.GetSubject(""))
|
|
case utils.Destination:
|
|
return rsrFld.ParseValue(fsev.GetDestination(""))
|
|
case utils.SetupTime:
|
|
st, _ := fsev.GetSetupTime("", timezone)
|
|
return rsrFld.ParseValue(st.String())
|
|
case utils.AnswerTime:
|
|
at, _ := fsev.GetAnswerTime("", timezone)
|
|
return rsrFld.ParseValue(at.String())
|
|
case utils.Usage:
|
|
dur, _ := fsev.GetDuration("")
|
|
return rsrFld.ParseValue(strconv.FormatInt(dur.Nanoseconds(), 10))
|
|
case utils.PDD:
|
|
PDD, _ := fsev.GetPdd(utils.MetaDefault)
|
|
return rsrFld.ParseValue(strconv.FormatFloat(PDD.Seconds(), 'f', -1, 64))
|
|
case utils.Route:
|
|
return rsrFld.ParseValue(fsev.GetRoute(""))
|
|
case utils.DisconnectCause:
|
|
return rsrFld.ParseValue(fsev.GetDisconnectCause(""))
|
|
case utils.RunID:
|
|
return rsrFld.ParseValue(utils.MetaDefault)
|
|
case utils.Cost:
|
|
return rsrFld.ParseValue(strconv.FormatFloat(-1, 'f', -1, 64)) // Recommended to use FormatCost
|
|
default:
|
|
if parsed, err = rsrFld.ParseValue(fsev[attrName]); err != nil {
|
|
parsed, err = rsrFld.ParseValue(fsev[FS_VARPREFIX+attrName])
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
// AsCGREvent converts FSEvent into CGREvent
|
|
func (fsev FSEvent) AsCGREvent(timezone string) (cgrEv *utils.CGREvent, err error) {
|
|
sTime, err := fsev.GetSetupTime(utils.MetaDefault, timezone)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cgrEv = &utils.CGREvent{
|
|
Tenant: fsev.GetTenant(utils.MetaDefault),
|
|
ID: utils.UUIDSha1Prefix(),
|
|
Time: &sTime,
|
|
Event: fsev.AsMapStringInterface(timezone),
|
|
APIOpts: fsev.GetOptions(),
|
|
}
|
|
return cgrEv, nil
|
|
}
|
|
|
|
// Used with RLs
|
|
func (fsev FSEvent) AsMapStringInterface(timezone string) map[string]any {
|
|
mp := make(map[string]any)
|
|
for fld, val := range fsev.GetExtraFields() {
|
|
mp[fld] = val
|
|
}
|
|
mp[utils.ToR] = utils.MetaVoice
|
|
mp[utils.OriginID] = fsev.GetUUID()
|
|
mp[utils.OriginHost] = fsev.GetOriginHost()
|
|
mp[utils.Source] = "FS_" + fsev.GetName()
|
|
mp[utils.RequestType] = fsev.GetReqType(utils.MetaDefault)
|
|
mp[utils.Tenant] = fsev.GetTenant(utils.MetaDefault)
|
|
mp[utils.Category] = fsev.GetCategory(utils.MetaDefault)
|
|
mp[utils.AccountField] = fsev.GetAccount(utils.MetaDefault)
|
|
mp[utils.Subject] = fsev.GetSubject(utils.MetaDefault)
|
|
mp[utils.Destination] = fsev.GetDestination(utils.MetaDefault)
|
|
mp[utils.SetupTime], _ = fsev.GetSetupTime(utils.MetaDefault, timezone)
|
|
mp[utils.AnswerTime], _ = fsev.GetAnswerTime(utils.MetaDefault, timezone)
|
|
mp[utils.Usage], _ = fsev.GetDuration(utils.MetaDefault)
|
|
mp[utils.PDD], _ = fsev.GetPdd(utils.MetaDefault)
|
|
mp[utils.ACD], _ = fsev.GetADC(utils.MetaDefault)
|
|
mp[utils.Cost] = -1.0
|
|
mp[utils.Route] = fsev.GetRoute(utils.MetaDefault)
|
|
mp[utils.DisconnectCause] = fsev.GetDisconnectCause(utils.MetaDefault)
|
|
return mp
|
|
}
|
|
|
|
// V1AuthorizeArgs returns the arguments used in SMGv1.Authorize
|
|
func (fsev FSEvent) V1AuthorizeArgs() (args *sessions.V1AuthorizeArgs) {
|
|
cgrEv, err := fsev.AsCGREvent(config.CgrConfig().GeneralCfg().DefaultTimezone)
|
|
if err != nil {
|
|
return
|
|
}
|
|
cgrEv.Event[utils.Usage] = config.CgrConfig().SessionSCfg().GetDefaultUsage(utils.IfaceAsString(cgrEv.Event[utils.ToR])) // no billsec available in auth
|
|
args = &sessions.V1AuthorizeArgs{ // defaults
|
|
CGREvent: cgrEv,
|
|
}
|
|
subsystems, has := fsev[VarCGRFlags]
|
|
if !has {
|
|
utils.Logger.Warning(fmt.Sprintf("<%s> cgr_flags variable is not set, using defaults",
|
|
utils.FreeSWITCHAgent))
|
|
args.GetMaxUsage = true
|
|
return
|
|
}
|
|
args.ParseFlags(subsystems, utils.InfieldSep)
|
|
return
|
|
}
|
|
|
|
// V1InitSessionArgs returns the arguments used in SessionSv1.InitSession
|
|
func (fsev FSEvent) V1InitSessionArgs() (args *sessions.V1InitSessionArgs) {
|
|
cgrEv, err := fsev.AsCGREvent(config.CgrConfig().GeneralCfg().DefaultTimezone)
|
|
if err != nil {
|
|
return
|
|
}
|
|
args = &sessions.V1InitSessionArgs{ // defaults
|
|
CGREvent: cgrEv,
|
|
}
|
|
subsystems, has := fsev[VarCGRFlags]
|
|
if !has {
|
|
utils.Logger.Warning(fmt.Sprintf("<%s> cgr_flags variable is not set, using defaults",
|
|
utils.FreeSWITCHAgent))
|
|
args.InitSession = true
|
|
return
|
|
}
|
|
args.ParseFlags(subsystems, utils.InfieldSep)
|
|
return
|
|
}
|
|
|
|
// V1TerminateSessionArgs returns the arguments used in SMGv1.TerminateSession
|
|
func (fsev FSEvent) V1TerminateSessionArgs() (args *sessions.V1TerminateSessionArgs) {
|
|
cgrEv, err := fsev.AsCGREvent(config.CgrConfig().GeneralCfg().DefaultTimezone)
|
|
if err != nil {
|
|
return
|
|
}
|
|
args = &sessions.V1TerminateSessionArgs{ // defaults
|
|
CGREvent: cgrEv,
|
|
}
|
|
subsystems, has := fsev[VarCGRFlags]
|
|
if !has {
|
|
utils.Logger.Warning(fmt.Sprintf("<%s> cgr_flags variable is not set, using defaults",
|
|
utils.FreeSWITCHAgent))
|
|
args.TerminateSession = true
|
|
return
|
|
}
|
|
args.ParseFlags(subsystems, utils.InfieldSep)
|
|
return
|
|
}
|
|
|
|
// SliceAsFsArray Converts a slice of strings into a FS array string, contains len(array) at first index since FS does not support len(ARRAY::) for now
|
|
func SliceAsFsArray(slc []string) (arry string) {
|
|
if len(slc) == 0 {
|
|
return
|
|
}
|
|
arry = fmt.Sprintf("ARRAY::%d", len(slc))
|
|
for _, itm := range slc {
|
|
arry += "|:" + itm
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetOptions returns the possible options
|
|
func (fsev FSEvent) GetOptions() (mp map[string]any) {
|
|
mp = make(map[string]any)
|
|
opts, has := fsev[VarCGROpts]
|
|
if !has {
|
|
return
|
|
}
|
|
for _, opt := range strings.Split(opts, utils.FieldsSep) {
|
|
spltOpt := strings.SplitN(opt, utils.InInFieldSep, 2)
|
|
if len(spltOpt) != 2 {
|
|
continue
|
|
}
|
|
mp[spltOpt[0]] = spltOpt[1]
|
|
}
|
|
return
|
|
}
|