mixed_sources.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. package query
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. "github.com/Jguer/aur"
  10. "github.com/Jguer/aur/rpc"
  11. "github.com/Jguer/go-alpm/v2"
  12. "github.com/adrg/strutil"
  13. "github.com/adrg/strutil/metrics"
  14. "github.com/leonelquinteros/gotext"
  15. "github.com/Jguer/yay/v12/pkg/db"
  16. "github.com/Jguer/yay/v12/pkg/intrange"
  17. "github.com/Jguer/yay/v12/pkg/settings/parser"
  18. "github.com/Jguer/yay/v12/pkg/stringset"
  19. "github.com/Jguer/yay/v12/pkg/text"
  20. )
  21. const sourceAUR = "aur"
  22. type Builder interface {
  23. Len() int
  24. Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string)
  25. Results(w io.Writer, dbExecutor db.Executor, verboseSearch SearchVerbosity) error
  26. GetTargets(include, exclude intrange.IntRanges, otherExclude stringset.StringSet) ([]string, error)
  27. }
  28. type MixedSourceQueryBuilder struct {
  29. results []abstractResult
  30. sortBy string
  31. searchBy string
  32. targetMode parser.TargetMode
  33. queryMap map[string]map[string]interface{}
  34. bottomUp bool
  35. singleLineResults bool
  36. newEngine bool
  37. aurClient aur.QueryClient
  38. rpcClient rpc.ClientInterface
  39. logger *text.Logger
  40. }
  41. func NewMixedSourceQueryBuilder(
  42. rpcClient rpc.ClientInterface,
  43. aurClient aur.QueryClient,
  44. logger *text.Logger,
  45. sortBy string,
  46. targetMode parser.TargetMode,
  47. searchBy string,
  48. bottomUp,
  49. singleLineResults bool,
  50. newEngine bool,
  51. ) *MixedSourceQueryBuilder {
  52. return &MixedSourceQueryBuilder{
  53. rpcClient: rpcClient,
  54. aurClient: aurClient,
  55. logger: logger,
  56. bottomUp: bottomUp,
  57. sortBy: sortBy,
  58. targetMode: targetMode,
  59. searchBy: searchBy,
  60. singleLineResults: singleLineResults,
  61. queryMap: map[string]map[string]interface{}{},
  62. results: make([]abstractResult, 0, 100),
  63. newEngine: newEngine,
  64. }
  65. }
  66. type abstractResult struct {
  67. source string
  68. name string
  69. description string
  70. votes int
  71. provides []string
  72. }
  73. type abstractResults struct {
  74. results []abstractResult
  75. search string
  76. distanceCache map[string]float64
  77. bottomUp bool
  78. metric strutil.StringMetric
  79. }
  80. func (a *abstractResults) Len() int { return len(a.results) }
  81. func (a *abstractResults) Swap(i, j int) { a.results[i], a.results[j] = a.results[j], a.results[i] }
  82. func (a *abstractResults) GetMetric(pkg *abstractResult) float64 {
  83. if v, ok := a.distanceCache[pkg.name]; ok {
  84. return v
  85. }
  86. sim := strutil.Similarity(pkg.name, a.search, a.metric)
  87. for _, prov := range pkg.provides {
  88. // If the package provides search, it's a perfect match
  89. // AUR packages don't populate provides
  90. candidate := strutil.Similarity(prov, a.search, a.metric)
  91. if candidate > sim {
  92. sim = candidate
  93. }
  94. }
  95. simDesc := strutil.Similarity(pkg.description, a.search, a.metric)
  96. // slightly overweight sync sources by always giving them max popularity
  97. popularity := 1.0
  98. if pkg.source == sourceAUR {
  99. popularity = float64(pkg.votes) / float64(pkg.votes+60)
  100. }
  101. sim = sim*0.6 + simDesc*0.2 + popularity*0.2
  102. a.distanceCache[pkg.name] = sim
  103. return sim
  104. }
  105. func (a *abstractResults) Less(i, j int) bool {
  106. pkgA := a.results[i]
  107. pkgB := a.results[j]
  108. simA := a.GetMetric(&pkgA)
  109. simB := a.GetMetric(&pkgB)
  110. if a.bottomUp {
  111. return simA < simB
  112. }
  113. return simA > simB
  114. }
  115. func (s *MixedSourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string) {
  116. var aurErr error
  117. pkgS = RemoveInvalidTargets(pkgS, s.targetMode)
  118. metric := &metrics.JaroWinkler{
  119. CaseSensitive: false,
  120. }
  121. sortableResults := &abstractResults{
  122. results: []abstractResult{},
  123. search: strings.Join(pkgS, ""),
  124. distanceCache: map[string]float64{},
  125. bottomUp: s.bottomUp,
  126. metric: metric,
  127. }
  128. if s.targetMode.AtLeastAUR() {
  129. var aurResults aurQuery
  130. aurResults, aurErr = queryAUR(ctx, s.rpcClient, s.aurClient, pkgS, s.searchBy, false)
  131. dbName := sourceAUR
  132. for i := range aurResults {
  133. if s.queryMap[dbName] == nil {
  134. s.queryMap[dbName] = map[string]interface{}{}
  135. }
  136. s.queryMap[dbName][aurResults[i].Name] = aurResults[i]
  137. sortableResults.results = append(sortableResults.results, abstractResult{
  138. source: dbName,
  139. name: aurResults[i].Name,
  140. description: aurResults[i].Description,
  141. provides: aurResults[i].Provides,
  142. votes: aurResults[i].NumVotes,
  143. })
  144. }
  145. }
  146. var repoResults []alpm.IPackage
  147. if s.targetMode.AtLeastRepo() {
  148. repoResults = dbExecutor.SyncPackages(pkgS...)
  149. for i := range repoResults {
  150. dbName := repoResults[i].DB().Name()
  151. if s.queryMap[dbName] == nil {
  152. s.queryMap[dbName] = map[string]interface{}{}
  153. }
  154. s.queryMap[dbName][repoResults[i].Name()] = repoResults[i]
  155. rawProvides := repoResults[i].Provides().Slice()
  156. provides := make([]string, len(rawProvides))
  157. for j := range rawProvides {
  158. provides[j] = rawProvides[j].Name
  159. }
  160. sortableResults.results = append(sortableResults.results, abstractResult{
  161. source: repoResults[i].DB().Name(),
  162. name: repoResults[i].Name(),
  163. description: repoResults[i].Description(),
  164. provides: provides,
  165. votes: -1,
  166. })
  167. }
  168. }
  169. sort.Sort(sortableResults)
  170. s.results = sortableResults.results
  171. if aurErr != nil {
  172. s.logger.Errorln(ErrAURSearch{inner: aurErr})
  173. if len(repoResults) != 0 {
  174. s.logger.Warnln(gotext.Get("Showing repo packages only"))
  175. }
  176. }
  177. }
  178. func (s *MixedSourceQueryBuilder) Results(w io.Writer, dbExecutor db.Executor, verboseSearch SearchVerbosity) error {
  179. for i := range s.results {
  180. if verboseSearch == Minimal {
  181. _, _ = fmt.Fprintln(w, s.results[i].name)
  182. continue
  183. }
  184. var toPrint string
  185. if verboseSearch == NumberMenu {
  186. if s.bottomUp {
  187. toPrint += text.Magenta(strconv.Itoa(len(s.results)-i)) + " "
  188. } else {
  189. toPrint += text.Magenta(strconv.Itoa(i+1)) + " "
  190. }
  191. }
  192. pkg := s.queryMap[s.results[i].source][s.results[i].name]
  193. if s.results[i].source == sourceAUR {
  194. aurPkg := pkg.(aur.Pkg)
  195. toPrint += aurPkgSearchString(&aurPkg, dbExecutor, s.singleLineResults)
  196. } else {
  197. syncPkg := pkg.(alpm.IPackage)
  198. toPrint += syncPkgSearchString(syncPkg, dbExecutor, s.singleLineResults)
  199. }
  200. fmt.Fprintln(w, toPrint)
  201. }
  202. return nil
  203. }
  204. func (s *MixedSourceQueryBuilder) Len() int {
  205. return len(s.results)
  206. }
  207. func (s *MixedSourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges,
  208. otherExclude stringset.StringSet,
  209. ) ([]string, error) {
  210. var (
  211. isInclude = len(exclude) == 0 && len(otherExclude) == 0
  212. targets []string
  213. lenRes = len(s.results)
  214. )
  215. for i := 0; i <= s.Len(); i++ {
  216. // FIXME: this is probably broken
  217. target := i - 1
  218. if s.bottomUp {
  219. target = lenRes - i
  220. }
  221. if (isInclude && include.Get(i)) || (!isInclude && !exclude.Get(i)) {
  222. targets = append(targets, s.results[target].source+"/"+s.results[target].name)
  223. }
  224. }
  225. return targets, nil
  226. }