mirror of
				https://github.com/fumiama/RVC-Models-Downloader.git
				synced 2025-11-04 04:45:08 +09:00 
			
		
		
		
	feat: add TUI implementation
This commit is contained in:
		
							parent
							
								
									d89b927611
								
							
						
					
					
						commit
						61ceedda33
					
				
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							@ -9,11 +9,17 @@ require (
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require github.com/fumiama/terasu v0.0.0-20240418161858-1c3273a78268
 | 
			
		||||
require (
 | 
			
		||||
	github.com/fumiama/terasu v0.0.0-20240418161858-1c3273a78268
 | 
			
		||||
	github.com/gizak/termui/v3 v3.1.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 // indirect
 | 
			
		||||
	github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.2 // indirect
 | 
			
		||||
	github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
 | 
			
		||||
	github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
 | 
			
		||||
	golang.org/x/net v0.24.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.14.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							@ -7,6 +7,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/fumiama/terasu v0.0.0-20240418161858-1c3273a78268 h1:6R8kSGVSIoR3xm2NG8Z4ivkTpAET783RFsJOVKrI7n8=
 | 
			
		||||
github.com/fumiama/terasu v0.0.0-20240418161858-1c3273a78268/go.mod h1:afchyfKAb7J/zvaENtYzjIEPVbwiEjJaow05zzT4usM=
 | 
			
		||||
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
 | 
			
		||||
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 | 
			
		||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
 | 
			
		||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
 | 
			
		||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
 | 
			
		||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
 | 
			
		||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
			
		||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										102
									
								
								log.go
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								log.go
									
									
									
									
									
								
							@ -2,8 +2,11 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/gizak/termui/v3/widgets"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -11,12 +14,92 @@ var (
 | 
			
		||||
	errZeroMeterSize = errors.New("zero meter size")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func infof(format string, args ...any) {
 | 
			
		||||
	if notui {
 | 
			
		||||
		logrus.Infof(format, args...)
 | 
			
		||||
	} else {
 | 
			
		||||
		sc.infof(format, args...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) infof(format string, args ...any) {
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	defer s.Unlock()
 | 
			
		||||
	s.logroll.Rows = append(s.logroll.Rows, fmt.Sprintf("[INFO](fg:blue) "+format, args...))
 | 
			
		||||
	s.logroll.ScrollDown()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func warnf(format string, args ...any) {
 | 
			
		||||
	if notui {
 | 
			
		||||
		logrus.Warnf(format, args...)
 | 
			
		||||
	} else {
 | 
			
		||||
		sc.warnf(format, args...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) warnf(format string, args ...any) {
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	defer s.Unlock()
 | 
			
		||||
	s.logroll.Rows = append(s.logroll.Rows, fmt.Sprintf("[WARN](fg:yellow) "+format, args...))
 | 
			
		||||
	s.logroll.ScrollDown()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func errorf(format string, args ...any) {
 | 
			
		||||
	if notui {
 | 
			
		||||
		logrus.Errorf(format, args...)
 | 
			
		||||
	} else {
 | 
			
		||||
		sc.errorf(format, args...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) errorf(format string, args ...any) {
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	defer s.Unlock()
 | 
			
		||||
	s.logroll.Rows = append(s.logroll.Rows, fmt.Sprintf("[ERRO](fg:red) "+format, args...))
 | 
			
		||||
	s.logroll.ScrollDown()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func infoln(args ...any) {
 | 
			
		||||
	if notui {
 | 
			
		||||
		logrus.Infoln(args...)
 | 
			
		||||
	} else {
 | 
			
		||||
		sc.infoln(args...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) infoln(args ...any) {
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	defer s.Unlock()
 | 
			
		||||
	s.logroll.Rows = append(s.logroll.Rows, strings.TrimSuffix(
 | 
			
		||||
		"[INFO](fg:blue) "+fmt.Sprintln(args...), "\n",
 | 
			
		||||
	))
 | 
			
		||||
	s.logroll.ScrollDown()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func errorln(args ...any) {
 | 
			
		||||
	if notui {
 | 
			
		||||
		logrus.Errorln(args...)
 | 
			
		||||
	} else {
 | 
			
		||||
		sc.errorln(args...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) errorln(args ...any) {
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	defer s.Unlock()
 | 
			
		||||
	s.logroll.Rows = append(s.logroll.Rows, strings.TrimSuffix(
 | 
			
		||||
		"[ERRO](fg:red) "+fmt.Sprintln(args...), "\n",
 | 
			
		||||
	))
 | 
			
		||||
	s.logroll.ScrollDown()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type progressmeter struct {
 | 
			
		||||
	prefix string
 | 
			
		||||
	name   string
 | 
			
		||||
	size   int
 | 
			
		||||
	prgs   int
 | 
			
		||||
	lstp   int
 | 
			
		||||
	fptr   *widgets.Gauge
 | 
			
		||||
	io.Writer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -24,6 +107,9 @@ func newmeter(prefix, name string, size int) (pm progressmeter) {
 | 
			
		||||
	pm.prefix = prefix
 | 
			
		||||
	pm.name = name
 | 
			
		||||
	pm.size = size
 | 
			
		||||
	if !notui {
 | 
			
		||||
		pm.fptr = sc.addfile(prefix+" "+name, size)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,11 +118,25 @@ func (pm *progressmeter) Write(p []byte) (n int, err error) {
 | 
			
		||||
		return 0, errZeroMeterSize
 | 
			
		||||
	}
 | 
			
		||||
	pm.prgs += len(p)
 | 
			
		||||
	if !notui {
 | 
			
		||||
		sc.logwrite(len(p))
 | 
			
		||||
	}
 | 
			
		||||
	percent := pm.prgs * 100 / pm.size
 | 
			
		||||
	if percent == pm.lstp {
 | 
			
		||||
		return len(p), nil
 | 
			
		||||
	}
 | 
			
		||||
	logrus.Infof("%s [%2d%%] %s\t(%d/%dMB)", pm.prefix, percent, pm.name, pm.prgs/1024/1024, pm.size/1024/1024)
 | 
			
		||||
	if notui {
 | 
			
		||||
		logrus.Infof("%s [%2d%%] %s\t(%dMB/%dMB)", pm.prefix, percent, pm.name, pm.prgs/1024/1024, pm.size/1024/1024)
 | 
			
		||||
	} else {
 | 
			
		||||
		pm.fptr.Percent = percent
 | 
			
		||||
		pm.fptr.Label = fmt.Sprintf("%d%% (%dMB/%dMB)", percent, pm.prgs/1024/1024, pm.size/1024/1024)
 | 
			
		||||
	}
 | 
			
		||||
	pm.lstp = percent
 | 
			
		||||
	return len(p), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pm *progressmeter) finish() {
 | 
			
		||||
	if !notui {
 | 
			
		||||
		sc.removefile(pm.fptr)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										75
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								main.go
									
									
									
									
									
								
							@ -9,6 +9,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/fumiama/terasu/dns"
 | 
			
		||||
	"github.com/fumiama/terasu/ip"
 | 
			
		||||
	ui "github.com/gizak/termui/v3"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
 | 
			
		||||
@ -19,15 +20,18 @@ import (
 | 
			
		||||
 | 
			
		||||
const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0"
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	notui = false
 | 
			
		||||
	sc    screen
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	logrus.Infoln("RVC Models Downloader start at", time.Now().Local().Format(time.DateTime+" (MST)"))
 | 
			
		||||
	logrus.Infof("operating system: %s, architecture: %s", runtime.GOOS, runtime.GOARCH)
 | 
			
		||||
	logrus.Infoln("can use ipv6:", ip.IsIPv6Available.Get())
 | 
			
		||||
	ntrs := flag.Bool("notrs", false, "use standard TLS client")
 | 
			
		||||
	dnsf := flag.String("dns", "", "custom dns.yaml")
 | 
			
		||||
	cust := flag.Bool("c", false, "use custom yaml instruction")
 | 
			
		||||
	force := flag.Bool("f", false, "force download even file exists")
 | 
			
		||||
	wait := flag.Uint("w", 4, "connection waiting seconds")
 | 
			
		||||
	flag.BoolVar(¬ui, "notui", false, "use plain text instead of TUI")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	args := flag.Args()
 | 
			
		||||
	if len(args) != 1 {
 | 
			
		||||
@ -38,35 +42,50 @@ func main() {
 | 
			
		||||
		fmt.Println(cmdlst.String())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if *dnsf != "" {
 | 
			
		||||
		f, err := os.Open(*dnsf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Errorln("open custom dns file", *dnsf, "err:", err)
 | 
			
		||||
	if notui {
 | 
			
		||||
		logrus.Infoln("RVC Models Downloader start at", time.Now().Local().Format(time.DateTime+" (MST)"))
 | 
			
		||||
		logrus.Infof("operating system: %s, architecture: %s", runtime.GOOS, runtime.GOARCH)
 | 
			
		||||
		logrus.Infoln("is ipv6 available:", ip.IsIPv6Available.Get())
 | 
			
		||||
	} else {
 | 
			
		||||
		if err := ui.Init(); err != nil {
 | 
			
		||||
			logrus.Errorln("failed to initialize termui:", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		m := map[string][]string{}
 | 
			
		||||
		err = yaml.NewDecoder(f).Decode(&m)
 | 
			
		||||
		defer ui.Close()
 | 
			
		||||
		sc = newscreen()
 | 
			
		||||
	}
 | 
			
		||||
	go func() {
 | 
			
		||||
		if *dnsf != "" {
 | 
			
		||||
			f, err := os.Open(*dnsf)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				errorln("open custom dns file", *dnsf, "err:", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			m := map[string][]string{}
 | 
			
		||||
			err = yaml.NewDecoder(f).Decode(&m)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				errorln("decode custom dns file", *dnsf, "err:", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			_ = f.Close()
 | 
			
		||||
			if ip.IsIPv6Available.Get() {
 | 
			
		||||
				dns.IPv6Servers.Add(m)
 | 
			
		||||
			} else {
 | 
			
		||||
				dns.IPv4Servers.Add(m)
 | 
			
		||||
			}
 | 
			
		||||
			infoln("custom dns file added")
 | 
			
		||||
		}
 | 
			
		||||
		usercfg, err := readconfig(args[0], *cust)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Errorln("decode custom dns file", *dnsf, "err:", err)
 | 
			
		||||
			errorln(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		_ = f.Close()
 | 
			
		||||
		if ip.IsIPv6Available.Get() {
 | 
			
		||||
			dns.IPv6Servers.Add(m)
 | 
			
		||||
		} else {
 | 
			
		||||
			dns.IPv4Servers.Add(m)
 | 
			
		||||
		err = usercfg.download(args[0], "", time.Second*time.Duration(*wait), *cust, !*ntrs, *force)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorln(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println("custom dns file added")
 | 
			
		||||
	}
 | 
			
		||||
	usercfg, err := readconfig(args[0], *cust)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorln(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = usercfg.download(args[0], "", time.Second*time.Duration(*wait), *cust, !*ntrs, *force)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorln(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	logrus.Info("all download tasks finished.")
 | 
			
		||||
		infoln("all download tasks finished.")
 | 
			
		||||
	}()
 | 
			
		||||
	sc.flushloop(time.Second)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								net.go
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								net.go
									
									
									
									
									
								
							@ -13,14 +13,13 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/fumiama/terasu/http2"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (c *config) download(path, prefix string, waits time.Duration, usecust, usetrs, force bool) error {
 | 
			
		||||
	for i, t := range c.Targets {
 | 
			
		||||
		if t.Refer != "" {
 | 
			
		||||
			refp := path[:strings.LastIndex(path, "/")+1] + t.Refer
 | 
			
		||||
			logrus.Infof("#%s%d refer to target '%s'.", prefix, i+1, refp)
 | 
			
		||||
			infof("#%s%d refer to target '%s'.", prefix, i+1, refp)
 | 
			
		||||
			refcfg, err := readconfig(refp, usecust)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
@ -32,25 +31,25 @@ func (c *config) download(path, prefix string, waits time.Duration, usecust, use
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if t.OS != "" && t.OS != runtime.GOOS {
 | 
			
		||||
			logrus.Warnf("#%s%d target required OS: %s but you are %s, skip.", prefix, i+1, t.OS, runtime.GOOS)
 | 
			
		||||
			warnf("#%s%d target required OS: %s but you are %s, skip.", prefix, i+1, t.OS, runtime.GOOS)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if t.Arch != "" && t.Arch != runtime.GOARCH {
 | 
			
		||||
			logrus.Warnf("#%s%d target required Arch: %s but you are %s, skip.", prefix, i+1, t.Arch, runtime.GOARCH)
 | 
			
		||||
			warnf("#%s%d target required Arch: %s but you are %s, skip.", prefix, i+1, t.Arch, runtime.GOARCH)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		err := os.MkdirAll(t.Folder, 0755)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, fmt.Sprintf("#%s%d make target folder '%s'", prefix, i+1, t.Folder))
 | 
			
		||||
		}
 | 
			
		||||
		logrus.Infof("#%s%d open target folder '%s'.", prefix, i+1, t.Folder)
 | 
			
		||||
		infof("#%s%d open target folder '%s'.", prefix, i+1, t.Folder)
 | 
			
		||||
		if len(t.Copy) == 0 {
 | 
			
		||||
			logrus.Warningf("#%s%d empty copy target.", prefix, i+1)
 | 
			
		||||
			warnf("#%s%d empty copy target.", prefix, i+1)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		wg := sync.WaitGroup{}
 | 
			
		||||
		wg.Add(len(t.Copy))
 | 
			
		||||
		logrus.Infof("#%s%d download copy: '%v'.", prefix, i+1, t.Copy)
 | 
			
		||||
		infof("#%s%d download copy: '%v'.", prefix, i+1, t.Copy)
 | 
			
		||||
		for j, cp := range t.Copy {
 | 
			
		||||
			go func(i int, cp, prefix string) {
 | 
			
		||||
				defer wg.Done()
 | 
			
		||||
@ -61,16 +60,16 @@ func (c *config) download(path, prefix string, waits time.Duration, usecust, use
 | 
			
		||||
				fname := t.Folder + "/" + cp[strings.LastIndex(cp, "/")+1:]
 | 
			
		||||
				if !force {
 | 
			
		||||
					if _, err := os.Stat(fname); err == nil || os.IsExist(err) {
 | 
			
		||||
						logrus.Warnf("#%s%d skip exist file %s", prefix, i+1, fname)
 | 
			
		||||
						warnf("#%s%d skip exist file %s", prefix, i+1, fname)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				req, err := http.NewRequest("GET", c.BaseURL+"/"+cp, nil)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logrus.Errorf("#%s%d new request to %s err: %v", prefix, i+1, cp, err)
 | 
			
		||||
					errorf("#%s%d new request to %s err: %v", prefix, i+1, cp, err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				logrus.Infof("#%s%d get: %s", prefix, i+1, req.URL)
 | 
			
		||||
				infof("#%s%d get: %s", prefix, i+1, req.URL)
 | 
			
		||||
				req.Header.Add("user-agent", ua)
 | 
			
		||||
				var resp *http.Response
 | 
			
		||||
				if usetrs {
 | 
			
		||||
@ -79,29 +78,30 @@ func (c *config) download(path, prefix string, waits time.Duration, usecust, use
 | 
			
		||||
					resp, err = http.DefaultClient.Do(req)
 | 
			
		||||
				}
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logrus.Errorf("#%s%d get %s err: %v", prefix, i+1, req.URL, err)
 | 
			
		||||
					errorf("#%s%d get %s err: %v", prefix, i+1, req.URL, err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				defer resp.Body.Close()
 | 
			
		||||
				if resp.StatusCode != http.StatusOK {
 | 
			
		||||
					err := errors.New(fmt.Sprintf("HTTP %d %s", resp.StatusCode, resp.Status))
 | 
			
		||||
					logrus.Errorf("#%s%d get %s err: %v", prefix, i+1, req.URL, err)
 | 
			
		||||
					errorf("#%s%d get %s err: %v", prefix, i+1, req.URL, err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				f, err := os.Create(fname)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logrus.Errorf("#%s%d create file %s err: %v", prefix, i+1, fname, err)
 | 
			
		||||
					errorf("#%s%d create file %s err: %v", prefix, i+1, fname, err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				defer f.Close()
 | 
			
		||||
				logrus.Infof("#%s%d writing file %s", prefix, i+1, fname)
 | 
			
		||||
				infof("#%s%d writing file %s", prefix, i+1, fname)
 | 
			
		||||
				pm := newmeter(fmt.Sprintf("#%s%d", prefix, i+1), fname, int(resp.ContentLength))
 | 
			
		||||
				defer pm.finish()
 | 
			
		||||
				_, err = io.Copy(io.MultiWriter(f, &pm), resp.Body)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logrus.Errorf("#%s%d download file %s err: %v", prefix, i+1, fname, err)
 | 
			
		||||
					errorf("#%s%d download file %s err: %v", prefix, i+1, fname, err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				logrus.Infof("#%s%d finished download %s", prefix, i+1, fname)
 | 
			
		||||
				infof("#%s%d finished download %s", prefix, i+1, fname)
 | 
			
		||||
			}(j, cp, fmt.Sprintf("%s%d.", prefix, i+1))
 | 
			
		||||
		}
 | 
			
		||||
		wg.Wait()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										141
									
								
								ui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								ui.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,141 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unsafe"
 | 
			
		||||
 | 
			
		||||
	"github.com/fumiama/terasu/ip"
 | 
			
		||||
	ui "github.com/gizak/termui/v3"
 | 
			
		||||
	"github.com/gizak/termui/v3/widgets"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type screen struct {
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
	sysinfo  *widgets.Paragraph
 | 
			
		||||
	logroll  *widgets.List
 | 
			
		||||
	speedln  *widgets.Plot
 | 
			
		||||
	prgbars  []ui.Drawable
 | 
			
		||||
	reusepg  []*widgets.Gauge
 | 
			
		||||
	currh, w int
 | 
			
		||||
	totaldl  int
 | 
			
		||||
	lastclr  time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newscreen() (s screen) {
 | 
			
		||||
	w, h := ui.TerminalDimensions()
 | 
			
		||||
	s.w = w
 | 
			
		||||
 | 
			
		||||
	s.sysinfo = widgets.NewParagraph()
 | 
			
		||||
	s.sysinfo.Title = "System Info"
 | 
			
		||||
	s.sysinfo.BorderStyle.Fg = ui.ColorGreen
 | 
			
		||||
	s.sysinfo.Text = fmt.Sprintf(
 | 
			
		||||
		"[Time](mod:bold): %s\n[OS](mod:bold): %s, [Architecture](mod:bold): %s\n[Is IPv6 available](mod:bold): %v",
 | 
			
		||||
		time.Now().Local().Format(time.DateTime+" (MST)"),
 | 
			
		||||
		runtime.GOOS, runtime.GOARCH,
 | 
			
		||||
		ip.IsIPv6Available.Get(),
 | 
			
		||||
	)
 | 
			
		||||
	s.sysinfo.SetRect(0, s.currh, w/2, s.currh+5)
 | 
			
		||||
	s.currh += 5
 | 
			
		||||
 | 
			
		||||
	s.logroll = widgets.NewList()
 | 
			
		||||
	s.logroll.Title = "Logs"
 | 
			
		||||
	s.logroll.BorderStyle.Fg = ui.ColorBlue
 | 
			
		||||
	s.logroll.WrapText = false
 | 
			
		||||
	s.logroll.SetRect(w/2, 0, w, h/2)
 | 
			
		||||
 | 
			
		||||
	s.speedln = widgets.NewPlot()
 | 
			
		||||
	s.speedln.Title = "Speed"
 | 
			
		||||
	s.speedln.Data = make([][]float64, 1)
 | 
			
		||||
	s.speedln.Data[0] = []float64{0, 0}
 | 
			
		||||
	s.speedln.AxesColor = ui.ColorWhite
 | 
			
		||||
	s.speedln.LineColors[0] = ui.ColorYellow
 | 
			
		||||
	s.speedln.BorderStyle.Fg = ui.ColorYellow
 | 
			
		||||
	s.speedln.SetRect(w/2, h/2, w, h)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) logwrite(sz int) {
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	defer s.Unlock()
 | 
			
		||||
	s.totaldl += sz
 | 
			
		||||
	tdiff := time.Since(s.lastclr)
 | 
			
		||||
	if tdiff > time.Second {
 | 
			
		||||
		s.speedln.Data[0] = append(s.speedln.Data[0],
 | 
			
		||||
			float64(s.totaldl/1024)/(float64(tdiff)/float64(time.Second)),
 | 
			
		||||
		)
 | 
			
		||||
		s.totaldl = 0
 | 
			
		||||
		s.lastclr = time.Now()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) flushloop(interval time.Duration) {
 | 
			
		||||
	t := time.NewTicker(interval)
 | 
			
		||||
	defer t.Stop()
 | 
			
		||||
	s.flush()
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case e := <-ui.PollEvents():
 | 
			
		||||
			s.flush()
 | 
			
		||||
			if e.Type == ui.KeyboardEvent {
 | 
			
		||||
				switch e.ID {
 | 
			
		||||
				case "q", "<Escape>", "<C-c>":
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case <-t.C:
 | 
			
		||||
			s.flush()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) flush() {
 | 
			
		||||
	s.RLock()
 | 
			
		||||
	defer s.RUnlock()
 | 
			
		||||
	ui.Render(s.sysinfo, s.logroll, s.speedln)
 | 
			
		||||
	if len(s.prgbars) > 0 {
 | 
			
		||||
		ui.Render(s.prgbars...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) addfile(name string, size int) *widgets.Gauge {
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	defer s.Unlock()
 | 
			
		||||
	var g *widgets.Gauge
 | 
			
		||||
	if len(s.reusepg) > 0 {
 | 
			
		||||
		b := len(s.reusepg) - 1
 | 
			
		||||
		g = s.reusepg[b]
 | 
			
		||||
		s.reusepg = s.reusepg[:b]
 | 
			
		||||
	} else {
 | 
			
		||||
		g = widgets.NewGauge()
 | 
			
		||||
		g.SetRect(0, s.currh, s.w/2, s.currh+3)
 | 
			
		||||
		s.currh += 3
 | 
			
		||||
	}
 | 
			
		||||
	g.Title = name
 | 
			
		||||
	g.Label = fmt.Sprintf("0%% (0MB/%dMB)", size/1024/1024)
 | 
			
		||||
	s.prgbars = append(s.prgbars, g)
 | 
			
		||||
	return g
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *screen) removefile(g *widgets.Gauge) {
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	defer s.Unlock()
 | 
			
		||||
	for i, obj := range s.prgbars {
 | 
			
		||||
		if *(**widgets.Gauge)(unsafe.Add(
 | 
			
		||||
			unsafe.Pointer(&obj), unsafe.Sizeof(uintptr(0)),
 | 
			
		||||
		)) == g {
 | 
			
		||||
			switch i {
 | 
			
		||||
			case 0:
 | 
			
		||||
				s.prgbars = s.prgbars[1:]
 | 
			
		||||
			case len(s.prgbars) - 1:
 | 
			
		||||
				s.prgbars = s.prgbars[:len(s.prgbars)-1]
 | 
			
		||||
			default:
 | 
			
		||||
				s.prgbars = append(s.prgbars[:i], s.prgbars[i+1:]...)
 | 
			
		||||
			}
 | 
			
		||||
			s.reusepg = append(s.reusepg, g)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user