Skip to content

Commit

Permalink
Added support for specific release installs.
Browse files Browse the repository at this point in the history
  • Loading branch information
ronoaldo committed Oct 13, 2021
1 parent db51e09 commit 500a3b2
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 19 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ To install a mod/modpack, use the `install` subcommand:

contentdb install mod rubenwardy/sfinv

Or alternativelly, specify a specific release to install:

contentdb install mod rubenwardy/sfinv@52

To update all mods in the mods folder (including those installed with git!), use the `update` subdommand:

contentdb update
Expand Down
49 changes: 48 additions & 1 deletion api/contentdb/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ type Package struct {
URL string `json:"url,omitempty"`
}

// PackageRelease is a single downloadable version of a package.
type PackageRelease struct {
ID int `json:"id,omitempty"`
Title string `json:"title,omitempty"`
ReleaseDate string `json:"release_date,omitempty"`
URL string `json:"url,omitempty"`
Commit string `json:"commit,omitempty"`
Downlads int `json:"downloads,omitempty"`
}

// Query can be used to filter out the content returned by ListPackages.
type Query struct {
Type string
Expand Down Expand Up @@ -177,10 +187,47 @@ func (c *Client) GetPackage(author, name string) (pkg *Package, err error) {
return pkg, err
}

func (c *Client) ListReleases(author, name string) (r []*PackageRelease, err error) {
resp, err := c.makeCall("GET", "/api/packages/"+author+"/"+name+"/releases/", nil, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err = json.NewDecoder(resp.Body).Decode(&r); err != nil {
return nil, err
}
return r, err
}

func (c *Client) GetRelease(author, name, release string) (r *PackageRelease, err error) {
resp, err := c.makeCall("GET", "/api/packages/"+author+"/"+name+"/releases/"+release, nil, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err = json.NewDecoder(resp.Body).Decode(&r); err != nil {
return nil, err
}
return r, err
}

// Download fetches the package archive from the ContentDB for the current revision.
func (c *Client) Download(author, name string) (*PackageArchive, error) {
return c.fetchArchive("/packages/" + author + "/" + name + "/download/")
}

// DownloadRelease fetches the provided package from the ContentDB in the specified revision.
func (c *Client) DownloadRelease(author, name, release string) (*PackageArchive, error) {
r, err := c.GetRelease(author, name, release)
if err != nil {
return nil, err
}
// We expect download URLs to be relative to the API endpoint.
return c.fetchArchive(r.URL)
}
func (c *Client) fetchArchive(path string) (*PackageArchive, error) {
start := time.Now()
resp, err := c.makeCall("GET", "/packages/"+author+"/"+name+"/download/", nil, nil)
resp, err := c.makeCall("GET", path, nil, nil)
if err != nil {
return nil, fmt.Errorf("contentdb: unable to download: %v", err)
}
Expand Down
72 changes: 62 additions & 10 deletions api/contentdb/packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package contentdb
import (
"context"
"encoding/json"
"io"
"log"
"net/http"
"net/http/httptest"
"os"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -39,17 +38,20 @@ func mockServer(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Too many requests", http.StatusInternalServerError)
return
}

if strings.Contains(r.URL.Path, "/sfinv/releases/6537") {
http.ServeFile(w, r, "./testdata/releases_6537.json")
return
} else if strings.Contains(r.URL.Path, "/sfinv/releases") {
http.ServeFile(w, r, "./testdata/releases.json")
return
}
if strings.HasSuffix(r.URL.Path, ".zip") {
fd, err := os.Open("./testdata" + r.URL.Path)
if err != nil {
http.Error(w, "Error opening file: "+err.Error(), http.StatusInternalServerError)
return
}
defer fd.Close()
io.Copy(w, fd)
http.ServeFile(w, r, "./testdata"+r.URL.Path)
return
}
http.Error(w, "Not found", http.StatusNotFound)
log.Printf("Not found: " + r.URL.Path)
http.Error(w, "Not found: "+r.URL.Path, http.StatusNotFound)
}

func TestListPackages(t *testing.T) {
Expand Down Expand Up @@ -183,3 +185,53 @@ func TestPackageDownload(t *testing.T) {
})
}
}

func TestDownloadRelease(t *testing.T) {
// setUp
testServer := httptest.NewServer(http.HandlerFunc(mockServer))
origHost := Host
Host = testServer.URL
if testing.Verbose() {
api.LogLevel = api.Debug
}

// tearDown
defer func() {
testServer.Close()
Host = origHost
}()

type args struct {
author string
name string
release string
}
tests := []struct {
name string
args args
wantBytesLen int
wantErr bool
}{
{
name: "can download specified release",
args: args{author: "rubenwardy", name: "sfinv", release: "6537"},
wantBytesLen: 38606,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := NewClient(context.Background())
got, err := c.DownloadRelease(tt.args.author, tt.args.name, tt.args.release)
if (err != nil) != tt.wantErr {
t.Errorf("Client.DownloadRelease() error = %v, wantErr %v", err, tt.wantErr)
return
}

if err != nil {
if len(got.Bytes()) != tt.wantBytesLen {
t.Errorf("Client.DownloadRelease().Bytes() = %v, want %v", len(got.Bytes()), tt.wantBytesLen)
}
}
})
}
}
22 changes: 22 additions & 0 deletions api/contentdb/testdata/releases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"commit": "556f70ed0e92a4a35bce67d3aee7e32eac20cf61",
"downloads": 1153,
"id": 6537,
"max_minetest_version": null,
"min_minetest_version": null,
"release_date": "2021-02-17T01:10:39.907286",
"title": "2021-02-17",
"url": "/sfinv.zip"
},
{
"commit": null,
"downloads": 3794,
"id": 52,
"max_minetest_version": null,
"min_minetest_version": null,
"release_date": "2018-05-24T15:06:28.476290",
"title": "2018-03-07",
"url": "/sfinv.zip"
}
]
10 changes: 10 additions & 0 deletions api/contentdb/testdata/releases_6537.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"commit": "556f70ed0e92a4a35bce67d3aee7e32eac20cf61",
"downloads": 1153,
"id": 6537,
"max_minetest_version": null,
"min_minetest_version": null,
"release_date": "2021-02-17T01:10:39.907286",
"title": "2021-02-17",
"url": "/sfinv.zip"
}
27 changes: 19 additions & 8 deletions cmd/contentdb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"text/tabwriter"

Expand Down Expand Up @@ -143,22 +144,33 @@ func installMod(mods []string) error {
return err
}

// Get package details
fmt.Println("Searching for package ", mod)
// Extract specified version.
release := ""
if strings.Count(mod, "@") == 1 {
// Use package version after @
parts := strings.Split(mod, "@")
mod, release = parts[0], parts[1]
}

fmt.Println("Fetching package details: ", mod)
s := strings.Split(mod, "/")
pkg, err = cdb.GetPackage(s[0], s[1])
if err != nil {
warnf("install: unable to find %v", mod)
return err
}
if release == "" {
// If no version was specified, use default version
// from package details
release = strconv.FormatInt(int64(pkg.Release), 10)
}

// Download zip file
fmt.Printf("Downloading %v/%v@%v ...\n", pkg.Author, pkg.Name, pkg.Release)
archive, err := cdb.Download(pkg.Author, pkg.Name)
// Download zip file for target release
fmt.Printf("Downloading %v/%v@%v ...\n", pkg.Author, pkg.Name, release)
archive, err := cdb.DownloadRelease(pkg.Author, pkg.Name, release)
if err != nil {
return err
}

pkgType := archive.Type()
if pkgType != contentdb.Mod && pkgType != contentdb.Modpack {
warnf("install: package is not a mod/modpack: %s", pkgType)
Expand Down Expand Up @@ -203,7 +215,7 @@ func installMod(mods []string) error {
modName := pkg.Name
cfg.Key("name").SetValue(modName)
cfg.Key("author").SetValue(pkg.Author)
cfg.Key("release").SetValue(fmt.Sprintf("%d", pkg.Release))
cfg.Key("release").SetValue(release)
if !cfg.HasKey("title") {
cfg.Key("title").SetValue(pkg.Title)
}
Expand Down Expand Up @@ -261,7 +273,6 @@ func installMod(mods []string) error {
}
}
green("Installed %v into %v\n", mod, destdir)
fmt.Printf("Add load_mod_%s = true to world.mt to use it.\n", pkg.Name)
fmt.Printf("* Dependencies: %v\n", cfg.Key("depends").String())
fmt.Printf("* Optional dependencies: %v\n", cfg.Key("optional_depends").String())
}
Expand Down

0 comments on commit 500a3b2

Please sign in to comment.