diff --git a/asset.go b/asset.go
new file mode 100644
index 0000000..8a7e8f7
--- /dev/null
+++ b/asset.go
@@ -0,0 +1,18 @@
+package pypihub
+
+import "fmt"
+
+type Asset struct {
+ ID int
+ Name string
+ Owner string
+ Repo string
+}
+
+func (a Asset) String() string {
+ return a.Name
+}
+
+func (a Asset) URL() string {
+ return fmt.Sprintf("/%s/%s/%d/%s", a.Owner, a.Repo, a.ID, a.Name)
+}
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..45fd47b
--- /dev/null
+++ b/client.go
@@ -0,0 +1,100 @@
+package pypihub
+
+import (
+ "io"
+ "net/http"
+ "strings"
+
+ "github.com/google/go-github/github"
+)
+
+type Client struct {
+ config Config
+ client *github.Client
+ repos []string
+}
+
+func NewClient(cfg Config) *Client {
+ var t = github.BasicAuthTransport{
+ Username: cfg.Username,
+ Password: cfg.AccessToken,
+ }
+ return &Client{
+ config: cfg,
+ client: github.NewClient(t.Client()),
+ repos: cfg.RepoNames,
+ }
+}
+
+func (c *Client) splitRepoName(r string) (string, string) {
+ var p = strings.SplitN(r, "/", 2)
+ switch len(p) {
+ case 2:
+ return p[0], p[1]
+ case 1:
+ return c.config.Username, p[0]
+ default:
+ return "", ""
+ }
+}
+
+func (c *Client) GetRepoAssets(r string) ([]Asset, error) {
+ var owner, repo string
+ owner, repo = c.splitRepoName(r)
+
+ var releases []*github.RepositoryRelease
+ var err error
+ releases, _, err = c.client.Repositories.ListReleases(owner, repo, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var allAssets = make([]Asset, 0)
+ for _, rel := range releases {
+ var assets []*github.ReleaseAsset
+ assets, _, err = c.client.Repositories.ListReleaseAssets(owner, repo, *rel.ID, nil)
+ if err != nil {
+ return nil, err
+ }
+ for _, a := range assets {
+ allAssets = append(allAssets, Asset{
+ ID: *a.ID,
+ Name: *a.Name,
+ Owner: owner,
+ Repo: repo,
+ })
+ }
+ }
+ return allAssets, nil
+}
+
+func (c *Client) GetAllAssets() ([]Asset, error) {
+ var allAssets = make([]Asset, 0)
+ for _, r := range c.config.RepoNames {
+ var repoAssets = make([]Asset, 0)
+ var err error
+ repoAssets, err = c.GetRepoAssets(r)
+ if err != nil {
+ return nil, err
+ }
+ allAssets = append(allAssets, repoAssets...)
+ }
+
+ return allAssets, nil
+}
+
+func (c *Client) DownloadAsset(a Asset) (io.ReadCloser, error) {
+ var rc io.ReadCloser
+ var redirect string
+ var err error
+ rc, redirect, err = c.client.Repositories.DownloadReleaseAsset(a.Owner, a.Repo, a.ID)
+ if rc == nil {
+ var resp *http.Response
+ resp, err = http.Get(redirect)
+ if err != nil {
+ return nil, err
+ }
+ rc = resp.Body
+ }
+ return rc, err
+}
diff --git a/cmd/pypihub/main.go b/cmd/pypihub/main.go
index cee3f34..32651bd 100644
--- a/cmd/pypihub/main.go
+++ b/cmd/pypihub/main.go
@@ -10,4 +10,8 @@ func main() {
var config pypihub.Config
config = pypihub.ParseConfig()
fmt.Printf("%#v\r\n", config)
+
+ var server *pypihub.Server
+ server = pypihub.NewServer(config)
+ panic(server.ListenAndServe())
}
diff --git a/config.go b/config.go
index 4d4c54d..40238a4 100644
--- a/config.go
+++ b/config.go
@@ -26,7 +26,7 @@ func ParseConfig() Config {
config.RepoNames = append(config.RepoNames, strings.Split(val, " ")...)
}
for i := range config.RepoNames {
- config.RepoNames[i] = strings.Trim(config.RepoNames[i], " ")
+ config.RepoNames[i] = strings.TrimSpace(config.RepoNames[i])
}
config.RepoNames = uniqueSlice(config.RepoNames)
diff --git a/server.go b/server.go
index 4b61034..55276bd 100644
--- a/server.go
+++ b/server.go
@@ -1 +1,162 @@
package pypihub
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type Server struct {
+ client *Client
+ config Config
+ assets []Asset
+ timer *time.Timer
+}
+
+func NewServer(cfg Config) *Server {
+ return &Server{
+ client: NewClient(cfg),
+ config: cfg,
+ assets: make([]Asset, 0),
+ }
+}
+
+func (s *Server) findAsset(owner string, repo string, id int) *Asset {
+ for _, a := range s.assets {
+ if a.Owner == owner && a.Repo == repo && a.ID == id {
+ return &a
+ }
+ }
+ return nil
+}
+
+func (s *Server) refetchAssets() {
+ var err error
+ s.assets, err = s.client.GetAllAssets()
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (s *Server) startTimer() {
+ if s.timer != nil {
+ s.timer.Stop()
+ }
+ s.timer = time.AfterFunc(time.Duration(5*time.Minute), func() {
+ go s.refetchAssets()
+ })
+}
+
+func (s *Server) listAssets(w http.ResponseWriter, r *http.Request) {
+ var repo = strings.Trim(r.URL.Path, "/")
+
+ w.Header().Add("Content-Type", "text/html")
+ fmt.Fprintf(w, "
Links for %s", repo)
+ fmt.Fprintf(w, "Links for %s
", repo)
+ for _, a := range s.assets {
+ if a.Repo == repo {
+ fmt.Fprintf(w, "%s ", a.URL(), a.Name)
+ }
+ }
+ fmt.Fprintf(w, "")
+}
+
+func (s *Server) listAllAssets(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-Type", "text/html")
+ fmt.Fprintf(w, "All asset links")
+ fmt.Fprintf(w, "All asset links
")
+ for _, a := range s.assets {
+ fmt.Fprintf(w, "%s ", a.URL(), a.Name)
+ }
+ fmt.Fprintf(w, "")
+}
+
+func (s *Server) listRepos(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-Type", "text/html")
+ fmt.Fprintf(w, "Simple index")
+ for _, r := range s.config.RepoNames {
+ var parts = strings.SplitN(r, "/", 2)
+ fmt.Fprintf(w, "%s ", parts[1], parts[1])
+ }
+ fmt.Fprintf(w, "")
+}
+
+func (s *Server) fetchAsset(w http.ResponseWriter, r *http.Request) {
+ var url = strings.Trim(r.URL.Path, "/")
+ var parts = strings.SplitN(url, "/", 4)
+
+ if len(parts) != 4 {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ var asset *Asset
+ var id int64
+ var err error
+ id, err = strconv.ParseInt(parts[2], 10, 32)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ asset = s.findAsset(parts[0], parts[1], int(id))
+ if asset == nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ var rc io.ReadCloser
+ rc, err = s.client.DownloadAsset(*asset)
+ if err != nil || rc == nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ defer rc.Close()
+ io.Copy(w, rc)
+}
+
+func (s *Server) serveFavicon(w http.ResponseWriter, r *http.Request) {
+ var decoded []byte
+ var err error
+ decoded, err = base64.StdEncoding.DecodeString("AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAPs7/AJ9vNwBO3f8APMz/AEzb/wCdbjYAStn/AJZqNgBI1/8ARtX/AETT/wBA0f8AqnY3AD7P/wCjcjcApHI3ADzN/wBQ3v8AOsv/AE7c/wA4yf8Amm02ALF6OACYazYARtb/AETU/wCveTcAQtL/AKh1NwCtdzcAQND/AKFxNwCmczcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcDA4RExUAAAAAAAAAAAAZGhwfDhEAFQAAAAAAAAAACRkaHB8OABMAAAAAAAAAAAcJGRocHw4RAAAAAAAgAgYFBwkZGhwfARETFQAhDyACAwUHCQoaHB8BERMVHSEPIBIUBQcJChocHwEREw0dIQ8AEhQFBwkKGhwfAQQeDR0hDyACBhYYCAALHB8BGx4NHSEPIAIGFhgICgscHxcbHg0dIRAgAgYWGAkKCxwAFxseDR0hECACBhYHCQoAAAAAAB4NHSEQIAIGAAAAAAAAAAAbAA0dIRAgAgAAAAAAAAAAFwAeDR0hECAAAAAAAAAAAAAXGx4NHSEAAAAAAPgfAADwLwAA8C8AAPAPAACAAQAAAAAAAAAAAAAIAAAAABAAAAAAAAAAAAAAgAEAAPAPAAD0DwAA9A8AAPgfAAA=")
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ fmt.Fprintf(w, "%s", decoded)
+}
+
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ var parts = strings.Split(strings.Trim(r.URL.Path, "/"), "/")
+ parts = removeEmpty(parts)
+
+ fmt.Println(r.URL.Path)
+
+ switch len(parts) {
+ case 0:
+ s.listAllAssets(w, r)
+ case 1:
+ if parts[0] == "simple" {
+ s.listRepos(w, r)
+ } else if parts[0] == "favicon.ico" {
+ s.serveFavicon(w, r)
+ } else {
+ s.listAssets(w, r)
+ }
+ default:
+ s.fetchAsset(w, r)
+ }
+}
+
+func (s *Server) ListenAndServe() error {
+ s.refetchAssets()
+ s.startTimer()
+
+ http.Handle("/", s)
+ fmt.Println("Server listening at", s.config.Bind)
+ return http.ListenAndServe(s.config.Bind, nil)
+}
diff --git a/utils.go b/utils.go
index 6036711..4bea6cc 100644
--- a/utils.go
+++ b/utils.go
@@ -12,3 +12,13 @@ func uniqueSlice(s []string) []string {
}
return o
}
+
+func removeEmpty(s []string) []string {
+ var n = make([]string, 0)
+ for _, c := range s {
+ if c != "" {
+ n = append(n, c)
+ }
+ }
+ return n
+}