commit
c1bbf5ddee
166
cmd/cmd.go
166
cmd/cmd.go
|
@ -30,7 +30,7 @@ import (
|
||||||
"github.com/jmorganca/ollama/api"
|
"github.com/jmorganca/ollama/api"
|
||||||
"github.com/jmorganca/ollama/format"
|
"github.com/jmorganca/ollama/format"
|
||||||
"github.com/jmorganca/ollama/parser"
|
"github.com/jmorganca/ollama/parser"
|
||||||
"github.com/jmorganca/ollama/progressbar"
|
"github.com/jmorganca/ollama/progress"
|
||||||
"github.com/jmorganca/ollama/readline"
|
"github.com/jmorganca/ollama/readline"
|
||||||
"github.com/jmorganca/ollama/server"
|
"github.com/jmorganca/ollama/server"
|
||||||
"github.com/jmorganca/ollama/version"
|
"github.com/jmorganca/ollama/version"
|
||||||
|
@ -48,14 +48,20 @@ func CreateHandler(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p := progress.NewProgress(os.Stderr)
|
||||||
|
defer p.Stop()
|
||||||
|
|
||||||
|
bars := make(map[string]*progress.Bar)
|
||||||
|
|
||||||
|
status := fmt.Sprintf("creating %s", args[0])
|
||||||
|
spinner := progress.NewSpinner(status)
|
||||||
|
p.Add(status, spinner)
|
||||||
|
|
||||||
modelfile, err := os.ReadFile(filename)
|
modelfile, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner := NewSpinner("transferring context")
|
|
||||||
go spinner.Spin(100 * time.Millisecond)
|
|
||||||
|
|
||||||
commands, err := parser.Parse(bytes.NewReader(modelfile))
|
commands, err := parser.Parse(bytes.NewReader(modelfile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -66,6 +72,12 @@ func CreateHandler(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spinner.Stop()
|
||||||
|
|
||||||
|
status = "transferring context"
|
||||||
|
spinner = progress.NewSpinner(status)
|
||||||
|
p.Add(status, spinner)
|
||||||
|
|
||||||
for _, c := range commands {
|
for _, c := range commands {
|
||||||
switch c.Name {
|
switch c.Name {
|
||||||
case "model", "adapter":
|
case "model", "adapter":
|
||||||
|
@ -99,41 +111,34 @@ func CreateHandler(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentDigest string
|
|
||||||
var bar *progressbar.ProgressBar
|
|
||||||
|
|
||||||
request := api.CreateRequest{Name: args[0], Path: filename, Modelfile: string(modelfile)}
|
|
||||||
fn := func(resp api.ProgressResponse) error {
|
fn := func(resp api.ProgressResponse) error {
|
||||||
if resp.Digest != currentDigest && resp.Digest != "" {
|
if resp.Digest != "" {
|
||||||
spinner.Stop()
|
spinner.Stop()
|
||||||
currentDigest = resp.Digest
|
|
||||||
// pulling
|
bar, ok := bars[resp.Digest]
|
||||||
bar = progressbar.DefaultBytes(
|
if !ok {
|
||||||
resp.Total,
|
bar = progress.NewBar(resp.Status, resp.Total, resp.Completed)
|
||||||
resp.Status,
|
bars[resp.Digest] = bar
|
||||||
)
|
p.Add(resp.Digest, bar)
|
||||||
bar.Set64(resp.Completed)
|
}
|
||||||
} else if resp.Digest == currentDigest && resp.Digest != "" {
|
|
||||||
bar.Set64(resp.Completed)
|
bar.Set(resp.Completed)
|
||||||
} else {
|
} else if status != resp.Status {
|
||||||
currentDigest = ""
|
|
||||||
spinner.Stop()
|
spinner.Stop()
|
||||||
spinner = NewSpinner(resp.Status)
|
|
||||||
go spinner.Spin(100 * time.Millisecond)
|
status = resp.Status
|
||||||
|
spinner = progress.NewSpinner(status)
|
||||||
|
p.Add(status, spinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request := api.CreateRequest{Name: args[0], Path: filename, Modelfile: string(modelfile)}
|
||||||
if err := client.Create(context.Background(), &request, fn); err != nil {
|
if err := client.Create(context.Background(), &request, fn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner.Stop()
|
|
||||||
if spinner.description != "success" {
|
|
||||||
return errors.New("unexpected end to create model")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,36 +175,44 @@ func PushHandler(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentDigest string
|
p := progress.NewProgress(os.Stderr)
|
||||||
var bar *progressbar.ProgressBar
|
defer p.Stop()
|
||||||
|
|
||||||
|
bars := make(map[string]*progress.Bar)
|
||||||
|
|
||||||
|
status := fmt.Sprintf("pushing %s", args[0])
|
||||||
|
spinner := progress.NewSpinner(status)
|
||||||
|
p.Add(status, spinner)
|
||||||
|
|
||||||
request := api.PushRequest{Name: args[0], Insecure: insecure}
|
|
||||||
fn := func(resp api.ProgressResponse) error {
|
fn := func(resp api.ProgressResponse) error {
|
||||||
if resp.Digest != currentDigest && resp.Digest != "" {
|
if resp.Digest != "" {
|
||||||
currentDigest = resp.Digest
|
spinner.Stop()
|
||||||
bar = progressbar.DefaultBytes(
|
|
||||||
resp.Total,
|
|
||||||
fmt.Sprintf("pushing %s...", resp.Digest[7:19]),
|
|
||||||
)
|
|
||||||
|
|
||||||
bar.Set64(resp.Completed)
|
bar, ok := bars[resp.Digest]
|
||||||
} else if resp.Digest == currentDigest && resp.Digest != "" {
|
if !ok {
|
||||||
bar.Set64(resp.Completed)
|
bar = progress.NewBar(resp.Status, resp.Total, resp.Completed)
|
||||||
} else {
|
bars[resp.Digest] = bar
|
||||||
currentDigest = ""
|
p.Add(resp.Digest, bar)
|
||||||
fmt.Println(resp.Status)
|
}
|
||||||
|
|
||||||
|
bar.Set(resp.Completed)
|
||||||
|
} else if status != resp.Status {
|
||||||
|
spinner.Stop()
|
||||||
|
|
||||||
|
status = resp.Status
|
||||||
|
spinner = progress.NewSpinner(status)
|
||||||
|
p.Add(status, spinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request := api.PushRequest{Name: args[0], Insecure: insecure}
|
||||||
if err := client.Push(context.Background(), &request, fn); err != nil {
|
if err := client.Push(context.Background(), &request, fn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bar != nil && !bar.IsFinished() {
|
spinner.Stop()
|
||||||
return errors.New("unexpected end to push model")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,46 +363,48 @@ func PullHandler(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return pull(args[0], insecure)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pull(model string, insecure bool) error {
|
|
||||||
client, err := api.ClientFromEnvironment()
|
client, err := api.ClientFromEnvironment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentDigest string
|
p := progress.NewProgress(os.Stderr)
|
||||||
var bar *progressbar.ProgressBar
|
defer p.Stop()
|
||||||
|
|
||||||
|
bars := make(map[string]*progress.Bar)
|
||||||
|
|
||||||
|
status := fmt.Sprintf("pulling %s", args[0])
|
||||||
|
spinner := progress.NewSpinner(status)
|
||||||
|
p.Add(status, spinner)
|
||||||
|
|
||||||
request := api.PullRequest{Name: model, Insecure: insecure}
|
|
||||||
fn := func(resp api.ProgressResponse) error {
|
fn := func(resp api.ProgressResponse) error {
|
||||||
if resp.Digest != currentDigest && resp.Digest != "" {
|
if resp.Digest != "" {
|
||||||
currentDigest = resp.Digest
|
spinner.Stop()
|
||||||
bar = progressbar.DefaultBytes(
|
|
||||||
resp.Total,
|
|
||||||
fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
|
|
||||||
)
|
|
||||||
|
|
||||||
bar.Set64(resp.Completed)
|
bar, ok := bars[resp.Digest]
|
||||||
} else if resp.Digest == currentDigest && resp.Digest != "" {
|
if !ok {
|
||||||
bar.Set64(resp.Completed)
|
bar = progress.NewBar(resp.Status, resp.Total, resp.Completed)
|
||||||
} else {
|
bars[resp.Digest] = bar
|
||||||
currentDigest = ""
|
p.Add(resp.Digest, bar)
|
||||||
fmt.Println(resp.Status)
|
}
|
||||||
|
|
||||||
|
bar.Set(resp.Completed)
|
||||||
|
} else if status != resp.Status {
|
||||||
|
spinner.Stop()
|
||||||
|
|
||||||
|
status = resp.Status
|
||||||
|
spinner = progress.NewSpinner(status)
|
||||||
|
p.Add(status, spinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request := api.PullRequest{Name: args[0], Insecure: insecure}
|
||||||
if err := client.Pull(context.Background(), &request, fn); err != nil {
|
if err := client.Pull(context.Background(), &request, fn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bar != nil && !bar.IsFinished() {
|
|
||||||
return errors.New("unexpected end to pull model")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,8 +457,11 @@ func generate(cmd *cobra.Command, model, prompt string, wordWrap bool, format st
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
spinner := NewSpinner("")
|
p := progress.NewProgress(os.Stderr)
|
||||||
go spinner.Spin(60 * time.Millisecond)
|
defer p.Stop()
|
||||||
|
|
||||||
|
spinner := progress.NewSpinner("")
|
||||||
|
p.Add("", spinner)
|
||||||
|
|
||||||
var latest api.GenerateResponse
|
var latest api.GenerateResponse
|
||||||
|
|
||||||
|
@ -475,9 +493,8 @@ func generate(cmd *cobra.Command, model, prompt string, wordWrap bool, format st
|
||||||
|
|
||||||
request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext, Format: format}
|
request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext, Format: format}
|
||||||
fn := func(response api.GenerateResponse) error {
|
fn := func(response api.GenerateResponse) error {
|
||||||
if !spinner.IsFinished() {
|
spinner.Stop()
|
||||||
spinner.Finish()
|
p.StopAndClear()
|
||||||
}
|
|
||||||
|
|
||||||
latest = response
|
latest = response
|
||||||
|
|
||||||
|
@ -511,7 +528,6 @@ func generate(cmd *cobra.Command, model, prompt string, wordWrap bool, format st
|
||||||
|
|
||||||
if err := client.Generate(cancelCtx, &request, fn); err != nil {
|
if err := client.Generate(cancelCtx, &request, fn); err != nil {
|
||||||
if strings.Contains(err.Error(), "context canceled") && abort {
|
if strings.Contains(err.Error(), "context canceled") && abort {
|
||||||
spinner.Finish()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jmorganca/ollama/progressbar"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Spinner struct {
|
|
||||||
description string
|
|
||||||
*progressbar.ProgressBar
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSpinner(description string) *Spinner {
|
|
||||||
return &Spinner{
|
|
||||||
description: description,
|
|
||||||
ProgressBar: progressbar.NewOptions(-1,
|
|
||||||
progressbar.OptionSetWriter(os.Stderr),
|
|
||||||
progressbar.OptionThrottle(60*time.Millisecond),
|
|
||||||
progressbar.OptionSpinnerType(14),
|
|
||||||
progressbar.OptionSetRenderBlankState(true),
|
|
||||||
progressbar.OptionSetElapsedTime(false),
|
|
||||||
progressbar.OptionClearOnFinish(),
|
|
||||||
progressbar.OptionSetDescription(description),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Spinner) Spin(tick time.Duration) {
|
|
||||||
for range time.Tick(tick) {
|
|
||||||
if s.IsFinished() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Add(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Spinner) Stop() {
|
|
||||||
s.Finish()
|
|
||||||
fmt.Println(s.description)
|
|
||||||
}
|
|
|
@ -7,10 +7,13 @@ const (
|
||||||
KiloByte = Byte * 1000
|
KiloByte = Byte * 1000
|
||||||
MegaByte = KiloByte * 1000
|
MegaByte = KiloByte * 1000
|
||||||
GigaByte = MegaByte * 1000
|
GigaByte = MegaByte * 1000
|
||||||
|
TeraByte = GigaByte * 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
func HumanBytes(b int64) string {
|
func HumanBytes(b int64) string {
|
||||||
switch {
|
switch {
|
||||||
|
case b > TeraByte:
|
||||||
|
return fmt.Sprintf("%.1f TB", float64(b)/TeraByte)
|
||||||
case b > GigaByte:
|
case b > GigaByte:
|
||||||
return fmt.Sprintf("%.1f GB", float64(b)/GigaByte)
|
return fmt.Sprintf("%.1f GB", float64(b)/GigaByte)
|
||||||
case b > MegaByte:
|
case b > MegaByte:
|
||||||
|
|
123
progress/bar.go
Normal file
123
progress/bar.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package progress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmorganca/ollama/format"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
message string
|
||||||
|
messageWidth int
|
||||||
|
|
||||||
|
maxValue int64
|
||||||
|
initialValue int64
|
||||||
|
currentValue int64
|
||||||
|
|
||||||
|
started time.Time
|
||||||
|
stopped time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBar(message string, maxValue, initialValue int64) *Bar {
|
||||||
|
return &Bar{
|
||||||
|
message: message,
|
||||||
|
messageWidth: -1,
|
||||||
|
maxValue: maxValue,
|
||||||
|
initialValue: initialValue,
|
||||||
|
currentValue: initialValue,
|
||||||
|
started: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bar) String() string {
|
||||||
|
termWidth, _, err := term.GetSize(int(os.Stderr.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pre, mid, suf strings.Builder
|
||||||
|
|
||||||
|
if b.message != "" {
|
||||||
|
message := strings.TrimSpace(b.message)
|
||||||
|
if b.messageWidth > 0 && len(message) > b.messageWidth {
|
||||||
|
message = message[:b.messageWidth]
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(&pre, "%s", message)
|
||||||
|
if b.messageWidth-pre.Len() >= 0 {
|
||||||
|
pre.WriteString(strings.Repeat(" ", b.messageWidth-pre.Len()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(&pre, "%.1f%% ", b.percent())
|
||||||
|
|
||||||
|
fmt.Fprintf(&suf, "(%s/%s, %s/s, %s)",
|
||||||
|
format.HumanBytes(b.currentValue),
|
||||||
|
format.HumanBytes(b.maxValue),
|
||||||
|
format.HumanBytes(int64(b.rate())),
|
||||||
|
b.elapsed())
|
||||||
|
|
||||||
|
mid.WriteString("[")
|
||||||
|
|
||||||
|
// pad 3 for last = or > and "] "
|
||||||
|
f := termWidth - pre.Len() - mid.Len() - suf.Len() - 3
|
||||||
|
n := int(float64(f) * b.percent() / 100)
|
||||||
|
if n > 0 {
|
||||||
|
mid.WriteString(strings.Repeat("=", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.currentValue >= b.maxValue {
|
||||||
|
mid.WriteString("=")
|
||||||
|
} else {
|
||||||
|
mid.WriteString(">")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f-n > 0 {
|
||||||
|
mid.WriteString(strings.Repeat(" ", f-n))
|
||||||
|
}
|
||||||
|
|
||||||
|
mid.WriteString("] ")
|
||||||
|
|
||||||
|
return pre.String() + mid.String() + suf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bar) Set(value int64) {
|
||||||
|
if value >= b.maxValue {
|
||||||
|
value = b.maxValue
|
||||||
|
b.stopped = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
b.currentValue = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bar) percent() float64 {
|
||||||
|
if b.maxValue > 0 {
|
||||||
|
return float64(b.currentValue) / float64(b.maxValue) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bar) rate() float64 {
|
||||||
|
elapsed := b.elapsed()
|
||||||
|
if elapsed.Seconds() > 0 {
|
||||||
|
return (float64(b.currentValue) - float64(b.initialValue)) / elapsed.Seconds()
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bar) elapsed() time.Duration {
|
||||||
|
stopped := b.stopped
|
||||||
|
if stopped.IsZero() {
|
||||||
|
stopped = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return stopped.Sub(b.started).Round(time.Second)
|
||||||
|
}
|
98
progress/progress.go
Normal file
98
progress/progress.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package progress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State interface {
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Progress struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
pos int
|
||||||
|
w io.Writer
|
||||||
|
|
||||||
|
ticker *time.Ticker
|
||||||
|
states []State
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProgress(w io.Writer) *Progress {
|
||||||
|
p := &Progress{w: w}
|
||||||
|
go p.start()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) Stop() bool {
|
||||||
|
for _, state := range p.states {
|
||||||
|
if spinner, ok := state.(*Spinner); ok {
|
||||||
|
spinner.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ticker != nil {
|
||||||
|
p.ticker.Stop()
|
||||||
|
p.ticker = nil
|
||||||
|
p.render()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) StopAndClear() bool {
|
||||||
|
fmt.Fprint(p.w, "\033[?25l")
|
||||||
|
defer fmt.Fprint(p.w, "\033[?25h")
|
||||||
|
|
||||||
|
stopped := p.Stop()
|
||||||
|
if stopped {
|
||||||
|
// clear the progress bar by:
|
||||||
|
// 1. for each line in the progress:
|
||||||
|
// a. move the cursor up one line
|
||||||
|
// b. clear the line
|
||||||
|
for i := 0; i < p.pos; i++ {
|
||||||
|
fmt.Fprint(p.w, "\033[A\033[2K")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) Add(key string, state State) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
p.states = append(p.states, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) render() error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
fmt.Fprint(p.w, "\033[?25l")
|
||||||
|
defer fmt.Fprint(p.w, "\033[?25h")
|
||||||
|
|
||||||
|
if p.pos > 0 {
|
||||||
|
fmt.Fprintf(p.w, "\033[%dA", p.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range p.states {
|
||||||
|
fmt.Fprintln(p.w, state.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.states) > 0 {
|
||||||
|
p.pos = len(p.states)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) start() {
|
||||||
|
p.ticker = time.NewTicker(100 * time.Millisecond)
|
||||||
|
for range p.ticker.C {
|
||||||
|
p.render()
|
||||||
|
}
|
||||||
|
}
|
102
progress/spinner.go
Normal file
102
progress/spinner.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package progress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Spinner struct {
|
||||||
|
message string
|
||||||
|
messageWidth int
|
||||||
|
|
||||||
|
parts []string
|
||||||
|
|
||||||
|
value int
|
||||||
|
|
||||||
|
ticker *time.Ticker
|
||||||
|
started time.Time
|
||||||
|
stopped time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSpinner(message string) *Spinner {
|
||||||
|
s := &Spinner{
|
||||||
|
message: message,
|
||||||
|
parts: []string{
|
||||||
|
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",
|
||||||
|
},
|
||||||
|
started: time.Now(),
|
||||||
|
}
|
||||||
|
go s.start()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) String() string {
|
||||||
|
termWidth, _, err := term.GetSize(int(os.Stderr.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pre strings.Builder
|
||||||
|
if len(s.message) > 0 {
|
||||||
|
message := strings.TrimSpace(s.message)
|
||||||
|
if s.messageWidth > 0 && len(message) > s.messageWidth {
|
||||||
|
message = message[:s.messageWidth]
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(&pre, "%s", message)
|
||||||
|
if s.messageWidth-pre.Len() >= 0 {
|
||||||
|
pre.WriteString(strings.Repeat(" ", s.messageWidth-pre.Len()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
var pad int
|
||||||
|
if s.stopped.IsZero() {
|
||||||
|
// spinner has a string length of 3 but a rune length of 1
|
||||||
|
// in order to align correctly, we need to pad with (3 - 1) = 2 spaces
|
||||||
|
spinner := s.parts[s.value]
|
||||||
|
pre.WriteString(spinner)
|
||||||
|
pad = len(spinner) - len([]rune(spinner))
|
||||||
|
}
|
||||||
|
|
||||||
|
var suf strings.Builder
|
||||||
|
fmt.Fprintf(&suf, "(%s)", s.elapsed())
|
||||||
|
|
||||||
|
var mid strings.Builder
|
||||||
|
f := termWidth - pre.Len() - mid.Len() - suf.Len() + pad
|
||||||
|
if f > 0 {
|
||||||
|
mid.WriteString(strings.Repeat(" ", f))
|
||||||
|
}
|
||||||
|
|
||||||
|
return pre.String() + mid.String() + suf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) start() {
|
||||||
|
s.ticker = time.NewTicker(100 * time.Millisecond)
|
||||||
|
for range s.ticker.C {
|
||||||
|
s.value = (s.value + 1) % len(s.parts)
|
||||||
|
if !s.stopped.IsZero() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) Stop() {
|
||||||
|
if s.stopped.IsZero() {
|
||||||
|
s.stopped = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) elapsed() time.Duration {
|
||||||
|
stopped := s.stopped
|
||||||
|
if stopped.IsZero() {
|
||||||
|
stopped = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return stopped.Sub(s.started).Round(time.Second)
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 Zack
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,121 +0,0 @@
|
||||||
# progressbar
|
|
||||||
|
|
||||||
[![CI](https://github.com/schollz/progressbar/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/schollz/progressbar/actions/workflows/ci.yml)
|
|
||||||
[![go report card](https://goreportcard.com/badge/github.com/schollz/progressbar)](https://goreportcard.com/report/github.com/schollz/progressbar)
|
|
||||||
[![coverage](https://img.shields.io/badge/coverage-84%25-brightgreen.svg)](https://gocover.io/github.com/schollz/progressbar)
|
|
||||||
[![godocs](https://godoc.org/github.com/schollz/progressbar?status.svg)](https://godoc.org/github.com/schollz/progressbar/v3)
|
|
||||||
|
|
||||||
A very simple thread-safe progress bar which should work on every OS without problems. I needed a progressbar for [croc](https://github.com/schollz/croc) and everything I tried had problems, so I made another one. In order to be OS agnostic I do not plan to support [multi-line outputs](https://github.com/schollz/progressbar/issues/6).
|
|
||||||
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```
|
|
||||||
go get -u github.com/schollz/progressbar/v3
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Basic usage
|
|
||||||
|
|
||||||
```golang
|
|
||||||
bar := progressbar.Default(100)
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
bar.Add(1)
|
|
||||||
time.Sleep(40 * time.Millisecond)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
which looks like:
|
|
||||||
|
|
||||||
![Example of basic bar](examples/basic/basic.gif)
|
|
||||||
|
|
||||||
|
|
||||||
### I/O operations
|
|
||||||
|
|
||||||
The `progressbar` implements an `io.Writer` so it can automatically detect the number of bytes written to a stream, so you can use it as a progressbar for an `io.Reader`.
|
|
||||||
|
|
||||||
```golang
|
|
||||||
req, _ := http.NewRequest("GET", "https://dl.google.com/go/go1.14.2.src.tar.gz", nil)
|
|
||||||
resp, _ := http.DefaultClient.Do(req)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
f, _ := os.OpenFile("go1.14.2.src.tar.gz", os.O_CREATE|os.O_WRONLY, 0644)
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
bar := progressbar.DefaultBytes(
|
|
||||||
resp.ContentLength,
|
|
||||||
"downloading",
|
|
||||||
)
|
|
||||||
io.Copy(io.MultiWriter(f, bar), resp.Body)
|
|
||||||
```
|
|
||||||
|
|
||||||
which looks like:
|
|
||||||
|
|
||||||
![Example of download bar](examples/download/download.gif)
|
|
||||||
|
|
||||||
|
|
||||||
### Progress bar with unknown length
|
|
||||||
|
|
||||||
A progressbar with unknown length is a spinner. Any bar with -1 length will automatically convert it to a spinner with a customizable spinner type. For example, the above code can be run and set the `resp.ContentLength` to `-1`.
|
|
||||||
|
|
||||||
which looks like:
|
|
||||||
|
|
||||||
![Example of download bar with unknown length](examples/download-unknown/download-unknown.gif)
|
|
||||||
|
|
||||||
|
|
||||||
### Customization
|
|
||||||
|
|
||||||
There is a lot of customization that you can do - change the writer, the color, the width, description, theme, etc. See [all the options](https://pkg.go.dev/github.com/schollz/progressbar/v3?tab=doc#Option).
|
|
||||||
|
|
||||||
```golang
|
|
||||||
bar := progressbar.NewOptions(1000,
|
|
||||||
progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
|
|
||||||
progressbar.OptionEnableColorCodes(true),
|
|
||||||
progressbar.OptionShowBytes(true),
|
|
||||||
progressbar.OptionSetWidth(15),
|
|
||||||
progressbar.OptionSetDescription("[cyan][1/3][reset] Writing moshable file..."),
|
|
||||||
progressbar.OptionSetTheme(progressbar.Theme{
|
|
||||||
Saucer: "[green]=[reset]",
|
|
||||||
SaucerHead: "[green]>[reset]",
|
|
||||||
SaucerPadding: " ",
|
|
||||||
BarStart: "[",
|
|
||||||
BarEnd: "]",
|
|
||||||
}))
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
bar.Add(1)
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
which looks like:
|
|
||||||
|
|
||||||
![Example of customized bar](examples/customization/customization.gif)
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Pull requests are welcome. Feel free to...
|
|
||||||
|
|
||||||
- Revise documentation
|
|
||||||
- Add new features
|
|
||||||
- Fix bugs
|
|
||||||
- Suggest improvements
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
|
|
||||||
Thanks [@Dynom](https://github.com/dynom) for massive improvements in version 2.0!
|
|
||||||
|
|
||||||
Thanks [@CrushedPixel](https://github.com/CrushedPixel) for adding descriptions and color code support!
|
|
||||||
|
|
||||||
Thanks [@MrMe42](https://github.com/MrMe42) for adding some minor features!
|
|
||||||
|
|
||||||
Thanks [@tehstun](https://github.com/tehstun) for some great PRs!
|
|
||||||
|
|
||||||
Thanks [@Benzammour](https://github.com/Benzammour) and [@haseth](https://github.com/haseth) for helping create v3!
|
|
||||||
|
|
||||||
Thanks [@briandowns](https://github.com/briandowns) for compiling the list of spinners.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,80 +0,0 @@
|
||||||
package progressbar
|
|
||||||
|
|
||||||
var spinners = map[int][]string{
|
|
||||||
0: {"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"},
|
|
||||||
1: {"▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"},
|
|
||||||
2: {"▖", "▘", "▝", "▗"},
|
|
||||||
3: {"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"},
|
|
||||||
4: {"◢", "◣", "◤", "◥"},
|
|
||||||
5: {"◰", "◳", "◲", "◱"},
|
|
||||||
6: {"◴", "◷", "◶", "◵"},
|
|
||||||
7: {"◐", "◓", "◑", "◒"},
|
|
||||||
8: {".", "o", "O", "@", "*"},
|
|
||||||
9: {"|", "/", "-", "\\"},
|
|
||||||
10: {"◡◡", "⊙⊙", "◠◠"},
|
|
||||||
11: {"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"},
|
|
||||||
12: {">))'>", " >))'>", " >))'>", " >))'>", " >))'>", " <'((<", " <'((<", " <'((<"},
|
|
||||||
13: {"⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"},
|
|
||||||
14: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
|
||||||
15: {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"},
|
|
||||||
16: {"▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉"},
|
|
||||||
17: {"■", "□", "▪", "▫"},
|
|
||||||
18: {"←", "↑", "→", "↓"},
|
|
||||||
19: {"╫", "╪"},
|
|
||||||
20: {"⇐", "⇖", "⇑", "⇗", "⇒", "⇘", "⇓", "⇙"},
|
|
||||||
21: {"⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈"},
|
|
||||||
22: {"⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈"},
|
|
||||||
23: {"⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"},
|
|
||||||
24: {"⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋"},
|
|
||||||
25: {"ヲ", "ァ", "ィ", "ゥ", "ェ", "ォ", "ャ", "ュ", "ョ", "ッ", "ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ", "タ", "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "ヒ", "フ", "ヘ", "ホ", "マ", "ミ", "ム", "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ", "ン"},
|
|
||||||
26: {".", "..", "..."},
|
|
||||||
27: {"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁"},
|
|
||||||
28: {".", "o", "O", "°", "O", "o", "."},
|
|
||||||
29: {"+", "x"},
|
|
||||||
30: {"v", "<", "^", ">"},
|
|
||||||
31: {">>--->", " >>--->", " >>--->", " >>--->", " >>--->", " <---<<", " <---<<", " <---<<", " <---<<", "<---<<"},
|
|
||||||
32: {"|", "||", "|||", "||||", "|||||", "|||||||", "||||||||", "|||||||", "||||||", "|||||", "||||", "|||", "||", "|"},
|
|
||||||
33: {"[ ]", "[= ]", "[== ]", "[=== ]", "[==== ]", "[===== ]", "[====== ]", "[======= ]", "[======== ]", "[========= ]", "[==========]"},
|
|
||||||
34: {"(*---------)", "(-*--------)", "(--*-------)", "(---*------)", "(----*-----)", "(-----*----)", "(------*---)", "(-------*--)", "(--------*-)", "(---------*)"},
|
|
||||||
35: {"█▒▒▒▒▒▒▒▒▒", "███▒▒▒▒▒▒▒", "█████▒▒▒▒▒", "███████▒▒▒", "██████████"},
|
|
||||||
36: {"[ ]", "[=> ]", "[===> ]", "[=====> ]", "[======> ]", "[========> ]", "[==========> ]", "[============> ]", "[==============> ]", "[================> ]", "[==================> ]", "[===================>]"},
|
|
||||||
37: {"ဝ", "၀"},
|
|
||||||
38: {"▌", "▀", "▐▄"},
|
|
||||||
39: {"🌍", "🌎", "🌏"},
|
|
||||||
40: {"◜", "◝", "◞", "◟"},
|
|
||||||
41: {"⬒", "⬔", "⬓", "⬕"},
|
|
||||||
42: {"⬖", "⬘", "⬗", "⬙"},
|
|
||||||
43: {"[>>> >]", "[]>>>> []", "[] >>>> []", "[] >>>> []", "[] >>>> []", "[] >>>>[]", "[>> >>]"},
|
|
||||||
44: {"♠", "♣", "♥", "♦"},
|
|
||||||
45: {"➞", "➟", "➠", "➡", "➠", "➟"},
|
|
||||||
46: {" | ", ` \ `, "_ ", ` \ `, " | ", " / ", " _", " / "},
|
|
||||||
47: {" . . . .", ". . . .", ". . . .", ". . . .", ". . . . ", ". . . . ."},
|
|
||||||
48: {" | ", " / ", " _ ", ` \ `, " | ", ` \ `, " _ ", " / "},
|
|
||||||
49: {"⎺", "⎻", "⎼", "⎽", "⎼", "⎻"},
|
|
||||||
50: {"▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"},
|
|
||||||
51: {"[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"},
|
|
||||||
52: {"( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )", "( ● )"},
|
|
||||||
53: {"✶", "✸", "✹", "✺", "✹", "✷"},
|
|
||||||
54: {"▐|\\____________▌", "▐_|\\___________▌", "▐__|\\__________▌", "▐___|\\_________▌", "▐____|\\________▌", "▐_____|\\_______▌", "▐______|\\______▌", "▐_______|\\_____▌", "▐________|\\____▌", "▐_________|\\___▌", "▐__________|\\__▌", "▐___________|\\_▌", "▐____________|\\▌", "▐____________/|▌", "▐___________/|_▌", "▐__________/|__▌", "▐_________/|___▌", "▐________/|____▌", "▐_______/|_____▌", "▐______/|______▌", "▐_____/|_______▌", "▐____/|________▌", "▐___/|_________▌", "▐__/|__________▌", "▐_/|___________▌", "▐/|____________▌"},
|
|
||||||
55: {"▐⠂ ▌", "▐⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂▌", "▐ ⠠▌", "▐ ⡀▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐⠠ ▌"},
|
|
||||||
56: {"¿", "?"},
|
|
||||||
57: {"⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"},
|
|
||||||
58: {"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
|
|
||||||
59: {". ", ".. ", "...", " ..", " .", " "},
|
|
||||||
60: {".", "o", "O", "°", "O", "o", "."},
|
|
||||||
61: {"▓", "▒", "░"},
|
|
||||||
62: {"▌", "▀", "▐", "▄"},
|
|
||||||
63: {"⊶", "⊷"},
|
|
||||||
64: {"▪", "▫"},
|
|
||||||
65: {"□", "■"},
|
|
||||||
66: {"▮", "▯"},
|
|
||||||
67: {"-", "=", "≡"},
|
|
||||||
68: {"d", "q", "p", "b"},
|
|
||||||
69: {"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"},
|
|
||||||
70: {"🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "},
|
|
||||||
71: {"☗", "☖"},
|
|
||||||
72: {"⧇", "⧆"},
|
|
||||||
73: {"◉", "◎"},
|
|
||||||
74: {"㊂", "㊀", "㊁"},
|
|
||||||
75: {"⦾", "⦿"},
|
|
||||||
}
|
|
|
@ -285,7 +285,7 @@ func (b *blobDownload) Wait(ctx context.Context, fn func(api.ProgressResponse))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn(api.ProgressResponse{
|
fn(api.ProgressResponse{
|
||||||
Status: fmt.Sprintf("downloading %s", b.Digest),
|
Status: fmt.Sprintf("downloading %s", b.Digest[7:19]),
|
||||||
Digest: b.Digest,
|
Digest: b.Digest,
|
||||||
Total: b.Total,
|
Total: b.Total,
|
||||||
Completed: b.Completed.Load(),
|
Completed: b.Completed.Load(),
|
||||||
|
@ -322,7 +322,7 @@ func downloadBlob(ctx context.Context, opts downloadOpts) error {
|
||||||
return err
|
return err
|
||||||
default:
|
default:
|
||||||
opts.fn(api.ProgressResponse{
|
opts.fn(api.ProgressResponse{
|
||||||
Status: fmt.Sprintf("downloading %s", opts.digest),
|
Status: fmt.Sprintf("downloading %s", opts.digest[7:19]),
|
||||||
Digest: opts.digest,
|
Digest: opts.digest,
|
||||||
Total: fi.Size(),
|
Total: fi.Size(),
|
||||||
Completed: fi.Size(),
|
Completed: fi.Size(),
|
||||||
|
|
|
@ -301,7 +301,7 @@ func (b *blobUpload) Wait(ctx context.Context, fn func(api.ProgressResponse)) er
|
||||||
}
|
}
|
||||||
|
|
||||||
fn(api.ProgressResponse{
|
fn(api.ProgressResponse{
|
||||||
Status: fmt.Sprintf("uploading %s", b.Digest),
|
Status: fmt.Sprintf("uploading %s", b.Digest[7:19]),
|
||||||
Digest: b.Digest,
|
Digest: b.Digest,
|
||||||
Total: b.Total,
|
Total: b.Total,
|
||||||
Completed: b.Completed.Load(),
|
Completed: b.Completed.Load(),
|
||||||
|
@ -352,7 +352,7 @@ func uploadBlob(ctx context.Context, mp ModelPath, layer *Layer, opts *RegistryO
|
||||||
default:
|
default:
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
fn(api.ProgressResponse{
|
fn(api.ProgressResponse{
|
||||||
Status: fmt.Sprintf("uploading %s", layer.Digest),
|
Status: fmt.Sprintf("uploading %s", layer.Digest[7:19]),
|
||||||
Digest: layer.Digest,
|
Digest: layer.Digest,
|
||||||
Total: layer.Size,
|
Total: layer.Size,
|
||||||
Completed: layer.Size,
|
Completed: layer.Size,
|
||||||
|
|
Loading…
Reference in a new issue