transfer

package
v0.97.2 Latest Latest
Warning

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

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package transfer is a small in-memory tracker for long-running file move/copy operations (post-download move, Local-tab move, AI/promote), so the UI can show ONE consistent progress pattern for all of them: X/Y files, bytes done/total, transfer rate and ETA. It is the move-side analogue of internal/localstream (which meters streaming reads) and reuses the same windowed-rate idea. State is in-memory and lost on restart — for durable resume of the post-download move, see downloads.StatusMoving / RescueStuckMoving.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ProgressReader

func ProgressReader(r io.Reader, onBytes func(int64)) io.Reader

ProgressReader wraps r so each Read reports the byte count via onBytes — drop-in for io.Copy(dst, ProgressReader(src, job.AddBytes)).

func ProgressReaderCtx

func ProgressReaderCtx(ctx context.Context, r io.Reader, onBytes func(int64)) io.Reader

ProgressReaderCtx is ProgressReader that also aborts the copy when ctx is canceled (returns ctx.Err() from Read), so a Tracker.Cancel stops an in-progress file copy mid-stream. nil ctx → no cancellation (plain progress).

Types

type Job

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

Job is one tracked move/copy operation. Safe for concurrent use: producers call AddBytes/FileDone from the copy loop while the API reads Snapshot.

func (*Job) AddBytes

func (j *Job) AddBytes(n int64)

AddBytes records bytes transferred (drives progress + rate). No-op on <=0 or a nil Job (so producers can stay agnostic to whether tracking is wired).

func (*Job) AddBytesFunc

func (j *Job) AddBytesFunc() func(int64)

AddBytesFunc returns a callback for instrumenting an io.Copy via ProgressReader.

func (*Job) AddSkipped

func (j *Job) AddSkipped(n int64)

AddSkipped advances progress by bytes that were NOT copied now because they already existed at the destination (a resumed transfer skipping a file that a previous run finished). Counts toward done/total/progress but does NOT enter the rate window — otherwise skipping a large file instantly would spike the reported speed to an absurd value.

func (*Job) Canceled

func (j *Job) Canceled() bool

Canceled reports whether the job was canceled via Tracker.Cancel.

func (*Job) Context

func (j *Job) Context() context.Context

Context returns the job's cancellation context (Background for a nil job). A producer should pass it to its copy loop / retries so Tracker.Cancel aborts the work in flight.

func (*Job) Done

func (j *Job) Done()

Done marks the job successful. Fail marks it failed with the error message.

func (*Job) Fail

func (j *Job) Fail(err error)

func (*Job) FileDone

func (j *Job) FileDone()

FileDone increments the completed-files counter (X of Y).

func (*Job) ID

func (j *Job) ID() string

ID returns the job's stable identifier (empty for a nil Job), so a producer can hand it back to the client to correlate with the dock entry.

func (*Job) SetBytesTotal

func (j *Job) SetBytesTotal(total int64)

SetBytesTotal sets/raises the known total byte size (when discovered late).

func (*Job) Snapshot

func (j *Job) Snapshot() Snapshot

Snapshot returns the current immutable view.

type Pending

type Pending struct {
	ID      int64
	Kind    string // "promote" | "local-move" | "local-promote"
	Src     string
	Dst     string
	Payload string // kind-specific JSON (promote: downloadID/userID/keepSeeding)
}

Pending is a transfer (move/promote) whose copy must survive a restart. The intent is persisted BEFORE the copy starts and removed when it completes, so a boot reconciler can re-submit whatever was interrupted by a deploy/crash. The copy itself is resume-aware (copyFileAndRemove skips files already at the destination), so a re-submit only finishes what's left — it never re-copies what a previous run already moved.

type Snapshot

type Snapshot struct {
	ID         string  `json:"id"`
	Label      string  `json:"label"`
	Kind       string  `json:"kind"`
	UserID     int     `json:"userId,omitempty"`
	Status     Status  `json:"status"`
	FilesDone  int     `json:"filesDone"`
	FilesTotal int     `json:"filesTotal"`
	BytesDone  int64   `json:"bytesDone"`
	BytesTotal int64   `json:"bytesTotal"`
	RatePerSec int64   `json:"ratePerSec"`
	ETASeconds int     `json:"etaSeconds"`
	Progress   float64 `json:"progress"` // 0..1 (by bytes when known, else by files)
	Error      string  `json:"error,omitempty"`
	StartedAt  string  `json:"startedAt"`
}

Snapshot is the immutable, JSON-serializable view of a Job for the UI.

type Status

type Status string

Status is the lifecycle of a transfer Job.

const (
	// StatusQueued: registered but waiting for a concurrency slot (see Submit).
	StatusQueued   Status = "queued"
	StatusRunning  Status = "running"
	StatusDone     Status = "done"
	StatusFailed   Status = "failed"
	StatusCanceled Status = "canceled"
)

type Store

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

Store persists pending transfers in PostgreSQL (shared pool). nil-safe so callers can stay agnostic to whether persistence is wired.

func OpenStore

func OpenStore(pool *sql.DB) (*Store, error)

OpenStore wires the pending-transfers store onto the shared Postgres pool. Schema is applied centrally (internal/db migrations).

func (*Store) Add

func (s *Store) Add(p Pending) (int64, error)

Add persists a pending transfer and returns its id (0 when the store is nil).

func (*Store) Close

func (s *Store) Close()

Close is a no-op: the shared pool's lifecycle is owned by main.

func (*Store) List

func (s *Store) List() ([]Pending, error)

List returns every pending transfer in insertion order (oldest first).

func (*Store) Remove

func (s *Store) Remove(id int64) error

Remove deletes a pending transfer once its copy completed (no-op on id 0).

type Tracker

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

Tracker holds the active and recently-finished jobs.

func New

func New(maxConcurrent ...int) *Tracker

New returns a Tracker using the wall clock. maxConcurrent (optional, default 3) caps simultaneous Submit() transfers; excess ones queue.

func (*Tracker) ActiveCount

func (t *Tracker) ActiveCount() int

ActiveCount returns how many jobs are still Queued or Running. Used by the graceful shutdown to decide whether to wait for in-flight moves.

func (*Tracker) Cancel

func (t *Tracker) Cancel(id string, userID int, includeAll bool) bool

Cancel cancels a tracked job by ID. When includeAll is false, only a job owned by userID may be canceled (userID 0 jobs are system-owned and cancelable by anyone).

func (*Tracker) List

func (t *Tracker) List(userID int, includeAll bool) []Snapshot

List returns snapshots of jobs (newest first). When includeAll is false, only jobs owned by userID (plus system jobs with userID 0) are included.

func (*Tracker) Start

func (t *Tracker) Start(label, kind string, filesTotal int, bytesTotal int64) *Job

Start registers and returns a new RUNNING Job (no queueing). filesTotal/ bytesTotal may be 0 when unknown (rate/ETA degrade gracefully). userID=0 is anonymous/system; prefer StartFor when the owner is known.

func (*Tracker) StartFor added in v0.97.0

func (t *Tracker) StartFor(userID int, label, kind string, filesTotal int, bytesTotal int64) *Job

StartFor is Start with an explicit owner userID for multi-tenant filtering.

func (*Tracker) Submit

func (t *Tracker) Submit(label, kind string, filesTotal int, bytesTotal int64, fn func(*Job)) *Job

Submit registers a job that starts QUEUED and runs fn once a concurrency slot frees (bounded by maxConcurrent; excess jobs wait FIFO). fn receives the now- running Job and owns its terminal Done()/Fail(). Returns immediately. On a nil Tracker, fn runs unbounded in a goroutine with a nil Job (tracking disabled).

func (*Tracker) SubmitFor added in v0.97.0

func (t *Tracker) SubmitFor(userID int, label, kind string, filesTotal int, bytesTotal int64, fn func(*Job)) *Job

SubmitFor is Submit with an explicit owner userID.

func (*Tracker) WaitIdle

func (t *Tracker) WaitIdle(ctx context.Context) bool

WaitIdle blocks until no job is Queued/Running or ctx is done, polling every 200ms. Best-effort: anything still in flight when ctx expires is left to the durable boot rescue (downloads.RescueStuckMoving). Returns true if it drained.

Jump to

Keyboard shortcuts

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