Skip to content

Commit

Permalink
cdb dependency install (#215)
Browse files Browse the repository at this point in the history
* cdb dependency install

* caching

* selected, resolved dep

* cdb

* better cdb caching

* debug logs

* fix typos / cleanup

* working dependency tree

* cdb

* recurse

* partially working resolution

* resolve

* cdb

* select_dep

* wip

* route props

* navbar component

* stats update

* cdb

* install

---------

Co-authored-by: BuckarooBanzay <[email protected]>
  • Loading branch information
BuckarooBanzay and BuckarooBanzay authored Sep 13, 2023
1 parent f8356fc commit 4986a3e
Show file tree
Hide file tree
Showing 32 changed files with 812 additions and 253 deletions.
71 changes: 71 additions & 0 deletions api/cdb/cached_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cdb

import (
"fmt"
"time"

cache "github.com/Code-Hex/go-generics-cache"
)

type CachedCDBClient struct {
ttl time.Duration
client *CDBClient
search_cache *cache.Cache[string, []*Package]
dependency_cache *cache.Cache[string, PackageDependency]
detail_cache *cache.Cache[string, *PackageDetails]
}

func NewCachedClient(client *CDBClient, ttl time.Duration) *CachedCDBClient {
return &CachedCDBClient{
ttl: ttl,
client: client,
search_cache: cache.New[string, []*Package](),
dependency_cache: cache.New[string, PackageDependency](),
detail_cache: cache.New[string, *PackageDetails](),
}
}

func (c *CachedCDBClient) SearchPackages(q *PackageQuery) ([]*Package, error) {
key := q.Params().Encode()
res, ok := c.search_cache.Get(key)

var err error
if !ok {
res, err = c.client.SearchPackages(q)
if err != nil {
return nil, err
}
c.search_cache.Set(key, res, cache.WithExpiration(c.ttl))
}
return res, nil
}

func (c *CachedCDBClient) GetDependencies(author, name string) (PackageDependency, error) {
key := fmt.Sprintf("%s/%s", author, name)
res, ok := c.dependency_cache.Get(key)

var err error
if !ok {
res, err = c.client.GetDependencies(author, name)
if err != nil {
return nil, err
}
c.dependency_cache.Set(key, res, cache.WithExpiration(c.ttl))
}
return res, nil
}

func (c *CachedCDBClient) GetDetails(author, name string) (*PackageDetails, error) {
key := fmt.Sprintf("%s/%s", author, name)
res, ok := c.detail_cache.Get(key)

var err error
if !ok {
res, err = c.client.GetDetails(author, name)
if err != nil {
return nil, err
}
c.detail_cache.Set(key, res, cache.WithExpiration(c.ttl))
}
return res, nil
}
24 changes: 1 addition & 23 deletions api/cdb/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,7 @@ func (c *CDBClient) GetPackages() ([]*Package, error) {
func (c *CDBClient) SearchPackages(q *PackageQuery) ([]*Package, error) {
pkgs := make([]*Package, 0)

params := url.Values{}
for _, t := range q.Type {
params.Add("type", string(t))
}
if q.Query != "" {
params.Add("q", q.Query)
}
if q.Author != "" {
params.Add("author", q.Author)
}
if q.Limit > 0 {
params.Add("limit", fmt.Sprintf("%d", q.Limit))
}
for _, cw := range q.Hide {
params.Add("hide", cw.Name)
}
if q.Sort != "" {
params.Add("sort", string(q.Sort))
}
if q.Order != "" {
params.Add("order", string(q.Order))
}

params := q.Params()
err := c.get("api/packages", &pkgs, params)
return pkgs, err
}
Expand Down
128 changes: 128 additions & 0 deletions api/cdb/dependency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package cdb

import (
"fmt"
)

type ResolvedDependency struct {
Name string `json:"name"`
Choices []string `json:"choices"`
Selected string `json:"selected"`
Installed bool `json:"installed"`
}

func ResolveDependencies(cc *CachedCDBClient, required_pkg string, selected_pkgs, installed_pkgs []string) ([]*ResolvedDependency, error) {
rd := []*ResolvedDependency{}

// already processed dependencies
processed_deps := map[string]bool{}

// convert to lookup maps
installed_pkg_map := map[string]bool{}
for _, pkg := range installed_pkgs {
installed_pkg_map[pkg] = true
}

selected_pkg_map := map[string]bool{}
for _, pkg := range selected_pkgs {
selected_pkg_map[pkg] = true
}

mod_list, err := cc.SearchPackages(&PackageQuery{Type: []PackageType{PackageTypeMod}})
if err != nil {
return nil, fmt.Errorf("failed to query mods: %v", err)
}

mod_map := map[string]*Package{}
for _, pkg := range mod_list {
mod_map[GetPackagename(pkg.Author, pkg.Name)] = pkg
}

resolved_dep_infos := map[string][]*DependencyInfo{}

// recursive resolver
var resolve func(string) error
resolve = func(pkgname string) error {
if processed_deps[pkgname] {
return nil
}
processed_deps[pkgname] = true

dep := resolved_dep_infos[pkgname]
author, name := GetAuthorName(pkgname)

if dep == nil {
// fetch dep infos
deps, err := cc.GetDependencies(author, name)
if err != nil {
return fmt.Errorf("failed to resolve deps for mod '%s': %v", pkgname, err)
}

for n, dep := range deps {
resolved_dep_infos[n] = dep
}
dep = resolved_dep_infos[pkgname]
}

if dep == nil {
// should not happen but check anyway
return fmt.Errorf("dep unresolved: '%s'", pkgname)
}

for _, di := range dep {
if di.IsOptional || processed_deps[di.Name] {
// optional or already processed
continue
}

processed_deps[di.Name] = true

d := &ResolvedDependency{
Name: di.Name,
Choices: []string{},
}

if installed_pkg_map[di.Name] {
// already installed
d.Installed = true
rd = append(rd, d)
continue
}

var selected_pkgname string
for _, dep_pkgname := range di.Packages {
if mod_map[dep_pkgname] == nil {
// not of "mod"-type
continue
}

d.Choices = append(d.Choices, dep_pkgname)
_, name := GetAuthorName(dep_pkgname)
if (selected_pkgname == "" && name == di.Name) || selected_pkg_map[dep_pkgname] {
// exact match found or manually selected
selected_pkgname = dep_pkgname
}
}

if selected_pkgname != "" {
d.Selected = selected_pkgname
// resolve selected sub-package
err = resolve(selected_pkgname)
if err != nil {
return err
}
}

rd = append(rd, d)
}

return nil
}

err = resolve(required_pkg)
if err != nil {
return nil, err
}

return rd, nil
}
32 changes: 32 additions & 0 deletions api/cdb/dependency_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cdb_test

import (
"mtui/api/cdb"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestResolveDependencies(t *testing.T) {
c := cdb.New()
cc := cdb.NewCachedClient(c, time.Hour*1)

installed_pkgs := []string{"default"}
selected_pkgs := []string{}

rd, err := cdb.ResolveDependencies(cc, "mt-mods/technic_plus", selected_pkgs, installed_pkgs)
assert.NoError(t, err)
assert.NotNil(t, rd)

for _, di := range rd {
switch di.Name {
case "basic_materials":
assert.True(t, len(di.Choices) >= 1)
case "default":
assert.True(t, di.Installed)
}
}

assert.Equal(t, 3, len(rd))
}
53 changes: 46 additions & 7 deletions api/cdb/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package cdb

import (
"fmt"
"net/url"
)

type PackageType string

const (
Expand All @@ -25,13 +30,47 @@ const (
)

type PackageQuery struct {
Type []PackageType `json:"type"`
Query string `json:"query"`
Author string `json:"author"`
Limit int `json:"limit"`
Hide []ContentWarning `json:"hide"`
Sort PackageSortType `json:"sort"`
Order PackageSortOrderType `json:"order"`
Type []PackageType `json:"type"`
Query string `json:"query"`
Author string `json:"author"`
Limit int `json:"limit"`
Hide []ContentWarning `json:"hide"`
Sort PackageSortType `json:"sort"`
Order PackageSortOrderType `json:"order"`
ProtocolVersion int `json:"protocol_version"`
EngineVersion string `json:"engine_version"`
}

func (q *PackageQuery) Params() url.Values {
params := url.Values{}
for _, t := range q.Type {
params.Add("type", string(t))
}
if q.Query != "" {
params.Add("q", q.Query)
}
if q.Author != "" {
params.Add("author", q.Author)
}
if q.Limit > 0 {
params.Add("limit", fmt.Sprintf("%d", q.Limit))
}
for _, cw := range q.Hide {
params.Add("hide", cw.Name)
}
if q.Sort != "" {
params.Add("sort", string(q.Sort))
}
if q.Order != "" {
params.Add("order", string(q.Order))
}
if q.ProtocolVersion > 0 {
params.Add("protocol_version", fmt.Sprintf("%d", q.ProtocolVersion))
}
if q.EngineVersion != "" {
params.Add("engine_version", q.EngineVersion)
}
return params
}

type Package struct {
Expand Down
15 changes: 15 additions & 0 deletions api/cdb/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cdb

import (
"fmt"
"strings"
)

func GetAuthorName(pkgname string) (string, string) {
parts := strings.Split(pkgname, "/")
return parts[0], parts[1]
}

func GetPackagename(author, name string) string {
return fmt.Sprintf("%s/%s", author, name)
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ require (

require (
dario.cat/mergo v1.0.0 // indirect
github.com/Code-Hex/go-generics-cache v1.3.1 // indirect
github.com/dchest/captcha v1.0.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
Expand All @@ -45,6 +47,7 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
golang.org/x/time v0.3.0 // indirect
gotest.tools/v3 v3.5.0 // indirect
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Code-Hex/go-generics-cache v1.3.1 h1:i8rLwyhoyhaerr7JpjtYjJZUcCbWOdiYO3fZXLiEC4g=
github.com/Code-Hex/go-generics-cache v1.3.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
github.com/HimbeerserverDE/srp v0.0.0 h1:Iy2GIF7DJphXXO9NjncLEBO6VsZd8Yhrlxl/qTr09eE=
github.com/HimbeerserverDE/srp v0.0.0/go.mod h1:pxNH8S2nh4n2DWE0ToX5GnnDr/uEAuaAhJsCpkDLIWw=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
Expand Down Expand Up @@ -259,6 +261,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
Expand Down
2 changes: 1 addition & 1 deletion jobs/log_cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func logCleanup(a *app.App) {
}
}

// re-schedule every minute
// re-schedule
time.Sleep(time.Second * 10)
}
}
Loading

0 comments on commit 4986a3e

Please sign in to comment.