1
mirror of https://github.com/rclone/rclone synced 2025-01-14 18:27:30 +01:00

b2: Implement link sharing #2178

This commit is contained in:
yparitcher 2019-06-30 16:51:59 -04:00 committed by Nick Craig-Wood
parent 1510e12659
commit 3f5767b94e
3 changed files with 156 additions and 36 deletions

View File

@ -189,6 +189,21 @@ type GetUploadURLResponse struct {
AuthorizationToken string `json:"authorizationToken"` // The authorizationToken that must be used when uploading files to this bucket, see b2_upload_file. AuthorizationToken string `json:"authorizationToken"` // The authorizationToken that must be used when uploading files to this bucket, see b2_upload_file.
} }
// GetDownloadAuthorizationRequest is passed to b2_get_download_authorization
type GetDownloadAuthorizationRequest struct {
BucketID string `json:"bucketId"` // The ID of the bucket that you want to upload to.
FileNamePrefix string `json:"fileNamePrefix"` // The file name prefix of files the download authorization token will allow access to.
ValidDurationInSeconds int64 `json:"validDurationInSeconds"` // The number of seconds before the authorization token will expire. The minimum value is 1 second. The maximum value is 604800 which is one week in seconds.
B2ContentDisposition string `json:"b2ContentDisposition,omitempty"` // optional - If this is present, download requests using the returned authorization must include the same value for b2ContentDisposition.
}
// GetDownloadAuthorizationResponse is received from b2_get_download_authorization
type GetDownloadAuthorizationResponse struct {
BucketID string `json:"bucketId"` // The unique ID of the bucket.
FileNamePrefix string `json:"fileNamePrefix"` // The file name prefix of files the download authorization token will allow access to.
AuthorizationToken string `json:"authorizationToken"` // The authorizationToken that must be used when downloading files, see b2_download_file_by_name.
}
// FileInfo is received from b2_upload_file, b2_get_file_info and b2_finish_large_file // FileInfo is received from b2_upload_file, b2_get_file_info and b2_finish_large_file
type FileInfo struct { type FileInfo struct {
ID string `json:"fileId"` // The unique identifier for this version of this file. Used with b2_get_file_info, b2_download_file_by_id, and b2_delete_file_version. ID string `json:"fileId"` // The unique identifier for this version of this file. Used with b2_get_file_info, b2_download_file_by_id, and b2_delete_file_version.

View File

@ -134,42 +134,53 @@ This is usually set to a Cloudflare CDN URL as Backblaze offers
free egress for data downloaded through the Cloudflare network. free egress for data downloaded through the Cloudflare network.
Leave blank if you want to use the endpoint provided by Backblaze.`, Leave blank if you want to use the endpoint provided by Backblaze.`,
Advanced: true, Advanced: true,
}, {
Name: "download_auth_duration",
Help: `Time before the authorization token will expire in s or suffix ms|s|m|h|d.
The duration before the download authorization token will expire.
The minimum value is 1 second. The maximum value is one week.`,
Default: fs.Duration(7 * 24 * time.Hour),
Advanced: true,
}}, }},
}) })
} }
// Options defines the configuration for this backend // Options defines the configuration for this backend
type Options struct { type Options struct {
Account string `config:"account"` Account string `config:"account"`
Key string `config:"key"` Key string `config:"key"`
Endpoint string `config:"endpoint"` Endpoint string `config:"endpoint"`
TestMode string `config:"test_mode"` TestMode string `config:"test_mode"`
Versions bool `config:"versions"` Versions bool `config:"versions"`
HardDelete bool `config:"hard_delete"` HardDelete bool `config:"hard_delete"`
UploadCutoff fs.SizeSuffix `config:"upload_cutoff"` UploadCutoff fs.SizeSuffix `config:"upload_cutoff"`
ChunkSize fs.SizeSuffix `config:"chunk_size"` ChunkSize fs.SizeSuffix `config:"chunk_size"`
DisableCheckSum bool `config:"disable_checksum"` DisableCheckSum bool `config:"disable_checksum"`
DownloadURL string `config:"download_url"` DownloadURL string `config:"download_url"`
DownloadAuthorizationDuration fs.Duration `config:"download_auth_duration"`
} }
// Fs represents a remote b2 server // Fs represents a remote b2 server
type Fs struct { type Fs struct {
name string // name of this remote name string // name of this remote
root string // the path we are working on if any root string // the path we are working on if any
opt Options // parsed config options opt Options // parsed config options
features *fs.Features // optional features features *fs.Features // optional features
srv *rest.Client // the connection to the b2 server srv *rest.Client // the connection to the b2 server
bucket string // the bucket we are working on bucket string // the bucket we are working on
bucketOKMu sync.Mutex // mutex to protect bucket OK bucketOKMu sync.Mutex // mutex to protect bucket OK
bucketOK bool // true if we have created the bucket bucketOK bool // true if we have created the bucket
bucketIDMutex sync.Mutex // mutex to protect _bucketID bucketIDMutex sync.Mutex // mutex to protect _bucketID
_bucketID string // the ID of the bucket we are working on _bucketID string // the ID of the bucket we are working on
info api.AuthorizeAccountResponse // result of authorize call bucketTypeMutex sync.Mutex // mutex to protect _bucketType
uploadMu sync.Mutex // lock for upload variable _bucketType string // the Type of the bucket we are working on
uploads []*api.GetUploadURLResponse // result of get upload URL calls info api.AuthorizeAccountResponse // result of authorize call
authMu sync.Mutex // lock for authorizing the account uploadMu sync.Mutex // lock for upload variable
pacer *fs.Pacer // To pace and retry the API calls uploads []*api.GetUploadURLResponse // result of get upload URL calls
bufferTokens chan []byte // control concurrency of multipart uploads authMu sync.Mutex // lock for authorizing the account
pacer *fs.Pacer // To pace and retry the API calls
bufferTokens chan []byte // control concurrency of multipart uploads
} }
// Object describes a b2 object // Object describes a b2 object
@ -796,6 +807,42 @@ func (f *Fs) listBucketsToFn(fn listBucketFn) error {
return nil return nil
} }
// getbucketType finds the bucketType for the current bucket name
// can be one of allPublic. allPrivate, or snapshot
func (f *Fs) getbucketType() (bucketType string, err error) {
f.bucketTypeMutex.Lock()
defer f.bucketTypeMutex.Unlock()
if f._bucketType != "" {
return f._bucketType, nil
}
err = f.listBucketsToFn(func(bucket *api.Bucket) error {
if bucket.Name == f.bucket {
bucketType = bucket.Type
}
return nil
})
if bucketType == "" {
err = fs.ErrorDirNotFound
}
f._bucketType = bucketType
return bucketType, err
}
// setBucketType sets the Type for the current bucket name
func (f *Fs) setBucketType(Type string) {
f.bucketTypeMutex.Lock()
f._bucketType = Type
f.bucketTypeMutex.Unlock()
}
// clearBucketType clears the Type for the current bucket name
func (f *Fs) clearBucketType() {
f.bucketTypeMutex.Lock()
f._bucketType = ""
f.bucketTypeMutex.Unlock()
}
// getBucketID finds the ID for the current bucket name // getBucketID finds the ID for the current bucket name
func (f *Fs) getBucketID() (bucketID string, err error) { func (f *Fs) getBucketID() (bucketID string, err error) {
f.bucketIDMutex.Lock() f.bucketIDMutex.Lock()
@ -890,6 +937,7 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error {
return errors.Wrap(err, "failed to create bucket") return errors.Wrap(err, "failed to create bucket")
} }
f.setBucketID(response.ID) f.setBucketID(response.ID)
f.setBucketType(response.Type)
f.bucketOK = true f.bucketOK = true
return nil return nil
} }
@ -925,6 +973,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
} }
f.bucketOK = false f.bucketOK = false
f.clearBucketID() f.clearBucketID()
f.clearBucketType()
f.clearUploadURL() f.clearUploadURL()
return nil return nil
} }
@ -1125,6 +1174,61 @@ func (f *Fs) Hashes() hash.Set {
return hash.Set(hash.SHA1) return hash.Set(hash.SHA1)
} }
// getDownloadAuthorization returns authorization token for downloading
// without accout.
func (f *Fs) getDownloadAuthorization(remote string) (authorization string, err error) {
validDurationInSeconds := time.Duration(f.opt.DownloadAuthorizationDuration).Nanoseconds() / 1e9
if validDurationInSeconds <= 0 || validDurationInSeconds > 604800 {
return "", errors.New("--b2-download-auth-duration must be between 1 sec and 1 week")
}
bucketID, err := f.getBucketID()
if err != nil {
return "", err
}
opts := rest.Opts{
Method: "POST",
Path: "/b2_get_download_authorization",
}
var request = api.GetDownloadAuthorizationRequest{
BucketID: bucketID,
FileNamePrefix: remote,
ValidDurationInSeconds: validDurationInSeconds,
}
var response api.GetDownloadAuthorizationResponse
err = f.pacer.Call(func() (bool, error) {
resp, err := f.srv.CallJSON(&opts, &request, &response)
return f.shouldRetry(resp, err)
})
if err != nil {
return "", errors.Wrap(err, "failed to get download authorization")
}
return response.AuthorizationToken, nil
}
// PublicLink returns a link for downloading without accout.
func (f *Fs) PublicLink(ctx context.Context, remote string) (link string, err error) {
var RootURL string
if f.opt.DownloadURL == "" {
RootURL = f.info.DownloadURL
} else {
RootURL = f.opt.DownloadURL
}
absPath := "/" + path.Join(f.root, remote)
link = RootURL + "/file/" + urlEncode(f.bucket) + absPath
bucketType, err := f.getbucketType()
if err != nil {
return "", err
}
if bucketType == "allPrivate" || bucketType == "snapshot" {
AuthorizationToken, err := f.getDownloadAuthorization(remote)
if err != nil {
return "", err
}
link += "?Authorization=" + AuthorizationToken
}
return link, nil
}
// ------------------------------------------------------------ // ------------------------------------------------------------
// Fs returns the parent Fs // Fs returns the parent Fs
@ -1651,13 +1755,14 @@ func (o *Object) ID() string {
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = &Fs{} _ fs.Fs = &Fs{}
_ fs.Purger = &Fs{} _ fs.Purger = &Fs{}
_ fs.Copier = &Fs{} _ fs.Copier = &Fs{}
_ fs.PutStreamer = &Fs{} _ fs.PutStreamer = &Fs{}
_ fs.CleanUpper = &Fs{} _ fs.CleanUpper = &Fs{}
_ fs.ListRer = &Fs{} _ fs.ListRer = &Fs{}
_ fs.Object = &Object{} _ fs.PublicLinker = &Fs{}
_ fs.MimeTyper = &Object{} _ fs.Object = &Object{}
_ fs.IDer = &Object{} _ fs.MimeTyper = &Object{}
_ fs.IDer = &Object{}
) )

View File

@ -136,7 +136,7 @@ operations more efficient.
| 1Fichier | No | No | No | No | No | No | No | No | No | | 1Fichier | No | No | No | No | No | No | No | No | No |
| Amazon Drive | Yes | No | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | | Amazon Drive | Yes | No | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
| Amazon S3 | No | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | | Amazon S3 | No | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
| Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | | Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | Yes | No |
| Box | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes | No | | Box | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes | No |
| Dropbox | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes | Yes | | Dropbox | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes | Yes |
| FTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No | | FTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |