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 ¶
- type Engine
- func (e *Engine) Close() error
- func (e *Engine) Download(taskID, url string, headers map[string]string, conns int)
- func (e *Engine) DownloadTo(taskID, url string, headers map[string]string, conns int, dir string)
- func (e *Engine) DownloadTorrent(taskID, uri, dir string, sel []int)
- func (e *Engine) DownloadVia(taskID, url string, headers map[string]string, conns int, dir string, ...)
- func (e *Engine) Pause(taskID string)
- func (e *Engine) Remove(taskID string, deleteFiles bool)
- func (e *Engine) Resume(taskID string)
- func (e *Engine) SetMetadataTimeout(d time.Duration)
- func (e *Engine) SetTorrentConfig(port int, seedRatio float64, seedDurationSeconds int) error
- func (e *Engine) Start(j Job)
- func (e *Engine) UseProxy(hostPort string) error
- type Job
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 ¶
New boots an embedded Gopeed downloader that saves into dir and reports per-task changes through onUpdate.
func (*Engine) Close ¶
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 ¶
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 ¶
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 ¶
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) Remove ¶
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) SetMetadataTimeout ¶
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 ¶
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.
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.