users.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // This file is subject to a 1-clause BSD license.
  2. // Its contents can be found in the enclosed LICENSE file.
  3. package stats
  4. import (
  5. "sort"
  6. "strings"
  7. "time"
  8. )
  9. // User defines the hostmask and all known nicknames for a single user.
  10. // Additionally, it defines some timestamps.
  11. type User struct {
  12. Hostmask string
  13. Nicknames []string
  14. FirstSeen time.Time
  15. LastSeen time.Time
  16. }
  17. // AddNickname adds the given nickname to the user's name list,
  18. // provided it is not already known.
  19. func (u *User) AddNickname(v string) {
  20. v = strings.ToLower(v)
  21. idx := stringIndex(u.Nicknames, v)
  22. if idx > -1 {
  23. return
  24. }
  25. u.Nicknames = append(u.Nicknames, v)
  26. sort.Strings(u.Nicknames)
  27. }
  28. // UserList defines a set of user descriptors, sortable by hostmask.
  29. type UserList []*User
  30. func (cl UserList) Len() int { return len(cl) }
  31. func (cl UserList) Less(i, j int) bool { return cl[i].Hostmask < cl[j].Hostmask }
  32. func (cl UserList) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
  33. // Get returns a user entry for the given hostmask. If it doesn't exist yet,
  34. // a new entry is created and added to the list implicitely.
  35. //
  36. // This implicitely updates the LastSeen timestamp for the user.
  37. func (cl *UserList) Get(mask string) *User {
  38. idx := userIndex(*cl, mask)
  39. if idx > -1 {
  40. (*cl)[idx].LastSeen = time.Now()
  41. return (*cl)[idx]
  42. }
  43. usr := &User{
  44. Hostmask: strings.ToLower(mask),
  45. FirstSeen: time.Now(),
  46. LastSeen: time.Now(),
  47. }
  48. *cl = append(*cl, usr)
  49. sort.Sort(*cl)
  50. return usr
  51. }
  52. // Find finds the user which exactly matches the given hostmask,
  53. // or all users which have a fuzzy match with the given nickname.
  54. // It returns at most limit users.
  55. func (cl UserList) Find(name string, limit int) []*User {
  56. name = strings.ToLower(name)
  57. // Known hostmask?
  58. idx := userIndex(cl, name)
  59. if idx > -1 {
  60. return []*User{cl[idx]}
  61. }
  62. // Known nickname then perhaps? This will return the first
  63. // instance of the nickname we find.
  64. out := make([]*User, 0, 4)
  65. // First find exact name matches.
  66. for _, usr := range cl {
  67. if userIndex(out, usr.Hostmask) > -1 {
  68. continue
  69. }
  70. if stringExactMatch(usr.Nicknames, name) {
  71. out = append(out, usr)
  72. if len(out) >= limit {
  73. break
  74. }
  75. }
  76. }
  77. // Then find any partial matches.
  78. for _, usr := range cl {
  79. if userIndex(out, usr.Hostmask) > -1 {
  80. continue
  81. }
  82. if stringPartialMatch(usr.Nicknames, name) {
  83. out = append(out, usr)
  84. if len(out) >= limit {
  85. break
  86. }
  87. }
  88. }
  89. return out
  90. }
  91. // userIndex returns the index of user hostmask v in set.
  92. // Returns -1 if it was not found. The list is expected to be sorted.
  93. func userIndex(set []*User, v string) int {
  94. var lo int
  95. hi := len(set) - 1
  96. for lo < hi {
  97. mid := lo + ((hi - lo) / 2)
  98. if set[mid].Hostmask < v {
  99. lo = mid + 1
  100. } else {
  101. hi = mid
  102. }
  103. }
  104. if hi == lo && set[lo].Hostmask == v {
  105. return lo
  106. }
  107. return -1
  108. }
  109. // stringIndex returns the index of string v in set.
  110. // Returns -1 if it was not found. The list is expected to be sorted.
  111. func stringIndex(set []string, v string) int {
  112. var lo int
  113. hi := len(set) - 1
  114. for lo < hi {
  115. mid := lo + ((hi - lo) / 2)
  116. if set[mid] < v {
  117. lo = mid + 1
  118. } else {
  119. hi = mid
  120. }
  121. }
  122. if hi == lo && set[lo] == v {
  123. return lo
  124. }
  125. return -1
  126. }
  127. // stringPartialMatch returns true if any of the values in set are
  128. // at least partially identical to v.
  129. func stringPartialMatch(set []string, v string) bool {
  130. for _, s := range set {
  131. if strings.Index(s, v) > -1 {
  132. return true
  133. }
  134. }
  135. return false
  136. }
  137. // stringExactMatch returns true if any of the values in set are identical
  138. // to v.
  139. func stringExactMatch(set []string, v string) bool {
  140. for _, s := range set {
  141. if s == v {
  142. return true
  143. }
  144. }
  145. return false
  146. }