func (c *Cacher) Save(ctx context.Context, i *SaveRequest) (retErr error) {
	if i == nil {
		retErr = fmt.Errorf("missing cache options")
		return
	}
	bucket := i.Bucket
	if bucket == "" {
		retErr = fmt.Errorf("missing bucket")
		return
	}
	dir := i.Dir
	if dir == "" {
		retErr = fmt.Errorf("missing directory")
		return
	}
	key := i.Key
	if key == "" {
		retErr = fmt.Errorf("missing key")
		return
	}
	attrs, err := c.client.Bucket(bucket).Object(key).Attrs(ctx)
	if err != nil && !errors.Is(err, storage.ErrObjectNotExist) {
		retErr = fmt.Errorf("failed to check if cached object exists: %w", err)
		return
	}
	if attrs != nil {
		c.log("cached object already exists, skipping")
		return
	}
	dne := storage.Conditions{DoesNotExist: true}
	gcsw := c.client.Bucket(bucket).Object(key).If(dne).NewWriter(ctx)
	defer func() {
		c.log("closing gcs writer")
		if cerr := gcsw.Close(); cerr != nil {
			if retErr != nil {
				retErr = fmt.Errorf("%v: failed to close gcs writer: %w", retErr, cerr)
				return
			}
			retErr = fmt.Errorf("failed to close gcs writer: %w", cerr)
		}
	}()
	gcsw.ChunkSize = 128_000_000
	gcsw.ObjectAttrs.ContentType = contentType
	gcsw.ObjectAttrs.CacheControl = cacheControl
	gcsw.ProgressFunc = func(soFar int64) {
		fmt.Printf("uploaded %d bytes\n", soFar)
	}
	gzw := gzip.NewWriter(gcsw)
	defer func() {
		c.log("closing gzip writer")
		if cerr := gzw.Close(); cerr != nil {
			if retErr != nil {
				retErr = fmt.Errorf("%v: failed to close gzip writer: %w", retErr, cerr)
				return
			}
			retErr = fmt.Errorf("failed to close gzip writer: %w", cerr)
		}
	}()
	tw := tar.NewWriter(gzw)
	defer func() {
		c.log("closing tar writer")
		if cerr := tw.Close(); cerr != nil {
			if retErr != nil {
				retErr = fmt.Errorf("%v: failed to close tar writer: %w", retErr, cerr)
				return
			}
			retErr = fmt.Errorf("failed to close tar writer: %w", cerr)
		}
	}()
	if err := filepath.Walk(dir, func(name string, f os.FileInfo, err error) error {
		c.log("walking file %s", name)
		if err != nil {
			return err
		}
		if !f.Mode().IsRegular() {
			c.log("file %s is not regular", name)
			return nil
		}
		header, err := tar.FileInfoHeader(f, f.Name())
		if err != nil {
			return fmt.Errorf("failed to create tar header for %s: %w", f.Name(), err)
		}
		header.Name = strings.TrimPrefix(strings.Replace(name, dir, "", -1), string(filepath.Separator))
		c.log("writing tar header for %s", name)
		if err := tw.WriteHeader(header); err != nil {
			return fmt.Errorf("failed to write tar header for %s: %w", f.Name(), err)
		}
		c.log("opening %s", name)
		file, err := os.Open(name)
		if err != nil {
			return fmt.Errorf("failed to open %s: %w", f.Name(), err)
		}
		c.log("copying %s to tar", name)
		if _, err := io.Copy(tw, file); err != nil {
			if cerr := file.Close(); cerr != nil {
				return fmt.Errorf("failed to close %s: %v: failed to write tar: %w", f.Name(), cerr, err)
			}
			return fmt.Errorf("failed to write tar for %s: %w", f.Name(), err)
		}
		c.log("closing %s", name)
		if err := file.Close(); err != nil {
			return fmt.Errorf("failed to close: %w", err)
		}
		return nil
	}); err != nil {
		retErr = fmt.Errorf("failed to walk files: %w", err)
		return
	}
	return
}
Save caches the given directory in storage.
package cacher
import (
	"archive/tar"
	"compress/gzip"
	"context"
	"errors"
	"fmt"
	"hash"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"cloud.google.com/go/storage"
	"golang.org/x/crypto/blake2b"
	"google.golang.org/api/iterator"
	"google.golang.org/api/option"
)
const (
	contentType  = "application/gzip"
	cacheControl = "public,max-age=3600"
)
type Cacher struct {
	client *storage.Client
	debug bool
}
func New(ctx context.Context) (*Cacher, error) {
	client, err := storage.NewClient(ctx,
		option.WithUserAgent("gcs-cacher/1.0"))
	if err != nil {
		return nil, fmt.Errorf("failed to create storage client: %w", err)
	}
	return &Cacher{
		client: client,
	}, nil
}
func (c *Cacher) Debug(val bool) {
	c.debug = val
}
type SaveRequest struct {
	Bucket string
	Key string
	Dir string
}