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 )