github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/lib/bucket/bucket.go (about)

     1  // Package bucket is contains utilities for managing bucket-based backends
     2  package bucket
     3  
     4  import (
     5  	"errors"
     6  	"strings"
     7  	"sync"
     8  )
     9  
    10  var (
    11  	// ErrAlreadyDeleted is returned when an already deleted
    12  	// bucket is passed to Remove
    13  	ErrAlreadyDeleted = errors.New("bucket already deleted")
    14  )
    15  
    16  // Split takes an absolute path which includes the bucket and
    17  // splits it into a bucket and a path in that bucket
    18  // bucketPath
    19  func Split(absPath string) (bucket, bucketPath string) {
    20  	// No bucket
    21  	if absPath == "" {
    22  		return "", ""
    23  	}
    24  	slash := strings.IndexRune(absPath, '/')
    25  	// Bucket but no path
    26  	if slash < 0 {
    27  		return absPath, ""
    28  	}
    29  	return absPath[:slash], absPath[slash+1:]
    30  }
    31  
    32  // Join path1 and path2
    33  //
    34  // Like path.Join but does not clean the path - useful to preserve trailing /
    35  func Join(path1, path2 string) string {
    36  	if path1 == "" {
    37  		return path2
    38  	}
    39  	if path2 == "" {
    40  		return path1
    41  	}
    42  	return strings.TrimSuffix(path1, "/") + "/" + strings.TrimPrefix(path2, "/")
    43  }
    44  
    45  // Cache stores whether buckets are available and their IDs
    46  type Cache struct {
    47  	mu       sync.Mutex      // mutex to protect created and deleted
    48  	status   map[string]bool // true if we have created the container, false if deleted
    49  	createMu sync.Mutex      // mutex to protect against simultaneous Remove
    50  	removeMu sync.Mutex      // mutex to protect against simultaneous Create
    51  }
    52  
    53  // NewCache creates an empty Cache
    54  func NewCache() *Cache {
    55  	return &Cache{
    56  		status: make(map[string]bool, 1),
    57  	}
    58  }
    59  
    60  // MarkOK marks the bucket as being present
    61  func (c *Cache) MarkOK(bucket string) {
    62  	if bucket != "" {
    63  		c.mu.Lock()
    64  		c.status[bucket] = true
    65  		c.mu.Unlock()
    66  	}
    67  }
    68  
    69  // MarkDeleted marks the bucket as being deleted
    70  func (c *Cache) MarkDeleted(bucket string) {
    71  	if bucket != "" {
    72  		c.mu.Lock()
    73  		c.status[bucket] = false
    74  		c.mu.Unlock()
    75  	}
    76  }
    77  
    78  type (
    79  	// ExistsFn should be passed to Create to see if a bucket
    80  	// exists or not
    81  	ExistsFn func() (found bool, err error)
    82  
    83  	// CreateFn should be passed to Create to make a bucket
    84  	CreateFn func() error
    85  )
    86  
    87  // Create the bucket with create() if it doesn't exist
    88  //
    89  // If exists is set then if the bucket has been deleted it will call
    90  // exists() to see if it still exists.
    91  //
    92  // If f returns an error we assume the bucket was not created
    93  func (c *Cache) Create(bucket string, create CreateFn, exists ExistsFn) (err error) {
    94  	// if we are at the root, then it is OK
    95  	if bucket == "" {
    96  		return nil
    97  	}
    98  
    99  	c.createMu.Lock()
   100  	defer c.createMu.Unlock()
   101  	c.mu.Lock()
   102  	defer c.mu.Unlock()
   103  
   104  	// if have exists function and bucket has been deleted, check
   105  	// it still exists
   106  	if created, ok := c.status[bucket]; ok && !created && exists != nil {
   107  		found, err := exists()
   108  		if err == nil {
   109  			c.status[bucket] = found
   110  		}
   111  		if err != nil || found {
   112  			return err
   113  		}
   114  	}
   115  
   116  	// If bucket already exists then it is OK
   117  	if created, ok := c.status[bucket]; ok && created {
   118  		return nil
   119  	}
   120  
   121  	// Create the bucket
   122  	c.mu.Unlock()
   123  	err = create()
   124  	c.mu.Lock()
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	// Mark OK if successful
   130  	c.status[bucket] = true
   131  	return nil
   132  }
   133  
   134  // Remove the bucket with f if it exists
   135  //
   136  // If f returns an error we assume the bucket was not removed.
   137  //
   138  // If the bucket has already been deleted it returns ErrAlreadyDeleted
   139  func (c *Cache) Remove(bucket string, f func() error) error {
   140  	// if we are at the root, then it is OK
   141  	if bucket == "" {
   142  		return nil
   143  	}
   144  
   145  	c.removeMu.Lock()
   146  	defer c.removeMu.Unlock()
   147  	c.mu.Lock()
   148  	defer c.mu.Unlock()
   149  
   150  	// If bucket already deleted then it is OK
   151  	if created, ok := c.status[bucket]; ok && !created {
   152  		return ErrAlreadyDeleted
   153  	}
   154  
   155  	// Remove the bucket
   156  	c.mu.Unlock()
   157  	err := f()
   158  	c.mu.Lock()
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	// Mark removed if successful
   164  	c.status[bucket] = false
   165  	return err
   166  }
   167  
   168  // IsDeleted returns true if the bucket has definitely been deleted by
   169  // us, false otherwise.
   170  func (c *Cache) IsDeleted(bucket string) bool {
   171  	c.mu.Lock()
   172  	created, ok := c.status[bucket]
   173  	c.mu.Unlock()
   174  	// if status unknown then return false
   175  	if !ok {
   176  		return false
   177  	}
   178  	return !created
   179  }