github.com/artpar/rclone@v1.67.3/backend/compress/compress.go (about) 1 // Package compress provides wrappers for Fs and Object which implement compression. 2 package compress 3 4 import ( 5 "bufio" 6 "bytes" 7 "context" 8 "crypto/md5" 9 "encoding/base64" 10 "encoding/binary" 11 "encoding/hex" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "io" 16 "os" 17 "path" 18 "regexp" 19 "strings" 20 "time" 21 22 "github.com/buengese/sgzip" 23 "github.com/gabriel-vasile/mimetype" 24 25 "github.com/artpar/rclone/fs" 26 "github.com/artpar/rclone/fs/accounting" 27 "github.com/artpar/rclone/fs/chunkedreader" 28 "github.com/artpar/rclone/fs/config/configmap" 29 "github.com/artpar/rclone/fs/config/configstruct" 30 "github.com/artpar/rclone/fs/fspath" 31 "github.com/artpar/rclone/fs/hash" 32 "github.com/artpar/rclone/fs/log" 33 "github.com/artpar/rclone/fs/object" 34 "github.com/artpar/rclone/fs/operations" 35 ) 36 37 // Globals 38 const ( 39 initialChunkSize = 262144 // Initial and max sizes of chunks when reading parts of the file. Currently 40 maxChunkSize = 8388608 // at 256 KiB and 8 MiB. 41 42 bufferSize = 8388608 43 heuristicBytes = 1048576 44 minCompressionRatio = 1.1 45 46 gzFileExt = ".gz" 47 metaFileExt = ".json" 48 uncompressedFileExt = ".bin" 49 ) 50 51 // Compression modes 52 const ( 53 Uncompressed = 0 54 Gzip = 2 55 ) 56 57 var nameRegexp = regexp.MustCompile(`^(.+?)\.([A-Za-z0-9-_]{11})$`) 58 59 // Register with Fs 60 func init() { 61 // Build compression mode options. 62 compressionModeOptions := []fs.OptionExample{ 63 { // Default compression mode options { 64 Value: "gzip", 65 Help: "Standard gzip compression with fastest parameters.", 66 }, 67 } 68 69 // Register our remote 70 fs.Register(&fs.RegInfo{ 71 Name: "compress", 72 Description: "Compress a remote", 73 NewFs: NewFs, 74 MetadataInfo: &fs.MetadataInfo{ 75 Help: `Any metadata supported by the underlying remote is read and written.`, 76 }, 77 Options: []fs.Option{{ 78 Name: "remote", 79 Help: "Remote to compress.", 80 Required: true, 81 }, { 82 Name: "mode", 83 Help: "Compression mode.", 84 Default: "gzip", 85 Examples: compressionModeOptions, 86 }, { 87 Name: "level", 88 Help: `GZIP compression level (-2 to 9). 89 90 Generally -1 (default, equivalent to 5) is recommended. 91 Levels 1 to 9 increase compression at the cost of speed. Going past 6 92 generally offers very little return. 93 94 Level -2 uses Huffman encoding only. Only use if you know what you 95 are doing. 96 Level 0 turns off compression.`, 97 Default: sgzip.DefaultCompression, 98 Advanced: true, 99 }, { 100 Name: "ram_cache_limit", 101 Help: `Some remotes don't allow the upload of files with unknown size. 102 In this case the compressed file will need to be cached to determine 103 it's size. 104 105 Files smaller than this limit will be cached in RAM, files larger than 106 this limit will be cached on disk.`, 107 Default: fs.SizeSuffix(20 * 1024 * 1024), 108 Advanced: true, 109 }}, 110 }) 111 } 112 113 // Options defines the configuration for this backend 114 type Options struct { 115 Remote string `config:"remote"` 116 CompressionMode string `config:"mode"` 117 CompressionLevel int `config:"level"` 118 RAMCacheLimit fs.SizeSuffix `config:"ram_cache_limit"` 119 } 120 121 /*** FILESYSTEM FUNCTIONS ***/ 122 123 // Fs represents a wrapped fs.Fs 124 type Fs struct { 125 fs.Fs 126 wrapper fs.Fs 127 name string 128 root string 129 opt Options 130 mode int // compression mode id 131 features *fs.Features // optional features 132 } 133 134 // NewFs constructs an Fs from the path, container:path 135 func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs, error) { 136 // Parse config into Options struct 137 opt := new(Options) 138 err := configstruct.Set(m, opt) 139 if err != nil { 140 return nil, err 141 } 142 143 remote := opt.Remote 144 if strings.HasPrefix(remote, name+":") { 145 return nil, errors.New("can't point press remote at itself - check the value of the remote setting") 146 } 147 148 wInfo, wName, wPath, wConfig, err := fs.ConfigFs(remote) 149 if err != nil { 150 return nil, fmt.Errorf("failed to parse remote %q to wrap: %w", remote, err) 151 } 152 153 // Strip trailing slashes if they exist in rpath 154 rpath = strings.TrimRight(rpath, "\\/") 155 156 // First, check for a file 157 // If a metadata file was found, return an error. Otherwise, check for a directory 158 remotePath := fspath.JoinRootPath(wPath, makeMetadataName(rpath)) 159 wrappedFs, err := wInfo.NewFs(ctx, wName, remotePath, wConfig) 160 if err != fs.ErrorIsFile { 161 remotePath = fspath.JoinRootPath(wPath, rpath) 162 wrappedFs, err = wInfo.NewFs(ctx, wName, remotePath, wConfig) 163 } 164 if err != nil && err != fs.ErrorIsFile { 165 return nil, fmt.Errorf("failed to make remote %s:%q to wrap: %w", wName, remotePath, err) 166 } 167 168 // Create the wrapping fs 169 f := &Fs{ 170 Fs: wrappedFs, 171 name: name, 172 root: rpath, 173 opt: *opt, 174 mode: compressionModeFromName(opt.CompressionMode), 175 } 176 // Correct root if definitely pointing to a file 177 if err == fs.ErrorIsFile { 178 f.root = path.Dir(f.root) 179 if f.root == "." || f.root == "/" { 180 f.root = "" 181 } 182 } 183 // the features here are ones we could support, and they are 184 // ANDed with the ones from wrappedFs 185 f.features = (&fs.Features{ 186 CaseInsensitive: true, 187 DuplicateFiles: false, 188 ReadMimeType: false, 189 WriteMimeType: false, 190 GetTier: true, 191 SetTier: true, 192 BucketBased: true, 193 CanHaveEmptyDirectories: true, 194 ReadMetadata: true, 195 WriteMetadata: true, 196 UserMetadata: true, 197 ReadDirMetadata: true, 198 WriteDirMetadata: true, 199 WriteDirSetModTime: true, 200 UserDirMetadata: true, 201 DirModTimeUpdatesOnWrite: true, 202 PartialUploads: true, 203 }).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs) 204 // We support reading MIME types no matter the wrapped fs 205 f.features.ReadMimeType = true 206 // We can only support putstream if we have serverside copy or move 207 if !operations.CanServerSideMove(wrappedFs) { 208 f.features.Disable("PutStream") 209 } 210 211 return f, err 212 } 213 214 func compressionModeFromName(name string) int { 215 switch name { 216 case "gzip": 217 return Gzip 218 default: 219 return Uncompressed 220 } 221 } 222 223 // Converts an int64 to base64 224 func int64ToBase64(number int64) string { 225 intBytes := make([]byte, 8) 226 binary.LittleEndian.PutUint64(intBytes, uint64(number)) 227 return base64.RawURLEncoding.EncodeToString(intBytes) 228 } 229 230 // Converts base64 to int64 231 func base64ToInt64(str string) (int64, error) { 232 intBytes, err := base64.RawURLEncoding.DecodeString(str) 233 if err != nil { 234 return 0, err 235 } 236 return int64(binary.LittleEndian.Uint64(intBytes)), nil 237 } 238 239 // Processes a file name for a compressed file. Returns the original file name, the extension, and the size of the original file. 240 // Returns -2 for the original size if the file is uncompressed. 241 func processFileName(compressedFileName string) (origFileName string, extension string, origSize int64, err error) { 242 // Separate the filename and size from the extension 243 extensionPos := strings.LastIndex(compressedFileName, ".") 244 if extensionPos == -1 { 245 return "", "", 0, errors.New("file name has no extension") 246 } 247 extension = compressedFileName[extensionPos:] 248 nameWithSize := compressedFileName[:extensionPos] 249 if extension == uncompressedFileExt { 250 return nameWithSize, extension, -2, nil 251 } 252 match := nameRegexp.FindStringSubmatch(nameWithSize) 253 if match == nil || len(match) != 3 { 254 return "", "", 0, errors.New("invalid filename") 255 } 256 size, err := base64ToInt64(match[2]) 257 if err != nil { 258 return "", "", 0, errors.New("could not decode size") 259 } 260 return match[1], gzFileExt, size, nil 261 } 262 263 // Generates the file name for a metadata file 264 func makeMetadataName(remote string) (newRemote string) { 265 return remote + metaFileExt 266 } 267 268 // Checks whether a file is a metadata file 269 func isMetadataFile(filename string) bool { 270 return strings.HasSuffix(filename, metaFileExt) 271 } 272 273 // Checks whether a file is a metadata file and returns the original 274 // file name and a flag indicating whether it was a metadata file or 275 // not. 276 func unwrapMetadataFile(filename string) (string, bool) { 277 if !isMetadataFile(filename) { 278 return "", false 279 } 280 return filename[:len(filename)-len(metaFileExt)], true 281 } 282 283 // makeDataName generates the file name for a data file with specified compression mode 284 func makeDataName(remote string, size int64, mode int) (newRemote string) { 285 if mode != Uncompressed { 286 newRemote = remote + "." + int64ToBase64(size) + gzFileExt 287 } else { 288 newRemote = remote + uncompressedFileExt 289 } 290 return newRemote 291 } 292 293 // dataName generates the file name for data file 294 func (f *Fs) dataName(remote string, size int64, compressed bool) (name string) { 295 if !compressed { 296 return makeDataName(remote, size, Uncompressed) 297 } 298 return makeDataName(remote, size, f.mode) 299 } 300 301 // addData parses an object and adds it to the DirEntries 302 func (f *Fs) addData(entries *fs.DirEntries, o fs.Object) { 303 origFileName, _, size, err := processFileName(o.Remote()) 304 if err != nil { 305 fs.Errorf(o, "Error on parsing file name: %v", err) 306 return 307 } 308 if size == -2 { // File is uncompressed 309 size = o.Size() 310 } 311 metaName := makeMetadataName(origFileName) 312 *entries = append(*entries, f.newObjectSizeAndNameOnly(o, metaName, size)) 313 } 314 315 // addDir adds a dir to the dir entries 316 func (f *Fs) addDir(entries *fs.DirEntries, dir fs.Directory) { 317 *entries = append(*entries, f.newDir(dir)) 318 } 319 320 // newDir returns a dir 321 func (f *Fs) newDir(dir fs.Directory) fs.Directory { 322 return dir // We're using the same dir 323 } 324 325 // processEntries parses the file names and adds metadata to the dir entries 326 func (f *Fs) processEntries(entries fs.DirEntries) (newEntries fs.DirEntries, err error) { 327 newEntries = entries[:0] // in place filter 328 for _, entry := range entries { 329 switch x := entry.(type) { 330 case fs.Object: 331 if !isMetadataFile(x.Remote()) { 332 f.addData(&newEntries, x) // Only care about data files for now; metadata files are redundant. 333 } 334 case fs.Directory: 335 f.addDir(&newEntries, x) 336 default: 337 return nil, fmt.Errorf("unknown object type %T", entry) 338 } 339 } 340 return newEntries, nil 341 } 342 343 // List the objects and directories in dir into entries. The 344 // entries can be returned in any order but should be for a 345 // complete directory. 346 // 347 // dir should be "" to list the root, and should not have 348 // trailing slashes. 349 // 350 // This should return ErrDirNotFound if the directory isn't 351 // found. 352 // List entries and process them 353 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 354 entries, err = f.Fs.List(ctx, dir) 355 if err != nil { 356 return nil, err 357 } 358 return f.processEntries(entries) 359 } 360 361 // ListR lists the objects and directories of the Fs starting 362 // from dir recursively into out. 363 // 364 // dir should be "" to start from the root, and should not 365 // have trailing slashes. 366 // 367 // This should return ErrDirNotFound if the directory isn't 368 // found. 369 // 370 // It should call callback for each tranche of entries read. 371 // These need not be returned in any particular order. If 372 // callback returns an error then the listing will stop 373 // immediately. 374 // 375 // Don't implement this unless you have a more efficient way 376 // of listing recursively that doing a directory traversal. 377 func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (err error) { 378 return f.Fs.Features().ListR(ctx, dir, func(entries fs.DirEntries) error { 379 newEntries, err := f.processEntries(entries) 380 if err != nil { 381 return err 382 } 383 return callback(newEntries) 384 }) 385 } 386 387 // NewObject finds the Object at remote. 388 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 389 // Read metadata from metadata object 390 mo, err := f.Fs.NewObject(ctx, makeMetadataName(remote)) 391 if err != nil { 392 return nil, err 393 } 394 meta, err := readMetadata(ctx, mo) 395 if err != nil { 396 return nil, fmt.Errorf("error decoding metadata: %w", err) 397 } 398 // Create our Object 399 o, err := f.Fs.NewObject(ctx, makeDataName(remote, meta.CompressionMetadata.Size, meta.Mode)) 400 if err != nil { 401 return nil, err 402 } 403 return f.newObject(o, mo, meta), nil 404 } 405 406 // checkCompressAndType checks if an object is compressible and determines it's mime type 407 // returns a multireader with the bytes that were read to determine mime type 408 func checkCompressAndType(in io.Reader) (newReader io.Reader, compressible bool, mimeType string, err error) { 409 in, wrap := accounting.UnWrap(in) 410 buf := make([]byte, heuristicBytes) 411 n, err := in.Read(buf) 412 buf = buf[:n] 413 if err != nil && err != io.EOF { 414 return nil, false, "", err 415 } 416 mime := mimetype.Detect(buf) 417 compressible, err = isCompressible(bytes.NewReader(buf)) 418 if err != nil { 419 return nil, false, "", err 420 } 421 in = io.MultiReader(bytes.NewReader(buf), in) 422 return wrap(in), compressible, mime.String(), nil 423 } 424 425 // isCompressible checks the compression ratio of the provided data and returns true if the ratio exceeds 426 // the configured threshold 427 func isCompressible(r io.Reader) (bool, error) { 428 var b bytes.Buffer 429 w, err := sgzip.NewWriterLevel(&b, sgzip.DefaultCompression) 430 if err != nil { 431 return false, err 432 } 433 n, err := io.Copy(w, r) 434 if err != nil { 435 return false, err 436 } 437 err = w.Close() 438 if err != nil { 439 return false, err 440 } 441 ratio := float64(n) / float64(b.Len()) 442 return ratio > minCompressionRatio, nil 443 } 444 445 // verifyObjectHash verifies the Objects hash 446 func (f *Fs) verifyObjectHash(ctx context.Context, o fs.Object, hasher *hash.MultiHasher, ht hash.Type) error { 447 srcHash := hasher.Sums()[ht] 448 dstHash, err := o.Hash(ctx, ht) 449 if err != nil { 450 return fmt.Errorf("failed to read destination hash: %w", err) 451 } 452 if srcHash != "" && dstHash != "" && srcHash != dstHash { 453 // remove object 454 err = o.Remove(ctx) 455 if err != nil { 456 fs.Errorf(o, "Failed to remove corrupted object: %v", err) 457 } 458 return fmt.Errorf("corrupted on transfer: %v compressed hashes differ src(%s) %q vs dst(%s) %q", ht, f.Fs, srcHash, o.Fs(), dstHash) 459 } 460 return nil 461 } 462 463 type putFn func(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) 464 465 type compressionResult struct { 466 err error 467 meta sgzip.GzipMetadata 468 } 469 470 // replicating some of operations.Rcat functionality because we want to support remotes without streaming 471 // support and of course cannot know the size of a compressed file before compressing it. 472 func (f *Fs) rcat(ctx context.Context, dstFileName string, in io.ReadCloser, modTime time.Time, options []fs.OpenOption) (o fs.Object, err error) { 473 474 // cache small files in memory and do normal upload 475 buf := make([]byte, f.opt.RAMCacheLimit) 476 if n, err := io.ReadFull(in, buf); err == io.EOF || err == io.ErrUnexpectedEOF { 477 src := object.NewStaticObjectInfo(dstFileName, modTime, int64(len(buf[:n])), false, nil, f.Fs) 478 return f.Fs.Put(ctx, bytes.NewBuffer(buf[:n]), src, options...) 479 } 480 481 // Need to include what we already read 482 in = &ReadCloserWrapper{ 483 Reader: io.MultiReader(bytes.NewReader(buf), in), 484 Closer: in, 485 } 486 487 canStream := f.Fs.Features().PutStream != nil 488 if canStream { 489 src := object.NewStaticObjectInfo(dstFileName, modTime, -1, false, nil, f.Fs) 490 return f.Fs.Features().PutStream(ctx, in, src, options...) 491 } 492 493 fs.Debugf(f, "Target remote doesn't support streaming uploads, creating temporary local file") 494 tempFile, err := os.CreateTemp("", "rclone-press-") 495 defer func() { 496 // these errors should be relatively uncritical and the upload should've succeeded so it's okay-ish 497 // to ignore them 498 _ = tempFile.Close() 499 _ = os.Remove(tempFile.Name()) 500 }() 501 if err != nil { 502 return nil, fmt.Errorf("failed to create temporary local FS to spool file: %w", err) 503 } 504 if _, err = io.Copy(tempFile, in); err != nil { 505 return nil, fmt.Errorf("failed to write temporary local file: %w", err) 506 } 507 if _, err = tempFile.Seek(0, 0); err != nil { 508 return nil, err 509 } 510 finfo, err := tempFile.Stat() 511 if err != nil { 512 return nil, err 513 } 514 return f.Fs.Put(ctx, tempFile, object.NewStaticObjectInfo(dstFileName, modTime, finfo.Size(), false, nil, f.Fs)) 515 } 516 517 // Put a compressed version of a file. Returns a wrappable object and metadata. 518 func (f *Fs) putCompress(ctx context.Context, in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, mimeType string) (fs.Object, *ObjectMetadata, error) { 519 // Unwrap reader accounting 520 in, wrap := accounting.UnWrap(in) 521 522 // Add the metadata hasher 523 metaHasher := md5.New() 524 in = io.TeeReader(in, metaHasher) 525 526 // Compress the file 527 pipeReader, pipeWriter := io.Pipe() 528 results := make(chan compressionResult) 529 go func() { 530 gz, err := sgzip.NewWriterLevel(pipeWriter, f.opt.CompressionLevel) 531 if err != nil { 532 results <- compressionResult{err: err, meta: sgzip.GzipMetadata{}} 533 return 534 } 535 _, err = io.Copy(gz, in) 536 gzErr := gz.Close() 537 if gzErr != nil { 538 fs.Errorf(nil, "Failed to close compress: %v", gzErr) 539 if err == nil { 540 err = gzErr 541 } 542 } 543 closeErr := pipeWriter.Close() 544 if closeErr != nil { 545 fs.Errorf(nil, "Failed to close pipe: %v", closeErr) 546 if err == nil { 547 err = closeErr 548 } 549 } 550 results <- compressionResult{err: err, meta: gz.MetaData()} 551 }() 552 wrappedIn := wrap(bufio.NewReaderSize(pipeReader, bufferSize)) // Probably no longer needed as sgzip has it's own buffering 553 554 // Find a hash the destination supports to compute a hash of 555 // the compressed data. 556 ht := f.Fs.Hashes().GetOne() 557 var hasher *hash.MultiHasher 558 var err error 559 if ht != hash.None { 560 // unwrap the accounting again 561 wrappedIn, wrap = accounting.UnWrap(wrappedIn) 562 hasher, err = hash.NewMultiHasherTypes(hash.NewHashSet(ht)) 563 if err != nil { 564 return nil, nil, err 565 } 566 // add the hasher and re-wrap the accounting 567 wrappedIn = io.TeeReader(wrappedIn, hasher) 568 wrappedIn = wrap(wrappedIn) 569 } 570 571 // Transfer the data 572 o, err := f.rcat(ctx, makeDataName(src.Remote(), src.Size(), f.mode), io.NopCloser(wrappedIn), src.ModTime(ctx), options) 573 //o, err := operations.Rcat(ctx, f.Fs, makeDataName(src.Remote(), src.Size(), f.mode), io.NopCloser(wrappedIn), src.ModTime(ctx)) 574 if err != nil { 575 if o != nil { 576 removeErr := o.Remove(ctx) 577 if removeErr != nil { 578 fs.Errorf(o, "Failed to remove partially transferred object: %v", err) 579 } 580 } 581 return nil, nil, err 582 } 583 // Check whether we got an error during compression 584 result := <-results 585 err = result.err 586 if err != nil { 587 if o != nil { 588 removeErr := o.Remove(ctx) 589 if removeErr != nil { 590 fs.Errorf(o, "Failed to remove partially compressed object: %v", err) 591 } 592 } 593 return nil, nil, err 594 } 595 596 // Generate metadata 597 meta := newMetadata(result.meta.Size, f.mode, result.meta, hex.EncodeToString(metaHasher.Sum(nil)), mimeType) 598 599 // Check the hashes of the compressed data if we were comparing them 600 if ht != hash.None && hasher != nil { 601 err = f.verifyObjectHash(ctx, o, hasher, ht) 602 if err != nil { 603 return nil, nil, err 604 } 605 } 606 607 return o, meta, nil 608 } 609 610 // Put an uncompressed version of a file. Returns a wrappable object and metadata. 611 func (f *Fs) putUncompress(ctx context.Context, in io.Reader, src fs.ObjectInfo, put putFn, options []fs.OpenOption, mimeType string) (fs.Object, *ObjectMetadata, error) { 612 // Unwrap the accounting, add our metadata hasher, then wrap it back on 613 in, wrap := accounting.UnWrap(in) 614 615 hs := hash.NewHashSet(hash.MD5) 616 ht := f.Fs.Hashes().GetOne() 617 if !hs.Contains(ht) { 618 hs.Add(ht) 619 } 620 metaHasher, err := hash.NewMultiHasherTypes(hs) 621 if err != nil { 622 return nil, nil, err 623 } 624 in = io.TeeReader(in, metaHasher) 625 wrappedIn := wrap(in) 626 627 // Put the object 628 o, err := put(ctx, wrappedIn, f.wrapInfo(src, makeDataName(src.Remote(), src.Size(), Uncompressed), src.Size()), options...) 629 if err != nil { 630 if o != nil { 631 removeErr := o.Remove(ctx) 632 if removeErr != nil { 633 fs.Errorf(o, "Failed to remove partially transferred object: %v", err) 634 } 635 } 636 return nil, nil, err 637 } 638 // Check the hashes of the compressed data if we were comparing them 639 if ht != hash.None { 640 err := f.verifyObjectHash(ctx, o, metaHasher, ht) 641 if err != nil { 642 return nil, nil, err 643 } 644 } 645 646 // Return our object and metadata 647 sum, err := metaHasher.Sum(hash.MD5) 648 if err != nil { 649 return nil, nil, err 650 } 651 return o, newMetadata(o.Size(), Uncompressed, sgzip.GzipMetadata{}, hex.EncodeToString(sum), mimeType), nil 652 } 653 654 // This function will write a metadata struct to a metadata Object for an src. Returns a wrappable metadata object. 655 func (f *Fs) putMetadata(ctx context.Context, meta *ObjectMetadata, src fs.ObjectInfo, options []fs.OpenOption, put putFn) (mo fs.Object, err error) { 656 // Generate the metadata contents 657 data, err := json.Marshal(meta) 658 if err != nil { 659 return nil, err 660 } 661 metaReader := bytes.NewReader(data) 662 663 // Put the data 664 mo, err = put(ctx, metaReader, f.wrapInfo(src, makeMetadataName(src.Remote()), int64(len(data))), options...) 665 if err != nil { 666 if mo != nil { 667 removeErr := mo.Remove(ctx) 668 if removeErr != nil { 669 fs.Errorf(mo, "Failed to remove partially transferred object: %v", err) 670 } 671 } 672 return nil, err 673 } 674 675 return mo, nil 676 } 677 678 // This function will put both the data and metadata for an Object. 679 // putData is the function used for data, while putMeta is the function used for metadata. 680 // The putData function will only be used when the object is not compressible if the 681 // data is compressible this parameter will be ignored. 682 func (f *Fs) putWithCustomFunctions(ctx context.Context, in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, 683 putData putFn, putMeta putFn, compressible bool, mimeType string) (*Object, error) { 684 // Put file then metadata 685 var dataObject fs.Object 686 var meta *ObjectMetadata 687 var err error 688 if compressible { 689 dataObject, meta, err = f.putCompress(ctx, in, src, options, mimeType) 690 } else { 691 dataObject, meta, err = f.putUncompress(ctx, in, src, putData, options, mimeType) 692 } 693 if err != nil { 694 return nil, err 695 } 696 697 mo, err := f.putMetadata(ctx, meta, src, options, putMeta) 698 699 // meta data upload may fail. in this case we try to remove the original object 700 if err != nil { 701 removeError := dataObject.Remove(ctx) 702 if removeError != nil { 703 return nil, removeError 704 } 705 return nil, err 706 } 707 return f.newObject(dataObject, mo, meta), nil 708 } 709 710 // Put in to the remote path with the modTime given of the given size 711 // 712 // May create the object even if it returns an error - if so 713 // will return the object and the error, otherwise will return 714 // nil and the error 715 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 716 // If there's already an existent objects we need to make sure to explicitly update it to make sure we don't leave 717 // orphaned data. Alternatively we could also deleted (which would simpler) but has the disadvantage that it 718 // destroys all server-side versioning. 719 o, err := f.NewObject(ctx, src.Remote()) 720 if err == fs.ErrorObjectNotFound { 721 // Get our file compressibility 722 in, compressible, mimeType, err := checkCompressAndType(in) 723 if err != nil { 724 return nil, err 725 } 726 return f.putWithCustomFunctions(ctx, in, src, options, f.Fs.Put, f.Fs.Put, compressible, mimeType) 727 } 728 if err != nil { 729 return nil, err 730 } 731 return o, o.Update(ctx, in, src, options...) 732 } 733 734 // PutStream uploads to the remote path with the modTime given of indeterminate size 735 func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 736 oldObj, err := f.NewObject(ctx, src.Remote()) 737 if err != nil && err != fs.ErrorObjectNotFound { 738 return nil, err 739 } 740 found := err == nil 741 742 in, compressible, mimeType, err := checkCompressAndType(in) 743 if err != nil { 744 return nil, err 745 } 746 newObj, err := f.putWithCustomFunctions(ctx, in, src, options, f.Fs.Features().PutStream, f.Fs.Put, compressible, mimeType) 747 if err != nil { 748 return nil, err 749 } 750 751 // Our transfer is now complete. We have to make sure to remove the old object because our new object will 752 // have a different name except when both the old and the new object where uncompressed. 753 if found && (oldObj.(*Object).meta.Mode != Uncompressed || compressible) { 754 err = oldObj.(*Object).Object.Remove(ctx) 755 if err != nil { 756 return nil, fmt.Errorf("couldn't remove original object: %w", err) 757 } 758 } 759 760 // If our new object is compressed we have to rename it with the correct size. 761 // Uncompressed objects don't store the size in the name so we they'll already have the correct name. 762 if compressible { 763 wrapObj, err := operations.Move(ctx, f.Fs, nil, f.dataName(src.Remote(), newObj.size, compressible), newObj.Object) 764 if err != nil { 765 return nil, fmt.Errorf("couldn't rename streamed object: %w", err) 766 } 767 newObj.Object = wrapObj 768 } 769 return newObj, nil 770 } 771 772 // Temporarily disabled. There might be a way to implement this correctly but with the current handling metadata duplicate objects 773 // will break stuff. Right no I can't think of a way to make this work. 774 775 // PutUnchecked uploads the object 776 // 777 // This will create a duplicate if we upload a new file without 778 // checking to see if there is one already - use Put() for that. 779 780 // Hashes returns the supported hash sets. 781 func (f *Fs) Hashes() hash.Set { 782 return hash.Set(hash.MD5) 783 } 784 785 // Mkdir makes the directory (container, bucket) 786 // 787 // Shouldn't return an error if it already exists 788 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 789 return f.Fs.Mkdir(ctx, dir) 790 } 791 792 // MkdirMetadata makes the root directory of the Fs object 793 func (f *Fs) MkdirMetadata(ctx context.Context, dir string, metadata fs.Metadata) (fs.Directory, error) { 794 if do := f.Fs.Features().MkdirMetadata; do != nil { 795 return do(ctx, dir, metadata) 796 } 797 return nil, fs.ErrorNotImplemented 798 } 799 800 // Rmdir removes the directory (container, bucket) if empty 801 // 802 // Return an error if it doesn't exist or isn't empty 803 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 804 return f.Fs.Rmdir(ctx, dir) 805 } 806 807 // Purge all files in the root and the root directory 808 // 809 // Implement this if you have a way of deleting all the files 810 // quicker than just running Remove() on the result of List() 811 // 812 // Return an error if it doesn't exist 813 func (f *Fs) Purge(ctx context.Context, dir string) error { 814 do := f.Fs.Features().Purge 815 if do == nil { 816 return fs.ErrorCantPurge 817 } 818 return do(ctx, dir) 819 } 820 821 // Copy src to this remote using server side copy operations. 822 // 823 // This is stored with the remote path given. 824 // 825 // It returns the destination Object and a possible error. 826 // 827 // Will only be called if src.Fs().Name() == f.Name() 828 // 829 // If it isn't possible then return fs.ErrorCantCopy 830 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 831 do := f.Fs.Features().Copy 832 if do == nil { 833 return nil, fs.ErrorCantCopy 834 } 835 o, ok := src.(*Object) 836 if !ok { 837 return nil, fs.ErrorCantCopy 838 } 839 // We might be trying to overwrite a file with a newer version but due to size difference the name 840 // is different. Therefore we have to remove the old file first (if it exists). 841 dstFile, err := f.NewObject(ctx, remote) 842 if err != nil && err != fs.ErrorObjectNotFound { 843 return nil, err 844 } 845 if err == nil { 846 err := dstFile.Remove(ctx) 847 if err != nil { 848 return nil, err 849 } 850 } 851 852 // Copy over metadata 853 err = o.loadMetadataIfNotLoaded(ctx) 854 if err != nil { 855 return nil, err 856 } 857 newFilename := makeMetadataName(remote) 858 moResult, err := do(ctx, o.mo, newFilename) 859 if err != nil { 860 return nil, err 861 } 862 // Copy over data 863 newFilename = makeDataName(remote, src.Size(), o.meta.Mode) 864 oResult, err := do(ctx, o.Object, newFilename) 865 if err != nil { 866 return nil, err 867 } 868 return f.newObject(oResult, moResult, o.meta), nil 869 } 870 871 // Move src to this remote using server side move operations. 872 // 873 // This is stored with the remote path given. 874 // 875 // It returns the destination Object and a possible error. 876 // 877 // Will only be called if src.Fs().Name() == f.Name() 878 // 879 // If it isn't possible then return fs.ErrorCantMove 880 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 881 do := f.Fs.Features().Move 882 if do == nil { 883 return nil, fs.ErrorCantMove 884 } 885 o, ok := src.(*Object) 886 if !ok { 887 return nil, fs.ErrorCantMove 888 } 889 // We might be trying to overwrite a file with a newer version but due to size difference the name 890 // is different. Therefore we have to remove the old file first (if it exists). 891 dstFile, err := f.NewObject(ctx, remote) 892 if err != nil && err != fs.ErrorObjectNotFound { 893 return nil, err 894 } 895 if err == nil { 896 err := dstFile.Remove(ctx) 897 if err != nil { 898 return nil, err 899 } 900 } 901 902 // Move metadata 903 err = o.loadMetadataIfNotLoaded(ctx) 904 if err != nil { 905 return nil, err 906 } 907 newFilename := makeMetadataName(remote) 908 moResult, err := do(ctx, o.mo, newFilename) 909 if err != nil { 910 return nil, err 911 } 912 913 // Move data 914 newFilename = makeDataName(remote, src.Size(), o.meta.Mode) 915 oResult, err := do(ctx, o.Object, newFilename) 916 if err != nil { 917 return nil, err 918 } 919 return f.newObject(oResult, moResult, o.meta), nil 920 } 921 922 // DirMove moves src, srcRemote to this remote at dstRemote 923 // using server side move operations. 924 // 925 // Will only be called if src.Fs().Name() == f.Name() 926 // 927 // If it isn't possible then return fs.ErrorCantDirMove 928 // 929 // If destination exists then return fs.ErrorDirExists 930 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 931 do := f.Fs.Features().DirMove 932 if do == nil { 933 return fs.ErrorCantDirMove 934 } 935 srcFs, ok := src.(*Fs) 936 if !ok { 937 fs.Debugf(srcFs, "Can't move directory - not same remote type") 938 return fs.ErrorCantDirMove 939 } 940 return do(ctx, srcFs.Fs, srcRemote, dstRemote) 941 } 942 943 // DirSetModTime sets the directory modtime for dir 944 func (f *Fs) DirSetModTime(ctx context.Context, dir string, modTime time.Time) error { 945 if do := f.Fs.Features().DirSetModTime; do != nil { 946 return do(ctx, dir, modTime) 947 } 948 return fs.ErrorNotImplemented 949 } 950 951 // CleanUp the trash in the Fs 952 // 953 // Implement this if you have a way of emptying the trash or 954 // otherwise cleaning up old versions of files. 955 func (f *Fs) CleanUp(ctx context.Context) error { 956 do := f.Fs.Features().CleanUp 957 if do == nil { 958 return errors.New("not supported by underlying remote") 959 } 960 return do(ctx) 961 } 962 963 // About gets quota information from the Fs 964 func (f *Fs) About(ctx context.Context) (*fs.Usage, error) { 965 do := f.Fs.Features().About 966 if do == nil { 967 return nil, errors.New("not supported by underlying remote") 968 } 969 return do(ctx) 970 } 971 972 // UnWrap returns the Fs that this Fs is wrapping 973 func (f *Fs) UnWrap() fs.Fs { 974 return f.Fs 975 } 976 977 // WrapFs returns the Fs that is wrapping this Fs 978 func (f *Fs) WrapFs() fs.Fs { 979 return f.wrapper 980 } 981 982 // SetWrapper sets the Fs that is wrapping this Fs 983 func (f *Fs) SetWrapper(wrapper fs.Fs) { 984 f.wrapper = wrapper 985 } 986 987 // MergeDirs merges the contents of all the directories passed 988 // in into the first one and rmdirs the other directories. 989 func (f *Fs) MergeDirs(ctx context.Context, dirs []fs.Directory) error { 990 do := f.Fs.Features().MergeDirs 991 if do == nil { 992 return errors.New("MergeDirs not supported") 993 } 994 out := make([]fs.Directory, len(dirs)) 995 for i, dir := range dirs { 996 out[i] = fs.NewDirCopy(ctx, dir).SetRemote(dir.Remote()) 997 } 998 return do(ctx, out) 999 } 1000 1001 // DirCacheFlush resets the directory cache - used in testing 1002 // as an optional interface 1003 func (f *Fs) DirCacheFlush() { 1004 do := f.Fs.Features().DirCacheFlush 1005 if do != nil { 1006 do() 1007 } 1008 } 1009 1010 // ChangeNotify calls the passed function with a path 1011 // that has had changes. If the implementation 1012 // uses polling, it should adhere to the given interval. 1013 func (f *Fs) ChangeNotify(ctx context.Context, notifyFunc func(string, fs.EntryType), pollIntervalChan <-chan time.Duration) { 1014 do := f.Fs.Features().ChangeNotify 1015 if do == nil { 1016 return 1017 } 1018 wrappedNotifyFunc := func(path string, entryType fs.EntryType) { 1019 fs.Logf(f, "path %q entryType %d", path, entryType) 1020 var ( 1021 wrappedPath string 1022 isMetadataFile bool 1023 ) 1024 switch entryType { 1025 case fs.EntryDirectory: 1026 wrappedPath = path 1027 case fs.EntryObject: 1028 // Note: All we really need to do to monitor the object is to check whether the metadata changed, 1029 // as the metadata contains the hash. This will work unless there's a hash collision and the sizes stay the same. 1030 wrappedPath, isMetadataFile = unwrapMetadataFile(path) 1031 if !isMetadataFile { 1032 return 1033 } 1034 default: 1035 fs.Errorf(path, "press ChangeNotify: ignoring unknown EntryType %d", entryType) 1036 return 1037 } 1038 notifyFunc(wrappedPath, entryType) 1039 } 1040 do(ctx, wrappedNotifyFunc, pollIntervalChan) 1041 } 1042 1043 // PublicLink generates a public link to the remote path (usually readable by anyone) 1044 func (f *Fs) PublicLink(ctx context.Context, remote string, duration fs.Duration, unlink bool) (string, error) { 1045 do := f.Fs.Features().PublicLink 1046 if do == nil { 1047 return "", errors.New("can't PublicLink: not supported by underlying remote") 1048 } 1049 o, err := f.NewObject(ctx, remote) 1050 if err != nil { 1051 // assume it is a directory 1052 return do(ctx, remote, duration, unlink) 1053 } 1054 return do(ctx, o.(*Object).Object.Remote(), duration, unlink) 1055 } 1056 1057 /*** OBJECT FUNCTIONS ***/ 1058 1059 // ObjectMetadata describes the metadata for an Object. 1060 type ObjectMetadata struct { 1061 Mode int // Compression mode of the file. 1062 Size int64 // Size of the object. 1063 MD5 string // MD5 hash of the file. 1064 MimeType string // Mime type of the file 1065 CompressionMetadata sgzip.GzipMetadata 1066 } 1067 1068 // Object with external metadata 1069 type Object struct { 1070 fs.Object // Wraps around data object for this object 1071 f *Fs // Filesystem object is in 1072 mo fs.Object // Metadata object for this object 1073 moName string // Metadata file name for this object 1074 size int64 // Size of this object 1075 meta *ObjectMetadata // Metadata struct for this object (nil if not loaded) 1076 } 1077 1078 // This function generates a metadata object 1079 func newMetadata(size int64, mode int, cmeta sgzip.GzipMetadata, md5 string, mimeType string) *ObjectMetadata { 1080 meta := new(ObjectMetadata) 1081 meta.Size = size 1082 meta.Mode = mode 1083 meta.CompressionMetadata = cmeta 1084 meta.MD5 = md5 1085 meta.MimeType = mimeType 1086 return meta 1087 } 1088 1089 // This function will read the metadata from a metadata object. 1090 func readMetadata(ctx context.Context, mo fs.Object) (meta *ObjectMetadata, err error) { 1091 // Open our meradata object 1092 rc, err := mo.Open(ctx) 1093 if err != nil { 1094 return nil, err 1095 } 1096 defer fs.CheckClose(rc, &err) 1097 jr := json.NewDecoder(rc) 1098 meta = new(ObjectMetadata) 1099 if err = jr.Decode(meta); err != nil { 1100 return nil, err 1101 } 1102 return meta, nil 1103 } 1104 1105 // Remove removes this object 1106 func (o *Object) Remove(ctx context.Context) error { 1107 err := o.loadMetadataObjectIfNotLoaded(ctx) 1108 if err != nil { 1109 return err 1110 } 1111 err = o.mo.Remove(ctx) 1112 objErr := o.Object.Remove(ctx) 1113 if err != nil { 1114 return err 1115 } 1116 return objErr 1117 } 1118 1119 // ReadCloserWrapper combines a Reader and a Closer to a ReadCloser 1120 type ReadCloserWrapper struct { 1121 io.Reader 1122 io.Closer 1123 } 1124 1125 // Update in to the object with the modTime given of the given size 1126 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) { 1127 err = o.loadMetadataIfNotLoaded(ctx) // Loads metadata object too 1128 if err != nil { 1129 return err 1130 } 1131 // Function that updates metadata object 1132 updateMeta := func(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 1133 return o.mo, o.mo.Update(ctx, in, src, options...) 1134 } 1135 1136 in, compressible, mimeType, err := checkCompressAndType(in) 1137 if err != nil { 1138 return err 1139 } 1140 1141 // Since we are storing the filesize in the name the new object may have different name than the old 1142 // We'll make sure to delete the old object in this case 1143 var newObject *Object 1144 origName := o.Remote() 1145 if o.meta.Mode != Uncompressed || compressible { 1146 newObject, err = o.f.putWithCustomFunctions(ctx, in, o.f.wrapInfo(src, origName, src.Size()), options, o.f.Fs.Put, updateMeta, compressible, mimeType) 1147 if err != nil { 1148 return err 1149 } 1150 if newObject.Object.Remote() != o.Object.Remote() { 1151 if removeErr := o.Object.Remove(ctx); removeErr != nil { 1152 return removeErr 1153 } 1154 } 1155 } else { 1156 // We can only support update when BOTH the old and the new object are uncompressed because only then 1157 // the filesize will be known beforehand and name will stay the same 1158 update := func(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 1159 return o.Object, o.Object.Update(ctx, in, src, options...) 1160 } 1161 // If we are, just update the object and metadata 1162 newObject, err = o.f.putWithCustomFunctions(ctx, in, src, options, update, updateMeta, compressible, mimeType) 1163 if err != nil { 1164 return err 1165 } 1166 } 1167 // Update object metadata and return 1168 o.Object = newObject.Object 1169 o.meta = newObject.meta 1170 o.size = newObject.size 1171 return nil 1172 } 1173 1174 // This will initialize the variables of a new press Object. The metadata object, mo, and metadata struct, meta, must be specified. 1175 func (f *Fs) newObject(o fs.Object, mo fs.Object, meta *ObjectMetadata) *Object { 1176 if o == nil { 1177 log.Trace(nil, "newObject(%#v, %#v, %#v) called with nil o", o, mo, meta) 1178 } 1179 return &Object{ 1180 Object: o, 1181 f: f, 1182 mo: mo, 1183 moName: mo.Remote(), 1184 size: meta.Size, 1185 meta: meta, 1186 } 1187 } 1188 1189 // This initializes the variables of a press Object with only the size. The metadata will be loaded later on demand. 1190 func (f *Fs) newObjectSizeAndNameOnly(o fs.Object, moName string, size int64) *Object { 1191 if o == nil { 1192 log.Trace(nil, "newObjectSizeAndNameOnly(%#v, %#v, %#v) called with nil o", o, moName, size) 1193 } 1194 return &Object{ 1195 Object: o, 1196 f: f, 1197 mo: nil, 1198 moName: moName, 1199 size: size, 1200 meta: nil, 1201 } 1202 } 1203 1204 // Shutdown the backend, closing any background tasks and any 1205 // cached connections. 1206 func (f *Fs) Shutdown(ctx context.Context) error { 1207 do := f.Fs.Features().Shutdown 1208 if do == nil { 1209 return nil 1210 } 1211 return do(ctx) 1212 } 1213 1214 // This loads the metadata of a press Object if it's not loaded yet 1215 func (o *Object) loadMetadataIfNotLoaded(ctx context.Context) (err error) { 1216 err = o.loadMetadataObjectIfNotLoaded(ctx) 1217 if err != nil { 1218 return err 1219 } 1220 if o.meta == nil { 1221 o.meta, err = readMetadata(ctx, o.mo) 1222 } 1223 return err 1224 } 1225 1226 // This loads the metadata object of a press Object if it's not loaded yet 1227 func (o *Object) loadMetadataObjectIfNotLoaded(ctx context.Context) (err error) { 1228 if o.mo == nil { 1229 o.mo, err = o.f.Fs.NewObject(ctx, o.moName) 1230 } 1231 return err 1232 } 1233 1234 // Fs returns read only access to the Fs that this object is part of 1235 func (o *Object) Fs() fs.Info { 1236 return o.f 1237 } 1238 1239 // Return a string version 1240 func (o *Object) String() string { 1241 if o == nil { 1242 return "<nil>" 1243 } 1244 return o.Remote() 1245 } 1246 1247 // Remote returns the remote path 1248 func (o *Object) Remote() string { 1249 origFileName, _, _, err := processFileName(o.Object.Remote()) 1250 if err != nil { 1251 fs.Errorf(o.f, "Could not get remote path for: %s", o.Object.Remote()) 1252 return o.Object.Remote() 1253 } 1254 return origFileName 1255 } 1256 1257 // Size returns the size of the file 1258 func (o *Object) Size() int64 { 1259 if o.meta == nil { 1260 return o.size 1261 } 1262 return o.meta.Size 1263 } 1264 1265 // MimeType returns the MIME type of the file 1266 func (o *Object) MimeType(ctx context.Context) string { 1267 err := o.loadMetadataIfNotLoaded(ctx) 1268 if err != nil { 1269 return "error/error" 1270 } 1271 return o.meta.MimeType 1272 } 1273 1274 // Metadata returns metadata for an object 1275 // 1276 // It should return nil if there is no Metadata 1277 func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) { 1278 err := o.loadMetadataIfNotLoaded(ctx) 1279 if err != nil { 1280 return nil, err 1281 } 1282 do, ok := o.mo.(fs.Metadataer) 1283 if !ok { 1284 return nil, nil 1285 } 1286 return do.Metadata(ctx) 1287 } 1288 1289 // Hash returns the selected checksum of the file 1290 // If no checksum is available it returns "" 1291 func (o *Object) Hash(ctx context.Context, ht hash.Type) (string, error) { 1292 if ht != hash.MD5 { 1293 return "", hash.ErrUnsupported 1294 } 1295 err := o.loadMetadataIfNotLoaded(ctx) 1296 if err != nil { 1297 return "", err 1298 } 1299 return o.meta.MD5, nil 1300 } 1301 1302 // SetTier performs changing storage tier of the Object if 1303 // multiple storage classes supported 1304 func (o *Object) SetTier(tier string) error { 1305 do, ok := o.Object.(fs.SetTierer) 1306 mdo, mok := o.mo.(fs.SetTierer) 1307 if !(ok && mok) { 1308 return errors.New("press: underlying remote does not support SetTier") 1309 } 1310 if err := mdo.SetTier(tier); err != nil { 1311 return err 1312 } 1313 return do.SetTier(tier) 1314 } 1315 1316 // GetTier returns storage tier or class of the Object 1317 func (o *Object) GetTier() string { 1318 do, ok := o.mo.(fs.GetTierer) 1319 if !ok { 1320 return "" 1321 } 1322 return do.GetTier() 1323 } 1324 1325 // UnWrap returns the wrapped Object 1326 func (o *Object) UnWrap() fs.Object { 1327 return o.Object 1328 } 1329 1330 // Open opens the file for read. Call Close() on the returned io.ReadCloser. Note that this call requires quite a bit of overhead. 1331 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (rc io.ReadCloser, err error) { 1332 err = o.loadMetadataIfNotLoaded(ctx) 1333 if err != nil { 1334 return nil, err 1335 } 1336 // If we're uncompressed, just pass this to the underlying object 1337 if o.meta.Mode == Uncompressed { 1338 return o.Object.Open(ctx, options...) 1339 } 1340 // Get offset and limit from OpenOptions, pass the rest to the underlying remote 1341 var openOptions = []fs.OpenOption{&fs.SeekOption{Offset: 0}} 1342 var offset, limit int64 = 0, -1 1343 for _, option := range options { 1344 switch x := option.(type) { 1345 case *fs.SeekOption: 1346 offset = x.Offset 1347 case *fs.RangeOption: 1348 offset, limit = x.Decode(o.Size()) 1349 default: 1350 openOptions = append(openOptions, option) 1351 } 1352 } 1353 // Get a chunkedreader for the wrapped object 1354 chunkedReader := chunkedreader.New(ctx, o.Object, initialChunkSize, maxChunkSize) 1355 // Get file handle 1356 var file io.Reader 1357 if offset != 0 { 1358 file, err = sgzip.NewReaderAt(chunkedReader, &o.meta.CompressionMetadata, offset) 1359 } else { 1360 file, err = sgzip.NewReader(chunkedReader) 1361 } 1362 if err != nil { 1363 return nil, err 1364 } 1365 1366 var fileReader io.Reader 1367 if limit != -1 { 1368 fileReader = io.LimitReader(file, limit) 1369 } else { 1370 fileReader = file 1371 } 1372 // Return a ReadCloser 1373 return ReadCloserWrapper{Reader: fileReader, Closer: chunkedReader}, nil 1374 } 1375 1376 // ObjectInfo describes a wrapped fs.ObjectInfo for being the source 1377 type ObjectInfo struct { 1378 src fs.ObjectInfo 1379 fs *Fs 1380 remote string 1381 size int64 1382 } 1383 1384 func (f *Fs) wrapInfo(src fs.ObjectInfo, newRemote string, size int64) *ObjectInfo { 1385 return &ObjectInfo{ 1386 src: src, 1387 fs: f, 1388 remote: newRemote, 1389 size: size, 1390 } 1391 } 1392 1393 // Fs returns read only access to the Fs that this object is part of 1394 func (o *ObjectInfo) Fs() fs.Info { 1395 if o.fs == nil { 1396 panic("stub ObjectInfo") 1397 } 1398 return o.fs 1399 } 1400 1401 // String returns string representation 1402 func (o *ObjectInfo) String() string { 1403 return o.src.String() 1404 } 1405 1406 // Storable returns whether object is storable 1407 func (o *ObjectInfo) Storable() bool { 1408 return o.src.Storable() 1409 } 1410 1411 // Remote returns the remote path 1412 func (o *ObjectInfo) Remote() string { 1413 if o.remote != "" { 1414 return o.remote 1415 } 1416 return o.src.Remote() 1417 } 1418 1419 // Size returns the size of the file 1420 func (o *ObjectInfo) Size() int64 { 1421 return o.size 1422 } 1423 1424 // ModTime returns the modification time 1425 func (o *ObjectInfo) ModTime(ctx context.Context) time.Time { 1426 return o.src.ModTime(ctx) 1427 } 1428 1429 // Hash returns the selected checksum of the file 1430 // If no checksum is available it returns "" 1431 func (o *ObjectInfo) Hash(ctx context.Context, ht hash.Type) (string, error) { 1432 return "", nil // cannot know the checksum 1433 } 1434 1435 // ID returns the ID of the Object if known, or "" if not 1436 func (o *ObjectInfo) ID() string { 1437 do, ok := o.src.(fs.IDer) 1438 if !ok { 1439 return "" 1440 } 1441 return do.ID() 1442 } 1443 1444 // MimeType returns the content type of the Object if 1445 // known, or "" if not 1446 func (o *ObjectInfo) MimeType(ctx context.Context) string { 1447 do, ok := o.src.(fs.MimeTyper) 1448 if !ok { 1449 return "" 1450 } 1451 return do.MimeType(ctx) 1452 } 1453 1454 // UnWrap returns the Object that this Object is wrapping or 1455 // nil if it isn't wrapping anything 1456 func (o *ObjectInfo) UnWrap() fs.Object { 1457 return fs.UnWrapObjectInfo(o.src) 1458 } 1459 1460 // Metadata returns metadata for an object 1461 // 1462 // It should return nil if there is no Metadata 1463 func (o *ObjectInfo) Metadata(ctx context.Context) (fs.Metadata, error) { 1464 do, ok := o.src.(fs.Metadataer) 1465 if !ok { 1466 return nil, nil 1467 } 1468 return do.Metadata(ctx) 1469 } 1470 1471 // GetTier returns storage tier or class of the Object 1472 func (o *ObjectInfo) GetTier() string { 1473 do, ok := o.src.(fs.GetTierer) 1474 if !ok { 1475 return "" 1476 } 1477 return do.GetTier() 1478 } 1479 1480 // ID returns the ID of the Object if known, or "" if not 1481 func (o *Object) ID() string { 1482 do, ok := o.Object.(fs.IDer) 1483 if !ok { 1484 return "" 1485 } 1486 return do.ID() 1487 } 1488 1489 // Name of the remote (as passed into NewFs) 1490 func (f *Fs) Name() string { 1491 return f.name 1492 } 1493 1494 // Root of the remote (as passed into NewFs) 1495 func (f *Fs) Root() string { 1496 return f.root 1497 } 1498 1499 // Features returns the optional features of this Fs 1500 func (f *Fs) Features() *fs.Features { 1501 return f.features 1502 } 1503 1504 // Return a string version 1505 func (f *Fs) String() string { 1506 return fmt.Sprintf("Compressed: %s:%s", f.name, f.root) 1507 } 1508 1509 // Precision returns the precision of this Fs 1510 func (f *Fs) Precision() time.Duration { 1511 return f.Fs.Precision() 1512 } 1513 1514 // Check the interfaces are satisfied 1515 var ( 1516 _ fs.Fs = (*Fs)(nil) 1517 _ fs.Purger = (*Fs)(nil) 1518 _ fs.Copier = (*Fs)(nil) 1519 _ fs.Mover = (*Fs)(nil) 1520 _ fs.DirMover = (*Fs)(nil) 1521 _ fs.DirSetModTimer = (*Fs)(nil) 1522 _ fs.MkdirMetadataer = (*Fs)(nil) 1523 _ fs.PutStreamer = (*Fs)(nil) 1524 _ fs.CleanUpper = (*Fs)(nil) 1525 _ fs.UnWrapper = (*Fs)(nil) 1526 _ fs.ListRer = (*Fs)(nil) 1527 _ fs.Abouter = (*Fs)(nil) 1528 _ fs.Wrapper = (*Fs)(nil) 1529 _ fs.MergeDirser = (*Fs)(nil) 1530 _ fs.DirCacheFlusher = (*Fs)(nil) 1531 _ fs.ChangeNotifier = (*Fs)(nil) 1532 _ fs.PublicLinker = (*Fs)(nil) 1533 _ fs.Shutdowner = (*Fs)(nil) 1534 _ fs.FullObjectInfo = (*ObjectInfo)(nil) 1535 _ fs.FullObject = (*Object)(nil) 1536 )