1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

Vendoring dependencies

This commit is contained in:
Chris Cummer
2019-07-15 09:06:49 -07:00
parent 122ea31e45
commit 2b19ccea1c
1980 changed files with 805966 additions and 0 deletions

22
vendor/github.com/hekmon/transmissionrpc/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,22 @@
# VSCode local settings
.vscode
# Vendoring
vendor/
## Github ignore
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/

28
vendor/github.com/hekmon/transmissionrpc/Gopkg.lock generated vendored Normal file
View File

@@ -0,0 +1,28 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:af105c7c5dc0b4ae41991f122cae860b9600f7d226072c2a83127048c991660c"
name = "github.com/hashicorp/go-cleanhttp"
packages = ["."]
pruneopts = "UT"
revision = "eda1e5db218aad1db63ca4642c8906b26bcf2744"
version = "v0.5.1"
[[projects]]
digest = "1:9715d8b13bda6dada8327ad7d4791ec8d4c2be49a660c8a9293361f80daac913"
name = "github.com/hekmon/cunits"
packages = ["."]
pruneopts = "UT"
revision = "3c45cb0124f153c1e8bbd2e965cf657c59331d49"
version = "v2.0.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/hashicorp/go-cleanhttp",
"github.com/hekmon/cunits",
]
solver-name = "gps-cdcl"
solver-version = 1

38
vendor/github.com/hekmon/transmissionrpc/Gopkg.toml generated vendored Normal file
View File

@@ -0,0 +1,38 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/hekmon/cunits"
version = "2.0.0"
[prune]
go-tests = true
unused-packages = true
[[constraint]]
name = "github.com/hashicorp/go-cleanhttp"
version = "0.5.1"

21
vendor/github.com/hekmon/transmissionrpc/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Edouard Hur
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

387
vendor/github.com/hekmon/transmissionrpc/README.md generated vendored Normal file
View File

@@ -0,0 +1,387 @@
# TransmissionRPC
[![GoDoc](https://godoc.org/github.com/hekmon/transmissionrpc?status.svg)](https://godoc.org/github.com/hekmon/transmissionrpc) [![Go Report Card](https://goreportcard.com/badge/github.com/hekmon/transmissionrpc)](https://goreportcard.com/report/github.com/hekmon/transmissionrpc)
Golang bindings to Transmission (bittorent) RPC interface (Work in Progress).
Even if there is some high level wrappers/helpers, the goal of this lib is to stay close to the original API in terms of methods and payloads while enhancing certain types to be more "golangish": timestamps are converted from/to time.Time, numeric durations in time.Duration, booleans in numeric form are converted to real bool, etc...
Also payload generation aims to be precise: when several values can be added to a payload, only instanciated values will be forwarded (and kept !) to the final payload. This means that the default JSON marshalling (with omitempty) can't always be used and therefor a manual, reflect based, approach is used to build the final payload and accurately send what the user have instanciated, even if a value is at its default type value.
This lib follow the [transmission v15 RPC specification](https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L639).
## Getting started
First the main client object must be instantiated with [New()](https://godoc.org/github.com/hekmon/transmissionrpc#New). In its basic form only host/ip, username and password must be provided. Default will apply for port (`9091`) rpc URI (`/transmission/rpc`) and others values.
```golang
transmissionbt := transmissionrpc.New("127.0.0.1", "rpcuser", "rpcpass", nil)
```
But advanced values can also be configured to your liking using [AdvancedConfig](https://godoc.org/github.com/hekmon/transmissionrpc#AdvancedConfig).
Each value of `AdvancedConfig` with a type default value will be replaced by the lib default value, so you can set only the ones you want:
```golang
transmissionbt := transmissionrpc.New("bt.mydomain.net", "rpcuser", "rpcpass",
&transmissionrpc.AdvancedConfig{
HTTPS: true,
Port: 443,
})
```
The remote RPC version can be checked against this library before starting to operate:
```golang
ok, serverVersion, serverMinimumVersion, err := transmission.RPCVersion()
if err != nil {
panic(err)
}
if !ok {
panic(fmt.Sprintf("Remote transmission RPC version (v%d) is incompatible with the transmission library (v%d): remote needs at least v%d",
serverVersion, transmissionrpc.RPCVersion, serverMinimumVersion))
}
fmt.Printf("Remote transmission RPC version (v%d) is compatible with our transmissionrpc library (v%d)\n",
serverVersion, transmissionrpc.RPCVersion)
```
## Features
* [Torrent Requests](#torrent-requests)
* [Torrent Action Requests](#torrent-action-requests)
* [x] torrent-start
* [x] torrent-start-now
* [x] torrent-stop
* [x] torrent-verify
* [x] torrent-reannounce
* [Torrent Mutators](#torrent-mutators)
* [x] torrent-set
* [Torrent Accessors](#torrent-accessors)
* [x] torrent-get
* [Adding a Torrent](#adding-a-torrent)
* [x] torrent-add
* [Removing a Torrent](#removing-a-torrent)
* [x] torrent-remove
* [Moving a Torrent](#moving-a-torrent)
* [x] torrent-set-location
* [Renaming a Torrent path](#renaming-a-torrent-path)
* [x] torrent-rename-path
* [Session Requests](#session-requests)
* [Session Arguments](#session-arguments)
* [x] session-set
* [x] session-get
* [Session Statistics](#session-statistics)
* [x] session-stats
* [Blocklist](#blocklist)
* [ ] blocklist-update
* [Port Checking](#port-checking)
* [x] port-test
* [Session Shutdown](#session-shutdown)
* [ ] session-close
* [Queue Movement Requests](#queue-movement-requests)
* [ ] queue-move-top
* [ ] queue-move-up
* [ ] queue-move-down
* [ ] queue-move-bottom
* [Free Space](#free-space)
* [x] free-space
### Torrent Requests
#### Torrent Action Requests
Each rpc methods here can work with ID list, hash list or `recently-active` magic word. Therefor, there is 3 golang method variants for each of them.
```golang
transmissionbt.TorrentXXXXIDs(...)
transmissionbt.TorrentXXXXHashes(...)
transmissionbt.TorrentXXXXRecentlyActive()
```
* torrent-start
Check [TorrentStartIDs()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStartIDs), [TorrentStartHashes()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStartHashes) and [TorrentStartRecentlyActive()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStartRecentlyActive).
Ex:
```golang
err := transmissionbt.TorrentStartIDs([]int64{55})
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Println("yay")
}
```
* torrent-start-now
Check [TorrentStartNowIDs()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStartNowIDs), [TorrentStartNowHashes()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStartNowHashes) and [TorrentStartNowRecentlyActive()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStartNowRecentlyActive).
Ex:
```golang
err := transmissionbt.TorrentStartNowHashes([]string{"f07e0b0584745b7bcb35e98097488d34e68623d0"})
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Println("yay")
}
```
* torrent-stop
Check [TorrentStopIDs()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStopIDs), [TorrentStopHashes()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStopHashes) and [TorrentStopRecentlyActive()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentStopRecentlyActive).
Ex:
```golang
err := transmissionbt.TorrentStopIDs([]int64{55})
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Println("yay")
}
```
* torrent-verify
Check [TorrentVerifyIDs()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentVerifyIDs), [TorrentVerifyHashes()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentVerifyHashes) and [TorrentVerifyRecentlyActive()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentVerifyRecentlyActive).
Ex:
```golang
err := transmissionbt.TorrentVerifyHashes([]string{"f07e0b0584745b7bcb35e98097488d34e68623d0"})
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Println("yay")
}
```
* torrent-reannounce
Check [TorrentReannounceIDs()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentReannounceIDs), [TorrentReannounceHashes()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentReannounceHashes) and [TorrentReannounceRecentlyActive()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentReannounceRecentlyActive).
Ex:
```golang
err := transmissionbt.TorrentReannounceRecentlyActive()
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Println("yay")
}
```
#### Torrent Mutators
* torrent-set
Mapped as [TorrentSet()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentSet).
Ex: apply a 1 MB/s limit to a torrent.
```golang
uploadLimited := true
uploadLimitKBps := int64(1000)
err := transmissionbt.TorrentSet(&transmissionrpc.TorrentSetPayload{
IDs: []int64{55},
UploadLimited: &uploadLimited,
UploadLimit: &uploadLimitKBps,
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Println("yay")
}
```
There is a lot more [mutators](https://godoc.org/github.com/hekmon/transmissionrpc#TorrentSetPayload) available.
#### Torrent Accessors
* torrent-get
All fields for all torrents with [TorrentGetAll()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentGetAll):
```golang
torrents, err := transmissionbt.TorrentGetAll()
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Println(torrents) // meh it's full of pointers
}
```
All fields for a restricted list of ids with [TorrentGetAllFor()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentGetAll):
```golang
torrents, err := transmissionbt.TorrentGetAllFor([]int64{31})
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Println(torrents) // meh it's still full of pointers
}
```
Some fields for some torrents with the low level accessor [TorrentGet()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentGet):
```golang
torrents, err := transmissionbt.TorrentGet([]string{"status"}, []int64{54, 55})
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
for _, torrent := range torrents {
fmt.Println(torrent.Status) // the only instanciated field, as requested
}
}
```
Some fields for all torrents, still with the low level accessor [TorrentGet()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentGet):
```golang
torrents, err := transmissionbt.TorrentGet([]string{"id", "name", "hashString"}, nil)
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
for _, torrent := range torrents {
fmt.Println(torrent.ID)
fmt.Println(torrent.Name)
fmt.Println(torrent.HashString)
}
}
```
Valid fields name can be found as JSON tag on the [Torrent](https://godoc.org/github.com/hekmon/transmissionrpc#Torrent) struct.
#### Adding a Torrent
* torrent-add
Adding a torrent from a file (using [TorrentAddFile](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentAddFile) wrapper):
```golang
filepath := "/home/hekmon/Downloads/ubuntu-17.10.1-desktop-amd64.iso.torrent"
torrent, err := transmissionbt.TorrentAddFile(filepath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
// Only 3 fields will be returned/set in the Torrent struct
fmt.Println(*torrent.ID)
fmt.Println(*torrent.Name)
fmt.Println(*torrent.HashString)
}
```
Adding a torrent from an URL (ex: a magnet) with the real [TorrentAdd](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentAdd) method:
```golang
magnet := "magnet:?xt=urn:btih:f07e0b0584745b7bcb35e98097488d34e68623d0&dn=ubuntu-17.10.1-desktop-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce"
torrent, err := btserv.TorrentAdd(&transmissionrpc.TorrentAddPayload{
Filename: &magnet,
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
// Only 3 fields will be returned/set in the Torrent struct
fmt.Println(*torrent.ID)
fmt.Println(*torrent.Name)
fmt.Println(*torrent.HashString)
}
```
Which would output:
```raw
55
ubuntu-17.10.1-desktop-amd64.iso
f07e0b0584745b7bcb35e98097488d34e68623d0
```
#### Removing a Torrent
* torrent-remove
Mapped as [TorrentRemove()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentRemove).
#### Moving a Torrent
* torrent-set-location
Mapped as [TorrentSetLocation()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentSetLocation).
#### Renaming a Torrent path
* torrent-rename-path
Mapped as [TorrentRenamePath()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.TorrentRenamePath).
### Session Requests
#### Session Arguments
* session-set
Mapped as [SessionArgumentsSet()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.SessionArgumentsSet).
* session-get
Mapped as [SessionArgumentsGet()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.SessionArgumentsGet).
#### Session Statistics
* session-stats
Mapped as [SessionStats()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.SessionStats).
#### Blocklist
* blocklist-update _(to do)_
#### Port Checking
* port-test
Mapped as [PortTest()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.PortTest).
Ex:
```golang
st, err := transmissionbt.PortTest()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
if st {
fmt.Println("Open!")
}
```
#### Session Shutdown
* session-close _(to do)_
#### Queue Movement Requests
* queue-move-top _(to do)_
* queue-move-up _(to do)_
* queue-move-down _(to do)_
* queue-move-bottom _(to do)_
#### Free Space
* free-space
Mappped as [FreeSpace()](https://godoc.org/github.com/hekmon/transmissionrpc#Client.FreeSpace).
Ex: Get the space available for /data.
```golang
freeSpace, err := transmissionbt.FreeSpace("/data")
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Printd("%s | %d | %v", freeSpace, freeSpace, freeSpace)
}
}
```
For more information about the freeSpace type, check the [ComputerUnits](https://github.com/hekmon/cunits) library.

97
vendor/github.com/hekmon/transmissionrpc/controller.go generated vendored Normal file
View File

@@ -0,0 +1,97 @@
package transmissionrpc
import (
"fmt"
"math/rand"
"net/http"
"net/url"
"sync"
"time"
cleanhttp "github.com/hashicorp/go-cleanhttp"
)
const (
// RPCVersion indicates the exact transmission RPC version this library is build against
RPCVersion = 15
defaultPort = 9091
defaultRPCPath = "/transmission/rpc"
defaultTimeout = 30 * time.Second
defaultUserAgent = "github.com/hekmon/transmissionrpc"
)
// Client is the base object to interract with a remote transmission rpc endpoint.
// It must be created with New().
type Client struct {
url string
user string
password string
sessionID string
sessionIDAccess sync.RWMutex
userAgent string
rnd *rand.Rand
httpC *http.Client
}
// AdvancedConfig handles options that are not mandatory for New().
// Default value for HTTPS is false, default port is 9091, default RPC URI is
// '/transmission/rpc', default HTTPTimeout is 30s.
type AdvancedConfig struct {
HTTPS bool
Port uint16
RPCURI string
HTTPTimeout time.Duration
UserAgent string
}
// New returns an initialized and ready to use Controller
func New(host, user, password string, conf *AdvancedConfig) (c *Client, err error) {
// Config
if conf != nil {
// Check custom config
if conf.Port == 0 {
conf.Port = defaultPort
}
if conf.RPCURI == "" {
conf.RPCURI = defaultRPCPath
}
if conf.HTTPTimeout == 0 {
conf.HTTPTimeout = defaultTimeout
}
if conf.UserAgent == "" {
conf.UserAgent = defaultUserAgent
}
} else {
// Spawn default config
conf = &AdvancedConfig{
// HTTPS false by default
Port: defaultPort,
RPCURI: defaultRPCPath,
HTTPTimeout: defaultTimeout,
UserAgent: defaultUserAgent,
}
}
// Build & validate URL
var scheme string
if conf.HTTPS {
scheme = "https"
} else {
scheme = "http"
}
remoteURL, err := url.Parse(fmt.Sprintf("%s://%s:%d%s", scheme, host, conf.Port, conf.RPCURI))
if err != nil {
err = fmt.Errorf("can't build a valid URL: %v", err)
return
}
// Initialize & return ready to use client
c = &Client{
url: remoteURL.String(),
user: user,
password: password,
userAgent: conf.UserAgent,
rnd: rand.New(rand.NewSource(time.Now().Unix())),
httpC: cleanhttp.DefaultPooledClient(),
}
c.httpC.Timeout = conf.HTTPTimeout
return
}

39
vendor/github.com/hekmon/transmissionrpc/free_space.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package transmissionrpc
import (
"fmt"
"github.com/hekmon/cunits"
)
/*
Free Space
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L618
*/
// FreeSpace allow to see how much free space is available in a client-specified folder.
func (c *Client) FreeSpace(path string) (freeSpace cunits.Bits, err error) {
payload := &transmissionFreeSpacePayload{Path: path}
var space TransmissionFreeSpace
if err = c.rpcCall("free-space", payload, &space); err == nil {
if space.Path == path {
freeSpace = cunits.ImportInByte(float64(space.Size))
} else {
err = fmt.Errorf("returned path '%s' does not match with requested path '%s'", space.Path, path)
}
} else {
err = fmt.Errorf("'free-space' rpc method failed: %v", err)
}
return
}
type transmissionFreeSpacePayload struct {
Path string `json:"path"`
}
// TransmissionFreeSpace represents the freespace available in bytes for a specific path.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L631
type TransmissionFreeSpace struct {
Path string `json:"path"`
Size int64 `json:"size-bytes"`
}

View File

@@ -0,0 +1,26 @@
package transmissionrpc
import (
"fmt"
)
/*
Port Checking
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L584
*/
// PortTest allows tests to see if your incoming peer port is accessible from the outside world.
func (c *Client) PortTest() (open bool, err error) {
var result portTestAnswer
// Send request
if err = c.rpcCall("port-test", nil, &result); err == nil {
open = result.PortOpen
} else {
err = fmt.Errorf("'port-test' rpc method failed: %v", err)
}
return
}
type portTestAnswer struct {
PortOpen bool `json:"port-is-open"`
}

140
vendor/github.com/hekmon/transmissionrpc/request.go generated vendored Normal file
View File

@@ -0,0 +1,140 @@
package transmissionrpc
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"sync"
)
const csrfHeader = "X-Transmission-Session-Id"
type requestPayload struct {
Method string `json:"method"`
Arguments interface{} `json:"arguments,omitempty"`
Tag int `json:"tag,omitempty"`
}
type answerPayload struct {
Arguments interface{} `json:"arguments"`
Result string `json:"result"`
Tag *int `json:"tag"`
}
func (c *Client) rpcCall(method string, arguments interface{}, result interface{}) (err error) {
return c.request(method, arguments, result, true)
}
func (c *Client) request(method string, arguments interface{}, result interface{}, retry bool) (err error) {
// Let's avoid crashing
if c.httpC == nil {
err = errors.New("this controller is not initialized, please use the New() function")
return
}
// Prepare the pipeline between payload generation and request
pOut, pIn := io.Pipe()
// Prepare the request
var req *http.Request
if req, err = http.NewRequest("POST", c.url, pOut); err != nil {
err = fmt.Errorf("can't prepare request for '%s' method: %v", method, err)
return
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", c.userAgent)
req.Header.Set(csrfHeader, c.getSessionID())
req.SetBasicAuth(c.user, c.password)
// Prepare the marshalling goroutine
var tag int
var encErr error
var mg sync.WaitGroup
mg.Add(1)
go func() {
tag = c.rnd.Int()
encErr = json.NewEncoder(pIn).Encode(&requestPayload{
Method: method,
Arguments: arguments,
Tag: tag,
})
pIn.Close()
mg.Done()
}()
// Execute request
var resp *http.Response
if resp, err = c.httpC.Do(req); err != nil {
mg.Wait()
if encErr != nil {
err = fmt.Errorf("request error: %v | json payload marshall error: %v", err, encErr)
} else {
err = fmt.Errorf("request error: %v", err)
}
return
}
defer resp.Body.Close()
// Let's test the enc result, just in case
mg.Wait()
if encErr != nil {
err = fmt.Errorf("request payload JSON marshalling failed: %v", encErr)
return
}
// Is the CRSF token invalid ?
if resp.StatusCode == http.StatusConflict {
// Recover new token and save it
c.updateSessionID(resp.Header.Get(csrfHeader))
// Retry request if first try
if retry {
return c.request(method, arguments, result, false)
}
err = errors.New("CSRF token invalid 2 times in a row: stopping to avoid infinite loop")
return
}
// Is request successful ?
if resp.StatusCode != 200 {
err = fmt.Errorf("HTTP error %d: %s", resp.StatusCode, http.StatusText(resp.StatusCode))
return
}
// // Debug
// {
// var data []byte
// data, err = ioutil.ReadAll(resp.Body)
// fmt.Println(string(data))
// return
// }
// Decode body
answer := answerPayload{
Arguments: result,
}
if err = json.NewDecoder(resp.Body).Decode(&answer); err != nil {
err = fmt.Errorf("can't unmarshall request answer body: %v", err)
return
}
// fmt.Println("DEBUG >", answer.Result)
// Final checks
if answer.Tag == nil {
err = errors.New("http answer does not have a tag within it's payload")
return
}
if *answer.Tag != tag {
err = errors.New("http request tag and answer payload tag do not match")
return
}
if answer.Result != "success" {
err = fmt.Errorf("http request ok but payload does not indicate success: %s", answer.Result)
return
}
// All good
return
}
func (c *Client) getSessionID() string {
defer c.sessionIDAccess.RUnlock()
c.sessionIDAccess.RLock()
return c.sessionID
}
func (c *Client) updateSessionID(newID string) {
defer c.sessionIDAccess.Unlock()
c.sessionIDAccess.Lock()
c.sessionID = newID
}

View File

@@ -0,0 +1,164 @@
package transmissionrpc
import (
"encoding/json"
"fmt"
"reflect"
"github.com/hekmon/cunits"
)
/*
Session Arguments
hhttps://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L461
*/
// RPCVersion returns true if the lib RPC version is greater or equals to the remote server rpc minimum version.
func (c *Client) RPCVersion() (ok bool, serverVersion int64, serverMinimumVersion int64, err error) {
payload, err := c.SessionArgumentsGet()
if err != nil {
err = fmt.Errorf("can't get session values: %v", err)
return
}
if payload.RPCVersion == nil {
err = fmt.Errorf("payload RPC Version is nil")
return
}
if payload.RPCVersionMinimum == nil {
err = fmt.Errorf("payload RPC Version minimum is nil")
return
}
serverVersion = *payload.RPCVersion
serverMinimumVersion = *payload.RPCVersionMinimum
ok = RPCVersion >= serverMinimumVersion
return
}
// SessionArgumentsSet allows to modify global/session values.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L534
func (c *Client) SessionArgumentsSet(payload *SessionArguments) (err error) {
// Checks
if payload == nil {
err = fmt.Errorf("payload can't be nil")
return
}
payload.BlocklistSize = nil
payload.ConfigDir = nil
payload.RPCVersion = nil
payload.RPCVersionMinimum = nil
payload.Version = nil
// Exec
if err = c.rpcCall("session-set", payload, nil); err != nil {
err = fmt.Errorf("'session-set' rpc method failed: %v", err)
}
return
}
// SessionArgumentsGet returns global/session values.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L542
func (c *Client) SessionArgumentsGet() (sessionArgs *SessionArguments, err error) {
if err = c.rpcCall("session-get", nil, &sessionArgs); err != nil {
err = fmt.Errorf("'session-get' rpc method failed: %v", err)
}
return
}
// SessionArguments represents all the global/session values.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L461
type SessionArguments struct {
AltSpeedDown *int64 `json:"alt-speed-down"` // max global download speed (KBps)
AltSpeedEnabled *bool `json:"alt-speed-enabled"` // true means use the alt speeds
AltSpeedTimeBegin *int64 `json:"alt-speed-time-begin"` // when to turn on alt speeds (units: minutes after midnight)
AltSpeedTimeEnabled *bool `json:"alt-speed-time-enabled"` // true means the scheduled on/off times are used
AltSpeedTimeEnd *int64 `json:"alt-speed-time-end"` // when to turn off alt speeds (units: same)
AltSpeedTimeDay *int64 `json:"alt-speed-time-day"` // what day(s) to turn on alt speeds (look at tr_sched_day)
AltSpeedUp *int64 `json:"alt-speed-up"` // max global upload speed (KBps)
BlocklistURL *string `json:"blocklist-url"` // location of the blocklist to use for "blocklist-update"
BlocklistEnabled *bool `json:"blocklist-enabled"` // true means enabled
BlocklistSize *int64 `json:"blocklist-size"` // number of rules in the blocklist
CacheSizeMB *int64 `json:"cache-size-mb"` // maximum size of the disk cache (MB)
ConfigDir *string `json:"config-dir"` // location of transmission's configuration directory
DownloadDir *string `json:"download-dir"` // default path to download torrents
DownloadQueueSize *int64 `json:"download-queue-size"` // max number of torrents to download at once (see download-queue-enabled)
DownloadQueueEnabled *bool `json:"download-queue-enabled"` // if true, limit how many torrents can be downloaded at once
DHTEnabled *bool `json:"dht-enabled"` // true means allow dht in public torrents
Encryption *string `json:"encryption"` // "required", "preferred", "tolerated"
IdleSeedingLimit *int64 `json:"idle-seeding-limit"` // torrents we're seeding will be stopped if they're idle for this long
IdleSeedingLimitEnabled *bool `json:"idle-seeding-limit-enabled"` // true if the seeding inactivity limit is honored by default
IncompleteDir *string `json:"incomplete-dir"` // path for incomplete torrents, when enabled
IncompleteDirEnabled *bool `json:"incomplete-dir-enabled"` // true means keep torrents in incomplete-dir until done
LPDEnabled *bool `json:"lpd-enabled"` // true means allow Local Peer Discovery in public torrents
PeerLimitGlobal *int64 `json:"peer-limit-global"` // maximum global number of peers
PeerLimitPerTorrent *int64 `json:"peer-limit-per-torrent"` // maximum global number of peers
PEXEnabled *bool `json:"pex-enabled"` // true means allow pex in public torrents
PeerPort *int64 `json:"peer-port"` // port number
PeerPortRandomOnStart *bool `json:"peer-port-random-on-start"` // true means pick a random peer port on launch
PortForwardingEnabled *bool `json:"port-forwarding-enabled"` // true means enabled
QueueStalledEnabled *bool `json:"queue-stalled-enabled"` // whether or not to consider idle torrents as stalled
QueueStalledMinutes *int64 `json:"queue-stalled-minutes"` // torrents that are idle for N minuets aren't counted toward seed-queue-size or download-queue-size
RenamePartialFiles *bool `json:"rename-partial-files"` // true means append ".part" to incomplete files
RPCVersion *int64 `json:"rpc-version"` // the current RPC API version
RPCVersionMinimum *int64 `json:"rpc-version-minimum"` // the minimum RPC API version supported
ScriptTorrentDoneFilename *string `json:"script-torrent-done-filename"` // filename of the script to run
ScriptTorrentDoneEnabled *bool `json:"script-torrent-done-enabled"` // whether or not to call the "done" script
SeedRatioLimit *float64 `json:"seedRatioLimit"` // the default seed ratio for torrents to use
SeedRatioLimited *bool `json:"seedRatioLimited"` // true if seedRatioLimit is honored by default
SeedQueueSize *int64 `json:"seed-queue-size"` // max number of torrents to uploaded at once (see seed-queue-enabled)
SeedQueueEnabled *bool `json:"seed-queue-enabled"` // if true, limit how many torrents can be uploaded at once
SpeedLimitDown *int64 `json:"speed-limit-down"` // max global download speed (KBps)
SpeedLimitDownEnabled *bool `json:"speed-limit-down-enabled"` // true means enabled
SpeedLimitUp *int64 `json:"speed-limit-up"` // max global upload speed (KBps)
SpeedLimitUpEnabled *bool `json:"speed-limit-up-enabled"` // true means enabled
StartAddedTorrents *bool `json:"start-added-torrents"` // true means added torrents will be started right away
TrashOriginalTorrentFiles *bool `json:"trash-original-torrent-files"` // true means the .torrent file of added torrents will be deleted
Units *Units `json:"units"` // see units below
UTPEnabled *bool `json:"utp-enabled"` // true means allow utp
Version *string `json:"version"` // long version string "$version ($revision)"
}
// MarshalJSON allows to marshall into JSON only the non nil fields.
// It differs from 'omitempty' which also skip default values
// (as 0 or false which can be valid here).
func (sa *SessionArguments) MarshalJSON() (data []byte, err error) {
// Build a payload with only the non nil fields
tspv := reflect.ValueOf(*sa)
tspt := tspv.Type()
cleanPayload := make(map[string]interface{}, tspt.NumField())
var currentValue reflect.Value
var currentStructField reflect.StructField
for i := 0; i < tspv.NumField(); i++ {
currentValue = tspv.Field(i)
currentStructField = tspt.Field(i)
if !currentValue.IsNil() {
cleanPayload[currentStructField.Tag.Get("json")] = currentValue.Interface()
}
}
// Marshall the clean payload
return json.Marshal(cleanPayload)
}
// Units is subset of SessionArguments.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L514
type Units struct {
SpeedUnits []string `json:"speed-units"` // 4 strings: KB/s, MB/s, GB/s, TB/s
SpeedBytes int64 `json:"speed-bytes"` // number of bytes in a KB (1000 for kB; 1024 for KiB)
SizeUnits []string `json:"size-units"` // 4 strings: KB/s, MB/s, GB/s, TB/s
SizeBytes int64 `json:"size-bytes"` // number of bytes in a KB (1000 for kB; 1024 for KiB)
MemoryUnits []string `json:"memory-units"` // 4 strings: KB/s, MB/s, GB/s, TB/s
MemoryBytes int64 `json:"memory-bytes"` // number of bytes in a KB (1000 for kB; 1024 for KiB)
}
// GetSpeed returns the speed in a handy format
func (u *Units) GetSpeed() (speed cunits.Bits) {
return cunits.ImportInByte(float64(u.SpeedBytes))
}
// GetSize returns the size in a handy format
func (u *Units) GetSize() (size cunits.Bits) {
return cunits.ImportInByte(float64(u.SizeBytes))
}
// GetMemory returns the memory in a handy format
func (u *Units) GetMemory() (memory cunits.Bits) {
return cunits.ImportInByte(float64(u.MemoryBytes))
}

View File

@@ -0,0 +1,73 @@
package transmissionrpc
import (
"fmt"
"github.com/hekmon/cunits"
)
/*
Session Statistics
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L546
*/
// SessionStats returns all (current/cumulative) statistics.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L548
func (c *Client) SessionStats() (stats *SessionStats, err error) {
if err = c.rpcCall("session-stats", nil, &stats); err != nil {
err = fmt.Errorf("'session-stats' rpc method failed: %v", err)
}
return
}
// SessionStats represents all (current/cumulative) statistics.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L554
type SessionStats struct {
ActiveTorrentCount int64 `json:"activeTorrentCount"`
CumulativeStats *CumulativeStats `json:"cumulative-stats"`
CurrentStats *CurrentStats `json:"current-stats"`
DownloadSpeed int64 `json:"downloadSpeed"`
PausedTorrentCount int64 `json:"pausedTorrentCount"`
TorrentCount int64 `json:"torrentCount"`
UploadSpeed int64 `json:"uploadSpeed"`
}
// CumulativeStats is subset of SessionStats.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L562
type CumulativeStats struct {
DownloadedBytes int64 `json:"downloadedBytes"`
FilesAdded int64 `json:"filesAdded"`
SecondsActive int64 `json:"secondsActive"`
SessionCount int64 `json:"sessionCount"`
UploadedBytes int64 `json:"uploadedBytes"`
}
// GetDownloaded returns cumulative stats downloaded size in a handy format
func (cs *CumulativeStats) GetDownloaded() (downloaded cunits.Bits) {
return cunits.ImportInByte(float64(cs.DownloadedBytes))
}
// GetUploaded returns cumulative stats uploaded size in a handy format
func (cs *CumulativeStats) GetUploaded() (uploaded cunits.Bits) {
return cunits.ImportInByte(float64(cs.UploadedBytes))
}
// CurrentStats is subset of SessionStats.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L570
type CurrentStats struct {
DownloadedBytes int64 `json:"downloadedBytes"`
FilesAdded int64 `json:"filesAdded"`
SecondsActive int64 `json:"secondsActive"`
SessionCount int64 `json:"sessionCount"`
UploadedBytes int64 `json:"uploadedBytes"`
}
// GetDownloaded returns current stats downloaded size in a handy format
func (cs *CurrentStats) GetDownloaded() (downloaded cunits.Bits) {
return cunits.ImportInByte(float64(cs.DownloadedBytes))
}
// GetUploaded returns current stats uploaded size in a handy format
func (cs *CurrentStats) GetUploaded() (uploaded cunits.Bits) {
return cunits.ImportInByte(float64(cs.UploadedBytes))
}

View File

@@ -0,0 +1,609 @@
package transmissionrpc
/*
Torrent Accessors
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L142
*/
import (
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/hekmon/cunits"
)
var validTorrentFields []string
func init() {
torrentType := reflect.TypeOf(Torrent{})
for i := 0; i < torrentType.NumField(); i++ {
validTorrentFields = append(validTorrentFields, torrentType.Field(i).Tag.Get("json"))
}
}
// TorrentGetAll returns all the known fields for all the torrents.
func (c *Client) TorrentGetAll() (torrents []*Torrent, err error) {
// Send already validated fields to the low level fx
return c.torrentGet(validTorrentFields, nil)
}
// TorrentGetAllFor returns all known fields for the given torrent's ids.
func (c *Client) TorrentGetAllFor(ids []int64) (torrents []*Torrent, err error) {
return c.torrentGet(validTorrentFields, ids)
}
// TorrentGetAllForHashes returns all known fields for the given torrent's ids by string (usually hash).
func (c *Client) TorrentGetAllForHashes(hashes []string) (torrents []*Torrent, err error) {
return c.torrentGetHash(validTorrentFields, hashes)
}
// TorrentGet returns the given of fields (mandatory) for each ids (optionnal).
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L144
func (c *Client) TorrentGet(fields []string, ids []int64) (torrents []*Torrent, err error) {
if err = c.validateFields(fields); err != nil {
return
}
return c.torrentGet(fields, ids)
}
// TorrentGetHashes returns the given of fields (mandatory) for each ids (optionnal).
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L144
func (c *Client) TorrentGetHashes(fields []string, hashes []string) (torrents []*Torrent, err error) {
if err = c.validateFields(fields); err != nil {
return
}
return c.torrentGetHash(fields, hashes)
}
func (c *Client) validateFields(fields []string) (err error) {
// Validate fields
var fieldInvalid bool
var knownField string
for _, inputField := range fields {
fieldInvalid = true
for _, knownField = range validTorrentFields {
if inputField == knownField {
fieldInvalid = false
break
}
}
if fieldInvalid {
err = fmt.Errorf("field '%s' is invalid", inputField)
return
}
}
return
}
func (c *Client) torrentGet(fields []string, ids []int64) (torrents []*Torrent, err error) {
var result torrentGetResults
if err = c.rpcCall("torrent-get", &torrentGetParams{
Fields: fields,
IDs: ids,
}, &result); err != nil {
err = fmt.Errorf("'torrent-get' rpc method failed: %v", err)
return
}
torrents = result.Torrents
return
}
func (c *Client) torrentGetHash(fields []string, hashes []string) (torrents []*Torrent, err error) {
var result torrentGetResults
if err = c.rpcCall("torrent-get", &torrentGetHashParams{
Fields: fields,
Hashes: hashes,
}, &result); err != nil {
err = fmt.Errorf("'torrent-get' rpc method failed: %v", err)
return
}
torrents = result.Torrents
return
}
type torrentGetParams struct {
Fields []string `json:"fields"`
IDs []int64 `json:"ids,omitempty"`
}
type torrentGetHashParams struct {
Fields []string `json:"fields"`
Hashes []string `json:"ids,omitempty"`
}
type torrentGetResults struct {
Torrents []*Torrent `json:"torrents"`
}
// Torrent represents all the possible fields of data for a torrent.
// All fields are pointers to detect if the value is nil (field not requested) or default real default value.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L163
type Torrent struct {
ActivityDate *time.Time `json:"activityDate"`
AddedDate *time.Time `json:"addedDate"`
BandwidthPriority *int64 `json:"bandwidthPriority"`
Comment *string `json:"comment"`
CorruptEver *int64 `json:"corruptEver"`
Creator *string `json:"creator"`
DateCreated *time.Time `json:"dateCreated"`
DesiredAvailable *int64 `json:"desiredAvailable"`
DoneDate *time.Time `json:"doneDate"`
DownloadDir *string `json:"downloadDir"`
DownloadedEver *int64 `json:"downloadedEver"`
DownloadLimit *int64 `json:"downloadLimit"`
DownloadLimited *bool `json:"downloadLimited"`
Error *int64 `json:"error"`
ErrorString *string `json:"errorString"`
Eta *int64 `json:"eta"`
EtaIdle *int64 `json:"etaIdle"`
Files []*TorrentFile `json:"files"`
FileStats []*TorrentFileStat `json:"fileStats"`
HashString *string `json:"hashString"`
HaveUnchecked *int64 `json:"haveUnchecked"`
HaveValid *int64 `json:"haveValid"`
HonorsSessionLimits *bool `json:"honorsSessionLimits"`
ID *int64 `json:"id"`
IsFinished *bool `json:"isFinished"`
IsPrivate *bool `json:"isPrivate"`
IsStalled *bool `json:"isStalled"`
LeftUntilDone *int64 `json:"leftUntilDone"`
MagnetLink *string `json:"magnetLink"`
ManualAnnounceTime *int64 `json:"manualAnnounceTime"`
MaxConnectedPeers *int64 `json:"maxConnectedPeers"`
MetadataPercentComplete *float64 `json:"metadataPercentComplete"`
Name *string `json:"name"`
PeerLimit *int64 `json:"peer-limit"`
Peers []*Peer `json:"peers"`
PeersConnected *int64 `json:"peersConnected"`
PeersFrom *TorrentPeersFrom `json:"peersFrom"`
PeersGettingFromUs *int64 `json:"peersGettingFromUs"`
PeersSendingToUs *int64 `json:"peersSendingToUs"`
PercentDone *float64 `json:"percentDone"`
Pieces *string `json:"pieces"` // https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L279
PieceCount *int64 `json:"pieceCount"`
PieceSize *cunits.Bits `json:"pieceSize"`
Priorities []int64 `json:"priorities"` // https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L285
QueuePosition *int64 `json:"queuePosition"`
RateDownload *int64 `json:"rateDownload"` // B/s
RateUpload *int64 `json:"rateUpload"` // B/s
RecheckProgress *float64 `json:"recheckProgress"`
SecondsDownloading *int64 `json:"secondsDownloading"`
SecondsSeeding *time.Duration `json:"secondsSeeding"`
SeedIdleLimit *int64 `json:"seedIdleLimit"`
SeedIdleMode *int64 `json:"seedIdleMode"`
SeedRatioLimit *float64 `json:"seedRatioLimit"`
SeedRatioMode *SeedRatioMode `json:"seedRatioMode"`
SizeWhenDone *cunits.Bits `json:"sizeWhenDone"`
StartDate *time.Time `json:"startDate"`
Status *TorrentStatus `json:"status"`
Trackers []*Tracker `json:"trackers"`
TrackerStats []*TrackerStats `json:"trackerStats"`
TotalSize *cunits.Bits `json:"totalSize"`
TorrentFile *string `json:"torrentFile"`
UploadedEver *int64 `json:"uploadedEver"`
UploadLimit *int64 `json:"uploadLimit"`
UploadLimited *bool `json:"uploadLimited"`
UploadRatio *float64 `json:"uploadRatio"`
Wanted []bool `json:"wanted"` //https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L325
WebSeeds []string `json:"webseeds"` // https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L329
WebSeedsSendingToUs *int64 `json:"webseedsSendingToUs"`
}
// ConvertDownloadSpeed will return the download speed as cunits.Bitss/second
func (t *Torrent) ConvertDownloadSpeed() (speed cunits.Bits) {
if t.RateDownload != nil {
speed = cunits.ImportInByte(float64(*t.RateDownload))
}
return
}
// ConvertUploadSpeed will return the upload speed as cunits.Bitss/second
func (t *Torrent) ConvertUploadSpeed() (speed cunits.Bits) {
if t.RateUpload != nil {
speed = cunits.ImportInByte(float64(*t.RateUpload))
}
return
}
// UnmarshalJSON allows to convert timestamps to golang time.Time values.
func (t *Torrent) UnmarshalJSON(data []byte) (err error) {
// Shadow real type for regular unmarshalling
type RawTorrent Torrent
tmp := &struct {
ActivityDate *int64 `json:"activityDate"`
AddedDate *int64 `json:"addedDate"`
DateCreated *int64 `json:"dateCreated"`
DoneDate *int64 `json:"doneDate"`
PieceSize *int64 `json:"pieceSize"`
SecondsSeeding *int64 `json:"secondsSeeding"`
SizeWhenDone *int64 `json:"sizeWhenDone"`
StartDate *int64 `json:"startDate"`
TotalSize *int64 `json:"totalSize"`
Wanted []int64 `json:"wanted"` // boolean in number form
*RawTorrent
}{
RawTorrent: (*RawTorrent)(t),
}
// Unmarshall (with timestamps as number)
if err = json.Unmarshal(data, &tmp); err != nil {
return
}
// Create the real time & duration from timsteamps and seconds
if tmp.ActivityDate != nil {
ad := time.Unix(*tmp.ActivityDate, 0)
t.ActivityDate = &ad
}
if tmp.AddedDate != nil {
ad := time.Unix(*tmp.AddedDate, 0)
t.AddedDate = &ad
}
if tmp.DateCreated != nil {
dc := time.Unix(*tmp.DateCreated, 0)
t.DateCreated = &dc
}
if tmp.DoneDate != nil {
dd := time.Unix(*tmp.DoneDate, 0)
t.DoneDate = &dd
}
if tmp.PieceSize != nil {
ps := cunits.ImportInByte(float64(*tmp.PieceSize))
t.PieceSize = &ps
}
if tmp.SecondsSeeding != nil {
dur := time.Duration(*tmp.SecondsSeeding) * time.Second
t.SecondsSeeding = &dur
}
if tmp.SizeWhenDone != nil {
swd := cunits.ImportInByte(float64(*tmp.SizeWhenDone))
t.SizeWhenDone = &swd
}
if tmp.StartDate != nil {
st := time.Unix(*tmp.StartDate, 0)
t.StartDate = &st
}
if tmp.TotalSize != nil {
ts := cunits.ImportInByte(float64(*tmp.TotalSize))
t.TotalSize = &ts
}
// Boolean slice in decimal form
if tmp.Wanted != nil {
t.Wanted = make([]bool, len(tmp.Wanted))
for index, value := range tmp.Wanted {
if value == 1 {
t.Wanted[index] = true
} else if value != 0 {
return fmt.Errorf("Can't convert Wanted index %d value '%d' as boolean", index, value)
}
}
}
return
}
// MarshalJSON allows to convert back golang values to original payload values.
func (t *Torrent) MarshalJSON() (data []byte, err error) {
// Shadow real type for regular unmarshalling
type RawTorrent Torrent
tmp := &struct {
ActivityDate *int64 `json:"activityDate"`
AddedDate *int64 `json:"addedDate"`
DateCreated *int64 `json:"dateCreated"`
DoneDate *int64 `json:"doneDate"`
SecondsSeeding *int64 `json:"secondsSeeding"`
StartDate *int64 `json:"startDate"`
Wanted []int64 `json:"wanted"` // boolean in number form
*RawTorrent
}{
RawTorrent: (*RawTorrent)(t),
}
// Timestamps & Duration
if t.ActivityDate != nil {
ad := t.ActivityDate.Unix()
tmp.ActivityDate = &ad
}
if t.AddedDate != nil {
ad := t.AddedDate.Unix()
tmp.AddedDate = &ad
}
if t.DateCreated != nil {
dc := t.DateCreated.Unix()
tmp.DateCreated = &dc
}
if t.DoneDate != nil {
dd := t.DoneDate.Unix()
tmp.DoneDate = &dd
}
if t.SecondsSeeding != nil {
ss := int64(*t.SecondsSeeding / time.Second)
tmp.SecondsSeeding = &ss
}
if t.StartDate != nil {
st := t.StartDate.Unix()
tmp.StartDate = &st
}
// Boolean as number
if t.Wanted != nil {
tmp.Wanted = make([]int64, len(t.Wanted))
for index, value := range t.Wanted {
if value {
tmp.Wanted[index] = 1
}
}
}
// Marshall original values within the tmp payload
return json.Marshal(&tmp)
}
// TorrentFile represent one file from a Torrent.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L236
type TorrentFile struct {
BytesCompleted int64 `json:"bytesCompleted"`
Length int64 `json:"length"`
Name string `json:"name"`
}
// TorrentFileStat represents the metadata of a torrent's file.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L242
type TorrentFileStat struct {
BytesCompleted int64 `json:"bytesCompleted"`
Wanted bool `json:"wanted"`
Priority int64 `json:"priority"`
}
// Peer represent a peer metadata of a torrent's peer list.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L250
type Peer struct {
Address string `json:"address"`
ClientName string `json:"clientName"`
ClientIsChoked bool `json:"clientIsChoked"`
ClientIsint64erested bool `json:"clientIsint64erested"`
FlagStr string `json:"flagStr"`
IsDownloadingFrom bool `json:"isDownloadingFrom"`
IsEncrypted bool `json:"isEncrypted"`
IsIncoming bool `json:"isIncoming"`
IsUploadingTo bool `json:"isUploadingTo"`
IsUTP bool `json:"isUTP"`
PeerIsChoked bool `json:"peerIsChoked"`
PeerIsint64erested bool `json:"peerIsint64erested"`
Port int64 `json:"port"`
Progress float64 `json:"progress"`
RateToClient int64 `json:"rateToClient"` // B/s
RateToPeer int64 `json:"rateToPeer"` // B/s
}
// ConvertDownloadSpeed will return the download speed from peer as cunits.Bits/second
func (p *Peer) ConvertDownloadSpeed() (speed cunits.Bits) {
return cunits.ImportInByte(float64(p.RateToClient))
}
// ConvertUploadSpeed will return the upload speed to peer as cunits.Bits/second
func (p *Peer) ConvertUploadSpeed() (speed cunits.Bits) {
return cunits.ImportInByte(float64(p.RateToPeer))
}
// TorrentPeersFrom represents the peers statistics of a torrent.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L269
type TorrentPeersFrom struct {
FromCache int64 `json:"fromCache"`
FromDHT int64 `json:"fromDht"`
FromIncoming int64 `json:"fromIncoming"`
FromLPD int64 `json:"fromLpd"`
FromLTEP int64 `json:"fromLtep"`
FromPEX int64 `json:"fromPex"`
FromTracker int64 `json:"fromTracker"`
}
// SeedRatioMode represents a torrent current seeding mode
type SeedRatioMode int64
const (
// SeedRatioModeGlobal represents the use of the global ratio for a torrent
SeedRatioModeGlobal SeedRatioMode = 0
// SeedRatioModeCustom represents the use of a custom ratio for a torrent
SeedRatioModeCustom SeedRatioMode = 1
// SeedRatioModeNoRatio represents the absence of ratio for a torrent
SeedRatioModeNoRatio SeedRatioMode = 2
)
func (srm SeedRatioMode) String() string {
switch srm {
case SeedRatioModeGlobal:
return "global"
case SeedRatioModeCustom:
return "custom"
case SeedRatioModeNoRatio:
return "no ratio"
default:
return "<unknown>"
}
}
// GoString implements the GoStringer interface from the stdlib fmt package
func (srm SeedRatioMode) GoString() string {
switch srm {
case SeedRatioModeGlobal:
return fmt.Sprintf("global (%d)", srm)
case SeedRatioModeCustom:
return fmt.Sprintf("custom (%d)", srm)
case SeedRatioModeNoRatio:
return fmt.Sprintf("no ratio (%d)", srm)
default:
return fmt.Sprintf("<unknown> (%d)", srm)
}
}
// TorrentStatus binds torrent status to a status code
type TorrentStatus int64
const (
// TorrentStatusStopped represents a stopped torrent
TorrentStatusStopped TorrentStatus = 0
// TorrentStatusCheckWait represents a torrent queued for files checking
TorrentStatusCheckWait TorrentStatus = 1
// TorrentStatusCheck represents a torrent which files are currently checked
TorrentStatusCheck TorrentStatus = 2
// TorrentStatusDownloadWait represents a torrent queue to download
TorrentStatusDownloadWait TorrentStatus = 3
// TorrentStatusDownload represents a torrent currently downloading
TorrentStatusDownload TorrentStatus = 4
// TorrentStatusSeedWait represents a torrent queued to seed
TorrentStatusSeedWait TorrentStatus = 5
// TorrentStatusSeed represents a torrent currently seeding
TorrentStatusSeed TorrentStatus = 6
// TorrentStatusIsolated represents a torrent which can't find peers
TorrentStatusIsolated TorrentStatus = 7
)
func (status TorrentStatus) String() string {
switch status {
case TorrentStatusStopped:
return "stopped"
case TorrentStatusCheckWait:
return "waiting to check files"
case TorrentStatusCheck:
return "checking files"
case TorrentStatusDownloadWait:
return "waiting to download"
case TorrentStatusDownload:
return "downloading"
case TorrentStatusSeedWait:
return "waiting to seed"
case TorrentStatusSeed:
return "seeding"
case TorrentStatusIsolated:
return "can't find peers"
default:
return "<unknown>"
}
}
// GoString implements the GoStringer interface from the stdlib fmt package
func (status TorrentStatus) GoString() string {
switch status {
case TorrentStatusStopped:
return fmt.Sprintf("stopped (%d)", status)
case TorrentStatusCheckWait:
return fmt.Sprintf("waiting to check files (%d)", status)
case TorrentStatusCheck:
return fmt.Sprintf("checking files (%d)", status)
case TorrentStatusDownloadWait:
return fmt.Sprintf("waiting to download (%d)", status)
case TorrentStatusDownload:
return fmt.Sprintf("downloading (%d)", status)
case TorrentStatusSeedWait:
return fmt.Sprintf("waiting to seed (%d)", status)
case TorrentStatusSeed:
return fmt.Sprintf("seeding (%d)", status)
case TorrentStatusIsolated:
return fmt.Sprintf("can't find peers (%d)", status)
default:
return fmt.Sprintf("<unknown> (%d)", status)
}
}
// Tracker represent the base data of a torrent's tracker.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L289
type Tracker struct {
Announce string `json:"announce"`
ID int64 `json:"id"`
Scrape string `json:"scrape"`
Tier int64 `json:"tier"`
}
// TrackerStats represent the extended data of a torrent's tracker.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L296
type TrackerStats struct {
Announce string `json:"announce"`
AnnounceState int64 `json:"announceState"`
DownloadCount int64 `json:"downloadCount"`
HasAnnounced bool `json:"hasAnnounced"`
HasScraped bool `json:"hasScraped"`
Host string `json:"host"`
ID int64 `json:"id"`
IsBackup bool `json:"isBackup"`
LastAnnouncePeerCount int64 `json:"lastAnnouncePeerCount"`
LastAnnounceResult string `json:"lastAnnounceResult"`
LastAnnounceStartTime time.Time `json:"lastAnnounceStartTime"`
LastAnnounceSucceeded bool `json:"lastAnnounceSucceeded"`
LastAnnounceTime time.Time `json:"lastAnnounceTime"`
LastAnnounceTimedOut bool `json:"lastAnnounceTimedOut"`
LastScrapeResult string `json:"lastScrapeResult"`
LastScrapeStartTime time.Time `json:"lastScrapeStartTime"`
LastScrapeSucceeded bool `json:"lastScrapeSucceeded"`
LastScrapeTime time.Time `json:"lastScrapeTime"`
LastScrapeTimedOut bool `json:"lastScrapeTimedOut"` // should be boolean but number. Will be converter in UnmarshalJSON
LeecherCount int64 `json:"leecherCount"`
NextAnnounceTime time.Time `json:"nextAnnounceTime"`
NextScrapeTime time.Time `json:"nextScrapeTime"`
Scrape string `json:"scrape"`
ScrapeState int64 `json:"scrapeState"`
SeederCount int64 `json:"seederCount"`
Tier int64 `json:"tier"`
}
// UnmarshalJSON allows to convert timestamps to golang time.Time values.
func (ts *TrackerStats) UnmarshalJSON(data []byte) (err error) {
// Shadow real type for regular unmarshalling
type RawTrackerStats TrackerStats
tmp := struct {
LastAnnounceStartTime int64 `json:"lastAnnounceStartTime"`
LastAnnounceTime int64 `json:"lastAnnounceTime"`
LastScrapeStartTime int64 `json:"lastScrapeStartTime"`
LastScrapeTime int64 `json:"lastScrapeTime"`
LastScrapeTimedOut int64 `json:"lastScrapeTimedOut"`
NextAnnounceTime int64 `json:"nextAnnounceTime"`
NextScrapeTime int64 `json:"nextScrapeTime"`
*RawTrackerStats
}{
RawTrackerStats: (*RawTrackerStats)(ts),
}
// Unmarshall (with timestamps as number)
if err = json.Unmarshal(data, &tmp); err != nil {
return
}
// Convert to real boolean
if tmp.LastScrapeTimedOut == 1 {
ts.LastScrapeTimedOut = true
} else if tmp.LastScrapeTimedOut != 0 {
return fmt.Errorf("can't convert 'lastScrapeTimedOut' value '%v' into boolean", tmp.LastScrapeTimedOut)
}
// Create the real time value from the timestamps
ts.LastAnnounceStartTime = time.Unix(tmp.LastAnnounceStartTime, 0)
ts.LastAnnounceTime = time.Unix(tmp.LastAnnounceTime, 0)
ts.LastScrapeStartTime = time.Unix(tmp.LastScrapeStartTime, 0)
ts.LastScrapeTime = time.Unix(tmp.LastScrapeTime, 0)
ts.NextAnnounceTime = time.Unix(tmp.NextAnnounceTime, 0)
ts.NextScrapeTime = time.Unix(tmp.NextScrapeTime, 0)
return
}
// MarshalJSON allows to convert back golang values to original payload values.
func (ts *TrackerStats) MarshalJSON() (data []byte, err error) {
// Shadow real type for regular unmarshalling
type RawTrackerStats TrackerStats
tmp := struct {
LastAnnounceStartTime int64 `json:"lastAnnounceStartTime"`
LastAnnounceTime int64 `json:"lastAnnounceTime"`
LastScrapeStartTime int64 `json:"lastScrapeStartTime"`
LastScrapeTime int64 `json:"lastScrapeTime"`
LastScrapeTimedOut int64 `json:"lastScrapeTimedOut"`
NextAnnounceTime int64 `json:"nextAnnounceTime"`
NextScrapeTime int64 `json:"nextScrapeTime"`
*RawTrackerStats
}{
LastAnnounceStartTime: ts.LastAnnounceStartTime.Unix(),
LastAnnounceTime: ts.LastAnnounceTime.Unix(),
LastScrapeStartTime: ts.LastScrapeStartTime.Unix(),
LastScrapeTime: ts.LastScrapeTime.Unix(),
NextAnnounceTime: ts.NextAnnounceTime.Unix(),
NextScrapeTime: ts.NextScrapeTime.Unix(),
RawTrackerStats: (*RawTrackerStats)(ts),
}
// Convert real bool to its number form
if ts.LastScrapeTimedOut {
tmp.LastScrapeTimedOut = 1
}
// MarshalJSON allows to convert back golang values to original payload values
return json.Marshal(&tmp)
}

View File

@@ -0,0 +1,152 @@
package transmissionrpc
import (
"fmt"
)
/*
Torrent Action Requests
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L86
*/
type torrentActionIDsParam struct {
IDs []int64 `json:"ids,omitempty"`
}
type torrentActionHashesParam struct {
IDs []string `json:"ids,omitempty"`
}
type torrentActionRecentlyActiveParam struct {
IDs string `json:"ids"`
}
// TorrentStartIDs starts torrent(s) which id is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentStartIDs(ids []int64) (err error) {
if err = c.rpcCall("torrent-start", &torrentActionIDsParam{IDs: ids}, nil); err != nil {
err = fmt.Errorf("'torrent-start' rpc method failed: %v", err)
}
return
}
// TorrentStartHashes starts torrent(s) which hash is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentStartHashes(hashes []string) (err error) {
if err = c.rpcCall("torrent-start", &torrentActionHashesParam{IDs: hashes}, nil); err != nil {
err = fmt.Errorf("'torrent-start' rpc method failed: %v", err)
}
return
}
// TorrentStartRecentlyActive starts torrent(s) which have been recently active.
func (c *Client) TorrentStartRecentlyActive() (err error) {
if err = c.rpcCall("torrent-start", &torrentActionRecentlyActiveParam{IDs: "recently-active"}, nil); err != nil {
err = fmt.Errorf("'torrent-start' rpc method failed: %v", err)
}
return
}
// TorrentStartNowIDs starts (now) torrent(s) which id is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentStartNowIDs(ids []int64) (err error) {
if err = c.rpcCall("torrent-start-now", &torrentActionIDsParam{IDs: ids}, nil); err != nil {
err = fmt.Errorf("'torrent-start-now' rpc method failed: %v", err)
}
return
}
// TorrentStartNowHashes starts (now) torrent(s) which hash is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentStartNowHashes(hashes []string) (err error) {
if err = c.rpcCall("torrent-start-now", &torrentActionHashesParam{IDs: hashes}, nil); err != nil {
err = fmt.Errorf("'torrent-start-now' rpc method failed: %v", err)
}
return
}
// TorrentStartNowRecentlyActive starts (now) torrent(s) which have been recently active.
func (c *Client) TorrentStartNowRecentlyActive() (err error) {
if err = c.rpcCall("torrent-start-now", &torrentActionRecentlyActiveParam{IDs: "recently-active"}, nil); err != nil {
err = fmt.Errorf("'torrent-start-now' rpc method failed: %v", err)
}
return
}
// TorrentStopIDs stops torrent(s) which id is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentStopIDs(ids []int64) (err error) {
if err = c.rpcCall("torrent-stop", &torrentActionIDsParam{IDs: ids}, nil); err != nil {
err = fmt.Errorf("'torrent-stop' rpc method failed: %v", err)
}
return
}
// TorrentStopHashes stops torrent(s) which hash is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentStopHashes(hashes []string) (err error) {
if err = c.rpcCall("torrent-stop", &torrentActionHashesParam{IDs: hashes}, nil); err != nil {
err = fmt.Errorf("'torrent-stop' rpc method failed: %v", err)
}
return
}
// TorrentStopRecentlyActive stops torrent(s) which have been recently active.
func (c *Client) TorrentStopRecentlyActive() (err error) {
if err = c.rpcCall("torrent-stop", &torrentActionRecentlyActiveParam{IDs: "recently-active"}, nil); err != nil {
err = fmt.Errorf("'torrent-stop' rpc method failed: %v", err)
}
return
}
// TorrentVerifyIDs verifys torrent(s) which id is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentVerifyIDs(ids []int64) (err error) {
if err = c.rpcCall("torrent-verify", &torrentActionIDsParam{IDs: ids}, nil); err != nil {
err = fmt.Errorf("'torrent-verify' rpc method failed: %v", err)
}
return
}
// TorrentVerifyHashes verifys torrent(s) which hash is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentVerifyHashes(hashes []string) (err error) {
if err = c.rpcCall("torrent-verify", &torrentActionHashesParam{IDs: hashes}, nil); err != nil {
err = fmt.Errorf("'torrent-verify' rpc method failed: %v", err)
}
return
}
// TorrentVerifyRecentlyActive verifys torrent(s) which have been recently active.
func (c *Client) TorrentVerifyRecentlyActive() (err error) {
if err = c.rpcCall("torrent-verify", &torrentActionRecentlyActiveParam{IDs: "recently-active"}, nil); err != nil {
err = fmt.Errorf("'torrent-verify' rpc method failed: %v", err)
}
return
}
// TorrentReannounceIDs reannounces torrent(s) which id is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentReannounceIDs(ids []int64) (err error) {
if err = c.rpcCall("torrent-reannounce", &torrentActionIDsParam{IDs: ids}, nil); err != nil {
err = fmt.Errorf("'torrent-reannounce' rpc method failed: %v", err)
}
return
}
// TorrentReannounceHashes reannounces torrent(s) which hash is in the provided slice.
// Can be one, can be several, can be all (if slice is empty or nil).
func (c *Client) TorrentReannounceHashes(hashes []string) (err error) {
if err = c.rpcCall("torrent-reannounce", &torrentActionHashesParam{IDs: hashes}, nil); err != nil {
err = fmt.Errorf("'torrent-reannounce' rpc method failed: %v", err)
}
return
}
// TorrentReannounceRecentlyActive reannounces torrent(s) which have been recently active.
func (c *Client) TorrentReannounceRecentlyActive() (err error) {
if err = c.rpcCall("torrent-reannounce", &torrentActionRecentlyActiveParam{IDs: "recently-active"}, nil); err != nil {
err = fmt.Errorf("'torrent-reannounce' rpc method failed: %v", err)
}
return
}

135
vendor/github.com/hekmon/transmissionrpc/torrent_add.go generated vendored Normal file
View File

@@ -0,0 +1,135 @@
package transmissionrpc
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"reflect"
)
/*
Adding a Torrent
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L371
*/
// TorrentAddFile is wrapper to directly add a torrent file (it handles the base64 encoding
// and payload generation). If successful (torrent added or duplicate) torrent return value
// will only have HashString, ID and Name fields set up.
func (c *Client) TorrentAddFile(filepath string) (torrent *Torrent, err error) {
// Validate
if filepath == "" {
err = errors.New("filepath can't be empty")
return
}
// Get base64 encoded file content
b64, err := file2Base64(filepath)
if err != nil {
err = fmt.Errorf("can't encode '%s' content as base64: %v", filepath, err)
return
}
// Prepare and send payload
return c.TorrentAdd(&TorrentAddPayload{MetaInfo: &b64})
}
// TorrentAdd allows to send an Add payload. If successful (torrent added or duplicate) torrent
// return value will only have HashString, ID and Name fields set up.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L373
func (c *Client) TorrentAdd(payload *TorrentAddPayload) (torrent *Torrent, err error) {
// Validate
if payload == nil {
err = errors.New("payload can't be nil")
return
}
if payload.Filename == nil && payload.MetaInfo == nil {
err = errors.New("Filename and MetaInfo can't be both nil")
return
}
// Send payload
var result torrentAddAnswer
if err = c.rpcCall("torrent-add", payload, &result); err != nil {
err = fmt.Errorf("'torrent-add' rpc method failed: %v", err)
return
}
// Extract results
if result.TorrentAdded != nil {
torrent = result.TorrentAdded
} else if result.TorrentDuplicate != nil {
torrent = result.TorrentDuplicate
} else {
err = errors.New("RPC call went fine but neither 'torrent-added' nor 'torrent-duplicate' result payload were found")
}
return
}
// TorrentAddPayload represents the data to send in order to add a torrent.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L37762
type TorrentAddPayload struct {
Cookies *string `json:"cookies"` // pointer to a string of one or more cookies
DownloadDir *string `json:"download-dir"` // path to download the torrent to
Filename *string `json:"filename"` // filename or URL of the .torrent file
MetaInfo *string `json:"metainfo"` // base64-encoded .torrent content
Paused *bool `json:"paused"` // if true, don't start the torrent
PeerLimit *int64 `json:"peer-limit"` // maximum number of peers
BandwidthPriority *int64 `json:"bandwidthPriority"` // torrent's bandwidth tr_priority_t
FilesWanted []int64 `json:"files-wanted"` // indices of file(s) to download
FilesUnwanted []int64 `json:"files-unwanted"` // indices of file(s) to not download
PriorityHigh []int64 `json:"priority-high"` // indices of high-priority file(s)
PriorityLow []int64 `json:"priority-low"` // indices of low-priority file(s)
PriorityNormal []int64 `json:"priority-normal"` // indices of normal-priority file(s)
}
// MarshalJSON allows to marshall into JSON only the non nil fields.
// It differs from 'omitempty' which also skip default values
// (as 0 or false which can be valid here).
func (tap *TorrentAddPayload) MarshalJSON() (data []byte, err error) {
// Build a payload with only the non nil fields
tspv := reflect.ValueOf(*tap)
tspt := tspv.Type()
cleanPayload := make(map[string]interface{}, tspt.NumField())
var currentValue reflect.Value
var currentStructField reflect.StructField
for i := 0; i < tspv.NumField(); i++ {
currentValue = tspv.Field(i)
currentStructField = tspt.Field(i)
if !currentValue.IsNil() {
cleanPayload[currentStructField.Tag.Get("json")] = currentValue.Interface()
}
}
// Marshall the clean payload
return json.Marshal(cleanPayload)
}
type torrentAddAnswer struct {
TorrentAdded *Torrent `json:"torrent-added"`
TorrentDuplicate *Torrent `json:"torrent-duplicate"`
}
func file2Base64(filename string) (b64 string, err error) {
// Try to open file
file, err := os.Open(filename)
if err != nil {
err = fmt.Errorf("can't open file: %v", err)
return
}
defer file.Close()
// Prepare encoder
buffer := new(bytes.Buffer)
encoder := base64.NewEncoder(base64.StdEncoding, buffer)
// Stream file to the encoder
if _, err = io.Copy(encoder, file); err != nil {
err = fmt.Errorf("can't copy file content into the base64 encoder: %v", err)
return
}
// Flush last bytes
if err = encoder.Close(); err != nil {
err = fmt.Errorf("can't flush last bytes of the base64 encoder: %v", err)
return
}
// Get the string form
b64 = buffer.String()
return
}

View File

@@ -0,0 +1,105 @@
package transmissionrpc
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"time"
)
/*
Torrent Mutators
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L105
*/
// TorrentSet apply a list of mutator(s) to a list of torrent ids.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L107
func (c *Client) TorrentSet(payload *TorrentSetPayload) (err error) {
// Validate
if payload == nil {
return errors.New("payload can't be nil")
}
if len(payload.IDs) == 0 {
return errors.New("there must be at least one ID")
}
// Send payload
if err = c.rpcCall("torrent-set", payload, nil); err != nil {
err = fmt.Errorf("'torrent-set' rpc method failed: %v", err)
}
return
}
// TorrentSetPayload contains all the mutators appliable on one torrent.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L111
type TorrentSetPayload struct {
BandwidthPriority *int64 `json:"bandwidthPriority"` // this torrent's bandwidth tr_priority_t
DownloadLimit *int64 `json:"downloadLimit"` // maximum download speed (KBps)
DownloadLimited *bool `json:"downloadLimited"` // true if "downloadLimit" is honored
FilesWanted []int64 `json:"files-wanted"` // indices of file(s) to download
FilesUnwanted []int64 `json:"files-unwanted"` // indices of file(s) to not download
HonorsSessionLimits *bool `json:"honorsSessionLimits"` // true if session upload limits are honored
IDs []int64 `json:"ids"` // torrent list
Location *string `json:"location"` // new location of the torrent's content
Peerlimit *int64 `json:"peer-limit"` // maximum number of peers
PriorityHigh []int64 `json:"priority-high"` // indices of high-priority file(s)
PriorityLow []int64 `json:"priority-low"` // indices of low-priority file(s)
PriorityNormal []int64 `json:"priority-normal"` // indices of normal-priority file(s)
QueuePosition *int64 `json:"queuePosition"` // position of this torrent in its queue [0...n)
SeedIdleLimit *time.Duration `json:"seedIdleLimit"` // torrent-level number of minutes of seeding inactivity
SeedIdleMode *int64 `json:"seedIdleMode"` // which seeding inactivity to use
SeedRatioLimit *float64 `json:"seedRatioLimit"` // torrent-level seeding ratio
SeedRatioMode *SeedRatioMode `json:"seedRatioMode"` // which ratio mode to use
TrackerAdd []string `json:"trackerAdd"` // strings of announce URLs to add
TrackerRemove []int64 `json:"trackerRemove"` // ids of trackers to remove
TrackerReplace []string `json:"trackerReplace"` // pairs of <trackerId/new announce URLs> (TODO: validate string value usable as is)
UploadLimit *int64 `json:"uploadLimit"` // maximum upload speed (KBps)
UploadLimited *bool `json:"uploadLimited"` // true if "uploadLimit" is honored
}
// MarshalJSON allows to marshall into JSON only the non nil fields.
// It differs from 'omitempty' which also skip default values
// (as 0 or false which can be valid here).
func (tsp *TorrentSetPayload) MarshalJSON() (data []byte, err error) {
// Build an intermediary payload with base types
type baseTorrentSetPayload TorrentSetPayload
tmp := struct {
SeedIdleLimit *int64 `json:"seedIdleLimit"`
*baseTorrentSetPayload
}{
baseTorrentSetPayload: (*baseTorrentSetPayload)(tsp),
}
if tsp.SeedIdleLimit != nil {
sil := int64(*tsp.SeedIdleLimit / time.Minute)
tmp.SeedIdleLimit = &sil
}
// Build a payload with only the non nil fields
tspv := reflect.ValueOf(tmp)
tspt := tspv.Type()
cleanPayload := make(map[string]interface{}, tspt.NumField())
var currentValue, nestedStruct, currentNestedValue reflect.Value
var currentStructField, currentNestedStructField reflect.StructField
var j int
for i := 0; i < tspv.NumField(); i++ {
currentValue = tspv.Field(i)
currentStructField = tspt.Field(i)
if !currentValue.IsNil() {
if currentStructField.Name == "baseTorrentSetPayload" {
// inherited/nested struct
nestedStruct = reflect.Indirect(currentValue)
for j = 0; j < nestedStruct.NumField(); j++ {
currentNestedValue = nestedStruct.Field(j)
currentNestedStructField = nestedStruct.Type().Field(j)
if !currentNestedValue.IsNil() {
cleanPayload[currentNestedStructField.Tag.Get("json")] = currentNestedValue.Interface()
}
}
} else {
// Overloaded field
cleanPayload[currentStructField.Tag.Get("json")] = currentValue.Interface()
}
}
}
// Marshall the clean payload
return json.Marshal(cleanPayload)
}

View File

@@ -0,0 +1,31 @@
package transmissionrpc
import (
"errors"
"fmt"
)
/*
Removing a Torrent
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L407
*/
// TorrentRemove allows to delete one or more torrents only or with their data.
func (c *Client) TorrentRemove(payload *TorrentRemovePayload) (err error) {
// Validate
if payload == nil {
return errors.New("payload can't be nil")
}
// Send payload
if err = c.rpcCall("torrent-remove", payload, nil); err != nil {
return fmt.Errorf("'torrent-remove' rpc method failed: %v", err)
}
return
}
// TorrentRemovePayload holds the torrent id(s) to delete with a data deletion flag.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L413
type TorrentRemovePayload struct {
IDs []int64 `json:"ids"`
DeleteLocalData bool `json:"delete-local-data"`
}

View File

@@ -0,0 +1,52 @@
package transmissionrpc
import (
"fmt"
)
/*
Rename a torrent path
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L438
*/
// TorrentRenamePath allows to rename torrent name or path.
// 'path' is the path to the file or folder that will be renamed.
// 'name' the file or folder's new name
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L440
func (c *Client) TorrentRenamePath(id int64, path, name string) (err error) {
if err = c.rpcCall("torrent-rename-path", torrentRenamePathPayload{
IDs: []int64{id},
Path: path,
Name: name,
}, nil); err != nil {
err = fmt.Errorf("'torrent-rename-path' rpc method failed: %v", err)
}
return
}
// TorrentRenamePathHash allows to rename torrent name or path by its hash.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L440
func (c *Client) TorrentRenamePathHash(hash, path, name string) (err error) {
if err = c.rpcCall("torrent-rename-path", torrentRenamePathHashPayload{
Hashes: []string{hash},
Path: path,
Name: name,
}, nil); err != nil {
err = fmt.Errorf("'torrent-rename-path' rpc method failed: %v", err)
}
return
}
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L447
type torrentRenamePathPayload struct {
IDs []int64 `json:"ids"` // the torrent torrent list, as described in 3.1 (must only be 1 torrent)
Path string `json:"path"` // the path to the file or folder that will be renamed
Name string `json:"name"` // the file or folder's new name
}
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L447
type torrentRenamePathHashPayload struct {
Hashes []string `json:"ids"` // the torrent torrent list, as described in 3.1 (must only be 1 torrent)
Path string `json:"path"` // the path to the file or folder that will be renamed
Name string `json:"name"` // the file or folder's new name
}

View File

@@ -0,0 +1,54 @@
package transmissionrpc
import (
"fmt"
)
/*
Moving a torrent
https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L421
*/
// TorrentSetLocation allows to set a new location for one or more torrents.
// 'location' is the new torrent location.
// 'move' if true, move from previous location. Otherwise, search "location" for file.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L423
func (c *Client) TorrentSetLocation(id int64, location string, move bool) (err error) {
if err = c.rpcCall("torrent-set-location", torrentSetLocationPayload{
IDs: []int64{id},
Location: location,
Move: move,
}, nil); err != nil {
err = fmt.Errorf("'torrent-set-location' rpc method failed: %v", err)
}
return
}
// TorrentSetLocationHash allows to set a new location for one or more torrents.
// 'location' is the new torrent location.
// 'move' if true, move from previous location. Otherwise, search "location" for file.
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L423
func (c *Client) TorrentSetLocationHash(hash, location string, move bool) (err error) {
if err = c.rpcCall("torrent-set-location", torrentSetLocationHashPayload{
Hashes: []string{hash},
Location: location,
Move: move,
}, nil); err != nil {
err = fmt.Errorf("'torrent-set-location' rpc method failed: %v", err)
}
return
}
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L427
type torrentSetLocationPayload struct {
IDs []int64 `json:"ids"` // torrent list
Location string `json:"location"` // the new torrent location
Move bool `json:"move"` // if true, move from previous location. Otherwise, search "location" for files
}
// https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt#L427
type torrentSetLocationHashPayload struct {
Hashes []string `json:"ids"` // torrent list
Location string `json:"location"` // the new torrent location
Move bool `json:"move"` // if true, move from previous location. Otherwise, search "location" for files
}