Files
zeekay a3245a76f4 Rebrand: Hanzo S3 CLI -- remove all MinIO branding
- Replace copyright headers: "MinIO, Inc." -> "Hanzo AI, Inc." across 411+ Go files
- Replace "MinIO Object Storage stack" -> "Hanzo S3 stack" in all file headers
- Replace all user-facing "MinIO" references -> "Hanzo S3" in Usage, help text, examples
- Replace example alias "myminio" -> "mys3" throughout
- Replace example endpoints play.min.io -> s3.hanzo.ai, dl.min.io -> s3.hanzo.ai
- Replace doc URLs min.io -> hanzo.space
- Rewrite README.md/README_zh_CN.md: product is "Hanzo S3 CLI" (the s3 command)
- Update CONTRIBUTING.md, CONFLICT.md, NOTICE, code_of_conduct.md
- Update all Dockerfiles: labels, entrypoints, image refs -> ghcr.io/hanzos3/cli
- Update Makefile: build output, docker tags, install paths -> s3
- Update docker-buildx.sh: image tags -> ghcr.io/hanzos3/cli
- DO NOT change go.mod module path or import paths (github.com/minio/mc preserved)
- DO NOT change Go identifiers, SDK types, or wire protocol constants
- All unit tests pass
2026-02-21 14:19:07 -08:00

286 lines
7.5 KiB
Go

// Copyright (c) 2015-2022 Hanzo AI, Inc.
//
// This file is part of Hanzo S3 stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"context"
"fmt"
"io"
"math"
"path/filepath"
"strconv"
"time"
humanize "github.com/dustin/go-humanize"
)
// odSetSizes sets necessary values for object transfer.
func odSetSizes(odURLs URLs, args argKVS) (combinedSize int64, partSize uint64, parts int, skip int64, e error) {
// If parts not specified, set to 0, else scan for integer.
p := args.Get("parts")
if p == "" {
parts = 0
} else {
parts, e = strconv.Atoi(p)
if e != nil {
return 0, 0, 0, 0, e
}
}
// Get number of parts to skip, defaults to 0.
sk := args.Get("skip")
var skipInt int
if sk == "" {
skipInt = 0
} else {
skipInt, e = strconv.Atoi(sk)
if e != nil {
return 0, 0, 0, 0, e
}
}
sourceSize := odURLs.SourceContent.Size
// If neither parts nor size is specified, copy full file in 1 part.
s := args.Get("size")
if parts <= 1 && s == "" {
return sourceSize, uint64(sourceSize), 1, 0, nil
}
// If size is not specified, calculate the size based on parts and upload full file.
if s == "" {
partSize = uint64(math.Ceil(float64(sourceSize) / float64(parts)))
skip = int64(skipInt) * int64(partSize)
parts = parts - skipInt
return -1, partSize, parts, skip, nil
}
partSize, e = humanize.ParseBytes(s)
if e != nil {
return 0, 0, 0, 0, e
}
// Convert skipInt to bytes.
skip = int64(skipInt) * int64(partSize)
// If source has no content, calculate combined size and return.
// This is needed for when source is /dev/zero.
if sourceSize == 0 {
if parts == 0 {
parts = 1
}
combinedSize = int64(partSize * uint64(parts))
return combinedSize, partSize, parts, skip, nil
}
// If source is smaller than part size, upload in 1 part.
if partSize > uint64(sourceSize) {
return sourceSize, uint64(sourceSize), 1, 0, nil
}
// If parts is not specified, calculate number of parts and upload full file.
if parts < 1 {
parts = int(math.Ceil(float64(sourceSize)/float64(partSize))) - skipInt
return sourceSize, partSize, parts, skip, nil
}
combinedSize = int64(partSize * uint64(parts))
// If combined size is larger than source after skip, recalculate number of parts.
if sourceSize-skip < combinedSize {
combinedSize = sourceSize - skip
parts = int(math.Ceil(float64(sourceSize)/float64(partSize))) - skipInt
}
return combinedSize, partSize, parts, skip, nil
}
// odCopy copies a file/object from local to server, server to server, or local to local.
func odCopy(ctx context.Context, odURLs URLs, args argKVS, odType string) (odMessage, error) {
// Set sizes.
combinedSize, partSize, parts, skip, e := odSetSizes(odURLs, args)
if e != nil {
return odMessage{}, e
}
sourceAlias := odURLs.SourceAlias
sourceURL := odURLs.SourceContent.URL
sourcePath := filepath.ToSlash(filepath.Join(sourceAlias, sourceURL.Path))
targetAlias := odURLs.TargetAlias
targetURL := odURLs.TargetContent.URL
targetPath := filepath.ToSlash(filepath.Join(targetAlias, targetURL.Path))
getOpts := GetOptions{}
// Skip given number of parts.
if skip > 0 {
getOpts.RangeStart = skip
}
// Placeholder encryption key database.
var encKeyDB map[string][]prefixSSEPair
// Create reader from source.
reader, err := getSourceStreamFromURL(ctx, sourcePath, encKeyDB, getSourceOpts{GetOptions: getOpts})
fatalIf(err.Trace(sourcePath), "Unable to get source stream")
defer reader.Close()
putOpts := PutOptions{
storageClass: odURLs.TargetContent.StorageClass,
md5: odURLs.MD5,
multipartSize: partSize,
}
// Disable multipart on files too small for multipart upload.
if combinedSize < 5242880 && combinedSize > 0 {
putOpts.disableMultipart = true
}
// Used to get transfer time
pg := newAccounter(combinedSize)
// Write to target.
targetClnt, err := newClientFromAlias(targetAlias, targetURL.String())
fatalIf(err.Trace(targetURL.String()), "Unable to initialize target client")
// Put object.
total, err := targetClnt.PutPart(ctx, reader, combinedSize, pg, putOpts)
fatalIf(err.Trace(targetURL.String()), "Unable to upload")
// Get upload time.
elapsed := time.Since(pg.startTime)
message := odMessage{
Status: "success",
Type: odType,
Source: sourcePath,
Target: targetPath,
PartSize: partSize,
TotalSize: total,
Parts: parts,
Skip: int(uint64(skip) / partSize),
Elapsed: elapsed.Milliseconds(),
}
return message, nil
}
// odSetParts sets parts for object download.
func odSetParts(args argKVS) (parts, skip int, e error) {
if args.Get("size") != "" {
return 0, 0, fmt.Errorf("size cannot be specified getting from server")
}
p := args.Get("parts")
if p == "" {
return 0, 0, nil
}
parts, e = strconv.Atoi(p)
if e != nil {
return 0, 0, e
}
if parts < 1 {
return 0, 0, fmt.Errorf("parts must be at least 1")
}
sk := args.Get("skip")
if sk == "" {
skip = 0
} else {
skip, e = strconv.Atoi(sk)
}
if e != nil {
return 0, 0, e
}
return parts, skip, nil
}
// odDownload copies an object from server to local.
func odDownload(ctx context.Context, odURLs URLs, args argKVS) (odMessage, error) {
/// Set number of parts to get.
parts, skip, e := odSetParts(args)
if e != nil {
return odMessage{}, e
}
targetPath := odURLs.TargetContent.URL.Path
sourceAlias := odURLs.SourceAlias
sourceURL := odURLs.SourceContent.URL
sourcePath := filepath.ToSlash(filepath.Join(sourceAlias, sourceURL.Path))
// Get server client.
cli, err := newClientFromAlias(sourceAlias, sourceURL.String())
fatalIf(err, "Unable to initialize client")
var reader io.Reader
if parts == 0 {
// Get the full file.
reader = singleGet(ctx, cli)
} else {
// Get the file in parts.
reader = multiGet(ctx, cli, parts, skip)
}
// Accounter to get transfer time.
pg := newAccounter(-1)
// Upload the file.
total, err := putTargetStream(ctx, "", targetPath, "", "", "",
reader, -1, pg, PutOptions{})
fatalIf(err.Trace(targetPath), "Unable to upload an object")
// Get upload time.
elapsed := time.Since(pg.startTime)
message := odMessage{
Status: "success",
Type: "S3toFS",
Source: sourcePath,
Target: targetPath,
TotalSize: total,
Parts: parts,
Skip: skip,
Elapsed: elapsed.Milliseconds(),
}
return message, nil
}
// singleGet helps odDownload download a single part.
func singleGet(ctx context.Context, cli Client) io.ReadCloser {
reader, err := cli.GetPart(ctx, 0)
fatalIf(err, "Unable to download object")
return reader
}
// multiGet helps odDownload download multiple parts.
func multiGet(ctx context.Context, cli Client, parts, skip int) io.Reader {
var readers []io.Reader
// Get reader for each part.
for i := 1 + skip; i <= parts; i++ {
reader, err := cli.GetPart(ctx, parts)
fatalIf(err, "Unable to download part of an object")
readers = append(readers, reader)
}
reader := io.MultiReader(readers...)
return reader
}