feat: threaded download pooling (#48)
* feat: threaded download pooling refactor: splice out resolver * chore: remove debug
This commit is contained in:
parent
b6592fe185
commit
4195463c60
22 changed files with 409 additions and 1056 deletions
|
@ -58,5 +58,4 @@ linters:
|
|||
- wrapcheck
|
||||
- gci
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- nonamedreturns
|
||||
|
|
125
cli/cache/download.go
vendored
125
cli/cache/download.go
vendored
|
@ -6,14 +6,39 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/utils"
|
||||
)
|
||||
|
||||
type downloadGroup struct {
|
||||
err error
|
||||
wait chan bool
|
||||
hash string
|
||||
updates []chan<- utils.GenericProgress
|
||||
size int64
|
||||
}
|
||||
|
||||
var downloadSync = *xsync.NewMapOf[string, *downloadGroup]()
|
||||
|
||||
func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- utils.GenericProgress, downloadSemaphore chan int) (*os.File, int64, error) {
|
||||
group, loaded := downloadSync.LoadOrCompute(cacheKey, func() *downloadGroup {
|
||||
return &downloadGroup{
|
||||
hash: hash,
|
||||
updates: make([]chan<- utils.GenericProgress, 0),
|
||||
wait: make(chan bool),
|
||||
}
|
||||
})
|
||||
|
||||
_, _ = downloadSync.Compute(cacheKey, func(oldValue *downloadGroup, loaded bool) (*downloadGroup, bool) {
|
||||
oldValue.updates = append(oldValue.updates, updates)
|
||||
return oldValue, false
|
||||
})
|
||||
|
||||
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
|
||||
if err := os.MkdirAll(downloadCache, 0o777); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
|
@ -23,6 +48,72 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
|||
|
||||
location := filepath.Join(downloadCache, cacheKey)
|
||||
|
||||
if loaded {
|
||||
if group.hash != hash {
|
||||
return nil, 0, errors.New("hash mismatch in download group")
|
||||
}
|
||||
|
||||
<-group.wait
|
||||
|
||||
if group.err != nil {
|
||||
return nil, 0, group.err
|
||||
}
|
||||
|
||||
f, err := os.Open(location)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to open file: "+location)
|
||||
}
|
||||
|
||||
return f, group.size, nil
|
||||
}
|
||||
|
||||
defer downloadSync.Delete(cacheKey)
|
||||
|
||||
upstreamUpdates := make(chan utils.GenericProgress)
|
||||
defer close(upstreamUpdates)
|
||||
|
||||
upstreamWaiter := make(chan bool)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case update := <-upstreamUpdates:
|
||||
for _, u := range group.updates {
|
||||
u <- update
|
||||
}
|
||||
case <-upstreamWaiter:
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
size, err := downloadInternal(cacheKey, location, hash, url, upstreamUpdates, downloadSemaphore)
|
||||
if err != nil {
|
||||
group.err = err
|
||||
close(group.wait)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
close(upstreamWaiter)
|
||||
wg.Wait()
|
||||
|
||||
group.size = size
|
||||
close(group.wait)
|
||||
|
||||
f, err := os.Open(location)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to open file: "+location)
|
||||
}
|
||||
|
||||
return f, size, nil
|
||||
}
|
||||
|
||||
func downloadInternal(cacheKey string, location string, hash string, url string, updates chan<- utils.GenericProgress, downloadSemaphore chan int) (int64, error) {
|
||||
stat, err := os.Stat(location)
|
||||
if err == nil {
|
||||
existingHash := ""
|
||||
|
@ -30,36 +121,31 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
|||
if hash != "" {
|
||||
f, err := os.Open(location)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to open file: "+location)
|
||||
return 0, errors.Wrap(err, "failed to open file: "+location)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
existingHash, err = utils.SHA256Data(f)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "could not compute hash for file: "+location)
|
||||
return 0, errors.Wrap(err, "could not compute hash for file: "+location)
|
||||
}
|
||||
}
|
||||
|
||||
if hash == existingHash {
|
||||
f, err := os.Open(location)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to open file: "+location)
|
||||
}
|
||||
|
||||
return f, stat.Size(), nil
|
||||
return stat.Size(), nil
|
||||
}
|
||||
|
||||
if err := os.Remove(location); err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to delete file: "+location)
|
||||
return 0, errors.Wrap(err, "failed to delete file: "+location)
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return nil, 0, errors.Wrap(err, "failed to stat file: "+location)
|
||||
return 0, errors.Wrap(err, "failed to stat file: "+location)
|
||||
}
|
||||
|
||||
if updates != nil {
|
||||
headResp, err := http.Head(url)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to head: "+url)
|
||||
return 0, errors.Wrap(err, "failed to head: "+url)
|
||||
}
|
||||
defer headResp.Body.Close()
|
||||
updates <- utils.GenericProgress{Total: headResp.ContentLength}
|
||||
|
@ -72,18 +158,18 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
|||
|
||||
out, err := os.Create(location)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed creating file at: "+location)
|
||||
return 0, errors.Wrap(err, "failed creating file at: "+location)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to fetch: "+url)
|
||||
return 0, errors.Wrap(err, "failed to fetch: "+url)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, 0, fmt.Errorf("bad status: %s on url: %s", resp.Status, url)
|
||||
return 0, fmt.Errorf("bad status: %s on url: %s", resp.Status, url)
|
||||
}
|
||||
|
||||
progresser := &utils.Progresser{
|
||||
|
@ -94,12 +180,7 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
|||
|
||||
_, err = io.Copy(out, progresser)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed writing file to disk")
|
||||
}
|
||||
|
||||
f, err := os.Open(location)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to open file: "+location)
|
||||
return 0, errors.Wrap(err, "failed writing file to disk")
|
||||
}
|
||||
|
||||
if updates != nil {
|
||||
|
@ -108,8 +189,8 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
|||
|
||||
_, err = addFileToCache(cacheKey)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to add file to cache")
|
||||
return 0, errors.Wrap(err, "failed to add file to cache")
|
||||
}
|
||||
|
||||
return f, resp.ContentLength, nil
|
||||
return resp.ContentLength, nil
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func InitCLI(apiOnly bool) (*GlobalContext, error) {
|
|||
|
||||
apiClient := ficsit.InitAPI()
|
||||
|
||||
mixedProvider := provider.InitMixedProvider(apiClient)
|
||||
mixedProvider := provider.InitMixedProvider(provider.NewFicsitProvider(apiClient), provider.NewLocalProvider())
|
||||
|
||||
if viper.GetBool("offline") {
|
||||
mixedProvider.Offline = true
|
||||
|
|
|
@ -1,209 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/mircearoata/pubgrub-go/pubgrub"
|
||||
"github.com/mircearoata/pubgrub-go/pubgrub/helpers"
|
||||
"github.com/mircearoata/pubgrub-go/pubgrub/semver"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli/provider"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
)
|
||||
|
||||
const (
|
||||
rootPkg = "$$root$$"
|
||||
smlPkg = "SML"
|
||||
factoryGamePkg = "FactoryGame"
|
||||
)
|
||||
|
||||
type DependencyResolver struct {
|
||||
provider provider.Provider
|
||||
}
|
||||
|
||||
func NewDependencyResolver(provider provider.Provider) DependencyResolver {
|
||||
return DependencyResolver{provider}
|
||||
}
|
||||
|
||||
type ficsitAPISource struct {
|
||||
provider provider.Provider
|
||||
lockfile *LockFile
|
||||
toInstall map[string]semver.Constraint
|
||||
modVersionInfo *xsync.MapOf[string, ficsit.AllVersionsResponse]
|
||||
gameVersion semver.Version
|
||||
smlVersions []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion
|
||||
}
|
||||
|
||||
func (f *ficsitAPISource) GetPackageVersions(pkg string) ([]pubgrub.PackageVersion, error) {
|
||||
if pkg == rootPkg {
|
||||
return []pubgrub.PackageVersion{{Version: semver.Version{}, Dependencies: f.toInstall}}, nil
|
||||
}
|
||||
if pkg == factoryGamePkg {
|
||||
return []pubgrub.PackageVersion{{Version: f.gameVersion}}, nil
|
||||
}
|
||||
if pkg == smlPkg {
|
||||
versions := make([]pubgrub.PackageVersion, len(f.smlVersions))
|
||||
for i, smlVersion := range f.smlVersions {
|
||||
v, err := semver.NewVersion(smlVersion.Version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse version %s", smlVersion.Version)
|
||||
}
|
||||
gameConstraint, err := semver.NewConstraint(fmt.Sprintf(">=%d", smlVersion.Satisfactory_version))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse constraint %s", fmt.Sprintf(">=%d", smlVersion.Satisfactory_version))
|
||||
}
|
||||
versions[i] = pubgrub.PackageVersion{
|
||||
Version: v,
|
||||
Dependencies: map[string]semver.Constraint{
|
||||
factoryGamePkg: gameConstraint,
|
||||
},
|
||||
}
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
response, err := f.provider.ModVersionsWithDependencies(context.TODO(), pkg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to fetch mod %s", pkg)
|
||||
}
|
||||
if !response.Success {
|
||||
if response.Error != nil {
|
||||
return nil, errors.Errorf("mod %s not found: %s", pkg, response.Error.Message)
|
||||
}
|
||||
return nil, errors.Errorf("mod %s not found", pkg)
|
||||
}
|
||||
f.modVersionInfo.Store(pkg, *response)
|
||||
versions := make([]pubgrub.PackageVersion, len(response.Data))
|
||||
for i, modVersion := range response.Data {
|
||||
v, err := semver.NewVersion(modVersion.Version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse version %s", modVersion.Version)
|
||||
}
|
||||
dependencies := make(map[string]semver.Constraint)
|
||||
optionalDependencies := make(map[string]semver.Constraint)
|
||||
for _, dependency := range modVersion.Dependencies {
|
||||
c, err := semver.NewConstraint(dependency.Condition)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse constraint %s", dependency.Condition)
|
||||
}
|
||||
if dependency.Optional {
|
||||
optionalDependencies[dependency.ModID] = c
|
||||
} else {
|
||||
dependencies[dependency.ModID] = c
|
||||
}
|
||||
}
|
||||
versions[i] = pubgrub.PackageVersion{
|
||||
Version: v,
|
||||
Dependencies: dependencies,
|
||||
OptionalDependencies: optionalDependencies,
|
||||
}
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
func (f *ficsitAPISource) PickVersion(pkg string, versions []semver.Version) semver.Version {
|
||||
if f.lockfile != nil {
|
||||
if existing, ok := f.lockfile.Mods[pkg]; ok {
|
||||
v, err := semver.NewVersion(existing.Version)
|
||||
if err == nil {
|
||||
if slices.ContainsFunc(versions, func(version semver.Version) bool {
|
||||
return v.Compare(version) == 0
|
||||
}) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return helpers.StandardVersionPriority(versions)
|
||||
}
|
||||
|
||||
func (d DependencyResolver) ResolveModDependencies(constraints map[string]string, lockFile *LockFile, gameVersion int) (*LockFile, error) {
|
||||
smlVersionsDB, err := d.provider.SMLVersions(context.TODO())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed fetching SML versions")
|
||||
}
|
||||
|
||||
gameVersionSemver, err := semver.NewVersion(fmt.Sprintf("%d", gameVersion))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed parsing game version")
|
||||
}
|
||||
|
||||
toInstall := make(map[string]semver.Constraint, len(constraints))
|
||||
for k, v := range constraints {
|
||||
c, err := semver.NewConstraint(v)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse constraint %s", v)
|
||||
}
|
||||
toInstall[k] = c
|
||||
}
|
||||
|
||||
ficsitSource := &ficsitAPISource{
|
||||
provider: d.provider,
|
||||
smlVersions: smlVersionsDB.SmlVersions.Sml_versions,
|
||||
gameVersion: gameVersionSemver,
|
||||
lockfile: lockFile,
|
||||
toInstall: toInstall,
|
||||
modVersionInfo: xsync.NewMapOf[string, ficsit.AllVersionsResponse](),
|
||||
}
|
||||
|
||||
result, err := pubgrub.Solve(helpers.NewCachingSource(ficsitSource), rootPkg)
|
||||
if err != nil {
|
||||
finalError := err
|
||||
var solverErr pubgrub.SolvingError
|
||||
if errors.As(err, &solverErr) {
|
||||
finalError = DependencyResolverError{SolvingError: solverErr, provider: d.provider, smlVersions: smlVersionsDB.SmlVersions.Sml_versions, gameVersion: gameVersion}
|
||||
}
|
||||
return nil, errors.Wrap(finalError, "failed to solve dependencies")
|
||||
}
|
||||
delete(result, rootPkg)
|
||||
delete(result, factoryGamePkg)
|
||||
|
||||
outputLock := MakeLockfile()
|
||||
for k, v := range result {
|
||||
if k == smlPkg {
|
||||
for _, version := range ficsitSource.smlVersions {
|
||||
if version.Version == v.String() {
|
||||
targets := make(map[string]LockedModTarget)
|
||||
for _, target := range version.Targets {
|
||||
targets[string(target.TargetName)] = LockedModTarget{
|
||||
Link: target.Link,
|
||||
}
|
||||
}
|
||||
|
||||
outputLock.Mods[k] = LockedMod{
|
||||
Version: v.String(),
|
||||
Targets: targets,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
value, _ := ficsitSource.modVersionInfo.Load(k)
|
||||
versions := value.Data
|
||||
for _, ver := range versions {
|
||||
if ver.Version == v.RawString() {
|
||||
targets := make(map[string]LockedModTarget)
|
||||
for _, target := range ver.Targets {
|
||||
targets[target.TargetName] = LockedModTarget{
|
||||
Link: viper.GetString("api-base") + "/v1/version/" + ver.ID + "/" + target.TargetName + "/download",
|
||||
Hash: target.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
outputLock.Mods[k] = LockedMod{
|
||||
Version: v.String(),
|
||||
Targets: targets,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outputLock, nil
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mircearoata/pubgrub-go/pubgrub"
|
||||
"github.com/mircearoata/pubgrub-go/pubgrub/semver"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli/provider"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
)
|
||||
|
||||
type DependencyResolverError struct {
|
||||
pubgrub.SolvingError
|
||||
provider provider.Provider
|
||||
smlVersions []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion
|
||||
gameVersion int
|
||||
}
|
||||
|
||||
func (e DependencyResolverError) Error() string {
|
||||
rootPkg := e.Cause().Terms()[0].Dependency()
|
||||
writer := pubgrub.NewStandardErrorWriter(rootPkg).
|
||||
WithIncompatibilityStringer(
|
||||
MakeDependencyResolverErrorStringer(e.provider, e.smlVersions, e.gameVersion),
|
||||
)
|
||||
e.WriteTo(writer)
|
||||
return writer.String()
|
||||
}
|
||||
|
||||
type DependencyResolverErrorStringer struct {
|
||||
pubgrub.StandardIncompatibilityStringer
|
||||
provider provider.Provider
|
||||
packageNames map[string]string
|
||||
smlVersions []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion
|
||||
gameVersion int
|
||||
}
|
||||
|
||||
func MakeDependencyResolverErrorStringer(provider provider.Provider, smlVersions []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion, gameVersion int) *DependencyResolverErrorStringer {
|
||||
s := &DependencyResolverErrorStringer{
|
||||
provider: provider,
|
||||
smlVersions: smlVersions,
|
||||
gameVersion: gameVersion,
|
||||
packageNames: map[string]string{},
|
||||
}
|
||||
s.StandardIncompatibilityStringer = pubgrub.NewStandardIncompatibilityStringer().WithTermStringer(s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (w *DependencyResolverErrorStringer) getPackageName(pkg string) string {
|
||||
if pkg == smlPkg {
|
||||
return "SML"
|
||||
}
|
||||
if pkg == factoryGamePkg {
|
||||
return "Satisfactory"
|
||||
}
|
||||
if name, ok := w.packageNames[pkg]; ok {
|
||||
return name
|
||||
}
|
||||
result, err := w.provider.GetModName(context.TODO(), pkg)
|
||||
if err != nil {
|
||||
return pkg
|
||||
}
|
||||
w.packageNames[pkg] = result.Mod.Name
|
||||
return result.Mod.Name
|
||||
}
|
||||
|
||||
func (w *DependencyResolverErrorStringer) Term(t pubgrub.Term, includeVersion bool) string {
|
||||
name := w.getPackageName(t.Dependency())
|
||||
fullName := fmt.Sprintf("%s (%s)", name, t.Dependency())
|
||||
if name == t.Dependency() {
|
||||
fullName = t.Dependency()
|
||||
}
|
||||
if includeVersion {
|
||||
if t.Constraint().IsAny() {
|
||||
return fmt.Sprintf("every version of %s", fullName)
|
||||
}
|
||||
switch t.Dependency() {
|
||||
case factoryGamePkg:
|
||||
// Remove ".0.0" from the versions mentioned, since only the major is ever used
|
||||
return fmt.Sprintf("%s \"%s\"", fullName, strings.ReplaceAll(t.Constraint().String(), ".0.0", ""))
|
||||
case smlPkg:
|
||||
var matched []semver.Version
|
||||
for _, v := range w.smlVersions {
|
||||
ver, err := semver.NewVersion(v.Version)
|
||||
if err != nil {
|
||||
// Assume it is contained in the constraint
|
||||
matched = append(matched, semver.Version{})
|
||||
continue
|
||||
}
|
||||
if t.Constraint().Contains(ver) {
|
||||
matched = append(matched, ver)
|
||||
}
|
||||
}
|
||||
if len(matched) == 1 {
|
||||
return fmt.Sprintf("%s \"%s\"", fullName, matched[0])
|
||||
}
|
||||
return fmt.Sprintf("%s \"%s\"", fullName, t.Constraint())
|
||||
default:
|
||||
res, err := w.provider.ModVersions(context.TODO(), t.Dependency(), ficsit.VersionFilter{
|
||||
Limit: 100,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%s \"%s\"", fullName, t.Constraint())
|
||||
}
|
||||
var matched []semver.Version
|
||||
for _, v := range res.Mod.Versions {
|
||||
ver, err := semver.NewVersion(v.Version)
|
||||
if err != nil {
|
||||
// Assume it is contained in the constraint
|
||||
matched = append(matched, semver.Version{})
|
||||
continue
|
||||
}
|
||||
if t.Constraint().Contains(ver) {
|
||||
matched = append(matched, ver)
|
||||
}
|
||||
}
|
||||
if len(matched) == 1 {
|
||||
return fmt.Sprintf("%s \"%s\"", fullName, matched[0])
|
||||
}
|
||||
return fmt.Sprintf("%s \"%s\"", fullName, t.Constraint())
|
||||
}
|
||||
}
|
||||
return fullName
|
||||
}
|
||||
|
||||
func (w *DependencyResolverErrorStringer) IncompatibilityString(incompatibility *pubgrub.Incompatibility, rootPkg string) string {
|
||||
terms := incompatibility.Terms()
|
||||
if len(terms) == 1 && terms[0].Dependency() == factoryGamePkg {
|
||||
return fmt.Sprintf("Satisfactory CL%d is installed", w.gameVersion)
|
||||
}
|
||||
return w.StandardIncompatibilityStringer.IncompatibilityString(incompatibility, rootPkg)
|
||||
}
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
|
@ -257,7 +258,7 @@ func (i *Installation) LockFilePath(ctx *GlobalContext) (string, error) {
|
|||
return filepath.Join(i.BasePath(), platform.LockfilePath, lockFileName), nil
|
||||
}
|
||||
|
||||
func (i *Installation) LockFile(ctx *GlobalContext) (*LockFile, error) {
|
||||
func (i *Installation) LockFile(ctx *GlobalContext) (*resolver.LockFile, error) {
|
||||
lockfilePath, err := i.LockFilePath(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -268,7 +269,7 @@ func (i *Installation) LockFile(ctx *GlobalContext) (*LockFile, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var lockFile *LockFile
|
||||
var lockFile *resolver.LockFile
|
||||
lockFileJSON, err := d.Read(lockfilePath)
|
||||
if err != nil {
|
||||
if !d.IsNotExist(err) {
|
||||
|
@ -283,7 +284,7 @@ func (i *Installation) LockFile(ctx *GlobalContext) (*LockFile, error) {
|
|||
return lockFile, nil
|
||||
}
|
||||
|
||||
func (i *Installation) WriteLockFile(ctx *GlobalContext, lockfile *LockFile) error {
|
||||
func (i *Installation) WriteLockFile(ctx *GlobalContext, lockfile *resolver.LockFile) error {
|
||||
lockfilePath, err := i.LockFilePath(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -327,20 +328,20 @@ func (i *Installation) Wipe() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (i *Installation) ResolveProfile(ctx *GlobalContext) (*LockFile, error) {
|
||||
func (i *Installation) ResolveProfile(ctx *GlobalContext) (*resolver.LockFile, error) {
|
||||
lockFile, err := i.LockFile(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolver := NewDependencyResolver(ctx.Provider)
|
||||
depResolver := resolver.NewDependencyResolver(ctx.Provider, viper.GetString("api-base"))
|
||||
|
||||
gameVersion, err := i.GetGameVersion(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to detect game version")
|
||||
}
|
||||
|
||||
lockfile, err := ctx.Profiles.Profiles[i.Profile].Resolve(resolver, lockFile, gameVersion)
|
||||
lockfile, err := ctx.Profiles.Profiles[i.Profile].Resolve(depResolver, lockFile, gameVersion)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not resolve mods")
|
||||
}
|
||||
|
@ -382,7 +383,7 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate)
|
|||
return errors.Wrap(err, "failed to detect platform")
|
||||
}
|
||||
|
||||
lockfile := MakeLockfile()
|
||||
lockfile := resolver.NewLockfile()
|
||||
|
||||
if !i.Vanilla {
|
||||
var err error
|
||||
|
@ -502,7 +503,7 @@ func (i *Installation) UpdateMods(ctx *GlobalContext, mods []string) error {
|
|||
return errors.Wrap(err, "failed to read lock file")
|
||||
}
|
||||
|
||||
resolver := NewDependencyResolver(ctx.Provider)
|
||||
resolver := resolver.NewDependencyResolver(ctx.Provider, viper.GetString("api-base"))
|
||||
|
||||
gameVersion, err := i.GetGameVersion(ctx)
|
||||
if err != nil {
|
||||
|
|
|
@ -34,8 +34,8 @@ func TestAddInstallation(t *testing.T) {
|
|||
profileName := "InstallationTest"
|
||||
profile, err := ctx.Profiles.AddProfile(profileName)
|
||||
testza.AssertNoError(t, err)
|
||||
testza.AssertNoError(t, profile.AddMod("AreaActions", ">=1.6.5"))
|
||||
testza.AssertNoError(t, profile.AddMod("RefinedPower", ">=3.2.10"))
|
||||
testza.AssertNoError(t, profile.AddMod("AreaActions", "1.6.5"))
|
||||
testza.AssertNoError(t, profile.AddMod("RefinedPower", "3.2.10"))
|
||||
|
||||
serverLocation := os.Getenv("SF_DEDICATED_SERVER")
|
||||
if serverLocation != "" {
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
package cli
|
||||
|
||||
type LockfileVersion int
|
||||
|
||||
const (
|
||||
InitialLockfileVersion = LockfileVersion(iota)
|
||||
|
||||
ModTargetsLockfileVersion
|
||||
|
||||
// Always last
|
||||
nextLockfileVersion
|
||||
CurrentLockfileVersion = nextLockfileVersion - 1
|
||||
)
|
||||
|
||||
type LockFile struct {
|
||||
Mods map[string]LockedMod `json:"mods"`
|
||||
Version LockfileVersion `json:"version"`
|
||||
}
|
||||
|
||||
type LockedMod struct {
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
Targets map[string]LockedModTarget `json:"targets"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type LockedModTarget struct {
|
||||
Hash string `json:"hash"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
func MakeLockfile() *LockFile {
|
||||
return &LockFile{
|
||||
Mods: make(map[string]LockedMod),
|
||||
Version: CurrentLockfileVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LockFile) Clone() *LockFile {
|
||||
lockFile := &LockFile{
|
||||
Mods: make(map[string]LockedMod),
|
||||
Version: l.Version,
|
||||
}
|
||||
for k, v := range l.Mods {
|
||||
lockFile.Mods[k] = v
|
||||
}
|
||||
return lockFile
|
||||
}
|
||||
|
||||
func (l *LockFile) Remove(modID ...string) *LockFile {
|
||||
for _, s := range modID {
|
||||
delete(l.Mods, s)
|
||||
}
|
||||
return l
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/utils"
|
||||
|
@ -45,6 +47,7 @@ type Profiles struct {
|
|||
type Profile struct {
|
||||
Mods map[string]ProfileMod `json:"mods"`
|
||||
Name string `json:"name"`
|
||||
RequiredTargets []resolver.TargetName `json:"required_targets"`
|
||||
}
|
||||
|
||||
type ProfileMod struct {
|
||||
|
@ -288,7 +291,7 @@ func (p *Profile) HasMod(reference string) bool {
|
|||
// An optional lockfile can be passed if one exists.
|
||||
//
|
||||
// Returns an error if resolution is impossible.
|
||||
func (p *Profile) Resolve(resolver DependencyResolver, lockFile *LockFile, gameVersion int) (*LockFile, error) {
|
||||
func (p *Profile) Resolve(resolver resolver.DependencyResolver, lockFile *resolver.LockFile, gameVersion int) (*resolver.LockFile, error) {
|
||||
toResolve := make(map[string]string)
|
||||
for modReference, mod := range p.Mods {
|
||||
if mod.Enabled {
|
||||
|
@ -296,7 +299,7 @@ func (p *Profile) Resolve(resolver DependencyResolver, lockFile *LockFile, gameV
|
|||
}
|
||||
}
|
||||
|
||||
resultLockfile, err := resolver.ResolveModDependencies(toResolve, lockFile, gameVersion)
|
||||
resultLockfile, err := resolver.ResolveModDependencies(context.TODO(), toResolve, lockFile, gameVersion, p.RequiredTargets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed resolving profile dependencies")
|
||||
}
|
||||
|
|
|
@ -4,40 +4,117 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
"github.com/pkg/errors"
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
)
|
||||
|
||||
type ficsitProvider struct {
|
||||
type FicsitProvider struct {
|
||||
client graphql.Client
|
||||
}
|
||||
|
||||
func initFicsitProvider(client graphql.Client) ficsitProvider {
|
||||
return ficsitProvider{
|
||||
func NewFicsitProvider(client graphql.Client) FicsitProvider {
|
||||
return FicsitProvider{
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
func (p ficsitProvider) Mods(context context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
||||
func (p FicsitProvider) Mods(context context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
||||
return ficsit.Mods(context, p.client, filter)
|
||||
}
|
||||
|
||||
func (p ficsitProvider) GetMod(context context.Context, modReference string) (*ficsit.GetModResponse, error) {
|
||||
func (p FicsitProvider) GetMod(context context.Context, modReference string) (*ficsit.GetModResponse, error) {
|
||||
return ficsit.GetMod(context, p.client, modReference)
|
||||
}
|
||||
|
||||
func (p ficsitProvider) ModVersions(context context.Context, modReference string, filter ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error) {
|
||||
func (p FicsitProvider) ModVersions(context context.Context, modReference string, filter ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error) {
|
||||
return ficsit.ModVersions(context, p.client, modReference, filter)
|
||||
}
|
||||
|
||||
func (p ficsitProvider) SMLVersions(context context.Context) (*ficsit.SMLVersionsResponse, error) {
|
||||
return ficsit.SMLVersions(context, p.client)
|
||||
func (p FicsitProvider) SMLVersions(context context.Context) ([]resolver.SMLVersion, error) {
|
||||
response, err := ficsit.SMLVersions(context, p.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
smlVersions := make([]resolver.SMLVersion, len(response.SmlVersions.Sml_versions))
|
||||
for i, version := range response.GetSmlVersions().Sml_versions {
|
||||
targets := make([]resolver.SMLVersionTarget, len(version.Targets))
|
||||
|
||||
for j, target := range version.Targets {
|
||||
targets[j] = resolver.SMLVersionTarget{
|
||||
TargetName: resolver.TargetName(target.TargetName),
|
||||
Link: target.Link,
|
||||
}
|
||||
}
|
||||
|
||||
smlVersions[i] = resolver.SMLVersion{
|
||||
ID: version.Id,
|
||||
Version: version.Version,
|
||||
SatisfactoryVersion: version.Satisfactory_version,
|
||||
Targets: targets,
|
||||
}
|
||||
}
|
||||
|
||||
return smlVersions, nil
|
||||
}
|
||||
|
||||
func (p ficsitProvider) ModVersionsWithDependencies(_ context.Context, modID string) (*ficsit.AllVersionsResponse, error) {
|
||||
return ficsit.GetAllModVersions(modID)
|
||||
func (p FicsitProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) {
|
||||
response, err := ficsit.GetAllModVersions(modID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.New(response.Error.Message)
|
||||
}
|
||||
|
||||
modVersions := make([]resolver.ModVersion, len(response.Data))
|
||||
for i, modVersion := range response.Data {
|
||||
dependencies := make([]resolver.Dependency, len(modVersion.Dependencies))
|
||||
for j, dependency := range modVersion.Dependencies {
|
||||
dependencies[j] = resolver.Dependency{
|
||||
ModID: dependency.ModID,
|
||||
Condition: dependency.Condition,
|
||||
Optional: dependency.Optional,
|
||||
}
|
||||
}
|
||||
|
||||
targets := make([]resolver.Target, len(modVersion.Targets))
|
||||
for j, target := range modVersion.Targets {
|
||||
targets[j] = resolver.Target{
|
||||
VersionID: target.VersionID,
|
||||
TargetName: resolver.TargetName(target.TargetName),
|
||||
Hash: target.Hash,
|
||||
Size: target.Size,
|
||||
}
|
||||
}
|
||||
|
||||
modVersions[i] = resolver.ModVersion{
|
||||
ID: modVersion.ID,
|
||||
Version: modVersion.Version,
|
||||
Dependencies: dependencies,
|
||||
Targets: targets,
|
||||
}
|
||||
}
|
||||
|
||||
return modVersions, err
|
||||
}
|
||||
|
||||
func (p ficsitProvider) GetModName(context context.Context, modReference string) (*ficsit.GetModNameResponse, error) {
|
||||
return ficsit.GetModName(context, p.client, modReference)
|
||||
func (p FicsitProvider) GetModName(context context.Context, modReference string) (*resolver.ModName, error) {
|
||||
response, err := ficsit.GetModName(context, p.client, modReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resolver.ModName{
|
||||
ID: response.Mod.Id,
|
||||
ModReference: response.Mod.Mod_reference,
|
||||
Name: response.Mod.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p FicsitProvider) IsOffline() bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -6,18 +6,19 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli/cache"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
)
|
||||
|
||||
type localProvider struct{}
|
||||
type LocalProvider struct{}
|
||||
|
||||
func initLocalProvider() localProvider {
|
||||
return localProvider{}
|
||||
func NewLocalProvider() LocalProvider {
|
||||
return LocalProvider{}
|
||||
}
|
||||
|
||||
func (p localProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
||||
func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
||||
cachedMods, err := cache.GetCache()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get cache")
|
||||
|
@ -89,7 +90,7 @@ func (p localProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (p localProvider) GetMod(_ context.Context, modReference string) (*ficsit.GetModResponse, error) {
|
||||
func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.GetModResponse, error) {
|
||||
cachedModFiles, err := cache.GetCacheMod(modReference)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get cache")
|
||||
|
@ -125,79 +126,44 @@ func (p localProvider) GetMod(_ context.Context, modReference string) (*ficsit.G
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (p localProvider) ModVersions(_ context.Context, modReference string, filter ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error) {
|
||||
cachedModFiles, err := cache.GetCacheMod(modReference)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get cache")
|
||||
}
|
||||
|
||||
if filter.Limit == 0 {
|
||||
filter.Limit = 25
|
||||
}
|
||||
|
||||
versions := make([]ficsit.ModVersionsModVersionsVersion, 0)
|
||||
|
||||
for _, modFile := range cachedModFiles[filter.Offset : filter.Offset+filter.Limit] {
|
||||
versions = append(versions, ficsit.ModVersionsModVersionsVersion{
|
||||
Id: modReference + ":" + modFile.Plugin.SemVersion,
|
||||
Version: modFile.Plugin.SemVersion,
|
||||
})
|
||||
}
|
||||
|
||||
return &ficsit.ModVersionsResponse{
|
||||
Mod: ficsit.ModVersionsMod{
|
||||
Id: modReference,
|
||||
Versions: versions,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p localProvider) SMLVersions(_ context.Context) (*ficsit.SMLVersionsResponse, error) {
|
||||
func (p LocalProvider) SMLVersions(_ context.Context) ([]resolver.SMLVersion, error) {
|
||||
cachedSMLFiles, err := cache.GetCacheMod("SML")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get cache")
|
||||
}
|
||||
|
||||
smlVersions := make([]ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion, 0)
|
||||
smlVersions := make([]resolver.SMLVersion, 0)
|
||||
|
||||
for _, smlFile := range cachedSMLFiles {
|
||||
smlVersions = append(smlVersions, ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion{
|
||||
Id: "SML:" + smlFile.Plugin.SemVersion,
|
||||
smlVersions = append(smlVersions, resolver.SMLVersion{
|
||||
ID: "SML:" + smlFile.Plugin.SemVersion,
|
||||
Version: smlFile.Plugin.SemVersion,
|
||||
Satisfactory_version: 0, // TODO: where can this be obtained from?
|
||||
SatisfactoryVersion: 0, // TODO: where can this be obtained from?
|
||||
})
|
||||
}
|
||||
|
||||
return &ficsit.SMLVersionsResponse{
|
||||
SmlVersions: ficsit.SMLVersionsSmlVersionsGetSMLVersions{
|
||||
Count: len(smlVersions),
|
||||
Sml_versions: smlVersions,
|
||||
},
|
||||
}, nil
|
||||
return smlVersions, nil
|
||||
}
|
||||
|
||||
func (p localProvider) ModVersionsWithDependencies(_ context.Context, modID string) (*ficsit.AllVersionsResponse, error) {
|
||||
func (p LocalProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) {
|
||||
cachedModFiles, err := cache.GetCacheMod(modID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get cache")
|
||||
}
|
||||
|
||||
versions := make([]ficsit.ModVersion, 0)
|
||||
versions := make([]resolver.ModVersion, 0)
|
||||
|
||||
for _, modFile := range cachedModFiles {
|
||||
versions = append(versions, ficsit.ModVersion{
|
||||
versions = append(versions, resolver.ModVersion{
|
||||
ID: modID + ":" + modFile.Plugin.SemVersion,
|
||||
Version: modFile.Plugin.SemVersion,
|
||||
})
|
||||
}
|
||||
|
||||
return &ficsit.AllVersionsResponse{
|
||||
Success: true,
|
||||
Data: versions,
|
||||
}, nil
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
func (p localProvider) GetModName(_ context.Context, modReference string) (*ficsit.GetModNameResponse, error) {
|
||||
func (p LocalProvider) GetModName(_ context.Context, modReference string) (*resolver.ModName, error) {
|
||||
cachedModFiles, err := cache.GetCacheMod(modReference)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get cache")
|
||||
|
@ -207,11 +173,13 @@ func (p localProvider) GetModName(_ context.Context, modReference string) (*fics
|
|||
return nil, errors.New("mod not found")
|
||||
}
|
||||
|
||||
return &ficsit.GetModNameResponse{
|
||||
Mod: ficsit.GetModNameMod{
|
||||
Id: modReference,
|
||||
return &resolver.ModName{
|
||||
ID: modReference,
|
||||
Name: cachedModFiles[0].Plugin.FriendlyName,
|
||||
Mod_reference: modReference,
|
||||
},
|
||||
ModReference: modReference,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p LocalProvider) IsOffline() bool {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -3,65 +3,58 @@ package provider
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
)
|
||||
|
||||
type MixedProvider struct {
|
||||
ficsitProvider ficsitProvider
|
||||
localProvider localProvider
|
||||
onlineProvider Provider
|
||||
offlineProvider Provider
|
||||
Offline bool
|
||||
}
|
||||
|
||||
func InitMixedProvider(client graphql.Client) *MixedProvider {
|
||||
func InitMixedProvider(onlineProvider Provider, offlineProvider Provider) *MixedProvider {
|
||||
return &MixedProvider{
|
||||
ficsitProvider: initFicsitProvider(client),
|
||||
localProvider: initLocalProvider(),
|
||||
onlineProvider: onlineProvider,
|
||||
offlineProvider: offlineProvider,
|
||||
Offline: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (p MixedProvider) Mods(context context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
||||
if p.Offline {
|
||||
return p.localProvider.Mods(context, filter)
|
||||
return p.offlineProvider.Mods(context, filter)
|
||||
}
|
||||
return p.ficsitProvider.Mods(context, filter)
|
||||
return p.onlineProvider.Mods(context, filter)
|
||||
}
|
||||
|
||||
func (p MixedProvider) GetMod(context context.Context, modReference string) (*ficsit.GetModResponse, error) {
|
||||
if p.Offline {
|
||||
return p.localProvider.GetMod(context, modReference)
|
||||
return p.offlineProvider.GetMod(context, modReference)
|
||||
}
|
||||
return p.ficsitProvider.GetMod(context, modReference)
|
||||
return p.onlineProvider.GetMod(context, modReference)
|
||||
}
|
||||
|
||||
func (p MixedProvider) ModVersions(context context.Context, modReference string, filter ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error) {
|
||||
func (p MixedProvider) SMLVersions(context context.Context) ([]resolver.SMLVersion, error) {
|
||||
if p.Offline {
|
||||
return p.localProvider.ModVersions(context, modReference, filter)
|
||||
return p.offlineProvider.SMLVersions(context) // nolint
|
||||
}
|
||||
return p.ficsitProvider.ModVersions(context, modReference, filter)
|
||||
return p.onlineProvider.SMLVersions(context) // nolint
|
||||
}
|
||||
|
||||
func (p MixedProvider) SMLVersions(context context.Context) (*ficsit.SMLVersionsResponse, error) {
|
||||
func (p MixedProvider) ModVersionsWithDependencies(context context.Context, modID string) ([]resolver.ModVersion, error) {
|
||||
if p.Offline {
|
||||
return p.localProvider.SMLVersions(context)
|
||||
return p.offlineProvider.ModVersionsWithDependencies(context, modID) // nolint
|
||||
}
|
||||
return p.ficsitProvider.SMLVersions(context)
|
||||
return p.onlineProvider.ModVersionsWithDependencies(context, modID) // nolint
|
||||
}
|
||||
|
||||
func (p MixedProvider) ModVersionsWithDependencies(context context.Context, modID string) (*ficsit.AllVersionsResponse, error) {
|
||||
func (p MixedProvider) GetModName(context context.Context, modReference string) (*resolver.ModName, error) {
|
||||
if p.Offline {
|
||||
return p.localProvider.ModVersionsWithDependencies(context, modID)
|
||||
return p.offlineProvider.GetModName(context, modReference) // nolint
|
||||
}
|
||||
return p.ficsitProvider.ModVersionsWithDependencies(context, modID)
|
||||
}
|
||||
|
||||
func (p MixedProvider) GetModName(context context.Context, modReference string) (*ficsit.GetModNameResponse, error) {
|
||||
if p.Offline {
|
||||
return p.localProvider.GetModName(context, modReference)
|
||||
}
|
||||
return p.ficsitProvider.GetModName(context, modReference)
|
||||
return p.onlineProvider.GetModName(context, modReference) // nolint
|
||||
}
|
||||
|
||||
func (p MixedProvider) IsOffline() bool {
|
||||
|
|
|
@ -3,15 +3,14 @@ package provider
|
|||
import (
|
||||
"context"
|
||||
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
resolver.Provider
|
||||
Mods(context context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error)
|
||||
GetMod(context context.Context, modReference string) (*ficsit.GetModResponse, error)
|
||||
ModVersions(context context.Context, modReference string, filter ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error)
|
||||
SMLVersions(context context.Context) (*ficsit.SMLVersionsResponse, error)
|
||||
ModVersionsWithDependencies(context context.Context, modID string) (*ficsit.AllVersionsResponse, error)
|
||||
GetModName(context context.Context, modReference string) (*ficsit.GetModNameResponse, error)
|
||||
IsOffline() bool
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/MarvinJWendt/testza"
|
||||
"github.com/rs/zerolog/log"
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/cfg"
|
||||
)
|
||||
|
@ -15,10 +18,6 @@ func init() {
|
|||
cfg.SetDefaults()
|
||||
}
|
||||
|
||||
func profilesGetResolver() DependencyResolver {
|
||||
return NewDependencyResolver(MockProvider{})
|
||||
}
|
||||
|
||||
func installWatcher() chan<- InstallUpdate {
|
||||
c := make(chan InstallUpdate)
|
||||
go func() {
|
||||
|
@ -35,60 +34,6 @@ func installWatcher() chan<- InstallUpdate {
|
|||
return c
|
||||
}
|
||||
|
||||
func TestProfileResolution(t *testing.T) {
|
||||
resolver := profilesGetResolver()
|
||||
|
||||
resolved, err := (&Profile{
|
||||
Name: DefaultProfileName,
|
||||
Mods: map[string]ProfileMod{
|
||||
"RefinedPower": {
|
||||
Version: "3.2.10",
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).Resolve(resolver, nil, math.MaxInt)
|
||||
|
||||
testza.AssertNoError(t, err)
|
||||
testza.AssertNotNil(t, resolved)
|
||||
testza.AssertLen(t, resolved.Mods, 4)
|
||||
}
|
||||
|
||||
func TestProfileRequiredOlderVersion(t *testing.T) {
|
||||
resolver := profilesGetResolver()
|
||||
|
||||
_, err := (&Profile{
|
||||
Name: DefaultProfileName,
|
||||
Mods: map[string]ProfileMod{
|
||||
"RefinedPower": {
|
||||
Version: "3.2.11",
|
||||
Enabled: true,
|
||||
},
|
||||
"RefinedRDLib": {
|
||||
Version: "1.1.5",
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).Resolve(resolver, nil, math.MaxInt)
|
||||
|
||||
testza.AssertEqual(t, "failed resolving profile dependencies: failed to solve dependencies: Because installing Refined Power (RefinedPower) \"3.2.11\" and Refined Power (RefinedPower) \"3.2.11\" depends on RefinedRDLib \"^1.1.6\", installing RefinedRDLib \"^1.1.6\".\nSo, because installing RefinedRDLib \"1.1.5\", version solving failed.", err.Error())
|
||||
}
|
||||
|
||||
func TestResolutionNonExistentMod(t *testing.T) {
|
||||
resolver := profilesGetResolver()
|
||||
|
||||
_, err := (&Profile{
|
||||
Name: DefaultProfileName,
|
||||
Mods: map[string]ProfileMod{
|
||||
"ThisModDoesNotExist$$$": {
|
||||
Version: ">0.0.0",
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).Resolve(resolver, nil, math.MaxInt)
|
||||
|
||||
testza.AssertEqual(t, "failed resolving profile dependencies: failed to solve dependencies: failed to make decision: failed to get package versions: mod ThisModDoesNotExist$$$ not found: mod not found", err.Error())
|
||||
}
|
||||
|
||||
func TestUpdateMods(t *testing.T) {
|
||||
ctx, err := InitCLI(false)
|
||||
testza.AssertNoError(t, err)
|
||||
|
@ -98,17 +43,11 @@ func TestUpdateMods(t *testing.T) {
|
|||
|
||||
ctx.Provider = MockProvider{}
|
||||
|
||||
resolver := NewDependencyResolver(ctx.Provider)
|
||||
depResolver := resolver.NewDependencyResolver(ctx.Provider, viper.GetString("api-base"))
|
||||
|
||||
oldLockfile, err := (&Profile{
|
||||
Name: DefaultProfileName,
|
||||
Mods: map[string]ProfileMod{
|
||||
"FicsitRemoteMonitoring": {
|
||||
Version: "0.9.8",
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).Resolve(resolver, nil, math.MaxInt)
|
||||
oldLockfile, err := depResolver.ResolveModDependencies(context.Background(), map[string]string{
|
||||
"FicsitRemoteMonitoring": "0.9.8",
|
||||
}, nil, math.MaxInt, nil)
|
||||
|
||||
testza.AssertNoError(t, err)
|
||||
testza.AssertNotNil(t, oldLockfile)
|
||||
|
|
|
@ -4,13 +4,17 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli/provider"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*MockProvider)(nil)
|
||||
|
||||
type MockProvider struct{}
|
||||
type MockProvider struct {
|
||||
resolver.MockProvider
|
||||
}
|
||||
|
||||
func (m MockProvider) Mods(_ context.Context, f ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
||||
if f.Offset > 0 {
|
||||
|
@ -66,200 +70,29 @@ func (m MockProvider) Mods(_ context.Context, f ficsit.ModFilter) (*ficsit.ModsR
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (m MockProvider) GetMod(_ context.Context, _ string) (*ficsit.GetModResponse, error) {
|
||||
// Currently used only by TUI
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m MockProvider) ModVersions(_ context.Context, modReference string, _ ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error) {
|
||||
switch modReference {
|
||||
//nolint
|
||||
case "RefinedPower":
|
||||
return &ficsit.ModVersionsResponse{Mod: ficsit.ModVersionsMod{
|
||||
Id: "DGiLzB3ZErWu2V",
|
||||
Versions: []ficsit.ModVersionsModVersionsVersion{
|
||||
{Id: "Eqgr4VcB8y1z9a", Version: "3.2.13"},
|
||||
{Id: "BwVKMJNP8doDLg", Version: "3.2.11"},
|
||||
{Id: "4XTjMpqFngbu9r", Version: "3.2.10"},
|
||||
},
|
||||
}}, nil
|
||||
//nolint
|
||||
case "RefinedRDLib":
|
||||
return &ficsit.ModVersionsResponse{Mod: ficsit.ModVersionsMod{
|
||||
Id: "B24emzbs6xVZQr",
|
||||
Versions: []ficsit.ModVersionsModVersionsVersion{
|
||||
{Id: "2XcE6RUzGhZW7p", Version: "1.1.7"},
|
||||
{Id: "52RMLEigqT5Ksn", Version: "1.1.6"},
|
||||
{Id: "F4HY9eP4D5XjWQ", Version: "1.1.5"},
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
|
||||
panic("ModVersions: " + modReference)
|
||||
}
|
||||
|
||||
func (m MockProvider) SMLVersions(_ context.Context) (*ficsit.SMLVersionsResponse, error) {
|
||||
return &ficsit.SMLVersionsResponse{
|
||||
SmlVersions: ficsit.SMLVersionsSmlVersionsGetSMLVersions{
|
||||
Count: 4,
|
||||
Sml_versions: []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion{
|
||||
{
|
||||
Id: "v2.2.1",
|
||||
Version: "2.2.1",
|
||||
Satisfactory_version: 125236,
|
||||
Targets: []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget{},
|
||||
},
|
||||
{
|
||||
Id: "v3.3.2",
|
||||
Version: "3.3.2",
|
||||
Satisfactory_version: 194714,
|
||||
Targets: []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget{
|
||||
{
|
||||
TargetName: ficsit.TargetNameWindows,
|
||||
Link: "https://github.com/satisfactorymodding/SatisfactoryModLoader/releases/download/v3.3.2/SML.zip",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "v3.6.0",
|
||||
Version: "3.6.0",
|
||||
Satisfactory_version: 264901,
|
||||
Targets: []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget{
|
||||
{
|
||||
TargetName: ficsit.TargetNameWindows,
|
||||
Link: "https://github.com/satisfactorymodding/SatisfactoryModLoader/releases/download/v3.6.0/SML.zip",
|
||||
},
|
||||
{
|
||||
TargetName: ficsit.TargetNameWindowsserver,
|
||||
Link: "https://github.com/satisfactorymodding/SatisfactoryModLoader/releases/download/v3.6.0/SML.zip",
|
||||
},
|
||||
{
|
||||
TargetName: ficsit.TargetNameLinuxserver,
|
||||
Link: "https://github.com/satisfactorymodding/SatisfactoryModLoader/releases/download/v3.6.0/SML.zip",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "v3.6.1",
|
||||
Version: "3.6.1",
|
||||
Satisfactory_version: 264901,
|
||||
Targets: []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersionTargetsSMLVersionTarget{
|
||||
{
|
||||
TargetName: ficsit.TargetNameWindows,
|
||||
Link: "https://github.com/satisfactorymodding/SatisfactoryModLoader/releases/download/v3.6.1/SML.zip",
|
||||
},
|
||||
{
|
||||
TargetName: ficsit.TargetNameWindowsserver,
|
||||
Link: "https://github.com/satisfactorymodding/SatisfactoryModLoader/releases/download/v3.6.1/SML.zip",
|
||||
},
|
||||
{
|
||||
TargetName: ficsit.TargetNameLinuxserver,
|
||||
Link: "https://github.com/satisfactorymodding/SatisfactoryModLoader/releases/download/v3.6.1/SML.zip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var commonTargets = []ficsit.Target{
|
||||
var commonTargets = []resolver.Target{
|
||||
{
|
||||
TargetName: "Windows",
|
||||
Hash: "62f5c84eca8480b3ffe7d6c90f759e3b463f482530e27d854fd48624fdd3acc9",
|
||||
Hash: "698df20278b3de3ec30405569a22050c6721cc682389312258c14948bd8f38ae",
|
||||
},
|
||||
{
|
||||
TargetName: "WindowsServer",
|
||||
Hash: "8a83fcd4abece4192038769cc672fff6764d72c32fb6c7a8c58d66156bb07917",
|
||||
Hash: "7be01ed372e0cf3287a04f5cb32bb9dcf6f6e7a5b7603b7e43669ec4c6c1457f",
|
||||
},
|
||||
{
|
||||
TargetName: "LinuxServer",
|
||||
Hash: "8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85",
|
||||
Hash: "bdbd4cb1b472a5316621939ae2fe270fd0e3c0f0a75666a9cbe74ff1313c3663",
|
||||
},
|
||||
}
|
||||
|
||||
func (m MockProvider) ModVersionsWithDependencies(_ context.Context, modID string) (*ficsit.AllVersionsResponse, error) {
|
||||
func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID string) ([]resolver.ModVersion, error) {
|
||||
switch modID {
|
||||
case "RefinedPower":
|
||||
return &ficsit.AllVersionsResponse{
|
||||
Success: true,
|
||||
Data: []ficsit.ModVersion{
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "3.2.13",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "ModularUI",
|
||||
Condition: "^2.1.11",
|
||||
Optional: false,
|
||||
},
|
||||
{
|
||||
ModID: "RefinedRDLib",
|
||||
Condition: "^1.1.7",
|
||||
Optional: false,
|
||||
},
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.1",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "3.2.11",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "ModularUI",
|
||||
Condition: "^2.1.10",
|
||||
Optional: false,
|
||||
},
|
||||
{
|
||||
ModID: "RefinedRDLib",
|
||||
Condition: "^1.1.6",
|
||||
Optional: false,
|
||||
},
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.0",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "3.2.10",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "ModularUI",
|
||||
Condition: "^2.1.9",
|
||||
Optional: false,
|
||||
},
|
||||
{
|
||||
ModID: "RefinedRDLib",
|
||||
Condition: "^1.1.5",
|
||||
Optional: false,
|
||||
},
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.0",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "AreaActions":
|
||||
return &ficsit.AllVersionsResponse{
|
||||
Success: true,
|
||||
Data: []ficsit.ModVersion{
|
||||
return []resolver.ModVersion{
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "1.6.7",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
Dependencies: []resolver.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.4.1",
|
||||
|
@ -271,7 +104,7 @@ func (m MockProvider) ModVersionsWithDependencies(_ context.Context, modID strin
|
|||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "1.6.6",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
Dependencies: []resolver.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.2.0",
|
||||
|
@ -283,7 +116,7 @@ func (m MockProvider) ModVersionsWithDependencies(_ context.Context, modID strin
|
|||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "1.6.5",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
Dependencies: []resolver.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.0.0",
|
||||
|
@ -292,108 +125,13 @@ func (m MockProvider) ModVersionsWithDependencies(_ context.Context, modID strin
|
|||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "RefinedRDLib":
|
||||
return &ficsit.AllVersionsResponse{
|
||||
Success: true,
|
||||
Data: []ficsit.ModVersion{
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "1.1.7",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.1",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "1.1.6",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.0",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "1.1.5",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.0",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "ModularUI":
|
||||
return &ficsit.AllVersionsResponse{
|
||||
Success: true,
|
||||
Data: []ficsit.ModVersion{
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "2.1.12",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.1",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "2.1.11",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.0",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "2.1.10",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.0",
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case "ThisModDoesNotExist$$$":
|
||||
return &ficsit.AllVersionsResponse{
|
||||
Success: false,
|
||||
Error: &ficsit.Error{
|
||||
Message: "mod not found",
|
||||
Code: 200,
|
||||
},
|
||||
}, nil
|
||||
case "FicsitRemoteMonitoring":
|
||||
return &ficsit.AllVersionsResponse{
|
||||
Success: true,
|
||||
Data: []ficsit.ModVersion{
|
||||
return []resolver.ModVersion{
|
||||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "0.10.1",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
Dependencies: []resolver.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.6.0",
|
||||
|
@ -405,7 +143,7 @@ func (m MockProvider) ModVersionsWithDependencies(_ context.Context, modID strin
|
|||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "0.10.0",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
Dependencies: []resolver.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.5.0",
|
||||
|
@ -417,7 +155,7 @@ func (m MockProvider) ModVersionsWithDependencies(_ context.Context, modID strin
|
|||
{
|
||||
ID: "7QcfNdo5QAAyoC",
|
||||
Version: "0.9.8",
|
||||
Dependencies: []ficsit.Dependency{
|
||||
Dependencies: []resolver.Dependency{
|
||||
{
|
||||
ModID: "SML",
|
||||
Condition: "^3.4.1",
|
||||
|
@ -426,30 +164,15 @@ func (m MockProvider) ModVersionsWithDependencies(_ context.Context, modID strin
|
|||
},
|
||||
Targets: commonTargets,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
panic("ModVersionsWithDependencies: " + modID)
|
||||
return m.MockProvider.ModVersionsWithDependencies(ctx, modID) // nolint
|
||||
}
|
||||
|
||||
func (m MockProvider) GetModName(_ context.Context, modReference string) (*ficsit.GetModNameResponse, error) {
|
||||
switch modReference {
|
||||
case "RefinedPower":
|
||||
return &ficsit.GetModNameResponse{Mod: ficsit.GetModNameMod{
|
||||
Id: "DGiLzB3ZErWu2V",
|
||||
Mod_reference: "RefinedPower",
|
||||
Name: "Refined Power",
|
||||
}}, nil
|
||||
case "RefinedRDLib":
|
||||
return &ficsit.GetModNameResponse{Mod: ficsit.GetModNameMod{
|
||||
Id: "B24emzbs6xVZQr",
|
||||
Mod_reference: "RefinedRDLib",
|
||||
Name: "RefinedRDLib",
|
||||
}}, nil
|
||||
}
|
||||
|
||||
panic("GetModName: " + modReference)
|
||||
func (m MockProvider) GetMod(_ context.Context, _ string) (*ficsit.GetModResponse, error) {
|
||||
// Currently used only by TUI
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m MockProvider) IsOffline() bool {
|
||||
|
|
19
cli/types.go
19
cli/types.go
|
@ -1,19 +0,0 @@
|
|||
package cli
|
||||
|
||||
type ModVersion struct {
|
||||
ID string
|
||||
Version string
|
||||
Targets map[string]VersionTarget
|
||||
Dependencies []VersionDependency
|
||||
}
|
||||
|
||||
type VersionTarget struct {
|
||||
Link string
|
||||
Hash string
|
||||
}
|
||||
|
||||
type VersionDependency struct {
|
||||
ModReference string
|
||||
Constraint string
|
||||
Optional bool
|
||||
}
|
3
go.mod
3
go.mod
|
@ -15,13 +15,13 @@ require (
|
|||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/charmbracelet/x/exp/teatest v0.0.0-20231206171822-6e7b9b308fe7
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
github.com/mircearoata/pubgrub-go v0.3.3
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pterm/pterm v0.12.71
|
||||
github.com/puzpuzpuz/xsync/v3 v3.0.2
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/satisfactorymodding/ficsit-resolver v0.0.2
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.18.0
|
||||
golang.org/x/sync v0.5.0
|
||||
|
@ -62,6 +62,7 @@ require (
|
|||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
|
||||
github.com/mircearoata/pubgrub-go v0.3.3 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -189,6 +189,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
|
|||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/satisfactorymodding/ficsit-resolver v0.0.2 h1:dj/OsDLpaMUqCHpfBVHvDMUv2nf5gT4HS2ydBMkmtcQ=
|
||||
github.com/satisfactorymodding/ficsit-resolver v0.0.2/go.mod h1:ckKMmMvDoYbbkEbWXEsMes608uvv6EKphXPhHX8LKSc=
|
||||
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
|
||||
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/pkg/errors"
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli"
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli/provider"
|
||||
|
@ -14,8 +16,8 @@ import (
|
|||
|
||||
type rootModel struct {
|
||||
headerComponent tea.Model
|
||||
dependencyResolver cli.DependencyResolver
|
||||
global *cli.GlobalContext
|
||||
dependencyResolver resolver.DependencyResolver
|
||||
currentSize tea.WindowSizeMsg
|
||||
}
|
||||
|
||||
|
@ -26,7 +28,7 @@ func newModel(global *cli.GlobalContext) *rootModel {
|
|||
Width: 20,
|
||||
Height: 14,
|
||||
},
|
||||
dependencyResolver: cli.NewDependencyResolver(global.Provider),
|
||||
dependencyResolver: resolver.NewDependencyResolver(global.Provider, viper.GetString("api-base")),
|
||||
}
|
||||
|
||||
m.headerComponent = components.NewHeaderComponent(m)
|
||||
|
|
|
@ -248,7 +248,7 @@ func (m apply) View() string {
|
|||
for _, installPath := range installationList {
|
||||
s := m.status[installPath]
|
||||
|
||||
strs = append(strs, lipgloss.NewStyle().Margin(topMargins, 0, bottomMargins, 1).Render(lipgloss.JoinHorizontal(
|
||||
strs = append(strs, lipgloss.NewStyle().Margin(topMargins, 0, bottomMargins).Render(lipgloss.JoinHorizontal(
|
||||
lipgloss.Left,
|
||||
m.overall.ViewAs(s.overallProgress.Percentage()),
|
||||
" - ",
|
||||
|
@ -265,22 +265,22 @@ func (m apply) View() string {
|
|||
for _, modReference := range modReferences {
|
||||
p := s.modProgresses[modReference]
|
||||
if p.complete || s.done {
|
||||
strs = append(strs, lipgloss.NewStyle().Foreground(lipgloss.Color("22")).Render("✓ ")+modReference)
|
||||
strs = append(strs, lipgloss.NewStyle().MarginLeft(2).Foreground(lipgloss.Color("22")).Render("✓ ")+modReference)
|
||||
} else {
|
||||
if p.downloading {
|
||||
strs = append(strs, lipgloss.JoinHorizontal(
|
||||
strs = append(strs, lipgloss.NewStyle().MarginLeft(1).Render(lipgloss.JoinHorizontal(
|
||||
lipgloss.Left,
|
||||
m.sub.ViewAs(p.downloadProgress.Percentage()),
|
||||
" - ",
|
||||
lipgloss.NewStyle().Render(modReference+" (Downloading)"),
|
||||
))
|
||||
)))
|
||||
} else {
|
||||
strs = append(strs, lipgloss.JoinHorizontal(
|
||||
strs = append(strs, lipgloss.NewStyle().MarginLeft(1).Render(lipgloss.JoinHorizontal(
|
||||
lipgloss.Left,
|
||||
m.sub.ViewAs(p.extractProgress.Percentage()),
|
||||
" - ",
|
||||
lipgloss.NewStyle().Render(modReference+" (Extracting)"),
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/scenes/keys"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
|
@ -53,34 +52,18 @@ func NewModVersionList(root components.RootModel, parent tea.Model, mod utils.Mo
|
|||
|
||||
go func() {
|
||||
items := make([]list.Item, 0)
|
||||
allVersions := make([]ficsit.ModVersionsModVersionsVersion, 0)
|
||||
offset := 0
|
||||
for {
|
||||
versions, err := root.GetProvider().ModVersions(context.TODO(), mod.Reference, ficsit.VersionFilter{
|
||||
Limit: 100,
|
||||
Offset: offset,
|
||||
Order: ficsit.OrderDesc,
|
||||
Order_by: ficsit.VersionFieldsCreatedAt,
|
||||
})
|
||||
versions, err := root.GetProvider().ModVersionsWithDependencies(context.TODO(), mod.Reference)
|
||||
if err != nil {
|
||||
m.err <- err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
if len(versions.Mod.Versions) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
allVersions = append(allVersions, versions.Mod.Versions...)
|
||||
|
||||
for i := 0; i < len(versions.Mod.Versions); i++ {
|
||||
currentOffset := offset
|
||||
currentI := i
|
||||
for _, version := range versions {
|
||||
tempVersion := version
|
||||
items = append(items, utils.SimpleItem[selectModVersionList]{
|
||||
ItemTitle: versions.Mod.Versions[i].Version,
|
||||
ItemTitle: tempVersion.Version,
|
||||
Activate: func(msg tea.Msg, currentModel selectModVersionList) (tea.Model, tea.Cmd) {
|
||||
version := allVersions[currentOffset+currentI]
|
||||
err := root.GetCurrentProfile().AddMod(mod.Reference, version.Version)
|
||||
err := root.GetCurrentProfile().AddMod(mod.Reference, tempVersion.Version)
|
||||
if err != nil {
|
||||
errorComponent, cmd := components.NewErrorComponent(err.Error(), time.Second*5)
|
||||
currentModel.error = errorComponent
|
||||
|
@ -91,9 +74,6 @@ func NewModVersionList(root components.RootModel, parent tea.Model, mod utils.Mo
|
|||
})
|
||||
}
|
||||
|
||||
offset += len(versions.Mod.Versions)
|
||||
}
|
||||
|
||||
m.items <- items
|
||||
}()
|
||||
|
||||
|
|
|
@ -13,8 +13,9 @@ import (
|
|||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/scenes/keys"
|
||||
|
@ -111,7 +112,7 @@ func (m updateModsList) LoadModData() {
|
|||
return
|
||||
}
|
||||
|
||||
resolver := cli.NewDependencyResolver(m.root.GetProvider())
|
||||
resolver := resolver.NewDependencyResolver(m.root.GetProvider(), viper.GetString("api-base"))
|
||||
|
||||
updatedLockfile, err := currentProfile.Resolve(resolver, nil, gameVersion)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue