diff --git a/.golangci.yml b/.golangci.yml index f618def..611d2e2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -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: diff --git a/batch.go b/batch.go index 40da9a1..56e41ad 100644 --- a/batch.go +++ b/batch.go @@ -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, diff --git a/client.go b/client.go index 14076d1..af06f86 100644 --- a/client.go +++ b/client.go @@ -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" @@ -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") @@ -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...) } diff --git a/disk.go b/disk.go index 8f279d4..f00f73b 100644 --- a/disk.go +++ b/disk.go @@ -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) diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..7d98212 --- /dev/null +++ b/doc.go @@ -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 diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..089ab33 --- /dev/null +++ b/example_test.go @@ -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) +} diff --git a/logger.go b/logger.go index ed96912..26a3e26 100644 --- a/logger.go +++ b/logger.go @@ -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 diff --git a/operations.go b/operations.go index d3590f9..667f1f5 100644 --- a/operations.go +++ b/operations.go @@ -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) diff --git a/public.go b/public.go index b3f0f1f..26706a3 100644 --- a/public.go +++ b/public.go @@ -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) } @@ -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, "") } @@ -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) } diff --git a/resources.go b/resources.go index d2dbf20..fc76610 100644 --- a/resources.go +++ b/resources.go @@ -51,7 +51,8 @@ func (c *Client) buildDeleteResourceURL(path string, permanently bool) string { return fmt.Sprintf("resources?%s", query.Encode()) } -// todo: add *ErrorResponse to return +// DeleteResource deletes the file or folder at path. It goes to the trash, +// or is removed for good when permanently is true. func (c *Client) DeleteResource(ctx context.Context, path string, permanently bool) error { if err := validatePath(path); err != nil { return fmt.Errorf("delete error: %w", err) @@ -109,6 +110,9 @@ func (o *ResourceOptions) apply(query url.Values) { } } +// GetMetadata returns the file or folder at path. For a folder, Embedded +// holds the first page of its contents; use [Client.GetMetadataWithOptions] +// to choose the page, the order or the fields. func (c *Client) GetMetadata(ctx context.Context, path string) (*Resource, *ErrorResponse) { return c.GetMetadataWithOptions(ctx, path, nil) } @@ -128,17 +132,14 @@ func (c *Client) GetMetadataWithOptions(ctx context.Context, path string, opts * return requestJSON[Resource](ctx, c, GET, "resources?"+query.Encode(), nil) } -/* -todo: add examples to README - - newMeta := map[string]map[string]string{ - "custom_properties": { - "key_01": "value_01", - "key_02": "value_02", - "key_07": "value_07", - }, - } -*/ +// UpdateMetadata sets custom attributes on the file or folder at path and +// returns it with [Resource.CustomProperties] updated. The map is sent as +// the request body, so the attributes go under "custom_properties": +// +// props := map[string]map[string]string{ +// "custom_properties": {"project": "yad", "reviewed": "yes"}, +// } +// res, errResp := client.UpdateMetadata(ctx, "disk:/report.pdf", props) func (c *Client) UpdateMetadata(ctx context.Context, path string, custom_properties map[string]map[string]string) (*Resource, *ErrorResponse) { if len(path) < 1 { return nil, &ErrorResponse{Error: "path cannot be empty"} @@ -229,6 +230,9 @@ func (c *Client) CreateDirAll(ctx context.Context, path string) *ErrorResponse { return nil } +// CopyResource copies the file or folder at from to path. It fails if path +// already exists. Copying a large folder may continue in the background; the +// returned [Link] then points to the operation, see [Client.GetOperationStatus]. func (c *Client) CopyResource(ctx context.Context, from, path string) (*Link, *ErrorResponse) { if len(from) < 1 || len(path) < 1 { return nil, &ErrorResponse{Error: "from and path cannot be empty"} @@ -242,6 +246,9 @@ func (c *Client) CopyResource(ctx context.Context, from, path string) (*Link, *E http.StatusOK, http.StatusCreated, http.StatusAccepted) } +// GetDownloadURL returns a link to download the file at path. The link +// works without the access token for a limited time. For a folder, it +// downloads a zip archive. func (c *Client) GetDownloadURL(ctx context.Context, path string) (*Link, *ErrorResponse) { if len(path) < 1 { return nil, &ErrorResponse{Error: "path cannot be empty"} @@ -288,6 +295,9 @@ func (o *FilesOptions) apply(query url.Values) { } } +// GetSortedFiles returns the first page of all files on the Disk, regardless +// of folder, in the API's default order. See +// [Client.GetSortedFilesWithOptions] to filter, sort or page through them. func (c *Client) GetSortedFiles(ctx context.Context) (*FilesResourceList, *ErrorResponse) { return c.GetSortedFilesWithPagination(ctx, nil) } @@ -352,7 +362,8 @@ func (c *Client) GetSortedFilesIterator(options *PaginationOptions) *PaginationI return NewPaginationIterator(c, fetcher, options) } -// get | sortBy = [name = default, uploadDate] +// GetLastUploadedResources returns the files uploaded most recently, newest +// first. func (c *Client) GetLastUploadedResources(ctx context.Context) (*LastUploadedResourceList, *ErrorResponse) { return c.GetLastUploadedResourcesWithPagination(ctx, nil) } @@ -410,6 +421,10 @@ func (c *Client) GetLastUploadedResourcesIterator(options *PaginationOptions) *P return NewPaginationIterator(c, fetcher, options) } +// MoveResource moves or renames the file or folder at from to path. It fails +// if path already exists. Moving a large folder may continue in the +// background; the returned [Link] then points to the operation, see +// [Client.GetOperationStatus]. func (c *Client) MoveResource(ctx context.Context, from, path string) (*Link, *ErrorResponse) { if len(from) < 1 || len(path) < 1 { return nil, &ErrorResponse{Error: "from and path cannot be empty"} @@ -423,6 +438,8 @@ func (c *Client) MoveResource(ctx context.Context, from, path string) (*Link, *E http.StatusCreated, http.StatusAccepted) } +// GetPublicResources returns the first page of the user's published files +// and folders. func (c *Client) GetPublicResources(ctx context.Context) (*PublicResourcesList, *ErrorResponse) { return c.GetPublicResourcesWithPagination(ctx, nil) } @@ -480,6 +497,9 @@ func (c *Client) GetPublicResourcesIterator(options *PaginationOptions) *Paginat return NewPaginationIterator(c, fetcher, options) } +// PublishResource publishes the file or folder at path, so anyone with the +// link can open it. The returned [Link] points to the resource; fetch it with +// [Client.GetMetadata] to read [Resource.PublicURL]. func (c *Client) PublishResource(ctx context.Context, path string) (*Link, *ErrorResponse) { if len(path) < 1 { return nil, &ErrorResponse{Error: "path cannot be empty"} @@ -491,6 +511,7 @@ func (c *Client) PublishResource(ctx context.Context, path string) (*Link, *Erro return requestJSON[Link](ctx, c, PUT, "resources/publish?"+query.Encode(), nil) } +// UnpublishResource withdraws the public link of the file or folder at path. func (c *Client) UnpublishResource(ctx context.Context, path string) (*Link, *ErrorResponse) { if len(path) < 1 { return nil, &ErrorResponse{Error: "path cannot be empty"} @@ -502,6 +523,9 @@ func (c *Client) UnpublishResource(ctx context.Context, path string) (*Link, *Er return requestJSON[Link](ctx, c, PUT, "resources/unpublish?"+query.Encode(), nil) } +// GetLinkForUpload returns where to send the contents of a new file at path. +// Most callers want [Client.UploadFileFromPath] instead, which does both +// steps. func (c *Client) GetLinkForUpload(ctx context.Context, path string) (*ResourceUploadLink, *ErrorResponse) { if len(path) < 1 { return nil, &ErrorResponse{Error: "path cannot be empty"} @@ -513,8 +537,10 @@ func (c *Client) GetLinkForUpload(ctx context.Context, path string) (*ResourceUp return requestJSON[ResourceUploadLink](ctx, c, GET, "resources/upload?"+query.Encode(), nil) } -// UploadFile asks Yandex Disk to fetch the file at uploadURL and store it at path. -// todo: empty resonses - fix it +// UploadFile asks Yandex.Disk to download the file at uploadURL from the +// internet and store it at path. The download happens on Yandex's side and +// in the background; the returned [Link] points to the operation, see +// [Client.GetOperationStatus]. func (c *Client) UploadFile(ctx context.Context, path, uploadURL string) (*Link, *ErrorResponse) { if len(path) < 1 || len(uploadURL) < 1 { return nil, &ErrorResponse{Error: "path and url cannot be empty"} diff --git a/types.go b/types.go index b8af9a6..81b6766 100644 --- a/types.go +++ b/types.go @@ -1,18 +1,35 @@ package disk -// GET /v1/disk +// Disk describes the user's Disk as a whole: space, limits and the owner. +// [Client.DiskInfo] returns it. +// +// Sizes are in bytes. type Disk struct { - UnlimitedAutouploadEnabled bool `json:"unlimited_autoupload_enabled,omitempty"` // boolean, optional: - MaxFileSize int64 `json:"max_file_size,omitempty"` // integer, optional: - TotalSpace int64 `json:"total_space,omitempty"` // integer, optional: - TrashSize int64 `json:"trash_size,omitempty"` // integer, optional: - IsPaid bool `json:"is_paid,omitempty"` // boolean, optional: - UsedSpace int64 `json:"used_space,omitempty"` // integer, optional: - SystemFolders *SystemFolders `json:"system_folders,omitempty"` // (SystemFolders, optional) - User *User `json:"user,omitempty"` // (User, optional) - Revision int64 `json:"revision,omitempty"` // (integer, optional): + // UnlimitedAutouploadEnabled reports whether photos and videos uploaded + // automatically from phones do not count against the space limit. + UnlimitedAutouploadEnabled bool `json:"unlimited_autoupload_enabled,omitempty"` + // MaxFileSize is the largest file that can be uploaded. + MaxFileSize int64 `json:"max_file_size,omitempty"` + // TotalSpace is the size of the Disk. + TotalSpace int64 `json:"total_space,omitempty"` + // TrashSize is how much the trash takes up. It counts towards UsedSpace. + TrashSize int64 `json:"trash_size,omitempty"` + // IsPaid reports whether the user has a paid plan. + IsPaid bool `json:"is_paid,omitempty"` + // UsedSpace is how much of TotalSpace is taken, trash included. + UsedSpace int64 `json:"used_space,omitempty"` + // SystemFolders holds the paths of folders the Disk manages itself. + SystemFolders *SystemFolders `json:"system_folders,omitempty"` + // User is the owner of the Disk. + User *User `json:"user,omitempty"` + // Revision changes whenever anything on the Disk changes. Yandex sends + // it as a timestamp in microseconds. + Revision int64 `json:"revision,omitempty"` } +// SystemFolders holds the paths of the folders the Disk creates and fills on +// its own, such as downloads or photos from social networks. Each value is a +// path like "disk:/Загрузки/", in the language of the account. type SystemFolders struct { Odnoklassniki string `json:"odnoklassniki,omitempty"` Google string `json:"google,omitempty"` @@ -27,133 +44,248 @@ type SystemFolders struct { Photostream string `json:"photostream,omitempty"` } +// User is the owner of a Disk, as part of [Disk]. type User struct { - Country string `json:"country,omitempty"` // string, optional: <Страна>, - Login string `json:"login,omitempty"` // string, optional: <Логин>, - DisplayName string `json:"display_name,omitempty"` // string, optional: <Отображаемое имя>, - Uid string `json:"uid,omitempty"` // string, optional: <Идентификатор пользователя> + // Country is the two-letter country code, such as "ru". + Country string `json:"country,omitempty"` + // Login is the Yandex login. + Login string `json:"login,omitempty"` + // DisplayName is the name shown to other people. + DisplayName string `json:"display_name,omitempty"` + // Uid is the numeric user ID, as a string. + Uid string `json:"uid,omitempty"` } +// Resource is a file or a folder on the Disk. +// +// For a folder fetched with its contents, Embedded holds one page of the +// items inside it. Times are ISO 8601 strings, such as +// "2026-10-07T12:00:00+00:00"; sizes are in bytes. type Resource struct { - AntivirusStatus string `json:"antivirus_status,omitempty"` // (object, optional): <Статус проверки антивирусом>, - ResourceID string `json:"resource_id,omitempty"` // (string, optional): <Идентификатор ресурса>, - Share *ShareInfo `json:"share,omitempty"` // (ShareInfo, optional), - File string `json:"file,omitempty"` // (string, optional): , - Size int64 `json:"size,omitempty"` // (integer, optional): <Размер файла>, - PhotosliceTime string `json:"photoslice_time,omitempty"` // (string, optional): <Дата создания фото или видео файла>, - Embedded *ResourceList `json:"_embedded,omitempty"` // (ResourceList, optional), - Exif *Exif `json:"exif,omitempty"` // (Exif, optional), - CustomProperties interface{} `json:"custom_properties,omitempty"` // (object, optional): <Пользовательские атрибуты ресурса>, - MediaType string `json:"media_type,omitempty"` // (string, optional): <Определённый Диском тип файла>, - Preview string `json:"preview,omitempty"` // (string, optional): , - Type string `json:"type"` // (string): <Тип>, - MimeType string `json:"mime_type,omitempty"` // (string, optional): , - Revision int64 `json:"revision,omitempty"` // (integer, optional): <Ревизия Диска в которой этот ресурс был изменён последний раз>, - PublicURL string `json:"public_url,omitempty"` // (string, optional): <Публичный URL>, - Path string `json:"path"` // (string): <Путь к ресурсу>, - Md5 string `json:"md5,omitempty"` // (string, optional): , - PublicKey string `json:"public_key,omitempty"` // (string, optional): <Ключ опубликованного ресурса>, - Sha256 string `json:"sha256,omitempty"` // (string, optional): , - Name string `json:"name"` // (string): <Имя>, - Created string `json:"created"` // (string): <Дата создания>, - Modified string `json:"modified"` // (string): <Дата изменения>, - CommentIDs *CommentIds `json:"comment_ids,omitempty"` // (CommentIds, optional) + // AntivirusStatus is the result of the virus check of a file, such as + // "clean". + AntivirusStatus string `json:"antivirus_status,omitempty"` + // ResourceID identifies the resource and stays the same when it is moved + // or renamed. + ResourceID string `json:"resource_id,omitempty"` + // Share is set when the resource is inside a shared folder. + Share *ShareInfo `json:"share,omitempty"` + // File is a link to download the file. Folders have none. + File string `json:"file,omitempty"` + // Size is the file size in bytes, zero for folders. + Size int64 `json:"size,omitempty"` + // PhotosliceTime is when a photo or video was taken, if the Disk knows it. + PhotosliceTime string `json:"photoslice_time,omitempty"` + // Embedded is one page of a folder's contents, when it was requested. + Embedded *ResourceList `json:"_embedded,omitempty"` + // Exif holds metadata read from a photo. + Exif *Exif `json:"exif,omitempty"` + // CustomProperties are the attributes set with [Client.UpdateMetadata]. + CustomProperties interface{} `json:"custom_properties,omitempty"` + // MediaType is the kind of file as the Disk sees it, such as "image", + // "video", "audio" or "document". + MediaType string `json:"media_type,omitempty"` + // Preview is a link to a thumbnail. Fetching it needs the access token. + Preview string `json:"preview,omitempty"` + // Type is "file" or "dir". + Type string `json:"type"` + // MimeType is the MIME type of a file, such as "image/jpeg". + MimeType string `json:"mime_type,omitempty"` + // Revision is the Disk revision in which the resource last changed. + Revision int64 `json:"revision,omitempty"` + // PublicURL is the public link, set only while the resource is published. + PublicURL string `json:"public_url,omitempty"` + // Path is the full path, such as "disk:/Photos/cat.jpg". + Path string `json:"path"` + // Md5 is the MD5 hash of a file's contents. + Md5 string `json:"md5,omitempty"` + // PublicKey identifies a published resource in the public API, such as + // [Client.GetMetadataForPublicResource]. + PublicKey string `json:"public_key,omitempty"` + // Sha256 is the SHA-256 hash of a file's contents. + Sha256 string `json:"sha256,omitempty"` + // Name is the last element of Path. + Name string `json:"name"` + // Created is when the resource was created. + Created string `json:"created"` + // Modified is when the resource last changed. + Modified string `json:"modified"` + // CommentIDs identify the comment threads of the resource. + CommentIDs *CommentIds `json:"comment_ids,omitempty"` } +// PublicResource is a published file or folder as seen by anyone with its +// link. The public API methods, such as +// [Client.GetMetadataForPublicResource], return it. type PublicResource struct { Resource - Owner *UserPublicInformation `json:"owner,omitempty"` - ViewsCount int `json:"views_count,omitempty"` + // Owner is the user who published the resource. + Owner *UserPublicInformation `json:"owner,omitempty"` + // ViewsCount is how many times the public link has been opened. + ViewsCount int `json:"views_count,omitempty"` } +// ShareInfo describes the user's access to a shared folder. type ShareInfo struct { - IsRoot bool `json:"is_root,omitempty"` - IsOwned bool `json:"is_owned,omitempty"` - Rights string `json:"rights"` + // IsRoot reports whether this resource is the shared folder itself + // rather than something inside it. + IsRoot bool `json:"is_root,omitempty"` + // IsOwned reports whether the user owns the shared folder. + IsOwned bool `json:"is_owned,omitempty"` + // Rights is the user's access level, such as "r" or "rw". + Rights string `json:"rights"` } +// ResourceList is one page of a folder's contents, in [Resource.Embedded]. type ResourceList struct { - Sort string `json:"sort,omitempty"` // (string, optional): <Поле, по которому отсортирован список>, - Items []*Resource `json:"items"` // (Array[Resource]): <Элементы списка>, - Limit int `json:"limit,omitempty"` // (integer, optional): <Количество элементов на странице>, - Offset int `json:"offset,omitempty"` // (integer, optional): <Смещение от начала списка>, - Path string `json:"path"` // (string): <Путь к ресурсу, для которого построен список>, - Total int `json:"total,omitempty"` // (integer, optional): <Общее количество элементов в списке>} + // Sort is the field the items are sorted by, such as "name" or + // "-modified" (a minus means descending). + Sort string `json:"sort,omitempty"` + // Items are the files and folders on this page. + Items []*Resource `json:"items"` + // Limit is the page size that was requested. + Limit int `json:"limit,omitempty"` + // Offset is how many items come before this page. + Offset int `json:"offset,omitempty"` + // Path is the folder being listed. + Path string `json:"path"` + // Total is the number of items in the folder, across all pages. + Total int `json:"total,omitempty"` } +// Exif holds metadata read from a photo. type Exif struct { + // DateTime is when the photo was taken, as an ISO 8601 string. DateTime string `json:"date_time,omitempty"` } +// CommentIds identify the comment threads attached to a resource. type CommentIds struct { + // PrivateResource is the thread seen by people with access to the Disk. PrivateResource string `json:"private_resource,omitempty"` - PublicResource string `json:"public_resource,omitempty"` + // PublicResource is the thread seen through the public link. + PublicResource string `json:"public_resource,omitempty"` } +// Link is a URL returned by the API: where to download a file, or, for an +// operation that runs in the background, where to check its status (see +// [Client.GetOperationStatus]). type Link struct { - Href string `json:"href"` - Method string `json:"method"` - Templated bool `json:"templated,omitempty"` + // Href is the URL. + Href string `json:"href"` + // Method is the HTTP method to use with Href, such as "GET". + Method string `json:"method"` + // Templated reports whether Href contains placeholders to fill in. + Templated bool `json:"templated,omitempty"` } +// ResourceUploadLink is where to send a file's contents, returned by +// [Client.GetLinkForUpload]. The upload helpers in this package use it for +// you. type ResourceUploadLink struct { - OperationID string `json:"operation_id"` // (string): <Идентификатор операции загрузки файла>, - Href string `json:"href"` // (string): , - Method string `json:"method"` // (string): , - Templated bool `json:"templated,omitempty"` // (boolean, optional): <Признак шаблонизированного URL> + // OperationID identifies the upload; its status can be checked with + // [Client.GetOperationStatus]. + OperationID string `json:"operation_id"` + // Href is the URL to send the contents to. + Href string `json:"href"` + // Method is the HTTP method to use, usually "PUT". + Method string `json:"method"` + // Templated reports whether Href contains placeholders to fill in. + Templated bool `json:"templated,omitempty"` } +// PublicResourcesList is one page of the user's published files and +// folders, returned by [Client.GetPublicResources]. type PublicResourcesList struct { - Items []*Resource `json:"items"` // (Array[Resource]): <Элементы списка>, - Type string `json:"type"` // (string, optional): <Значение фильтра по типу ресурсов>, - Limit int `json:"limit"` // (integer, optional): <Количество элементов на странице>, - Offset int `json:"offset"` // (integer, optional): <Смещение от начала списка> + // Items are the published resources on this page. + Items []*Resource `json:"items"` + // Type is the filter that was applied: "file", "dir" or empty for both. + Type string `json:"type"` + // Limit is the page size that was requested. + Limit int `json:"limit"` + // Offset is how many items come before this page. + Offset int `json:"offset"` } +// LastUploadedResourceList is the user's most recently uploaded files, +// returned by [Client.GetLastUploadedResources]. type LastUploadedResourceList struct { - Items []*Resource `json:"items"` //(Array[Resource]): <Элементы списка>, - Limit int `json:"limit,omitempty"` // (integer, optional): <Количество элементов на странице> + // Items are the files, newest first. + Items []*Resource `json:"items"` + // Limit is how many files were requested. + Limit int `json:"limit,omitempty"` } +// FilesResourceList is one page of all files on the Disk, regardless of +// folder, returned by [Client.GetSortedFiles]. type FilesResourceList struct { - Items []*Resource `json:"items"` // (Array[Resource]): <Элементы списка>, - Limit int `json:"limit,omitempty"` //(integer, optional): <Количество элементов на странице>, - Offset int `json:"offset,omitempty"` // (integer, optional): <Смещение от начала списка> + // Items are the files on this page. + Items []*Resource `json:"items"` + // Limit is the page size that was requested. + Limit int `json:"limit,omitempty"` + // Offset is how many files come before this page. + Offset int `json:"offset,omitempty"` } +// UserPublicInformation is what anyone can see about the owner of a +// published resource. type UserPublicInformation struct { - Login string `json:"login,omitempty"` // (string, optional): <Логин.>, - DisplayName string `json:"display_name,omitempty"` // (string, optional): <Отображаемое имя пользователя.>, - Uid string `json:"uid,omitempty"` // (string, optional): <Идентификатор пользователя.> + // Login is the Yandex login. + Login string `json:"login,omitempty"` + // DisplayName is the name shown to other people. + DisplayName string `json:"display_name,omitempty"` + // Uid is the numeric user ID, as a string. + Uid string `json:"uid,omitempty"` } +// Operation is the state of a background operation, such as copying a large +// folder, as reported by [Client.GetOperationStatus]. type Operation struct { + // Status is "success", "failed" or "in-progress". Status string `json:"status"` } +// ErrorResponse is an error reported by the API, or by this package when a +// request could not be made or its answer could not be read. +// +// Error is a short machine-readable code, such as "DiskNotFoundError"; +// Description is in English and Message is in the language of the request. type ErrorResponse struct { - Message string `json:"message"` + // Message is a description meant for people. + Message string `json:"message"` + // Description is a description in English. Description string `json:"description"` - Error string `json:"error"` + // Error is the error code, such as "DiskNotFoundError" or + // "UnauthorizedError". + Error string `json:"error"` // StatusCode is the HTTP status that produced this error, or 0 when the // request never reached a response (connection failure, decode error). - // Not part of the API payload — it is filled in by this package. + // It is not part of the API payload; this package fills it in. StatusCode int `json:"-"` } -// TrashResource represents a resource in the trash +// TrashResource is a file or folder in the trash. type TrashResource struct { Resource - OriginPath string `json:"origin_path,omitempty"` // Original path before deletion - Deleted string `json:"deleted,omitempty"` // Deletion timestamp + // OriginPath is where the resource was before it was deleted, and where + // restoring puts it back. + OriginPath string `json:"origin_path,omitempty"` + // Deleted is when the resource was moved to the trash. + Deleted string `json:"deleted,omitempty"` } -// TrashResourceList represents a list of resources in trash +// TrashResourceList is one page of the trash, returned by +// [Client.ListTrashResources]. type TrashResourceList struct { - Items []*TrashResource `json:"items"` // List of trash resources - Limit int `json:"limit,omitempty"` // Number of items per page - Offset int `json:"offset,omitempty"` // Offset from the beginning of the list - Path string `json:"path"` // Path in trash - Total int `json:"total,omitempty"` // Number of items in the trash + // Items are the deleted resources on this page. + Items []*TrashResource `json:"items"` + // Limit is the page size that was requested. + Limit int `json:"limit,omitempty"` + // Offset is how many items come before this page. + Offset int `json:"offset,omitempty"` + // Path is the trash folder being listed, "trash:/" for the root. + Path string `json:"path"` + // Total is the number of items in the trash, across all pages. + Total int `json:"total,omitempty"` }