func (u *Uploader) UploadFileMIME(ctx context.Context, fileName, mimeType string) (content blob.Ref, err error) {
	fh, err := os.Open(fileName)
	if err != nil {
		return content, err
	}
	defer fh.Close()
	fi, err := fh.Stat()
	if err != nil {
		return content, err
	}
	rdr := io.Reader(fh)
	if mimeType == "" || mimeType == "application/octet-stream" {
		mimeType, rdr = MIMETypeFromReader(fh)
	}
	br, err := u.FromReaderInfo(ctx, fi, mimeType, rdr)
	return br, err
}
UploadFileMIME uploads a regular file with the given MIME type.
package camutil
import (
	"bytes"
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"perkeep.org/pkg/blob"
	"perkeep.org/pkg/blobserver"
	"perkeep.org/pkg/blobserver/localdisk"
	"perkeep.org/pkg/client"
	"perkeep.org/pkg/schema"
)
type Uploader struct {
	*client.Client
	blobserver.StatReceiver
	*schema.Signer
	gate          chan struct{}
	server        string
	args          []string
	opts          []string
	env           []string
	mtx           sync.Mutex
	skipHaveCache bool
}
var ErrFileIsEmpty = errors.New("file is empty")
var cachedUploader = make(map[string]*Uploader, 1)
var cachedUploaderMtx = new(sync.Mutex)
func Close() error {
	cachedUploaderMtx.Lock()
	defer cachedUploaderMtx.Unlock()
	for k := range cachedUploader {
		delete(cachedUploader, k)
	}
	cachedDownloaderMtx.Lock()
	defer cachedDownloaderMtx.Unlock()
	for k := range cachedDownloader {
		cachedDownloader[k].Close()
		delete(cachedDownloader, k)
	}
	return nil
}
func NewUploader(server string, capCtime bool, skipHaveCache bool) *Uploader {
	key := fmt.Sprintf("%q\t%t\t%t", server, capCtime, skipHaveCache)
	cachedUploaderMtx.Lock()
	defer cachedUploaderMtx.Unlock()
	u, ok := cachedUploader[key]
	if ok {
		return u
	}
	maxProcs := runtime.GOMAXPROCS(-1)
	if strings.HasPrefix(server, "file://") {
		recv, err := localdisk.New(server[7:])
		if err != nil {
			Log("msg", "localdisk.New", "server", server, "error", err)
			return nil
		}
		u = &Uploader{
			server:        server,
			gate:          make(chan struct{}, maxProcs),
			skipHaveCache: skipHaveCache,
			StatReceiver:  recv,
			Signer:        newDummySigner(),
		}
		cachedUploader[key] = u
		return u
	}
	c, err := NewClient(server)
	if err != nil || c == nil {
		Log("msg", "NewClient", "server", server, "error", err)
		return nil
	}
	u = &Uploader{
		server:        server,
		args:          make([]string, 1, 2),
		opts:          make([]string, 0, 3),
		gate:          make(chan struct{}, maxProcs),
		skipHaveCache: skipHaveCache,
		Client:        c,
		StatReceiver:  c,
	}
	u.args[0] = cmdPkPut
	if server != "" {
		u.args = append(u.args, "-server="+server)
	}
	needDebugEnv := false
	if skipHaveCache {
		u.opts = append(u.opts, "-havecache=false", "-statcache=false")
		needDebugEnv = true
	}
	if capCtime {
		u.opts = append(u.opts, "-capctime")
		needDebugEnv = true
	}
	if needDebugEnv {
		if os.Getenv("CAMLI_DEBUG") != "true" {  
			u.env = append(os.Environ(), "CAMLI_DEBUG=true")
		}
	}
	cachedUploader[key] = u
	return u
}
func (u *Uploader) Close() error {
	var err error
	if u.StatReceiver != nil {
		if cl, ok := u.StatReceiver.(io.Closer); ok {
			err = cl.Close()
		}
		u.StatReceiver = nil
	}
	if u.Client == nil {
		return err
	}
	defer func() {
		if r := recover(); r != nil {
			err = r.(error)
		}
	}()
	err = u.Client.Close()
	return err
}
func (u *Uploader) UploadBytes(ctx context.Context, r io.Reader) (blob.Ref, error) {
	bb := schema.NewBuilder()
	bb.SetType("bytes")
	err := schema.WriteFileChunks(ctx, u.StatReceiver, bb, r)
	if err != nil {
		return blob.Ref{}, err
	}
	b := bb.Blob()
	br, err := u.Client.UploadBlob(ctx, b)
	return br.BlobRef, err
}
func (u *Uploader) FromReader(ctx context.Context, fileName string, r io.Reader) (blob.Ref, error) {
	if err := ctx.Err(); err != nil {
		return blob.Ref{}, err
	}
	select {
	case u.gate <- struct{}{}:
		defer func() { <-u.gate }()
	case <-ctx.Done():
		return blob.Ref{}, ctx.Err()
	}
	return schema.WriteFileFromReader(ctx, u.StatReceiver, filepath.Base(fileName), r)
}
func (u *Uploader) FromReaderInfo(ctx context.Context, fi os.FileInfo, mime string, r io.Reader) (blob.Ref, error) {
	if err := ctx.Err(); err != nil {
		return blob.Ref{}, err
	}
	file := schema.NewCommonFileMap(filepath.Base(fi.Name()), fi)
	file = file.CapCreationTime().SetRawStringField("mimeType", mime)
	file = file.SetType("file")
	select {
	case u.gate <- struct{}{}:
		defer func() { <-u.gate }()
	case <-ctx.Done():
		return blob.Ref{}, ctx.Err()
	}
	return schema.WriteFileMap(ctx, u.StatReceiver, file, r)
}
func (u *Uploader) UploadFile(
	ctx context.Context,
	path, mime string,
	permanode bool,
) (content, perma blob.Ref, err error) {
	if err = ctx.Err(); err != nil {
		return
	}
	direct := u.StatReceiver != nil
	if direct {
		fi, err := os.Stat(path)
		if err != nil {
			return content, perma, err
		}
		direct = fi.Mode().IsRegular()
	}
	if !direct {
		return u.UploadFileExt(ctx, path, permanode)
	}
	if content, err = u.UploadFileMIME(ctx, path, mime); !permanode || err != nil {
		return content, perma, err
	}
	pbRes, err := u.Client.UploadPlannedPermanode(ctx, content.String(), time.Now())
	if err != nil {
		return content, perma, err
	}
	perma = pbRes.BlobRef
	_, err = u.Client.UploadAndSignBlob(ctx, schema.NewAddAttributeClaim(pbRes.BlobRef, "camliContent", content.String()))
	return content, perma, err
}
func (u *Uploader) UploadFileLazyAttr(
	ctx context.Context,
	path, mime string,
	attrs map[string]string,
) (content, perma blob.Ref, err error) {
	if err = ctx.Err(); err != nil {
		return
	}
	direct := u.StatReceiver != nil
	if direct {
		fi, err := os.Stat(path)
		if err != nil {
			return content, perma, err
		}
		direct = fi.Mode().IsRegular()
	}
	if !direct {
		return u.UploadFileExtLazyAttr(ctx, path, attrs)
	}
	filteredAttrs := filterAttrs("camli", attrs)
	if content, err = u.UploadFileMIME(ctx, path, mime); len(filteredAttrs) == 0 || err != nil {
		return content, perma, err
	}
	filteredAttrs["camliContent"] = content.String()
	if perma, err = u.NewPermanode(ctx, filteredAttrs); err != nil {
		Log("msg", "NewPermanode", "attrs", filteredAttrs, "error", err)
	}
	return content, perma, nil
}
func (u *Uploader) UploadReaderInfoLazyAttr(
	ctx context.Context,
	fi os.FileInfo, mime string, r io.Reader,
	attrs map[string]string,
) (content, perma blob.Ref, err error) {
	if err = ctx.Err(); err != nil {
		return
	}
	filteredAttrs := filterAttrs("camli", attrs)
	if content, err = u.FromReaderInfo(ctx, fi, mime, r); err != nil || len(filteredAttrs) == 0 {
		return content, perma, err
	}
	filteredAttrs["camliContent"] = content.String()
	if perma, err = u.NewPermanode(ctx, filteredAttrs); err != nil {
		Log("msg", "NewPermanode", "attrs", filteredAttrs, "error", err)
	}
	return content, perma, nil
}
func filterAttrs(skipPrefix string, attrs map[string]string) map[string]string {
	filteredAttrs := make(map[string]string, len(attrs)+1)
	for k, v := range attrs {
		if strings.HasPrefix(k, skipPrefix) {
			continue
		}
		filteredAttrs[k] = v
	}
	return filteredAttrs
}
func (u *Uploader) NewPermanode(ctx context.Context, attrs map[string]string) (blob.Ref, error) {
	if err := ctx.Err(); err != nil {
		return blob.Ref{}, err
	}
	if u.Client != nil {
		pRes, err := u.Client.UploadNewPermanode(ctx)
		if err != nil {
			Log("msg", "UploadNewPermanode", "error", err)
			return blob.Ref{}, err
		}
		if len(attrs) > 0 {
			err = u.SetPermanodeAttrs(ctx, pRes.BlobRef, attrs)
		}
		return pRes.BlobRef, err
	}
	if u.Signer != nil {  
		signed, err := schema.NewUnsignedPermanode().Sign(ctx, u.Signer)
		if err != nil {
			Log("msg", "Sign", "signer", u.Signer, "error", err)
			return blob.Ref{}, err
		}
		return blob.RefFromString(signed), err
	}
	refs, err := u.camput(ctx, "permanode")
	if err != nil || len(refs) == 0 {
		return blob.Ref{}, err
	}
	err = u.SetPermanodeAttrs(ctx, refs[0], attrs)
	return refs[0], err
}
func (u *Uploader) SetPermanodeAttrs(ctx context.Context, perma blob.Ref, attrs map[string]string) error {
	var setAttr func(k, v string) (blob.Ref, error)
	if u.Client != nil {
		setAttr = func(k, v string) (blob.Ref, error) {
			pRes, err := u.Client.UploadAndSignBlob(ctx, schema.NewSetAttributeClaim(perma, k, v))
			if err != nil {
				return blob.Ref{}, err
			}
			return pRes.BlobRef, nil
		}
	} else {
		pS := perma.String()
		setAttr = func(k, v string) (blob.Ref, error) {
			refs, err := u.camput(ctx, "attr", pS, k, v)
			if err != nil || len(refs) == 0 {
				Log("msg", "SetPermanodeAttrs", "key", k, "value", v, "perma", pS, "error", err)
				return blob.Ref{}, err
			}
			return refs[0], nil
		}
	}
	for k, v := range attrs {
		if _, err := setAttr(k, v); err != nil {
			Log("msg", "SetPermanodeAttrs", "key", k, "value", v, "perma", perma.String(), "error", err)
			return err
		}
	}
	return nil
}