248 lines
5.0 KiB
Go
248 lines
5.0 KiB
Go
package iox
|
|
|
|
import (
|
|
`context`
|
|
`io`
|
|
)
|
|
|
|
/*
|
|
CopyBufN is a mix between [io.CopyN] and [io.CopyBuffer].
|
|
|
|
Despite what the docs may suggest, [io.CopyN] does NOT *read* n bytes from src AND write n bytes to dst.
|
|
Instead, it always reads 32 KiB from src, and writes n bytes to dst.
|
|
|
|
There are cases where this is dreadfully undesired.
|
|
|
|
One can, of course, use [io.CopyBuffer], but this is a bit annoying since you then have to provide a buffer yourself.
|
|
|
|
This convenience-wraps [io.CopyBuffer] to have a similar signature to [io.CopyN] but properly uses n for both reading and writing.
|
|
*/
|
|
func CopyBufN(dst io.Writer, src io.Reader, n int64) (written int64, err error) {
|
|
|
|
var b []byte
|
|
|
|
if n <= 0 {
|
|
err = ErrBufTooSmall
|
|
return
|
|
}
|
|
|
|
b = make([]byte, n)
|
|
|
|
written, err = io.CopyBuffer(dst, src, b)
|
|
|
|
return
|
|
}
|
|
|
|
// CopyCtxBufN copies from `src` to `dst`, `n` bytes at a time, interruptible by `ctx`.
|
|
func CopyCtxBufN(ctx context.Context, dst io.Writer, src io.Reader, n int64) (written int64, err error) {
|
|
|
|
var nr int
|
|
var nw int
|
|
var end bool
|
|
var buf []byte
|
|
|
|
if ctx == nil {
|
|
err = ErrNilCtx
|
|
return
|
|
}
|
|
if n <= 0 {
|
|
err = ErrBufTooSmall
|
|
return
|
|
}
|
|
|
|
endCopy:
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
err = ctx.Err()
|
|
return
|
|
default:
|
|
buf = make([]byte, n)
|
|
nr, err = src.Read(buf)
|
|
if err == io.EOF {
|
|
err = nil
|
|
end = true
|
|
} else if err != nil {
|
|
return
|
|
}
|
|
buf = buf[:nr]
|
|
|
|
if nw, err = dst.Write(buf); err != nil {
|
|
written += int64(nw)
|
|
return
|
|
}
|
|
written += int64(nw)
|
|
if len(buf) != nw {
|
|
err = io.ErrShortWrite
|
|
return
|
|
}
|
|
if end {
|
|
break endCopy
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
CopyBufWith allows for specifying a buffer allocator function, otherwise acts as [CopyBufN].
|
|
|
|
bufFunc *MUST NOT* return a nil or len == 0 buffer. [ErrBufTooSmall] will be returned if it does.
|
|
|
|
This uses a fixed buffer size from a single call to `bufFunc`.
|
|
If you need something with dynamic buffer sizing according to some state, use [CopyBufWithDynamic] instead.
|
|
(Note that CopyBufWithDynamic is generally a little slower, but it should only be noticeable on very large amounts of data.)
|
|
*/
|
|
func CopyBufWith(dst io.Writer, src io.Reader, bufFunc func() (b []byte)) (written int64, err error) {
|
|
|
|
var buf []byte = bufFunc()
|
|
|
|
if buf == nil || len(buf) == 0 {
|
|
err = ErrBufTooSmall
|
|
return
|
|
}
|
|
|
|
written, err = io.CopyBuffer(dst, src, buf)
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
CopyBufWithDynamic is like [CopyBufWith] except it will call bufFunc after each previous buffer is written.
|
|
|
|
That is to say (using a particularly contrived example):
|
|
|
|
import time
|
|
|
|
func dynBuf() (b []byte) {
|
|
|
|
var t time.Time = time.Now()
|
|
|
|
b = make([]byte, t.Seconds())
|
|
|
|
return
|
|
}
|
|
|
|
Then:
|
|
|
|
CopyBufWithDynamic(w, r, dynBuf)
|
|
|
|
will use a buffer sized to the seconds of the time it reads in/writes out the next buffer, whereas with [CopyBufWith]:
|
|
|
|
CopyBufWith(w, r, dynBuf)
|
|
|
|
would use a *fixed* buffer size of whatever the seconds was equal to at the time of the *first call* to dynBuf.
|
|
|
|
`src` MUST return an [io.EOF] when its end is reached, but (as per e.g. [io.CopyBuffer]) the io.EOF error will not
|
|
be returned from CopyBufWithDynamic. (Any/all other errors encountered will be returned, however, and copying will
|
|
immediately cease.)
|
|
*/
|
|
func CopyBufWithDynamic(dst io.Writer, src io.Reader, bufFunc func() (b []byte)) (written int64, err error) {
|
|
|
|
var nr int
|
|
var nw int
|
|
var end bool
|
|
var buf []byte
|
|
|
|
for {
|
|
buf = bufFunc()
|
|
if buf == nil || len(buf) == 0 {
|
|
err = ErrBufTooSmall
|
|
return
|
|
}
|
|
nr, err = src.Read(buf)
|
|
if err == io.EOF {
|
|
err = nil
|
|
end = true
|
|
} else if err != nil {
|
|
return
|
|
}
|
|
buf = buf[:nr]
|
|
|
|
if nw, err = dst.Write(buf); err != nil {
|
|
written += int64(nw)
|
|
return
|
|
}
|
|
written += int64(nw)
|
|
if len(buf) != nw {
|
|
err = ErrShortWrite
|
|
return
|
|
}
|
|
if end {
|
|
break
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// NewChunker returns a [ChunkLocker] ready to use.
|
|
func NewChunker(chunkSize uint) (c *ChunkLocker, err error) {
|
|
|
|
c = &ChunkLocker{}
|
|
err = c.SetChunkLen(chunkSize)
|
|
|
|
return
|
|
}
|
|
|
|
// NewCtxIO returns a [CtxIO].
|
|
func NewCtxIO(ctx context.Context, r io.Reader, w io.Writer, chunkSize uint) (c *CtxIO, err error) {
|
|
|
|
if r == nil {
|
|
err = ErrNilReader
|
|
return
|
|
}
|
|
if w == nil {
|
|
err = ErrNilWriter
|
|
return
|
|
}
|
|
|
|
if chunkSize == 0 {
|
|
err = ErrInvalidChunkSize
|
|
return
|
|
}
|
|
|
|
if ctx == nil {
|
|
err = ErrNilCtx
|
|
return
|
|
}
|
|
|
|
c = &CtxIO{
|
|
r: r,
|
|
w: w,
|
|
l: ChunkLocker{
|
|
chunkLen: chunkSize,
|
|
},
|
|
ctx: ctx,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
NewXIO returns a nil [XIO].
|
|
|
|
A weird "feature" of Golang is that a nil XIO is perfectly fine to use;
|
|
it's completely stateless and only has pointer receivers that only work with passed in
|
|
values so `new(XIO)` is completely unnecessary (as is NewXCopier).
|
|
In other words, this works fine:
|
|
|
|
var xc *iox.XIO
|
|
|
|
if n, err = xc.Copy(w, r); err != nil {
|
|
return
|
|
}
|
|
|
|
This function is just to maintain cleaner-looking code if you should so need it,
|
|
or want an XIO without declaring one:
|
|
|
|
if n, err = iox.NewXCopier().Copy(w, r); err != nil {
|
|
return
|
|
}
|
|
*/
|
|
func NewXIO() (x *XIO) {
|
|
// No-op lel
|
|
return
|
|
}
|