Skip to content

Commit 256dba0

Browse files
committed
refactor: harden and modernize rtapi
1 parent 7e83be8 commit 256dba0

8 files changed

Lines changed: 988 additions & 1107 deletions

File tree

.github/workflows/ci.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
env:
11+
GOWORK: "off"
12+
13+
jobs:
14+
verify:
15+
name: Go ${{ matrix.go }}
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 10
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
go: ["1.26.7", "1.27.0"]
22+
steps:
23+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24+
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
25+
with:
26+
go-version: ${{ matrix.go }}
27+
cache: false
28+
- name: Verify module
29+
run: |
30+
go mod tidy -diff
31+
go mod verify
32+
- name: Test, vet, and build
33+
run: |
34+
go test -count=1 ./...
35+
go vet ./...
36+
go build ./...
37+
- name: Race detector
38+
if: matrix.go == '1.26.7'
39+
run: go test -race -count=1 ./...
40+
- name: 32-bit tests
41+
if: matrix.go == '1.26.7'
42+
run: GOARCH=386 CGO_ENABLED=0 go test -count=1 ./...
43+
- name: Vulnerability scan
44+
if: matrix.go == '1.26.7'
45+
uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # v1.1.0
46+
with:
47+
repo-checkout: false
48+
go-version-input: "1.26.7"
49+
go-package: ./...

README.md

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,82 @@
11
# rtapi
2-
Raw rTorrent XML-RPC in Go, Written for [rtelegram](https://github.com/pyed/rtelegram)
2+
3+
`rtapi` is a small Go client for rTorrent's XML-RPC interface over SCGI. It is
4+
the library used by [rtelegram](https://github.com/pyed/rtelegram).
35

46
## Requirements
5-
* [`rTorrent`](https://github.com/rakshasa/rtorrent) compiled with `--with-xmlrpc-c`.
6-
* `scgi_port = localhost:5000` in your `.rtorrent.rc`
77

8-
## How to get
9-
`go get github.com/pyed/rtapi`
8+
- Go 1.26 or newer.
9+
- rTorrent built with XML-RPC support.
10+
- A local SCGI endpoint such as a protected Unix socket or
11+
`scgi_port = 127.0.0.1:5000`.
12+
13+
The rTorrent RPC endpoint has no authentication and exposes powerful methods.
14+
Prefer a permission-protected Unix socket. Never expose SCGI directly to an
15+
untrusted network; see rTorrent's
16+
[official XML-RPC security guidance](https://github.com/rakshasa/rtorrent-doc/blob/master/RPC-Setup-XMLRPC.md).
1017

11-
## How to use
12-
``` go
18+
## Install
19+
20+
```sh
21+
go get github.com/pyed/rtapi@latest
22+
```
23+
24+
## Example
25+
26+
```go
1327
package main
1428

1529
import (
1630
"fmt"
31+
"log"
1732

1833
"github.com/pyed/rtapi"
1934
)
2035

2136
func main() {
22-
rt, err := rtapi.NewRtorrent("localhost:5000") // Or /path/to/socket for "scgi_local".
37+
rt, err := rtapi.NewRtorrent("/run/user/1000/rtorrent.sock")
2338
if err != nil {
24-
// ...
39+
log.Fatal(err)
2540
}
2641

27-
// Get torrents
2842
torrents, err := rt.Torrents()
2943
if err != nil {
30-
// ...
44+
log.Fatal(err)
3145
}
32-
33-
fmt.Println("Number of torrents:", len(torrents))
34-
35-
for _, t := range torrents {
36-
fmt.Println(t.Name)
46+
for _, torrent := range torrents {
47+
fmt.Printf("%s: %d/%d bytes\n", torrent.Name, torrent.Completed, torrent.Size)
3748
}
3849
}
3950
```
51+
52+
TCP addresses such as `127.0.0.1:5000` are also accepted. Every SCGI request is
53+
bounded by `rtapi.DefaultTimeout` (30 seconds) unless `Rtorrent.Timeout` is set.
54+
Responses default to a 16 MiB safety bound; set `Rtorrent.MaxResponseSize` when
55+
a legitimately large library needs more. Transport errors, malformed responses,
56+
and XML-RPC faults are returned to the caller; `errors.As` can inspect an
57+
`*rtapi.XMLRPCFault`.
58+
59+
## Important APIs and compatibility
60+
61+
- Transfer fields (`Size`, `Completed`, and `UpTotal`) contain exact byte counts.
62+
- `SpeedsWithError` reports failures. `Speeds` remains as a deprecated
63+
compatibility shim that cannot distinguish failure from zero traffic.
64+
- `DownloadRaw` loads torrent bytes directly, avoiding credential-bearing
65+
intermediary URLs. `DownloadWithOptions` remains available for URL loading.
66+
- `Torrents.Sort` takes an explicit `rtapi.Sorting` value. The unsafe
67+
process-global `CurrentSorting` variable was removed; call `Sort` on each
68+
returned value instead.
69+
- `DeleteMetadata` erases metadata only after rTorrent acknowledges the RPC.
70+
The older `Delete(false, ...)` form remains as a deprecated compatibility shim.
71+
- `Delete(true, ...)` returns `ErrUnsafeDataDelete` before any RPC or local
72+
filesystem access. Data belongs to the rTorrent host; an application that
73+
offers data deletion must enforce its own explicit local root and containment
74+
policy.
75+
76+
## Development
77+
78+
```sh
79+
go test ./...
80+
go vet ./...
81+
go build ./...
82+
```

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/pyed/rtapi
22

3-
go 1.24.3
3+
go 1.26.0

public_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package rtapi_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/pyed/rtapi"
7+
)
8+
9+
var _ interface {
10+
Stats() (*rtapi.Stats, error)
11+
DownloadRaw([]byte, *rtapi.DotTorrentWithOptions) error
12+
DeleteMetadata(...*rtapi.Torrent) error
13+
SpeedsWithError() (uint64, uint64, error)
14+
} = (*rtapi.Rtorrent)(nil)
15+
16+
func TestPublicTypesAreNameable(t *testing.T) {
17+
var sorting rtapi.Sorting = rtapi.ByName
18+
var stats *rtapi.Stats
19+
if sorting != rtapi.ByName || stats != nil {
20+
t.Fatal("unexpected public type values")
21+
}
22+
}

0 commit comments

Comments
 (0)