mirror of
https://github.com/rclone/rclone.git
synced 2025-03-05 02:41:58 +01:00
Before this change, bucket.Join would tidy up object keys by removing repeated / in them. This means we can't access objects with // in them which is valid for object keys (but not for file system paths). This could have consequences for users who are relying on rclone to fix improper paths for them.
197 lines
4.2 KiB
Go
197 lines
4.2 KiB
Go
// Package bucket is contains utilities for managing bucket-based backends
|
|
package bucket
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
// ErrAlreadyDeleted is returned when an already deleted
|
|
// bucket is passed to Remove
|
|
ErrAlreadyDeleted = errors.New("bucket already deleted")
|
|
)
|
|
|
|
// Split takes an absolute path which includes the bucket and
|
|
// splits it into a bucket and a path in that bucket
|
|
// bucketPath
|
|
func Split(absPath string) (bucket, bucketPath string) {
|
|
// No bucket
|
|
if absPath == "" {
|
|
return "", ""
|
|
}
|
|
slash := strings.IndexRune(absPath, '/')
|
|
// Bucket but no path
|
|
if slash < 0 {
|
|
return absPath, ""
|
|
}
|
|
return absPath[:slash], absPath[slash+1:]
|
|
}
|
|
|
|
// Join path1 and path2
|
|
//
|
|
// Like path.Join but does not clean the path - useful to preserve trailing /.
|
|
//
|
|
// It also does not clean multiple // in the path.
|
|
func Join(path1, path2 string) string {
|
|
if path1 == "" {
|
|
return path2
|
|
}
|
|
if path2 == "" {
|
|
return path1
|
|
}
|
|
return path1 + "/" + path2
|
|
}
|
|
|
|
// IsAllSlashes returns true if s is all / characters.
|
|
//
|
|
// It returns false if s is "".
|
|
func IsAllSlashes(s string) bool {
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
for _, c := range s {
|
|
if c != '/' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Cache stores whether buckets are available and their IDs
|
|
type Cache struct {
|
|
mu sync.Mutex // mutex to protect created and deleted
|
|
status map[string]bool // true if we have created the container, false if deleted
|
|
createMu sync.Mutex // mutex to protect against simultaneous Remove
|
|
removeMu sync.Mutex // mutex to protect against simultaneous Create
|
|
}
|
|
|
|
// NewCache creates an empty Cache
|
|
func NewCache() *Cache {
|
|
return &Cache{
|
|
status: make(map[string]bool, 1),
|
|
}
|
|
}
|
|
|
|
// MarkOK marks the bucket as being present
|
|
func (c *Cache) MarkOK(bucket string) {
|
|
if bucket != "" {
|
|
c.mu.Lock()
|
|
c.status[bucket] = true
|
|
c.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// MarkDeleted marks the bucket as being deleted
|
|
func (c *Cache) MarkDeleted(bucket string) {
|
|
if bucket != "" {
|
|
c.mu.Lock()
|
|
c.status[bucket] = false
|
|
c.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
type (
|
|
// ExistsFn should be passed to Create to see if a bucket
|
|
// exists or not
|
|
ExistsFn func() (found bool, err error)
|
|
|
|
// CreateFn should be passed to Create to make a bucket
|
|
CreateFn func() error
|
|
)
|
|
|
|
// Create the bucket with create() if it doesn't exist
|
|
//
|
|
// If exists is set then if the bucket has been deleted it will call
|
|
// exists() to see if it still exists.
|
|
//
|
|
// If f returns an error we assume the bucket was not created
|
|
func (c *Cache) Create(bucket string, create CreateFn, exists ExistsFn) (err error) {
|
|
// if we are at the root, then it is OK
|
|
if bucket == "" {
|
|
return nil
|
|
}
|
|
|
|
c.createMu.Lock()
|
|
defer c.createMu.Unlock()
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
// if have exists function and bucket has been deleted, check
|
|
// it still exists
|
|
if created, ok := c.status[bucket]; ok && !created && exists != nil {
|
|
found, err := exists()
|
|
if err == nil {
|
|
c.status[bucket] = found
|
|
}
|
|
if err != nil || found {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If bucket already exists then it is OK
|
|
if created, ok := c.status[bucket]; ok && created {
|
|
return nil
|
|
}
|
|
|
|
// Create the bucket
|
|
c.mu.Unlock()
|
|
err = create()
|
|
c.mu.Lock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Mark OK if successful
|
|
c.status[bucket] = true
|
|
return nil
|
|
}
|
|
|
|
// Remove the bucket with f if it exists
|
|
//
|
|
// If f returns an error we assume the bucket was not removed.
|
|
//
|
|
// If the bucket has already been deleted it returns ErrAlreadyDeleted
|
|
func (c *Cache) Remove(bucket string, f func() error) error {
|
|
// if we are at the root, then it is OK
|
|
if bucket == "" {
|
|
return nil
|
|
}
|
|
|
|
c.removeMu.Lock()
|
|
defer c.removeMu.Unlock()
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
// If bucket already deleted then it is OK
|
|
if created, ok := c.status[bucket]; ok && !created {
|
|
return ErrAlreadyDeleted
|
|
}
|
|
|
|
// Remove the bucket
|
|
c.mu.Unlock()
|
|
err := f()
|
|
c.mu.Lock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Mark removed if successful
|
|
c.status[bucket] = false
|
|
return err
|
|
}
|
|
|
|
// IsDeleted returns true if the bucket has definitely been deleted by
|
|
// us, false otherwise.
|
|
func (c *Cache) IsDeleted(bucket string) bool {
|
|
c.mu.Lock()
|
|
created, ok := c.status[bucket]
|
|
c.mu.Unlock()
|
|
// if status unknown then return false
|
|
if !ok {
|
|
return false
|
|
}
|
|
return !created
|
|
}
|