dlprogress

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 25, 2026 License: MIT Imports: 5 Imported by: 0

README

dlprogress

Go Reference Go Report Card

A reusable Bubble Tea TUI for visualising concurrent / multi-connection file downloads in the terminal.

This package is not a downloader. It's a drop-in progress UI: you drive it from your own download goroutines by sending typed messages, and it renders nicely. Bring your own HTTP / S3 / torrent client.

Demos

Per-worker bars (BarModeWorkers, default)

One overall bar per file, plus a row of mini bars showing every worker / chunk.

workers demo

▶ Watch on asciinema: https://asciinema.org/a/1WWcnJAb0lmG4tGw

Unified IDM-style bar (BarModeUnified)

A single bar represents the whole file's byte range; each worker fills its own slice in place, with red dividers at chunk boundaries — like Internet Download Manager's "start positions and download progress by connections" bar.

unified demo

▶ Watch on asciinema: https://asciinema.org/a/cIekJCySAVdM20Ei

Features

  • 🧵 Designed for N concurrent workers per file (e.g. 16 HTTP range requests).
  • 📊 Two visualisations: per-worker mini bars or IDM-style unified bar.
  • ⚡ Moving-average speed and ETA per file.
  • 📦 Multiple files tracked simultaneously, each with its own worker count.
  • 🎨 Fully customisable lipgloss styles.
  • 🪶 No goroutines started by the library; you control concurrency.
  • 🧪 Safe to call Send from any goroutine (Bubble Tea handles that).

Install

go get github.com/mhb8898/bubbletea-downloader-progressbar

Requires Go 1.21+.

Quick start

package main

import (
    "fmt"
    "os"

    tea "github.com/charmbracelet/bubbletea"
    dl "github.com/mhb8898/bubbletea-downloader-progressbar"
)

func main() {
    m := dl.New(dl.WithWidth(100))
    p := tea.NewProgram(m)

    go func() {
        // 1. Register the file once, declaring how many concurrent
        //    workers will report progress for it.
        p.Send(dl.AddFileMsg{
            ID:      "f1",
            Name:    "ubuntu.iso",
            Size:    4_700_000_000,
            Workers: 16,
        })

        // 2. From each of your 16 download goroutines, push cumulative
        //    bytes downloaded by that worker.
        //
        //    p.Send(dl.ChunkProgressMsg{
        //        FileID:     "f1",
        //        WorkerID:   w,            // 0..Workers-1
        //        Downloaded: cumBytes,     // monotonic, not a delta
        //        ChunkTotal: chunkSize,    // optional but recommended
        //    })

        // 3. Finalise.
        p.Send(dl.FileDoneMsg{FileID: "f1"})
    }()

    if _, err := p.Run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Bar modes

// Default — overall bar + per-worker mini bars.
m := dl.New(dl.WithBarMode(dl.BarModeWorkers))

// IDM-style — one bar, chunks fill their byte ranges in place.
m := dl.New(dl.WithBarMode(dl.BarModeUnified))

For non-uniform chunk layouts (e.g. resumed downloads where workers don't start at evenly-spaced offsets), pass Chunks to AddFileMsg:

p.Send(dl.AddFileMsg{
    ID: "f1", Name: "ubuntu.iso", Size: size, Workers: 4,
    Chunks: []dl.ChunkRange{
        {Offset: 0,           Size: 1_000_000_000},
        {Offset: 1_000_000_000, Size: 1_500_000_000},
        {Offset: 2_500_000_000, Size:   900_000_000},
        {Offset: 3_400_000_000, Size: 1_300_000_000},
    },
})

If Chunks is omitted, the library assumes an even contiguous split of Size across Workers.

Messages

The complete public API surface:

Message Purpose
AddFileMsg Register a new file (one call per file, before progress).
ChunkProgressMsg Cumulative bytes for one (FileID, WorkerID).
FileDoneMsg Mark a file complete — final speed/duration is captured.
FileErrorMsg Mark a file as failed; the error text is rendered.
RemoveFileMsg Drop a file from the view.
QuitMsg Convenience: request a graceful tea.Quit.

All updates are sent via tea.Program.Send, which is safe to call from any goroutine. Downloaded is cumulative (the total bytes that worker has fetched so far), not a delta — the library ignores any decrease.

Options

m := dl.New(
    dl.WithWidth(120),                // total render width in cells
    dl.WithBarMode(dl.BarModeUnified),// IDM-style single bar
    dl.WithShowWorkers(true),         // mini bars below main bar (workers mode)
    dl.WithWorkerBarWidth(8),         // width of each worker mini bar
    dl.WithRefreshRate(100*time.Millisecond),
    dl.WithStyles(dl.DefaultStyles()),// override lipgloss styles
)

Examples

Two runnable demos live in examples/:

go run ./examples/concurrent  # workers mode, 3 files × 16/8/12 workers
go run ./examples/unified     # IDM-style unified bar

How the speed/ETA are computed

The library keeps a rolling ~3-second sample window per file of (timestamp, totalBytes) snapshots and reports the slope between the oldest and newest sample. ETA is remaining / speed. When the speed window is empty (very start) the speed displays as -- B/s.

License

MIT © Mahdi Bahreini

Documentation

Overview

Package dlprogress provides a reusable Bubble Tea TUI for visualising concurrent file downloads. It is NOT a downloader: you drive it by sending messages (AddFileMsg, ChunkProgressMsg, FileDoneMsg, …) from your own download goroutines, and it renders the progress.

Quick start

m := dlprogress.New(dlprogress.WithWidth(100))
p := tea.NewProgram(m)

go func() {
    p.Send(dlprogress.AddFileMsg{
        ID:      "f1",
        Name:    "ubuntu.iso",
        Size:    size,
        Workers: 16,
    })
    // From each of your 16 download goroutines:
    p.Send(dlprogress.ChunkProgressMsg{
        FileID:     "f1",
        WorkerID:   w,
        Downloaded: cumulativeBytes,
        ChunkTotal: chunkSize,
    })
    p.Send(dlprogress.FileDoneMsg{FileID: "f1"})
}()

if _, err := p.Run(); err != nil { /* … */ }

Bar modes

Two visualisations are available:

  • BarModeWorkers (default): one continuous overall bar plus a row of small per-worker chunk bars.
  • BarModeUnified: a single IDM-style bar where each worker fills its own slice of the file's byte range in place, with red boundary markers at chunk starts. Requires a known Size.

Select with WithBarMode.

Concurrency

All updates flow through tea.Program.Send, which is safe to call from any goroutine. Downloaded values are cumulative; the library ignores decreases.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AddFileMsg

type AddFileMsg struct {
	ID      string       // unique identifier
	Name    string       // display name (e.g. filename)
	Size    int64        // total bytes, 0 if unknown
	Workers int          // number of concurrent workers/chunks (>=1)
	Chunks  []ChunkRange // optional explicit chunk layout
}

AddFileMsg registers a new file for tracking. Send once per file before any ChunkProgressMsg for that file.

If Chunks is nil/empty, the library assumes an even split of Size across Workers contiguous chunks. Provide Chunks for non-uniform layouts (e.g. resumed downloads). When set, len(Chunks) must equal Workers.

type BarMode

type BarMode int

BarMode controls how the main progress bar visualises workers/chunks.

const (
	// BarModeWorkers renders one continuous progress bar showing overall
	// progress, followed by a row of per-worker mini bars. This is the
	// default and is best for many small workers.
	BarModeWorkers BarMode = iota

	// BarModeUnified renders a single bar that represents the whole file
	// byte-range, with each worker filling its own slice in place and red
	// separators at chunk boundaries (IDM-style). Requires a known Size.
	// Falls back to BarModeWorkers when Size is unknown.
	BarModeUnified
)

type ChunkProgressMsg

type ChunkProgressMsg struct {
	FileID     string
	WorkerID   int
	Downloaded int64
	ChunkTotal int64
}

ChunkProgressMsg reports progress from one worker/chunk of a file. Downloaded is the cumulative bytes that worker has fetched so far (not a delta). ChunkTotal is the size of that worker's chunk if known.

type ChunkRange

type ChunkRange struct {
	Offset int64
	Size   int64
}

ChunkRange describes one worker's slice of the file: where it starts (Offset, bytes from the file start) and how big it is (Size, bytes).

type FileDoneMsg

type FileDoneMsg struct {
	FileID string
}

FileDoneMsg marks a file as completed.

type FileErrorMsg

type FileErrorMsg struct {
	FileID string
	Err    error
}

FileErrorMsg marks a file as failed. The TUI will display Err.

type FileStatus

type FileStatus int

FileStatus is the lifecycle state of a tracked file.

const (
	StatusActive FileStatus = iota
	StatusDone
	StatusError
)

type Model

type Model struct {
	// contains filtered or unexported fields
}

Model is the Bubble Tea model. Pass it to tea.NewProgram.

func New

func New(opts ...Option) Model

New returns a configured Model.

func (Model) AllDone

func (m Model) AllDone() bool

AllDone returns true if every tracked file is done or errored.

func (Model) FileCount

func (m Model) FileCount() int

FileCount returns the number of files currently tracked.

func (Model) Init

func (m Model) Init() tea.Cmd

Init implements tea.Model.

func (Model) Update

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update implements tea.Model.

func (Model) View

func (m Model) View() string

View implements tea.Model.

type Option

type Option func(*Model)

Option configures a Model.

func WithBarMode

func WithBarMode(m BarMode) Option

WithBarMode selects the main bar visualisation. Defaults to BarModeWorkers.

func WithRefreshRate

func WithRefreshRate(d time.Duration) Option

WithRefreshRate sets how often the model emits internal ticks for animation and ETA updates. Defaults to 100ms.

func WithShowWorkers

func WithShowWorkers(show bool) Option

WithShowWorkers toggles per-worker mini-bar rendering. Default: true.

func WithStyles

func WithStyles(s Styles) Option

WithStyles overrides the default lipgloss styles.

func WithWidth

func WithWidth(w int) Option

WithWidth sets the total rendering width in cells. Defaults to 80.

func WithWorkerBarWidth

func WithWorkerBarWidth(w int) Option

WithWorkerBarWidth sets the width of each per-worker mini bar. Defaults to 6.

type QuitMsg

type QuitMsg struct{}

QuitMsg requests a graceful quit. Optional helper.

type RemoveFileMsg

type RemoveFileMsg struct {
	FileID string
}

RemoveFileMsg drops a file from the view.

type Styles

type Styles struct {
	Name      lipgloss.Style
	Size      lipgloss.Style
	BarFull   lipgloss.Style
	BarEmpty  lipgloss.Style
	BarTip    lipgloss.Style
	Percent   lipgloss.Style
	Speed     lipgloss.Style
	ETA       lipgloss.Style
	Worker    lipgloss.Style
	Done      lipgloss.Style
	Error     lipgloss.Style
	Separator lipgloss.Style
	Boundary  lipgloss.Style // chunk-boundary marker in unified bar mode
}

Styles controls the look of the rendered UI.

func DefaultStyles

func DefaultStyles() Styles

DefaultStyles returns a pleasant default style set.

Directories

Path Synopsis
examples
concurrent command
Example: simulate 3 concurrent file downloads, each split into 16 worker goroutines, and visualise them with dlprogress.
Example: simulate 3 concurrent file downloads, each split into 16 worker goroutines, and visualise them with dlprogress.
unified command
Example: IDM-style unified progress bar.
Example: IDM-style unified progress bar.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL