github.com/artpar/rclone@v1.67.3/backend/cache/object.go (about)

     1  //go:build !plan9 && !js
     2  
     3  package cache
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"path"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/artpar/rclone/fs"
    14  	"github.com/artpar/rclone/fs/hash"
    15  	"github.com/artpar/rclone/lib/readers"
    16  )
    17  
    18  const (
    19  	objectInCache       = "Object"
    20  	objectPendingUpload = "TempObject"
    21  )
    22  
    23  // Object is a generic file like object that stores basic information about it
    24  type Object struct {
    25  	fs.Object `json:"-"`
    26  
    27  	ParentFs      fs.Fs     `json:"-"`        // parent fs
    28  	CacheFs       *Fs       `json:"-"`        // cache fs
    29  	Name          string    `json:"name"`     // name of the directory
    30  	Dir           string    `json:"dir"`      // abs path of the object
    31  	CacheModTime  int64     `json:"modTime"`  // modification or creation time - IsZero for unknown
    32  	CacheSize     int64     `json:"size"`     // size of directory and contents or -1 if unknown
    33  	CacheStorable bool      `json:"storable"` // says whether this object can be stored
    34  	CacheType     string    `json:"cacheType"`
    35  	CacheTs       time.Time `json:"cacheTs"`
    36  	cacheHashesMu sync.Mutex
    37  	CacheHashes   map[hash.Type]string // all supported hashes cached
    38  
    39  	refreshMutex sync.Mutex
    40  }
    41  
    42  // NewObject builds one from a generic fs.Object
    43  func NewObject(f *Fs, remote string) *Object {
    44  	fullRemote := path.Join(f.Root(), remote)
    45  	dir, name := path.Split(fullRemote)
    46  
    47  	cacheType := objectInCache
    48  	parentFs := f.UnWrap()
    49  	if f.opt.TempWritePath != "" {
    50  		_, err := f.cache.SearchPendingUpload(fullRemote)
    51  		if err == nil { // queued for upload
    52  			cacheType = objectPendingUpload
    53  			parentFs = f.tempFs
    54  			fs.Debugf(fullRemote, "pending upload found")
    55  		}
    56  	}
    57  
    58  	co := &Object{
    59  		ParentFs:      parentFs,
    60  		CacheFs:       f,
    61  		Name:          cleanPath(name),
    62  		Dir:           cleanPath(dir),
    63  		CacheModTime:  time.Now().UnixNano(),
    64  		CacheSize:     0,
    65  		CacheStorable: false,
    66  		CacheType:     cacheType,
    67  		CacheTs:       time.Now(),
    68  	}
    69  	return co
    70  }
    71  
    72  // ObjectFromOriginal builds one from a generic fs.Object
    73  func ObjectFromOriginal(ctx context.Context, f *Fs, o fs.Object) *Object {
    74  	var co *Object
    75  	fullRemote := cleanPath(path.Join(f.Root(), o.Remote()))
    76  	dir, name := path.Split(fullRemote)
    77  
    78  	cacheType := objectInCache
    79  	parentFs := f.UnWrap()
    80  	if f.opt.TempWritePath != "" {
    81  		_, err := f.cache.SearchPendingUpload(fullRemote)
    82  		if err == nil { // queued for upload
    83  			cacheType = objectPendingUpload
    84  			parentFs = f.tempFs
    85  			fs.Debugf(fullRemote, "pending upload found")
    86  		}
    87  	}
    88  
    89  	co = &Object{
    90  		ParentFs:  parentFs,
    91  		CacheFs:   f,
    92  		Name:      cleanPath(name),
    93  		Dir:       cleanPath(dir),
    94  		CacheType: cacheType,
    95  		CacheTs:   time.Now(),
    96  	}
    97  	co.updateData(ctx, o)
    98  	return co
    99  }
   100  
   101  func (o *Object) updateData(ctx context.Context, source fs.Object) {
   102  	o.Object = source
   103  	o.CacheModTime = source.ModTime(ctx).UnixNano()
   104  	o.CacheSize = source.Size()
   105  	o.CacheStorable = source.Storable()
   106  	o.CacheTs = time.Now()
   107  	o.cacheHashesMu.Lock()
   108  	o.CacheHashes = make(map[hash.Type]string)
   109  	o.cacheHashesMu.Unlock()
   110  }
   111  
   112  // Fs returns its FS info
   113  func (o *Object) Fs() fs.Info {
   114  	return o.CacheFs
   115  }
   116  
   117  // String returns a human friendly name for this object
   118  func (o *Object) String() string {
   119  	if o == nil {
   120  		return "<nil>"
   121  	}
   122  	return o.Remote()
   123  }
   124  
   125  // Remote returns the remote path
   126  func (o *Object) Remote() string {
   127  	p := path.Join(o.Dir, o.Name)
   128  	return o.CacheFs.cleanRootFromPath(p)
   129  }
   130  
   131  // abs returns the absolute path to the object
   132  func (o *Object) abs() string {
   133  	return path.Join(o.Dir, o.Name)
   134  }
   135  
   136  // ModTime returns the cached ModTime
   137  func (o *Object) ModTime(ctx context.Context) time.Time {
   138  	_ = o.refresh(ctx)
   139  	return time.Unix(0, o.CacheModTime)
   140  }
   141  
   142  // Size returns the cached Size
   143  func (o *Object) Size() int64 {
   144  	_ = o.refresh(context.TODO())
   145  	return o.CacheSize
   146  }
   147  
   148  // Storable returns the cached Storable
   149  func (o *Object) Storable() bool {
   150  	_ = o.refresh(context.TODO())
   151  	return o.CacheStorable
   152  }
   153  
   154  // refresh will check if the object info is expired and request the info from source if it is
   155  // all these conditions must be true to ignore a refresh
   156  // 1. cache ts didn't expire yet
   157  // 2. is not pending a notification from the wrapped fs
   158  func (o *Object) refresh(ctx context.Context) error {
   159  	isNotified := o.CacheFs.isNotifiedRemote(o.Remote())
   160  	isExpired := time.Now().After(o.CacheTs.Add(time.Duration(o.CacheFs.opt.InfoAge)))
   161  	if !isExpired && !isNotified {
   162  		return nil
   163  	}
   164  
   165  	return o.refreshFromSource(ctx, true)
   166  }
   167  
   168  // refreshFromSource requests the original FS for the object in case it comes from a cached entry
   169  func (o *Object) refreshFromSource(ctx context.Context, force bool) error {
   170  	o.refreshMutex.Lock()
   171  	defer o.refreshMutex.Unlock()
   172  	var err error
   173  	var liveObject fs.Object
   174  
   175  	if o.Object != nil && !force {
   176  		return nil
   177  	}
   178  	if o.isTempFile() {
   179  		liveObject, err = o.ParentFs.NewObject(ctx, o.Remote())
   180  		if err != nil {
   181  			err = fmt.Errorf("in parent fs %v: %w", o.ParentFs, err)
   182  		}
   183  	} else {
   184  		liveObject, err = o.CacheFs.Fs.NewObject(ctx, o.Remote())
   185  		if err != nil {
   186  			err = fmt.Errorf("in cache fs %v: %w", o.CacheFs.Fs, err)
   187  		}
   188  	}
   189  	if err != nil {
   190  		fs.Errorf(o, "error refreshing object in : %v", err)
   191  		return err
   192  	}
   193  	o.updateData(ctx, liveObject)
   194  	o.persist()
   195  
   196  	return nil
   197  }
   198  
   199  // SetModTime sets the ModTime of this object
   200  func (o *Object) SetModTime(ctx context.Context, t time.Time) error {
   201  	if err := o.refreshFromSource(ctx, false); err != nil {
   202  		return err
   203  	}
   204  
   205  	err := o.Object.SetModTime(ctx, t)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	o.CacheModTime = t.UnixNano()
   211  	o.persist()
   212  	fs.Debugf(o, "updated ModTime: %v", t)
   213  
   214  	return nil
   215  }
   216  
   217  // Open is used to request a specific part of the file using fs.RangeOption
   218  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
   219  	var err error
   220  
   221  	if o.Object == nil {
   222  		err = o.refreshFromSource(ctx, true)
   223  	} else {
   224  		err = o.refresh(ctx)
   225  	}
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	cacheReader := NewObjectHandle(ctx, o, o.CacheFs)
   231  	var offset, limit int64 = 0, -1
   232  	for _, option := range options {
   233  		switch x := option.(type) {
   234  		case *fs.SeekOption:
   235  			offset = x.Offset
   236  		case *fs.RangeOption:
   237  			offset, limit = x.Decode(o.Size())
   238  		}
   239  		_, err = cacheReader.Seek(offset, io.SeekStart)
   240  		if err != nil {
   241  			return nil, err
   242  		}
   243  	}
   244  
   245  	return readers.NewLimitedReadCloser(cacheReader, limit), nil
   246  }
   247  
   248  // Update will change the object data
   249  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
   250  	if err := o.refreshFromSource(ctx, false); err != nil {
   251  		return err
   252  	}
   253  	// pause background uploads if active
   254  	if o.CacheFs.opt.TempWritePath != "" {
   255  		o.CacheFs.backgroundRunner.pause()
   256  		defer o.CacheFs.backgroundRunner.play()
   257  		// don't allow started uploads
   258  		if o.isTempFile() && o.tempFileStartedUpload() {
   259  			return fmt.Errorf("%v is currently uploading, can't update", o)
   260  		}
   261  	}
   262  	fs.Debugf(o, "updating object contents with size %v", src.Size())
   263  
   264  	// FIXME use reliable upload
   265  	err := o.Object.Update(ctx, in, src, options...)
   266  	if err != nil {
   267  		fs.Errorf(o, "error updating source: %v", err)
   268  		return err
   269  	}
   270  
   271  	// deleting cached chunks and info to be replaced with new ones
   272  	_ = o.CacheFs.cache.RemoveObject(o.abs())
   273  	// advertise to ChangeNotify if wrapped doesn't do that
   274  	o.CacheFs.notifyChangeUpstreamIfNeeded(o.Remote(), fs.EntryObject)
   275  
   276  	o.CacheModTime = src.ModTime(ctx).UnixNano()
   277  	o.CacheSize = src.Size()
   278  	o.cacheHashesMu.Lock()
   279  	o.CacheHashes = make(map[hash.Type]string)
   280  	o.cacheHashesMu.Unlock()
   281  	o.CacheTs = time.Now()
   282  	o.persist()
   283  
   284  	return nil
   285  }
   286  
   287  // Remove deletes the object from both the cache and the source
   288  func (o *Object) Remove(ctx context.Context) error {
   289  	if err := o.refreshFromSource(ctx, false); err != nil {
   290  		return err
   291  	}
   292  	// pause background uploads if active
   293  	if o.CacheFs.opt.TempWritePath != "" {
   294  		o.CacheFs.backgroundRunner.pause()
   295  		defer o.CacheFs.backgroundRunner.play()
   296  		// don't allow started uploads
   297  		if o.isTempFile() && o.tempFileStartedUpload() {
   298  			return fmt.Errorf("%v is currently uploading, can't delete", o)
   299  		}
   300  	}
   301  	err := o.Object.Remove(ctx)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	fs.Debugf(o, "removing object")
   307  	_ = o.CacheFs.cache.RemoveObject(o.abs())
   308  	_ = o.CacheFs.cache.removePendingUpload(o.abs())
   309  	parentCd := NewDirectory(o.CacheFs, cleanPath(path.Dir(o.Remote())))
   310  	_ = o.CacheFs.cache.ExpireDir(parentCd)
   311  	// advertise to ChangeNotify if wrapped doesn't do that
   312  	o.CacheFs.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
   313  
   314  	return nil
   315  }
   316  
   317  // Hash requests a hash of the object and stores in the cache
   318  // since it might or might not be called, this is lazy loaded
   319  func (o *Object) Hash(ctx context.Context, ht hash.Type) (string, error) {
   320  	_ = o.refresh(ctx)
   321  	o.cacheHashesMu.Lock()
   322  	if o.CacheHashes == nil {
   323  		o.CacheHashes = make(map[hash.Type]string)
   324  	}
   325  	cachedHash, found := o.CacheHashes[ht]
   326  	o.cacheHashesMu.Unlock()
   327  	if found {
   328  		return cachedHash, nil
   329  	}
   330  	if err := o.refreshFromSource(ctx, false); err != nil {
   331  		return "", err
   332  	}
   333  	liveHash, err := o.Object.Hash(ctx, ht)
   334  	if err != nil {
   335  		return "", err
   336  	}
   337  	o.cacheHashesMu.Lock()
   338  	o.CacheHashes[ht] = liveHash
   339  	o.cacheHashesMu.Unlock()
   340  
   341  	o.persist()
   342  	fs.Debugf(o, "object hash cached: %v", liveHash)
   343  
   344  	return liveHash, nil
   345  }
   346  
   347  // persist adds this object to the persistent cache
   348  func (o *Object) persist() *Object {
   349  	err := o.CacheFs.cache.AddObject(o)
   350  	if err != nil {
   351  		fs.Errorf(o, "failed to cache object: %v", err)
   352  	}
   353  	return o
   354  }
   355  
   356  func (o *Object) isTempFile() bool {
   357  	_, err := o.CacheFs.cache.SearchPendingUpload(o.abs())
   358  	if err != nil {
   359  		o.CacheType = objectInCache
   360  		return false
   361  	}
   362  
   363  	o.CacheType = objectPendingUpload
   364  	return true
   365  }
   366  
   367  func (o *Object) tempFileStartedUpload() bool {
   368  	started, err := o.CacheFs.cache.SearchPendingUpload(o.abs())
   369  	if err != nil {
   370  		return false
   371  	}
   372  	return started
   373  }
   374  
   375  // UnWrap returns the Object that this Object is wrapping or
   376  // nil if it isn't wrapping anything
   377  func (o *Object) UnWrap() fs.Object {
   378  	return o.Object
   379  }
   380  
   381  var (
   382  	_ fs.Object          = (*Object)(nil)
   383  	_ fs.ObjectUnWrapper = (*Object)(nil)
   384  )