mirror of https://github.com/perkeep/perkeep.git
239 lines
6.4 KiB
Go
239 lines
6.4 KiB
Go
package b2
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// DeleteFile deletes a file version.
|
|
func (c *Client) DeleteFile(id, name string) error {
|
|
res, err := c.doRequest("b2_delete_file_version", map[string]interface{}{
|
|
"fileId": id, "fileName": name,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
drainAndClose(res.Body)
|
|
return nil
|
|
}
|
|
|
|
// A FileInfo is the metadata associated with a specific file version.
|
|
type FileInfo struct {
|
|
ID string
|
|
Name string
|
|
|
|
// Had to remove BucketID since it is not returned by b2_download_file_by_*
|
|
// BucketID string
|
|
|
|
ContentSHA1 string // hex encoded
|
|
ContentLength int
|
|
ContentType string
|
|
|
|
CustomMetadata map[string]interface{}
|
|
UploadTimestamp time.Time
|
|
|
|
// If Action is "hide", this ID does not refer to a file version
|
|
// but to an hiding action. Otherwise "upload".
|
|
Action string
|
|
}
|
|
|
|
type fileInfoObj struct {
|
|
AccountID string `json:"accountId"`
|
|
BucketID string `json:"bucketId"`
|
|
ContentLength int `json:"contentLength"`
|
|
ContentSHA1 string `json:"contentSha1"`
|
|
ContentType string `json:"contentType"`
|
|
FileID string `json:"fileId"`
|
|
FileInfo map[string]interface{} `json:"fileInfo"`
|
|
FileName string `json:"fileName"`
|
|
UploadTimestamp int64 `json:"uploadTimestamp"`
|
|
Action string `json:"action"`
|
|
}
|
|
|
|
func (fi *fileInfoObj) makeFileInfo() *FileInfo {
|
|
return &FileInfo{
|
|
ID: fi.FileID,
|
|
Name: fi.FileName,
|
|
ContentLength: fi.ContentLength,
|
|
ContentSHA1: fi.ContentSHA1,
|
|
ContentType: fi.ContentType,
|
|
CustomMetadata: fi.FileInfo,
|
|
Action: fi.Action,
|
|
UploadTimestamp: time.Unix(fi.UploadTimestamp/1e3, fi.UploadTimestamp%1e3*1e6),
|
|
}
|
|
}
|
|
|
|
// GetFileInfoByID obtains a FileInfo for a given ID.
|
|
//
|
|
// The ID can refer to any file version or "hide" action in any bucket.
|
|
func (c *Client) GetFileInfoByID(id string) (*FileInfo, error) {
|
|
res, err := c.doRequest("b2_get_file_info", map[string]interface{}{
|
|
"fileId": id,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer drainAndClose(res.Body)
|
|
var fi *fileInfoObj
|
|
if err := json.NewDecoder(res.Body).Decode(&fi); err != nil {
|
|
return nil, err
|
|
}
|
|
return fi.makeFileInfo(), nil
|
|
}
|
|
|
|
var FileNotFoundError = errors.New("no file with the given name in the bucket")
|
|
|
|
// GetFileInfoByName obtains a FileInfo for a given name.
|
|
//
|
|
// If the file doesn't exist, FileNotFoundError is returned.
|
|
// If multiple versions of the file exist, only the latest is returned.
|
|
func (b *Bucket) GetFileInfoByName(name string) (*FileInfo, error) {
|
|
l := b.ListFiles(name)
|
|
l.SetPageCount(1)
|
|
if l.Next() {
|
|
if l.FileInfo().Name == name {
|
|
return l.FileInfo(), nil
|
|
}
|
|
}
|
|
if err := l.Err(); err != nil {
|
|
return nil, l.Err()
|
|
}
|
|
return nil, FileNotFoundError
|
|
}
|
|
|
|
// A Listing is the result of (*Bucket).ListFiles[Versions].
|
|
// It works like sql.Rows: use Next to advance and then FileInfo.
|
|
// Check Err once Next returns false.
|
|
//
|
|
// l := b.ListFiles("", 50)
|
|
// for l.Next() {
|
|
// fi := l.FileInfo()
|
|
// ...
|
|
// }
|
|
// if err := l.Err(); err != nil {
|
|
// ...
|
|
// }
|
|
//
|
|
// A Listing handles pagination transparently, so it iterates until
|
|
// the last file in the bucket. To limit the number of results, do this.
|
|
//
|
|
// for i := 0; i < limit && l.Next(); i++ {
|
|
//
|
|
type Listing struct {
|
|
b *Bucket
|
|
versions bool
|
|
nextPageCount int
|
|
nextName, nextID *string
|
|
objects []*FileInfo // in reverse order
|
|
err error
|
|
}
|
|
|
|
// SetPageCount controls the number of results to be fetched with each API
|
|
// call. The maximum n is 1000, higher values are automatically limited to 1000.
|
|
//
|
|
// SetPageCount does not limit the number of results returned by a Listing.
|
|
func (l *Listing) SetPageCount(n int) {
|
|
if n > 1000 {
|
|
n = 1000
|
|
}
|
|
l.nextPageCount = n
|
|
}
|
|
|
|
// Next calls the list API if needed and prepares the FileInfo results.
|
|
// It returns true on success, or false if there is no next result
|
|
// or an error happened while preparing it. Err should be
|
|
// consulted to distinguish between the two cases.
|
|
func (l *Listing) Next() bool {
|
|
if l.err != nil {
|
|
return false
|
|
}
|
|
if len(l.objects) > 0 {
|
|
l.objects = l.objects[:len(l.objects)-1]
|
|
}
|
|
if len(l.objects) > 0 {
|
|
return true
|
|
}
|
|
if l.nextName == nil {
|
|
return false // end of iteration
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"bucketId": l.b.ID,
|
|
"startFileName": *l.nextName,
|
|
"maxFileCount": l.nextPageCount,
|
|
}
|
|
endpoint := "b2_list_file_names"
|
|
if l.versions {
|
|
endpoint = "b2_list_file_versions"
|
|
}
|
|
if l.nextID != nil && *l.nextID != "" {
|
|
data["startFileId"] = *l.nextID
|
|
}
|
|
r, err := l.b.c.doRequest(endpoint, data)
|
|
if err != nil {
|
|
l.err = err
|
|
return false
|
|
}
|
|
defer drainAndClose(r.Body)
|
|
|
|
var x struct {
|
|
Files []fileInfoObj
|
|
NextFileName *string
|
|
NextFileID *string
|
|
}
|
|
if l.err = json.NewDecoder(r.Body).Decode(&x); l.err != nil {
|
|
return false
|
|
}
|
|
|
|
l.objects = make([]*FileInfo, len(x.Files))
|
|
for i, f := range x.Files {
|
|
l.objects[len(l.objects)-1-i] = f.makeFileInfo()
|
|
}
|
|
l.nextName, l.nextID = x.NextFileName, x.NextFileID
|
|
return len(l.objects) > 0
|
|
}
|
|
|
|
// FileInfo returns the FileInfo object made available by Next.
|
|
//
|
|
// FileInfo must only be called after a call to Next returned true.
|
|
func (l *Listing) FileInfo() *FileInfo {
|
|
return l.objects[len(l.objects)-1]
|
|
}
|
|
|
|
// Err returns the error, if any, that was encountered while listing.
|
|
func (l *Listing) Err() error {
|
|
return l.err
|
|
}
|
|
|
|
// ListFiles returns a Listing of files in the Bucket, alphabetically sorted,
|
|
// starting from the file named fromName (included if it exists). To start from
|
|
// the first file in the bucket, set fileName to "".
|
|
//
|
|
// ListFiles only returns the most recent version of each (non-hidden) file.
|
|
// If you want to fetch all versions, use ListFilesVersions.
|
|
func (b *Bucket) ListFiles(fromName string) *Listing {
|
|
return &Listing{
|
|
b: b,
|
|
nextName: &fromName,
|
|
}
|
|
}
|
|
|
|
// ListFilesVersions is like ListFiles, but returns all file versions,
|
|
// alphabetically sorted first, and by reverse of date/time uploaded then.
|
|
//
|
|
// If fromID is specified, the name-and-id pair is the starting point.
|
|
func (b *Bucket) ListFilesVersions(fromName, fromID string) *Listing {
|
|
if fromName == "" && fromID != "" {
|
|
return &Listing{
|
|
err: errors.New("can't set fromID if fromName is not set"),
|
|
}
|
|
}
|
|
return &Listing{
|
|
b: b,
|
|
versions: true,
|
|
nextName: &fromName,
|
|
nextID: &fromID,
|
|
}
|
|
}
|