func (s *Storage) Upload(ctx context.Context, name, mediaType string, cb func(Upload) (int64, error)) (string, *File, error) {
	ctx, span := xo.Trace(ctx, "blaze/Storage.Upload")
	span.Tag("type", mediaType)
	defer span.End()
	if coal.HasTransaction(ctx) {
		return "", nil, xo.F("unexpected transaction for upload")
	}
	if len(name) > maxFileNameLength {
		return "", nil, xo.SF("file name too long")
	}
	if mediaType == "" {
		if name != "" {
			mediaType = serve.MimeTypeByExtension(path.Ext(name), false)
		}
		if mediaType == "" {
			mediaType = "application/octet-stream"
		}
	}
	handle, err := s.service.Prepare(ctx)
	if err != nil {
		return "", nil, xo.W(err)
	}
	file := &File{
		Base:    coal.B(),
		State:   Uploading,
		Updated: time.Now(),
		Name:    name,
		Type:    mediaType,
		Handle:  handle,
	}
	err = file.Validate()
	if err != nil {
		return "", nil, err
	}
	err = s.store.M(file).Insert(ctx, file)
	if err != nil {
		return "", nil, err
	}
	upload, err := s.service.Upload(ctx, handle, name, mediaType)
	if err != nil {
		return "", nil, xo.W(err)
	}
	size, err := cb(upload)
	if err != nil {
		return "", nil, xo.W(err)
	}
	now := time.Now()
	file.State = Uploaded
	file.Updated = now
	file.Size = size
	err = file.Validate()
	if err != nil {
		return "", nil, err
	}
	_, err = s.store.M(file).Update(ctx, nil, file.ID(), bson.M{
		"$set": bson.M{
			"State":   Uploaded,
			"Updated": now,
			"Size":    size,
		},
	}, false)
	if err != nil {
		return "", nil, err
	}
	claimKey, err := s.notary.Issue(&ClaimKey{
		File: file.ID(),
		Size: file.Size,
		Name: file.Name,
		Type: file.Type,
	})
	if err != nil {
		return "", nil, err
	}
	return claimKey, file, nil
}
Upload will initiate and perform an upload using the provided callback and
return a claim key and the uploaded file. Upload must be called outside of a
transaction to ensure the uploaded file is tracked in case of errors.
package blaze
import (
	"context"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"path"
	"strconv"
	"strings"
	"time"
	"github.com/256dpi/serve"
	"github.com/256dpi/xo"
	"go.mongodb.org/mongo-driver/bson"
	"github.com/256dpi/fire"
	"github.com/256dpi/fire/axe"
	"github.com/256dpi/fire/coal"
	"github.com/256dpi/fire/heat"
	"github.com/256dpi/fire/stick"
)
type Storage struct {
	store    *coal.Store
	notary   *heat.Notary
	service  Service
	register *Register
}
func NewStorage(store *coal.Store, notary *heat.Notary, service Service, register *Register) *Storage {
	return &Storage{
		store:    store,
		notary:   notary,
		service:  service,
		register: register,
	}
}