engine

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package engine wraps the embedded Gopeed download library and reports task updates back to the app. Gopeed fetches bytes; the app owns state and UI.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Engine

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

func New

func New(dir string, onUpdate func(taskID string, u core.Update)) (*Engine, error)

New boots an embedded Gopeed downloader that saves into dir and reports per-task changes through onUpdate.

func (*Engine) Close

func (e *Engine) Close() error

Close stops every goroutine this engine started and then shuts the download library down, in that order.

THE ORDER IS THE POINT. Waiting first means nothing of ours is still calling into the library when it is torn down. The one goroutine that cannot be waited for is the bare resolve inside resolveTorrent, which is parked in the torrent client with no way to be interrupted - what releases that one is the d.Close() below, and its own caller has already stopped listening for it. It is idempotent because the app shuts down from more than one place - a second call must not close an already-closed channel or hand the download library a second Close.

THE WAIT IS BOUNDED, and the bound is not laziness. Every goroutine started here watches e.done and leaves promptly, except the one thing that cannot: a resolve already inside the download library, which takes no context. A shutdown that hung on one slow host would be a worse failure than the race this wait exists to close, so after closeGrace the library is shut down anyway - which is also what releases the resolve.

e.closed is set here, under e.mu, BEFORE anything else - not as a second copy of what e.done already says, but because it is the one half of the handshake Start's own e.mu.Lock can serialise against. e.done alone lets a caller observe "not closed yet" and then still lose the race to actually call wg.Add before this function's own wg.Wait begins - narrow, but a race detector run with real network activity behind it did find that window. Setting the flag under the same lock Start checks it under closes the window rather than shrinking it.

func (*Engine) Download

func (e *Engine) Download(taskID, url string, headers map[string]string, conns int)

Download resolves the direct URL (to learn name+size), then starts a task. It runs async so the caller (an HTTP handler) never blocks on the network.

This is the shape every download backend shares, and it deliberately carries no collision policy. Only this engine can be told the name it must write; a task handed to headless JD or TorBox is fetched in another process that names the file itself. Widening the shared shape would put a decision on every backend that all but one of them would drop on the floor.

func (*Engine) DownloadTo

func (e *Engine) DownloadTo(taskID, url string, headers map[string]string, conns int, dir string)

DownloadTo is Download with an explicit destination; an empty dir falls back to the engine's default. It routes nothing: the download goes out the way every download went out before connections could be named, which is over the loopback proxy UseProxy configured.

func (*Engine) DownloadTorrent

func (e *Engine) DownloadTorrent(taskID, uri, dir string, sel []int)

DownloadTorrent starts a magnet link or an uploaded .torrent, and is the torrent-shaped sibling of DownloadTo.

It is a NEW CALL SHAPE INTO THE SAME DOWNLOADER, not a second pipeline. DownloadTo is HTTP-shaped - a URL, a header map, a connection count - and none of those three mean anything to a swarm, while the two things that do (which files, which extra trackers) have nowhere to go in it. The downloader underneath is the identical one: gopeed's default FetchManagers already include its bt fetcher, so there is nothing to register and nothing to configure, only a request built the shape the bt fetcher reads.

sel names the files to fetch by their index in the resolved file list; nil fetches all of them. See core.SelectedTorrentIndices for where that list comes from.

func (*Engine) DownloadVia

func (e *Engine) DownloadVia(taskID, url string, headers map[string]string, conns int, dir string, route proxycfg.Route)

DownloadVia is DownloadTo carried on one named outbound connection.

This is the whole of per-download routing, and it is one field on the request because that is all gopeed needs: setupFetcher resolves the request's own proxy ahead of the global one ("task request proxy config has higher priority"), so a per-download connection is stated where the download is stated. No dialer, no second proxy, no chain - and the small version is the right one to build, because the large one was sized against a belief about this library that turned out to be wrong.

It has a cost, and the cost is real rather than theoretical: a request that names its own proxy no longer passes through the loopback proxy, and the loopback proxy is the only place this build can meter bytes. A routed download is therefore an unthrottled download until metering moves to a listener per task, which is the arrangement the bandwidth budget is designed around. Until then, routing wins over the speed limit for the downloads that are routed.

func (*Engine) Pause

func (e *Engine) Pause(taskID string)

func (*Engine) Remove

func (e *Engine) Remove(taskID string, deleteFiles bool)

Remove drops the task. deleteFiles also erases what was already written — used for a restart (the partial must go), never for tidying the list.

func (*Engine) Resume

func (e *Engine) Resume(taskID string)

func (*Engine) SetMetadataTimeout

func (e *Engine) SetMetadataTimeout(d time.Duration)

SetMetadataTimeout caps how long a magnet may spend waiting for the swarm to send its file list. Zero restores the built-in default.

func (*Engine) SetTorrentConfig

func (e *Engine) SetTorrentConfig(port int, seedRatio float64, seedDurationSeconds int) error

SetTorrentConfig pushes this instance's listen-port, seed-ratio and seed-duration policy into gopeed's own per-protocol config - DownloaderStoreConfig.ProtocolConfig["bt"] is the one surface that actually reaches a running torrent. See settings_torrent.go's own SeedRatioTarget/ SeedDurationSeconds/Port doc comments for why exactly these three fields and no others: UploadLimitKiBs and the DHT/PEX toggles have nowhere to go through this same surface, verified there, not re-verified here. Trackers/SeedKeep are read back and written back unchanged - this instance has no setting for either, and leaving them as whatever is already configured (gopeed's own zero-value defaults, since nothing on this side ever sets them) is correct, not a gap.

Call it once at boot and again on every settings save (mirroring UseProxy above) - but the two calls do not carry equal weight. seedRatio and seedDurationSeconds reach every torrent task from here on: gopeed's own Fetcher.Setup reads ProtocolConfig["bt"] fresh for each new task (internal/protocol/bt/fetcher.go's Setup calling ctl.GetConfig(&f.config)), so a later call here changes what the NEXT torrent added does, without reaching back into one already running (its own Fetcher already holds its own copy). port does not: internal/protocol/bt/fetcher.go's initClient reads f.config.ListenPort into the shared torrent.Client's own config exactly once, on the first torrent this process ever starts ("if client != nil { return }"), and never again - a later call here still saves the new port correctly, but it only takes if no torrent has started yet this process. That is gopeed's own constraint, not something a caller on this side of it can work around; see settings_torrent.go's Port doc comment for where it was first verified.

func (*Engine) Start

func (e *Engine) Start(j Job)

Start resolves the URL (to learn the name, the size and the shape of what is on the other end), settles where the file lands, and then starts the task.

func (*Engine) UseProxy

func (e *Engine) UseProxy(hostPort string) error

UseProxy routes every engine download through a proxy. KnightLoader points this at its own loopback proxy, which is where the speed limit is applied — the download library itself offers no rate-limit hook.

type Job

type Job struct {
	TaskID  string
	URL     string
	Headers map[string]string
	Conns   int
	// Dir is where the file lands; empty means the engine's own folder.
	Dir   string
	Route proxycfg.Route

	// TorrentSelect names which files of a multi-file torrent to fetch, by
	// index in the resolved file list. Nil fetches all of them, which is what
	// the download library reads an empty selection as. Ignored for every job
	// whose URL is not a torrent.
	TorrentSelect []int
	// Trackers are extra announce URLs to add to a torrent. Ignored for a
	// private torrent by the library itself, which is the correct behaviour and
	// not something this side has to remember.
	Trackers []string

	// Collision is what to do when the resolved name is already taken. EMPTY
	// MEANS NO POLICY AT ALL, which is not what the collide package reads it as:
	// there an empty policy is its own default, Rename. The difference is
	// deliberate and it is why this is not passed straight through - the older
	// entry points above set no policy, and folding them onto a default would
	// give every caller that never asked for one a silent rename.
	Collision collide.Policy
	// MaxCollisionAttempts caps how many counted names a rename tries. Zero means
	// the collide package's own cap.
	MaxCollisionAttempts int
}

Job is one download as the engine takes it.

A struct rather than a longer parameter list, because the list had reached six and the two this wave adds are a policy and a cap - the kind that compile perfectly well in the wrong order.

Jump to

Keyboard shortcuts

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