1/*
2NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, version 3 of the License.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with this program.  If not, see <http://www.gnu.org/licenses/>.
16*/
17
18package nncp
19
20import (
21	"fmt"
22	"io"
23	"os"
24	"sync"
25	"time"
26
27	"github.com/dustin/go-humanize"
28	"go.cypherpunks.ru/nncp/v8/uilive"
29)
30
31func init() {
32	uilive.Out = os.Stderr
33}
34
35var progressBars = make(map[string]*ProgressBar)
36var progressBarsLock sync.RWMutex
37
38type ProgressBar struct {
39	w       *uilive.Writer
40	hash    string
41	started time.Time
42	initial int64
43	full    int64
44}
45
46func ProgressBarNew(initial, full int64) *ProgressBar {
47	pb := ProgressBar{
48		w:       uilive.New(),
49		started: time.Now(),
50		initial: initial,
51		full:    full,
52	}
53	pb.w.Start()
54	return &pb
55}
56
57func (pb ProgressBar) Render(what string, size int64) {
58	now := time.Now().UTC()
59	timeDiff := now.Sub(pb.started).Seconds()
60	if timeDiff == 0 {
61		timeDiff = 1
62	}
63	percentage := int64(100)
64	if pb.full > 0 {
65		percentage = 100 * size / pb.full
66	}
67	fmt.Fprintf(
68		pb.w, "%s %s %s/%s %d%% (%s/sec)\n",
69		now.Format(time.RFC3339), what,
70		humanize.IBytes(uint64(size)),
71		humanize.IBytes(uint64(pb.full)),
72		percentage,
73		humanize.IBytes(uint64(float64(size-pb.initial)/timeDiff)),
74	)
75}
76
77func (pb ProgressBar) Kill() {
78	pb.w.Stop()
79}
80
81func CopyProgressed(
82	dst io.Writer,
83	src io.Reader,
84	prgrsPrefix string,
85	les LEs,
86	showPrgrs bool,
87) (written int64, err error) {
88	buf := make([]byte, EncBlkSize)
89	var nr, nw int
90	var er, ew error
91	for {
92		nr, er = src.Read(buf)
93		if nr > 0 {
94			nw, ew = dst.Write(buf[:nr])
95			if nw > 0 {
96				written += int64(nw)
97				if showPrgrs {
98					Progress(prgrsPrefix, append(les, LE{"Size", written}))
99				}
100			}
101			if ew != nil {
102				err = ew
103				break
104			}
105			if nr != nw {
106				err = io.ErrShortWrite
107				break
108			}
109		}
110		if er != nil {
111			if er != io.EOF {
112				err = er
113			}
114			break
115		}
116	}
117	if showPrgrs {
118		for _, le := range les {
119			if le.K == "FullSize" {
120				if le.V.(int64) == 0 {
121					Progress(prgrsPrefix, append(
122						les, LE{"Size", written}, LE{"FullSize", written},
123					))
124				}
125				break
126			}
127		}
128	}
129	return
130}
131
132func Progress(prefix string, les LEs) {
133	var size int64
134	var fullsize int64
135	var pkt string
136	for _, le := range les {
137		switch le.K {
138		case "Size":
139			size = le.V.(int64)
140		case "FullSize":
141			fullsize = le.V.(int64)
142		case "Pkt":
143			pkt = le.V.(string)
144		}
145	}
146	progressBarsLock.RLock()
147	pb := progressBars[pkt]
148	progressBarsLock.RUnlock()
149	if pb == nil {
150		progressBarsLock.Lock()
151		pb = ProgressBarNew(size, fullsize)
152		progressBars[pkt] = pb
153		progressBarsLock.Unlock()
154	}
155	what := pkt
156	if len(what) >= Base32Encoded32Len { // Base32 encoded
157		what = what[:16] + ".." + what[len(what)-16:]
158	}
159	what = prefix + " " + what
160	pb.Render(what, size)
161	if fullsize != 0 && size >= fullsize {
162		pb.Kill()
163		progressBarsLock.Lock()
164		delete(progressBars, pkt)
165		progressBarsLock.Unlock()
166	}
167}
168
169func ProgressKill(pkt string) {
170	progressBarsLock.Lock()
171	pb := progressBars[pkt]
172	if pb != nil {
173		pb.Kill()
174		delete(progressBars, pkt)
175	}
176	progressBarsLock.Unlock()
177}
178