sourcecodeloader.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. // Copyright (C) 2019 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bytes"
  9. "fmt"
  10. "io"
  11. "net/http"
  12. "path/filepath"
  13. "strings"
  14. "sync"
  15. "time"
  16. )
  17. const (
  18. urlPrefix = "https://raw.githubusercontent.com/syncthing/syncthing/"
  19. httpTimeout = 10 * time.Second
  20. )
  21. type githubSourceCodeLoader struct {
  22. mut sync.Mutex
  23. version string
  24. cache map[string]map[string][][]byte // version -> file -> lines
  25. client *http.Client
  26. }
  27. func newGithubSourceCodeLoader() *githubSourceCodeLoader {
  28. return &githubSourceCodeLoader{
  29. cache: make(map[string]map[string][][]byte),
  30. client: &http.Client{Timeout: httpTimeout},
  31. }
  32. }
  33. func (l *githubSourceCodeLoader) LockWithVersion(version string) {
  34. l.mut.Lock()
  35. l.version = version
  36. if _, ok := l.cache[version]; !ok {
  37. l.cache[version] = make(map[string][][]byte)
  38. }
  39. }
  40. func (l *githubSourceCodeLoader) Unlock() {
  41. l.mut.Unlock()
  42. }
  43. func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]byte, int) {
  44. filename = filepath.ToSlash(filename)
  45. lines, ok := l.cache[l.version][filename]
  46. if !ok {
  47. // Cache whatever we managed to find (or nil if nothing, so we don't try again)
  48. defer func() {
  49. l.cache[l.version][filename] = lines
  50. }()
  51. knownPrefixes := []string{"/lib/", "/cmd/"}
  52. var idx int
  53. for _, pref := range knownPrefixes {
  54. idx = strings.Index(filename, pref)
  55. if idx >= 0 {
  56. break
  57. }
  58. }
  59. if idx == -1 {
  60. return nil, 0
  61. }
  62. url := urlPrefix + l.version + filename[idx:]
  63. resp, err := l.client.Get(url)
  64. if err != nil {
  65. fmt.Println("Loading source:", err)
  66. return nil, 0
  67. }
  68. if resp.StatusCode != http.StatusOK {
  69. fmt.Println("Loading source:", resp.Status)
  70. return nil, 0
  71. }
  72. data, err := io.ReadAll(resp.Body)
  73. _ = resp.Body.Close()
  74. if err != nil {
  75. fmt.Println("Loading source:", err.Error())
  76. return nil, 0
  77. }
  78. lines = bytes.Split(data, []byte{'\n'})
  79. }
  80. return getLineFromLines(lines, line, context)
  81. }
  82. func getLineFromLines(lines [][]byte, line, context int) ([][]byte, int) {
  83. if lines == nil {
  84. // cached error from ReadFile: return no lines
  85. return nil, 0
  86. }
  87. line-- // stack trace lines are 1-indexed
  88. start := line - context
  89. var idx int
  90. if start < 0 {
  91. start = 0
  92. idx = line
  93. } else {
  94. idx = context
  95. }
  96. end := line + context + 1
  97. if line >= len(lines) {
  98. return nil, 0
  99. }
  100. if end > len(lines) {
  101. end = len(lines)
  102. }
  103. return lines[start:end], idx
  104. }