diff --git a/handlers/check.go b/handlers/check.go index d4409c4..1826e50 100644 --- a/handlers/check.go +++ b/handlers/check.go @@ -2,12 +2,21 @@ package handlers import ( "encoding/json" + "fmt" "log" "net/http" "strings" + "time" - "gopkg.in/mgo.v2" - "gopkg.in/mgo.v2/bson" + "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" ) // CheckHandler handles the request for checking a repo @@ -31,26 +40,34 @@ func CheckHandler(w http.ResponseWriter, r *http.Request) { return } - b, err := json.Marshal(resp) + respBytes, err := json.Marshal(resp) if err != nil { log.Println("ERROR: could not marshal json:", err) http.Error(w, err.Error(), 500) return } - w.Write(b) + w.Write(respBytes) - // write to mongo - session, err := mgo.Dial(mongoURL) + // write to boltdb + db, err := bolt.Open(DBPath, 0755, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { - log.Println("Failed to get mongo collection: ", err) + log.Println("Failed to open bolt database: ", err) return } - defer session.Close() - coll := session.DB(mongoDatabase).C(mongoCollection) - log.Printf("Upserting repo %s...", repo) - _, err = coll.Upsert(bson.M{"repo": repo}, resp) + defer db.Close() + + log.Printf("Saving repo %q to cache...", repo) + + err = db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(RepoBucket)) + if b == nil { + return fmt.Errorf("Repo bucket not found") + } + return b.Put([]byte(repo), respBytes) + }) + if err != nil { - log.Println("Mongo writing error:", err) - return + log.Println("Bolt writing error:", err) } + return } diff --git a/handlers/checks.go b/handlers/checks.go index bf19dc9..f457e01 100644 --- a/handlers/checks.go +++ b/handlers/checks.go @@ -1,6 +1,9 @@ package handlers import ( + "encoding/binary" + "encoding/json" + "errors" "fmt" "log" "os" @@ -8,36 +11,51 @@ import ( "strings" "time" + "github.com/boltdb/bolt" "github.com/gojp/goreportcard/check" - "gopkg.in/mgo.v2" - "gopkg.in/mgo.v2/bson" -) - -var ( - mongoURL = "mongodb://127.0.0.1:27017" - mongoDatabase = "goreportcard" - mongoCollection = "reports" ) func getFromCache(repo string) (checksResp, error) { - // try and fetch from mongo - session, err := mgo.Dial(mongoURL) + // try and fetch from boltdb + db, err := bolt.Open(DBPath, 0600, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { - return checksResp{}, fmt.Errorf("Failed to get mongo collection during GET: %v", err) + return checksResp{}, fmt.Errorf("Failed to open bolt database during GET: %v", err) } - defer session.Close() - coll := session.DB(mongoDatabase).C(mongoCollection) + defer db.Close() + resp := checksResp{} - err = coll.Find(bson.M{"repo": repo}).One(&resp) + err = db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(RepoBucket)) + if b == nil { + return errors.New("No repo bucket") + } + cached := b.Get([]byte(repo)) + if cached == nil { + return fmt.Errorf("%q not found in cache", repo) + } + + err = json.Unmarshal(cached, &resp) + if err != nil { + return fmt.Errorf("failed to parse JSON for %q in cache", repo) + } + return nil + }) + if err != nil { - return checksResp{}, fmt.Errorf("Failed to fetch %q from mongo: %v", repo, err) + return resp, err } resp.LastRefresh = resp.LastRefresh.UTC() - return resp, nil } +// itob returns an 8-byte big endian representation of v. +func itob(v int) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(v)) + return b +} + type score struct { Name string `json:"name"` Description string `json:"description"` diff --git a/handlers/high_scores.go b/handlers/high_scores.go index 7021188..517bc35 100644 --- a/handlers/high_scores.go +++ b/handlers/high_scores.go @@ -2,13 +2,7 @@ package handlers import ( "fmt" - "log" "net/http" - "text/template" - - "github.com/dustin/go-humanize" - "gopkg.in/mgo.v2" - "gopkg.in/mgo.v2/bson" ) func add(x, y int) int { @@ -21,36 +15,36 @@ func formatScore(x float64) string { // HighScoresHandler handles the stats page func HighScoresHandler(w http.ResponseWriter, r *http.Request) { - session, err := mgo.Dial(mongoURL) - if err != nil { - log.Println("ERROR: could not get collection:", err) - http.Error(w, err.Error(), 500) - return - } - defer session.Close() - coll := session.DB(mongoDatabase).C(mongoCollection) - - var highScores []struct { - Repo string - Files int - Average float64 - } - err = coll.Find(bson.M{"files": bson.M{"$gt": 100}}).Sort("-average").Limit(50).All(&highScores) - if err != nil { - log.Println("ERROR: could not get high scores: ", err) - http.Error(w, err.Error(), 500) - return - } - - count, err := coll.Count() - if err != nil { - log.Println("ERROR: could not get count of high scores: ", err) - http.Error(w, err.Error(), 500) - return - } - - funcs := template.FuncMap{"add": add, "formatScore": formatScore} - t := template.Must(template.New("high_scores.html").Funcs(funcs).ParseFiles("templates/high_scores.html")) - - t.Execute(w, map[string]interface{}{"HighScores": highScores, "Count": humanize.Comma(int64(count))}) + // session, err := mgo.Dial(mongoURL) + // if err != nil { + // log.Println("ERROR: could not get collection:", err) + // http.Error(w, err.Error(), 500) + // return + // } + // defer session.Close() + // coll := session.DB(mongoDatabase).C(mongoCollection) + // + // var highScores []struct { + // Repo string + // Files int + // Average float64 + // } + // err = coll.Find(bson.M{"files": bson.M{"$gt": 100}}).Sort("-average").Limit(50).All(&highScores) + // if err != nil { + // log.Println("ERROR: could not get high scores: ", err) + // http.Error(w, err.Error(), 500) + // return + // } + // + // count, err := coll.Count() + // if err != nil { + // log.Println("ERROR: could not get count of high scores: ", err) + // http.Error(w, err.Error(), 500) + // return + // } + // + // funcs := template.FuncMap{"add": add, "formatScore": formatScore} + // t := template.Must(template.New("high_scores.html").Funcs(funcs).ParseFiles("templates/high_scores.html")) + // + // t.Execute(w, map[string]interface{}{"HighScores": highScores, "Count": humanize.Comma(int64(count))}) } diff --git a/main.go b/main.go index 01ea93b..b3e9478 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,9 @@ import ( "net/http" "os" "regexp" + "time" + "github.com/boltdb/bolt" "github.com/gojp/goreportcard/handlers" ) @@ -31,11 +33,32 @@ func makeHandler(name string, fn func(http.ResponseWriter, *http.Request, string } } +// initDB opens the bolt database file (or creates it if it does not exist), and creates +// a bucket for saving the repos, also only if it does not exist. +func initDB() error { + db, err := bolt.Open(handlers.DBPath, 0600, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return err + } + defer db.Close() + + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte(handlers.RepoBucket)) + return err + }) + return err +} + func main() { if err := os.MkdirAll("repos/src/github.com", 0755); err != nil && !os.IsExist(err) { log.Fatal("ERROR: could not create repos dir: ", err) } + // initialize database + if err := initDB(); err != nil { + log.Fatal("ERROR: could not open bolt db: ", err) + } + http.HandleFunc("/assets/", handlers.AssetsHandler) http.HandleFunc("/checks", handlers.CheckHandler) http.HandleFunc("/report/", makeHandler("report", handlers.ReportHandler))