You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: s3.go
    path of file: ./repos/gotosocial/vendor/codeberg.org/gruf/go-store/v2/storage
    
    the code of the file until where you have to start completion: package storage

import (
	"bytes"
	"context"
	"io"
	"sync/atomic"

	"codeberg.org/gruf/go-store/v2/util"
	"github.com/minio/minio-go/v7"
)

// DefaultS3Config is the default S3Storage configuration.
var DefaultS3Config = &S3Config{
	CoreOpts:     minio.Options{},
	GetOpts:      minio.GetObjectOptions{},
	PutOpts:      minio.PutObjectOptions{},
	PutChunkOpts: minio.PutObjectPartOptions{},
	PutChunkSize: 4 * 1024 * 1024, // 4MiB
	StatOpts:     minio.StatObjectOptions{},
	RemoveOpts:   minio.RemoveObjectOptions{},
	ListSize:     200,
}

// S3Config defines options to be used when opening an S3Storage,
// mostly options for underlying S3 client library.
type S3Config struct {
	// CoreOpts are S3 client options passed during initialization.
	CoreOpts minio.Options

	// GetOpts are S3 client options passed during .Read___() calls.
	GetOpts minio.GetObjectOptions

	// PutOpts are S3 client options passed during .Write___() calls.
	PutOpts minio.PutObjectOptions

	// PutChunkSize is the chunk size (in bytes) to use when sending
	// a byte stream reader of unknown size as a multi-part object.
	PutChunkSize int64

	// PutChunkOpts are S3 client options passed during chunked .Write___() calls.
	PutChunkOpts minio.Pu
    
func (st *S3Storage) WriteStream(ctx context.Context, key string, r io.Reader) (int64, error) {
	// Check storage open
	if st.closed() {
		return 0, ErrClosed
	}

	if rs, ok := r.(util.ReaderSize); ok {
		// This reader supports providing us the size of
		// the encompassed data, allowing us to perform
		// a singular .PutObject() call with length.
		info, err := st.client.PutObject(
			ctx,
			st.bucket,
			key,
			r,
			rs.Size(),
			"",
			"",
			st.config.PutOpts,
		)
		if err != nil {
			err = transformS3Error(err)
		}
		return info.Size, err
	}

	// Start a new multipart upload to get ID
	uploadID, err := st.client.NewMultipartUpload(
		ctx,
		st.bucket,
		key,
		st.config.PutOpts,
	)
	if err != nil {
		return 0, transformS3Error(err)
	}

	var (
		index = int(1) // parts index
		total = int64(0)
		parts []minio.CompletePart
		chunk = make([]byte, st.config.PutChunkSize)
		rbuf  = bytes.NewReader(nil)
	)

	// Note that we do not perform any kind of
	// memory pooling of the chunk buffers here.
	// Optimal chunking sizes for S3 writes are in
	// the orders of megabytes, so letting the GC
	// collect these ASAP is much preferred.

loop:
	for done := false; !done; {
		// Read next chunk into byte buffer
		n, err := io.ReadFull(r, chunk)

		switch err {
		// Successful read
		case nil:

		// Reached end, buffer empty
		case io.EOF:
			break loop

		// Reached end, but buffer not empty
		case io.ErrUnexpectedEOF:
			done = true

		// All other errors
		default:
			return 0, err
		}

		// Reset byte reader
		rbuf.Reset(chunk[:n])

		// Put this object chunk in S3 store
		pt, err := st.client.PutObjectPart(
			ctx,
			st.bucket,
			key,
			uploadID,
			index,
			rbuf,
			int64(n),
			st.config.PutChunkOpts,
		)
		if err != nil {
			return 0, err
		}

		// Append completed part to slice
		parts = append(parts, minio.CompletePart{
			PartNumber:     pt.PartNumber,
			ETag:           pt.ETag,
			ChecksumCRC32:  pt.ChecksumCRC32,
			ChecksumCRC32C: pt.ChecksumCRC32C,
			ChecksumSHA1:   pt.ChecksumSHA1,
			ChecksumSHA256: pt.ChecksumSHA256,
		})

		// Iterate idx
		index++

		// Update total size
		total += pt.Size
	}

	// Complete this multi-part upload operation
	_, err = st.client.CompleteMultipartUpload(
		ctx,
		st.bucket,
		key,
		uploadID,
		parts,
		st.config.PutOpts,
	)
	if err != nil {
		return 0, err
	}

	return total, nil
}