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
18package minio
19
20import (
21	"context"
22	"io"
23	"os"
24	"path/filepath"
25
26	"github.com/minio/minio-go/v6/pkg/s3utils"
27)
28
29// FGetObjectWithContext - download contents of an object to a local file.
30// The options can be used to specify the GET request further.
31func (c Client) FGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string, opts GetObjectOptions) error {
32	return c.fGetObjectWithContext(ctx, bucketName, objectName, filePath, opts)
33}
34
35// FGetObject - download contents of an object to a local file.
36func (c Client) FGetObject(bucketName, objectName, filePath string, opts GetObjectOptions) error {
37	return c.fGetObjectWithContext(context.Background(), bucketName, objectName, filePath, opts)
38}
39
40// fGetObjectWithContext - fgetObject wrapper function with context
41func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string, opts GetObjectOptions) error {
42	// Input validation.
43	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
44		return err
45	}
46	if err := s3utils.CheckValidObjectName(objectName); err != nil {
47		return err
48	}
49
50	// Verify if destination already exists.
51	st, err := os.Stat(filePath)
52	if err == nil {
53		// If the destination exists and is a directory.
54		if st.IsDir() {
55			return ErrInvalidArgument("fileName is a directory.")
56		}
57	}
58
59	// Proceed if file does not exist. return for all other errors.
60	if err != nil {
61		if !os.IsNotExist(err) {
62			return err
63		}
64	}
65
66	// Extract top level directory.
67	objectDir, _ := filepath.Split(filePath)
68	if objectDir != "" {
69		// Create any missing top level directories.
70		if err := os.MkdirAll(objectDir, 0700); err != nil {
71			return err
72		}
73	}
74
75	// Gather md5sum.
76	objectStat, err := c.StatObject(bucketName, objectName, StatObjectOptions{opts})
77	if err != nil {
78		return err
79	}
80
81	// Write to a temporary file "fileName.part.minio" before saving.
82	filePartPath := filePath + objectStat.ETag + ".part.minio"
83
84	// If exists, open in append mode. If not create it as a part file.
85	filePart, err := os.OpenFile(filePartPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
86	if err != nil {
87		return err
88	}
89
90	// If we return early with an error, be sure to close and delete
91	// filePart.  If we have an error along the way there is a chance
92	// that filePart is somehow damaged, and we should discard it.
93	closeAndRemove := true
94	defer func() {
95		if closeAndRemove {
96			_ = filePart.Close()
97			_ = os.Remove(filePartPath)
98		}
99	}()
100
101	// Issue Stat to get the current offset.
102	st, err = filePart.Stat()
103	if err != nil {
104		return err
105	}
106
107	// Initialize get object request headers to set the
108	// appropriate range offsets to read from.
109	if st.Size() > 0 {
110		opts.SetRange(st.Size(), 0)
111	}
112
113	// Seek to current position for incoming reader.
114	objectReader, objectStat, _, err := c.getObject(ctx, bucketName, objectName, opts)
115	if err != nil {
116		return err
117	}
118
119	// Write to the part file.
120	if _, err = io.CopyN(filePart, objectReader, objectStat.Size); err != nil {
121		return err
122	}
123
124	// Close the file before rename, this is specifically needed for Windows users.
125	closeAndRemove = false
126	if err = filePart.Close(); err != nil {
127		return err
128	}
129
130	// Safely completed. Now commit by renaming to actual filename.
131	if err = os.Rename(filePartPath, filePath); err != nil {
132		return err
133	}
134
135	// Return.
136	return nil
137}
138