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 )