You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

208 line
5.0 KiB

  1. // Copyright 2016 The Go Authors. All rights reserved.
  2. //
  3. // Use of this source code is governed by a BSD-style
  4. // license that can be found in the LICENSE file or at
  5. // https://developers.google.com/open-source/licenses/bsd.
  6. package database
  7. import (
  8. "bytes"
  9. "errors"
  10. "fmt"
  11. "log"
  12. "math"
  13. "strings"
  14. "unicode"
  15. "golang.org/x/net/context"
  16. "google.golang.org/appengine/search"
  17. "github.com/golang/gddo/doc"
  18. )
  19. func (p *Package) Load(fields []search.Field, meta *search.DocumentMetadata) error {
  20. for _, f := range fields {
  21. switch f.Name {
  22. case "Name":
  23. if v, ok := f.Value.(search.Atom); ok {
  24. p.Name = string(v)
  25. }
  26. case "Path":
  27. if v, ok := f.Value.(string); ok {
  28. p.Path = v
  29. }
  30. case "Synopsis":
  31. if v, ok := f.Value.(string); ok {
  32. p.Synopsis = v
  33. }
  34. case "ImportCount":
  35. if v, ok := f.Value.(float64); ok {
  36. p.ImportCount = int(v)
  37. }
  38. case "Stars":
  39. if v, ok := f.Value.(float64); ok {
  40. p.Stars = int(v)
  41. }
  42. case "Score":
  43. if v, ok := f.Value.(float64); ok {
  44. p.Score = v
  45. }
  46. }
  47. }
  48. if p.Path == "" {
  49. return errors.New("Invalid document: missing Path field")
  50. }
  51. for _, f := range meta.Facets {
  52. if f.Name == "Fork" {
  53. p.Fork = f.Value.(search.Atom) == "true"
  54. }
  55. }
  56. return nil
  57. }
  58. func (p *Package) Save() ([]search.Field, *search.DocumentMetadata, error) {
  59. fields := []search.Field{
  60. {Name: "Name", Value: search.Atom(p.Name)},
  61. {Name: "Path", Value: p.Path},
  62. {Name: "Synopsis", Value: p.Synopsis},
  63. {Name: "Score", Value: p.Score},
  64. {Name: "ImportCount", Value: float64(p.ImportCount)},
  65. {Name: "Stars", Value: float64(p.Stars)},
  66. }
  67. fork := fmt.Sprint(p.Fork) // "true" or "false"
  68. meta := &search.DocumentMetadata{
  69. // Customize the rank property by the product of the package score and
  70. // natural logarithm of the import count. Rank must be a positive integer.
  71. // Use 1 as minimum rank and keep 3 digits of precision to distinguish
  72. // close ranks.
  73. Rank: int(math.Max(1, 1000*p.Score*math.Log(math.E+float64(p.ImportCount)))),
  74. Facets: []search.Facet{
  75. {Name: "Fork", Value: search.Atom(fork)},
  76. },
  77. }
  78. return fields, meta, nil
  79. }
  80. // PutIndex creates or updates a package entry in the search index. id identifies the document in the index.
  81. // If pdoc is non-nil, PutIndex will update the package's name, path and synopsis supplied by pdoc.
  82. // pdoc must be non-nil for a package's first call to PutIndex.
  83. // PutIndex updates the Score to score, if non-negative.
  84. func PutIndex(c context.Context, pdoc *doc.Package, id string, score float64, importCount int) error {
  85. if id == "" {
  86. return errors.New("indexae: no id assigned")
  87. }
  88. idx, err := search.Open("packages")
  89. if err != nil {
  90. return err
  91. }
  92. var pkg Package
  93. if err := idx.Get(c, id, &pkg); err != nil {
  94. if err != search.ErrNoSuchDocument {
  95. return err
  96. } else if pdoc == nil {
  97. // Cannot update a non-existing document.
  98. return errors.New("indexae: cannot create new document with nil pdoc")
  99. }
  100. // No such document in the index, fall through.
  101. }
  102. // Update document information accordingly.
  103. if pdoc != nil {
  104. pkg.Name = pdoc.Name
  105. pkg.Path = pdoc.ImportPath
  106. pkg.Synopsis = pdoc.Synopsis
  107. pkg.Stars = pdoc.Stars
  108. pkg.Fork = pdoc.Fork
  109. }
  110. if score >= 0 {
  111. pkg.Score = score
  112. }
  113. pkg.ImportCount = importCount
  114. if _, err := idx.Put(c, id, &pkg); err != nil {
  115. return err
  116. }
  117. return nil
  118. }
  119. // Search searches the packages index for a given query. A path-like query string
  120. // will be passed in unchanged, whereas single words will be stemmed.
  121. func Search(c context.Context, q string) ([]Package, error) {
  122. index, err := search.Open("packages")
  123. if err != nil {
  124. return nil, err
  125. }
  126. var pkgs []Package
  127. opt := &search.SearchOptions{
  128. Limit: 100,
  129. }
  130. for it := index.Search(c, parseQuery2(q), opt); ; {
  131. var p Package
  132. _, err := it.Next(&p)
  133. if err == search.Done {
  134. break
  135. }
  136. if err != nil {
  137. return nil, err
  138. }
  139. pkgs = append(pkgs, p)
  140. }
  141. return pkgs, nil
  142. }
  143. func parseQuery2(q string) string {
  144. var buf bytes.Buffer
  145. for _, s := range strings.FieldsFunc(q, isTermSep2) {
  146. if strings.ContainsAny(s, "./") {
  147. // Quote terms with / or . for path like query.
  148. fmt.Fprintf(&buf, "%q ", s)
  149. } else {
  150. // Stem for single word terms.
  151. fmt.Fprintf(&buf, "~%v ", s)
  152. }
  153. }
  154. return buf.String()
  155. }
  156. func isTermSep2(r rune) bool {
  157. return unicode.IsSpace(r) ||
  158. r != '.' && r != '/' && unicode.IsPunct(r) ||
  159. unicode.IsSymbol(r)
  160. }
  161. func deleteIndex(c context.Context, id string) error {
  162. idx, err := search.Open("packages")
  163. if err != nil {
  164. return err
  165. }
  166. return idx.Delete(c, id)
  167. }
  168. // PurgeIndex deletes all the packages from the search index.
  169. func PurgeIndex(c context.Context) error {
  170. idx, err := search.Open("packages")
  171. if err != nil {
  172. return err
  173. }
  174. n := 0
  175. for it := idx.List(c, &search.ListOptions{IDsOnly: true}); ; n++ {
  176. var pkg Package
  177. id, err := it.Next(&pkg)
  178. if err == search.Done {
  179. break
  180. }
  181. if err != nil {
  182. return err
  183. }
  184. if err := idx.Delete(c, id); err != nil {
  185. log.Printf("Failed to delete package %s: %v", id, err)
  186. continue
  187. }
  188. }
  189. log.Printf("Purged %d packages from the search index.", n)
  190. return nil
  191. }