upload

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package upload orchestrates Gumroad's S3 multipart upload flow: presign → parallel part PUTs → complete, with best-effort abort on failure.

Index

Constants

View Source
const DefaultConcurrency = 4

DefaultConcurrency is the default number of part uploads in flight.

View Source
const DefaultPartTimeout = 60 * time.Minute

DefaultPartTimeout bounds each part PUT attempt so a stalled S3 peer cannot hang the upload indefinitely. 60 minutes sets a lower throughput bound of roughly 230 kbps on a 100 MB part, which covers healthy slow links (mobile data, congested Wi-Fi) with margin. Uploads slower than that should set Options.PartTimeout explicitly.

View Source
const MaxFileSize int64 = 20 * 1024 * 1024 * 1024

MaxFileSize is the server-side upload cap.

View Source
const PartSize int64 = 100 * 1024 * 1024

PartSize is the fixed multipart part size the Gumroad API expects.

Variables

View Source
var ErrCompleteStateUnknown = errors.New("upload state unknown; /files/complete did not return a definitive response")

ErrCompleteStateUnknown is the sentinel matched by errors.Is on any error returned from Upload where /files/complete did not produce a definitive response (5xx, transient 4xx, transport, parse). Callers MUST verify server-side state before retrying — blind retry risks duplicate files. The concrete error implements *UnknownStateError and carries the handles needed for that verification.

View Source
var ErrPresignExpired = errors.New("presigned URL expired; restart the upload")

ErrPresignExpired is returned when S3 rejects a part PUT because the presigned URL has expired. The caller must retry the whole upload; the orchestrator does not refresh URLs mid-flight.

Functions

func Upload

func Upload(ctx context.Context, client *api.Client, path string, opts Options) (string, error)

Upload runs the full multipart flow and returns the canonical file_url. presign, part PUTs, and /files/complete are bounded by ctx; /files/abort runs under an independent 10-second timeout so cleanup still fires when ctx is already canceled, which also means ctx cannot shorten or skip it. The *api.Client supplies auth and base URL only; its baked-in context is ignored so ctx bounds all in-flight work except that abort cleanup.

Types

type CleanupFailedError

type CleanupFailedError struct {
	UploadID string
	Key      string
	Cause    error
}

CleanupFailedError wraps a /files/abort failure together with the handles needed to retry cleanup (e.g. from a separate job). It is always returned joined with the original upload error via errors.Join, so callers keep access to both: errors.Is on the upload cause, errors.As on this type for the orphan identifiers.

func (*CleanupFailedError) Error

func (e *CleanupFailedError) Error() string

func (*CleanupFailedError) Unwrap

func (e *CleanupFailedError) Unwrap() error

type CompletedPart

type CompletedPart struct {
	PartNumber int
	ETag       string
}

CompletedPart is one entry of the manifest passed to /files/complete. Callers use these to retry finalize after an ambiguous failure.

type Options

type Options struct {
	// Filename overrides the display filename sent to presign. Defaults to
	// filepath.Base(path) when empty.
	Filename string

	// HTTPClient is used only for S3 part PUTs. Defaults to a client with no
	// overall timeout (each PUT's lifetime is bounded by context and the
	// presigned URL's 15-minute expiry). Tests inject this to redirect S3
	// traffic at an in-process httptest.Server.
	HTTPClient *http.Client

	// Concurrency bounds the number of part PUTs in flight. Defaults to
	// DefaultConcurrency. Values <= 0 use the default.
	Concurrency int

	// Progress, when non-nil, is called with the cumulative number of bytes
	// uploaded so far. Invocations are serialized and monotonic — callbacks
	// never run concurrently and the value never goes backwards. A slow or
	// blocked callback does NOT stall part uploads; it also does not keep
	// Upload from returning. The last callback may therefore run after
	// Upload has already returned — including after it returns an error.
	Progress func(bytesUploaded int64)

	// PartTimeout bounds each part PUT attempt (including retries). Zero
	// means DefaultPartTimeout. Set to a negative value to disable; negative
	// timeouts let a stalled S3 peer hang the upload indefinitely.
	PartTimeout time.Duration
}

Options configures Describe and Upload.

type Plan

type Plan struct {
	Path      string
	Filename  string
	Size      int64
	PartSize  int64
	PartCount int
}

Plan is the preflight view of a planned upload. It is returned by Describe and is suitable for --dry-run rendering; it makes no network calls.

func Describe

func Describe(path string, opts Options) (Plan, error)

Describe performs local-only preflight: it stats the file, validates size bounds, and computes the part count. It never touches the network.

type UnknownStateError

type UnknownStateError struct {
	FileURL        string
	UploadID       string
	Key            string
	CompletedParts []CompletedPart
	Cause          error
}

UnknownStateError is returned from Upload when the complete phase failed ambiguously. It carries the identifiers a caller needs to reconcile: the canonical file_url (to check whether the server committed — empty if the presign response did not include it), the upload_id/key (to abort the orphan later if it did not), and the completed part manifest so the caller can retry /files/complete without re-uploading the file.

func (*UnknownStateError) Error

func (e *UnknownStateError) Error() string

func (*UnknownStateError) Is

func (e *UnknownStateError) Is(target error) bool

func (*UnknownStateError) Unwrap

func (e *UnknownStateError) Unwrap() error

Jump to

Keyboard shortcuts

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