mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
136 lines
4.7 KiB
Go
136 lines
4.7 KiB
Go
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
|
|
}
|