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

14
vendor/github.com/hekmon/cunits/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,14 @@
# 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/

21
vendor/github.com/hekmon/cunits/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.

60
vendor/github.com/hekmon/cunits/README.md generated vendored Normal file
View File

@@ -0,0 +1,60 @@
# ComputerUnits
[![GoDoc](https://godoc.org/github.com/hekmon/cunits?status.svg)](https://godoc.org/github.com/hekmon/cunits)
ComputerUnits allows to manipulate binary and decimal representations of bits and bytes.
## Constants example
```golang
fmt.Println(cunits.Kbit)
fmt.Println(cunits.Kibit)
fmt.Println(cunits.KB)
fmt.Println(cunits.KiB)
fmt.Printf("1000 MiB = %f MB\n", float64(1000)*cunits.MiB/cunits.MB)
```
will output:
```
1000
1024
8000
8192
1000 MiB = 1048.576000 MB
```
## Custom type example
```golang
size := cunits.Bit(58) * cunits.MiB
fmt.Println(size.Mbit())
fmt.Println(size.GiBString())
```
will output:
```
486.539264
0.06 GiB
```
## Parsing example
```golang
size, err := cunits.Parse("7632 MiB")
if err != nil {
panic(err)
}
fmt.Println(size)
fmt.Println(size.KiB())
fmt.Println(size.KbitString())
```
will output:
```
7.45 GiB
7.815168e+06
64021856.26 Kbit
```

424
vendor/github.com/hekmon/cunits/bits.go generated vendored Normal file
View File

@@ -0,0 +1,424 @@
package cunits
import "fmt"
// Bits represent a size in bits
type Bits uint64
// String allows direct reprensetation of Bit by calling GetHumanSizeRepresentation()
func (size Bits) String() string {
return size.GetHumanSizeRepresentation()
}
// GetHumanSizeRepresentation returns the size in a human readable binary prefix of bytes format
func (size Bits) GetHumanSizeRepresentation() string {
// if size >= YiB {
// return size.YiBString()
// }
// if size >= ZiB {
// return size.ZiBString()
// }
if size >= EiB {
return size.EiBString()
}
if size >= PiB {
return size.PiBString()
}
if size >= TiB {
return size.TiBString()
}
if size >= GiB {
return size.GiBString()
}
if size >= MiB {
return size.MiBString()
}
if size >= KiB {
return size.KiBString()
}
return size.ByteString()
}
// GetHumanSpeedRepresentation returns the size in a human readable decimal prefix of bits format
func (size Bits) GetHumanSpeedRepresentation() string {
// if size >= Ybit {
// return size.YbitString()
// }
// if size >= Zbit {
// return size.ZbitString()
// }
if size >= Ebit {
return size.EbitString()
}
if size >= Pbit {
return size.PbitString()
}
if size >= Tbit {
return size.TbitString()
}
if size >= Gbit {
return size.GbitString()
}
if size >= Mbit {
return size.MbitString()
}
if size >= Kbit {
return size.KbitString()
}
return size.BitString()
}
/*
Base forms
*/
// BitString returns the size in bit with unit suffix
func (size Bits) BitString() string {
return fmt.Sprintf("%d b", size)
}
// Byte returns the size in byte
func (size Bits) Byte() float64 {
return float64(size) / 8
}
// ByteString returns the size in byte with unit suffix
func (size Bits) ByteString() string {
return fmt.Sprintf("%.2f B", size.Byte())
}
/*
Decimal prefix of Bit
*/
// Kbit returns the size in kilobit
func (size Bits) Kbit() float64 {
return float64(size) / Kbit
}
// KbitString returns the size in kilobit with unit suffix
func (size Bits) KbitString() string {
return fmt.Sprintf("%.2f Kb", size.Kbit())
}
// Mbit returns the size in megabit
func (size Bits) Mbit() float64 {
return float64(size) / Mbit
}
// MbitString returns the size in megabit with unit suffix
func (size Bits) MbitString() string {
return fmt.Sprintf("%.2f Mb", size.Mbit())
}
// Gbit returns the size in gigabit
func (size Bits) Gbit() float64 {
return float64(size) / Gbit
}
// GbitString returns the size in gigabit with unit suffix
func (size Bits) GbitString() string {
return fmt.Sprintf("%.2f Gb", size.Gbit())
}
// Tbit returns the size in terabit
func (size Bits) Tbit() float64 {
return float64(size) / Tbit
}
// TbitString returns the size in terabit with unit suffix
func (size Bits) TbitString() string {
return fmt.Sprintf("%.2f Tb", size.Tbit())
}
// Pbit returns the size in petabit
func (size Bits) Pbit() float64 {
return float64(size) / Pbit
}
// PbitString returns the size in petabit with unit suffix
func (size Bits) PbitString() string {
return fmt.Sprintf("%.2f Pb", size.Pbit())
}
// Ebit returns the size in exabit
func (size Bits) Ebit() float64 {
return float64(size) / Ebit
}
// EbitString returns the size in exabit with unit suffix
func (size Bits) EbitString() string {
return fmt.Sprintf("%.2f Eb", size.Ebit())
}
// Zbit returns the size in zettabit
func (size Bits) Zbit() float64 {
return float64(size) / Zbit
}
// ZbitString returns the size in zettabit with unit suffix (carefull with sub zeros !)
func (size Bits) ZbitString() string {
return fmt.Sprintf("%f Zb", size.Zbit())
}
// Ybit returns the size in yottabit
func (size Bits) Ybit() float64 {
return float64(size) / Ybit
}
// YbitString returns the size in yottabit with unit suffix (carefull with sub zeros !)
func (size Bits) YbitString() string {
return fmt.Sprintf("%f Yb", size.Ybit())
}
/*
Binary prefix of Bit
*/
// Kibit returns the size in kibibit
func (size Bits) Kibit() float64 {
return float64(size) / Kibit
}
// KibitString returns the size in kibibit with unit suffix
func (size Bits) KibitString() string {
return fmt.Sprintf("%.2f Kib", size.Kibit())
}
// Mibit returns the size in mebibit
func (size Bits) Mibit() float64 {
return float64(size) / Mibit
}
// MibitString returns the size in mebibit with unit suffix
func (size Bits) MibitString() string {
return fmt.Sprintf("%.2f Mib", size.Mibit())
}
// Gibit returns the size in gibibit
func (size Bits) Gibit() float64 {
return float64(size) / Gibit
}
// GibitString returns the size in gibibit with unit suffix
func (size Bits) GibitString() string {
return fmt.Sprintf("%.2f Gib", size.Gibit())
}
// Tibit returns the size in tebibit
func (size Bits) Tibit() float64 {
return float64(size) / Tibit
}
// TibitString returns the size in tebibit with unit suffix
func (size Bits) TibitString() string {
return fmt.Sprintf("%.2f Tib", size.Tibit())
}
// Pibit returns the size in pebibit
func (size Bits) Pibit() float64 {
return float64(size) / Pibit
}
// PibitString returns the size in pebibit with unit suffix
func (size Bits) PibitString() string {
return fmt.Sprintf("%.2f Pib", size.Pibit())
}
// Eibit returns the size in exbibit
func (size Bits) Eibit() float64 {
return float64(size) / Eibit
}
// EibitString returns the size in exbibit with unit suffix
func (size Bits) EibitString() string {
return fmt.Sprintf("%.2f Eib", size.Eibit())
}
// Zibit returns the size in zebibit
func (size Bits) Zibit() float64 {
return float64(size) / Zibit
}
// ZibitString returns the size in zebibit with unit suffix (carefull with sub zeros !)
func (size Bits) ZibitString() string {
return fmt.Sprintf("%f Zib", size.Zibit())
}
// Yibit returns the size in yobibit
func (size Bits) Yibit() float64 {
return float64(size) / Yibit
}
// YibitString returns the size in yobibit with unit suffix (carefull with sub zeros !)
func (size Bits) YibitString() string {
return fmt.Sprintf("%f Yib", size.Yibit())
}
/*
Decimal prefix of bytes
*/
// KB returns the size in kilobyte
func (size Bits) KB() float64 {
return float64(size) / KB
}
// KBString returns the size in kilobyte with unit suffix
func (size Bits) KBString() string {
return fmt.Sprintf("%.2f KB", size.KB())
}
// MB returns the size in megabyte
func (size Bits) MB() float64 {
return float64(size) / MB
}
// MBString returns the size in megabyte with unit suffix
func (size Bits) MBString() string {
return fmt.Sprintf("%.2f MB", size.MB())
}
// GB returns the size in gigabyte
func (size Bits) GB() float64 {
return float64(size) / GB
}
// GBString returns the size in gigabyte with unit suffix
func (size Bits) GBString() string {
return fmt.Sprintf("%.2f GB", size.GB())
}
// TB returns the size in terabyte
func (size Bits) TB() float64 {
return float64(size) / TB
}
// TBString returns the size in terabyte with unit suffix
func (size Bits) TBString() string {
return fmt.Sprintf("%.2f TB", size.TB())
}
// PB returns the size in petabyte
func (size Bits) PB() float64 {
return float64(size) / PB
}
// PBString returns the size in petabyte with unit suffix
func (size Bits) PBString() string {
return fmt.Sprintf("%.2f PB", size.PB())
}
// EB returns the size in exabyte
func (size Bits) EB() float64 {
return float64(size) / EB
}
// EBString returns the size in exabyte with unit suffix
func (size Bits) EBString() string {
return fmt.Sprintf("%.2f EB", size.EB())
}
// ZB returns the size in zettabyte
func (size Bits) ZB() float64 {
return float64(size) / ZB
}
// ZBString returns the size in zettabyte with unit suffix (carefull with sub zeros !)
func (size Bits) ZBString() string {
return fmt.Sprintf("%f ZB", size.ZB())
}
// YB returns the size in yottabyte
func (size Bits) YB() float64 {
return float64(size) / YB
}
// YBString returns the size in yottabyte with unit suffix (carefull with sub zeros !)
func (size Bits) YBString() string {
return fmt.Sprintf("%f YB", size.YB())
}
/*
Binary prefix of bytes
*/
// KiB returns the size in kibibyte
func (size Bits) KiB() float64 {
return float64(size) / KiB
}
// KiBString returns the size in kibibyte with unit suffix
func (size Bits) KiBString() string {
return fmt.Sprintf("%.2f KiB", size.KiB())
}
// MiB returns the size in mebibyte
func (size Bits) MiB() float64 {
return float64(size) / MiB
}
// MiBString returns the size in mebibyte with unit suffix
func (size Bits) MiBString() string {
return fmt.Sprintf("%.2f MiB", size.MiB())
}
// GiB returns the size in gibibyte
func (size Bits) GiB() float64 {
return float64(size) / GiB
}
// GiBString returns the size in gibibyte with unit suffix
func (size Bits) GiBString() string {
return fmt.Sprintf("%.2f GiB", size.GiB())
}
// TiB returns the size in tebibyte
func (size Bits) TiB() float64 {
return float64(size) / TiB
}
// TiBString returns the size in tebibyte wit unit suffix
func (size Bits) TiBString() string {
return fmt.Sprintf("%.2f TiB", size.TiB())
}
// PiB returns the size in pebibyte
func (size Bits) PiB() float64 {
return float64(size) / PiB
}
// PiBString returns the size in pebibyte with unit suffix
func (size Bits) PiBString() string {
return fmt.Sprintf("%.2f PiB", size.PiB())
}
// EiB returns the size in exbibyte
func (size Bits) EiB() float64 {
return float64(size) / EiB
}
// EiBString returns the size in exbibyte with unit suffix (carefull with sub zeros !)
func (size Bits) EiBString() string {
return fmt.Sprintf("%f EiB", size.EiB())
}
// ZiB returns the size in zebibyte
func (size Bits) ZiB() float64 {
return float64(size) / ZiB
}
// ZiBString returns the size in zebibyte with unit suffix (carefull with sub zeros !)
func (size Bits) ZiBString() string {
return fmt.Sprintf("%f ZiB", size.ZiB())
}
// YiB returns the size in yobibyte
func (size Bits) YiB() float64 {
return float64(size) / YiB
}
// YiBString returns the size in yobibyte with unit suffix (carefull with sub zeros !)
func (size Bits) YiBString() string {
return fmt.Sprintf("%f YiB", size.YiB())
}

84
vendor/github.com/hekmon/cunits/consts.go generated vendored Normal file
View File

@@ -0,0 +1,84 @@
package cunits
// Byte represent a byte
const Byte = 8
// Decimal prefix of bits
const (
// Kbit represents a kilobit
Kbit = 1000
// Mbit represents a megabit
Mbit = 1000000
// Gbit represents a gigabit
Gbit = 1000000000
// Tbit represents a terabit
Tbit = 1000000000000
// Pbit represents a petabit
Pbit = 1000000000000000
// Ebit represents an exabit
Ebit = 1000000000000000000
// Zbit represents a zettabit (overflows int64)
Zbit = 1000000000000000000000
// Ybit represents a yottabit (overflows int64)
Ybit = 1000000000000000000000000
)
// Binary prefix of bits
const (
// Kibit represents a kibibit
Kibit = 1 << 10
// Mibit represents a mebibit
Mibit = 1 << 20
// Gibit represents a gibibit
Gibit = 1 << 30
// Tibit represents a tebibit
Tibit = 1 << 40
// Pibit represents a pebibit
Pibit = 1 << 50
// Eibit represents an exbibit
Eibit = 1 << 60
// Zibit represents a zebibit (overflows int64)
Zibit = 1 << 70
// Yibit represents a yobibit (overflows int64)
Yibit = 1 << 80
)
// Decimal prefix of bytes
const (
// KB represents a kilobyte
KB = Kbit * Byte
// MB represents a megabyte
MB = Mbit * Byte
// GB represents a gigabyte
GB = Gbit * Byte
// TB represents a terabyte
TB = Tbit * Byte
// PB represents a petabyte
PB = Pbit * Byte
// EB represents an exabyte
EB = Ebit * Byte
// ZB represents a zettabyte (overflows int64)
ZB = Zbit * Byte
// YB represents a yottabyte (overflows int64)
YB = Ybit * Byte
)
// Binary prefix of bytes
const (
// KiB represents a kibibyte
KiB = Kibit * Byte
// MiB represents a mebibyte
MiB = Mibit * Byte
// GiB represents a gibibyte
GiB = Gibit * Byte
// TiB represents a tebibyte
TiB = Tibit * Byte
// PiB represents a pebibyte
PiB = Pibit * Byte
// EiB represents an exbibyte (overflows int64)
EiB = Eibit * Byte
// ZiB represents a zebibyte (overflows int64)
ZiB = Zbit * Byte
// YiB represents a yobibyte (overflows int64)
YiB = Ybit * Byte
)

292
vendor/github.com/hekmon/cunits/import.go generated vendored Normal file
View File

@@ -0,0 +1,292 @@
package cunits
import (
"fmt"
"regexp"
"strconv"
"strings"
)
const parseRegex = "^([0-9,]+(\\.[0-9]+)?) ?(([KMGTPEZY]i?)?(B|bit))$"
var sizeMatch = regexp.MustCompile(parseRegex)
// Parse parses un string representation of a number with a suffix
func Parse(sizeSuffix string) (size Bits, err error) {
// Does it match ?
match := sizeMatch.FindSubmatch([]byte(sizeSuffix))
if match == nil {
err = fmt.Errorf("string does not match the parsing regex: %s", parseRegex)
return
}
if len(match) < 4 {
err = fmt.Errorf("regex matching did not return enough groups")
return
}
// Extract number
num, err := strconv.ParseFloat(strings.Replace(string(match[1]), ",", "", -1), 64)
if err != nil {
err = fmt.Errorf("extracted number '%s' can't be parsed as float64: %v", string(match[1]), err)
return
}
// Findout the unit
switch string(match[3]) {
case "bit":
size = Bits(num)
case "B":
size = ImportInByte(num)
// Decimal prefix of bits
case "Kbit":
size = ImportInKbit(num)
case "Mbit":
size = ImportInMbit(num)
case "Gbit":
size = ImportInGbit(num)
case "Tbit":
size = ImportInTbit(num)
case "Pbit":
size = ImportInPbit(num)
case "Ebit":
size = ImportInEbit(num)
case "Zbit":
size = ImportInZbit(num)
case "Ybit":
size = ImportInYbit(num)
// Binary prefix of bits
case "Kibit":
size = ImportInKibit(num)
case "Mibit":
size = ImportInMibit(num)
case "Gibit":
size = ImportInGibit(num)
case "Tibit":
size = ImportInTibit(num)
case "Pibit":
size = ImportInPibit(num)
case "Eibit":
size = ImportInEibit(num)
case "Zibit":
size = ImportInZibit(num)
case "Yibit":
size = ImportInYibit(num)
// Decimal prefix of bytes
case "KB":
size = ImportInKB(num)
case "MB":
size = ImportInMB(num)
case "GB":
size = ImportInGB(num)
case "TB":
size = ImportInTB(num)
case "PB":
size = ImportInPB(num)
case "EB":
size = ImportInEB(num)
case "ZB":
size = ImportInZB(num)
case "YB":
size = ImportInYB(num)
// Binary prefix of bytes
case "KiB":
size = ImportInKiB(num)
case "MiB":
size = ImportInMiB(num)
case "GiB":
size = ImportInGiB(num)
case "TiB":
size = ImportInTiB(num)
case "PiB":
size = ImportInPiB(num)
case "EiB":
size = ImportInEiB(num)
case "ZiB":
size = ImportInZiB(num)
case "YiB":
size = ImportInYiB(num)
// or not
default:
err = fmt.Errorf("extracted unit '%s' is unknown", string(match[3]))
}
return
}
// ImportInByte imports a number in byte
func ImportInByte(sizeInByte float64) Bits {
return Bits(sizeInByte * Byte)
}
/*
Decimal prefix of bits
*/
// ImportInKbit imports a number in kilobit
func ImportInKbit(sizeInKbit float64) Bits {
return Bits(sizeInKbit * Kbit)
}
// ImportInMbit imports a number in megabit
func ImportInMbit(sizeInMbit float64) Bits {
return Bits(sizeInMbit * Mbit)
}
// ImportInGbit imports a number in gigabit
func ImportInGbit(sizeInGbit float64) Bits {
return Bits(sizeInGbit * Gbit)
}
// ImportInTbit imports a number in terabit
func ImportInTbit(sizeInTbit float64) Bits {
return Bits(sizeInTbit * Tbit)
}
// ImportInPbit imports a number in petabit
func ImportInPbit(sizeInPbit float64) Bits {
return Bits(sizeInPbit * Pbit)
}
// ImportInEbit imports a number in exabit
func ImportInEbit(sizeInEbit float64) Bits {
return Bits(sizeInEbit * Ebit)
}
// ImportInZbit imports a number in zettabit (sizeInZbit better < 1)
func ImportInZbit(sizeInZbit float64) Bits {
return Bits(sizeInZbit * Zbit)
}
// ImportInYbit imports a number in yottabit (sizeInYbit better < 1)
func ImportInYbit(sizeInYbit float64) Bits {
return Bits(sizeInYbit * Ybit)
}
/*
Binary prefix of bits
*/
// ImportInKibit imports a number in kibibit
func ImportInKibit(sizeInKibit float64) Bits {
return Bits(sizeInKibit * Kibit)
}
// ImportInMibit imports a number in mebibit
func ImportInMibit(sizeInMibit float64) Bits {
return Bits(sizeInMibit * Mibit)
}
// ImportInGibit imports a number in gibibit
func ImportInGibit(sizeInGibit float64) Bits {
return Bits(sizeInGibit * Gibit)
}
// ImportInTibit imports a number in tebibit
func ImportInTibit(sizeInTibit float64) Bits {
return Bits(sizeInTibit * Tibit)
}
// ImportInPibit imports a number in pebibit
func ImportInPibit(sizeInPibit float64) Bits {
return Bits(sizeInPibit * Pibit)
}
// ImportInEibit imports a number in exbibit
func ImportInEibit(sizeInEibit float64) Bits {
return Bits(sizeInEibit * Eibit)
}
// ImportInZibit imports a number in zebibit (sizeInZibit better < 1)
func ImportInZibit(sizeInZibit float64) Bits {
return Bits(sizeInZibit * Zibit)
}
// ImportInYibit imports a number in yobibit (sizeInYibit better < 1)
func ImportInYibit(sizeInYibit float64) Bits {
return Bits(sizeInYibit * Yibit)
}
/*
Decimal prefix of bytes
*/
// ImportInKB imports a number in kilobyte
func ImportInKB(sizeInKB float64) Bits {
return Bits(sizeInKB * KB)
}
// ImportInMB imports a number in megabyte
func ImportInMB(sizeInMB float64) Bits {
return Bits(sizeInMB * MB)
}
// ImportInGB imports a number in gigabyte
func ImportInGB(sizeInGB float64) Bits {
return Bits(sizeInGB * GB)
}
// ImportInTB imports a number in terabyte
func ImportInTB(sizeInTB float64) Bits {
return Bits(sizeInTB * TB)
}
// ImportInPB imports a number in petabyte
func ImportInPB(sizeInPB float64) Bits {
return Bits(sizeInPB * PB)
}
// ImportInEB imports a number in exabyte
func ImportInEB(sizeInEB float64) Bits {
return Bits(sizeInEB * EB)
}
// ImportInZB imports a number in zettabyte (sizeInZB better < 1)
func ImportInZB(sizeInZB float64) Bits {
return Bits(sizeInZB * ZB)
}
// ImportInYB imports a number in yottabyte (sizeInYB better < 1)
func ImportInYB(sizeInYB float64) Bits {
return Bits(sizeInYB * YB)
}
/*
Binary prefix of bytes
*/
// ImportInKiB imports a number in kilobyte
func ImportInKiB(sizeInKiB float64) Bits {
return Bits(sizeInKiB * KiB)
}
// ImportInMiB imports a number in megabyte
func ImportInMiB(sizeInMiB float64) Bits {
return Bits(sizeInMiB * MiB)
}
// ImportInGiB imports a number in gigabyte
func ImportInGiB(sizeInGiB float64) Bits {
return Bits(sizeInGiB * GiB)
}
// ImportInTiB imports a number in terabyte
func ImportInTiB(sizeInTiB float64) Bits {
return Bits(sizeInTiB * TiB)
}
// ImportInPiB imports a number in petabyte
func ImportInPiB(sizeInPiB float64) Bits {
return Bits(sizeInPiB * PiB)
}
// ImportInEiB imports a number in exabyte (sizeInEiB better < 1)
func ImportInEiB(sizeInEiB float64) Bits {
return Bits(sizeInEiB * EiB)
}
// ImportInZiB imports a number in zettabyte (sizeInZiB better < 1)
func ImportInZiB(sizeInZiB float64) Bits {
return Bits(sizeInZiB * ZiB)
}
// ImportInYiB imports a number in yottabyte (sizeInYiB better < 1)
func ImportInYiB(sizeInYiB float64) Bits {
return Bits(sizeInYiB * YiB)
}

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
}