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:
parent
1510e12659
commit
3f5767b94e
@ -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.
|
||||||
|
105
backend/b2/b2.go
105
backend/b2/b2.go
@ -134,6 +134,14 @@ 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,
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -150,6 +158,7 @@ type Options struct {
|
|||||||
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
|
||||||
@ -164,6 +173,8 @@ type Fs struct {
|
|||||||
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
|
||||||
|
bucketTypeMutex sync.Mutex // mutex to protect _bucketType
|
||||||
|
_bucketType string // the Type of the bucket we are working on
|
||||||
info api.AuthorizeAccountResponse // result of authorize call
|
info api.AuthorizeAccountResponse // result of authorize call
|
||||||
uploadMu sync.Mutex // lock for upload variable
|
uploadMu sync.Mutex // lock for upload variable
|
||||||
uploads []*api.GetUploadURLResponse // result of get upload URL calls
|
uploads []*api.GetUploadURLResponse // result of get upload URL calls
|
||||||
@ -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
|
||||||
@ -1657,6 +1761,7 @@ var (
|
|||||||
_ fs.PutStreamer = &Fs{}
|
_ fs.PutStreamer = &Fs{}
|
||||||
_ fs.CleanUpper = &Fs{}
|
_ fs.CleanUpper = &Fs{}
|
||||||
_ fs.ListRer = &Fs{}
|
_ fs.ListRer = &Fs{}
|
||||||
|
_ fs.PublicLinker = &Fs{}
|
||||||
_ fs.Object = &Object{}
|
_ fs.Object = &Object{}
|
||||||
_ fs.MimeTyper = &Object{}
|
_ fs.MimeTyper = &Object{}
|
||||||
_ fs.IDer = &Object{}
|
_ fs.IDer = &Object{}
|
||||||
|
@ -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 |
|
||||||
|
Loading…
Reference in New Issue
Block a user