Documentation
¶
Overview ¶
Package upload orchestrates Gumroad's S3 multipart upload flow: presign → parallel part PUTs → complete, with best-effort abort on failure.
Index ¶
Constants ¶
const DefaultConcurrency = 4
DefaultConcurrency is the default number of part uploads in flight.
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.
const MaxFileSize int64 = 20 * 1024 * 1024 * 1024
MaxFileSize is the server-side upload cap.
const PartSize int64 = 100 * 1024 * 1024
PartSize is the fixed multipart part size the Gumroad API expects.
Variables ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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.
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