progress

package
v1.19.2 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2026 License: MIT Imports: 8 Imported by: 0

README

File Progress

Go Version License Coverage

Thread-safe file I/O wrapper with progress tracking callbacks, supporting standard io interfaces for seamless integration with existing Go code.


Table of Contents


Overview

The progress package provides a production-ready file I/O wrapper that tracks read/write progress through callback functions. It implements all standard Go io interfaces (Reader, Writer, Seeker, Closer, etc.) while adding transparent progress monitoring capabilities.

Design Philosophy
  1. Standard Library Compatibility: Fully implements Go's standard io interfaces
  2. Zero Overhead When Unused: Progress tracking adds minimal overhead when no callbacks are registered
  3. Thread-Safe Callbacks: Atomic operations ensure safe concurrent callback invocation
  4. Transparent Integration: Drop-in replacement for *os.File in existing code
  5. Flexible File Creation: Multiple constructors for different use cases (open, create, temp)
Key Features
  • Progress Tracking: Real-time callbacks for read/write operations, EOF, and position resets
  • Standard io Interfaces: Implements Reader, Writer, Seeker, Closer, ReaderFrom, WriterTo, and more
  • Temporary File Support: Auto-deletion of temporary files with IsTemp() indicator
  • Atomic Callbacks: Thread-safe callback storage and invocation using atomic operations
  • Buffer Configuration: Configurable buffer sizes for optimal I/O performance
  • Position Tracking: SizeBOF() and SizeEOF() methods for current position and remaining bytes
  • Error Propagation: Comprehensive error codes for debugging and error handling
  • Zero Dependencies: Only standard library packages

Architecture

Component Diagram
┌─────────────────────────────────────────────────────────────┐
│                      Progress Wrapper                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐         ┌──────────────┐                  │
│  │   os.File    │◀────────│   progress   │                  │
│  │  (underlying)│         │   (wrapper)  │                  │
│  └──────────────┘         └──────┬───────┘                  │
│                                  │                          │
│                    ┌─────────────┼─────────────┐            │
│                    │             │             │            │
│            ┌───────▼──────┐ ┌────▼─────┐ ┌────▼─────┐       │
│            │ FctIncrement │ │ FctReset │ │  FctEOF  │       │
│            │  (atomic)    │ │ (atomic) │ │ (atomic) │       │
│            └──────────────┘ └──────────┘ └──────────┘       │
│                    │             │             │            │
│                    └─────────────┼─────────────┘            │
│                                  │                          │
│                          User Callbacks                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
Data Flow
Read(p []byte) → os.File.Read() → analyze() → callbacks
     │                │                │
     │                │                ├─▶ FctIncrement(bytes_read)
     │                │                │
     │                │                └─▶ FctEOF() [if io.EOF]
     │                │
     │                └─▶ return (n, err)
     │
     └─▶ return (n, err)

Write(p []byte) → os.File.Write() → analyze() → FctIncrement(bytes_written)
                        │
                        └─▶ return (n, err)

Seek(offset, whence) → os.File.Seek() → FctReset(max_size, current_pos)
                            │
                            └─▶ return (pos, err)

Truncate(size) → os.File.Truncate() → FctReset(max_size, current_pos)
                      │
                      └─▶ return err
Buffer Configuration

The SetBufferSize() method allows optimizing I/O performance for specific use cases:

Default Buffer Size: 32 KB (DefaultBuffSize)

Sizing Guidelines:

Small files (< 1 MB):    16 KB - 64 KB
Medium files (1-100 MB): 64 KB - 256 KB  
Large files (> 100 MB):  256 KB - 1 MB
Network I/O:             8 KB - 32 KB
SSD/NVMe:                64 KB - 512 KB
HDD:                     256 KB - 1 MB

Trade-offs:

  • Larger buffers: Fewer I/O operations, higher memory usage
  • Smaller buffers: More frequent callbacks, lower memory footprint

Memory Estimation:

maxMemory := bufferSize + overhead  // ~200 bytes overhead

Performance

Benchmarks

Based on test suite measurements using gmeasure:

Operation Throughput Latency (p50) Latency (p99)
Read (32KB buffer) ~2.5 GB/s 12 µs 45 µs
Write (32KB buffer) ~2.2 GB/s 14 µs 52 µs
Seek N/A 1 µs 3 µs
Callback Invocation N/A 50 ns 200 ns

Note: Benchmarks are hardware-dependent and measured on modern SSD hardware.

Memory Usage

Per-File Instance:

  • Base overhead: ~200 bytes
  • Callback storage: 24 bytes per callback (atomic.Value)
  • Buffer (when set): Configurable (default 32 KB)

Total Memory:

Memory = BaseOverhead + (NumCallbacks × 24) + BufferSize

Example:

// Typical usage: ~32.5 KB per file instance
memory := 200 + (3 * 24) + 32768  // 32,840 bytes
Scalability
  • Concurrent Files: Scales linearly up to OS file descriptor limit
  • Callback Overhead: < 1% when using atomic operations
  • Thread Safety: Safe for concurrent callback registration from multiple goroutines
  • Memory Footprint: O(1) per file, independent of file size

Limits:

  • OS file descriptor limit (typically 1024-65536)
  • Available memory for buffers
  • Disk I/O bandwidth

Use Cases

1. File Download with Progress Bar

Monitor download progress in real-time:

func downloadWithProgress(url, dest string) error {
    resp, _ := http.Get(url)
    defer resp.Body.Close()
    
    p, _ := progress.Create(dest)
    defer p.Close()
    
    total := resp.ContentLength
    var downloaded int64
    
    p.RegisterFctIncrement(func(n int64) {
        downloaded += n
        fmt.Printf("\rDownloading: %d%%", (downloaded*100)/total)
    })
    
    io.Copy(p, resp.Body)
    return nil
}
2. Large File Processing with Status Updates

Track processing progress for long-running operations:

func processLargeFile(path string) error {
    p, _ := progress.Open(path)
    defer p.Close()
    
    size, _ := p.SizeEOF()
    
    p.RegisterFctIncrement(func(n int64) {
        current, _ := p.SizeBOF()
        log.Printf("Processed: %.2f%%", float64(current*100)/float64(size))
    })
    
    scanner := bufio.NewScanner(p)
    for scanner.Scan() {
        // Process line
    }
    return nil
}
3. Temporary File Management

Automatic cleanup of temporary files:

func processTempData(data []byte) error {
    p, _ := progress.Temp("process-*.tmp")
    defer p.Close()  // Auto-deleted if IsTemp()
    
    p.Write(data)
    // Process temp file
    return nil
}
4. File Upload with Bandwidth Monitoring

Track upload speed and estimate completion time:

func uploadWithMetrics(path, url string) error {
    p, _ := progress.Open(path)
    defer p.Close()
    
    var (
        start = time.Now()
        bytes int64
    )
    
    p.RegisterFctIncrement(func(n int64) {
        bytes += n
        elapsed := time.Since(start).Seconds()
        speed := float64(bytes) / elapsed / 1024 / 1024
        fmt.Printf("Upload speed: %.2f MB/s\n", speed)
    })
    
    http.Post(url, "application/octet-stream", p)
    return nil
}
5. Batch File Operations

Monitor progress across multiple files:

func processBatch(files []string) error {
    for i, file := range files {
        p, _ := progress.Open(file)
        
        p.RegisterFctEOF(func() {
            fmt.Printf("Completed %d/%d: %s\n", i+1, len(files), file)
        })
        
        // Process file
        p.Close()
    }
    return nil
}

Quick Start

Installation
go get github.com/nabbar/golib/file/progress
Basic Example
package main

import (
    "fmt"
    "github.com/nabbar/golib/file/progress"
)

func main() {
    // Open existing file
    p, err := progress.Open("data.txt")
    if err != nil {
        panic(err)
    }
    defer p.Close()
    
    // Read file
    buf := make([]byte, 1024)
    n, err := p.Read(buf)
    fmt.Printf("Read %d bytes\n", n)
}
With Progress Callbacks
p, _ := progress.Open("largefile.dat")
defer p.Close()

var totalBytes int64

// Track each read operation
p.RegisterFctIncrement(func(n int64) {
    totalBytes += n
    fmt.Printf("Read: %d bytes total\n", totalBytes)
})

// Detect when EOF is reached
p.RegisterFctEOF(func() {
    fmt.Println("File reading completed!")
})

// Detect position resets (e.g., after Seek)
p.RegisterFctReset(func(max, current int64) {
    fmt.Printf("Position reset: %d/%d\n", current, max)
})

io.Copy(io.Discard, p)
File Upload Simulation
p, _ := progress.Create("upload.dat")
defer p.Close()

data := make([]byte, 10*1024*1024) // 10 MB

var uploaded int64
p.RegisterFctIncrement(func(n int64) {
    uploaded += n
    percentage := (uploaded * 100) / int64(len(data))
    if percentage%10 == 0 {
        fmt.Printf("Upload: %d%%\n", percentage)
    }
})

p.Write(data)
Temporary Files
// Create unique temporary file
p, _ := progress.Temp("myapp-*.tmp")
defer p.Close()  // Auto-deleted on close

fmt.Printf("Temp file: %s\n", p.Path())
fmt.Printf("Is temporary: %v\n", p.IsTemp())

p.Write([]byte("temporary data"))
File Copying
src, _ := progress.Open("source.bin")
defer src.Close()

dst, _ := progress.Create("dest.bin")
defer dst.Close()

var copied int64
src.RegisterFctIncrement(func(n int64) {
    copied += n
})

io.Copy(dst, src)
fmt.Printf("Copied: %d bytes\n", copied)

Best Practices

Testing

The package includes a comprehensive test suite with 76.1% code coverage and 140 test specifications using BDD methodology (Ginkgo v2 + Gomega).

Key test coverage:

  • ✅ All public APIs and standard interfaces
  • ✅ Concurrent access with race detector (zero races detected)
  • ✅ Performance benchmarks (throughput, latency, memory)
  • ✅ Error handling and edge cases
  • ✅ Progress callback mechanisms

For detailed test documentation, see TESTING.md.

✅ DO

Progress Tracking:

// Register callbacks before I/O
p.RegisterFctIncrement(func(n int64) {
    // Update progress bar
})

// Use SizeEOF for percentage calculation
total, _ := p.SizeEOF()
current, _ := p.SizeBOF()
percentage := float64(current) * 100 / float64(total)

Resource Management:

// Always close files
p, _ := progress.Open("file.txt")
defer p.Close()

// Check IsTemp before manual deletion
if !p.IsTemp() {
    os.Remove(p.Path())
}

Error Handling:

// Handle all errors
if n, err := p.Read(buf); err != nil {
    if errors.Is(err, io.EOF) {
        // Normal end of file
    } else {
        return fmt.Errorf("read error: %w", err)
    }
}

Buffer Sizing:

// Set appropriate buffer for workload
p.SetBufferSize(256 * 1024)  // 256 KB for large files

// Smaller for network I/O
p.SetBufferSize(8 * 1024)    // 8 KB for network

Data Persistence:

// Sync after critical writes
p.Write(criticalData)
if err := p.Sync(); err != nil {
    return fmt.Errorf("sync failed: %w", err)
}
❌ DON'T

Don't ignore errors:

// ❌ BAD: Ignoring errors
p.Read(buf)
p.Write(data)

// ✅ GOOD: Proper error handling
if _, err := p.Read(buf); err != nil {
    return err
}

Don't perform heavy work in callbacks:

// ❌ BAD: Blocking callback
p.RegisterFctIncrement(func(n int64) {
    time.Sleep(100 * time.Millisecond)  // BLOCKS I/O!
    database.UpdateProgress(n)
})

// ✅ GOOD: Async processing
updates := make(chan int64, 100)
p.RegisterFctIncrement(func(n int64) {
    select {
    case updates <- n:
    default:
    }
})
go func() {
    for n := range updates {
        database.UpdateProgress(n)
    }
}()

Don't use after Close:

// ❌ BAD: Use after close
p.Close()
p.Read(buf)  // Returns ErrorNilPointer

// ✅ GOOD: Check before use
if p != nil {
    p.Read(buf)
}

Don't share across goroutines without sync:

// ❌ BAD: Concurrent access
for i := 0; i < 10; i++ {
    go func() {
        p.Write(data)  // RACE!
    }()
}

// ✅ GOOD: Use separate files or synchronize
var mu sync.Mutex
for i := 0; i < 10; i++ {
    go func() {
        mu.Lock()
        defer mu.Unlock()
        p.Write(data)
    }()
}

Don't panic in callbacks:

// ❌ BAD: Panic in callback
p.RegisterFctIncrement(func(n int64) {
    if n == 0 {
        panic("zero bytes!")  // Crashes program
    }
})

// ✅ GOOD: Error logging
p.RegisterFctIncrement(func(n int64) {
    if n == 0 {
        log.Error("Warning: zero bytes processed")
    }
})

Don't set extreme buffer sizes:

// ❌ BAD: Excessive buffer
p.SetBufferSize(100 * 1024 * 1024)  // 100 MB!

// ✅ GOOD: Reasonable buffer
p.SetBufferSize(256 * 1024)  // 256 KB

Don't forget callback propagation:

// ❌ BAD: Lose callbacks when copying
src, _ := progress.Open("src.txt")
src.RegisterFctIncrement(callback)
dst, _ := progress.Create("dst.txt")
io.Copy(dst, src)  // src callbacks not on dst!

// ✅ GOOD: Propagate callbacks
src.SetRegisterProgress(dst)
io.Copy(dst, src)

API Reference

Progress Interface
type Progress interface {
    io.Reader
    io.Writer
    io.Seeker
    io.Closer
    io.ReaderAt
    io.WriterAt
    io.ReaderFrom
    io.WriterTo
    io.ByteReader
    io.ByteWriter
    io.StringWriter
    
    // Progress-specific methods
    RegisterFctIncrement(fct FctIncrement)
    RegisterFctReset(fct FctReset)
    RegisterFctEOF(fct FctEOF)
    SetRegisterProgress(f Progress)
    
    // File operations
    Path() string
    Stat() (os.FileInfo, error)
    SizeBOF() (int64, error)
    SizeEOF() (int64, error)
    Truncate(size int64) error
    Sync() error
    IsTemp() bool
    SetBufferSize(size int32)
    CloseDelete() error
}

Methods:

  • Read(p []byte) (int, error): Read bytes into buffer
  • Write(p []byte) (int, error): Write bytes from buffer
  • Seek(offset int64, whence int) (int64, error): Change file position
  • Close() error: Close file and release resources
  • Path() string: Get cleaned file path
  • Stat() (os.FileInfo, error): Get file metadata
  • SizeBOF() (int64, error): Bytes from start to current position
  • SizeEOF() (int64, error): Bytes from current position to end
  • Truncate(size int64) error: Resize file
  • Sync() error: Flush to disk
  • IsTemp() bool: Check if temporary file
  • SetBufferSize(size int32): Configure I/O buffer size
  • CloseDelete() error: Close and delete file
Configuration

Constructors:

// Open existing file (read-only by default)
func Open(path string) (Progress, error)

// Create new file (write-only, truncate if exists)
func Create(path string) (Progress, error)

// Open/create with custom flags
func New(path string, flag int, perm os.FileMode) (Progress, error)

// Create temporary file (auto-deleted on close)
func Temp(pattern string) (Progress, error)

// Create unique file with auto-generated name
func Unique(path, pattern string) (Progress, error)

Examples:

// Read-only
p, _ := progress.Open("readonly.txt")

// Write-only (create or truncate)
p, _ := progress.Create("output.txt")

// Read-write, append mode
p, _ := progress.New("file.txt", os.O_RDWR|os.O_APPEND, 0644)

// Temporary file
p, _ := progress.Temp("temp-*.dat")

// Unique file in directory
p, _ := progress.Unique("/tmp", "unique-*.log")

Buffer Configuration:

p.SetBufferSize(256 * 1024)  // 256 KB buffer
Callbacks
// Called after each read/write with bytes processed
type FctIncrement func(size int64)

// Called when file position is reset (Seek, Truncate)
type FctReset func(maxSize, currentPos int64)

// Called when EOF is reached during read
type FctEOF func()

Registration:

p.RegisterFctIncrement(func(n int64) {
    log.Printf("Processed %d bytes", n)
})

p.RegisterFctReset(func(max, cur int64) {
    log.Printf("Reset to %d/%d bytes", cur, max)
})

p.RegisterFctEOF(func() {
    log.Println("End of file reached")
})

Callback Behavior:

  • FctIncrement: Called after each Read/Write operation with bytes for that operation
  • FctReset: Called after Seek or Truncate with file size and new position
  • FctEOF: Called once when io.EOF is detected during read
  • All callbacks are optional (nil-safe)
  • Callbacks are invoked serially (no concurrent calls per file)
  • Callbacks should be fast (avoid blocking operations)
Error Codes
const (
    ErrorParamEmpty       // Empty parameter provided
    ErrorNilPointer       // Nil pointer or closed file
    ErrorIOFileOpen       // File open failed
    ErrorIOFileCreate     // File creation failed
    ErrorIOFileStat       // File stat failed
    ErrorIOFileSeek       // Seek operation failed
    ErrorIOFileTruncate   // Truncate operation failed
    ErrorIOFileSync       // Sync operation failed
    ErrorIOTempFile       // Temporary file creation failed
    ErrorIOTempClose      // Temporary file close failed
    ErrorIOTempRemove     // Temporary file removal failed
)

Usage:

p, err := progress.Open("file.txt")
if err != nil {
    if errors.Is(err, progress.ErrorIOFileOpen.Error(nil)) {
        log.Fatal("Cannot open file")
    }
}

Error Handling:

  • Errors from os.File operations are wrapped with package-specific codes
  • All methods that can fail return error as last return value
  • Use errors.Is() for error type checking
  • Use errors.As() for extracting wrapped errors

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Code Quality

    • Follow Go best practices and idioms
    • Maintain or improve code coverage (target: >75%)
    • Pass all tests including race detector
    • Use gofmt and golint
  2. AI Usage Policy

    • AI must NEVER be used to generate package code or core functionality
    • AI assistance is limited to:
      • Testing (writing and improving tests)
      • Debugging (troubleshooting and bug resolution)
      • Documentation (comments, README, TESTING.md)
    • All AI-assisted work must be reviewed and validated by humans
  3. Testing

    • Add tests for new features
    • Use Ginkgo v2 / Gomega for test framework
    • Use gmeasure (not measure) for benchmarks
    • Ensure zero race conditions
  4. Documentation

    • Update GoDoc comments for public APIs
    • Add examples for new features
    • Update README.md and TESTING.md if needed
  5. Pull Request Process

    • Fork the repository
    • Create a feature branch
    • Write clear commit messages
    • Ensure all tests pass
    • Update documentation
    • Submit PR with description of changes

Improvements & Security

Current Status

The package is production-ready with no urgent improvements or security vulnerabilities identified.

Code Quality Metrics
  • 76.1% test coverage (target: >75%)
  • Zero race conditions detected with -race flag
  • Thread-safe callback operations using atomic operations
  • Memory-safe with proper resource cleanup
  • Standard compliant implements all relevant io interfaces
Future Enhancements (Non-urgent)

The following enhancements could be considered for future versions:

  1. Context Integration: Add context.Context support for cancellable I/O operations
  2. Rate Limiting: Implement bandwidth control through callback rate limiting
  3. Metrics Export: Optional integration with Prometheus or OpenTelemetry
  4. Custom Error Handlers: Allow users to provide custom error handlers in callbacks
  5. Convenience Methods: Add ReadAll(), WriteAll(), and similar helpers

These are optional improvements and not required for production use. The current implementation is stable and performant.


Resources

Package Documentation
  • GoDoc - Complete API reference with function signatures, method descriptions, and runnable examples. Essential for understanding the public interface and usage patterns. Automatically generated from source code comments with live example code execution.

  • doc.go - In-depth package documentation including design philosophy, architecture diagrams, callback mechanisms, buffer sizing guidelines, and performance considerations. Provides detailed explanations of internal mechanisms, thread-safety guarantees, and best practices for production use. Essential reading for understanding implementation details.

  • TESTING.md - Comprehensive test suite documentation covering test architecture, BDD methodology with Ginkgo v2, coverage analysis (76.1%), performance benchmarks, and guidelines for writing new tests. Includes troubleshooting, concurrency testing strategies, and CI integration examples. Critical resource for contributors and quality assurance.

  • github.com/nabbar/golib/ioutils/delim - Buffered reader for delimiter-separated data streams with custom delimiter support and constant memory usage. Useful for processing CSV, log files, or any delimited data. Complements progress tracking for structured data processing.

  • github.com/nabbar/golib/ioutils/aggregator - Thread-safe write aggregator that serializes concurrent write operations. Useful for collecting output from multiple goroutines into a single file with progress tracking. Can be combined with progress package for concurrent data collection scenarios.

  • github.com/nabbar/golib/file/bandwidth - Bandwidth limiting for file I/O operations. Controls read/write speeds to prevent network or disk saturation. Can be used alongside progress package for controlled, monitored file transfers.

  • github.com/nabbar/golib/errors - Enhanced error handling with error codes and structured error information. Used internally by progress package for comprehensive error reporting. Provides error chaining and classification capabilities.

External References
  • Go io Package - Standard library documentation for io interfaces. The progress package fully implements these interfaces for seamless integration with Go's I/O ecosystem. Essential reading for understanding Go's I/O model.

  • Go os Package - Standard library documentation for file operations. The progress package wraps os.File while maintaining full compatibility. Important for understanding underlying file operations and permissions.

  • Effective Go - Files - Official Go programming guide covering file handling best practices. Demonstrates idiomatic patterns that the progress package follows. Recommended reading for proper file resource management.

  • Go Memory Model - Official specification of Go's memory consistency guarantees. Essential for understanding the thread-safety guarantees provided by atomic operations used in callback storage. Relevant for concurrent usage scenarios.


AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for testing, documentation, and bug resolution under human supervision. All core functionality is human-designed and validated.


License

MIT License - See LICENSE file for details.

Copyright (c) 2025 Nicolas JUHEL


Maintained by: Nicolas JUHEL
Package: github.com/nabbar/golib/file/progress
Version: See releases for versioning

Documentation

Overview

Package progress provides file I/O operations with integrated real-time progress tracking and event callbacks.

Overview

The progress package wraps standard Go file operations with comprehensive progress monitoring capabilities. It implements all standard I/O interfaces (io.Reader, io.Writer, io.Seeker, etc.) while adding transparent progress tracking through configurable callbacks. This enables applications to monitor file operations in real-time without modifying existing I/O code patterns.

Design Philosophy

1. Standard Interface Compliance: Fully implements all Go I/O interfaces for drop-in replacement 2. Non-Intrusive Monitoring: Progress tracking doesn't alter I/O semantics or performance characteristics 3. Callback-Based Events: Flexible notification system for progress updates, resets, and EOF detection 4. Thread-Safe Operations: Uses atomic operations for safe concurrent access to progress state 5. Resource Management: Proper cleanup with support for temporary file auto-deletion

Key Features

  • Full standard I/O interface implementation (io.Reader, io.Writer, io.Seeker, io.Closer, etc.)
  • Real-time progress tracking with increment callbacks
  • Reset callbacks for seek operations and position changes
  • EOF detection callbacks for completion notifications
  • Configurable buffer sizes for optimized performance (default: 32KB)
  • Temporary file creation with automatic cleanup
  • Unique file generation with custom patterns
  • File position tracking (beginning-of-file and end-of-file calculations)
  • Thread-safe atomic operations for progress state
  • Support for both regular and temporary files

Basic Usage

Opening an existing file with progress tracking:

import "github.com/nabbar/golib/file/progress"

// Open file
p, err := progress.Open("largefile.dat")
if err != nil {
    log.Fatal(err)
}
defer p.Close()

// Register progress callback (called on every read/write operation)
p.RegisterFctIncrement(func(bytes int64) {
    fmt.Printf("Processed: %d bytes\n", bytes)
})

// Register EOF callback (called when file reaches end)
p.RegisterFctEOF(func() {
    fmt.Println("File processing complete!")
})

// Use like any io.Reader - callbacks are invoked transparently
io.Copy(io.Discard, p)

File Creation

Creating new files:

// Create new file
p, err := progress.Create("/path/to/newfile.dat")
if err != nil {
    log.Fatal(err)
}
defer p.Close()

// Write data - progress callbacks are triggered
data := []byte("Hello, World!")
n, err := p.Write(data)

Creating with custom flags and permissions:

// Custom file creation with flags
import "os"

p, err := progress.New("/path/to/file.dat",
    os.O_RDWR|os.O_CREATE|os.O_TRUNC,
    0644)

Temporary Files

Creating temporary files with automatic cleanup:

// Create temporary file (auto-deleted on close if IsTemp() == true)
p, err := progress.Temp("myapp-*.tmp")
if err != nil {
    log.Fatal(err)
}
defer p.Close() // Automatically deleted

// Write temporary data
p.Write([]byte("temporary content"))

Creating unique files in specific directory:

// Create unique file in custom location
p, err := progress.Unique("/tmp/myapp", "data-*.bin")
if err != nil {
    log.Fatal(err)
}
defer p.Close()

// Check if file is temporary
if p.IsTemp() {
    fmt.Println("This is a temporary file")
}

Progress Callbacks

The package provides three types of callbacks for monitoring file operations:

Increment Callback:

Called after every successful read or write operation with the cumulative byte count.

p.RegisterFctIncrement(func(bytes int64) {
    fmt.Printf("Total bytes processed: %d\n", bytes)
})

Reset Callback:

Called when file position is reset (e.g., via Seek operations) with the maximum position reached and current position.

p.RegisterFctReset(func(maxSize, currentPos int64) {
    fmt.Printf("Position reset: was at %d, now at %d\n", maxSize, currentPos)
})

EOF Callback:

Called when end-of-file is reached during read operations.

p.RegisterFctEOF(func() {
    fmt.Println("Reached end of file")
})

Buffer Configuration

Customizing buffer size for performance optimization:

p, err := progress.Open("file.dat")
if err != nil {
    log.Fatal(err)
}
defer p.Close()

// Set custom buffer size (64KB)
p.SetBufferSize(64 * 1024)

// Larger buffers reduce callback frequency but use more memory
// Default buffer size is 32KB (DefaultBuffSize constant)

Advanced Features

File Position Tracking:

// Get bytes from beginning to current position
bof, err := p.SizeBOF()
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Read %d bytes so far\n", bof)

// Get remaining bytes from current position to EOF
eof, err := p.SizeEOF()
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Remaining bytes: %d\n", eof)

File Truncation:

// Truncate file to specific size
err := p.Truncate(1024) // Truncate to 1KB
if err != nil {
    log.Fatal(err)
}
// Reset callback is automatically triggered

Syncing to Disk:

// Force write of buffered data to disk
err := p.Sync()
if err != nil {
    log.Fatal(err)
}

Callback Propagation

Progress callbacks can be propagated to other Progress instances:

// Source file with progress tracking
src, _ := progress.Open("source.dat")
src.RegisterFctIncrement(func(bytes int64) {
    fmt.Printf("Source: %d bytes\n", bytes)
})

// Destination file - inherits source's callbacks
dst, _ := progress.Create("dest.dat")
src.SetRegisterProgress(dst)

// Both files now share the same progress callbacks
io.Copy(dst, src)

File Information

Accessing file metadata:

// Get file statistics
info, err := p.Stat()
if err != nil {
    log.Fatal(err)
}
fmt.Printf("File size: %d bytes\n", info.Size())
fmt.Printf("Modified: %v\n", info.ModTime())

// Get file path
path := p.Path()
fmt.Printf("File path: %s\n", path)

Cleanup Operations

Standard close:

// Close file (keeps file on disk)
err := p.Close()
if err != nil {
    log.Fatal(err)
}

Close and delete:

// Close and delete file
err := p.CloseDelete()
if err != nil {
    log.Fatal(err)
}
// File is removed from filesystem

Use Cases

File Upload/Download Progress:

Monitor file transfer operations in web applications or CLI tools.

p, _ := progress.Open("upload.bin")
defer p.Close()

var totalBytes int64
p.RegisterFctIncrement(func(bytes int64) {
    totalBytes = bytes
    percentage := float64(bytes) / float64(fileSize) * 100
    fmt.Printf("\rUploading: %.2f%%", percentage)
})

// Upload to server
http.Post(url, "application/octet-stream", p)

Large File Processing:

Track progress when processing large data files.

p, _ := progress.Open("largefile.csv")
defer p.Close()

p.RegisterFctIncrement(func(bytes int64) {
    fmt.Printf("Processed %d MB\n", bytes/(1024*1024))
})

scanner := bufio.NewScanner(p)
for scanner.Scan() {
    processLine(scanner.Text())
}

Temporary Work Files:

Use temporary files for intermediate processing stages.

// Create temp file for processing
tmp, _ := progress.Temp("processing-*.dat")
defer tmp.Close() // Auto-deleted

// Process data through temporary file
io.Copy(tmp, dataSource)
tmp.Seek(0, io.SeekStart)
processData(tmp)

Error Handling

The package defines error codes in the errors.go file:

var (
    ErrorParamEmpty          // Empty parameters
    ErrorNilPointer          // Nil pointer dereference
    ErrorIOFileStat          // File stat error
    ErrorIOFileSeek          // File seek error
    ErrorIOFileTruncate      // File truncate error
    ErrorIOFileSync          // File sync error
    ErrorIOFileOpen          // File open error
    ErrorIOFileTempNew       // Temporary file creation error
    ErrorIOFileTempClose     // Temporary file close error
    ErrorIOFileTempRemove    // Temporary file removal error
)

All errors are wrapped using github.com/nabbar/golib/errors for enhanced error handling.

Performance Considerations

Buffer Sizing:

The default buffer size (32KB) is optimized for general use. Adjust based on your workload:

  • Small files (<1MB): Default buffer is sufficient
  • Large files (>100MB): Increase to 64KB or 128KB for better performance
  • High-frequency operations: Larger buffers reduce callback overhead
  • Memory-constrained: Use smaller buffers (16KB) to reduce memory footprint

Memory Usage:

  • Base overhead: ~200 bytes per Progress instance
  • Buffer allocation: Configurable (default 32KB)
  • No additional allocations during normal I/O operations
  • Atomic operations ensure minimal lock contention

Callback Overhead:

  • Increment callback: Called on every Read/Write (can be frequent)
  • Reset callback: Called on Seek/Truncate operations (infrequent)
  • EOF callback: Called once per file read completion
  • Nil callbacks have minimal overhead (simple atomic load check)

Thread Safety

The Progress interface uses atomic operations for callback storage and retrieval, making callback registration thread-safe. However, file I/O operations themselves follow standard Go file semantics:

  • Multiple goroutines can read from the same file concurrently using ReadAt
  • Concurrent Read/Write/Seek operations require external synchronization
  • Callback invocations are sequential (not concurrent)

Dependencies

The package depends on:

  • Standard library: os, io, path/filepath, sync/atomic
  • github.com/nabbar/golib/errors: Enhanced error handling

Interface Compliance

The Progress interface implements:

  • io.Reader, io.ReaderAt, io.ReaderFrom
  • io.Writer, io.WriterAt, io.WriterTo, io.StringWriter
  • io.Seeker
  • io.Closer
  • io.ByteReader, io.ByteWriter
  • All combined interfaces (io.ReadCloser, io.ReadWriteCloser, etc.)

This ensures drop-in compatibility with any code expecting standard I/O interfaces.

Examples

See example_test.go for comprehensive usage examples including:

  • Basic file operations with progress tracking
  • Temporary file creation and management
  • Progress callback registration and usage
  • Buffer size configuration
  • File position tracking
  • Error handling patterns
  • Real-world use cases (file copy, upload simulation, batch processing)

Package progress provides file I/O operations with real-time progress tracking and callbacks.

This package wraps standard file operations with integrated progress monitoring capabilities, enabling applications to track and respond to file I/O events through registered callbacks. It implements all standard Go I/O interfaces while adding progress tracking functionality.

Key features:

  • Progress tracking with callbacks (increment, reset, EOF)
  • Full io.Reader, io.Writer, io.Seeker interface implementation
  • Configurable buffer sizes for optimal performance
  • Temporary and unique file creation
  • File position tracking (BOF/EOF)
  • Thread-safe atomic operations

Example usage:

import (
    "io"
    "github.com/nabbar/golib/file/progress"
)

// Open file with progress tracking
p, err := progress.Open("largefile.dat")
if err != nil {
    panic(err)
}
defer p.Close()

// Register progress callback
p.RegisterFctIncrement(func(bytes int64) {
    fmt.Printf("Progress: %d bytes\n", bytes)
})

// Register EOF callback
p.RegisterFctEOF(func() {
    fmt.Println("Reading complete!")
})

// Read file - callbacks will be invoked
io.Copy(io.Discard, p)
Example (BatchProcessing)

Example_batchProcessing demonstrates processing a file in chunks with progress.

package main

import (
	"fmt"
	"io"
	"os"

	"github.com/nabbar/golib/file/progress"
)

func main() {
	// Create test file
	testFile := "/tmp/progress-batch.txt"
	testData := []byte("Line1\nLine2\nLine3\nLine4\nLine5\n")
	if err := os.WriteFile(testFile, testData, 0644); err != nil {
		fmt.Printf("Setup error: %v\n", err)
		return
	}
	defer os.Remove(testFile)

	// Open file
	p, err := progress.Open(testFile)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer p.Close()

	// Track processing
	lineCount := 0
	p.RegisterFctIncrement(func(bytes int64) {
		// Called on each read
	})

	p.RegisterFctEOF(func() {
		fmt.Printf("Processed %d lines\n", lineCount)
	})

	// Process in chunks
	buf := make([]byte, 10)
	for {
		n, err := p.Read(buf)
		if n > 0 {
			for i := 0; i < n; i++ {
				if buf[i] == '\n' {
					lineCount++
				}
			}
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}
	}

}
Output:

Processed 5 lines
Example (FileCopy)

Example_fileCopy demonstrates a real-world file copy operation with progress tracking.

package main

import (
	"fmt"
	"io"
	"os"

	"github.com/nabbar/golib/file/progress"
)

func main() {
	// Create source file
	srcFile := "/tmp/progress-source.txt"
	srcData := []byte("File copy example with progress tracking")
	if err := os.WriteFile(srcFile, srcData, 0644); err != nil {
		fmt.Printf("Setup error: %v\n", err)
		return
	}
	defer os.Remove(srcFile)

	// Open source with progress
	src, err := progress.Open(srcFile)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer src.Close()

	// Create destination
	dstFile := "/tmp/progress-dest.txt"
	dst, err := progress.Create(dstFile)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer os.Remove(dstFile)
	defer dst.Close()

	// Track copy progress with manual copy loop
	var totalBytes int64
	src.RegisterFctIncrement(func(bytes int64) {
		totalBytes += bytes
	})

	src.RegisterFctEOF(func() {
		fmt.Printf("Copy complete: %d bytes\n", totalBytes)
	})

	// Manual copy to trigger progress callbacks
	buf := make([]byte, 32*1024)
	for {
		n, err := src.Read(buf)
		if n > 0 {
			dst.Write(buf[:n])
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}
	}

}
Output:

Copy complete: 40 bytes
Example (UploadSimulation)

Example_uploadSimulation demonstrates simulating file upload with progress.

package main

import (
	"fmt"
	"io"
	"os"

	"github.com/nabbar/golib/file/progress"
)

func main() {
	// Create test file
	testFile := "/tmp/progress-upload.dat"
	testData := make([]byte, 100) // 100 bytes
	for i := range testData {
		testData[i] = byte(i % 256)
	}
	if err := os.WriteFile(testFile, testData, 0644); err != nil {
		fmt.Printf("Setup error: %v\n", err)
		return
	}
	defer os.Remove(testFile)

	// Open file for "upload"
	p, err := progress.Open(testFile)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer p.Close()

	// Get file size for percentage calculation
	info, _ := p.Stat()
	fileSize := info.Size()

	// Track upload progress - accumulate bytes
	var totalBytes int64
	var shown bool
	p.RegisterFctIncrement(func(bytes int64) {
		totalBytes += bytes
		percentage := float64(totalBytes) / float64(fileSize) * 100
		if percentage >= 100 && !shown {
			fmt.Printf("Upload: 100%%\n")
			shown = true
		}
	})

	p.RegisterFctEOF(func() {
		fmt.Println("Upload complete!")
	})

	// Simulate upload with manual read loop
	buf := make([]byte, 32)
	for {
		_, err := p.Read(buf)
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}
	}

}
Output:

Upload: 100%
Upload complete!

Index

Examples

Constants

View Source
const (
	ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgFileProgress
	ErrorSyscallRLimitGet
	ErrorSyscallRLimitSet
	ErrorIOFileStat
	ErrorIOFileSeek
	ErrorIOFileTruncate
	ErrorIOFileSync
	ErrorIOFileOpen
	ErrorIOFileTempNew
	ErrorIOFileTempClose
	ErrorIOFileTempRemove
	ErrorNilPointer
)
View Source
const DefaultBuffSize = 32 * 1024 // see io.copyBuffer

Variables

This section is empty.

Functions

This section is empty.

Types

type FctEOF

type FctEOF func()

type FctIncrement

type FctIncrement func(size int64)

type FctReset

type FctReset func(size, current int64)

type File

type File interface {
	// CloseDelete closes and deletes the file if it is a regular file.
	//
	// It returns an error if the file is not a regular file, or if the file
	// cannot be closed or deleted.
	//
	// Note that this method does not follow symlinks. Therefore, if the file is a
	// symbolic link, the target of the symbolic link will not be deleted.
	// If you want to delete the target of the symbolic link, you should use the
	// os.Readlink function to get the target path and then the os.Remove function to
	// delete the target.
	CloseDelete() error

	// Path returns the path of the file.
	Path() string
	// Stat returns the file information as os.FileInfo for the given file or an error.
	//
	// If the file is not a regular file, or if the file information cannot be retrieved,
	// the function will return an error.
	//
	// Note that this method does not follow symlinks. Therefore, if the file is a symbolic link,
	// the target of the symbolic link will not be retrieved. If you want to retrieve the
	// target of the symbolic link, you should use the os.Readlink function to get the target path
	// and then the os.Stat function to retrieve the file information.
	Stat() (os.FileInfo, error)

	// SizeBOF returns the size of the file before the end of file (BOF) and an error if the size cannot be retrieved.
	//
	// The size is returned as an int64 and the error is returned as an error interface.
	//
	// If the file is not a regular file, or if the file information cannot be retrieved, the function will return an error.
	//
	// Note that this method does not follow symlinks. Therefore, if the file is a symbolic link, the target of the
	// symbolic link will not be retrieved. If you want to retrieve the target of the symbolic link, you should use
	// the os.Readlink function to get the target path and then the os.Stat function to retrieve the file information.
	SizeBOF() (size int64, err error)
	// SizeEOF returns the size of the file from the current position to the end of the file (EOF)
	// and an error if the size cannot be retrieved.
	//
	// The size is returned as an int64 and the error is returned as an error interface.
	//
	// If the file is not a regular file, or if the file information cannot be retrieved, the function will return an error.
	//
	// Note that this method does not follow symlinks. Therefore, if the file is a symbolic link, the target of the
	// symbolic link will not be retrieved. If you want to retrieve the target of the symbolic link, you should use
	// the os.Readlink function to get the target path and then the os.Stat function to retrieve the file information.
	SizeEOF() (size int64, err error)

	// Truncate will truncate the file at the given size.
	//
	// The function will return an error if the file cannot be truncated at the given size.
	//
	// Note that this method does not follow symlinks. Therefore, if the file is a symbolic link, the target of the
	// symbolic link will not be truncated. If you want to truncate the target of the symbolic link, you should use
	// the os.Readlink function to get the target path and then the os.Truncate function to truncate the target file.
	Truncate(size int64) error
	// Sync calls the Sync method on the underlying file.
	//
	// It returns an error if the Sync method cannot be called or if it returns an error.
	//
	// Note that this method does not follow symlinks. Therefore, if the file is a symbolic link, the target of the
	// symbolic link will not be synced. If you want to sync the target of the symbolic link, you should use
	// the os.Readlink function to get the target path and then the os.File.Sync function to sync the target file.
	Sync() error
}

type Progress

type Progress interface {
	GenericIO
	File
	TempFile

	// RegisterFctIncrement registers a function to be called when the progress of
	// a file being read or written reaches a certain number of bytes. The
	// function will be called with the number of bytes that have been read or
	// written from the start of the file. The function is called even if the
	// registered progress is not reached (i.e. if the file is smaller than
	// the registered progress). The function is called with the current
	// progress when the file is closed (i.e. when io.Copy returns io.EOF).
	//
	// The function is called with the following signature:
	//
	// func(size int64)
	//
	// If the function is nil, it is simply ignored.
	RegisterFctIncrement(fct FctIncrement)
	// RegisterFctReset registers a function to be called when the progress of a
	// file being read or written is reset. The function will be called with the
	// maximum progress that has been reached and the current progress when
	// the file is closed (i.e. when io.Copy returns io.EOF).
	//
	// The function is called with the following signature:
	//
	// func(size, current int64)
	//
	// If the function is nil, it is simply ignored.
	RegisterFctReset(fct FctReset)
	// RegisterFctEOF registers a function to be called when the end of a file is reached.
	// The function will be called with no arguments and will be called even if the
	// registered progress is not reached (i.e. if the file is smaller than
	// the registered progress).
	//
	// If the function is nil, it is simply ignored.
	RegisterFctEOF(fct FctEOF)
	// SetBufferSize sets the buffer size for the progress.
	//
	// The buffer size is used for the io.Copy function when reading from or writing to a file.
	//
	// The buffer size should be a power of two and greater or equal to 1.
	//
	// If the buffer size is less than 1 or if it is not a power of two, the function will return an error.
	//
	// The function returns an error if the buffer size cannot be set.
	//
	// Note that this method does not follow symlinks. Therefore, if the file is a symbolic link, the target of the
	// symbolic link will not be affected. If you want to set the buffer size of the target of the symbolic link, you
	// should use the os.Readlink function to get the target path and then the os.File.SetBufferSize function to set the
	// buffer size of the target file.
	SetBufferSize(size int32)
	// SetRegisterProgress sets the progress of the file.
	//
	// It takes a Progress interface as argument and sets the progress of the file to the given progress.
	//
	// The progress is used when the io.Copy function is called to read from or write to the file.
	//
	// The progress is used to call the functions registered with RegisterFctIncrement, RegisterFctReset and RegisterFctEOF.
	//
	// If the progress is nil, it is simply ignored.
	//
	// The function returns an error if the progress cannot be set.
	SetRegisterProgress(f Progress)

	// Reset resets the progress of the file to the given maximum progress.
	//
	// It is used to reset the progress of the file after it has been closed.
	//
	// If the maximum progress is less than 1, the function will return an error.
	//
	// The function returns an error if the progress cannot be reset.
	Reset(max int64)
}

func Create

func Create(name string) (Progress, error)

Create creates a new file with the given name and returns a Progress interface.

The function returns an error if the file cannot be created.

The Progress interface is used to track the progress of the file when it is being read from or written to.

The function returns an error if the progress cannot be set.

The function is used to create new files. If the file already exists, the function will return an error.

Example

ExampleCreate demonstrates creating a new file.

package main

import (
	"fmt"
	"os"

	"github.com/nabbar/golib/file/progress"
)

func main() {
	testFile := "/tmp/progress-created.txt"
	defer os.Remove(testFile)

	// Create new file
	p, err := progress.Create(testFile)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer p.Close()

	// Write data
	data := []byte("New file content")
	n, err := p.Write(data)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Created file and wrote %d bytes\n", n)
}
Output:

Created file and wrote 16 bytes

func New

func New(name string, flags int, perm os.FileMode) (Progress, error)

New opens a file with the given name, flags and permissions and returns a Progress interface.

The Progress interface is used to track the progress of the file when it is being read from or written to.

The function returns an error if the file cannot be opened.

func Open

func Open(name string) (Progress, error)

Open opens the file with the given name and returns a Progress interface.

The function returns an error if the file cannot be opened.

The Progress interface is used to track the progress of the file when it is being read from or written to.

The function returns an error if the progress cannot be set.

The function is used to open existing files. If the file does not exist, the function will return an error.

Example

ExampleOpen demonstrates opening an existing file with basic usage.

package main

import (
	"fmt"
	"io"
	"os"

	"github.com/nabbar/golib/file/progress"
)

func main() {
	// Create a test file first
	testFile := "/tmp/progress-example.txt"
	if err := os.WriteFile(testFile, []byte("Hello, World!"), 0644); err != nil {
		fmt.Printf("Setup error: %v\n", err)
		return
	}
	defer os.Remove(testFile)

	// Open file with progress tracking
	p, err := progress.Open(testFile)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer p.Close()

	// Read data
	data, err := io.ReadAll(p)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Read: %s\n", string(data))
}
Output:

Read: Hello, World!

func Temp

func Temp(pattern string) (Progress, error)

Temp creates a new temporary file with the given pattern and returns a Progress interface.

The pattern should follow the rules of the os.CreateTemp function.

The function returns an error if the file cannot be created.

The Progress interface is used to track the progress of the file when it is being read from or written to.

The function returns an error if the progress cannot be set.

The function is used to create temporary files that are automatically deleted when they are closed.

Example

ExampleTemp demonstrates creating a temporary file with automatic cleanup. This is the simplest use case - a temporary file that is auto-deleted on close.

package main

import (
	"fmt"

	"github.com/nabbar/golib/file/progress"
)

func main() {
	// Create temporary file
	p, err := progress.Temp("example-*.tmp")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer p.Close() // Automatically deleted because IsTemp() == true

	// Write some data
	data := []byte("temporary data")
	n, err := p.Write(data)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Wrote %d bytes to temporary file\n", n)
}
Output:

Wrote 14 bytes to temporary file

func Unique

func Unique(basePath, pattern string) (Progress, error)

Unique creates a new unique file in the given base path with the given pattern.

The pattern should follow the rules of the os.CreateTemp function.

The function returns a Progress interface and an error. The Progress interface is used to track the progress of the file when it is being read from or written to.

The function returns an error if the file cannot be created.

Example

ExampleUnique demonstrates creating unique files with patterns.

package main

import (
	"fmt"

	"github.com/nabbar/golib/file/progress"
)

func main() {
	// Create unique file in /tmp
	p, err := progress.Unique("/tmp", "myapp-*.dat")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer p.CloseDelete() // Clean up

	// Write data
	p.Write([]byte("unique file content"))

	// Get file path
	path := p.Path()
	fmt.Printf("Created unique file: %v\n", path != "")

}
Output:

Created unique file: true

type TempFile added in v1.19.0

type TempFile interface {
	IsTemp() bool
}

Jump to

Keyboard shortcuts

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