~welt/murse

f34e87efc1d7fe42b7ed7e1cc456074bc6eb1919 — welt 5 months ago 46724b6 v0.0.1
Fix concurrency bug, simplify upgrade function, add verify command.
6 files changed, 207 insertions(+), 16 deletions(-)

M concurrent.go
A const.go
M main.go
M upgrade.go
A verify.go
M versioning.go
M concurrent.go => concurrent.go +4 -2
@@ 1,6 1,8 @@
package main

import "context"
import (
	"context"
)

type Pool struct {
	Errch  chan error


@@ 46,6 48,6 @@ func NewPool(workers int) *Pool {
func (p *Pool) Stop() {
	p.ctxf()
	for i := 1; i != p.num; i++ {
		_ = p.donech
		<-p.donech
	}
}

A const.go => const.go +3 -0
@@ 0,0 1,3 @@
package main

const VERSION = "0.0.1"

M main.go => main.go +18 -2
@@ 2,25 2,35 @@ package main

import (
	"os"
	"runtime"

	"github.com/integrii/flaggy"
)

func main() {
	url := "https://toast.openfortress.fun/toast/"
	var threads uint64 = 6
	var threads int = (runtime.NumCPU())
	var dir string
	var dry bool
	var http2 bool
	var repair bool

	upgrade := flaggy.NewSubcommand("upgrade")
	upgrade.Description = "Upgrades your game files to the latest version."
	upgrade.String(&url, "u", "url", "URL of TVS repository with a slash at the end.")
	upgrade.UInt64(&threads, "c", "threads", "Number of threads to use for downloading, minimum two.")
	upgrade.Int(&threads, "c", "threads", "Number of threads to use for downloading, minimum two.")
	upgrade.Bool(&http2, "2", "http2", "Enables HTTP/2, may cause problems with the servers but is faster.")
	upgrade.AddPositionalValue(&dir, "directory", 1, true, "Directory of game files.")
	flaggy.AttachSubcommand(upgrade, 1)

	verify := flaggy.NewSubcommand("verify")
	verify.Description = "Verifies an installation's files."
	verify.AddPositionalValue(&dir, "directory", 1, true, "Directory of game files.")
	verify.String(&url, "u", "url", "URL of TVS repository with a slash at the end.")
	verify.Bool(&repair, "r", "repair", "Repair the files where problems are found automatically.")
	verify.Bool(&http2, "2", "http2", "Enables HTTP/2, may cause problems with the servers but is faster.")
	flaggy.AttachSubcommand(verify, 1)

	var tvsdir string
	toast := flaggy.NewSubcommand("toast")
	toast.Description = "Toasts files. Don't use this unless you know what you're doing."


@@ 29,6 39,8 @@ func main() {
	toast.Bool(&dry, "d", "dry", "Runs the command, but doesn't write any changes.")
	flaggy.AttachSubcommand(toast, 1)

	flaggy.SetVersion(VERSION)

	flaggy.Parse()

	if upgrade.Used {


@@ 39,6 51,10 @@ func main() {
		os.Exit(upgradeMain(dir, url, threads, http2))
	}

	if verify.Used {
		os.Exit(verifyMain(dir, url, repair, http2))
	}

	if toast.Used {
		os.Exit(toastMain(dir, tvsdir, dry))
	}

M upgrade.go => upgrade.go +4 -11
@@ 9,7 9,7 @@ import (
	"strconv"
)

func upgradeMain(dir string, url string, threads uint64, http2 bool) int {
func upgradeMain(dir string, url string, threads int, http2 bool) int {
	instRev, err := getInstalledRevision(dir)
	if err != nil {
		errPrintln(err)


@@ 73,9 73,7 @@ func upgradeMain(dir string, url string, threads uint64, http2 bool) int {
		}
	}

	pool := NewPool(int(threads))
	jobs := len(writes)
	done := 0
	pool := NewPool(threads)

	for _, v := range writes {
		change := v


@@ 123,18 121,13 @@ func upgradeMain(dir string, url string, threads uint64, http2 bool) int {
			postloop:
				return 1
			}

			done++
		default:
			continue
		}

		if done >= jobs {
			pool.Stop()
			break
		}
	}

	pool.Stop()

	file, err := os.Create(filepath.Join(dir, ".revision"))
	if err != nil {
		errPrintln(err)

A verify.go => verify.go +177 -0
@@ 0,0 1,177 @@
package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"os"
	"path/filepath"
)

func verifyMain(dir string, url string, repair bool, http2 bool) int {
	instRev, err := getInstalledRevision(dir)
	if err != nil {
		errPrintln(err)
		return 1
	}

	client := NewClient(url)

	if !http2 {
		client.DisableHTTP2()
	}

	var revisions [][]Change
	for i := 0; i != instRev+1; i++ {
		rev, err := client.GetRevision(i)
		if err != nil {
			errPrintln(err)
			return 1
		}

		revisions = append(revisions, rev)
	}

	changes := replayChanges(revisions)

	writes, mkdirs, deletes := splitChanges(changes)

	for _, v := range deletes {
		path := filepath.Join(dir, filepath.FromSlash(v.Path))
		_, err := os.Stat(path)
		if err != nil {
			if !os.IsNotExist(err) {
				errPrintln(err)
				return 1
			}
		} else {
			errPrintln("EXISTS", v.Path)
			if repair {
				err = os.Remove(path)
				if err != nil {
					errPrintln(err)
					return 1
				}
				fmt.Println("FIXDIR", v.Path)
			}
		}
	}

	for _, v := range mkdirs {
		path := filepath.Join(dir, filepath.FromSlash(v.Path))

		stat, err := os.Stat(path)
		if err != nil {
			if os.IsNotExist(err) {
				fmt.Println("MISSING", v.Path)
				if repair {
					err := os.MkdirAll(path, 0777)
					if err != nil {
						errPrintln(err)
						return 1
					}

					fmt.Println("FIXDIR", v.Path)

				}
			} else {
				errPrintln(err)
				return 1
			}
		} else {
			if !stat.IsDir() {
				fmt.Println("NOTDIR", v.Path)
				if repair {
					err := os.RemoveAll(path)
					if err != nil {
						errPrintln(err)
						return 1
					}

					err = os.MkdirAll(path, 0777)
					if err != nil {
						errPrintln(err)
						return 1
					}

					fmt.Println("FIXDIR", v.Path)

				}
			}
		}
	}

	for _, v := range writes {
		path := filepath.Join(dir, filepath.FromSlash(v.Path))
		var fix func() error

		if repair {
			fix = func() error {
				object, err := client.GetObject(v.Object)
				if err != nil {
					return err
				}

				defer object.Close()

				file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0777)
				if err != nil {
					return err
				}

				defer file.Close()

				_, err = io.Copy(file, object)
				if err == nil {
					fmt.Println("FIXWRITE", v.Path)
				}
				return err
			}
		}

		file, err := os.Open(path)
		if err != nil {
			if !os.IsNotExist(err) {
				errPrintln(err)
				return 1
			} else {
				fmt.Println("MISSING", v.Path)
				if repair {
					err := fix()
					if err != nil {
						errPrintln(err)
						return 1
					}
				}
			}
		} else {
			defer file.Close()

			hash := md5.New()
			_, err = io.Copy(hash, file)
			if err != nil {
				errPrintln(err)
				return 1
			}

			file.Close()

			md5 := hex.EncodeToString(hash.Sum(nil))
			if v.MD5 != md5 {
				fmt.Println("INCORRECT", v.Path, "EXPECTED", v.MD5, "GOT", md5)
				if repair {
					err := fix()
					if err != nil {
						errPrintln(err)
						return 1
					}
				}
			}
		}

	}

	return 0

}

M versioning.go => versioning.go +1 -1
@@ 13,7 13,7 @@ const (
type Change struct {
	Type   uint   `json:"type"`
	Path   string `json:"path"`
	MD5    string `json:"md5"`
	MD5    string `json:"hash"`
	Object string `json:"object"`
}