Merge pull request #46 from jkowalski/read-stream-range

added ReadStreamRange() method to efficiently read a range of data
This commit is contained in:
Christoph Polcin 2021-11-06 10:05:35 +01:00 committed by GitHub
commit 29e74efa70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 0 deletions

View File

@ -3,6 +3,7 @@ package gowebdav
import ( import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -346,6 +347,43 @@ func (c *Client) ReadStream(path string) (io.ReadCloser, error) {
return nil, newPathError("ReadStream", path, rs.StatusCode) return nil, newPathError("ReadStream", path, rs.StatusCode)
} }
// ReadStreamRange reads the stream representing a subset of bytes for a given path,
// utilizing HTTP Range Requests if the server supports it.
// The range is expressed as offset from the start of the file and length, for example
// offset=10, length=10 will return bytes 10 through 19.
//
// If the server does not support partial content requests and returns full content instead,
// this function will emulate the behavior by skipping `offset` bytes and limiting the result
// to `length`.
func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error) {
rs, err := c.req("GET", path, nil, func(r *http.Request) {
r.Header.Add("Range", fmt.Sprintf("bytes=%v-%v", offset, offset+length-1))
})
if err != nil {
return nil, newPathErrorErr("ReadStreamRange", path, err)
}
if rs.StatusCode == http.StatusPartialContent {
// server supported partial content, return as-is.
return rs.Body, nil
}
// server returned success, but did not support partial content, so we have the whole
// stream in rs.Body
if rs.StatusCode == 200 {
// discard first 'offset' bytes.
if _, err := io.Copy(io.Discard, io.LimitReader(rs.Body, offset)); err != nil {
return nil, newPathErrorErr("ReadStreamRange", path, err)
}
// return a io.ReadCloser that is limited to `length` bytes.
return &limitedReadCloser{rs.Body, int(length)}, nil
}
rs.Body.Close()
return nil, newPathError("ReadStream", path, rs.StatusCode)
}
// Write writes data to a given path // Write writes data to a given path
func (c *Client) Write(path string, data []byte, _ os.FileMode) error { func (c *Client) Write(path string, data []byte, _ os.FileMode) error {
s := c.put(path, bytes.NewReader(data)) s := c.put(path, bytes.NewReader(data))

View File

@ -108,3 +108,28 @@ func parseXML(data io.Reader, resp interface{}, parse func(resp interface{}) err
} }
return nil return nil
} }
// limitedReadCloser wraps a io.ReadCloser and limits the number of bytes that can be read from it.
type limitedReadCloser struct {
rc io.ReadCloser
remaining int
}
func (l *limitedReadCloser) Read(buf []byte) (int, error) {
if l.remaining <= 0 {
return 0, io.EOF
}
if len(buf) > l.remaining {
buf = buf[0:l.remaining]
}
n, err := l.rc.Read(buf)
l.remaining -= n
return n, err
}
func (l *limitedReadCloser) Close() error {
return l.rc.Close()
}