forked from gojp/goreportcard
/
check.go
205 lines (178 loc) · 5.1 KB
/
check.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package handlers
import (
"container/heap"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"golang.org/x/tools/go/vcs"
"github.com/boltdb/bolt"
)
const (
// DBPath is the relative (or absolute) path to the bolt database file
DBPath string = "goreportcard.db"
// RepoBucket is the bucket in which repos will be cached in the bolt DB
RepoBucket string = "repos"
// MetaBucket is the bucket containing the names of the projects with the
// top 100 high scores, and other meta information
MetaBucket string = "meta"
)
// CheckHandler handles the request for checking a repo
func CheckHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
repo := r.FormValue("repo")
repoRoot, err := vcs.RepoRootForImportPath(repo, true)
if err != nil || repoRoot.Root == "" || repoRoot.Repo == "" {
log.Println("Failed to create repoRoot:", repoRoot, err)
b, marshalErr := json.Marshal("Please enter a valid 'go get'-able package name")
if marshalErr != nil {
log.Println("JSON marshal error:", marshalErr)
}
w.WriteHeader(http.StatusBadRequest)
w.Write(b)
return
}
log.Printf("Checking repo %q...", repo)
forceRefresh := r.Method != "GET" // if this is a GET request, try to fetch from cached version in boltdb first
resp, err := newChecksResp(repo, forceRefresh)
if err != nil {
log.Println("ERROR: from newChecksResp:", err)
b, marshalErr := json.Marshal("Could not go get the repository.")
if marshalErr != nil {
log.Println("JSON marshal error:", marshalErr)
}
w.WriteHeader(http.StatusBadRequest)
w.Write(b)
return
}
respBytes, err := json.Marshal(resp)
if err != nil {
log.Println("ERROR: could not marshal json:", err)
http.Error(w, err.Error(), 500)
return
}
// write to boltdb
db, err := bolt.Open(DBPath, 0755, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
log.Println("Failed to open bolt database: ", err)
return
}
defer db.Close()
// is this a new repo? if so, increase the count in the high scores bucket later
isNewRepo := false
err = db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(RepoBucket))
if b == nil {
return fmt.Errorf("repo bucket not found")
}
isNewRepo = b.Get([]byte(repo)) == nil
return nil
})
if err != nil {
log.Println(err)
}
// if this is a new repo, or the user force-refreshed, update the cache
if isNewRepo || forceRefresh {
err = db.Update(func(tx *bolt.Tx) error {
log.Printf("Saving repo %q to cache...", repo)
b := tx.Bucket([]byte(RepoBucket))
if b == nil {
return fmt.Errorf("repo bucket not found")
}
// save repo to cache
err = b.Put([]byte(repo), respBytes)
if err != nil {
return err
}
// fetch meta-bucket
mb := tx.Bucket([]byte(MetaBucket))
if mb == nil {
return fmt.Errorf("high score bucket not found")
}
// update total repos count
if isNewRepo {
err = updateReposCount(mb, resp, repo)
if err != nil {
return err
}
}
return updateHighScores(mb, resp, repo)
})
if err != nil {
log.Println("Bolt writing error:", err)
}
}
b, marshalErr := json.Marshal(map[string]string{"redirect": "/report/" + repo})
if marshalErr != nil {
log.Println("JSON marshal error:", marshalErr)
}
w.WriteHeader(http.StatusOK)
w.Write(b)
return
}
func updateHighScores(mb *bolt.Bucket, resp checksResp, repo string) error {
// check if we need to update the high score list
if resp.Files < 100 {
// only repos with >= 100 files are considered for the high score list
return nil
}
// start updating high score list
scoreBytes := mb.Get([]byte("scores"))
if scoreBytes == nil {
scoreBytes, _ = json.Marshal([]scoreHeap{})
}
scores := &scoreHeap{}
json.Unmarshal(scoreBytes, scores)
heap.Init(scores)
if len(*scores) > 0 && (*scores)[0].Score > resp.Average*100.0 && len(*scores) == 50 {
// lowest score on list is higher than this repo's score, so no need to add, unless
// we do not have 50 high scores yet
return nil
}
// if this repo is already in the list, remove the original entry:
for i := range *scores {
if (*scores)[i].Repo == repo {
heap.Remove(scores, i)
break
}
}
// now we can safely push it onto the heap
heap.Push(scores, scoreItem{
Repo: repo,
Score: resp.Average * 100.0,
Files: resp.Files,
})
if len(*scores) > 50 {
// trim heap if it's grown to over 50
*scores = (*scores)[1:51]
}
scoreBytes, err := json.Marshal(&scores)
if err != nil {
return err
}
err = mb.Put([]byte("scores"), scoreBytes)
if err != nil {
return err
}
return nil
}
func updateReposCount(mb *bolt.Bucket, resp checksResp, repo string) (err error) {
log.Printf("New repo %q, adding to repo count...", repo)
totalInt := 0
total := mb.Get([]byte("total_repos"))
if total != nil {
err = json.Unmarshal(total, &totalInt)
if err != nil {
return fmt.Errorf("could not unmarshal total repos count: %v", err)
}
}
totalInt++ // increase repo count
total, err = json.Marshal(totalInt)
if err != nil {
return fmt.Errorf("could not marshal total repos count: %v", err)
}
mb.Put([]byte("total_repos"), total)
log.Println("Repo count is now", totalInt)
return nil
}