api-get-object-file.go (3439B)
1 /* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2015-2017 MinIO, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package minio 19 20 import ( 21 "context" 22 "io" 23 "os" 24 "path/filepath" 25 26 "github.com/minio/minio-go/v7/pkg/s3utils" 27 ) 28 29 // FGetObject - download contents of an object to a local file. 30 // The options can be used to specify the GET request further. 31 func (c *Client) FGetObject(ctx context.Context, bucketName, objectName, filePath string, opts GetObjectOptions) error { 32 // Input validation. 33 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 34 return err 35 } 36 if err := s3utils.CheckValidObjectName(objectName); err != nil { 37 return err 38 } 39 40 // Verify if destination already exists. 41 st, err := os.Stat(filePath) 42 if err == nil { 43 // If the destination exists and is a directory. 44 if st.IsDir() { 45 return errInvalidArgument("fileName is a directory.") 46 } 47 } 48 49 // Proceed if file does not exist. return for all other errors. 50 if err != nil { 51 if !os.IsNotExist(err) { 52 return err 53 } 54 } 55 56 // Extract top level directory. 57 objectDir, _ := filepath.Split(filePath) 58 if objectDir != "" { 59 // Create any missing top level directories. 60 if err := os.MkdirAll(objectDir, 0o700); err != nil { 61 return err 62 } 63 } 64 65 // Gather md5sum. 66 objectStat, err := c.StatObject(ctx, bucketName, objectName, StatObjectOptions(opts)) 67 if err != nil { 68 return err 69 } 70 71 // Write to a temporary file "fileName.part.minio" before saving. 72 filePartPath := filePath + objectStat.ETag + ".part.minio" 73 74 // If exists, open in append mode. If not create it as a part file. 75 filePart, err := os.OpenFile(filePartPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) 76 if err != nil { 77 return err 78 } 79 80 // If we return early with an error, be sure to close and delete 81 // filePart. If we have an error along the way there is a chance 82 // that filePart is somehow damaged, and we should discard it. 83 closeAndRemove := true 84 defer func() { 85 if closeAndRemove { 86 _ = filePart.Close() 87 _ = os.Remove(filePartPath) 88 } 89 }() 90 91 // Issue Stat to get the current offset. 92 st, err = filePart.Stat() 93 if err != nil { 94 return err 95 } 96 97 // Initialize get object request headers to set the 98 // appropriate range offsets to read from. 99 if st.Size() > 0 { 100 opts.SetRange(st.Size(), 0) 101 } 102 103 // Seek to current position for incoming reader. 104 objectReader, objectStat, _, err := c.getObject(ctx, bucketName, objectName, opts) 105 if err != nil { 106 return err 107 } 108 109 // Write to the part file. 110 if _, err = io.CopyN(filePart, objectReader, objectStat.Size); err != nil { 111 return err 112 } 113 114 // Close the file before rename, this is specifically needed for Windows users. 115 closeAndRemove = false 116 if err = filePart.Close(); err != nil { 117 return err 118 } 119 120 // Safely completed. Now commit by renaming to actual filename. 121 if err = os.Rename(filePartPath, filePath); err != nil { 122 return err 123 } 124 125 // Return. 126 return nil 127 }