github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/union/union.go (about)

     1  package union
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/ncw/rclone/fs"
    13  	"github.com/ncw/rclone/fs/cache"
    14  	"github.com/ncw/rclone/fs/config/configmap"
    15  	"github.com/ncw/rclone/fs/config/configstruct"
    16  	"github.com/ncw/rclone/fs/hash"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // Register with Fs
    21  func init() {
    22  	fsi := &fs.RegInfo{
    23  		Name:        "union",
    24  		Description: "A stackable unification remote, which can appear to merge the contents of several remotes",
    25  		NewFs:       NewFs,
    26  		Options: []fs.Option{{
    27  			Name:     "remotes",
    28  			Help:     "List of space separated remotes.\nCan be 'remotea:test/dir remoteb:', '\"remotea:test/space dir\" remoteb:', etc.\nThe last remote is used to write to.",
    29  			Required: true,
    30  		}},
    31  	}
    32  	fs.Register(fsi)
    33  }
    34  
    35  // Options defines the configuration for this backend
    36  type Options struct {
    37  	Remotes fs.SpaceSepList `config:"remotes"`
    38  }
    39  
    40  // Fs represents a union of remotes
    41  type Fs struct {
    42  	name     string       // name of this remote
    43  	features *fs.Features // optional features
    44  	opt      Options      // options for this Fs
    45  	root     string       // the path we are working on
    46  	remotes  []fs.Fs      // slice of remotes
    47  	wr       fs.Fs        // writable remote
    48  	hashSet  hash.Set     // intersection of hash types
    49  }
    50  
    51  // Object describes a union Object
    52  //
    53  // This is a wrapped object which returns the Union Fs as its parent
    54  type Object struct {
    55  	fs.Object
    56  	fs *Fs // what this object is part of
    57  }
    58  
    59  // Wrap an existing object in the union Object
    60  func (f *Fs) wrapObject(o fs.Object) *Object {
    61  	return &Object{
    62  		Object: o,
    63  		fs:     f,
    64  	}
    65  }
    66  
    67  // Fs returns the union Fs as the parent
    68  func (o *Object) Fs() fs.Info {
    69  	return o.fs
    70  }
    71  
    72  // Name of the remote (as passed into NewFs)
    73  func (f *Fs) Name() string {
    74  	return f.name
    75  }
    76  
    77  // Root of the remote (as passed into NewFs)
    78  func (f *Fs) Root() string {
    79  	return f.root
    80  }
    81  
    82  // String converts this Fs to a string
    83  func (f *Fs) String() string {
    84  	return fmt.Sprintf("union root '%s'", f.root)
    85  }
    86  
    87  // Features returns the optional features of this Fs
    88  func (f *Fs) Features() *fs.Features {
    89  	return f.features
    90  }
    91  
    92  // Rmdir removes the root directory of the Fs object
    93  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
    94  	return f.wr.Rmdir(ctx, dir)
    95  }
    96  
    97  // Hashes returns hash.HashNone to indicate remote hashing is unavailable
    98  func (f *Fs) Hashes() hash.Set {
    99  	return f.hashSet
   100  }
   101  
   102  // Mkdir makes the root directory of the Fs object
   103  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   104  	return f.wr.Mkdir(ctx, dir)
   105  }
   106  
   107  // Purge all files in the root and the root directory
   108  //
   109  // Implement this if you have a way of deleting all the files
   110  // quicker than just running Remove() on the result of List()
   111  //
   112  // Return an error if it doesn't exist
   113  func (f *Fs) Purge(ctx context.Context) error {
   114  	return f.wr.Features().Purge(ctx)
   115  }
   116  
   117  // Copy src to this remote using server side copy operations.
   118  //
   119  // This is stored with the remote path given
   120  //
   121  // It returns the destination Object and a possible error
   122  //
   123  // Will only be called if src.Fs().Name() == f.Name()
   124  //
   125  // If it isn't possible then return fs.ErrorCantCopy
   126  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   127  	if src.Fs() != f.wr {
   128  		fs.Debugf(src, "Can't copy - not same remote type")
   129  		return nil, fs.ErrorCantCopy
   130  	}
   131  	o, err := f.wr.Features().Copy(ctx, src, remote)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	return f.wrapObject(o), nil
   136  }
   137  
   138  // Move src to this remote using server side move operations.
   139  //
   140  // This is stored with the remote path given
   141  //
   142  // It returns the destination Object and a possible error
   143  //
   144  // Will only be called if src.Fs().Name() == f.Name()
   145  //
   146  // If it isn't possible then return fs.ErrorCantMove
   147  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   148  	if src.Fs() != f.wr {
   149  		fs.Debugf(src, "Can't move - not same remote type")
   150  		return nil, fs.ErrorCantMove
   151  	}
   152  	o, err := f.wr.Features().Move(ctx, src, remote)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	return f.wrapObject(o), err
   157  }
   158  
   159  // DirMove moves src, srcRemote to this remote at dstRemote
   160  // using server side move operations.
   161  //
   162  // Will only be called if src.Fs().Name() == f.Name()
   163  //
   164  // If it isn't possible then return fs.ErrorCantDirMove
   165  //
   166  // If destination exists then return fs.ErrorDirExists
   167  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
   168  	srcFs, ok := src.(*Fs)
   169  	if !ok {
   170  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   171  		return fs.ErrorCantDirMove
   172  	}
   173  	return f.wr.Features().DirMove(ctx, srcFs.wr, srcRemote, dstRemote)
   174  }
   175  
   176  // ChangeNotify calls the passed function with a path
   177  // that has had changes. If the implementation
   178  // uses polling, it should adhere to the given interval.
   179  // At least one value will be written to the channel,
   180  // specifying the initial value and updated values might
   181  // follow. A 0 Duration should pause the polling.
   182  // The ChangeNotify implementation must empty the channel
   183  // regularly. When the channel gets closed, the implementation
   184  // should stop polling and release resources.
   185  func (f *Fs) ChangeNotify(ctx context.Context, fn func(string, fs.EntryType), ch <-chan time.Duration) {
   186  	var remoteChans []chan time.Duration
   187  
   188  	for _, remote := range f.remotes {
   189  		if ChangeNotify := remote.Features().ChangeNotify; ChangeNotify != nil {
   190  			ch := make(chan time.Duration)
   191  			remoteChans = append(remoteChans, ch)
   192  			ChangeNotify(ctx, fn, ch)
   193  		}
   194  	}
   195  
   196  	go func() {
   197  		for i := range ch {
   198  			for _, c := range remoteChans {
   199  				c <- i
   200  			}
   201  		}
   202  		for _, c := range remoteChans {
   203  			close(c)
   204  		}
   205  	}()
   206  }
   207  
   208  // DirCacheFlush resets the directory cache - used in testing
   209  // as an optional interface
   210  func (f *Fs) DirCacheFlush() {
   211  	for _, remote := range f.remotes {
   212  		if DirCacheFlush := remote.Features().DirCacheFlush; DirCacheFlush != nil {
   213  			DirCacheFlush()
   214  		}
   215  	}
   216  }
   217  
   218  // PutStream uploads to the remote path with the modTime given of indeterminate size
   219  //
   220  // May create the object even if it returns an error - if so
   221  // will return the object and the error, otherwise will return
   222  // nil and the error
   223  func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   224  	o, err := f.wr.Features().PutStream(ctx, in, src, options...)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	return f.wrapObject(o), err
   229  }
   230  
   231  // About gets quota information from the Fs
   232  func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
   233  	return f.wr.Features().About(ctx)
   234  }
   235  
   236  // Put in to the remote path with the modTime given of the given size
   237  //
   238  // May create the object even if it returns an error - if so
   239  // will return the object and the error, otherwise will return
   240  // nil and the error
   241  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   242  	o, err := f.wr.Put(ctx, in, src, options...)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  	return f.wrapObject(o), err
   247  }
   248  
   249  // List the objects and directories in dir into entries.  The
   250  // entries can be returned in any order but should be for a
   251  // complete directory.
   252  //
   253  // dir should be "" to list the root, and should not have
   254  // trailing slashes.
   255  //
   256  // This should return ErrDirNotFound if the directory isn't
   257  // found.
   258  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   259  	set := make(map[string]fs.DirEntry)
   260  	found := false
   261  	for _, remote := range f.remotes {
   262  		var remoteEntries, err = remote.List(ctx, dir)
   263  		if err == fs.ErrorDirNotFound {
   264  			continue
   265  		}
   266  		if err != nil {
   267  			return nil, errors.Wrapf(err, "List failed on %v", remote)
   268  		}
   269  		found = true
   270  		for _, remoteEntry := range remoteEntries {
   271  			set[remoteEntry.Remote()] = remoteEntry
   272  		}
   273  	}
   274  	if !found {
   275  		return nil, fs.ErrorDirNotFound
   276  	}
   277  	for _, entry := range set {
   278  		if o, ok := entry.(fs.Object); ok {
   279  			entry = f.wrapObject(o)
   280  		}
   281  		entries = append(entries, entry)
   282  	}
   283  	return entries, nil
   284  }
   285  
   286  // NewObject creates a new remote union file object based on the first Object it finds (reverse remote order)
   287  func (f *Fs) NewObject(ctx context.Context, path string) (fs.Object, error) {
   288  	for i := range f.remotes {
   289  		var remote = f.remotes[len(f.remotes)-i-1]
   290  		var obj, err = remote.NewObject(ctx, path)
   291  		if err == fs.ErrorObjectNotFound {
   292  			continue
   293  		}
   294  		if err != nil {
   295  			return nil, errors.Wrapf(err, "NewObject failed on %v", remote)
   296  		}
   297  		return f.wrapObject(obj), nil
   298  	}
   299  	return nil, fs.ErrorObjectNotFound
   300  }
   301  
   302  // Precision is the greatest Precision of all remotes
   303  func (f *Fs) Precision() time.Duration {
   304  	var greatestPrecision time.Duration
   305  	for _, remote := range f.remotes {
   306  		if remote.Precision() > greatestPrecision {
   307  			greatestPrecision = remote.Precision()
   308  		}
   309  	}
   310  	return greatestPrecision
   311  }
   312  
   313  // NewFs constructs an Fs from the path.
   314  //
   315  // The returned Fs is the actual Fs, referenced by remote in the config
   316  func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
   317  	// Parse config into Options struct
   318  	opt := new(Options)
   319  	err := configstruct.Set(m, opt)
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  	if len(opt.Remotes) == 0 {
   324  		return nil, errors.New("union can't point to an empty remote - check the value of the remotes setting")
   325  	}
   326  	if len(opt.Remotes) == 1 {
   327  		return nil, errors.New("union can't point to a single remote - check the value of the remotes setting")
   328  	}
   329  	for _, remote := range opt.Remotes {
   330  		if strings.HasPrefix(remote, name+":") {
   331  			return nil, errors.New("can't point union remote at itself - check the value of the remote setting")
   332  		}
   333  	}
   334  
   335  	var remotes []fs.Fs
   336  	for i := range opt.Remotes {
   337  		// Last remote first so we return the correct (last) matching fs in case of fs.ErrorIsFile
   338  		var remote = opt.Remotes[len(opt.Remotes)-i-1]
   339  		_, configName, fsPath, err := fs.ParseRemote(remote)
   340  		if err != nil {
   341  			return nil, err
   342  		}
   343  		var rootString = path.Join(fsPath, filepath.ToSlash(root))
   344  		if configName != "local" {
   345  			rootString = configName + ":" + rootString
   346  		}
   347  		myFs, err := cache.Get(rootString)
   348  		if err != nil {
   349  			if err == fs.ErrorIsFile {
   350  				return myFs, err
   351  			}
   352  			return nil, err
   353  		}
   354  		remotes = append(remotes, myFs)
   355  	}
   356  
   357  	// Reverse the remotes again so they are in the order as before
   358  	for i, j := 0, len(remotes)-1; i < j; i, j = i+1, j-1 {
   359  		remotes[i], remotes[j] = remotes[j], remotes[i]
   360  	}
   361  
   362  	f := &Fs{
   363  		name:    name,
   364  		root:    root,
   365  		opt:     *opt,
   366  		remotes: remotes,
   367  		wr:      remotes[len(remotes)-1],
   368  	}
   369  	var features = (&fs.Features{
   370  		CaseInsensitive:         true,
   371  		DuplicateFiles:          false,
   372  		ReadMimeType:            true,
   373  		WriteMimeType:           true,
   374  		CanHaveEmptyDirectories: true,
   375  		BucketBased:             true,
   376  		SetTier:                 true,
   377  		GetTier:                 true,
   378  	}).Fill(f)
   379  	features = features.Mask(f.wr) // mask the features just on the writable fs
   380  
   381  	// Really need the union of all remotes for these, so
   382  	// re-instate and calculate separately.
   383  	features.ChangeNotify = f.ChangeNotify
   384  	features.DirCacheFlush = f.DirCacheFlush
   385  
   386  	// FIXME maybe should be masking the bools here?
   387  
   388  	// Clear ChangeNotify and DirCacheFlush if all are nil
   389  	clearChangeNotify := true
   390  	clearDirCacheFlush := true
   391  	for _, remote := range f.remotes {
   392  		remoteFeatures := remote.Features()
   393  		if remoteFeatures.ChangeNotify != nil {
   394  			clearChangeNotify = false
   395  		}
   396  		if remoteFeatures.DirCacheFlush != nil {
   397  			clearDirCacheFlush = false
   398  		}
   399  	}
   400  	if clearChangeNotify {
   401  		features.ChangeNotify = nil
   402  	}
   403  	if clearDirCacheFlush {
   404  		features.DirCacheFlush = nil
   405  	}
   406  
   407  	f.features = features
   408  
   409  	// Get common intersection of hashes
   410  	hashSet := f.remotes[0].Hashes()
   411  	for _, remote := range f.remotes[1:] {
   412  		hashSet = hashSet.Overlap(remote.Hashes())
   413  	}
   414  	f.hashSet = hashSet
   415  
   416  	return f, nil
   417  }
   418  
   419  // Check the interfaces are satisfied
   420  var (
   421  	_ fs.Fs              = (*Fs)(nil)
   422  	_ fs.Purger          = (*Fs)(nil)
   423  	_ fs.PutStreamer     = (*Fs)(nil)
   424  	_ fs.Copier          = (*Fs)(nil)
   425  	_ fs.Mover           = (*Fs)(nil)
   426  	_ fs.DirMover        = (*Fs)(nil)
   427  	_ fs.DirCacheFlusher = (*Fs)(nil)
   428  	_ fs.ChangeNotifier  = (*Fs)(nil)
   429  	_ fs.Abouter         = (*Fs)(nil)
   430  )