2018-01-12 17:30:54 +01:00
|
|
|
// Package mockobject provides a mock object which can be created from a string
|
|
|
|
package mockobject
|
|
|
|
|
|
|
|
import (
|
2018-02-18 15:10:15 +01:00
|
|
|
"bytes"
|
2019-06-17 10:34:30 +02:00
|
|
|
"context"
|
2018-01-12 17:30:54 +01:00
|
|
|
"errors"
|
2018-02-18 15:10:15 +01:00
|
|
|
"fmt"
|
2018-01-12 17:30:54 +01:00
|
|
|
"io"
|
|
|
|
"time"
|
|
|
|
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fs/hash"
|
2018-01-12 17:30:54 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var errNotImpl = errors.New("not implemented")
|
|
|
|
|
|
|
|
// Object is a mock fs.Object useful for testing
|
|
|
|
type Object string
|
|
|
|
|
2018-02-18 15:10:15 +01:00
|
|
|
// New returns mock fs.Object useful for testing
|
|
|
|
func New(name string) Object {
|
|
|
|
return Object(name)
|
|
|
|
}
|
|
|
|
|
2018-01-12 17:30:54 +01:00
|
|
|
// String returns a description of the Object
|
|
|
|
func (o Object) String() string {
|
|
|
|
return string(o)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fs returns read only access to the Fs that this object is part of
|
|
|
|
func (o Object) Fs() fs.Info {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remote returns the remote path
|
|
|
|
func (o Object) Remote() string {
|
|
|
|
return string(o)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hash returns the selected checksum of the file
|
|
|
|
// If no checksum is available it returns ""
|
2019-06-17 10:34:30 +02:00
|
|
|
func (o Object) Hash(ctx context.Context, t hash.Type) (string, error) {
|
2018-01-12 17:30:54 +01:00
|
|
|
return "", errNotImpl
|
|
|
|
}
|
|
|
|
|
|
|
|
// ModTime returns the modification date of the file
|
|
|
|
// It should return a best guess if one isn't available
|
2019-06-17 10:34:30 +02:00
|
|
|
func (o Object) ModTime(ctx context.Context) (t time.Time) {
|
2018-01-12 17:30:54 +01:00
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size returns the size of the file
|
|
|
|
func (o Object) Size() int64 { return 0 }
|
|
|
|
|
|
|
|
// Storable says whether this object can be stored
|
|
|
|
func (o Object) Storable() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetModTime sets the metadata on the object to set the modification date
|
2019-06-17 10:34:30 +02:00
|
|
|
func (o Object) SetModTime(ctx context.Context, t time.Time) error {
|
2018-01-12 17:30:54 +01:00
|
|
|
return errNotImpl
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open opens the file for read. Call Close() on the returned io.ReadCloser
|
2019-06-17 10:34:30 +02:00
|
|
|
func (o Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
|
2018-01-12 17:30:54 +01:00
|
|
|
return nil, errNotImpl
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update in to the object with the modTime given of the given size
|
2019-06-17 10:34:30 +02:00
|
|
|
func (o Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
|
2018-01-12 17:30:54 +01:00
|
|
|
return errNotImpl
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove this object
|
2019-06-17 10:34:30 +02:00
|
|
|
func (o Object) Remove(ctx context.Context) error {
|
2018-01-12 17:30:54 +01:00
|
|
|
return errNotImpl
|
|
|
|
}
|
2018-02-18 15:10:15 +01:00
|
|
|
|
|
|
|
// SeekMode specifies the optional Seek interface for the ReadCloser returned by Open
|
|
|
|
type SeekMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// SeekModeNone specifies no seek interface
|
|
|
|
SeekModeNone SeekMode = iota
|
|
|
|
// SeekModeRegular specifies the regular io.Seek interface
|
|
|
|
SeekModeRegular
|
|
|
|
// SeekModeRange specifies the fs.RangeSeek interface
|
|
|
|
SeekModeRange
|
|
|
|
)
|
|
|
|
|
|
|
|
// SeekModes contains all valid SeekMode's
|
|
|
|
var SeekModes = []SeekMode{SeekModeNone, SeekModeRegular, SeekModeRange}
|
|
|
|
|
2019-08-13 14:20:37 +02:00
|
|
|
// ContentMockObject mocks an fs.Object and has content
|
|
|
|
type ContentMockObject struct {
|
2018-02-18 15:10:15 +01:00
|
|
|
Object
|
2019-09-14 14:07:01 +02:00
|
|
|
content []byte
|
|
|
|
seekMode SeekMode
|
|
|
|
f fs.Fs
|
|
|
|
unknownSize bool
|
2018-02-18 15:10:15 +01:00
|
|
|
}
|
|
|
|
|
2020-05-20 12:39:20 +02:00
|
|
|
// WithContent returns an fs.Object with the given content.
|
2019-08-13 14:20:37 +02:00
|
|
|
func (o Object) WithContent(content []byte, mode SeekMode) *ContentMockObject {
|
|
|
|
return &ContentMockObject{
|
2018-02-18 15:10:15 +01:00
|
|
|
Object: o,
|
|
|
|
content: content,
|
|
|
|
seekMode: mode,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-13 14:20:37 +02:00
|
|
|
// SetFs sets the return value of the Fs() call
|
|
|
|
func (o *ContentMockObject) SetFs(f fs.Fs) {
|
|
|
|
o.f = f
|
|
|
|
}
|
|
|
|
|
2019-09-14 14:07:01 +02:00
|
|
|
// SetUnknownSize makes the mock object return -1 for size if true
|
|
|
|
func (o *ContentMockObject) SetUnknownSize(unknownSize bool) {
|
|
|
|
o.unknownSize = true
|
|
|
|
}
|
|
|
|
|
2019-08-13 14:20:37 +02:00
|
|
|
// Fs returns read only access to the Fs that this object is part of
|
|
|
|
//
|
|
|
|
// This is nil unless SetFs has been called
|
|
|
|
func (o *ContentMockObject) Fs() fs.Info {
|
|
|
|
return o.f
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open opens the file for read. Call Close() on the returned io.ReadCloser
|
|
|
|
func (o *ContentMockObject) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
|
2019-09-14 14:07:01 +02:00
|
|
|
size := int64(len(o.content))
|
2018-02-18 15:10:15 +01:00
|
|
|
var offset, limit int64 = 0, -1
|
|
|
|
for _, option := range options {
|
|
|
|
switch x := option.(type) {
|
|
|
|
case *fs.SeekOption:
|
|
|
|
offset = x.Offset
|
|
|
|
case *fs.RangeOption:
|
2019-09-14 14:07:01 +02:00
|
|
|
offset, limit = x.Decode(size)
|
2018-02-18 15:10:15 +01:00
|
|
|
default:
|
|
|
|
if option.Mandatory() {
|
|
|
|
return nil, fmt.Errorf("Unsupported mandatory option: %v", option)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 14:07:01 +02:00
|
|
|
if limit == -1 || offset+limit > size {
|
|
|
|
limit = size - offset
|
2018-02-18 15:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var r *bytes.Reader
|
|
|
|
if o.seekMode == SeekModeNone {
|
|
|
|
r = bytes.NewReader(o.content[offset : offset+limit])
|
|
|
|
} else {
|
|
|
|
r = bytes.NewReader(o.content)
|
|
|
|
_, err := r.Seek(offset, io.SeekStart)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch o.seekMode {
|
|
|
|
case SeekModeNone:
|
|
|
|
return &readCloser{r}, nil
|
|
|
|
case SeekModeRegular:
|
|
|
|
return &readSeekCloser{r}, nil
|
|
|
|
case SeekModeRange:
|
|
|
|
return &readRangeSeekCloser{r}, nil
|
|
|
|
default:
|
|
|
|
return nil, errors.New(o.seekMode.String())
|
|
|
|
}
|
|
|
|
}
|
2019-08-13 14:20:37 +02:00
|
|
|
|
|
|
|
// Size returns the size of the file
|
|
|
|
func (o *ContentMockObject) Size() int64 {
|
2019-09-14 14:07:01 +02:00
|
|
|
if o.unknownSize {
|
|
|
|
return -1
|
|
|
|
}
|
2018-02-18 15:10:15 +01:00
|
|
|
return int64(len(o.content))
|
|
|
|
}
|
|
|
|
|
2020-06-19 12:02:25 +02:00
|
|
|
// Hash returns the selected checksum of the file
|
|
|
|
// If no checksum is available it returns ""
|
|
|
|
func (o *ContentMockObject) Hash(ctx context.Context, t hash.Type) (string, error) {
|
|
|
|
hasher, err := hash.NewMultiHasherTypes(hash.NewHashSet(t))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
_, err = hasher.Write(o.content)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return hasher.Sums()[t], nil
|
|
|
|
}
|
|
|
|
|
2018-02-18 15:10:15 +01:00
|
|
|
type readCloser struct{ io.Reader }
|
|
|
|
|
|
|
|
func (r *readCloser) Close() error { return nil }
|
|
|
|
|
|
|
|
type readSeekCloser struct{ io.ReadSeeker }
|
|
|
|
|
|
|
|
func (r *readSeekCloser) Close() error { return nil }
|
|
|
|
|
|
|
|
type readRangeSeekCloser struct{ io.ReadSeeker }
|
|
|
|
|
|
|
|
func (r *readRangeSeekCloser) RangeSeek(offset int64, whence int, length int64) (int64, error) {
|
|
|
|
return r.ReadSeeker.Seek(offset, whence)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *readRangeSeekCloser) Close() error { return nil }
|
|
|
|
|
|
|
|
func (m SeekMode) String() string {
|
|
|
|
switch m {
|
|
|
|
case SeekModeNone:
|
|
|
|
return "SeekModeNone"
|
|
|
|
case SeekModeRegular:
|
|
|
|
return "SeekModeRegular"
|
|
|
|
case SeekModeRange:
|
|
|
|
return "SeekModeRange"
|
|
|
|
default:
|
|
|
|
return fmt.Sprintf("SeekModeInvalid(%d)", m)
|
|
|
|
}
|
|
|
|
}
|