rclone/vendor/github.com/putdotio/go-putio/putio/files.go

430 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package putio
import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"net/url"
"strconv"
"strings"
)
// FilesService is a general service to gather information about user files,
// such as listing, searching, creating new ones, or just fetching a single
// file.
type FilesService struct {
client *Client
}
// 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)
if err != nil {
return File{}, err
}
var r struct {
File File `json:"file"`
}
_, err = f.client.Do(req, &r)
if err != nil {
return File{}, err
}
return r.File, nil
}
// 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)
if err != nil {
return
}
var r struct {
Files []File `json:"files"`
Parent File `json:"parent"`
Cursor string `json:"cursor"`
}
_, err = f.client.Do(req, &r)
if err != nil {
return
}
children = append(children, r.Files...)
parent = r.Parent
for r.Cursor != "" {
body := strings.NewReader(`{"cursor": "` + r.Cursor + `"}`)
req, err = f.client.NewRequest(ctx, "POST", "/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)
if err != nil {
return
}
children = append(children, r.Files...)
}
return
}
// URL returns a URL of the file for downloading or streaming.
func (f *FilesService) URL(ctx context.Context, id int64, useTunnel bool) (string, error) {
notunnel := "notunnel=1"
if useTunnel {
notunnel = "notunnel=0"
}
req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/url?"+notunnel, nil)
if err != nil {
return "", err
}
var r struct {
URL string `json:"url"`
}
_, err = f.client.Do(req, &r)
if err != nil {
return "", err
}
return r.URL, nil
}
// 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")
}
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()))
if err != nil {
return File{}, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
var r struct {
File File `json:"file"`
}
_, err = f.client.Do(req, &r)
if err != nil {
return File{}, err
}
return r.File, nil
}
// 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")
}
var ids []string
for _, id := range files {
ids = append(ids, itoa(id))
}
params := url.Values{}
params.Set("file_ids", strings.Join(ids, ","))
req, err := f.client.NewRequest(ctx, "POST", "/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{}{})
if err != nil {
return err
}
return nil
}
// 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")
}
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()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
_, err = f.client.Do(req, &struct{}{})
if err != nil {
return err
}
return nil
}
// 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")
}
var ids []string
for _, file := range files {
ids = append(ids, itoa(file))
}
params := url.Values{}
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()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
_, err = f.client.Do(req, &struct{}{})
if err != nil {
return err
}
return nil
}
// 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.
//
// 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.
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")
}
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
// negative parent means use user's prefered download folder.
if parent >= 0 {
err := mw.WriteField("parent_id", itoa(parent))
if err != nil {
return Upload{}, err
}
}
formfile, err := mw.CreateFormFile("file", filename)
if err != nil {
return Upload{}, err
}
_, err = io.Copy(formfile, r)
if err != nil {
return Upload{}, err
}
err = mw.Close()
if err != nil {
return Upload{}, err
}
req, err := f.client.NewRequest(ctx, "POST", "/v2/files/upload", &buf)
if err != nil {
return Upload{}, err
}
req.Header.Set("Content-Type", mw.FormDataContentType())
var response struct {
Upload
}
_, err = f.client.Do(req, &response)
if err != nil {
return Upload{}, err
}
return response.Upload, nil
}
// Search makes a search request with the given query. Servers return 50
// results at a time. The URL for the next 50 results are in Next field. If
// 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")
}
if query == "" {
return Search{}, fmt.Errorf("no query given")
}
req, err := f.client.NewRequest(ctx, "GET", "/v2/files/search/"+query+"/page/"+itoa(page), nil)
if err != nil {
return Search{}, err
}
var r Search
_, err = f.client.Do(req, &r)
if err != nil {
return Search{}, err
}
return r, nil
}
// 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)
if err != nil {
return nil, err
}
var r struct {
Shared []share
}
_, err = f.client.Do(req, &r)
if err != nil {
return nil, err
}
return r.Shared, nil
}
// SharedWith returns list of users the given file is shared with.
func (f *FilesService) sharedWith(ctx context.Context, id int64) ([]share, error) {
// 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)
if err != nil {
return nil, err
}
var r struct {
Shared []share `json:"shared-with"`
}
_, err = f.client.Do(req, &r)
if err != nil {
return nil, err
}
return r.Shared, nil
}
// Subtitles lists available subtitles for the given file for user's prefered
// 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)
if err != nil {
return nil, err
}
var r struct {
Subtitles []Subtitle
Default string
}
_, err = f.client.Do(req, &r)
if err != nil {
return nil, err
}
return r.Subtitles, nil
}
// DownloadSubtitle sends the contents of the subtitle file. If the key is empty string,
// `default` key is used. This key is used to search for a subtitle in the
// following order and returns the first match:
// - 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) {
if key == "" {
key = "default"
}
req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/subtitles/"+key, nil)
if err != nil {
return nil, err
}
resp, err := f.client.Do(req, nil)
if err != nil {
return nil, err
}
return resp.Body, nil
}
// HLSPlaylist serves a HLS playlist for a video file. Use “all” as
// subtitleKey to get available subtitles for users 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")
}
req, err := f.client.NewRequest(ctx, "GET", "/v2/files/"+itoa(id)+"/hls/media.m3u8?subtitle_key"+subtitleKey, nil)
if err != nil {
return nil, err
}
resp, err := f.client.Do(req, nil)
if err != nil {
return nil, err
}
return resp.Body, nil
}
// 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")
}
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()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
_, err = f.client.Do(req, &struct{}{})
if err != nil {
return err
}
return nil
}
// 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)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
_, err = f.client.Do(req, &struct{}{})
if err != nil {
return err
}
return nil
}
func itoa(i int64) string {
return strconv.FormatInt(i, 10)
}