blob: 3e4d6cab12b3fa003f7a9ac47a4eae96dc3bd8ef [file] [log] [blame]
package main
import (
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"code.hackerspace.pl/hscloud/go/pki"
"github.com/gogo/protobuf/proto"
"github.com/golang/glog"
"google.golang.org/grpc"
"code.hackerspace.pl/hscloud/games/factorio/modproxy/modportal"
pb "code.hackerspace.pl/hscloud/games/factorio/modproxy/proto"
)
func init() {
flag.Set("logtostderr", "true")
}
var (
flagProxy string
flagFactorioPath string
flagConfigPath string
)
func main() {
flag.StringVar(&flagProxy, "proxy", "modproxy.factorio.svc.k0.hswaw.net:4200", "Address of modproxy service")
flag.StringVar(&flagFactorioPath, "factorio_path", "", "Path to factorio server root")
flag.StringVar(&flagConfigPath, "config_path", "config.pb.text", "Path to client config file")
flag.Parse()
conn, err := grpc.Dial(flagProxy, pki.WithClientHSPKI())
if err != nil {
glog.Exitf("Dial(%q): %v", flagProxy, err)
return
}
if flagFactorioPath == "" {
glog.Exitf("factorio_path must be set")
}
if flagConfigPath == "" {
glog.Exitf("config_path must be set")
}
configBytes, err := ioutil.ReadFile(flagConfigPath)
if err != nil {
glog.Exitf("could not read config: %v", err)
}
configString := string(configBytes)
config := &pb.ClientConfig{}
err = proto.UnmarshalText(configString, config)
if err != nil {
glog.Exitf("could not parse config: %v", err)
}
ctx := context.Background()
proxy := pb.NewModProxyClient(conn)
// mod name -> wanted mod version
managed := make(map[string]string)
for _, m := range config.Mod {
modPath := fmt.Sprintf("%s/mods/%s_%s.zip", flagFactorioPath, m.Name, m.Version)
_, err := os.Stat(modPath)
if err == nil {
glog.Infof("Mod %s/%s up to date, skipping.", m.Name, m.Version)
continue
}
i, err := modportal.GetMod(ctx, m.Name)
if err != nil {
glog.Errorf("Could not fetch info about %s/%s: %v", m.Name, m.Version, err)
continue
}
release := i.ReleaseByVersion(m.Version)
if release == nil {
glog.Errorf("%s/%s: version does not exist!", m.Name, m.Version)
continue
}
glog.Infof("Trying to download %s/%s (%s)...", m.Name, m.Version, release.SHA1)
err = downloadMod(ctx, proxy, m.Name, release.SHA1, modPath)
if err != nil {
glog.Errorf("%s/%s: could not download mod: %v", m.Name, m.Version, err)
} else {
glog.Infof("Mod %s/%s downloaded.", m.Name, m.Version)
managed[m.Name] = m.Version
}
}
glog.Infof("Cleaning up old versions of managed mods...")
for mn, mv := range managed {
modPath := fmt.Sprintf("%s/mods/%s_%s.zip", flagFactorioPath, mn, mv)
modGlob := fmt.Sprintf("%s/mods/%s_*.zip", flagFactorioPath, mn)
matches, err := filepath.Glob(modGlob)
if err != nil {
glog.Errorf("Could not find old versions of %q: %v", mn, err)
continue
}
for _, m := range matches {
// skip managed version
if m == modPath {
continue
}
glog.Infof("Deleting old version: %s", m)
err := os.Remove(m)
if err != nil {
glog.Errorf("Could not remove old version %q: %v", m, err)
}
}
}
glog.Infof("Done!")
}
func downloadMod(ctx context.Context, proxy pb.ModProxyClient, modName, sha1, dest string) error {
req := &pb.DownloadRequest{
ModName: modName,
FileSha1: sha1,
}
stream, err := proxy.Download(ctx, req)
if err != nil {
return err
}
data := []byte{}
status := pb.DownloadResponse_STATUS_INVALID
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
if res.Status != pb.DownloadResponse_STATUS_INVALID {
status = res.Status
}
data = append(data, res.Chunk...)
}
switch status {
case pb.DownloadResponse_STATUS_OKAY:
case pb.DownloadResponse_STATUS_NOT_AVAILABLE:
return fmt.Errorf("version not available on proxy")
default:
return fmt.Errorf("invalid download status: %v", status)
}
return ioutil.WriteFile(dest, data, 0644)
}