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

     1  // Package memory provides an interface to an in memory object storage system
     2  package memory
     3  
     4  import (
     5  	"bytes"
     6  	"context"
     7  	"crypto/md5"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"io"
    11  	"path"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/rclone/rclone/fs"
    17  	"github.com/rclone/rclone/fs/config/configmap"
    18  	"github.com/rclone/rclone/fs/config/configstruct"
    19  	"github.com/rclone/rclone/fs/hash"
    20  	"github.com/rclone/rclone/fs/walk"
    21  	"github.com/rclone/rclone/lib/bucket"
    22  )
    23  
    24  var (
    25  	hashType = hash.MD5
    26  	// the object storage is persistent
    27  	buckets = newBucketsInfo()
    28  )
    29  
    30  // Register with Fs
    31  func init() {
    32  	fs.Register(&fs.RegInfo{
    33  		Name:        "memory",
    34  		Description: "In memory object storage system.",
    35  		NewFs:       NewFs,
    36  		Options:     []fs.Option{},
    37  	})
    38  }
    39  
    40  // Options defines the configuration for this backend
    41  type Options struct{}
    42  
    43  // Fs represents a remote memory server
    44  type Fs struct {
    45  	name          string       // name of this remote
    46  	root          string       // the path we are working on if any
    47  	opt           Options      // parsed config options
    48  	rootBucket    string       // bucket part of root (if any)
    49  	rootDirectory string       // directory part of root (if any)
    50  	features      *fs.Features // optional features
    51  }
    52  
    53  // bucketsInfo holds info about all the buckets
    54  type bucketsInfo struct {
    55  	mu      sync.RWMutex
    56  	buckets map[string]*bucketInfo
    57  }
    58  
    59  func newBucketsInfo() *bucketsInfo {
    60  	return &bucketsInfo{
    61  		buckets: make(map[string]*bucketInfo, 16),
    62  	}
    63  }
    64  
    65  // getBucket gets a names bucket or nil
    66  func (bi *bucketsInfo) getBucket(name string) (b *bucketInfo) {
    67  	bi.mu.RLock()
    68  	b = bi.buckets[name]
    69  	bi.mu.RUnlock()
    70  	return b
    71  }
    72  
    73  // makeBucket returns the bucket or makes it
    74  func (bi *bucketsInfo) makeBucket(name string) (b *bucketInfo) {
    75  	bi.mu.Lock()
    76  	defer bi.mu.Unlock()
    77  	b = bi.buckets[name]
    78  	if b != nil {
    79  		return b
    80  	}
    81  	b = newBucketInfo()
    82  	bi.buckets[name] = b
    83  	return b
    84  }
    85  
    86  // deleteBucket deleted the bucket or returns an error
    87  func (bi *bucketsInfo) deleteBucket(name string) error {
    88  	bi.mu.Lock()
    89  	defer bi.mu.Unlock()
    90  	b := bi.buckets[name]
    91  	if b == nil {
    92  		return fs.ErrorDirNotFound
    93  	}
    94  	if !b.isEmpty() {
    95  		return fs.ErrorDirectoryNotEmpty
    96  	}
    97  	delete(bi.buckets, name)
    98  	return nil
    99  }
   100  
   101  // getObjectData gets an object from (bucketName, bucketPath) or nil
   102  func (bi *bucketsInfo) getObjectData(bucketName, bucketPath string) (od *objectData) {
   103  	b := bi.getBucket(bucketName)
   104  	if b == nil {
   105  		return nil
   106  	}
   107  	return b.getObjectData(bucketPath)
   108  }
   109  
   110  // updateObjectData updates an object from (bucketName, bucketPath)
   111  func (bi *bucketsInfo) updateObjectData(bucketName, bucketPath string, od *objectData) {
   112  	b := bi.makeBucket(bucketName)
   113  	b.mu.Lock()
   114  	b.objects[bucketPath] = od
   115  	b.mu.Unlock()
   116  }
   117  
   118  // removeObjectData removes an object from (bucketName, bucketPath) returning true if removed
   119  func (bi *bucketsInfo) removeObjectData(bucketName, bucketPath string) (removed bool) {
   120  	b := bi.getBucket(bucketName)
   121  	if b != nil {
   122  		b.mu.Lock()
   123  		od := b.objects[bucketPath]
   124  		if od != nil {
   125  			delete(b.objects, bucketPath)
   126  			removed = true
   127  		}
   128  		b.mu.Unlock()
   129  	}
   130  	return removed
   131  }
   132  
   133  // bucketInfo holds info about a single bucket
   134  type bucketInfo struct {
   135  	mu      sync.RWMutex
   136  	objects map[string]*objectData
   137  }
   138  
   139  func newBucketInfo() *bucketInfo {
   140  	return &bucketInfo{
   141  		objects: make(map[string]*objectData, 16),
   142  	}
   143  }
   144  
   145  // getBucket gets a names bucket or nil
   146  func (bi *bucketInfo) getObjectData(name string) (od *objectData) {
   147  	bi.mu.RLock()
   148  	od = bi.objects[name]
   149  	bi.mu.RUnlock()
   150  	return od
   151  }
   152  
   153  // getBucket gets a names bucket or nil
   154  func (bi *bucketInfo) isEmpty() (empty bool) {
   155  	bi.mu.RLock()
   156  	empty = len(bi.objects) == 0
   157  	bi.mu.RUnlock()
   158  	return empty
   159  }
   160  
   161  // the object data and metadata
   162  type objectData struct {
   163  	modTime  time.Time
   164  	hash     string
   165  	mimeType string
   166  	data     []byte
   167  }
   168  
   169  // Object describes a memory object
   170  type Object struct {
   171  	fs     *Fs         // what this object is part of
   172  	remote string      // The remote path
   173  	od     *objectData // the object data
   174  }
   175  
   176  // ------------------------------------------------------------
   177  
   178  // Name of the remote (as passed into NewFs)
   179  func (f *Fs) Name() string {
   180  	return f.name
   181  }
   182  
   183  // Root of the remote (as passed into NewFs)
   184  func (f *Fs) Root() string {
   185  	return f.root
   186  }
   187  
   188  // String converts this Fs to a string
   189  func (f *Fs) String() string {
   190  	return fmt.Sprintf("Memory root '%s'", f.root)
   191  }
   192  
   193  // Features returns the optional features of this Fs
   194  func (f *Fs) Features() *fs.Features {
   195  	return f.features
   196  }
   197  
   198  // parsePath parses a remote 'url'
   199  func parsePath(path string) (root string) {
   200  	root = strings.Trim(path, "/")
   201  	return
   202  }
   203  
   204  // split returns bucket and bucketPath from the rootRelativePath
   205  // relative to f.root
   206  func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) {
   207  	return bucket.Split(path.Join(f.root, rootRelativePath))
   208  }
   209  
   210  // split returns bucket and bucketPath from the object
   211  func (o *Object) split() (bucket, bucketPath string) {
   212  	return o.fs.split(o.remote)
   213  }
   214  
   215  // setRoot changes the root of the Fs
   216  func (f *Fs) setRoot(root string) {
   217  	f.root = parsePath(root)
   218  	f.rootBucket, f.rootDirectory = bucket.Split(f.root)
   219  }
   220  
   221  // NewFs constructs an Fs from the path, bucket:path
   222  func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) {
   223  	// Parse config into Options struct
   224  	opt := new(Options)
   225  	err := configstruct.Set(m, opt)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	root = strings.Trim(root, "/")
   230  	f := &Fs{
   231  		name: name,
   232  		root: root,
   233  		opt:  *opt,
   234  	}
   235  	f.setRoot(root)
   236  	f.features = (&fs.Features{
   237  		ReadMimeType:      true,
   238  		WriteMimeType:     true,
   239  		BucketBased:       true,
   240  		BucketBasedRootOK: true,
   241  	}).Fill(ctx, f)
   242  	if f.rootBucket != "" && f.rootDirectory != "" {
   243  		od := buckets.getObjectData(f.rootBucket, f.rootDirectory)
   244  		if od != nil {
   245  			newRoot := path.Dir(f.root)
   246  			if newRoot == "." {
   247  				newRoot = ""
   248  			}
   249  			f.setRoot(newRoot)
   250  			// return an error with an fs which points to the parent
   251  			err = fs.ErrorIsFile
   252  		}
   253  	}
   254  	return f, err
   255  }
   256  
   257  // newObject makes an object from a remote and an objectData
   258  func (f *Fs) newObject(remote string, od *objectData) *Object {
   259  	return &Object{fs: f, remote: remote, od: od}
   260  }
   261  
   262  // NewObject finds the Object at remote.  If it can't be found
   263  // it returns the error fs.ErrorObjectNotFound.
   264  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   265  	bucket, bucketPath := f.split(remote)
   266  	od := buckets.getObjectData(bucket, bucketPath)
   267  	if od == nil {
   268  		return nil, fs.ErrorObjectNotFound
   269  	}
   270  	return f.newObject(remote, od), nil
   271  }
   272  
   273  // listFn is called from list to handle an object.
   274  type listFn func(remote string, entry fs.DirEntry, isDirectory bool) error
   275  
   276  // list the buckets to fn
   277  func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBucket bool, recurse bool, fn listFn) (err error) {
   278  	if prefix != "" {
   279  		prefix += "/"
   280  	}
   281  	if directory != "" {
   282  		directory += "/"
   283  	}
   284  	b := buckets.getBucket(bucket)
   285  	if b == nil {
   286  		return fs.ErrorDirNotFound
   287  	}
   288  	b.mu.RLock()
   289  	defer b.mu.RUnlock()
   290  	dirs := make(map[string]struct{})
   291  	for absPath, od := range b.objects {
   292  		if strings.HasPrefix(absPath, directory) {
   293  			remote := absPath[len(prefix):]
   294  			if !recurse {
   295  				localPath := absPath[len(directory):]
   296  				slash := strings.IndexRune(localPath, '/')
   297  				if slash >= 0 {
   298  					// send a directory if have a slash
   299  					dir := strings.TrimPrefix(directory, f.rootDirectory+"/") + localPath[:slash]
   300  					if addBucket {
   301  						dir = path.Join(bucket, dir)
   302  					}
   303  					_, found := dirs[dir]
   304  					if !found {
   305  						err = fn(dir, fs.NewDir(dir, time.Time{}), true)
   306  						if err != nil {
   307  							return err
   308  						}
   309  						dirs[dir] = struct{}{}
   310  					}
   311  					continue // don't send this file if not recursing
   312  				}
   313  			}
   314  			// send an object
   315  			if addBucket {
   316  				remote = path.Join(bucket, remote)
   317  			}
   318  			err = fn(remote, f.newObject(remote, od), false)
   319  			if err != nil {
   320  				return err
   321  			}
   322  		}
   323  	}
   324  	return nil
   325  }
   326  
   327  // listDir lists the bucket to the entries
   328  func (f *Fs) listDir(ctx context.Context, bucket, directory, prefix string, addBucket bool) (entries fs.DirEntries, err error) {
   329  	// List the objects and directories
   330  	err = f.list(ctx, bucket, directory, prefix, addBucket, false, func(remote string, entry fs.DirEntry, isDirectory bool) error {
   331  		entries = append(entries, entry)
   332  		return nil
   333  	})
   334  	return entries, err
   335  }
   336  
   337  // listBuckets lists the buckets to entries
   338  func (f *Fs) listBuckets(ctx context.Context) (entries fs.DirEntries, err error) {
   339  	buckets.mu.RLock()
   340  	defer buckets.mu.RUnlock()
   341  	for name := range buckets.buckets {
   342  		entries = append(entries, fs.NewDir(name, time.Time{}))
   343  	}
   344  	return entries, nil
   345  }
   346  
   347  // List the objects and directories in dir into entries.  The
   348  // entries can be returned in any order but should be for a
   349  // complete directory.
   350  //
   351  // dir should be "" to list the root, and should not have
   352  // trailing slashes.
   353  //
   354  // This should return ErrDirNotFound if the directory isn't
   355  // found.
   356  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   357  	// defer fslog.Trace(dir, "")("entries = %q, err = %v", &entries, &err)
   358  	bucket, directory := f.split(dir)
   359  	if bucket == "" {
   360  		if directory != "" {
   361  			return nil, fs.ErrorListBucketRequired
   362  		}
   363  		return f.listBuckets(ctx)
   364  	}
   365  	return f.listDir(ctx, bucket, directory, f.rootDirectory, f.rootBucket == "")
   366  }
   367  
   368  // ListR lists the objects and directories of the Fs starting
   369  // from dir recursively into out.
   370  //
   371  // dir should be "" to start from the root, and should not
   372  // have trailing slashes.
   373  //
   374  // This should return ErrDirNotFound if the directory isn't
   375  // found.
   376  //
   377  // It should call callback for each tranche of entries read.
   378  // These need not be returned in any particular order.  If
   379  // callback returns an error then the listing will stop
   380  // immediately.
   381  //
   382  // Don't implement this unless you have a more efficient way
   383  // of listing recursively that doing a directory traversal.
   384  func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (err error) {
   385  	bucket, directory := f.split(dir)
   386  	list := walk.NewListRHelper(callback)
   387  	entries := fs.DirEntries{}
   388  	listR := func(bucket, directory, prefix string, addBucket bool) error {
   389  		err = f.list(ctx, bucket, directory, prefix, addBucket, true, func(remote string, entry fs.DirEntry, isDirectory bool) error {
   390  			entries = append(entries, entry) // can't list.Add here -- could deadlock
   391  			return nil
   392  		})
   393  		if err != nil {
   394  			return err
   395  		}
   396  		for _, entry := range entries {
   397  			err = list.Add(entry)
   398  			if err != nil {
   399  				return err
   400  			}
   401  		}
   402  		return nil
   403  	}
   404  	if bucket == "" {
   405  		entries, err := f.listBuckets(ctx)
   406  		if err != nil {
   407  			return err
   408  		}
   409  		for _, entry := range entries {
   410  			err = list.Add(entry)
   411  			if err != nil {
   412  				return err
   413  			}
   414  			bucket := entry.Remote()
   415  			err = listR(bucket, "", f.rootDirectory, true)
   416  			if err != nil {
   417  				return err
   418  			}
   419  		}
   420  	} else {
   421  		err = listR(bucket, directory, f.rootDirectory, f.rootBucket == "")
   422  		if err != nil {
   423  			return err
   424  		}
   425  	}
   426  	return list.Flush()
   427  }
   428  
   429  // Put the object into the bucket
   430  //
   431  // Copy the reader in to the new object which is returned.
   432  //
   433  // The new object may have been created if an error is returned
   434  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   435  	// Temporary Object under construction
   436  	fs := &Object{
   437  		fs:     f,
   438  		remote: src.Remote(),
   439  		od: &objectData{
   440  			modTime: src.ModTime(ctx),
   441  		},
   442  	}
   443  	return fs, fs.Update(ctx, in, src, options...)
   444  }
   445  
   446  // PutStream uploads to the remote path with the modTime given of indeterminate size
   447  func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   448  	return f.Put(ctx, in, src, options...)
   449  }
   450  
   451  // Mkdir creates the bucket if it doesn't exist
   452  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   453  	bucket, _ := f.split(dir)
   454  	buckets.makeBucket(bucket)
   455  	return nil
   456  }
   457  
   458  // Rmdir deletes the bucket if the fs is at the root
   459  //
   460  // Returns an error if it isn't empty
   461  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   462  	bucket, directory := f.split(dir)
   463  	if bucket == "" || directory != "" {
   464  		return nil
   465  	}
   466  	return buckets.deleteBucket(bucket)
   467  }
   468  
   469  // Precision of the remote
   470  func (f *Fs) Precision() time.Duration {
   471  	return time.Nanosecond
   472  }
   473  
   474  // Copy src to this remote using server-side copy operations.
   475  //
   476  // This is stored with the remote path given.
   477  //
   478  // It returns the destination Object and a possible error.
   479  //
   480  // Will only be called if src.Fs().Name() == f.Name()
   481  //
   482  // If it isn't possible then return fs.ErrorCantCopy
   483  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   484  	dstBucket, dstPath := f.split(remote)
   485  	_ = buckets.makeBucket(dstBucket)
   486  	srcObj, ok := src.(*Object)
   487  	if !ok {
   488  		fs.Debugf(src, "Can't copy - not same remote type")
   489  		return nil, fs.ErrorCantCopy
   490  	}
   491  	srcBucket, srcPath := srcObj.split()
   492  	od := buckets.getObjectData(srcBucket, srcPath)
   493  	if od == nil {
   494  		return nil, fs.ErrorObjectNotFound
   495  	}
   496  	odCopy := *od
   497  	buckets.updateObjectData(dstBucket, dstPath, &odCopy)
   498  	return f.NewObject(ctx, remote)
   499  }
   500  
   501  // Hashes returns the supported hash sets.
   502  func (f *Fs) Hashes() hash.Set {
   503  	return hash.Set(hashType)
   504  }
   505  
   506  // ------------------------------------------------------------
   507  
   508  // Fs returns the parent Fs
   509  func (o *Object) Fs() fs.Info {
   510  	return o.fs
   511  }
   512  
   513  // Return a string version
   514  func (o *Object) String() string {
   515  	if o == nil {
   516  		return "<nil>"
   517  	}
   518  	return o.Remote()
   519  }
   520  
   521  // Remote returns the remote path
   522  func (o *Object) Remote() string {
   523  	return o.remote
   524  }
   525  
   526  // Hash returns the hash of an object returning a lowercase hex string
   527  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
   528  	if t != hashType {
   529  		return "", hash.ErrUnsupported
   530  	}
   531  	if o.od.hash == "" {
   532  		sum := md5.Sum(o.od.data)
   533  		o.od.hash = hex.EncodeToString(sum[:])
   534  	}
   535  	return o.od.hash, nil
   536  }
   537  
   538  // Size returns the size of an object in bytes
   539  func (o *Object) Size() int64 {
   540  	return int64(len(o.od.data))
   541  }
   542  
   543  // ModTime returns the modification time of the object
   544  //
   545  // It attempts to read the objects mtime and if that isn't present the
   546  // LastModified returned in the http headers
   547  //
   548  // SHA-1 will also be updated once the request has completed.
   549  func (o *Object) ModTime(ctx context.Context) (result time.Time) {
   550  	return o.od.modTime
   551  }
   552  
   553  // SetModTime sets the modification time of the local fs object
   554  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
   555  	o.od.modTime = modTime
   556  	return nil
   557  }
   558  
   559  // Storable returns if this object is storable
   560  func (o *Object) Storable() bool {
   561  	return true
   562  }
   563  
   564  // Open an object for read
   565  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
   566  	var offset, limit int64 = 0, -1
   567  	for _, option := range options {
   568  		switch x := option.(type) {
   569  		case *fs.RangeOption:
   570  			offset, limit = x.Decode(int64(len(o.od.data)))
   571  		case *fs.SeekOption:
   572  			offset = x.Offset
   573  		default:
   574  			if option.Mandatory() {
   575  				fs.Logf(o, "Unsupported mandatory option: %v", option)
   576  			}
   577  		}
   578  	}
   579  	if offset > int64(len(o.od.data)) {
   580  		offset = int64(len(o.od.data))
   581  	}
   582  	data := o.od.data[offset:]
   583  	if limit >= 0 {
   584  		if limit > int64(len(data)) {
   585  			limit = int64(len(data))
   586  		}
   587  		data = data[:limit]
   588  	}
   589  	return io.NopCloser(bytes.NewBuffer(data)), nil
   590  }
   591  
   592  // Update the object with the contents of the io.Reader, modTime and size
   593  //
   594  // The new object may have been created if an error is returned
   595  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
   596  	bucket, bucketPath := o.split()
   597  	data, err := io.ReadAll(in)
   598  	if err != nil {
   599  		return fmt.Errorf("failed to update memory object: %w", err)
   600  	}
   601  	o.od = &objectData{
   602  		data:     data,
   603  		hash:     "",
   604  		modTime:  src.ModTime(ctx),
   605  		mimeType: fs.MimeType(ctx, src),
   606  	}
   607  	buckets.updateObjectData(bucket, bucketPath, o.od)
   608  	return nil
   609  }
   610  
   611  // Remove an object
   612  func (o *Object) Remove(ctx context.Context) error {
   613  	bucket, bucketPath := o.split()
   614  	removed := buckets.removeObjectData(bucket, bucketPath)
   615  	if !removed {
   616  		return fs.ErrorObjectNotFound
   617  	}
   618  	return nil
   619  }
   620  
   621  // MimeType of an Object if known, "" otherwise
   622  func (o *Object) MimeType(ctx context.Context) string {
   623  	return o.od.mimeType
   624  }
   625  
   626  // Check the interfaces are satisfied
   627  var (
   628  	_ fs.Fs          = &Fs{}
   629  	_ fs.Copier      = &Fs{}
   630  	_ fs.PutStreamer = &Fs{}
   631  	_ fs.ListRer     = &Fs{}
   632  	_ fs.Object      = &Object{}
   633  	_ fs.MimeTyper   = &Object{}
   634  )