Skip to content

Commit

Permalink
Include GCS and cache integration
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigo-brito committed Oct 22, 2018
1 parent d726cac commit 1f06d1b
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 38 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -14,4 +14,5 @@
.vscode
.idea

*/**/node_modules
*/**/node_modules
gcs-credentials.json
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -3,7 +3,7 @@ dev-dependencies:
go install github.com/canthefason/go-watcher/cmd/watcher

watcher: dev-dependencies
watcher # github.com/canthefason/go-watcher
GOOGLE_APPLICATION_CREDENTIALS=`pwd`/gcs-credentials.json watcher # github.com/canthefason/go-watcher

run:
docker run -ti -v`pwd`:/go/src/github.com/rodrigo-brito/gocity -p80:4000 -d -w /go/src/github.com/rodrigo-brito/gocity golang go run main.go
22 changes: 7 additions & 15 deletions analyzer/analyzer.go
@@ -1,11 +1,11 @@
package analyzer

import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"strings"
Expand All @@ -15,8 +15,6 @@ import (
"github.com/rodrigo-brito/gocity/lib"
)

var ErrInvalidPackage = errors.New("invalid package")

type Analyzer interface {
FetchPackage() error
Analyze() (map[string]*NodeInfo, error)
Expand All @@ -25,13 +23,15 @@ type Analyzer interface {
type analyzer struct {
PackageName string
IgnoreNodes []string
fetcher lib.Fetcher
}

type Option func(a *analyzer)

func NewAnalyzer(packageName string, options ...Option) Analyzer {
analyzer := &analyzer{
PackageName: packageName,
fetcher: lib.NewFetcher(),
}

for _, option := range options {
Expand All @@ -48,17 +48,7 @@ func WithIgnoreList(files ...string) Option {
}

func (p *analyzer) FetchPackage() error {
fetcher := lib.NewFetcher()
ok, err := fetcher.Fetch(p.PackageName)
if err != nil {
return err
}

if !ok {
return ErrInvalidPackage
}

return nil
return p.fetcher.Fetch(p.PackageName)
}

func (p *analyzer) IsInvalidPath(path string) bool {
Expand All @@ -84,7 +74,9 @@ func (a *analyzer) Analyze() (map[string]*NodeInfo, error) {

file, err := parser.ParseFile(fileSet, path, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("invalid input %s: %s", path, err)
// TODO: Logo with project information
log.Printf("invalid file %s: %s", path, err)
return nil
}

v := &Visitor{
Expand Down
6 changes: 5 additions & 1 deletion analyzer/visitor.go
Expand Up @@ -63,7 +63,11 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor {
if ident, ok := typeObj.(*ast.Ident); ok {
structName = ident.Name
} else {
structName = typeObj.(*ast.StarExpr).X.(*ast.Ident).Name
if ident, ok := typeObj.(*ast.StarExpr).X.(*ast.Ident); ok {
structName = ident.Name
} else if ident, ok := typeObj.(*ast.StarExpr).X.(*ast.SelectorExpr); ok {
structName = ident.Sel.Name
}
}
}

Expand Down
52 changes: 52 additions & 0 deletions lib/cache.go
@@ -0,0 +1,52 @@
package lib

import (
"time"

"github.com/karlseguin/ccache"
)

type Cache interface {
Get(key string) (bool, []byte)
Set(key string, value []byte, TTL time.Duration)
GetSet(key string, set func() ([]byte, error), TTL time.Duration) ([]byte, error)
}

func NewCache() Cache {
return &cache{
client: ccache.New(ccache.Configure()),
}
}

type cache struct {
client *ccache.Cache
}

func (c *cache) Get(key string) (bool, []byte) {
item := c.client.Get(key)
if item != nil {
return true, item.Value().([]byte)
}

return false, nil
}

func (c *cache) Set(key string, value []byte, TTL time.Duration) {
c.client.Set(key, value, TTL)
}

func (c *cache) GetSet(key string, getValue func() ([]byte, error), TTL time.Duration) ([]byte, error) {
hit, value := c.Get(key)
if hit {
return value, nil
}

value, err := getValue()
if err != nil {
return nil, err
}

c.Set(key, value, TTL)

return value, nil
}
15 changes: 11 additions & 4 deletions lib/fetch.go
Expand Up @@ -2,13 +2,14 @@ package lib

import (
"fmt"
"log"
"os"

git "gopkg.in/src-d/go-git.v4"
)

type Fetcher interface {
Fetch(packageName string) (bool, error)
Fetch(packageName string) error
}

func NewFetcher() Fetcher {
Expand All @@ -25,7 +26,7 @@ func (fetcher) packageFound(name string) bool {
return true
}

func (f *fetcher) Fetch(name string) (bool, error) {
func (f *fetcher) Fetch(name string) error {
gitAddress := fmt.Sprintf("https://%s", name)
folder := fmt.Sprintf("%s/src/%s", os.Getenv("GOPATH"), name)

Expand All @@ -36,8 +37,14 @@ func (f *fetcher) Fetch(name string) (bool, error) {
})

if err != nil && err != git.ErrRepositoryAlreadyExists {
return false, err
go func() {
if err := os.RemoveAll(folder); err != nil {
log.Printf("error on remove: %s", err)
}
}()

return err
}

return f.packageFound(name), nil
return nil
}
86 changes: 86 additions & 0 deletions lib/storage.go
@@ -0,0 +1,86 @@
package lib

import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"log"

"cloud.google.com/go/storage"
)

// Sets the name for the new bucket.
const bucketName = "gocity"

type Storage interface {
Get(projectName string) (bool, []byte, error)
Save(projectName string, content []byte) error
Delete(projectName string) error
}

type GCS struct {
ctx context.Context
client *storage.Client
}

func NewGCS(ctx context.Context) (Storage, error) {
client, err := storage.NewClient(ctx)
if err != nil {
return nil, err
}

return &GCS{
ctx: ctx,
client: client,
}, nil
}

func getObjectName(name string) string {
return fmt.Sprintf("%s.json", name)
}

func (g *GCS) Get(projectName string) (bool, []byte, error) {
object := g.client.Bucket(bucketName).Object(getObjectName(projectName))
if object == nil {
return false, nil, nil
}

reader, err := object.NewReader(g.ctx)
if err != nil {
if err == storage.ErrObjectNotExist {
log.Print("file not exists...")
return false, nil, nil
}

return false, nil, err
}
defer reader.Close()

data, err := ioutil.ReadAll(reader)
if err != nil {
return false, nil, err
}

return true, data, nil
}

func (g *GCS) Save(projectName string, content []byte) error {
client, err := storage.NewClient(g.ctx)
if err != nil {
return err
}

buffer := bytes.NewBuffer(content)
wc := client.Bucket(bucketName).Object(getObjectName(projectName)).NewWriter(g.ctx)
if _, err = io.Copy(wc, buffer); err != nil {
return err
}

return wc.Close()
}

func (g *GCS) Delete(projectName string) error {
return nil
}
65 changes: 51 additions & 14 deletions main.go
@@ -1,22 +1,29 @@
package main

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path/filepath"

"github.com/rodrigo-brito/gocity/model"
"time"

"github.com/go-chi/chi"
"github.com/go-chi/cors"
"github.com/rodrigo-brito/gocity/analyzer"
"github.com/rodrigo-brito/gocity/lib"
"github.com/rodrigo-brito/gocity/model"
)

func main() {
router := chi.NewRouter()
cache := lib.NewCache()
storage, err := lib.NewGCS(context.Background())
if err != nil {
log.Fatal(err)
}

cors := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
Expand All @@ -31,29 +38,59 @@ func main() {
router.Get("/api", func(w http.ResponseWriter, r *http.Request) {
projectName := r.URL.Query().Get("q")
if len(projectName) == 0 {
w.WriteHeader(http.StatusNotFound)
return
}

analyzer := analyzer.NewAnalyzer(projectName, analyzer.WithIgnoreList("/vendor/"))
err := analyzer.FetchPackage()
result, err := cache.GetSet(projectName, func() ([]byte, error) {
ok, data, err := storage.Get(projectName)
if err != nil {
return nil, err
}

if ok && len(data) > 0 {
return data, nil
}

analyzer := analyzer.NewAnalyzer(projectName, analyzer.WithIgnoreList("/vendor/"))
err = analyzer.FetchPackage()
if err != nil {
return nil, err
}

summary, err := analyzer.Analyze()
if err != nil {
return nil, err
}

body, err := json.Marshal(model.New(summary, projectName))
if err != nil {
return nil, err
}

// store result on Google Cloud Storage
go func() {
if err := storage.Save(projectName, body); err != nil {
log.Print(err)
}
}()

return body, nil
}, time.Hour*48)

if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
log.Print(err)
return
}

summary, err := analyzer.Analyze()
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
log.Printf("error on analyzetion %s", err)
if len(result) == 0 {
w.WriteHeader(http.StatusNotFound)
return
}

body, err := json.Marshal(model.New(summary, projectName))
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
log.Print(err)
}
w.Header().Set("Content-Type", "application/json")
w.Write(body)
w.Write(result)
})

workDir, _ := os.Getwd()
Expand Down

0 comments on commit 1f06d1b

Please sign in to comment.