mirror of
https://github.com/XIU2/CloudflareSpeedTest.git
synced 2026-04-05 10:36:24 +08:00
tcping add progressbar
This commit is contained in:
16
main.go
16
main.go
@@ -11,6 +11,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"CloudflareSpeedTest/task"
|
||||||
|
|
||||||
"github.com/cheggaaa/pb/v3"
|
"github.com/cheggaaa/pb/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,12 +78,16 @@ https://github.com/XIU2/CloudflareSpeedTest
|
|||||||
flag.Float64Var(&speedLimit, "sl", 0, "下载速度下限")
|
flag.Float64Var(&speedLimit, "sl", 0, "下载速度下限")
|
||||||
flag.IntVar(&printResultNum, "p", 20, "显示结果数量")
|
flag.IntVar(&printResultNum, "p", 20, "显示结果数量")
|
||||||
flag.BoolVar(&disableDownload, "dd", false, "禁用下载测速")
|
flag.BoolVar(&disableDownload, "dd", false, "禁用下载测速")
|
||||||
flag.BoolVar(&ipv6Mode, "ipv6", false, "禁用下载测速")
|
flag.BoolVar(&ipv6Mode, "ipv6", false, "启用IPv6")
|
||||||
flag.BoolVar(&allip, "allip", false, "测速全部 IP")
|
flag.BoolVar(&allip, "allip", false, "测速全部 IP")
|
||||||
flag.StringVar(&ipFile, "f", "ip.txt", "IP 数据文件")
|
flag.StringVar(&ipFile, "f", "ip.txt", "IP 数据文件")
|
||||||
flag.StringVar(&outputFile, "o", "result.csv", "输出结果文件")
|
flag.StringVar(&outputFile, "o", "result.csv", "输出结果文件")
|
||||||
flag.BoolVar(&printVersion, "v", false, "打印程序版本")
|
flag.BoolVar(&printVersion, "v", false, "打印程序版本")
|
||||||
|
|
||||||
|
task.TCPPort = tcpPort
|
||||||
|
task.IPv6 = ipv6Mode
|
||||||
|
task.DefaultRoutine = pingRoutine
|
||||||
|
|
||||||
flag.Usage = func() { fmt.Print(help) }
|
flag.Usage = func() { fmt.Print(help) }
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if printVersion {
|
if printVersion {
|
||||||
@@ -155,16 +161,20 @@ func main() {
|
|||||||
ipVersion = "IPv6"
|
ipVersion = "IPv6"
|
||||||
}
|
}
|
||||||
fmt.Printf("开始延迟测速(模式:TCP %s,端口:%d ,平均延迟上限:%.2f ms,平均延迟下限:%.2f ms):\n", ipVersion, tcpPort, timeLimit, timeLimitLow)
|
fmt.Printf("开始延迟测速(模式:TCP %s,端口:%d ,平均延迟上限:%.2f ms,平均延迟下限:%.2f ms):\n", ipVersion, tcpPort, timeLimit, timeLimitLow)
|
||||||
|
|
||||||
|
// ping := task.NewPing(ips)
|
||||||
|
// ping.Run()
|
||||||
control := make(chan bool, pingRoutine)
|
control := make(chan bool, pingRoutine)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
// control <- false
|
control <- false
|
||||||
handleProgress := handleProgressGenerator(bar) // 多线程进度条
|
handleProgress := handleProgressGenerator(bar) // 多线程进度条
|
||||||
go tcpingGoroutine(&wg, &mu, ip, tcpPort, pingTime, &data, control, handleProgress)
|
go tcpingGoroutine(&wg, &mu, ip, tcpPort, pingTime, &data, control, handleProgress)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
bar.Finish()
|
bar.Finish()
|
||||||
|
// data := ping.Data()
|
||||||
|
// sort.Sort(utils.CloudflareIPDataSet(data))
|
||||||
sort.Sort(CloudflareIPDataSet(data)) // 排序(按延迟,从低到高,不同丢包率会分开单独按延迟和丢包率排序)
|
sort.Sort(CloudflareIPDataSet(data)) // 排序(按延迟,从低到高,不同丢包率会分开单独按延迟和丢包率排序)
|
||||||
|
|
||||||
// 延迟测速完毕后,以 [平均延迟上限] + [平均延迟下限] 条件过滤结果
|
// 延迟测速完毕后,以 [平均延迟上限] + [平均延迟下限] 条件过滤结果
|
||||||
|
|||||||
6
task/ip.go
Normal file
6
task/ip.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
var (
|
||||||
|
// IPv6 IP version is 6
|
||||||
|
IPv6 = false
|
||||||
|
)
|
||||||
135
task/tcping.go
Normal file
135
task/tcping.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"CloudflareSpeedTest/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tcpConnectTimeout = time.Second * 1
|
||||||
|
maxRoutine = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultRoutine = 200
|
||||||
|
TCPPort int = 443
|
||||||
|
PingTimes int = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ping struct {
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
m *sync.Mutex
|
||||||
|
ips []net.IPAddr
|
||||||
|
csv utils.PingDelaySet
|
||||||
|
control chan bool
|
||||||
|
bar *utils.Bar
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPing(ips []net.IPAddr) *Ping {
|
||||||
|
return &Ping{
|
||||||
|
wg: &sync.WaitGroup{},
|
||||||
|
m: &sync.Mutex{},
|
||||||
|
ips: ips,
|
||||||
|
csv: make(utils.PingDelaySet, 0),
|
||||||
|
control: make(chan bool, DefaultRoutine),
|
||||||
|
bar: utils.NewBar(len(ips) * PingTimes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Ping) Run() utils.PingDelaySet {
|
||||||
|
for _, ip := range p.ips {
|
||||||
|
p.wg.Add(1)
|
||||||
|
p.control <- false
|
||||||
|
go p.start(ip)
|
||||||
|
}
|
||||||
|
p.wg.Wait()
|
||||||
|
p.bar.Done()
|
||||||
|
return p.csv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Ping) start(ip net.IPAddr) {
|
||||||
|
defer p.wg.Done()
|
||||||
|
p.tcpingHandler(ip)
|
||||||
|
<-p.control
|
||||||
|
}
|
||||||
|
|
||||||
|
//bool connectionSucceed float32 time
|
||||||
|
func (p *Ping) tcping(ip net.IPAddr) (bool, time.Duration) {
|
||||||
|
startTime := time.Now()
|
||||||
|
fullAddress := fmt.Sprintf("%s:%d", ip.String(), TCPPort)
|
||||||
|
//fmt.Println(ip.String())
|
||||||
|
if IPv6 { // IPv6 需要加上 []
|
||||||
|
fullAddress = fmt.Sprintf("[%s]:%d", ip.String(), TCPPort)
|
||||||
|
}
|
||||||
|
conn, err := net.DialTimeout("tcp", fullAddress, tcpConnectTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
return true, duration
|
||||||
|
}
|
||||||
|
|
||||||
|
//pingReceived pingTotalTime
|
||||||
|
func (p *Ping) checkConnection(ip net.IPAddr) (recv int, totalDelay time.Duration) {
|
||||||
|
for i := 0; i < PingTimes; i++ {
|
||||||
|
if ok, delay := p.tcping(ip); ok {
|
||||||
|
recv++
|
||||||
|
totalDelay += delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Ping) appendIPData(data *utils.PingData) {
|
||||||
|
p.m.Lock()
|
||||||
|
defer p.m.Unlock()
|
||||||
|
p.csv = append(p.csv, utils.CloudflareIPData{
|
||||||
|
PingData: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//return Success packetRecv averagePingTime specificIPAddr
|
||||||
|
func (p *Ping) tcpingHandler(ip net.IPAddr) {
|
||||||
|
ipCanConnect := false
|
||||||
|
pingRecv := 0
|
||||||
|
var delay time.Duration
|
||||||
|
for !ipCanConnect {
|
||||||
|
recv, totalDlay := p.checkConnection(ip)
|
||||||
|
if recv > 0 {
|
||||||
|
ipCanConnect = true
|
||||||
|
pingRecv = recv
|
||||||
|
delay = totalDlay
|
||||||
|
} else {
|
||||||
|
ip.IP[15]++
|
||||||
|
if ip.IP[15] == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.bar.Grow(PingTimes)
|
||||||
|
if !ipCanConnect {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// for i := 0; i < PingTimes; i++ {
|
||||||
|
// pingSuccess, pingTimeCurrent := p.tcping(ip)
|
||||||
|
// progressHandler(utils.NormalPing)
|
||||||
|
// if pingSuccess {
|
||||||
|
// pingRecv++
|
||||||
|
// pingTime += pingTimeCurrent
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
data := &utils.PingData{
|
||||||
|
IP: ip,
|
||||||
|
Sended: PingTimes,
|
||||||
|
Received: pingRecv,
|
||||||
|
Delay: delay / time.Duration(pingRecv),
|
||||||
|
}
|
||||||
|
p.appendIPData(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -91,7 +91,7 @@ func tcpingGoroutine(wg *sync.WaitGroup, mutex *sync.Mutex, ip net.IPAddr, tcpPo
|
|||||||
*csv = append(*csv, cfdata)
|
*csv = append(*csv, cfdata)
|
||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
}
|
}
|
||||||
// <-control
|
<-control
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDialContextByAddr(fakeSourceAddr string) func(ctx context.Context, network, address string) (net.Conn, error) {
|
func GetDialContextByAddr(fakeSourceAddr string) func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
|||||||
129
tcping/ping.go
129
tcping/ping.go
@@ -1,129 +0,0 @@
|
|||||||
package tcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"CloudflareSpeedTest/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tcpConnectTimeout = time.Second * 1
|
|
||||||
|
|
||||||
type Ping struct {
|
|
||||||
wg *sync.WaitGroup
|
|
||||||
m *sync.Mutex
|
|
||||||
ips []net.IPAddr
|
|
||||||
isIPv6 bool
|
|
||||||
tcpPort int
|
|
||||||
pingCount int
|
|
||||||
csv []utils.CloudflareIPData
|
|
||||||
control chan bool
|
|
||||||
progressHandler func(e utils.ProgressEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPing(ips []net.IPAddr, port, pingTime int, ipv6 bool) *Ping {
|
|
||||||
return &Ping{
|
|
||||||
wg: &sync.WaitGroup{},
|
|
||||||
m: &sync.Mutex{},
|
|
||||||
ips: ips,
|
|
||||||
isIPv6: ipv6,
|
|
||||||
tcpPort: port,
|
|
||||||
pingCount: pingTime,
|
|
||||||
csv: make([]utils.CloudflareIPData, 0),
|
|
||||||
control: make(chan bool),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Ping) Run() {
|
|
||||||
for _, ip := range p.ips {
|
|
||||||
p.wg.Add(1)
|
|
||||||
p.control <- false
|
|
||||||
go p.start(ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Ping) start(ip net.IPAddr) {
|
|
||||||
defer p.wg.Done()
|
|
||||||
if ok, data := p.tcpingHandler(ip, nil); ok {
|
|
||||||
p.appendIPData(data)
|
|
||||||
}
|
|
||||||
<-p.control
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Ping) appendIPData(data *utils.PingData) {
|
|
||||||
p.m.Lock()
|
|
||||||
defer p.m.Unlock()
|
|
||||||
p.csv = append(p.csv, utils.CloudflareIPData{
|
|
||||||
PingData: data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//bool connectionSucceed float32 time
|
|
||||||
func (p *Ping) tcping(ip net.IPAddr) (bool, time.Duration) {
|
|
||||||
startTime := time.Now()
|
|
||||||
fullAddress := fmt.Sprintf("%s:%d", ip.String(), p.tcpPort)
|
|
||||||
//fmt.Println(ip.String())
|
|
||||||
if p.isIPv6 { // IPv6 需要加上 []
|
|
||||||
fullAddress = fmt.Sprintf("[%s]:%d", ip.String(), p.tcpPort)
|
|
||||||
}
|
|
||||||
conn, err := net.DialTimeout("tcp", fullAddress, tcpConnectTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return false, 0
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
duration := time.Since(startTime)
|
|
||||||
return true, duration
|
|
||||||
}
|
|
||||||
|
|
||||||
//pingReceived pingTotalTime
|
|
||||||
func (p *Ping) checkConnection(ip net.IPAddr) (pingRecv int, pingTime time.Duration) {
|
|
||||||
for i := 0; i < p.pingCount; i++ {
|
|
||||||
if pingSucceed, pingTimeCurrent := p.tcping(ip); pingSucceed {
|
|
||||||
pingRecv++
|
|
||||||
pingTime += pingTimeCurrent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//return Success packetRecv averagePingTime specificIPAddr
|
|
||||||
func (p *Ping) tcpingHandler(ip net.IPAddr, progressHandler func(e utils.ProgressEvent)) (bool, *utils.PingData) {
|
|
||||||
ipCanConnect := false
|
|
||||||
pingRecv := 0
|
|
||||||
var pingTime time.Duration
|
|
||||||
for !ipCanConnect {
|
|
||||||
pingRecvCurrent, pingTimeCurrent := p.checkConnection(ip)
|
|
||||||
if pingRecvCurrent != 0 {
|
|
||||||
ipCanConnect = true
|
|
||||||
pingRecv = pingRecvCurrent
|
|
||||||
pingTime = pingTimeCurrent
|
|
||||||
} else {
|
|
||||||
ip.IP[15]++
|
|
||||||
if ip.IP[15] == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ipCanConnect {
|
|
||||||
progressHandler(utils.NoAvailableIPFound)
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
progressHandler(utils.AvailableIPFound)
|
|
||||||
for i := 0; i < p.pingCount; i++ {
|
|
||||||
pingSuccess, pingTimeCurrent := p.tcping(ip)
|
|
||||||
progressHandler(utils.NormalPing)
|
|
||||||
if pingSuccess {
|
|
||||||
pingRecv++
|
|
||||||
pingTime += pingTimeCurrent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, &utils.PingData{
|
|
||||||
IP: ip,
|
|
||||||
Count: p.pingCount,
|
|
||||||
Received: pingRecv,
|
|
||||||
Delay: pingTime / time.Duration(pingRecv),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
87
utils/csv.go
87
utils/csv.go
@@ -5,13 +5,22 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
MaxDelay = 9999 * time.Millisecond
|
||||||
|
MinDelay = time.Duration(0)
|
||||||
|
|
||||||
|
InputMaxDelay = MaxDelay
|
||||||
|
InputMinDelay = MinDelay
|
||||||
|
)
|
||||||
|
|
||||||
type PingData struct {
|
type PingData struct {
|
||||||
IP net.IPAddr
|
IP net.IPAddr
|
||||||
Count int
|
Sended int
|
||||||
Received int
|
Received int
|
||||||
Delay time.Duration
|
Delay time.Duration
|
||||||
}
|
}
|
||||||
@@ -24,12 +33,23 @@ type CloudflareIPData struct {
|
|||||||
|
|
||||||
func (cf *CloudflareIPData) getRecvRate() float32 {
|
func (cf *CloudflareIPData) getRecvRate() float32 {
|
||||||
if cf.recvRate == 0 {
|
if cf.recvRate == 0 {
|
||||||
pingLost := cf.Count - cf.Received
|
pingLost := cf.Sended - cf.Received
|
||||||
cf.recvRate = float32(pingLost) / float32(cf.Count)
|
cf.recvRate = float32(pingLost) / float32(cf.Sended)
|
||||||
}
|
}
|
||||||
return cf.recvRate
|
return cf.recvRate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cf *CloudflareIPData) toString() []string {
|
||||||
|
result := make([]string, 6)
|
||||||
|
result[0] = cf.IP.String()
|
||||||
|
result[1] = strconv.Itoa(cf.Sended)
|
||||||
|
result[2] = strconv.Itoa(cf.Received)
|
||||||
|
result[3] = strconv.FormatFloat(float64(cf.getRecvRate()), 'f', 2, 32)
|
||||||
|
result[4] = cf.Delay.String()
|
||||||
|
result[5] = strconv.FormatFloat(float64(cf.downloadSpeed)/1024/1024, 'f', 2, 32)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func ExportCsv(filePath string, data []CloudflareIPData) {
|
func ExportCsv(filePath string, data []CloudflareIPData) {
|
||||||
fp, err := os.Create(filePath)
|
fp, err := os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -43,17 +63,6 @@ func ExportCsv(filePath string, data []CloudflareIPData) {
|
|||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cf *CloudflareIPData) toString() []string {
|
|
||||||
result := make([]string, 6)
|
|
||||||
result[0] = cf.IP.String()
|
|
||||||
result[1] = strconv.Itoa(cf.Count)
|
|
||||||
result[2] = strconv.Itoa(cf.Received)
|
|
||||||
result[3] = strconv.FormatFloat(float64(cf.getRecvRate()), 'f', 2, 32)
|
|
||||||
result[4] = cf.Delay.String()
|
|
||||||
result[5] = strconv.FormatFloat(float64(cf.downloadSpeed)/1024/1024, 'f', 2, 32)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToString(data []CloudflareIPData) [][]string {
|
func convertToString(data []CloudflareIPData) [][]string {
|
||||||
result := make([][]string, 0)
|
result := make([][]string, 0)
|
||||||
for _, v := range data {
|
for _, v := range data {
|
||||||
@@ -61,3 +70,53 @@ func convertToString(data []CloudflareIPData) [][]string {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PingDelaySet []CloudflareIPData
|
||||||
|
|
||||||
|
func (s PingDelaySet) FilterDelay() (data PingDelaySet) {
|
||||||
|
sort.Sort(s)
|
||||||
|
if InputMaxDelay >= MaxDelay || InputMinDelay <= MinDelay {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
for _, v := range s {
|
||||||
|
if v.Delay > MaxDelay { // 平均延迟上限
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if v.Delay <= MinDelay { // 平均延迟下限
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data = append(data, v) // 延迟满足条件时,添加到新数组中
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PingDelaySet) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PingDelaySet) Less(i, j int) bool {
|
||||||
|
iRate, jRate := s[i].getRecvRate(), s[j].getRecvRate()
|
||||||
|
if iRate != jRate {
|
||||||
|
return iRate < jRate
|
||||||
|
}
|
||||||
|
return s[i].Delay < s[j].Delay
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PingDelaySet) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载速度排序
|
||||||
|
type DownloadSpeedSet []CloudflareIPData
|
||||||
|
|
||||||
|
func (s DownloadSpeedSet) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DownloadSpeedSet) Less(i, j int) bool {
|
||||||
|
return s[i].downloadSpeed > s[j].downloadSpeed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DownloadSpeedSet) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,13 +11,21 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Bar struct {
|
type Bar struct {
|
||||||
*pb.ProgressBar
|
pb *pb.ProgressBar
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBar(count int) *Bar {
|
func NewBar(count int) *Bar {
|
||||||
return &Bar{pb.Simple.Start(count)}
|
return &Bar{pb.Simple.Start(count)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bar) Grow(num int) {
|
||||||
|
b.pb.Add(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bar) Done() {
|
||||||
|
b.pb.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
func handleProgressGenerator(pb *pb.ProgressBar) func(e ProgressEvent) {
|
func handleProgressGenerator(pb *pb.ProgressBar) func(e ProgressEvent) {
|
||||||
return func(e ProgressEvent) {
|
return func(e ProgressEvent) {
|
||||||
switch e {
|
switch e {
|
||||||
|
|||||||
Reference in New Issue
Block a user