forked from
tangled.org/core
Monorepo for Tangled
1package xrpc
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "math"
8 "net/http"
9 "runtime/pprof"
10 "time"
11
12 "github.com/bluesky-social/indigo/atproto/atclient"
13 "github.com/bluesky-social/indigo/atproto/syntax"
14 "tangled.org/core/api/tangled"
15 "tangled.org/core/knotserver/git"
16)
17
18const (
19 RepoLanguagesByDid = "git_list_languages:repo:%s:%s"
20 RepoLanguagesTTL = 24 * time.Hour
21)
22
23func (x *Xrpc) ListLanguages(w http.ResponseWriter, r *http.Request) {
24 var (
25 repoQuery = r.URL.Query().Get("repo")
26 ref = r.URL.Query().Get("ref")
27 )
28 l := x.logger.With("method", "git.listLanguages", "repo", repoQuery, "ref", ref)
29 l.Debug("request")
30
31 repo, err := syntax.ParseATURI(repoQuery)
32 if err != nil || repo.RecordKey() == "" {
33 l.Error("invalid repo at-uri", "err", err)
34 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)})
35 return
36 }
37
38 if val, err := x.rdb.Get(r.Context(), fmt.Sprintf(RepoLanguagesByDid, repo, ref)).Result(); err == nil {
39 l.Debug("served from cache")
40 var langs []*tangled.GitTempListLanguages_Language
41 err = json.Unmarshal([]byte(val), &langs)
42 if err == nil {
43 writeJson(w, http.StatusOK, &tangled.GitTempListLanguages_Output{
44 Ref: ref,
45 Languages: langs,
46 })
47 return
48 }
49 }
50
51 var out *tangled.GitTempListLanguages_Output
52 pprof.Do(r.Context(), pprof.Labels("repo", repo.String()), func(ctx context.Context) {
53 out, err = x.listLanguages(ctx, repo, ref)
54 })
55 if err != nil {
56 l.Warn("local mirror failed, trying proxy", "err", err)
57 if x.proxyToKnot(w, r, repo) {
58 return
59 }
60 writeErr(w, err)
61 return
62 }
63
64 writeJson(w, http.StatusOK, out)
65}
66
67func (x *Xrpc) listLanguages(ctx context.Context, repo syntax.ATURI, ref string) (*tangled.GitTempListLanguages_Output, error) {
68 repoPath, err := x.makeRepoPath(ctx, repo)
69 if err != nil {
70 return nil, fmt.Errorf("resolving repo at-uri: %w", err)
71 }
72
73 gr, err := git.Open(repoPath, ref)
74 if err != nil {
75 return nil, &atclient.APIError{StatusCode: http.StatusNotFound, Name: "RepoNotFound", Message: "failed to find git repo"}
76 }
77
78 ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
79 defer cancel()
80
81 sizes, err := gr.AnalyzeLanguages(ctx)
82 if err != nil {
83 return nil, fmt.Errorf("analyzing languages: %w", err)
84 }
85
86 langs := sizesToLanguages(sizes)
87
88 go func() {
89 ctx := context.Background()
90 encoded, err := json.Marshal(langs)
91 if err != nil {
92 return
93 }
94 x.rdb.Set(ctx, fmt.Sprintf(RepoLanguagesByDid, repo, ref), encoded, RepoLanguagesTTL)
95 }()
96
97 return &tangled.GitTempListLanguages_Output{
98 Ref: ref,
99 Languages: sizesToLanguages(sizes),
100 }, nil
101}
102
103func sizesToLanguages(sizes git.LangBreakdown) []*tangled.GitTempListLanguages_Language {
104 var apiLanguages []*tangled.GitTempListLanguages_Language
105 var totalSize int64
106 for _, size := range sizes {
107 totalSize += size
108 }
109
110 for name, size := range sizes {
111 percentagef64 := float64(size) / float64(totalSize) * 100
112 percentage := math.Round(percentagef64)
113
114 lang := &tangled.GitTempListLanguages_Language{
115 Name: name,
116 Size: size,
117 Percentage: int64(percentage),
118 }
119
120 apiLanguages = append(apiLanguages, lang)
121 }
122
123 return apiLanguages
124}