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
|
- wrapcheck
|
||||||
- gci
|
- gci
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofumpt
|
|
||||||
- nonamedreturns
|
- nonamedreturns
|
||||||
|
|
125
cli/cache/download.go
vendored
125
cli/cache/download.go
vendored
|
@ -6,14 +6,39 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/utils"
|
"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) {
|
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")
|
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
|
||||||
if err := os.MkdirAll(downloadCache, 0o777); err != nil {
|
if err := os.MkdirAll(downloadCache, 0o777); err != nil {
|
||||||
if !os.IsExist(err) {
|
if !os.IsExist(err) {
|
||||||
|
@ -23,6 +48,72 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
||||||
|
|
||||||
location := filepath.Join(downloadCache, cacheKey)
|
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)
|
stat, err := os.Stat(location)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
existingHash := ""
|
existingHash := ""
|
||||||
|
@ -30,36 +121,31 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
||||||
if hash != "" {
|
if hash != "" {
|
||||||
f, err := os.Open(location)
|
f, err := os.Open(location)
|
||||||
if err != nil {
|
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()
|
defer f.Close()
|
||||||
|
|
||||||
existingHash, err = utils.SHA256Data(f)
|
existingHash, err = utils.SHA256Data(f)
|
||||||
if err != nil {
|
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 {
|
if hash == existingHash {
|
||||||
f, err := os.Open(location)
|
return stat.Size(), nil
|
||||||
if err != nil {
|
|
||||||
return nil, 0, errors.Wrap(err, "failed to open file: "+location)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, stat.Size(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Remove(location); err != 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) {
|
} 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 {
|
if updates != nil {
|
||||||
headResp, err := http.Head(url)
|
headResp, err := http.Head(url)
|
||||||
if err != nil {
|
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()
|
defer headResp.Body.Close()
|
||||||
updates <- utils.GenericProgress{Total: headResp.ContentLength}
|
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)
|
out, err := os.Create(location)
|
||||||
if err != nil {
|
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()
|
defer out.Close()
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
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{
|
progresser := &utils.Progresser{
|
||||||
|
@ -94,12 +180,7 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
||||||
|
|
||||||
_, err = io.Copy(out, progresser)
|
_, err = io.Copy(out, progresser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, errors.Wrap(err, "failed writing file to disk")
|
return 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if updates != nil {
|
if updates != nil {
|
||||||
|
@ -108,8 +189,8 @@ func DownloadOrCache(cacheKey string, hash string, url string, updates chan<- ut
|
||||||
|
|
||||||
_, err = addFileToCache(cacheKey)
|
_, err = addFileToCache(cacheKey)
|
||||||
if err != nil {
|
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()
|
apiClient := ficsit.InitAPI()
|
||||||
|
|
||||||
mixedProvider := provider.InitMixedProvider(apiClient)
|
mixedProvider := provider.InitMixedProvider(provider.NewFicsitProvider(apiClient), provider.NewLocalProvider())
|
||||||
|
|
||||||
if viper.GetBool("offline") {
|
if viper.GetBool("offline") {
|
||||||
mixedProvider.Offline = true
|
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/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"golang.org/x/sync/errgroup"
|
"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
|
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)
|
lockfilePath, err := i.LockFilePath(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -268,7 +269,7 @@ func (i *Installation) LockFile(ctx *GlobalContext) (*LockFile, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var lockFile *LockFile
|
var lockFile *resolver.LockFile
|
||||||
lockFileJSON, err := d.Read(lockfilePath)
|
lockFileJSON, err := d.Read(lockfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !d.IsNotExist(err) {
|
if !d.IsNotExist(err) {
|
||||||
|
@ -283,7 +284,7 @@ func (i *Installation) LockFile(ctx *GlobalContext) (*LockFile, error) {
|
||||||
return lockFile, nil
|
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)
|
lockfilePath, err := i.LockFilePath(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -327,20 +328,20 @@ func (i *Installation) Wipe() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Installation) ResolveProfile(ctx *GlobalContext) (*LockFile, error) {
|
func (i *Installation) ResolveProfile(ctx *GlobalContext) (*resolver.LockFile, error) {
|
||||||
lockFile, err := i.LockFile(ctx)
|
lockFile, err := i.LockFile(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resolver := NewDependencyResolver(ctx.Provider)
|
depResolver := resolver.NewDependencyResolver(ctx.Provider, viper.GetString("api-base"))
|
||||||
|
|
||||||
gameVersion, err := i.GetGameVersion(ctx)
|
gameVersion, err := i.GetGameVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to detect game version")
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not resolve mods")
|
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")
|
return errors.Wrap(err, "failed to detect platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
lockfile := MakeLockfile()
|
lockfile := resolver.NewLockfile()
|
||||||
|
|
||||||
if !i.Vanilla {
|
if !i.Vanilla {
|
||||||
var err error
|
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")
|
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)
|
gameVersion, err := i.GetGameVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -34,8 +34,8 @@ func TestAddInstallation(t *testing.T) {
|
||||||
profileName := "InstallationTest"
|
profileName := "InstallationTest"
|
||||||
profile, err := ctx.Profiles.AddProfile(profileName)
|
profile, err := ctx.Profiles.AddProfile(profileName)
|
||||||
testza.AssertNoError(t, err)
|
testza.AssertNoError(t, err)
|
||||||
testza.AssertNoError(t, profile.AddMod("AreaActions", ">=1.6.5"))
|
testza.AssertNoError(t, profile.AddMod("AreaActions", "1.6.5"))
|
||||||
testza.AssertNoError(t, profile.AddMod("RefinedPower", ">=3.2.10"))
|
testza.AssertNoError(t, profile.AddMod("RefinedPower", "3.2.10"))
|
||||||
|
|
||||||
serverLocation := os.Getenv("SF_DEDICATED_SERVER")
|
serverLocation := os.Getenv("SF_DEDICATED_SERVER")
|
||||||
if serverLocation != "" {
|
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
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/utils"
|
"github.com/satisfactorymodding/ficsit-cli/utils"
|
||||||
|
@ -43,8 +45,9 @@ type Profiles struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
Mods map[string]ProfileMod `json:"mods"`
|
Mods map[string]ProfileMod `json:"mods"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
RequiredTargets []resolver.TargetName `json:"required_targets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProfileMod struct {
|
type ProfileMod struct {
|
||||||
|
@ -288,7 +291,7 @@ func (p *Profile) HasMod(reference string) bool {
|
||||||
// An optional lockfile can be passed if one exists.
|
// An optional lockfile can be passed if one exists.
|
||||||
//
|
//
|
||||||
// Returns an error if resolution is impossible.
|
// 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)
|
toResolve := make(map[string]string)
|
||||||
for modReference, mod := range p.Mods {
|
for modReference, mod := range p.Mods {
|
||||||
if mod.Enabled {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed resolving profile dependencies")
|
return nil, errors.Wrap(err, "failed resolving profile dependencies")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,40 +4,117 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/Khan/genqlient/graphql"
|
"github.com/Khan/genqlient/graphql"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ficsitProvider struct {
|
type FicsitProvider struct {
|
||||||
client graphql.Client
|
client graphql.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFicsitProvider(client graphql.Client) ficsitProvider {
|
func NewFicsitProvider(client graphql.Client) FicsitProvider {
|
||||||
return ficsitProvider{
|
return FicsitProvider{
|
||||||
client,
|
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)
|
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)
|
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)
|
return ficsit.ModVersions(context, p.client, modReference, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ficsitProvider) SMLVersions(context context.Context) (*ficsit.SMLVersionsResponse, error) {
|
func (p FicsitProvider) SMLVersions(context context.Context) ([]resolver.SMLVersion, error) {
|
||||||
return ficsit.SMLVersions(context, p.client)
|
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) {
|
func (p FicsitProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) {
|
||||||
return ficsit.GetAllModVersions(modID)
|
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) {
|
func (p FicsitProvider) GetModName(context context.Context, modReference string) (*resolver.ModName, error) {
|
||||||
return ficsit.GetModName(context, p.client, modReference)
|
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"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/cli/cache"
|
"github.com/satisfactorymodding/ficsit-cli/cli/cache"
|
||||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type localProvider struct{}
|
type LocalProvider struct{}
|
||||||
|
|
||||||
func initLocalProvider() localProvider {
|
func NewLocalProvider() LocalProvider {
|
||||||
return 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()
|
cachedMods, err := cache.GetCache()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get cache")
|
return nil, errors.Wrap(err, "failed to get cache")
|
||||||
|
@ -89,7 +90,7 @@ func (p localProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit
|
||||||
}, nil
|
}, 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)
|
cachedModFiles, err := cache.GetCacheMod(modReference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get cache")
|
return nil, errors.Wrap(err, "failed to get cache")
|
||||||
|
@ -125,79 +126,44 @@ func (p localProvider) GetMod(_ context.Context, modReference string) (*ficsit.G
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p localProvider) ModVersions(_ context.Context, modReference string, filter ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error) {
|
func (p LocalProvider) SMLVersions(_ context.Context) ([]resolver.SMLVersion, 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) {
|
|
||||||
cachedSMLFiles, err := cache.GetCacheMod("SML")
|
cachedSMLFiles, err := cache.GetCacheMod("SML")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get cache")
|
return nil, errors.Wrap(err, "failed to get cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
smlVersions := make([]ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion, 0)
|
smlVersions := make([]resolver.SMLVersion, 0)
|
||||||
|
|
||||||
for _, smlFile := range cachedSMLFiles {
|
for _, smlFile := range cachedSMLFiles {
|
||||||
smlVersions = append(smlVersions, ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion{
|
smlVersions = append(smlVersions, resolver.SMLVersion{
|
||||||
Id: "SML:" + smlFile.Plugin.SemVersion,
|
ID: "SML:" + smlFile.Plugin.SemVersion,
|
||||||
Version: 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{
|
return smlVersions, nil
|
||||||
SmlVersions: ficsit.SMLVersionsSmlVersionsGetSMLVersions{
|
|
||||||
Count: len(smlVersions),
|
|
||||||
Sml_versions: 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)
|
cachedModFiles, err := cache.GetCacheMod(modID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get cache")
|
return nil, errors.Wrap(err, "failed to get cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
versions := make([]ficsit.ModVersion, 0)
|
versions := make([]resolver.ModVersion, 0)
|
||||||
|
|
||||||
for _, modFile := range cachedModFiles {
|
for _, modFile := range cachedModFiles {
|
||||||
versions = append(versions, ficsit.ModVersion{
|
versions = append(versions, resolver.ModVersion{
|
||||||
ID: modID + ":" + modFile.Plugin.SemVersion,
|
ID: modID + ":" + modFile.Plugin.SemVersion,
|
||||||
Version: modFile.Plugin.SemVersion,
|
Version: modFile.Plugin.SemVersion,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ficsit.AllVersionsResponse{
|
return versions, nil
|
||||||
Success: true,
|
|
||||||
Data: 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)
|
cachedModFiles, err := cache.GetCacheMod(modReference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get cache")
|
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 nil, errors.New("mod not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ficsit.GetModNameResponse{
|
return &resolver.ModName{
|
||||||
Mod: ficsit.GetModNameMod{
|
ID: modReference,
|
||||||
Id: modReference,
|
Name: cachedModFiles[0].Plugin.FriendlyName,
|
||||||
Name: cachedModFiles[0].Plugin.FriendlyName,
|
ModReference: modReference,
|
||||||
Mod_reference: modReference,
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p LocalProvider) IsOffline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -3,65 +3,58 @@ package provider
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/Khan/genqlient/graphql"
|
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MixedProvider struct {
|
type MixedProvider struct {
|
||||||
ficsitProvider ficsitProvider
|
onlineProvider Provider
|
||||||
localProvider localProvider
|
offlineProvider Provider
|
||||||
Offline bool
|
Offline bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitMixedProvider(client graphql.Client) *MixedProvider {
|
func InitMixedProvider(onlineProvider Provider, offlineProvider Provider) *MixedProvider {
|
||||||
return &MixedProvider{
|
return &MixedProvider{
|
||||||
ficsitProvider: initFicsitProvider(client),
|
onlineProvider: onlineProvider,
|
||||||
localProvider: initLocalProvider(),
|
offlineProvider: offlineProvider,
|
||||||
Offline: false,
|
Offline: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p MixedProvider) Mods(context context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
func (p MixedProvider) Mods(context context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
||||||
if p.Offline {
|
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) {
|
func (p MixedProvider) GetMod(context context.Context, modReference string) (*ficsit.GetModResponse, error) {
|
||||||
if p.Offline {
|
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 {
|
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 {
|
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 {
|
if p.Offline {
|
||||||
return p.localProvider.ModVersionsWithDependencies(context, modID)
|
return p.offlineProvider.GetModName(context, modReference) // nolint
|
||||||
}
|
}
|
||||||
return p.ficsitProvider.ModVersionsWithDependencies(context, modID)
|
return p.onlineProvider.GetModName(context, modReference) // nolint
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p MixedProvider) IsOffline() bool {
|
func (p MixedProvider) IsOffline() bool {
|
||||||
|
|
|
@ -3,15 +3,14 @@ package provider
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
|
resolver.Provider
|
||||||
Mods(context context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error)
|
Mods(context context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error)
|
||||||
GetMod(context context.Context, modReference string) (*ficsit.GetModResponse, 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
|
IsOffline() bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/MarvinJWendt/testza"
|
"github.com/MarvinJWendt/testza"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/cfg"
|
"github.com/satisfactorymodding/ficsit-cli/cfg"
|
||||||
)
|
)
|
||||||
|
@ -15,10 +18,6 @@ func init() {
|
||||||
cfg.SetDefaults()
|
cfg.SetDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
func profilesGetResolver() DependencyResolver {
|
|
||||||
return NewDependencyResolver(MockProvider{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func installWatcher() chan<- InstallUpdate {
|
func installWatcher() chan<- InstallUpdate {
|
||||||
c := make(chan InstallUpdate)
|
c := make(chan InstallUpdate)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -35,60 +34,6 @@ func installWatcher() chan<- InstallUpdate {
|
||||||
return c
|
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) {
|
func TestUpdateMods(t *testing.T) {
|
||||||
ctx, err := InitCLI(false)
|
ctx, err := InitCLI(false)
|
||||||
testza.AssertNoError(t, err)
|
testza.AssertNoError(t, err)
|
||||||
|
@ -98,17 +43,11 @@ func TestUpdateMods(t *testing.T) {
|
||||||
|
|
||||||
ctx.Provider = MockProvider{}
|
ctx.Provider = MockProvider{}
|
||||||
|
|
||||||
resolver := NewDependencyResolver(ctx.Provider)
|
depResolver := resolver.NewDependencyResolver(ctx.Provider, viper.GetString("api-base"))
|
||||||
|
|
||||||
oldLockfile, err := (&Profile{
|
oldLockfile, err := depResolver.ResolveModDependencies(context.Background(), map[string]string{
|
||||||
Name: DefaultProfileName,
|
"FicsitRemoteMonitoring": "0.9.8",
|
||||||
Mods: map[string]ProfileMod{
|
}, nil, math.MaxInt, nil)
|
||||||
"FicsitRemoteMonitoring": {
|
|
||||||
Version: "0.9.8",
|
|
||||||
Enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).Resolve(resolver, nil, math.MaxInt)
|
|
||||||
|
|
||||||
testza.AssertNoError(t, err)
|
testza.AssertNoError(t, err)
|
||||||
testza.AssertNotNil(t, oldLockfile)
|
testza.AssertNotNil(t, oldLockfile)
|
||||||
|
|
|
@ -4,13 +4,17 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
resolver "github.com/satisfactorymodding/ficsit-resolver"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/cli/provider"
|
"github.com/satisfactorymodding/ficsit-cli/cli/provider"
|
||||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ provider.Provider = (*MockProvider)(nil)
|
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) {
|
func (m MockProvider) Mods(_ context.Context, f ficsit.ModFilter) (*ficsit.ModsResponse, error) {
|
||||||
if f.Offset > 0 {
|
if f.Offset > 0 {
|
||||||
|
@ -66,392 +70,111 @@ func (m MockProvider) Mods(_ context.Context, f ficsit.ModFilter) (*ficsit.ModsR
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var commonTargets = []resolver.Target{
|
||||||
|
{
|
||||||
|
TargetName: "Windows",
|
||||||
|
Hash: "698df20278b3de3ec30405569a22050c6721cc682389312258c14948bd8f38ae",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TargetName: "WindowsServer",
|
||||||
|
Hash: "7be01ed372e0cf3287a04f5cb32bb9dcf6f6e7a5b7603b7e43669ec4c6c1457f",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TargetName: "LinuxServer",
|
||||||
|
Hash: "bdbd4cb1b472a5316621939ae2fe270fd0e3c0f0a75666a9cbe74ff1313c3663",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockProvider) ModVersionsWithDependencies(ctx context.Context, modID string) ([]resolver.ModVersion, error) {
|
||||||
|
switch modID {
|
||||||
|
case "AreaActions":
|
||||||
|
return []resolver.ModVersion{
|
||||||
|
{
|
||||||
|
ID: "7QcfNdo5QAAyoC",
|
||||||
|
Version: "1.6.7",
|
||||||
|
Dependencies: []resolver.Dependency{
|
||||||
|
{
|
||||||
|
ModID: "SML",
|
||||||
|
Condition: "^3.4.1",
|
||||||
|
Optional: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: commonTargets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "7QcfNdo5QAAyoC",
|
||||||
|
Version: "1.6.6",
|
||||||
|
Dependencies: []resolver.Dependency{
|
||||||
|
{
|
||||||
|
ModID: "SML",
|
||||||
|
Condition: "^3.2.0",
|
||||||
|
Optional: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: commonTargets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "7QcfNdo5QAAyoC",
|
||||||
|
Version: "1.6.5",
|
||||||
|
Dependencies: []resolver.Dependency{
|
||||||
|
{
|
||||||
|
ModID: "SML",
|
||||||
|
Condition: "^3.0.0",
|
||||||
|
Optional: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: commonTargets,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
case "FicsitRemoteMonitoring":
|
||||||
|
return []resolver.ModVersion{
|
||||||
|
{
|
||||||
|
ID: "7QcfNdo5QAAyoC",
|
||||||
|
Version: "0.10.1",
|
||||||
|
Dependencies: []resolver.Dependency{
|
||||||
|
{
|
||||||
|
ModID: "SML",
|
||||||
|
Condition: "^3.6.0",
|
||||||
|
Optional: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: commonTargets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "7QcfNdo5QAAyoC",
|
||||||
|
Version: "0.10.0",
|
||||||
|
Dependencies: []resolver.Dependency{
|
||||||
|
{
|
||||||
|
ModID: "SML",
|
||||||
|
Condition: "^3.5.0",
|
||||||
|
Optional: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: commonTargets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "7QcfNdo5QAAyoC",
|
||||||
|
Version: "0.9.8",
|
||||||
|
Dependencies: []resolver.Dependency{
|
||||||
|
{
|
||||||
|
ModID: "SML",
|
||||||
|
Condition: "^3.4.1",
|
||||||
|
Optional: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: commonTargets,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.MockProvider.ModVersionsWithDependencies(ctx, modID) // nolint
|
||||||
|
}
|
||||||
|
|
||||||
func (m MockProvider) GetMod(_ context.Context, _ string) (*ficsit.GetModResponse, error) {
|
func (m MockProvider) GetMod(_ context.Context, _ string) (*ficsit.GetModResponse, error) {
|
||||||
// Currently used only by TUI
|
// Currently used only by TUI
|
||||||
return nil, nil
|
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{
|
|
||||||
{
|
|
||||||
TargetName: "Windows",
|
|
||||||
Hash: "62f5c84eca8480b3ffe7d6c90f759e3b463f482530e27d854fd48624fdd3acc9",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
TargetName: "WindowsServer",
|
|
||||||
Hash: "8a83fcd4abece4192038769cc672fff6764d72c32fb6c7a8c58d66156bb07917",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
TargetName: "LinuxServer",
|
|
||||||
Hash: "8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m MockProvider) ModVersionsWithDependencies(_ context.Context, modID string) (*ficsit.AllVersionsResponse, 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{
|
|
||||||
{
|
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "1.6.7",
|
|
||||||
Dependencies: []ficsit.Dependency{
|
|
||||||
{
|
|
||||||
ModID: "SML",
|
|
||||||
Condition: "^3.4.1",
|
|
||||||
Optional: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Targets: commonTargets,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "1.6.6",
|
|
||||||
Dependencies: []ficsit.Dependency{
|
|
||||||
{
|
|
||||||
ModID: "SML",
|
|
||||||
Condition: "^3.2.0",
|
|
||||||
Optional: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Targets: commonTargets,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "1.6.5",
|
|
||||||
Dependencies: []ficsit.Dependency{
|
|
||||||
{
|
|
||||||
ModID: "SML",
|
|
||||||
Condition: "^3.0.0",
|
|
||||||
Optional: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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{
|
|
||||||
{
|
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "0.10.1",
|
|
||||||
Dependencies: []ficsit.Dependency{
|
|
||||||
{
|
|
||||||
ModID: "SML",
|
|
||||||
Condition: "^3.6.0",
|
|
||||||
Optional: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Targets: commonTargets,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "0.10.0",
|
|
||||||
Dependencies: []ficsit.Dependency{
|
|
||||||
{
|
|
||||||
ModID: "SML",
|
|
||||||
Condition: "^3.5.0",
|
|
||||||
Optional: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Targets: commonTargets,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "7QcfNdo5QAAyoC",
|
|
||||||
Version: "0.9.8",
|
|
||||||
Dependencies: []ficsit.Dependency{
|
|
||||||
{
|
|
||||||
ModID: "SML",
|
|
||||||
Condition: "^3.4.1",
|
|
||||||
Optional: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Targets: commonTargets,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("ModVersionsWithDependencies: " + modID)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) IsOffline() bool {
|
func (m MockProvider) IsOffline() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
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/lipgloss v0.9.1
|
||||||
github.com/charmbracelet/x/exp/teatest v0.0.0-20231206171822-6e7b9b308fe7
|
github.com/charmbracelet/x/exp/teatest v0.0.0-20231206171822-6e7b9b308fe7
|
||||||
github.com/jlaffaye/ftp v0.2.0
|
github.com/jlaffaye/ftp v0.2.0
|
||||||
github.com/mircearoata/pubgrub-go v0.3.3
|
|
||||||
github.com/muesli/reflow v0.3.0
|
github.com/muesli/reflow v0.3.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pterm/pterm v0.12.71
|
github.com/pterm/pterm v0.12.71
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.0.2
|
github.com/puzpuzpuz/xsync/v3 v3.0.2
|
||||||
github.com/rs/zerolog v1.31.0
|
github.com/rs/zerolog v1.31.0
|
||||||
github.com/sahilm/fuzzy v0.1.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/cobra v1.8.0
|
||||||
github.com/spf13/viper v1.18.0
|
github.com/spf13/viper v1.18.0
|
||||||
golang.org/x/sync v0.5.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-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26 // 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/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // 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/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 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
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 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
|
||||||
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
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=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/pkg/errors"
|
"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"
|
||||||
"github.com/satisfactorymodding/ficsit-cli/cli/provider"
|
"github.com/satisfactorymodding/ficsit-cli/cli/provider"
|
||||||
|
@ -14,8 +16,8 @@ import (
|
||||||
|
|
||||||
type rootModel struct {
|
type rootModel struct {
|
||||||
headerComponent tea.Model
|
headerComponent tea.Model
|
||||||
dependencyResolver cli.DependencyResolver
|
|
||||||
global *cli.GlobalContext
|
global *cli.GlobalContext
|
||||||
|
dependencyResolver resolver.DependencyResolver
|
||||||
currentSize tea.WindowSizeMsg
|
currentSize tea.WindowSizeMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +28,7 @@ func newModel(global *cli.GlobalContext) *rootModel {
|
||||||
Width: 20,
|
Width: 20,
|
||||||
Height: 14,
|
Height: 14,
|
||||||
},
|
},
|
||||||
dependencyResolver: cli.NewDependencyResolver(global.Provider),
|
dependencyResolver: resolver.NewDependencyResolver(global.Provider, viper.GetString("api-base")),
|
||||||
}
|
}
|
||||||
|
|
||||||
m.headerComponent = components.NewHeaderComponent(m)
|
m.headerComponent = components.NewHeaderComponent(m)
|
||||||
|
|
|
@ -248,7 +248,7 @@ func (m apply) View() string {
|
||||||
for _, installPath := range installationList {
|
for _, installPath := range installationList {
|
||||||
s := m.status[installPath]
|
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,
|
lipgloss.Left,
|
||||||
m.overall.ViewAs(s.overallProgress.Percentage()),
|
m.overall.ViewAs(s.overallProgress.Percentage()),
|
||||||
" - ",
|
" - ",
|
||||||
|
@ -265,22 +265,22 @@ func (m apply) View() string {
|
||||||
for _, modReference := range modReferences {
|
for _, modReference := range modReferences {
|
||||||
p := s.modProgresses[modReference]
|
p := s.modProgresses[modReference]
|
||||||
if p.complete || s.done {
|
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 {
|
} else {
|
||||||
if p.downloading {
|
if p.downloading {
|
||||||
strs = append(strs, lipgloss.JoinHorizontal(
|
strs = append(strs, lipgloss.NewStyle().MarginLeft(1).Render(lipgloss.JoinHorizontal(
|
||||||
lipgloss.Left,
|
lipgloss.Left,
|
||||||
m.sub.ViewAs(p.downloadProgress.Percentage()),
|
m.sub.ViewAs(p.downloadProgress.Percentage()),
|
||||||
" - ",
|
" - ",
|
||||||
lipgloss.NewStyle().Render(modReference+" (Downloading)"),
|
lipgloss.NewStyle().Render(modReference+" (Downloading)"),
|
||||||
))
|
)))
|
||||||
} else {
|
} else {
|
||||||
strs = append(strs, lipgloss.JoinHorizontal(
|
strs = append(strs, lipgloss.NewStyle().MarginLeft(1).Render(lipgloss.JoinHorizontal(
|
||||||
lipgloss.Left,
|
lipgloss.Left,
|
||||||
m.sub.ViewAs(p.extractProgress.Percentage()),
|
m.sub.ViewAs(p.extractProgress.Percentage()),
|
||||||
" - ",
|
" - ",
|
||||||
lipgloss.NewStyle().Render(modReference+" (Extracting)"),
|
lipgloss.NewStyle().Render(modReference+" (Extracting)"),
|
||||||
))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
|
||||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||||
"github.com/satisfactorymodding/ficsit-cli/tea/scenes/keys"
|
"github.com/satisfactorymodding/ficsit-cli/tea/scenes/keys"
|
||||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||||
|
@ -53,45 +52,26 @@ func NewModVersionList(root components.RootModel, parent tea.Model, mod utils.Mo
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
items := make([]list.Item, 0)
|
items := make([]list.Item, 0)
|
||||||
allVersions := make([]ficsit.ModVersionsModVersionsVersion, 0)
|
versions, err := root.GetProvider().ModVersionsWithDependencies(context.TODO(), mod.Reference)
|
||||||
offset := 0
|
if err != nil {
|
||||||
for {
|
m.err <- err.Error()
|
||||||
versions, err := root.GetProvider().ModVersions(context.TODO(), mod.Reference, ficsit.VersionFilter{
|
return
|
||||||
Limit: 100,
|
}
|
||||||
Offset: offset,
|
|
||||||
Order: ficsit.OrderDesc,
|
for _, version := range versions {
|
||||||
Order_by: ficsit.VersionFieldsCreatedAt,
|
tempVersion := version
|
||||||
|
items = append(items, utils.SimpleItem[selectModVersionList]{
|
||||||
|
ItemTitle: tempVersion.Version,
|
||||||
|
Activate: func(msg tea.Msg, currentModel selectModVersionList) (tea.Model, tea.Cmd) {
|
||||||
|
err := root.GetCurrentProfile().AddMod(mod.Reference, tempVersion.Version)
|
||||||
|
if err != nil {
|
||||||
|
errorComponent, cmd := components.NewErrorComponent(err.Error(), time.Second*5)
|
||||||
|
currentModel.error = errorComponent
|
||||||
|
return currentModel, cmd
|
||||||
|
}
|
||||||
|
return currentModel.parent, nil
|
||||||
|
},
|
||||||
})
|
})
|
||||||
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
|
|
||||||
items = append(items, utils.SimpleItem[selectModVersionList]{
|
|
||||||
ItemTitle: versions.Mod.Versions[i].Version,
|
|
||||||
Activate: func(msg tea.Msg, currentModel selectModVersionList) (tea.Model, tea.Cmd) {
|
|
||||||
version := allVersions[currentOffset+currentI]
|
|
||||||
err := root.GetCurrentProfile().AddMod(mod.Reference, version.Version)
|
|
||||||
if err != nil {
|
|
||||||
errorComponent, cmd := components.NewErrorComponent(err.Error(), time.Second*5)
|
|
||||||
currentModel.error = errorComponent
|
|
||||||
return currentModel, cmd
|
|
||||||
}
|
|
||||||
return currentModel.parent, nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += len(versions.Mod.Versions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.items <- items
|
m.items <- items
|
||||||
|
|
|
@ -13,8 +13,9 @@ import (
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/muesli/reflow/truncate"
|
"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/ficsit"
|
||||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||||
"github.com/satisfactorymodding/ficsit-cli/tea/scenes/keys"
|
"github.com/satisfactorymodding/ficsit-cli/tea/scenes/keys"
|
||||||
|
@ -111,7 +112,7 @@ func (m updateModsList) LoadModData() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resolver := cli.NewDependencyResolver(m.root.GetProvider())
|
resolver := resolver.NewDependencyResolver(m.root.GetProvider(), viper.GetString("api-base"))
|
||||||
|
|
||||||
updatedLockfile, err := currentProfile.Resolve(resolver, nil, gameVersion)
|
updatedLockfile, err := currentProfile.Resolve(resolver, nil, gameVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue