Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

136 lignes
3.2 KiB

  1. // Copyright 2013 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 gosrc
  7. import (
  8. "archive/tar"
  9. "bytes"
  10. "compress/gzip"
  11. "crypto/md5"
  12. "encoding/hex"
  13. "io"
  14. "net/http"
  15. "path"
  16. "regexp"
  17. "sort"
  18. "strings"
  19. )
  20. func init() {
  21. addService(&service{
  22. pattern: regexp.MustCompile(`^launchpad\.net/(?P<repo>(?P<project>[a-z0-9A-Z_.\-]+)(?P<series>/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]+)*$`),
  23. prefix: "launchpad.net/",
  24. get: getLaunchpadDir,
  25. })
  26. }
  27. type byHash []byte
  28. func (p byHash) Len() int { return len(p) / md5.Size }
  29. func (p byHash) Less(i, j int) bool {
  30. return -1 == bytes.Compare(p[i*md5.Size:(i+1)*md5.Size], p[j*md5.Size:(j+1)*md5.Size])
  31. }
  32. func (p byHash) Swap(i, j int) {
  33. var temp [md5.Size]byte
  34. copy(temp[:], p[i*md5.Size:])
  35. copy(p[i*md5.Size:(i+1)*md5.Size], p[j*md5.Size:])
  36. copy(p[j*md5.Size:], temp[:])
  37. }
  38. func getLaunchpadDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
  39. c := &httpClient{client: client}
  40. if match["project"] != "" && match["series"] != "" {
  41. rc, err := c.getReader(expand("https://code.launchpad.net/{project}{series}/.bzr/branch-format", match))
  42. switch {
  43. case err == nil:
  44. rc.Close()
  45. // The structure of the import path is launchpad.net/{root}/{dir}.
  46. case IsNotFound(err):
  47. // The structure of the import path is is launchpad.net/{project}/{dir}.
  48. match["repo"] = match["project"]
  49. match["dir"] = expand("{series}{dir}", match)
  50. default:
  51. return nil, err
  52. }
  53. }
  54. p, err := c.getBytes(expand("https://bazaar.launchpad.net/+branch/{repo}/tarball", match))
  55. if err != nil {
  56. return nil, err
  57. }
  58. gzr, err := gzip.NewReader(bytes.NewReader(p))
  59. if err != nil {
  60. return nil, err
  61. }
  62. defer gzr.Close()
  63. tr := tar.NewReader(gzr)
  64. var hash []byte
  65. inTree := false
  66. dirPrefix := expand("+branch/{repo}{dir}/", match)
  67. var files []*File
  68. for {
  69. h, err := tr.Next()
  70. if err == io.EOF {
  71. break
  72. }
  73. if err != nil {
  74. return nil, err
  75. }
  76. d, f := path.Split(h.Name)
  77. if !isDocFile(f) {
  78. continue
  79. }
  80. b := make([]byte, h.Size)
  81. if _, err := io.ReadFull(tr, b); err != nil {
  82. return nil, err
  83. }
  84. m := md5.New()
  85. m.Write(b)
  86. hash = m.Sum(hash)
  87. if !strings.HasPrefix(h.Name, dirPrefix) {
  88. continue
  89. }
  90. inTree = true
  91. if d == dirPrefix {
  92. files = append(files, &File{
  93. Name: f,
  94. BrowseURL: expand("http://bazaar.launchpad.net/+branch/{repo}/view/head:{dir}/{0}", match, f),
  95. Data: b})
  96. }
  97. }
  98. if !inTree {
  99. return nil, NotFoundError{Message: "Directory tree does not contain Go files."}
  100. }
  101. sort.Sort(byHash(hash))
  102. m := md5.New()
  103. m.Write(hash)
  104. hash = m.Sum(hash[:0])
  105. etag := hex.EncodeToString(hash)
  106. if etag == savedEtag {
  107. return nil, NotModifiedError{}
  108. }
  109. return &Directory{
  110. BrowseURL: expand("http://bazaar.launchpad.net/+branch/{repo}/view/head:{dir}/", match),
  111. Etag: etag,
  112. Files: files,
  113. LineFmt: "%s#L%d",
  114. ProjectName: match["repo"],
  115. ProjectRoot: expand("launchpad.net/{repo}", match),
  116. ProjectURL: expand("https://launchpad.net/{repo}/", match),
  117. VCS: "bzr",
  118. }, nil
  119. }