forked from hekmon/transmissionrpc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtorrent_add.go
157 lines (148 loc) · 5.72 KB
/
torrent_add.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package transmissionrpc
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"reflect"
)
/*
Adding a Torrent
https://github.com/transmission/transmission/blob/4.0.3/docs/rpc-spec.md#34-adding-a-torrent
*/
// TorrentAddFileDownloadDir is wrapper to directly add a torrent file (it handles the base64 encoding
// and payload generation) to a DownloadDir (not the default download dir). If successful (torrent added
// or duplicate) torrent return value will only have HashString, ID and Name fields set up.
func (c *Client) TorrentAddFileDownloadDir(ctx context.Context, filepath, downloaddir string) (torrent Torrent, err error) {
// Validate filepath
if filepath == "" {
err = errors.New("filepath can't be empty")
return
}
// Validate downloaddir
if downloaddir == "" {
err = errors.New("downloaddir 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: %w", filepath, err)
return
}
// Prepare and send payload
return c.TorrentAdd(ctx, TorrentAddPayload{MetaInfo: &b64, DownloadDir: &downloaddir})
}
// 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(ctx context.Context, 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: %w", filepath, err)
return
}
// Prepare and send payload
return c.TorrentAdd(ctx, 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.
func (c *Client) TorrentAdd(ctx context.Context, payload TorrentAddPayload) (torrent Torrent, err error) {
// Validate
if payload.Filename == nil && payload.MetaInfo == nil {
err = errors.New("fields Filename and MetaInfo can't be both nil")
return
}
// Send payload
var result torrentAddAnswer
if err = c.rpcCall(ctx, "torrent-add", payload, &result); err != nil {
err = fmt.Errorf("'torrent-add' rpc method failed: %w", 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.
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
Labels []string `json:"labels"` // Labels for the torrent
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"`
}
// File2Base64 returns the base64 encoding of the file provided by filename.
// This can then be passed as MetaInfo in TorrentAddPayload.
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: %w", 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: %w", err)
return
}
// Flush last bytes
if err = encoder.Close(); err != nil {
err = fmt.Errorf("can't flush last bytes of the base64 encoder: %w", err)
return
}
// Get the string form
b64 = buffer.String()
return
}