Files
elmprodvpn/selective-vpn-api/app/transport_singbox_profiles_state_normalize.go

167 lines
3.7 KiB
Go

package app
import (
transportcfgpkg "selective-vpn-api/app/transportcfg"
"sort"
"strconv"
"strings"
)
func deriveSingBoxProfileID(name, protocol string, existing []SingBoxProfile) string {
base := sanitizeID(name)
if base == "" {
base = sanitizeID(protocol)
}
if base == "" {
base = "singbox-profile"
}
cand := base
used := make(map[string]struct{}, len(existing))
for _, it := range existing {
used[it.ID] = struct{}{}
}
if _, ok := used[cand]; !ok {
return cand
}
for i := 2; i < 1000; i++ {
v := base + "-" + strconv.Itoa(i)
if _, ok := used[v]; !ok {
return v
}
}
return ""
}
func findSingBoxProfileIndex(items []SingBoxProfile, id string) int {
for i := range items {
if strings.TrimSpace(items[i].ID) == id {
return i
}
}
return -1
}
func ensureSingBoxProfilesActiveID(st *singBoxProfilesState) {
if st == nil {
return
}
if len(st.Items) == 0 {
st.ActiveProfileID = ""
return
}
active := strings.TrimSpace(st.ActiveProfileID)
if active != "" {
for _, it := range st.Items {
if it.ID == active && it.Enabled {
return
}
}
}
for _, it := range st.Items {
if it.Enabled {
st.ActiveProfileID = it.ID
return
}
}
st.ActiveProfileID = st.Items[0].ID
}
func normalizeSingBoxProfilesState(in singBoxProfilesState) singBoxProfilesState {
out := in
if out.Version == 0 {
out.Version = singBoxProfilesStateVersion
}
if out.Revision < 0 {
out.Revision = 0
}
byID := map[string]SingBoxProfile{}
for _, raw := range in.Items {
it := normalizeSingBoxProfile(raw)
if it.ID == "" {
continue
}
cur, ok := byID[it.ID]
if !ok || preferSingBoxProfile(it, cur) {
byID[it.ID] = it
}
}
keys := make([]string, 0, len(byID))
for id := range byID {
keys = append(keys, id)
}
sort.Strings(keys)
out.Items = make([]SingBoxProfile, 0, len(keys))
for _, id := range keys {
out.Items = append(out.Items, byID[id])
}
ensureSingBoxProfilesActiveID(&out)
return out
}
func normalizeSingBoxProfile(in SingBoxProfile) SingBoxProfile {
out := in
out.ID = sanitizeID(in.ID)
if out.ID == "" {
return SingBoxProfile{}
}
out.Name = strings.TrimSpace(in.Name)
if out.Name == "" {
out.Name = out.ID
}
if mode, ok := normalizeSingBoxProfileMode(in.Mode); ok {
out.Mode = mode
} else {
out.Mode = SingBoxProfileModeTyped
}
out.Protocol = normalizeSingBoxProtocol(in.Protocol)
out.SchemaVersion = normalizeSingBoxSchemaVersion(in.SchemaVersion)
if out.ProfileRevision <= 0 {
out.ProfileRevision = 1
}
if out.RenderRevision < 0 {
out.RenderRevision = 0
}
out.LastValidatedAt = strings.TrimSpace(in.LastValidatedAt)
out.LastAppliedAt = strings.TrimSpace(in.LastAppliedAt)
out.LastError = strings.TrimSpace(in.LastError)
out.CreatedAt = strings.TrimSpace(in.CreatedAt)
out.UpdatedAt = strings.TrimSpace(in.UpdatedAt)
out.Typed = cloneMapDeep(in.Typed)
out.RawConfig = cloneMapDeep(in.RawConfig)
out.Meta = cloneMapDeep(in.Meta)
secretMap, err := readSingBoxSecrets(out.ID)
if err == nil {
out.HasSecrets = len(secretMap) > 0
out.SecretsMasked = transportcfgpkg.MaskStringMap(secretMap, "******")
} else {
out.HasSecrets = in.HasSecrets
out.SecretsMasked = transportcfgpkg.CloneStringMap(in.SecretsMasked)
}
if !out.HasSecrets {
out.SecretsMasked = nil
}
return out
}
func preferSingBoxProfile(cand, cur SingBoxProfile) bool {
if cand.ProfileRevision != cur.ProfileRevision {
return cand.ProfileRevision > cur.ProfileRevision
}
cu := strings.TrimSpace(cand.UpdatedAt)
ou := strings.TrimSpace(cur.UpdatedAt)
if cu != ou {
if cu == "" {
return false
}
if ou == "" {
return true
}
return cu > ou
}
return strings.TrimSpace(cand.CreatedAt) > strings.TrimSpace(cur.CreatedAt)
}