New Upstream Release - golang-github-putdotio-go-putio
Ready changes
Summary
Merged new upstream version: 0.0~git20221111.2e102ad+ds (was: 0.0~git20190822.19b9c63).
Resulting package
Built on 2022-12-11T06:25 (took 3m21s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-putdotio-go-putio-dev
Lintian Result
- golang-github-putdotio-go-putio-dev_0.0~git20221111.2e102ad+ds-1~jan+nur1_all.deb
- golang-github-putdotio-go-putio_0.0~git20221111.2e102ad+ds-1~jan+nur1.dsc
- golang-github-putdotio-go-putio_0.0~git20221111.2e102ad+ds-1~jan+nur1_amd64.buildinfo
- golang-github-putdotio-go-putio_0.0~git20221111.2e102ad+ds-1~jan+nur1_amd64.changes
Diff
diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml
new file mode 100644
index 0000000..0819247
--- /dev/null
+++ b/.github/workflows/go-test.yml
@@ -0,0 +1,21 @@
+name: Run go tests
+on:
+ push:
+ pull_request:
+
+jobs:
+ test:
+ name: Run tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Setup Go 1.13
+ uses: actions/setup-go@v1
+ with:
+ go-version: 1.13
+ id: go
+ - name: Checkout code
+ uses: actions/checkout@v1
+ - name: Get dependencies
+ run: go mod download
+ - name: Run tests
+ run: go test -p 1 -v -race ./...
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
new file mode 100644
index 0000000..033b84b
--- /dev/null
+++ b/.github/workflows/golangci-lint.yml
@@ -0,0 +1,15 @@
+name: Golang CI Linter
+on:
+ push:
+ pull_request:
+
+jobs:
+ golangci:
+ name: lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v2
+ with:
+ version: latest
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 1a0bbea..455ac23 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,6 @@
+arch:
+ - amd64
+ - ppc64le
language: go
go:
- tip
diff --git a/README.md b/README.md
index 3afe2c1..964c772 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,20 @@
-# putio [![Build Status](https://travis-ci.org/putdotio/go-putio.svg?branch=master)](https://travis-ci.org/putdotio/go-putio)
+[![Golang CI Linter](https://github.com/putdotio/go-putio/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/putdotio/go-putio/actions/workflows/golangci-lint.yml)
+[![Golang Tests](https://github.com/putdotio/go-putio/actions/workflows/go-test.yml/badge.svg)](https://github.com/putdotio/go-putio/actions/workflows/go-test.yml)
+
+
+# putio
putio is a Go client library for accessing the [Put.io API v2](https://api.put.io/v2/docs).
## Documentation
-Available on [GoDoc](http://godoc.org/github.com/putdotio/go-putio/putio)
+Available on [GoDoc](http://godoc.org/github.com/putdotio/go-putio)
## Install
```sh
-go get -u github.com/putdotio/go-putio/putio"
+go get github.com/putdotio/go-putio@latest
+go get golang.org/x/oauth2@latest
```
## Usage
@@ -23,21 +28,21 @@ import (
"context"
"golang.org/x/oauth2"
- "github.com/putdotio/go-putio/putio"
+ "github.com/putdotio/go-putio"
)
func main() {
oauthToken := "<YOUR-TOKEN-HERE>"
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: oauthToken})
- oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
+ oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
client := putio.NewClient(oauthClient)
const rootDir = 0
- root, err := client.Files.Get(context.Background(), rootDir)
+ root, err := client.Files.Get(context.TODO(), rootDir)
if err != nil {
log.Fatal(err)
}
- fmt.Println(root.Filename)
+ fmt.Println(root.Name)
}
```
diff --git a/putio/account.go b/account.go
similarity index 64%
rename from putio/account.go
rename to account.go
index cd39638..ea298ae 100644
--- a/putio/account.go
+++ b/account.go
@@ -1,6 +1,9 @@
package putio
-import "context"
+import (
+ "context"
+ "net/http"
+)
// AccountService is the service to gather information about user account.
type AccountService struct {
@@ -9,15 +12,15 @@ type AccountService struct {
// Info retrieves user account information.
func (a *AccountService) Info(ctx context.Context) (AccountInfo, error) {
- req, err := a.client.NewRequest(ctx, "GET", "/v2/account/info", nil)
+ req, err := a.client.NewRequest(ctx, http.MethodGet, "/v2/account/info", nil)
if err != nil {
- return AccountInfo{}, nil
+ return AccountInfo{}, err
}
var r struct {
Info AccountInfo
}
- _, err = a.client.Do(req, &r)
+ _, err = a.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return AccountInfo{}, err
}
@@ -26,14 +29,14 @@ func (a *AccountService) Info(ctx context.Context) (AccountInfo, error) {
// Settings retrieves user preferences.
func (a *AccountService) Settings(ctx context.Context) (Settings, error) {
- req, err := a.client.NewRequest(ctx, "GET", "/v2/account/settings", nil)
+ req, err := a.client.NewRequest(ctx, http.MethodGet, "/v2/account/settings", nil)
if err != nil {
- return Settings{}, nil
+ return Settings{}, err
}
var r struct {
Settings Settings
}
- _, err = a.client.Do(req, &r)
+ _, err = a.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return Settings{}, err
}
diff --git a/putio/account_test.go b/account_test.go
similarity index 97%
rename from putio/account_test.go
rename to account_test.go
index 1a35de2..6253ef9 100644
--- a/putio/account_test.go
+++ b/account_test.go
@@ -58,7 +58,7 @@ func TestAccount_Info(t *testing.T) {
}
`
mux.HandleFunc("/v2/account/info", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -106,7 +106,7 @@ func TestAccount_Settings(t *testing.T) {
}
`
mux.HandleFunc("/v2/account/settings", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
diff --git a/putio/client.go b/client.go
similarity index 65%
rename from putio/client.go
rename to client.go
index ec0d9d7..919ff53 100644
--- a/putio/client.go
+++ b/client.go
@@ -8,6 +8,13 @@ import (
"io/ioutil"
"net/http"
"net/url"
+ "strings"
+ "time"
+)
+
+// Constants.
+const (
+ DefaultClientTimeout = 30 * time.Second
)
const (
@@ -15,6 +22,7 @@ const (
defaultMediaType = "application/json"
defaultBaseURL = "https://api.put.io"
defaultUploadURL = "https://upload.put.io"
+ defaultTusURL = "https://upload.put.io/files/"
)
// Client manages communication with Put.io v2 API.
@@ -25,15 +33,18 @@ type Client struct {
// Base URL for API requests
BaseURL *url.URL
- // base url for upload requests
- uploadURL *url.URL
-
// User agent for client
UserAgent string
+ // Override host header for API requests
+ Host string
+
// ExtraHeaders are passed to the API server on every request.
ExtraHeaders http.Header
+ // Timeout for HTTP requests. Zero means no timeout.
+ Timeout time.Duration
+
// Services used for communicating with the API
Account *AccountService
Files *FilesService
@@ -41,6 +52,8 @@ type Client struct {
Zips *ZipsService
Friends *FriendsService
Events *EventsService
+ Config *ConfigService
+ Upload *UploadService
}
// NewClient returns a new Put.io API client, using the htttpClient, which must
@@ -52,13 +65,12 @@ func NewClient(httpClient *http.Client) *Client {
}
baseURL, _ := url.Parse(defaultBaseURL)
- uploadURL, _ := url.Parse(defaultUploadURL)
c := &Client{
client: httpClient,
BaseURL: baseURL,
- uploadURL: uploadURL,
UserAgent: defaultUserAgent,
ExtraHeaders: make(http.Header),
+ Timeout: DefaultClientTimeout,
}
c.Account = &AccountService{client: c}
@@ -67,20 +79,26 @@ func NewClient(httpClient *http.Client) *Client {
c.Zips = &ZipsService{client: c}
c.Friends = &FriendsService{client: c}
c.Events = &EventsService{client: c}
+ c.Config = &ConfigService{client: c}
+ c.Upload = &UploadService{client: c}
return c
}
+// ValidateToken validates user's OAuth Token.
func (c *Client) ValidateToken(ctx context.Context) (userID *int64, err error) {
- req, err := c.NewRequest(ctx, "GET", "/v2/oauth2/validate", nil)
+ req, err := c.NewRequest(ctx, http.MethodGet, "/v2/oauth2/validate", nil)
if err != nil {
return
}
var r struct {
UserID *int64 `json:"user_id"`
}
- _, err = c.Do(req, &r)
- return r.UserID, err
+ _, err = c.Do(req, &r) // nolint:bodyclose
+ if err != nil {
+ return nil, err
+ }
+ return r.UserID, nil
}
// NewRequest creates an API request. A relative URL can be provided via
@@ -88,26 +106,31 @@ func (c *Client) ValidateToken(ctx context.Context) (userID *int64, err error) {
func (c *Client) NewRequest(ctx context.Context, method, relURL string, body io.Reader) (*http.Request, error) {
rel, err := url.Parse(relURL)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
+ // Workaround for upload endpoints. Upload server is different than API server.
var u *url.URL
- // XXX: workaroud for upload endpoint. upload method has a different base url,
- // so we've a special case for testing purposes.
- if relURL == "/v2/files/upload" {
- u = c.uploadURL.ResolveReference(rel)
- } else {
+ switch {
+ case relURL == "$upload$":
+ u, _ = url.Parse(defaultUploadURL)
+ case relURL == "$upload-tus$":
+ u, _ = url.Parse(defaultTusURL)
+ case strings.HasPrefix(relURL, "http://") || strings.HasPrefix(relURL, "https://"):
+ u = rel
+ default:
u = c.BaseURL.ResolveReference(rel)
}
- req, err := http.NewRequest(method, u.String(), body)
+ req, err := http.NewRequestWithContext(ctx, method, u.String(), body)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
-
- req = req.WithContext(ctx)
req.Header.Set("Accept", defaultMediaType)
req.Header.Set("User-Agent", c.UserAgent)
+ if c.Host != "" {
+ req.Host = c.Host
+ }
// merge headers with extra headers
for header, values := range c.ExtraHeaders {
@@ -125,15 +148,21 @@ func (c *Client) NewRequest(ctx context.Context, method, relURL string, body io.
// v is nil. If v is nil, response body is not closed and the body can be used
// for streaming.
func (c *Client) Do(r *http.Request, v interface{}) (*http.Response, error) {
+ if c.Timeout > 0 {
+ ctx, cancel := context.WithTimeout(r.Context(), c.Timeout)
+ defer cancel()
+ r = r.WithContext(ctx)
+ }
+
resp, err := c.client.Do(r)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
err = checkResponse(resp)
if err != nil {
// close the body at all times if there is an http error
- resp.Body.Close()
+ _ = resp.Body.Close()
return resp, err
}
@@ -142,11 +171,13 @@ func (c *Client) Do(r *http.Request, v interface{}) (*http.Response, error) {
}
// close the body for all cases from here
- defer resp.Body.Close()
+ defer func() {
+ _ = resp.Body.Close()
+ }()
err = json.NewDecoder(resp.Body).Decode(v)
if err != nil {
- return resp, err
+ return resp, fmt.Errorf("%w", err)
}
return resp, nil
@@ -156,25 +187,22 @@ func (c *Client) Do(r *http.Request, v interface{}) (*http.Response, error) {
// status code is not in success range, it will try to return a structured
// error.
func checkResponse(r *http.Response) error {
- status := r.StatusCode
- switch {
- case status >= 200 && status <= 399:
+ if r.StatusCode >= 200 && r.StatusCode <= 399 {
return nil
- case status >= 400 && status <= 599:
- // server returns json
- default:
- return fmt.Errorf("unexpected status code: %d", status)
}
- errorResponse := &ErrorResponse{Response: r}
- data, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return fmt.Errorf("body read error: %s. status: %v. Details: %v:", err, status, string(data[:250]))
+
+ // server possibly returns json and more details
+ er := &ErrorResponse{Response: r}
+
+ er.Body, er.ParseError = ioutil.ReadAll(r.Body)
+ if er.ParseError != nil {
+ return er
}
- if len(data) > 0 {
- err = json.Unmarshal(data, errorResponse)
- if err != nil {
- return fmt.Errorf("json decode error: %s. status: %v. Details: %v:", err, status, string(data[:250]))
+ if r.Header.Get("content-type") == "application/json" {
+ er.ParseError = json.Unmarshal(er.Body, er)
+ if er.ParseError != nil {
+ return er
}
}
- return errorResponse
+ return er
}
diff --git a/putio/client_test.go b/client_test.go
similarity index 71%
rename from putio/client_test.go
rename to client_test.go
index e475f27..c187460 100644
--- a/putio/client_test.go
+++ b/client_test.go
@@ -21,7 +21,6 @@ func setup() {
client = NewClient(nil)
url, _ := url.Parse(server.URL)
client.BaseURL = url
- client.uploadURL = url
}
func teardown() {
@@ -34,22 +33,22 @@ func testMethod(t *testing.T, r *http.Request, want string) {
}
}
-func testHeader(t *testing.T, r *http.Request, key, value string) {
+func testHeader(t *testing.T, r *http.Request, key, value string) { // nolint
if r.Header.Get(key) != value {
t.Errorf("missing header. want: %q: %q", key, value)
}
}
func TestNewClient(t *testing.T) {
- client := NewClient(nil)
- if client.BaseURL.String() != defaultBaseURL {
- t.Errorf("got: %v, want: %v", client.BaseURL.String(), defaultBaseURL)
+ cl := NewClient(nil)
+ if cl.BaseURL.String() != defaultBaseURL {
+ t.Errorf("got: %v, want: %v", cl.BaseURL.String(), defaultBaseURL)
}
}
func TestNewRequest_badURL(t *testing.T) {
- client := NewClient(nil)
- _, err := client.NewRequest(context.Background(), "GET", ":", nil)
+ cl := NewClient(nil)
+ _, err := cl.NewRequest(context.Background(), http.MethodGet, ":", nil)
if err == nil {
t.Errorf("bad URL accepted")
}
@@ -57,10 +56,10 @@ func TestNewRequest_badURL(t *testing.T) {
func TestNewRequest_customUserAgent(t *testing.T) {
userAgent := "test"
- client := NewClient(nil)
- client.UserAgent = userAgent
+ cl := NewClient(nil)
+ cl.UserAgent = userAgent
- req, _ := client.NewRequest(context.Background(), "GET", "/test", nil)
+ req, _ := cl.NewRequest(context.Background(), http.MethodGet, "/test", nil)
if got := req.Header.Get("User-Agent"); got != userAgent {
t.Errorf("got: %v, want: %v", got, userAgent)
}
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..7e23177
--- /dev/null
+++ b/config.go
@@ -0,0 +1,107 @@
+package putio
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+)
+
+// ConfigService represents configuration related operations.
+type ConfigService struct {
+ client *Client
+}
+
+// GetAll all fills config.
+func (f *ConfigService) GetAll(ctx context.Context, config interface{}) error {
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/config", nil)
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ var r struct {
+ Config json.RawMessage `json:"config"`
+ }
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ return json.Unmarshal(r.Config, &config) // nolint:wrapcheck
+}
+
+// Get fetches config item via given key.
+func (f *ConfigService) Get(ctx context.Context, key string, value interface{}) (found bool, err error) {
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/config/"+key, nil)
+ if err != nil {
+ return false, err
+ }
+ var r struct {
+ Value *json.RawMessage `json:"value"`
+ }
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
+ if err != nil {
+ return false, err
+ }
+ if r.Value == nil {
+ return false, nil
+ }
+ return true, json.Unmarshal(*r.Value, &value) // nolint:wrapcheck
+}
+
+// SetAll updates all config items.
+func (f *ConfigService) SetAll(ctx context.Context, config interface{}) error {
+ b, err := json.Marshal(config)
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ v := struct {
+ Config json.RawMessage `json:"config"`
+ }{
+ Config: b,
+ }
+ body, err := json.Marshal(v)
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ req, err := f.client.NewRequest(ctx, http.MethodPut, "/v2/config", bytes.NewReader(body))
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ req.Header.Set("content-type", "application/json")
+ _, err = f.client.Do(req, nil) // nolint:bodyclose
+ return fmt.Errorf("%w", err)
+}
+
+// Set updates given config key's value.
+func (f *ConfigService) Set(ctx context.Context, key string, value interface{}) error {
+ b, err := json.Marshal(value)
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ v := struct {
+ Value json.RawMessage `json:"value"`
+ }{
+ Value: b,
+ }
+ body, err := json.Marshal(v)
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ req, err := f.client.NewRequest(ctx, http.MethodPut, "/v2/config/"+key, bytes.NewReader(body))
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ req.Header.Set("content-type", "application/json")
+ _, err = f.client.Do(req, nil) // nolint:bodyclose
+ return fmt.Errorf("%w", err)
+}
+
+// Del destroys config item via given key.
+func (f *ConfigService) Del(ctx context.Context, key string) error {
+ req, err := f.client.NewRequest(ctx, http.MethodDelete, "/v2/config/"+key, nil)
+ if err != nil {
+ return fmt.Errorf("%w", err)
+ }
+ _, err = f.client.Do(req, nil) // nolint:bodyclose
+ return fmt.Errorf("%w", err)
+}
diff --git a/debian/changelog b/debian/changelog
index 77a1201..5e4ae4f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-putdotio-go-putio (0.0~git20221111.2e102ad+ds-1) UNRELEASED; urgency=low
+
+ * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk> Sun, 11 Dec 2022 06:22:45 -0000
+
golang-github-putdotio-go-putio (0.0~git20190822.19b9c63-3) unstable; urgency=medium
[ Debian Janitor ]
diff --git a/putio/doc.go b/doc.go
similarity index 100%
rename from putio/doc.go
rename to doc.go
diff --git a/error.go b/error.go
new file mode 100644
index 0000000..7aa2ca1
--- /dev/null
+++ b/error.go
@@ -0,0 +1,59 @@
+package putio
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+)
+
+// Sentinel errors.
+var (
+ ErrEmptyFolderName = errors.New("empty folder name")
+ ErrNoFileIDIsGiven = errors.New("no file id is given")
+ ErrFilenameCanNotBeEmpty = errors.New("filename cannot be empty")
+ ErrNewFilenameCanNotBeEmpty = errors.New("new filename cannot be empty")
+ ErrInvalidPageNumber = errors.New("invalid page number")
+ ErrNoQueryGiven = errors.New("no query given")
+ ErrEmptySubtitleKey = errors.New("empty subtitle key is given")
+ ErrNegativeTimeValue = errors.New("time cannot be negative")
+ ErrNoFileIsGiven = errors.New("no files given")
+ ErrEmptyUserName = errors.New("empty username")
+ ErrEmptyURL = errors.New("empty URL")
+ ErrUnexpected = errors.New("unexpected error")
+)
+
+// ErrorResponse reports the error caused by an API request.
+type ErrorResponse struct {
+ // Original http.Response
+ Response *http.Response `json:"-"`
+
+ // Body read from Response
+ Body []byte `json:"-"`
+
+ // Error while parsing the response
+ ParseError error
+
+ // These fileds are parsed from response if JSON.
+ Message string `json:"error_message"`
+ Type string `json:"error_type"`
+}
+
+// nolint:goerr113
+func (e *ErrorResponse) Error() string {
+ if e.ParseError != nil {
+ return fmt.Errorf(
+ "cannot parse response. code:%d error:%w body:%q",
+ e.Response.StatusCode,
+ e.ParseError,
+ string(e.Body[:250]),
+ ).Error()
+ }
+ return fmt.Sprintf(
+ "putio error. code:%d type:%q message:%q request:%v %v",
+ e.Response.StatusCode,
+ e.Type,
+ e.Message,
+ e.Response.Request.Method,
+ e.Response.Request.URL,
+ )
+}
diff --git a/putio/events.go b/events.go
similarity index 65%
rename from putio/events.go
rename to events.go
index 0b0db2f..4d3753c 100644
--- a/putio/events.go
+++ b/events.go
@@ -1,17 +1,21 @@
package putio
-import "context"
+import (
+ "context"
+ "net/http"
+)
// EventsService is the service to gather information about user's events.
type EventsService struct {
client *Client
}
+// nolint:godox
// FIXME: events list returns inconsistent data structures.
// List gets list of dashboard events. It includes downloads and share events.
func (e *EventsService) List(ctx context.Context) ([]Event, error) {
- req, err := e.client.NewRequest(ctx, "GET", "/v2/events/list", nil)
+ req, err := e.client.NewRequest(ctx, http.MethodGet, "/v2/events/list", nil)
if err != nil {
return nil, err
}
@@ -19,23 +23,22 @@ func (e *EventsService) List(ctx context.Context) ([]Event, error) {
var r struct {
Events []Event
}
- _, err = e.client.Do(req, &r)
+ _, err = e.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return nil, err
}
return r.Events, nil
-
}
-// Delete Clears all all dashboard events.
+// Delete Clears all dashboard events.
func (e *EventsService) Delete(ctx context.Context) error {
- req, err := e.client.NewRequest(ctx, "POST", "/v2/events/delete", nil)
+ req, err := e.client.NewRequest(ctx, http.MethodPost, "/v2/events/delete", nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = e.client.Do(req, &struct{}{})
+ _, err = e.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
diff --git a/putio/events_test.go b/events_test.go
similarity index 95%
rename from putio/events_test.go
rename to events_test.go
index b8f2397..a7fc079 100644
--- a/putio/events_test.go
+++ b/events_test.go
@@ -35,7 +35,7 @@ func TestEvents_List(t *testing.T) {
}
`
mux.HandleFunc("/v2/events/list", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -58,7 +58,7 @@ func TestEvents_Delete(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/events/delete", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status":"OK"}`)
})
diff --git a/putio/example_test.go b/example_test.go
similarity index 92%
rename from putio/example_test.go
rename to example_test.go
index bf4cf6c..bac3b63 100644
--- a/putio/example_test.go
+++ b/example_test.go
@@ -5,7 +5,7 @@ import (
"fmt"
"log"
- "github.com/putdotio/go-putio/putio"
+ "github.com/putdotio/go-putio"
"golang.org/x/oauth2"
)
diff --git a/putio/files.go b/files.go
similarity index 64%
rename from putio/files.go
rename to files.go
index 079ab49..596b5fb 100644
--- a/putio/files.go
+++ b/files.go
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"mime/multipart"
+ "net/http"
"net/url"
"strconv"
"strings"
@@ -20,7 +21,7 @@ type FilesService struct {
// Get fetches file metadata for given file ID.
func (f *FilesService) Get(ctx context.Context, id int64) (File, error) {
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id), nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id), nil)
if err != nil {
return File{}, err
}
@@ -28,7 +29,7 @@ func (f *FilesService) Get(ctx context.Context, id int64) (File, error) {
var r struct {
File File `json:"file"`
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return File{}, err
}
@@ -37,7 +38,7 @@ func (f *FilesService) Get(ctx context.Context, id int64) (File, error) {
// List fetches children for given directory ID.
func (f *FilesService) List(ctx context.Context, id int64) (children []File, parent File, err error) {
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/list?parent_id="+itoa(id)+"&per_page=1000", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/list?parent_id="+itoa(id)+"&per_page=1000", nil)
if err != nil {
return
}
@@ -46,7 +47,7 @@ func (f *FilesService) List(ctx context.Context, id int64) (children []File, par
Parent File `json:"parent"`
Cursor string `json:"cursor"`
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return
}
@@ -54,14 +55,14 @@ func (f *FilesService) List(ctx context.Context, id int64) (children []File, par
parent = r.Parent
for r.Cursor != "" {
body := strings.NewReader(`{"cursor": "` + r.Cursor + `"}`)
- req, err = f.client.NewRequest(ctx, "POST", "/v2/files/list/continue", body)
+ req, err = f.client.NewRequest(ctx, http.MethodPost, "/v2/files/list/continue", body)
if err != nil {
return
}
req.Header.Set("content-type", "application/json")
r.Files = nil
r.Cursor = ""
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return
}
@@ -77,7 +78,7 @@ func (f *FilesService) URL(ctx context.Context, id int64, useTunnel bool) (strin
notunnel = "notunnel=0"
}
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/url?"+notunnel, nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id)+"/url?"+notunnel, nil)
if err != nil {
return "", err
}
@@ -86,7 +87,7 @@ func (f *FilesService) URL(ctx context.Context, id int64, useTunnel bool) (strin
URL string `json:"url"`
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return "", err
}
@@ -97,14 +98,14 @@ func (f *FilesService) URL(ctx context.Context, id int64, useTunnel bool) (strin
// CreateFolder creates a new folder under parent.
func (f *FilesService) CreateFolder(ctx context.Context, name string, parent int64) (File, error) {
if name == "" {
- return File{}, fmt.Errorf("empty folder name")
+ return File{}, ErrEmptyFolderName
}
params := url.Values{}
params.Set("name", name)
params.Set("parent_id", itoa(parent))
- req, err := f.client.NewRequest(ctx, "POST", "/v2/files/create-folder", strings.NewReader(params.Encode()))
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/create-folder", strings.NewReader(params.Encode()))
if err != nil {
return File{}, err
}
@@ -113,7 +114,7 @@ func (f *FilesService) CreateFolder(ctx context.Context, name string, parent int
var r struct {
File File `json:"file"`
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return File{}, err
}
@@ -124,10 +125,10 @@ func (f *FilesService) CreateFolder(ctx context.Context, name string, parent int
// Delete deletes given files.
func (f *FilesService) Delete(ctx context.Context, files ...int64) error {
if len(files) == 0 {
- return fmt.Errorf("no file id is given")
+ return ErrNoFileIDIsGiven
}
- var ids []string
+ ids := []string{}
for _, id := range files {
ids = append(ids, itoa(id))
}
@@ -135,13 +136,13 @@ func (f *FilesService) Delete(ctx context.Context, files ...int64) error {
params := url.Values{}
params.Set("file_ids", strings.Join(ids, ","))
- req, err := f.client.NewRequest(ctx, "POST", "/v2/files/delete", strings.NewReader(params.Encode()))
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/delete", strings.NewReader(params.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
@@ -151,20 +152,20 @@ func (f *FilesService) Delete(ctx context.Context, files ...int64) error {
// Rename change the name of the file to newname.
func (f *FilesService) Rename(ctx context.Context, id int64, newname string) error {
if newname == "" {
- return fmt.Errorf("new filename cannot be empty")
+ return ErrNewFilenameCanNotBeEmpty
}
params := url.Values{}
params.Set("file_id", itoa(id))
params.Set("name", newname)
- req, err := f.client.NewRequest(ctx, "POST", "/v2/files/rename", strings.NewReader(params.Encode()))
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/rename", strings.NewReader(params.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
@@ -175,10 +176,10 @@ func (f *FilesService) Rename(ctx context.Context, id int64, newname string) err
// Move moves files to the given destination.
func (f *FilesService) Move(ctx context.Context, parent int64, files ...int64) error {
if len(files) == 0 {
- return fmt.Errorf("no files given")
+ return ErrNoFileIsGiven
}
- var ids []string
+ ids := []string{}
for _, file := range files {
ids = append(ids, itoa(file))
}
@@ -187,13 +188,13 @@ func (f *FilesService) Move(ctx context.Context, parent int64, files ...int64) e
params.Set("file_ids", strings.Join(ids, ","))
params.Set("parent_id", itoa(parent))
- req, err := f.client.NewRequest(ctx, "POST", "/v2/files/move", strings.NewReader(params.Encode()))
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/move", strings.NewReader(params.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
@@ -202,58 +203,58 @@ func (f *FilesService) Move(ctx context.Context, parent int64, files ...int64) e
// Upload reads from given io.Reader and uploads the file contents to Put.io
// servers under directory given by parent. If parent is negative, user's
-// prefered folder is used.
+// preferred folder is used.
//
// If the uploaded file is a torrent file, Put.io will interpret it as a
// transfer and Transfer field will be present to represent the status of the
// tranfer. Likewise, if the uploaded file is a regular file, Transfer field
// would be nil and the uploaded file will be represented by the File field.
//
-// This method reads the file contents into the memory, so it should be used for
-// <150MB files.
+// This method reads the file contents into the memory, so it should only be used for small files.
+// Use UploadService for larger files.
func (f *FilesService) Upload(ctx context.Context, r io.Reader, filename string, parent int64) (Upload, error) {
if filename == "" {
- return Upload{}, fmt.Errorf("filename cannot be empty")
+ return Upload{}, ErrFilenameCanNotBeEmpty
}
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
- // negative parent means use user's prefered download folder.
+ // negative parent means use user's preferred download folder.
if parent >= 0 {
err := mw.WriteField("parent_id", itoa(parent))
if err != nil {
- return Upload{}, err
+ return Upload{}, fmt.Errorf("%w", err)
}
}
formfile, err := mw.CreateFormFile("file", filename)
if err != nil {
- return Upload{}, err
+ return Upload{}, fmt.Errorf("%w", err)
}
_, err = io.Copy(formfile, r)
if err != nil {
- return Upload{}, err
+ return Upload{}, fmt.Errorf("%w", err)
}
err = mw.Close()
if err != nil {
- return Upload{}, err
+ return Upload{}, fmt.Errorf("%w", err)
}
- req, err := f.client.NewRequest(ctx, "POST", "/v2/files/upload", &buf)
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "$upload$", &buf)
if err != nil {
- return Upload{}, err
+ return Upload{}, fmt.Errorf("%w", err)
}
req.Header.Set("Content-Type", mw.FormDataContentType())
var response struct {
Upload
}
- _, err = f.client.Do(req, &response)
+ _, err = f.client.Do(req, &response) // nolint:bodyclose
if err != nil {
- return Upload{}, err
+ return Upload{}, fmt.Errorf("%w", err)
}
return response.Upload, nil
}
@@ -263,21 +264,21 @@ func (f *FilesService) Upload(ctx context.Context, r io.Reader, filename string,
// page is -1, all results are returned.
func (f *FilesService) Search(ctx context.Context, query string, page int64) (Search, error) {
if page == 0 || page < -1 {
- return Search{}, fmt.Errorf("invalid page number")
+ return Search{}, ErrInvalidPageNumber
}
if query == "" {
- return Search{}, fmt.Errorf("no query given")
+ return Search{}, ErrNoQueryGiven
}
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/search/"+query+"/page/"+itoa(page), nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/search/"+query+"/page/"+itoa(page), nil)
if err != nil {
- return Search{}, err
+ return Search{}, fmt.Errorf("%w", err)
}
var r Search
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
- return Search{}, err
+ return Search{}, fmt.Errorf("%w", err)
}
return r, nil
@@ -285,17 +286,17 @@ func (f *FilesService) Search(ctx context.Context, query string, page int64) (Se
// Shared returns list of shared files and share information.
func (f *FilesService) shared(ctx context.Context) ([]share, error) {
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/shared", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/shared", nil)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
var r struct {
Shared []share
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
return r.Shared, nil
@@ -303,40 +304,41 @@ func (f *FilesService) shared(ctx context.Context) ([]share, error) {
// SharedWith returns list of users the given file is shared with.
func (f *FilesService) sharedWith(ctx context.Context, id int64) ([]share, error) {
+ // nolint:godox
// FIXME: shared-with returns different json structure than /shared/
// endpoint. so it's not an exported method until a common structure is
// decided
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/shared-with", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id)+"/shared-with", nil)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
var r struct {
Shared []share `json:"shared-with"`
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
return r.Shared, nil
}
-// Subtitles lists available subtitles for the given file for user's prefered
+// Subtitles lists available subtitles for the given file for user's preferred
// subtitle language.
func (f *FilesService) Subtitles(ctx context.Context, id int64) ([]Subtitle, error) {
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/subtitles", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id)+"/subtitles", nil)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
var r struct {
Subtitles []Subtitle
Default string
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
return r.Subtitles, nil
@@ -348,18 +350,22 @@ func (f *FilesService) Subtitles(ctx context.Context, id int64) ([]Subtitle, err
// - A subtitle file that has identical parent folder and name with the video.
// - Subtitle file extracted from video if the format is MKV.
// - First match from OpenSubtitles.org.
-func (f *FilesService) DownloadSubtitle(ctx context.Context, id int64, key string, format string) (io.ReadCloser, error) {
+func (f *FilesService) DownloadSubtitle(
+ ctx context.Context,
+ id int64,
+ key string,
+) (io.ReadCloser, error) {
if key == "" {
key = "default"
}
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/subtitles/"+key, nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/files/"+itoa(id)+"/subtitles/"+key, nil)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
- resp, err := f.client.Do(req, nil)
+ resp, err := f.client.Do(req, nil) // nolint:bodyclose
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
return resp.Body, nil
@@ -369,17 +375,22 @@ func (f *FilesService) DownloadSubtitle(ctx context.Context, id int64, key strin
// subtitleKey to get available subtitles for user’s preferred languages.
func (f *FilesService) HLSPlaylist(ctx context.Context, id int64, subtitleKey string) (io.ReadCloser, error) {
if subtitleKey == "" {
- return nil, fmt.Errorf("empty subtitle key is given")
+ return nil, ErrEmptySubtitleKey
}
- req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/hls/media.m3u8?subtitle_key"+subtitleKey, nil)
+ req, err := f.client.NewRequest(
+ ctx,
+ http.MethodGet,
+ "/v2/files/"+itoa(id)+"/hls/media.m3u8?subtitle_key"+subtitleKey,
+ nil,
+ )
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
- resp, err := f.client.Do(req, nil)
+ resp, err := f.client.Do(req, nil) // nolint:bodyclose
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("%w", err)
}
return resp.Body, nil
@@ -388,21 +399,26 @@ func (f *FilesService) HLSPlaylist(ctx context.Context, id int64, subtitleKey st
// SetVideoPosition sets default video position for a video file.
func (f *FilesService) SetVideoPosition(ctx context.Context, id int64, t int) error {
if t < 0 {
- return fmt.Errorf("time cannot be negative")
+ return ErrNegativeTimeValue
}
params := url.Values{}
params.Set("time", strconv.Itoa(t))
- req, err := f.client.NewRequest(ctx, "POST", "/v2/files/"+itoa(id)+"/start-from", strings.NewReader(params.Encode()))
+ req, err := f.client.NewRequest(
+ ctx,
+ http.MethodPost,
+ "/v2/files/"+itoa(id)+"/start-from",
+ strings.NewReader(params.Encode()),
+ )
if err != nil {
- return err
+ return fmt.Errorf("%w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
- return err
+ return fmt.Errorf("%w", err)
}
return nil
@@ -410,15 +426,15 @@ func (f *FilesService) SetVideoPosition(ctx context.Context, id int64, t int) er
// DeleteVideoPosition deletes video position for a video file.
func (f *FilesService) DeleteVideoPosition(ctx context.Context, id int64) error {
- req, err := f.client.NewRequest(ctx, "POST", "/v2/files/"+itoa(id)+"/start-from/delete", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/files/"+itoa(id)+"/start-from/delete", nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
- return err
+ return fmt.Errorf("%w", err)
}
return nil
diff --git a/putio/files_test.go b/files_test.go
similarity index 91%
rename from putio/files_test.go
rename to files_test.go
index 99d6262..4f3793e 100644
--- a/putio/files_test.go
+++ b/files_test.go
@@ -3,6 +3,7 @@ package putio
import (
"bytes"
"context"
+ "errors"
"fmt"
"io"
"net/http"
@@ -30,14 +31,15 @@ func TestFiles_Get(t *testing.T) {
"opensubtitles_hash": null,
"parent_id": 123,
"screenshot": null,
- "size": 92
+ "size": 92,
+ "file_type": "TEXT"
},
"status": "OK"
}
`
mux.HandleFunc("/v2/files/1", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
mux.HandleFunc("/v2/files/2", http.NotFound)
@@ -47,6 +49,10 @@ func TestFiles_Get(t *testing.T) {
t.Error(err)
}
+ if file.FileType != FileTypeText {
+ t.Errorf("got: %v, want: %v", file.FileType, FileTypeText)
+ }
+
if file.Size != 92 {
t.Errorf("got: %v, want: 92", file.Size)
}
@@ -56,8 +62,10 @@ func TestFiles_Get(t *testing.T) {
if err == nil {
t.Fatal("must not return nil")
}
- if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode != 404 {
- t.Errorf("error: %s, excepted: 404", err)
+
+ var myErr *ErrorResponse
+ if errors.As(err, &myErr) && myErr.Response.StatusCode != http.StatusNotFound {
+ t.Errorf("error: %s, excepted: %d", err, http.StatusNotFound)
}
}
@@ -81,7 +89,8 @@ func TestFiles_List(t *testing.T) {
"opensubtitles_hash": null,
"parent_id": 123,
"screenshot": null,
- "size": 92
+ "size": 92,
+ "file_type": "TEXT"
},
{
"content_type": "video/x-matroska",
@@ -96,7 +105,8 @@ func TestFiles_List(t *testing.T) {
"opensubtitles_hash": "acc2785ffa573c69",
"parent_id": 123,
"screenshot": "https://put.io/screenshots/aF5rkZVtYV9pV1iWimSOZWJjWWFaXGZdaZBmY2OJY4uJlV5pj5FiXg%3D%3D.jpg",
- "size": 1155197659
+ "size": 1155197659,
+ "file_type": "VIDEO"
}
],
"parent": {
@@ -112,13 +122,14 @@ func TestFiles_List(t *testing.T) {
"opensubtitles_hash": null,
"parent_id": 0,
"screenshot": null,
- "size": 1155197751
+ "size": 1155197751,
+ "file_type": "FOLDER"
},
"status": "OK"
}
`
mux.HandleFunc("/v2/files/list", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
// not found handler
parentID := r.URL.Query().Get("parent_id")
@@ -147,8 +158,10 @@ func TestFiles_List(t *testing.T) {
if err == nil {
t.Fatal("must not return nil")
}
- if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode != 404 {
- t.Errorf("error: %s, excepted: 404", err)
+
+ var myErr *ErrorResponse
+ if errors.As(err, &myErr) && myErr.Response.StatusCode != http.StatusNotFound {
+ t.Errorf("error: %s, excepted: %d", err, http.StatusNotFound)
}
}
@@ -182,7 +195,7 @@ func TestFiles_CreateFolder(t *testing.T) {
}
`
mux.HandleFunc("/v2/files/create-folder", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, fixture)
})
@@ -208,7 +221,7 @@ func TestFiles_Delete(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/files/delete", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status": "OK"}`)
})
@@ -230,7 +243,7 @@ func TestFiles_Rename(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/files/rename", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status":"OK"}`)
})
@@ -252,7 +265,7 @@ func TestFiles_Move(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/files/move", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status":"OK"}`)
})
@@ -345,7 +358,7 @@ func TestFiles_Search(t *testing.T) {
}
`
mux.HandleFunc("/v2/files/search/", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -380,7 +393,7 @@ func TestFiles_SetVideoPosition(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/files/1/start-from", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"statutus":"OK"}`)
})
@@ -402,7 +415,7 @@ func TestFiles_DeleteVideoPosition(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/files/1/start-from/delete", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"statutus":"OK"}`)
})
@@ -443,7 +456,7 @@ http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/2540_vod.m3u8
http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/3340_vod.m3u8
`
mux.HandleFunc("/v2/files/1/hls/media.m3u8", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
http.ServeContent(w, r, "media.m3u8", time.Now().UTC(), strings.NewReader(sampleHLS))
})
@@ -451,7 +464,9 @@ http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/3340_vod.m3u8
if err != nil {
t.Error(err)
}
- defer body.Close()
+ defer func() {
+ _ = body.Close()
+ }()
var buf bytes.Buffer
_, err = io.Copy(&buf, body)
@@ -497,7 +512,7 @@ func TestFiles_Shared(t *testing.T) {
}
`
mux.HandleFunc("/v2/files/shared", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -537,7 +552,7 @@ func TestFiles_SharedWith(t *testing.T) {
`
mux.HandleFunc("/v2/files/1/shared-with", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -577,7 +592,7 @@ func TestFiles_Subtitles(t *testing.T) {
}
`
mux.HandleFunc("/v2/files/1/subtitles", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -616,7 +631,7 @@ Let's go down.
- Three out, eight left.
`
mux.HandleFunc("/v2/files/1/subtitles/", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
// trim leading and trailing slashes and split the url path
f := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
@@ -639,11 +654,13 @@ Let's go down.
})
// valid file ID and valid key
- rc, err := client.Files.DownloadSubtitle(context.Background(), 1, "key0", "")
+ rc, err := client.Files.DownloadSubtitle(context.Background(), 1, "key0")
if err != nil {
t.Error(err)
}
- defer rc.Close()
+ defer func() {
+ _ = rc.Close()
+ }()
var buf bytes.Buffer
_, err = io.Copy(&buf, rc)
@@ -655,25 +672,31 @@ Let's go down.
}
// negative file ID
- rc, err = client.Files.DownloadSubtitle(context.Background(), -1, "key0", "")
+ rc, err = client.Files.DownloadSubtitle(context.Background(), -1, "key0")
if err == nil {
- defer rc.Close()
+ defer func() {
+ _ = rc.Close()
+ }()
t.Errorf("negative file ID accepted")
}
// invalid key
- rc, err = client.Files.DownloadSubtitle(context.Background(), 1, "key3", "")
+ rc, err = client.Files.DownloadSubtitle(context.Background(), 1, "key3")
if err == nil {
- defer rc.Close()
+ defer func() {
+ _ = rc.Close()
+ }()
t.Errorf("invalid key accepted")
}
// empty key
- rc, err = client.Files.DownloadSubtitle(context.Background(), 1, "", "")
+ rc, err = client.Files.DownloadSubtitle(context.Background(), 1, "")
if err != nil {
t.Error(err)
}
- defer rc.Close()
+ defer func() {
+ _ = rc.Close()
+ }()
buf.Reset()
_, err = io.Copy(&buf, rc)
diff --git a/putio/friends.go b/friends.go
similarity index 65%
rename from putio/friends.go
rename to friends.go
index cf29761..462e7df 100644
--- a/putio/friends.go
+++ b/friends.go
@@ -2,7 +2,7 @@ package putio
import (
"context"
- "fmt"
+ "net/http"
)
// FriendsService is the service to operate on user friends.
@@ -12,7 +12,7 @@ type FriendsService struct {
// List lists users friends.
func (f *FriendsService) List(ctx context.Context) ([]Friend, error) {
- req, err := f.client.NewRequest(ctx, "GET", "/v2/friends/list", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/friends/list", nil)
if err != nil {
return nil, err
}
@@ -21,7 +21,7 @@ func (f *FriendsService) List(ctx context.Context) ([]Friend, error) {
Friends []Friend
Total int
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return nil, err
}
@@ -31,7 +31,7 @@ func (f *FriendsService) List(ctx context.Context) ([]Friend, error) {
// WaitingRequests lists user's pending friend requests.
func (f *FriendsService) WaitingRequests(ctx context.Context) ([]Friend, error) {
- req, err := f.client.NewRequest(ctx, "GET", "/v2/friends/waiting-requests", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodGet, "/v2/friends/waiting-requests", nil)
if err != nil {
return nil, err
}
@@ -39,7 +39,7 @@ func (f *FriendsService) WaitingRequests(ctx context.Context) ([]Friend, error)
var r struct {
Friends []Friend
}
- _, err = f.client.Do(req, &r)
+ _, err = f.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return nil, err
}
@@ -50,15 +50,15 @@ func (f *FriendsService) WaitingRequests(ctx context.Context) ([]Friend, error)
// Request sends a friend request to the given username.
func (f *FriendsService) Request(ctx context.Context, username string) error {
if username == "" {
- return fmt.Errorf("empty username")
+ return ErrEmptyUserName
}
- req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/request", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/friends/"+username+"/request", nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
@@ -69,16 +69,16 @@ func (f *FriendsService) Request(ctx context.Context, username string) error {
// Approve approves a friend request from the given username.
func (f *FriendsService) Approve(ctx context.Context, username string) error {
if username == "" {
- return fmt.Errorf("empty username")
+ return ErrEmptyUserName
}
- req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/approve", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/friends/"+username+"/approve", nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
@@ -88,16 +88,16 @@ func (f *FriendsService) Approve(ctx context.Context, username string) error {
// Deny denies a friend request from the given username.
func (f *FriendsService) Deny(ctx context.Context, username string) error {
if username == "" {
- return fmt.Errorf("empty username")
+ return ErrEmptyUserName
}
- req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/deny", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/friends/"+username+"/deny", nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
@@ -107,16 +107,16 @@ func (f *FriendsService) Deny(ctx context.Context, username string) error {
// Unfriend removed friend from user's friend list.
func (f *FriendsService) Unfriend(ctx context.Context, username string) error {
if username == "" {
- return fmt.Errorf("empty username")
+ return ErrEmptyUserName
}
- req, err := f.client.NewRequest(ctx, "POST", "/v2/friends/"+username+"/unfriend", nil)
+ req, err := f.client.NewRequest(ctx, http.MethodPost, "/v2/friends/"+username+"/unfriend", nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = f.client.Do(req, &struct{}{})
+ _, err = f.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
diff --git a/putio/friends_test.go b/friends_test.go
similarity index 94%
rename from putio/friends_test.go
rename to friends_test.go
index 4f63ec1..2d0bf36 100644
--- a/putio/friends_test.go
+++ b/friends_test.go
@@ -46,7 +46,7 @@ func TestFriends_List(t *testing.T) {
`
mux.HandleFunc("/v2/friends/list", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -91,7 +91,7 @@ func TestFriends_WaitingRequests(t *testing.T) {
`
mux.HandleFunc("/v2/friends/waiting-requests", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -118,7 +118,7 @@ func TestFriends_Request(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/friends/annie/request", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status":"OK"}`)
})
@@ -140,7 +140,7 @@ func TestFriends_Approve(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/friends/bob/approve", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status":"OK"}`)
})
@@ -162,7 +162,7 @@ func TestFriends_Deny(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/friends/andy/deny", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status":"OK"}`)
})
@@ -184,7 +184,7 @@ func TestFriends_Unfriend(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/friends/lin/unfriend", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status":"OK"}`)
})
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..ba49599
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/putdotio/go-putio
+
+go 1.13
+
+require golang.org/x/oauth2 v0.2.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..75f5822
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,47 @@
+cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
+golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
diff --git a/putio/error.go b/putio/error.go
deleted file mode 100644
index 6597278..0000000
--- a/putio/error.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package putio
-
-import (
- "fmt"
- "net/http"
-)
-
-// ErrorResponse reports the error caused by an API request.
-type ErrorResponse struct {
- Response *http.Response `json:"-"`
-
- Message string `json:"error_message"`
- Type string `json:"error_type"`
-}
-
-func (e *ErrorResponse) Error() string {
- return fmt.Sprintf(
- "Type: %v Message: %q. Original error: %v %v: %v",
- e.Type,
- e.Message,
- e.Response.Request.Method,
- e.Response.Request.URL,
- e.Response.Status,
- )
-}
diff --git a/putio/time.go b/time.go
similarity index 84%
rename from putio/time.go
rename to time.go
index 7684ce8..425b9b3 100644
--- a/putio/time.go
+++ b/time.go
@@ -1,6 +1,9 @@
package putio
-import "time"
+import (
+ "fmt"
+ "time"
+)
// Time is a wrapper around time.Time that can be unmarshalled from a JSON
// string formatted as "2016-04-19T15:44:42". All methods of time.Time can be
@@ -17,7 +20,7 @@ func (t *Time) String() string {
func (t *Time) UnmarshalJSON(data []byte) error {
// put.io API has inconsistent time layouts for different endpoints, such
// as /files and /events
- var timeLayouts = []string{`"2006-01-02T15:04:05"`, `"2006-01-02 15:04:05"`}
+ timeLayouts := []string{`"2006-01-02T15:04:05"`, `"2006-01-02 15:04:05"`}
s := string(data)
var err error
@@ -29,5 +32,5 @@ func (t *Time) UnmarshalJSON(data []byte) error {
return nil
}
}
- return err
+ return fmt.Errorf("%w", err)
}
diff --git a/putio/transfers.go b/transfers.go
similarity index 74%
rename from putio/transfers.go
rename to transfers.go
index 627dff4..6726515 100644
--- a/putio/transfers.go
+++ b/transfers.go
@@ -2,7 +2,7 @@ package putio
import (
"context"
- "fmt"
+ "net/http"
"net/url"
"strings"
)
@@ -16,7 +16,7 @@ type TransfersService struct {
// List lists all active transfers. If a transfer is completed, it will not be
// available in response.
func (t *TransfersService) List(ctx context.Context) ([]Transfer, error) {
- req, err := t.client.NewRequest(ctx, "GET", "/v2/transfers/list", nil)
+ req, err := t.client.NewRequest(ctx, http.MethodGet, "/v2/transfers/list", nil)
if err != nil {
return nil, err
}
@@ -24,7 +24,7 @@ func (t *TransfersService) List(ctx context.Context) ([]Transfer, error) {
var r struct {
Transfers []Transfer
}
- _, err = t.client.Do(req, &r)
+ _, err = t.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return nil, err
}
@@ -38,7 +38,7 @@ func (t *TransfersService) List(ctx context.Context) ([]Transfer, error) {
// used to send a POST request after the transfer is finished downloading.
func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64, callbackURL string) (Transfer, error) {
if urlStr == "" {
- return Transfer{}, fmt.Errorf("empty URL")
+ return Transfer{}, ErrEmptyURL
}
params := url.Values{}
@@ -52,7 +52,7 @@ func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64,
params.Set("callback_url", callbackURL)
}
- req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/add", strings.NewReader(params.Encode()))
+ req, err := t.client.NewRequest(ctx, http.MethodPost, "/v2/transfers/add", strings.NewReader(params.Encode()))
if err != nil {
return Transfer{}, err
}
@@ -61,7 +61,7 @@ func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64,
var r struct {
Transfer Transfer
}
- _, err = t.client.Do(req, &r)
+ _, err = t.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return Transfer{}, err
}
@@ -71,7 +71,7 @@ func (t *TransfersService) Add(ctx context.Context, urlStr string, parent int64,
// Get returns the given transfer's properties.
func (t *TransfersService) Get(ctx context.Context, id int64) (Transfer, error) {
- req, err := t.client.NewRequest(ctx, "GET", "/v2/transfers/"+itoa(id), nil)
+ req, err := t.client.NewRequest(ctx, http.MethodGet, "/v2/transfers/"+itoa(id), nil)
if err != nil {
return Transfer{}, err
}
@@ -79,7 +79,7 @@ func (t *TransfersService) Get(ctx context.Context, id int64) (Transfer, error)
var r struct {
Transfer Transfer
}
- _, err = t.client.Do(req, &r)
+ _, err = t.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return Transfer{}, err
}
@@ -92,7 +92,7 @@ func (t *TransfersService) Retry(ctx context.Context, id int64) (Transfer, error
params := url.Values{}
params.Set("id", itoa(id))
- req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/retry", strings.NewReader(params.Encode()))
+ req, err := t.client.NewRequest(ctx, http.MethodPost, "/v2/transfers/retry", strings.NewReader(params.Encode()))
if err != nil {
return Transfer{}, err
}
@@ -101,7 +101,7 @@ func (t *TransfersService) Retry(ctx context.Context, id int64) (Transfer, error
var r struct {
Transfer Transfer
}
- _, err = t.client.Do(req, &r)
+ _, err = t.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return Transfer{}, err
}
@@ -112,10 +112,10 @@ func (t *TransfersService) Retry(ctx context.Context, id int64) (Transfer, error
// Cancel deletes given transfers.
func (t *TransfersService) Cancel(ctx context.Context, ids ...int64) error {
if len(ids) == 0 {
- return fmt.Errorf("no id given")
+ return ErrNoFileIDIsGiven
}
- var transfers []string
+ transfers := []string{}
for _, id := range ids {
transfers = append(transfers, itoa(id))
}
@@ -123,13 +123,13 @@ func (t *TransfersService) Cancel(ctx context.Context, ids ...int64) error {
params := url.Values{}
params.Set("transfer_ids", strings.Join(transfers, ","))
- req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/cancel", strings.NewReader(params.Encode()))
+ req, err := t.client.NewRequest(ctx, http.MethodPost, "/v2/transfers/cancel", strings.NewReader(params.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = t.client.Do(req, &struct{}{})
+ _, err = t.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
@@ -139,13 +139,13 @@ func (t *TransfersService) Cancel(ctx context.Context, ids ...int64) error {
// Clean removes completed transfers from the transfer list.
func (t *TransfersService) Clean(ctx context.Context) error {
- req, err := t.client.NewRequest(ctx, "POST", "/v2/transfers/clean", nil)
+ req, err := t.client.NewRequest(ctx, http.MethodPost, "/v2/transfers/clean", nil)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- _, err = t.client.Do(req, &struct{}{})
+ _, err = t.client.Do(req, &struct{}{}) // nolint:bodyclose
if err != nil {
return err
}
diff --git a/putio/transfers_test.go b/transfers_test.go
similarity index 91%
rename from putio/transfers_test.go
rename to transfers_test.go
index 53985cc..e381e4d 100644
--- a/putio/transfers_test.go
+++ b/transfers_test.go
@@ -2,6 +2,7 @@ package putio
import (
"context"
+ "errors"
"fmt"
"net/http"
"strings"
@@ -56,7 +57,7 @@ func TestTransfers_Get(t *testing.T) {
}
`
mux.HandleFunc("/v2/transfers/1", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -138,7 +139,7 @@ func TestTransfers_List(t *testing.T) {
`
mux.HandleFunc("/v2/transfers/list", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -204,7 +205,7 @@ func TestTransfers_Add(t *testing.T) {
}
`
mux.HandleFunc("/v2/transfers/add", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
// form values
@@ -217,7 +218,12 @@ func TestTransfers_Add(t *testing.T) {
fmt.Fprintln(w, fixture)
})
- transfer, err := client.Transfers.Add(context.Background(), "http://releases.ubuntu.com/16.04/ubuntu-16.04-desktop-amd64.iso.torrent", 0, "")
+ transfer, err := client.Transfers.Add(
+ context.Background(),
+ "http://releases.ubuntu.com/16.04/ubuntu-16.04-desktop-amd64.iso.torrent",
+ 0,
+ "",
+ )
if err != nil {
t.Error(err)
}
@@ -232,7 +238,7 @@ func TestTransfers_Add(t *testing.T) {
t.Errorf("empty URL accepted")
}
- // negative parent folder means use the user's prefered download folder.
+ // negative parent folder means use the user's preferred download folder.
_, err = client.Transfers.Add(context.Background(), "filepath", -1, "")
if err != nil {
t.Error(err)
@@ -293,7 +299,7 @@ func TestTransfers_Retry(t *testing.T) {
}
`
mux.HandleFunc("/v2/transfers/retry", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
id := r.FormValue("id")
@@ -319,8 +325,9 @@ func TestTransfers_Retry(t *testing.T) {
if err == nil {
t.Fatal("must not return nil")
}
- if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode != 404 {
- t.Errorf("error: %s, excepted: 404", err)
+ var myErr *ErrorResponse
+ if errors.As(err, &myErr) && myErr.Response.StatusCode != http.StatusNotFound {
+ t.Errorf("error: %s, excepted: %d", err, http.StatusNotFound)
}
}
@@ -338,7 +345,7 @@ func TestTransfers_Cancel(t *testing.T) {
}
mux.HandleFunc("/v2/transfers/cancel", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
transferIdsStr := r.FormValue("transfer_ids")
@@ -376,8 +383,9 @@ func TestTransfers_Cancel(t *testing.T) {
if err == nil {
t.Fatal("must not return nil")
}
- if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode != 404 {
- t.Errorf("error: %s, excepted: 404", err)
+ var myErr *ErrorResponse
+ if errors.As(err, &myErr) && myErr.Response.StatusCode != http.StatusNotFound {
+ t.Errorf("error: %s, excepted: %d", err, http.StatusNotFound)
}
}
@@ -386,7 +394,7 @@ func TestTransfers_Clean(t *testing.T) {
defer teardown()
mux.HandleFunc("/v2/transfers/clean", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, `{"status":"OK"}`)
})
diff --git a/putio/types.go b/types.go
similarity index 93%
rename from putio/types.go
rename to types.go
index 6a5ae84..7b2b76f 100644
--- a/putio/types.go
+++ b/types.go
@@ -2,6 +2,19 @@ package putio
import "fmt"
+// File types.
+const (
+ FileTypeFolder string = "FOLDER"
+ FileTypeFile string = "FILE"
+ FileTypeAudio string = "AUDIO"
+ FileTypeVideo string = "VIDEO"
+ FileTypeImage string = "IMAGE"
+ FileTypeArchive string = "ARCHIVE"
+ FileTypePDF string = "PDF"
+ FileTypeText string = "TEXT"
+ FileTypeSWF string = "SWF"
+)
+
// File represents a Put.io file.
type File struct {
ID int64 `json:"id"`
@@ -18,6 +31,7 @@ type File struct {
Icon string `json:"icon"`
CRC32 string `json:"crc32"`
IsShared bool `json:"is_shared"`
+ FileType string `json:"file_type"`
}
func (f *File) String() string {
@@ -50,6 +64,7 @@ type Transfer struct {
CreatedTorrent bool `json:"created_torrent"`
ClientIP string `json:"client_ip"`
+ // nolint:godox
// FIXME: API returns either string or float non-deterministically.
// CurrentRatio float32 `json:"current_ratio"`
diff --git a/upload.go b/upload.go
new file mode 100644
index 0000000..bab9661
--- /dev/null
+++ b/upload.go
@@ -0,0 +1,190 @@
+package putio
+
+import (
+ "context"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// UploadService uses TUS (resumable upload protocol) for sending files to put.io.
+type UploadService struct {
+ // Log is a user supplied function to collect log messages from upload methods.
+ Log func(message string)
+
+ client *Client
+}
+
+func (u *UploadService) log(message string) {
+ if u.Log != nil {
+ u.Log(message)
+ }
+}
+
+// CreateUpload is used for beginning new upload. Use returned location in SendFile function.
+func (u *UploadService) CreateUpload(
+ ctx context.Context,
+ filename string,
+ parentID, length int64,
+ overwrite bool,
+) (location string, err error) {
+ u.log(fmt.Sprintf("Creating upload %q at parent=%d", filename, parentID))
+ req, err := u.client.NewRequest(ctx, http.MethodPost, "$upload-tus$", nil)
+ if err != nil {
+ return
+ }
+ metadata := map[string]string{
+ "name": filename,
+ "parent_id": strconv.FormatInt(parentID, 10),
+ "no-torrent": "true",
+ "overwrite": strconv.FormatBool(overwrite),
+ }
+ req.Header.Set("Content-Length", "0")
+ req.Header.Set("Upload-Length", strconv.FormatInt(length, 10))
+ req.Header.Set("Upload-Metadata", encodeMetadata(metadata))
+
+ resp, err := u.client.client.Do(req)
+ if err != nil {
+ return
+ }
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+
+ u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+ if resp.StatusCode != http.StatusCreated {
+ err = fmt.Errorf("%w status: %d", ErrUnexpected, resp.StatusCode)
+ return
+ }
+ location = resp.Header.Get("Location")
+ return
+}
+
+// SendFile sends the contents of the file to put.io.
+// In case of an transmission error, you can resume upload but you have to get the correct offset from server by
+// calling GetOffset and must seek to the new offset on io.Reader.
+func (u *UploadService) SendFile(
+ ctx context.Context,
+ r io.Reader,
+ location string,
+ offset int64,
+) (fileID int64, crc32 string, err error) {
+ u.log(fmt.Sprintf("Sending file %q offset=%d", location, offset))
+
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ // Stop upload if speed is too slow.
+ // Wrap reader so each read call resets the timer that cancels the request on certain duration.
+ if u.client.Timeout > 0 {
+ r = &timerResetReader{r: r, timer: time.AfterFunc(u.client.Timeout, cancel), timeout: u.client.Timeout}
+ }
+
+ req, err := u.client.NewRequest(ctx, http.MethodPatch, location, r)
+ if err != nil {
+ return
+ }
+
+ // putio.Client.NewRequests add another context for handling Client.Timeout. Replace it with original context.
+ // Request must not be cancelled on timeout because sending upload body takes a long time.
+ // We will be using timerResetReader for tracking uploaded bytes and doint cancellation there.
+ if u.client.Timeout > 0 {
+ req = req.WithContext(ctx)
+ }
+
+ req.Header.Set("content-type", "application/offset+octet-stream")
+ req.Header.Set("upload-offset", strconv.FormatInt(offset, 10))
+ resp, err := u.client.client.Do(req)
+ if err != nil {
+ return
+ }
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+
+ u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+ if resp.StatusCode != http.StatusNoContent {
+ err = fmt.Errorf("%w status: %d", ErrUnexpected, resp.StatusCode)
+ return
+ }
+ fileID, err = strconv.ParseInt(resp.Header.Get("putio-file-id"), 10, 64)
+ if err != nil {
+ err = fmt.Errorf("cannot parse putio-file-id header: %w", err)
+ return
+ }
+ crc32 = resp.Header.Get("putio-file-crc32")
+ return
+}
+
+// GetOffset returns the offset at the server.
+func (u *UploadService) GetOffset(ctx context.Context, location string) (n int64, err error) {
+ u.log(fmt.Sprintf("Getting upload offset %q", location))
+ req, err := u.client.NewRequest(ctx, http.MethodHead, location, nil)
+ if err != nil {
+ return
+ }
+
+ resp, err := u.client.client.Do(req)
+ if err != nil {
+ return
+ }
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+
+ u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+ if resp.StatusCode != http.StatusOK {
+ err = fmt.Errorf("%w status: %d", ErrUnexpected, resp.StatusCode)
+ return
+ }
+ n, err = strconv.ParseInt(resp.Header.Get("upload-offset"), 10, 64)
+ u.log(fmt.Sprintln("uploadJob offset:", n))
+ return n, fmt.Errorf("%w", err)
+}
+
+// TerminateUpload removes incomplete file from the server.
+func (u *UploadService) TerminateUpload(ctx context.Context, location string) (err error) {
+ u.log(fmt.Sprintf("Terminating upload %q", location))
+ req, err := u.client.NewRequest(ctx, http.MethodDelete, location, nil)
+ if err != nil {
+ return
+ }
+
+ resp, err := u.client.client.Do(req)
+ if err != nil {
+ return
+ }
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+
+ u.log(fmt.Sprintln("Status code:", resp.StatusCode))
+ if resp.StatusCode != http.StatusNoContent {
+ err = fmt.Errorf("%w status: %d", ErrUnexpected, resp.StatusCode)
+ return
+ }
+ return nil
+}
+
+func encodeMetadata(metadata map[string]string) string {
+ encoded := make([]string, 0, len(metadata))
+ for k, v := range metadata {
+ encoded = append(encoded, fmt.Sprintf("%s %s", k, base64.StdEncoding.EncodeToString([]byte(v))))
+ }
+ return strings.Join(encoded, ",")
+}
+
+type timerResetReader struct {
+ r io.Reader
+ timer *time.Timer
+ timeout time.Duration
+}
+
+func (r *timerResetReader) Read(p []byte) (int, error) {
+ r.timer.Reset(r.timeout)
+ return r.r.Read(p) // nolint:wrapcheck
+}
diff --git a/putio/zips.go b/zips.go
similarity index 71%
rename from putio/zips.go
rename to zips.go
index 66e33cc..ee8d812 100644
--- a/putio/zips.go
+++ b/zips.go
@@ -2,7 +2,7 @@ package putio
import (
"context"
- "fmt"
+ "net/http"
"net/url"
"strings"
)
@@ -14,24 +14,23 @@ type ZipsService struct {
// Get gives detailed information about the given zip file id.
func (z *ZipsService) Get(ctx context.Context, id int64) (Zip, error) {
- req, err := z.client.NewRequest(ctx, "GET", "/v2/zips/"+itoa(id), nil)
+ req, err := z.client.NewRequest(ctx, http.MethodGet, "/v2/zips/"+itoa(id), nil)
if err != nil {
return Zip{}, err
}
var r Zip
- _, err = z.client.Do(req, &r)
+ _, err = z.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return Zip{}, err
}
return r, nil
-
}
// List lists active zip files.
func (z *ZipsService) List(ctx context.Context) ([]Zip, error) {
- req, err := z.client.NewRequest(ctx, "GET", "/v2/zips/list", nil)
+ req, err := z.client.NewRequest(ctx, http.MethodGet, "/v2/zips/list", nil)
if err != nil {
return nil, err
}
@@ -39,7 +38,7 @@ func (z *ZipsService) List(ctx context.Context) ([]Zip, error) {
var r struct {
Zips []Zip
}
- _, err = z.client.Do(req, &r)
+ _, err = z.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return nil, err
}
@@ -51,10 +50,10 @@ func (z *ZipsService) List(ctx context.Context) ([]Zip, error) {
// a zip ID will be returned to keep track of zip process.
func (z *ZipsService) Create(ctx context.Context, fileIDs ...int64) (int64, error) {
if len(fileIDs) == 0 {
- return 0, fmt.Errorf("no file id given")
+ return 0, ErrNoFileIDIsGiven
}
- var ids []string
+ ids := []string{}
for _, id := range fileIDs {
ids = append(ids, itoa(id))
}
@@ -62,7 +61,7 @@ func (z *ZipsService) Create(ctx context.Context, fileIDs ...int64) (int64, erro
params := url.Values{}
params.Set("file_ids", strings.Join(ids, ","))
- req, err := z.client.NewRequest(ctx, "POST", "/v2/zips/create", strings.NewReader(params.Encode()))
+ req, err := z.client.NewRequest(ctx, http.MethodPost, "/v2/zips/create", strings.NewReader(params.Encode()))
if err != nil {
return 0, err
}
@@ -71,7 +70,7 @@ func (z *ZipsService) Create(ctx context.Context, fileIDs ...int64) (int64, erro
var r struct {
ID int64 `json:"zip_id"`
}
- _, err = z.client.Do(req, &r)
+ _, err = z.client.Do(req, &r) // nolint:bodyclose
if err != nil {
return 0, err
}
diff --git a/putio/zips_test.go b/zips_test.go
similarity index 94%
rename from putio/zips_test.go
rename to zips_test.go
index f868d3a..0250923 100644
--- a/putio/zips_test.go
+++ b/zips_test.go
@@ -20,7 +20,7 @@ func TestZips_Get(t *testing.T) {
}
`
mux.HandleFunc("/v2/zips/1", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -50,7 +50,7 @@ func TestZips_List(t *testing.T) {
}
`
mux.HandleFunc("/v2/zips/list", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
+ testMethod(t, r, http.MethodGet)
fmt.Fprintln(w, fixture)
})
@@ -79,7 +79,7 @@ func TestZips_Create(t *testing.T) {
}
`
mux.HandleFunc("/v2/zips/create", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "POST")
+ testMethod(t, r, http.MethodPost)
testHeader(t, r, "Content-Type", "application/x-www-form-urlencoded")
fmt.Fprintln(w, fixture)
})
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/account.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/account_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/client.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/client_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/config.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/doc.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/error.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/events.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/events_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/example_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/files.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/files_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/friends.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/friends_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/go.mod -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/go.sum -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/time.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/transfers.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/transfers_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/types.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/upload.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/zips.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/zips_test.go
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/account.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/account_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/client.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/client_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/doc.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/error.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/events.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/events_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/example_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/files.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/files_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/friends.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/friends_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/time.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/transfers.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/transfers_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/types.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/zips.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/putdotio/go-putio/putio/zips_test.go
No differences were encountered in the control files