From 92120524a09f9014012b28d2681e0040661d9b82 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Wed, 21 Dec 2016 10:03:28 -0500 Subject: [PATCH] support non-published releases --- asset.go | 25 ++++++-- client.go | 75 ++++++++++++++++++++++ cmd/pypihub/main.go | 12 ++-- router.go | 150 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+), 10 deletions(-) create mode 100644 router.go diff --git a/asset.go b/asset.go index 8a7e8f7..4bd2f83 100644 --- a/asset.go +++ b/asset.go @@ -1,12 +1,17 @@ package pypihub -import "fmt" +import ( + "fmt" + "io" +) type Asset struct { - ID int - Name string - Owner string - Repo string + ID int + Name string + Owner string + Repo string + Ref string + Format string } func (a Asset) String() string { @@ -14,5 +19,13 @@ func (a Asset) String() string { } func (a Asset) URL() string { - return fmt.Sprintf("/%s/%s/%d/%s", a.Owner, a.Repo, a.ID, a.Name) + return fmt.Sprintf("/%s/%s/%s", a.Owner, a.Repo, a.Name) +} + +func (a Asset) Download(c *Client) (io.ReadCloser, error) { + if a.Ref != "" && a.Format != "" { + return c.DownloadArchive(a) + } + + return c.DownloadAsset(a) } diff --git a/client.go b/client.go index 45fd47b..a1f9bc4 100644 --- a/client.go +++ b/client.go @@ -1,8 +1,11 @@ package pypihub import ( + "fmt" "io" + "log" "net/http" + "net/url" "strings" "github.com/google/go-github/github" @@ -38,6 +41,32 @@ func (c *Client) splitRepoName(r string) (string, string) { } } +func (c *Client) getRepoTagAssets(owner string, repo string) ([]Asset, error) { + var tags []*github.RepositoryTag + var err error + tags, _, err = c.client.Repositories.ListTags(owner, repo, nil) + if err != nil { + return nil, err + } + + var allAssets = make([]Asset, 0) + for _, tag := range tags { + // Remove any `v` prefix, e.g. `v1.0.0` -> `1.0.0` + var name = strings.Trim(*tag.Name, "v") + name = fmt.Sprintf("%s-%s.tar.gz", repo, name) + allAssets = append(allAssets, Asset{ + Name: name, + Owner: owner, + Repo: repo, + Ref: *tag.Name, + Format: "tarball", + }) + log.Printf("found asset %s/%s/%s/%s", owner, repo, *tag.Name, name) + } + + return allAssets, nil +} + func (c *Client) GetRepoAssets(r string) ([]Asset, error) { var owner, repo string owner, repo = c.splitRepoName(r) @@ -49,6 +78,10 @@ func (c *Client) GetRepoAssets(r string) ([]Asset, error) { return nil, err } + if len(releases) == 0 { + return c.getRepoTagAssets(owner, repo) + } + var allAssets = make([]Asset, 0) for _, rel := range releases { var assets []*github.ReleaseAsset @@ -56,14 +89,35 @@ func (c *Client) GetRepoAssets(r string) ([]Asset, error) { if err != nil { return nil, err } + + var hasTar = false for _, a := range assets { + if strings.HasSuffix(*a.Name, ".tar.gz") { + hasTar = true + } allAssets = append(allAssets, Asset{ ID: *a.ID, Name: *a.Name, Owner: owner, Repo: repo, }) + log.Printf("found asset %s/%s/%s/%s", owner, repo, *rel.Name, *a.Name) } + + if hasTar == false { + // Remove any `v` prefix, e.g. `v1.0.0` -> `1.0.0` + var name = strings.Trim(*rel.Name, "v") + name = fmt.Sprintf("%s-%s.tar.gz", repo, name) + allAssets = append(allAssets, Asset{ + Name: name, + Owner: owner, + Repo: repo, + Ref: *rel.TagName, + Format: "tarball", + }) + log.Printf("found asset %s/%s/%s/%s", owner, repo, *rel.Name, name) + } + } return allAssets, nil } @@ -98,3 +152,24 @@ func (c *Client) DownloadAsset(a Asset) (io.ReadCloser, error) { } return rc, err } + +func (c *Client) DownloadArchive(a Asset) (io.ReadCloser, error) { + var f = github.Tarball + if a.Format == "zipball" { + f = github.Zipball + } + + var u *url.URL + var err error + u, _, err = c.client.Repositories.GetArchiveLink(a.Owner, a.Repo, f, nil) + if err != nil { + return nil, err + } + + var resp *http.Response + resp, err = http.Get(u.String()) + if err != nil { + return nil, err + } + return resp.Body, nil +} diff --git a/cmd/pypihub/main.go b/cmd/pypihub/main.go index f0d4870..789c5de 100644 --- a/cmd/pypihub/main.go +++ b/cmd/pypihub/main.go @@ -1,12 +1,16 @@ package main -import "github.com/brettlangdon/pypihub" +import ( + "log" + + "github.com/brettlangdon/pypihub" +) func main() { var config pypihub.Config config = pypihub.ParseConfig() - var server *pypihub.Server - server = pypihub.NewServer(config) - panic(server.ListenAndServe()) + var router *pypihub.Router + router = pypihub.NewRouter(config) + log.Fatal(router.Start()) } diff --git a/router.go b/router.go new file mode 100644 index 0000000..7d60f1b --- /dev/null +++ b/router.go @@ -0,0 +1,150 @@ +package pypihub + +import ( + "fmt" + "io" + "log" + "net/http" + "strings" + "time" + + "github.com/gorilla/mux" +) + +type Router struct { + config Config + client *Client + assets []Asset + timer *time.Timer +} + +func NewRouter(config Config) *Router { + return &Router{ + config: config, + client: NewClient(config), + assets: make([]Asset, 0), + } +} + +func (r *Router) refetchAssets() { + go r.startAssetsTimer() + + var err error + r.assets, err = r.client.GetAllAssets() + if err != nil { + log.Println(err) + } +} + +func (r *Router) startAssetsTimer() { + if r.timer != nil { + r.timer.Stop() + } + r.timer = time.AfterFunc(5*time.Minute, r.refetchAssets) +} + +func (r *Router) handleSimple(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "Simple index") + var projects = make(map[string]bool) + for _, a := range r.assets { + projects[strings.ToLower(a.Repo)] = true + } + + for project := range projects { + fmt.Fprintf(w, "%s ", project, project) + } + fmt.Fprintf(w, "") +} + +func (r *Router) handleIndex(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "Links for all projects") + fmt.Fprintf(w, "

Links for all projects

") + for _, a := range r.assets { + fmt.Fprintf(w, "%s ", a.URL(), a.Name) + } + fmt.Fprintf(w, "") +} + +func (r *Router) handleFavicon(w http.ResponseWriter, req *http.Request) { + +} + +func (r *Router) handleOwnerIndex(w http.ResponseWriter, req *http.Request) { + var vars map[string]string + vars = mux.Vars(req) + var owner = strings.ToLower(vars["owner"]) + + fmt.Fprintf(w, "Packages for %s", owner) + fmt.Fprintf(w, "

Links for %s projects

", owner) + for _, a := range r.assets { + if strings.ToLower(a.Owner) == owner { + fmt.Fprintf(w, "%s ", a.URL(), a.Name) + } + } + fmt.Fprintf(w, "") +} + +func (r *Router) handleRepoIndex(w http.ResponseWriter, req *http.Request) { + var vars map[string]string + vars = mux.Vars(req) + var owner = strings.ToLower(vars["owner"]) + var repo = strings.ToLower(vars["repo"]) + + fmt.Fprintf(w, "Packages for %s/%s", owner, repo) + for _, a := range r.assets { + if strings.ToLower(a.Owner) == owner && strings.ToLower(a.Repo) == repo { + fmt.Fprintf(w, "%s ", a.URL(), a.Name) + } + } + fmt.Fprintf(w, "") +} + +func (r *Router) handleFetchAsset(w http.ResponseWriter, req *http.Request) { + var vars map[string]string + vars = mux.Vars(req) + var owner = strings.ToLower(vars["owner"]) + var repo = strings.ToLower(vars["repo"]) + var asset = vars["asset"] + + for _, a := range r.assets { + if strings.ToLower(a.Owner) == owner && strings.ToLower(a.Repo) == repo && a.Name == asset { + var rc io.ReadCloser + var err error + rc, err = a.Download(r.client) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + io.Copy(w, rc) + return + } + } + + w.WriteHeader(http.StatusNotFound) +} + +func (r *Router) logRequests(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + log.Println(req.Method, req.URL.Path) + h.ServeHTTP(w, req) + }) +} + +func (r *Router) Handler() http.Handler { + var h *mux.Router + h = mux.NewRouter() + h.HandleFunc("/", r.handleIndex).Methods("GET") + h.HandleFunc("/favicon.ico", r.handleFavicon).Methods("GET") + h.HandleFunc("/simple", r.handleSimple).Methods("GET") + h.HandleFunc("/{owner}", r.handleOwnerIndex).Methods("GET") + h.HandleFunc("/{owner}/{repo}", r.handleRepoIndex).Methods("GET") + h.HandleFunc("/{owner}/{repo}", r.handleRepoIndex).Methods("GET") + h.HandleFunc("/{owner}/{repo}/{asset}", r.handleFetchAsset).Methods("GET") + return r.logRequests(h) +} + +func (r *Router) Start() error { + r.refetchAssets() + http.Handle("/", r.Handler()) + return http.ListenAndServe(r.config.Bind, nil) +}