1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
|
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("")
// 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,
}
}
|