inbound_settings_commit.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package service
  2. import (
  3. "encoding/json"
  4. "reflect"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. "gorm.io/gorm"
  7. )
  8. // commitInboundClientSettings writes a client op's edit (base → ib.Settings) onto
  9. // the settings committed now: a traffic tick after the op's read must survive.
  10. func commitInboundClientSettings(tx *gorm.DB, ib *model.Inbound, base string) error {
  11. var current []string
  12. if err := tx.Model(&model.Inbound{}).Where("id = ?", ib.Id).Pluck("settings", &current).Error; err != nil {
  13. return err
  14. }
  15. if len(current) == 1 && current[0] != base {
  16. merged, err := rebaseClientSettings(base, ib.Settings, current[0])
  17. if err != nil {
  18. return err
  19. }
  20. ib.Settings = merged
  21. }
  22. return tx.Model(&model.Inbound{}).Where("id = ?", ib.Id).Update("settings", ib.Settings).Error
  23. }
  24. // rebaseClientSettings three-way merges settings JSON: every key and client field
  25. // ours left as base had it takes current's value; clients are matched by email.
  26. func rebaseClientSettings(base, ours, current string) (string, error) {
  27. var baseM, oursM, curM map[string]any
  28. for _, p := range []struct {
  29. raw string
  30. dst *map[string]any
  31. }{{base, &baseM}, {ours, &oursM}, {current, &curM}} {
  32. if err := json.Unmarshal([]byte(p.raw), p.dst); err != nil {
  33. return "", err
  34. }
  35. }
  36. out := mergeFields(baseM, oursM, curM)
  37. baseClients, _ := baseM["clients"].([]any)
  38. oursClients, _ := oursM["clients"].([]any)
  39. curClients, _ := curM["clients"].([]any)
  40. if _, has := oursM["clients"]; has {
  41. out["clients"] = mergeClientLists(baseClients, oursClients, curClients)
  42. }
  43. b, err := json.MarshalIndent(out, "", " ")
  44. if err != nil {
  45. return "", err
  46. }
  47. return string(b), nil
  48. }
  49. func mergeFields(base, ours, current map[string]any) map[string]any {
  50. out := make(map[string]any, len(current)+len(ours))
  51. for k, v := range current {
  52. out[k] = v
  53. }
  54. keys := make(map[string]struct{}, len(base)+len(ours))
  55. for k := range base {
  56. keys[k] = struct{}{}
  57. }
  58. for k := range ours {
  59. keys[k] = struct{}{}
  60. }
  61. for k := range keys {
  62. bv, inBase := base[k]
  63. ov, inOurs := ours[k]
  64. if inBase == inOurs && reflect.DeepEqual(bv, ov) {
  65. continue
  66. }
  67. if inOurs {
  68. out[k] = ov
  69. } else {
  70. delete(out, k)
  71. }
  72. }
  73. return out
  74. }
  75. func clientEntryEmail(entry any) (map[string]any, string) {
  76. m, ok := entry.(map[string]any)
  77. if !ok {
  78. return nil, ""
  79. }
  80. email, _ := m["email"].(string)
  81. return m, email
  82. }
  83. func indexClientsByEmail(list []any) map[string]map[string]any {
  84. out := make(map[string]map[string]any, len(list))
  85. for _, entry := range list {
  86. if m, email := clientEntryEmail(entry); email != "" {
  87. out[email] = m
  88. }
  89. }
  90. return out
  91. }
  92. // mergeClientLists keeps ours' order. A client ours removed stays removed; one a
  93. // concurrent writer added or removed keeps that change unless ours edited it.
  94. func mergeClientLists(base, ours, current []any) []any {
  95. baseBy := indexClientsByEmail(base)
  96. curBy := indexClientsByEmail(current)
  97. out := make([]any, 0, len(ours)+len(current))
  98. placed := make(map[string]struct{}, len(ours))
  99. for _, entry := range ours {
  100. o, email := clientEntryEmail(entry)
  101. if email == "" {
  102. out = append(out, entry)
  103. continue
  104. }
  105. placed[email] = struct{}{}
  106. b, inBase := baseBy[email]
  107. c, inCur := curBy[email]
  108. switch {
  109. case !inBase:
  110. out = append(out, o)
  111. case !inCur:
  112. if !reflect.DeepEqual(b, o) {
  113. out = append(out, o)
  114. }
  115. default:
  116. out = append(out, mergeFields(b, o, c))
  117. }
  118. }
  119. for _, entry := range current {
  120. c, email := clientEntryEmail(entry)
  121. if email == "" {
  122. continue
  123. }
  124. if _, done := placed[email]; done {
  125. continue
  126. }
  127. if _, inBase := baseBy[email]; !inBase {
  128. out = append(out, c)
  129. }
  130. }
  131. return out
  132. }