Files
adguardhome-sync/internal/client/model/model-functions.go
2025-09-15 08:07:27 +02:00

497 lines
11 KiB
Go

package model
import (
"fmt"
"sort"
"strings"
"github.com/jinzhu/copier"
"go.uber.org/zap"
"k8s.io/utils/ptr"
"github.com/bakito/adguardhome-sync/internal/utils"
)
// Clone the config.
func (c *DhcpStatus) Clone() *DhcpStatus {
clone := &DhcpStatus{}
_ = copier.Copy(clone, c)
return clone
}
func (c *DhcpStatus) cleanV4V6() {
if c.V4 != nil && !c.V4.isValid() {
c.V4 = nil
}
if c.V6 != nil && !c.V6.isValid() {
c.V6 = nil
}
}
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance.
func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool {
c.cleanV4V6()
o.cleanV4V6()
return c.Equals(o)
}
// Equals dhcp server config equal check.
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
return utils.JSONEquals(c, o)
}
func (c *DhcpStatus) HasConfig() bool {
return (c.V4 != nil && c.V4.isValid()) || (c.V6 != nil && c.V6.isValid())
}
func (j DhcpConfigV4) isValid() bool {
return j.GatewayIp != nil && *j.GatewayIp != "" &&
j.SubnetMask != nil && *j.SubnetMask != "" &&
j.RangeStart != nil && *j.RangeStart != "" &&
j.RangeEnd != nil && *j.RangeEnd != ""
}
func (j DhcpConfigV6) isValid() bool {
return j.RangeStart != nil && *j.RangeStart != ""
}
type DhcpStaticLeases []DhcpStaticLease
// MergeDhcpStaticLeases the leases.
func MergeDhcpStaticLeases(l, other *[]DhcpStaticLease) (adds, removes DhcpStaticLeases) {
var thisLeases []DhcpStaticLease
var otherLeases []DhcpStaticLease
if l != nil {
thisLeases = *l
}
if other != nil {
otherLeases = *other
}
current := make(map[string]DhcpStaticLease)
for _, le := range thisLeases {
current[le.Mac] = le
}
for _, le := range otherLeases {
if _, ok := current[le.Mac]; ok {
delete(current, le.Mac)
} else {
adds = append(adds, le)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes
}
// Equals dns config equal check.
func (c *DNSConfig) Equals(o *DNSConfig) bool {
cc := c.Clone()
oo := o.Clone()
cc.Sort()
oo.Sort()
return utils.JSONEquals(cc, oo)
}
func (c *DNSConfig) Clone() *DNSConfig {
return utils.Clone(c, &DNSConfig{})
}
// Sort dns config.
func (c *DNSConfig) Sort() {
if c.UpstreamDns != nil {
sort.Strings(*c.UpstreamDns)
}
if c.UpstreamDns != nil {
sort.Strings(*c.BootstrapDns)
}
if c.UpstreamDns != nil {
sort.Strings(*c.LocalPtrUpstreams)
}
}
// Equals access list equal check.
func (al *AccessList) Equals(o *AccessList) bool {
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
}
func EqualsStringSlice(a, b *[]string, sortIt bool) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
aa := *a
bb := *b
if sortIt {
sort.Strings(aa)
sort.Strings(bb)
}
if len(aa) != len(bb) {
return false
}
for i, v := range aa {
if v != bb[i] {
return false
}
}
return true
}
// Sort clients.
func (cl *Client) Sort() {
if cl.Ids != nil {
sort.Strings(*cl.Ids)
}
if cl.Tags != nil {
sort.Strings(*cl.Tags)
}
if cl.BlockedServices != nil {
sort.Strings(*cl.BlockedServices)
}
if cl.Upstreams != nil {
sort.Strings(*cl.Upstreams)
}
}
// PrepareDiff so we skip it in diff.
func (cl *Client) PrepareDiff() *string {
var tz *string
bss := cl.BlockedServicesSchedule
if bss != nil && bss.Mon == nil && bss.Tue == nil && bss.Wed == nil &&
bss.Thu == nil && bss.Fri == nil && bss.Sat == nil && bss.Sun == nil {
tz = cl.BlockedServicesSchedule.TimeZone
cl.BlockedServicesSchedule.TimeZone = nil
}
return tz
}
// AfterDiff reset after diff.
func (cl *Client) AfterDiff(tz *string) {
if cl.BlockedServicesSchedule != nil {
cl.BlockedServicesSchedule.TimeZone = tz
}
}
// Equals Clients equal check.
func (cl *Client) Equals(o *Client) bool {
cl.Sort()
o.Sort()
bssCl := cl.PrepareDiff()
bssO := o.PrepareDiff()
defer func() {
cl.AfterDiff(bssCl)
o.AfterDiff(bssO)
}()
return utils.JSONEquals(cl, o)
}
// Add ac client.
func (clients *Clients) Add(cl Client) {
if clients.Clients == nil {
clients.Clients = &ClientsArray{cl}
} else {
a := append(*clients.Clients, cl)
clients.Clients = &a
}
}
// Merge merge Clients.
func (clients *Clients) Merge(other *Clients) (adds, removes, updates []*Client) {
current := make(map[string]*Client)
if clients.Clients != nil {
cc := *clients.Clients
for _, client := range cc {
current[*client.Name] = &client
}
}
expected := make(map[string]*Client)
if other.Clients != nil {
oc := *other.Clients
for _, client := range oc {
expected[*client.Name] = &client
}
}
for _, cl := range expected {
if oc, ok := current[*cl.Name]; ok {
if !cl.Equals(oc) {
updates = append(updates, cl)
}
delete(current, *cl.Name)
} else {
adds = append(adds, cl)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, updates, removes
}
// Key RewriteEntry key.
func (re *RewriteEntry) Key() string {
var d string
var a string
if re.Domain != nil {
d = *re.Domain
}
if re.Answer != nil {
a = *re.Answer
}
return fmt.Sprintf("%s#%s", d, a)
}
// RewriteEntries list of RewriteEntry.
type RewriteEntries []RewriteEntry
// Merge RewriteEntries.
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (adds, removes, duplicates RewriteEntries) {
current := make(map[string]RewriteEntry)
processed := make(map[string]bool)
for _, rr := range *rwe {
if _, ok := processed[rr.Key()]; !ok {
current[rr.Key()] = rr
processed[rr.Key()] = true
} else {
// remove duplicate
removes = append(removes, rr)
}
}
for _, rr := range *other {
if _, ok := current[rr.Key()]; ok {
delete(current, rr.Key())
} else {
if _, ok := processed[rr.Key()]; !ok {
adds = append(adds, rr)
processed[rr.Key()] = true
} else {
// skip duplicate
duplicates = append(duplicates, rr)
}
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes, duplicates
}
func MergeFilters(this, other *[]Filter) (adds, updates, removes []Filter) {
if this == nil && other == nil {
return nil, nil, nil
}
current := make(map[string]*Filter)
if this != nil {
for _, fi := range *this {
current[fi.Url] = &fi
}
}
if other != nil {
for _, rr := range *other {
if c, ok := current[rr.Url]; ok {
if !c.Equals(&rr) {
updates = append(updates, rr)
}
delete(current, rr.Url)
} else {
adds = append(adds, rr)
}
}
}
for _, rr := range current {
removes = append(removes, *rr)
}
return adds, updates, removes
}
// Equals Filter equal check.
func (f *Filter) Equals(o *Filter) bool {
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
}
type QueryLogConfigWithIgnored struct {
QueryLogConfig
// Ignored List of host names, which should not be written to log
Ignored []string `json:"ignored,omitempty"`
}
// Equals QueryLogConfig equal check.
func (qlc *QueryLogConfigWithIgnored) Equals(o *QueryLogConfigWithIgnored) bool {
return utils.JSONEquals(qlc, o)
}
// Equals QueryLogConfigInterval equal check.
func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
return ptrEquals(qlc, o)
}
func ptrEquals[T comparable](a, b *T) bool {
if a == nil && b == nil {
return true
}
var aa T
if a != nil {
aa = *a
}
var bb T
if b != nil {
bb = *b
}
return aa == bb
}
// EnableConfig API struct.
type EnableConfig struct {
Enabled bool `json:"enabled"`
}
func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool {
return ptrEquals(ssc.Enabled, o.Enabled) &&
ptrEquals(ssc.Bing, o.Bing) &&
ptrEquals(ssc.Duckduckgo, o.Duckduckgo) &&
ptrEquals(ssc.Google, o.Google) &&
ptrEquals(ssc.Pixabay, o.Pixabay) &&
ptrEquals(ssc.Yandex, o.Yandex) &&
ptrEquals(ssc.Youtube, o.Youtube)
}
func (pi *ProfileInfo) Equals(o *ProfileInfo, withTheme bool) bool {
return pi.Language == o.Language && (!withTheme || pi.Theme == o.Theme)
}
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo, withTheme bool) *ProfileInfo {
if pi.Equals(o, withTheme) {
return nil
}
merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme}
if o.Language != "" {
merged.Language = o.Language
}
if withTheme && o.Theme != "" {
merged.Theme = o.Theme
}
if merged.Name == "" || merged.Language == "" || merged.Equals(pi, false) {
return nil
}
return merged
}
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
return utils.JSONEquals(bss, o)
}
func (bss *BlockedServicesSchedule) ServicesString() string {
return ArrayString(bss.Ids)
}
func ArrayString(a *[]string) string {
if a == nil {
return "[]"
}
sorted := *a
sort.Strings(sorted)
return fmt.Sprintf("[%s]", strings.Join(sorted, ","))
}
func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) {
// disable UsePrivatePtrResolvers if not configured
// https://github.com/AdguardTeam/AdGuardHome/issues/6820
if c.UsePrivatePtrResolvers != nil && *c.UsePrivatePtrResolvers &&
(c.LocalPtrUpstreams == nil || len(*c.LocalPtrUpstreams) == 0) {
l.Warn(
"disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin",
)
c.UsePrivatePtrResolvers = utils.Ptr(false)
}
}
// Equals GetStatsConfigResponse equal check.
func (sc *GetStatsConfigResponse) Equals(o *GetStatsConfigResponse) bool {
return utils.JSONEquals(sc, o)
}
func NewStats() *Stats {
return &Stats{
NumBlockedFiltering: ptr.To(0),
NumReplacedParental: ptr.To(0),
NumReplacedSafesearch: ptr.To(0),
NumReplacedSafebrowsing: ptr.To(0),
NumDnsQueries: ptr.To(0),
BlockedFiltering: ptr.To(make([]int, 24)),
DnsQueries: ptr.To(make([]int, 24)),
ReplacedParental: ptr.To(make([]int, 24)),
ReplacedSafebrowsing: ptr.To(make([]int, 24)),
}
}
func (s *Stats) Add(other *Stats) {
s.NumBlockedFiltering = addInt(s.NumBlockedFiltering, other.NumBlockedFiltering)
s.NumReplacedSafebrowsing = addInt(s.NumReplacedSafebrowsing, other.NumReplacedSafebrowsing)
s.NumDnsQueries = addInt(s.NumDnsQueries, other.NumDnsQueries)
s.NumReplacedSafesearch = addInt(s.NumReplacedSafesearch, other.NumReplacedSafesearch)
s.NumReplacedParental = addInt(s.NumReplacedParental, other.NumReplacedParental)
s.BlockedFiltering = sumUp(s.BlockedFiltering, other.BlockedFiltering)
s.DnsQueries = sumUp(s.DnsQueries, other.DnsQueries)
s.ReplacedParental = sumUp(s.ReplacedParental, other.ReplacedParental)
s.ReplacedSafebrowsing = sumUp(s.ReplacedSafebrowsing, other.ReplacedSafebrowsing)
}
func addInt(t, add *int) *int {
if add != nil {
return ptr.To(*t + *add)
}
return t
}
func sumUp(t, o *[]int) *[]int {
if o != nil {
tt := *t
oo := *o
var sum []int
for i := range tt {
if len(oo) >= i {
sum = append(sum, tt[i]+oo[i])
}
}
return &sum
}
return t
}
func (c *TlsConfig) Equals(config *TlsConfig) bool {
return utils.JSONEquals(c, config)
}