2016-11-04 00:02:34 +00:00
|
|
|
package content
|
|
|
|
|
|
|
|
import (
|
2017-02-17 08:07:02 +00:00
|
|
|
"context"
|
2017-02-22 07:41:11 +00:00
|
|
|
"fmt"
|
2016-11-04 00:02:34 +00:00
|
|
|
"io"
|
2017-02-17 08:07:02 +00:00
|
|
|
"io/ioutil"
|
2016-11-04 00:02:34 +00:00
|
|
|
|
2017-01-09 23:10:52 +00:00
|
|
|
"github.com/opencontainers/go-digest"
|
2016-11-04 00:02:34 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
// WriteBlob writes data with the expected digest into the content store. If
|
|
|
|
// expected already exists, the method returns immediately and the reader will
|
|
|
|
// not be consumed.
|
|
|
|
//
|
|
|
|
// This is useful when the digest and size are known beforehand.
|
|
|
|
//
|
|
|
|
// Copy is buffered, so no need to wrap reader in buffered io.
|
2017-02-22 07:41:11 +00:00
|
|
|
func WriteBlob(ctx context.Context, cs Ingester, ref string, r io.Reader, size int64, expected digest.Digest) error {
|
|
|
|
cw, err := cs.Writer(ctx, ref, size, expected)
|
2017-02-17 08:07:02 +00:00
|
|
|
if err != nil {
|
2017-02-22 07:41:11 +00:00
|
|
|
if !IsExists(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil // all ready present
|
2017-02-17 08:07:02 +00:00
|
|
|
}
|
2017-02-25 00:08:31 +00:00
|
|
|
defer cw.Close()
|
2017-02-17 08:07:02 +00:00
|
|
|
|
|
|
|
ws, err := cw.Status()
|
2016-11-04 00:02:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-02-17 08:07:02 +00:00
|
|
|
|
|
|
|
if ws.Offset > 0 {
|
2017-02-22 07:41:11 +00:00
|
|
|
r, err = seekReader(r, ws.Offset, size)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "unabled to resume write to %v", ref)
|
|
|
|
}
|
2017-02-17 08:07:02 +00:00
|
|
|
}
|
|
|
|
|
2017-02-16 22:45:13 +00:00
|
|
|
buf := BufPool.Get().([]byte)
|
|
|
|
defer BufPool.Put(buf)
|
2016-11-04 00:02:34 +00:00
|
|
|
|
2017-02-22 07:41:11 +00:00
|
|
|
if _, err := io.CopyBuffer(cw, r, buf); err != nil {
|
2016-11-04 00:02:34 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cw.Commit(size, expected); err != nil {
|
2017-02-22 07:41:11 +00:00
|
|
|
if !IsExists(err) {
|
|
|
|
return err
|
|
|
|
}
|
2016-11-04 00:02:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2017-02-17 08:07:02 +00:00
|
|
|
|
2017-02-22 07:41:11 +00:00
|
|
|
// seekReader attempts to seek the reader to the given offset, either by
|
|
|
|
// resolving `io.Seeker` or by detecting `io.ReaderAt`.
|
|
|
|
func seekReader(r io.Reader, offset, size int64) (io.Reader, error) {
|
|
|
|
// attempt to resolve r as a seeker and setup the offset.
|
|
|
|
seeker, ok := r.(io.Seeker)
|
|
|
|
if ok {
|
|
|
|
nn, err := seeker.Seek(offset, io.SeekStart)
|
|
|
|
if nn != offset {
|
|
|
|
return nil, fmt.Errorf("failed to seek to offset %v", offset)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ok, let's try io.ReaderAt!
|
|
|
|
readerAt, ok := r.(io.ReaderAt)
|
|
|
|
if ok && size > offset {
|
|
|
|
sr := io.NewSectionReader(readerAt, offset, size)
|
|
|
|
return sr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.Errorf("cannot seek to offset %v", offset)
|
|
|
|
}
|
|
|
|
|
2017-02-17 08:07:02 +00:00
|
|
|
func readFileString(path string) (string, error) {
|
|
|
|
p, err := ioutil.ReadFile(path)
|
|
|
|
return string(p), err
|
|
|
|
}
|