1
0
mirror of https://github.com/rclone/rclone.git synced 2026-01-08 19:43:58 +00:00

operations: add ReadAt method to ReOpen

This commit is contained in:
Nick Craig-Wood
2025-10-29 21:33:01 +00:00
parent 203df6cc58
commit fb30c5f8dd
2 changed files with 74 additions and 12 deletions

View File

@@ -20,6 +20,7 @@ type AccountFn func(n int) error
type ReOpen struct {
ctx context.Context
mu sync.Mutex // mutex to protect the below
readAtMu sync.Mutex // mutex to serialize the ReadAt calls
src fs.Object // object to open
baseOptions []fs.OpenOption // options to pass to initial open and where offset == 0
options []fs.OpenOption // option to pass on subsequent opens where offset != 0
@@ -232,6 +233,35 @@ func (h *ReOpen) Read(p []byte) (n int, err error) {
return n, err
}
// ReadAt reads len(p) bytes at absolute offset off without changing
// the read position.
//
// Note: operations are serialized; it won't behave like a truly
// concurrent ReaderAt.
func (h *ReOpen) ReadAt(p []byte, off int64) (n int, err error) {
h.readAtMu.Lock()
defer h.readAtMu.Unlock()
// Save current position
cur, err := h.Seek(0, io.SeekCurrent)
if err != nil {
return 0, err
}
// Seek to requested offset
if _, err = h.Seek(off, io.SeekStart); err != nil {
return 0, err
}
// Restore position on exit
defer func() {
if _, seekErr := h.Seek(cur, io.SeekStart); seekErr != nil && err == nil {
err = seekErr
}
}()
// Fill p fully unless EOF
return h.Read(p)
}
// Seek sets the offset for the next Read or Write to offset, interpreted
// according to whence: SeekStart means relative to the start of the file,
// SeekCurrent means relative to the current offset, and SeekEnd means relative

View File

@@ -18,6 +18,7 @@ import (
// check interfaces
var (
_ io.ReadSeekCloser = (*ReOpen)(nil)
_ io.ReaderAt = (*ReOpen)(nil)
_ pool.DelayAccountinger = (*ReOpen)(nil)
)
@@ -137,6 +138,16 @@ func TestReOpen(t *testing.T) {
return rc, src, err
}
// Reset the start after a seek, taking into account the offset
setWantStart := func(src *reOpenTestObject, x int64) {
src.wantStart = x
if rangeOption != nil {
src.wantStart += rangeOption.Start
} else if seekOption != nil {
src.wantStart += seekOption.Offset
}
}
t.Run("Basics", func(t *testing.T) {
// open
h, _, err := testReOpen(nil, 10)
@@ -215,6 +226,37 @@ func TestReOpen(t *testing.T) {
assert.Equal(t, errFileClosed, h.Close())
})
t.Run("ReadAt", func(t *testing.T) {
// open
h, src, err := testReOpen([]int64{2, 1, 3}, 10)
assert.NoError(t, err)
buf := make([]byte, 5)
// Read at 0
n, err := h.ReadAt(buf, 0)
require.NoError(t, err)
assert.Equal(t, 5, n)
assert.Equal(t, expectedRead[:n], buf[:n])
// Read at 1
setWantStart(src, 1)
n, err = h.ReadAt(buf[:3], 1)
require.NoError(t, err)
assert.Equal(t, 3, n)
assert.Equal(t, expectedRead[1:n+1], buf[:n])
// check position unchanged
pos, err := h.Seek(0, io.SeekCurrent)
require.NoError(t, err)
assert.Equal(t, int64(0), pos)
// check close
assert.NoError(t, h.Close())
_, err = h.Seek(0, io.SeekCurrent)
assert.Equal(t, errFileClosed, err)
})
t.Run("Seek", func(t *testing.T) {
// open
h, src, err := testReOpen([]int64{2, 1, 3}, 10)
@@ -267,18 +309,8 @@ func TestReOpen(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, 2, int(pos))
// Reset the start after a seek, taking into account the offset
setWantStart := func(x int64) {
src.wantStart = x
if rangeOption != nil {
src.wantStart += rangeOption.Start
} else if seekOption != nil {
src.wantStart += seekOption.Offset
}
}
// check read
setWantStart(2)
setWantStart(src, 2)
n, err = h.Read(dst)
assert.Nil(t, err)
assert.Equal(t, 5, n)
@@ -305,7 +337,7 @@ func TestReOpen(t *testing.T) {
// check read
dst = make([]byte, 3)
setWantStart(int64(len(expectedRead) - 3))
setWantStart(src, int64(len(expectedRead)-3))
n, err = h.Read(dst)
assert.Nil(t, err)
assert.Equal(t, 3, n)