Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ linters:
settings:
revive:
rules:
# Most of the exported API still lacks doc comments, which is worth
# fixing because they show on pkg.go.dev. Until that pass is done,
# leaving this on would keep CI permanently red.
# Every exported symbol needs a doc comment: they are the API
# reference on pkg.go.dev. Stuttering names such as disk.DiskLogger
# are allowed, since renaming them would break callers.
- name: exported
disabled: true
arguments:
- disableStutteringCheck

exclusions:
presets:
Expand Down
4 changes: 3 additions & 1 deletion batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,9 @@ func (c *Client) BatchUpdateMetadata(ctx context.Context, paths []string, custom
return status, nil
}

// GetBatchOperationsSummary provides a summary of batch operation results
// GetSummary returns the state of a batch operation as a map with the keys
// "total", "completed", "successful", "failed", "percentage" and "duration",
// plus "completed_at" (RFC 3339) once the batch has finished.
func (status *BatchOperationStatus) GetSummary() map[string]interface{} {
summary := map[string]interface{}{
"total": status.Total,
Expand Down
29 changes: 21 additions & 8 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import (
"time"
)

// Context management and timeout handling implemented

// API_URL is the Yandex.Disk REST API endpoint the client talks to unless
// [ClientConfig.BaseURL] says otherwise.
const API_URL = "https://cloud-api.yandex.net/v1/disk/"

// HttpMethod is an HTTP method used for requests to the API.
type HttpMethod string

// HTTP methods used by the client.
const (
GET HttpMethod = "GET"
POST HttpMethod = "POST"
Expand Down Expand Up @@ -63,14 +65,24 @@ func (c *Client) baseURL() string {
return API_URL
}

// Client talks to the Yandex.Disk REST API on behalf of one user. Create it
// with [New] or [NewWithConfig]. A Client is safe for concurrent use.
//
// Most methods take a context, which bounds how long the request may take,
// and paths such as "disk:/Photos" or "/Photos" (the two are the same).
type Client struct {
// AccessToken is the user's OAuth token, sent with every request.
AccessToken string
HTTPClient *http.Client
Logger *DiskLogger
Config *ClientConfig
// HTTPClient sends the requests. Replace it to use your own transport.
HTTPClient *http.Client
// Logger receives what the client logs; see [ClientConfig.Logger].
Logger *DiskLogger
// Config is the configuration the client was created with.
Config *ClientConfig
}

// NewWithConfig creates a new Client with custom configuration
// NewWithConfig creates a Client with the given configuration, or with
// [DefaultClientConfig] when config is nil. The token works as in [New].
func NewWithConfig(config *ClientConfig, token ...string) (*Client, error) {
if len(token) == 0 {
envToken := os.Getenv("YANDEX_DISK_ACCESS_TOKEN")
Expand Down Expand Up @@ -124,8 +136,9 @@ func NewWithConfig(config *ClientConfig, token ...string) (*Client, error) {
}, nil
}

// New(token ...string) fetch token from OS env var if has not direct defined
// Uses default configuration for backward compatibility
// New creates a Client with [DefaultClientConfig] and the given OAuth token.
// Without a token argument, it reads one from the YANDEX_DISK_ACCESS_TOKEN
// environment variable, and fails if that is empty too.
func New(token ...string) (*Client, error) {
return NewWithConfig(nil, token...)
}
Expand Down
2 changes: 2 additions & 0 deletions disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
)

// DiskInfo returns the user's Disk: its size, how much is used, and who owns
// it.
func (c *Client) DiskInfo(ctx context.Context) (*Disk, error) {
var disk *Disk
resp, err := c.doRequest(ctx, GET, "", nil)
Expand Down
44 changes: 44 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Package disk is a client for the Yandex.Disk REST API: list, upload,
// download, copy, move, publish and delete files, and work with the trash.
//
// # Getting started
//
// The client signs requests with a user's OAuth token. Register an
// application at https://oauth.yandex.ru with the cloud_api:disk.* scopes
// and obtain a token for the user, then:
//
// client, err := disk.New("y0_...") // or disk.New() to read YANDEX_DISK_ACCESS_TOKEN
// if err != nil {
// log.Fatal(err)
// }
// info, err := client.DiskInfo(ctx)
//
// Paths may be written as "disk:/Photos/cat.jpg" or "/Photos/cat.jpg"; the
// two are the same. The trash uses "trash:/".
//
// # Errors
//
// Many methods return an [*ErrorResponse] in place of an error: nil on
// success, otherwise its Error field carries the API's code, such as
// "DiskNotFoundError", and StatusCode the HTTP status. Others, such as
// [Client.DiskInfo] and the upload and download helpers, return a plain
// error.
//
// # Background operations
//
// Copying or moving a large folder can continue after the request
// returns. Such methods return a [Link] to the operation; poll it with
// [Client.GetOperationStatus] until it reports "success" or "failed".
//
// # Pages
//
// Lists are paged. Methods ending in WithPagination take a
// [PaginationOptions], those ending in Paged report whether more pages
// follow, and those ending in Iterator walk all pages for you.
//
// # Configuration
//
// [NewWithConfig] sets timeouts, retries, logging and the API endpoint; see
// [ClientConfig]. Requests without a body are retried on connection errors
// and on 429 and 5xx answers.
package disk
98 changes: 98 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package disk_test

import (
"context"
"fmt"
"log"
"strings"

"github.com/ilyabrin/disk"
)

// These examples talk to the real API, so they are compiled but not run.

func Example() {
// Reads YANDEX_DISK_ACCESS_TOKEN when no token is passed.
client, err := disk.New()
if err != nil {
log.Fatal(err)
}

info, err := client.DiskInfo(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s of %s used\n", disk.FormatFileSize(info.UsedSpace), disk.FormatFileSize(info.TotalSpace))
}

func ExampleClient_GetMetadataWithOptions() {
client, err := disk.New()
if err != nil {
log.Fatal(err)
}

// The 20 most recently changed items in Photos.
folder, errResp := client.GetMetadataWithOptions(context.Background(), "disk:/Photos",
&disk.ResourceOptions{Limit: 20, Sort: "-modified"})
if errResp != nil {
log.Fatalf("%s: %s", errResp.Error, errResp.Description)
}
for _, item := range folder.Embedded.Items {
fmt.Println(item.Type, item.Name, disk.FormatFileSize(item.Size))
}
}

func ExampleClient_UploadFileFromPath() {
client, err := disk.New()
if err != nil {
log.Fatal(err)
}

res, err := client.UploadFileFromPath(context.Background(), "report.pdf", "disk:/Documents/report.pdf",
&disk.UploadOptions{Overwrite: true})
if err != nil {
log.Fatal(err)
}
fmt.Println("uploaded", res.Path)
}

func ExampleClient_PublishResource() {
client, err := disk.New()
if err != nil {
log.Fatal(err)
}
ctx := context.Background()

if _, errResp := client.PublishResource(ctx, "disk:/Documents/report.pdf"); errResp != nil {
log.Fatal(errResp.Error)
}
res, errResp := client.GetMetadata(ctx, "disk:/Documents/report.pdf")
if errResp != nil {
log.Fatal(errResp.Error)
}
fmt.Println("anyone can open", res.PublicURL)
}

func ExampleClient_GetOperationStatus() {
client, err := disk.New()
if err != nil {
log.Fatal(err)
}
ctx := context.Background()

link, errResp := client.CopyResource(ctx, "disk:/Photos", "disk:/Backup/Photos")
if errResp != nil {
log.Fatal(errResp.Error)
}
// A small folder is copied at once and link points to the copy. A large
// one is copied in the background and link points to the operation.
if !strings.Contains(link.Href, "/operations/") {
fmt.Println("copied")
return
}
op, err := client.GetOperationStatus(ctx, link.Href)
if err != nil {
log.Fatal(err)
}
fmt.Println("copy is", op.Status)
}
1 change: 1 addition & 0 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
// LogLevel represents the severity level of a log message
type LogLevel int

// Log levels, from the most to the least verbose. SILENT turns logging off.
const (
DEBUG LogLevel = iota
INFO
Expand Down
8 changes: 7 additions & 1 deletion operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import (
"strings"
)

// TODO: add tests and use generics instead of interface{}
// OperationStatus asks for the state of a background operation by its ID.
// The result is an [*Operation], or an [*ErrorResponse] (with a nil err)
// when the API reports an error. The returned response's body is already
// closed.
//
// Prefer [Client.GetOperationStatus], which returns an [*Operation] directly
// and also accepts the href from a [Link].
func (c *Client) OperationStatus(ctx context.Context, operationID string) (any, *http.Response, error) {
query := url.Values{}
query.Set("operation_id", operationID)
Expand Down
7 changes: 7 additions & 0 deletions public.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ func (o *PublicResourceOptions) apply(query url.Values) {
}
}

// GetMetadataForPublicResource returns a published file or folder by its
// public key or public link. It works for anyone's published resources.
func (c *Client) GetMetadataForPublicResource(ctx context.Context, public_key string) (*PublicResource, *ErrorResponse) {
return c.GetMetadataForPublicResourceWithOptions(ctx, public_key, nil)
}
Expand All @@ -66,6 +68,8 @@ func (c *Client) GetMetadataForPublicResourceWithOptions(ctx context.Context, pu
return requestJSON[PublicResource](ctx, c, GET, "public/resources?"+query.Encode(), nil)
}

// GetDownloadURLForPublicResource returns a link to download a published
// file, or a published folder as a zip archive, by its public key or link.
func (c *Client) GetDownloadURLForPublicResource(ctx context.Context, public_key string) (*Link, *ErrorResponse) {
return c.GetDownloadURLForPublicResourceAt(ctx, public_key, "")
}
Expand Down Expand Up @@ -94,6 +98,9 @@ type SavePublicResourceOptions struct {
Name string // Name to save the resource under
}

// SavePublicResource copies a published file or folder into the user's
// Downloads folder. See [Client.SavePublicResourceWithOptions] for what the
// returned [Link] points to.
func (c *Client) SavePublicResource(ctx context.Context, public_key string) (*Link, *ErrorResponse) {
return c.SavePublicResourceWithOptions(ctx, public_key, nil)
}
Expand Down
Loading
Loading