feat: initial goget release — modern IPv6-first download utility

This commit is contained in:
2026-06-26 21:21:58 +02:00
commit 53db81e1f7
147 changed files with 45931 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
//go:build linux || freebsd
// +build linux freebsd
package core
import (
"time"
)
// TimeoutConfig configures smart timeout
type TimeoutConfig struct {
Min time.Duration
Max time.Duration
MinSpeedBytesPerSec int64
SafetyFactor float64
DefaultUnknown time.Duration
}
// DefaultTimeoutConfig returns default settings
func DefaultTimeoutConfig() *TimeoutConfig {
return &TimeoutConfig{
Min: 30 * time.Second,
Max: 24 * time.Hour,
MinSpeedBytesPerSec: 10 * 1024,
SafetyFactor: 3.0,
DefaultUnknown: 30 * time.Minute,
}
}
// CalculateTimeout calculates the recommended timeout
func (tc *TimeoutConfig) CalculateTimeout(fileSize int64, userOverride time.Duration) time.Duration {
if userOverride > 0 {
return userOverride
}
if fileSize <= 0 {
return tc.DefaultUnknown
}
expectedSeconds := float64(fileSize) / float64(tc.MinSpeedBytesPerSec)
calculated := time.Duration(expectedSeconds * tc.SafetyFactor * float64(time.Second))
if calculated < tc.Min {
return tc.Min
}
if calculated > tc.Max {
return tc.Max
}
return calculated
}
+65
View File
@@ -0,0 +1,65 @@
//go:build linux || freebsd
// +build linux freebsd
package core
import (
"context"
"time"
)
// RequestContext holds metadata about the request across layers
type RequestContext struct {
// Request ID for logging
RequestID string
// Start of operation
StartTime time.Time
// Protocols used in the chain
ProtocolChain []string
// IP address to which was connected
ConnectedIP string
// IP version (4 or 6)
IPVersion int
// Number of attempts
AttemptCount int
// Context for cancellation
Context context.Context
}
// NewRequestContext creates a new request context
func NewRequestContext(ctx context.Context, requestID string) *RequestContext {
return &RequestContext{
RequestID: requestID,
StartTime: time.Now(),
ProtocolChain: make([]string, 0),
IPVersion: 0,
AttemptCount: 0,
Context: ctx,
}
}
// Duration returns the duration of operation
func (rc *RequestContext) Duration() time.Duration {
return time.Since(rc.StartTime)
}
// AddProtocol adds a protocol to the chain
func (rc *RequestContext) AddProtocol(protocol string) {
rc.ProtocolChain = append(rc.ProtocolChain, protocol)
}
// IsCancelled returns whether the context was cancelled
func (rc *RequestContext) IsCancelled() bool {
select {
case <-rc.Context.Done():
return true
default:
return false
}
}
File diff suppressed because it is too large Load Diff
+135
View File
@@ -0,0 +1,135 @@
//go:build linux || freebsd
// +build linux freebsd
package core
import (
"fmt"
"net/url"
)
// ErrorType defines the error type
type ErrorType string
const (
ErrNetwork ErrorType = "NETWORK"
ErrProtocol ErrorType = "PROTOCOL"
ErrFile ErrorType = "FILE"
ErrTimeout ErrorType = "TIMEOUT"
ErrAuth ErrorType = "AUTH"
ErrChecksum ErrorType = "CHECKSUM"
ErrUnsupported ErrorType = "UNSUPPORTED"
ErrCancelled ErrorType = "CANCELLED"
)
// GogetError is a structured error with context
type GogetError struct {
Type ErrorType
Message string
Cause error
URL string
Hint string // Actionable suggestion for the user
Details map[string]string
}
func (e *GogetError) Error() string {
var msg string
if e.Cause != nil {
msg = fmt.Sprintf("[%s] %s: %v", e.Type, e.Message, e.Cause)
} else {
msg = fmt.Sprintf("[%s] %s", e.Type, e.Message)
}
if e.Hint != "" {
msg += fmt.Sprintf(" (hint: %s)", e.Hint)
}
return msg
}
func (e *GogetError) Unwrap() error {
return e.Cause
}
// GetDetail returns a value from the Details map, or an empty string if the key is absent.
func (e *GogetError) GetDetail(key string) string {
if e.Details == nil {
return ""
}
return e.Details[key]
}
// NewNetworkError creates a network error
func NewNetworkError(msg string, cause error, url string) *GogetError {
return &GogetError{
Type: ErrNetwork,
Message: msg,
Cause: cause,
URL: url,
}
}
// NewProtocolError creates a protocol error
func NewProtocolError(msg string, cause error, url string) *GogetError {
return &GogetError{
Type: ErrProtocol,
Message: msg,
Cause: cause,
URL: url,
}
}
// NewFileError creates a file error
func NewFileError(msg string, cause error) *GogetError {
return &GogetError{
Type: ErrFile,
Message: msg,
Cause: cause,
}
}
// NewTimeoutError creates a timeout error
func NewTimeoutError(msg string, url string) *GogetError {
return &GogetError{
Type: ErrTimeout,
Message: msg,
URL: url,
Hint: "increase --max-time or --connect-timeout, or check network connectivity",
}
}
// NewChecksumError creates a checksum error
func NewChecksumError(expected, actual string) *GogetError {
return &GogetError{
Type: ErrChecksum,
Message: fmt.Sprintf("checksum mismatch: expected %s, got %s", expected, actual),
Hint: "verify the file integrity with an external checksum tool or re-download",
Details: map[string]string{
"expected": expected,
"actual": actual,
},
}
}
// NewUnsupportedError creates an unsupported operation error
func NewUnsupportedError(msg string) *GogetError {
return &GogetError{
Type: ErrUnsupported,
Message: msg,
}
}
// WithHint returns the error with the hint set. Use for adding contextual,
// actionable suggestions to an existing error without creating a new one.
func (e *GogetError) WithHint(hint string) *GogetError {
e.Hint = hint
return e
}
// SafeURL returns the URL string with credentials removed for safe error logging.
func SafeURL(u *url.URL) string {
if u == nil {
return ""
}
clean := *u
clean.User = nil
return clean.String()
}
+174
View File
@@ -0,0 +1,174 @@
//go:build linux || freebsd
// +build linux freebsd
package core
import (
"context"
"fmt"
"io"
"net/url"
"time"
)
// ParallelConfig configures parallel downloading
type ParallelConfig struct {
Connections int
MinSize int64
MinChunkSize int64
MaxChunkSize int64
}
// DefaultParallelConfig returns default settings
func DefaultParallelConfig() *ParallelConfig {
return &ParallelConfig{
Connections: 0,
MinSize: 100 * 1024 * 1024,
MinChunkSize: 1 * 1024 * 1024,
MaxChunkSize: 50 * 1024 * 1024,
}
}
// GetConnections returns the number of connections
func (pc *ParallelConfig) GetConnections(fileSize int64) int {
if pc.Connections > 0 {
return pc.Connections
}
if fileSize >= pc.MinSize {
return 4
}
return 1
}
// ChunkInfo represents one file segment
type ChunkInfo struct {
ID int
Start int64
End int64
Downloaded int64
Status int
Error error
}
// ResumeInfo contains metadata for continuation
type ResumeInfo struct {
DownloadedBytes int64
ETag string
LastModified string
URL string
LastWrite time.Time
Chunks map[int]int64
}
// DownloadRequest represents a download request
type DownloadRequest struct {
URL *url.URL
Output string
Writer io.Writer
Resume bool
Timeout time.Duration
Verbose bool
DebugTransport bool
Checksum string
Headers map[string]string
Proxy string
AutoDecompress bool
ProgressCallback func(current, total int64, speed float64)
ResumeInfo *ResumeInfo
Parallel *ParallelConfig
Recursive bool
RecursiveParallel int // worker pool size for recursive downloads (0 = sequential)
DryRun bool // list what would be downloaded, do not write any files
MaxDepth int
MaxFileSize int64 // max file size to download (0 = unlimited)
AcceptPatterns []string // glob patterns for accepted files (e.g., "*.pdf,*.html")
RejectPatterns []string // glob patterns for rejected files (e.g., "*.tmp,*.bak")
KeepTimestamps bool // set file modification time from server response
Ctx context.Context
SSHKnownHosts string // path to known_hosts file (SFTP)
SSHInsecure bool // skip SSH host key verification (SFTP)
}
// DownloadResult represents a download result
type DownloadResult struct {
BytesDownloaded int64
TotalSize int64
Duration time.Duration
Protocol string
IPVersion int
Speed float64
OutputPath string
Checksum string
Resumed bool
Parallel bool
ChunksCount int
HTTPStatusCode int // HTTP status code from response
TraceTimings *TraceTimings // HTTP tracing breakdown
}
// TraceTimings holds HTTP request timing breakdown.
type TraceTimings struct {
DNSLookup time.Duration
TCPConnect time.Duration
TLSHandshake time.Duration
TTFB time.Duration // Time To First Byte
Total time.Duration
}
func (tt *TraceTimings) String() string {
return fmt.Sprintf(
"DNS: %v | TCP: %v | TLS: %v | TTFB: %v | Total: %v",
tt.DNSLookup.Round(time.Millisecond),
tt.TCPConnect.Round(time.Millisecond),
tt.TLSHandshake.Round(time.Millisecond),
tt.TTFB.Round(time.Millisecond),
tt.Total.Round(time.Millisecond),
)
}
// Protocol is the interface for protocols
type Protocol interface {
Scheme() string
CanHandle(url *url.URL) bool
Download(ctx context.Context, req *DownloadRequest) (*DownloadResult, error)
Capabilities() []string
SupportsResume() bool
SupportsCompression() bool
SupportsParallel() bool
SupportsRecursive() bool
}
// UploadRequest represents an upload request
type UploadRequest struct {
URL *url.URL
Input string
Reader io.Reader
Timeout time.Duration
Verbose bool
Headers map[string]string
Proxy string
Ctx context.Context
}
// UploadResult represents an upload result
type UploadResult struct {
BytesUploaded int64
Duration time.Duration
Protocol string
ResultURL string
}
// Uploader is the interface for protocols with upload support
type Uploader interface {
Upload(ctx context.Context, req *UploadRequest) (*UploadResult, error)
SupportsUpload() bool
}
// ProgressCallback is a function for progress reporting
type ProgressCallback func(current, total int64, speed float64)
// ProgressWriter is a writer with progress support
type ProgressWriter interface {
io.Writer
SetProgressCallback(cb ProgressCallback)
}
+10
View File
@@ -0,0 +1,10 @@
//go:build linux || freebsd
// Package version exposes the goget release identity.
package core
// Name is the program name reported by `--version` and in log lines.
const Name = "goget"
// Version is the goget release version. Follows SemVer.
var Version = "1.0.0"