github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fstest/mockobject/mockobject.go (about)

     1  // Package mockobject provides a mock object which can be created from a string
     2  package mockobject
     3  
     4  import (
     5  	"bytes"
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"time"
    11  
    12  	"github.com/rclone/rclone/fs"
    13  	"github.com/rclone/rclone/fs/hash"
    14  )
    15  
    16  var errNotImpl = errors.New("not implemented")
    17  
    18  // Object is a mock fs.Object useful for testing
    19  type Object string
    20  
    21  // New returns mock fs.Object useful for testing
    22  func New(name string) Object {
    23  	return Object(name)
    24  }
    25  
    26  // String returns a description of the Object
    27  func (o Object) String() string {
    28  	return string(o)
    29  }
    30  
    31  // Fs returns read only access to the Fs that this object is part of
    32  func (o Object) Fs() fs.Info {
    33  	return nil
    34  }
    35  
    36  // Remote returns the remote path
    37  func (o Object) Remote() string {
    38  	return string(o)
    39  }
    40  
    41  // Hash returns the selected checksum of the file
    42  // If no checksum is available it returns ""
    43  func (o Object) Hash(ctx context.Context, t hash.Type) (string, error) {
    44  	return "", errNotImpl
    45  }
    46  
    47  // ModTime returns the modification date of the file
    48  // It should return a best guess if one isn't available
    49  func (o Object) ModTime(ctx context.Context) (t time.Time) {
    50  	return t
    51  }
    52  
    53  // Size returns the size of the file
    54  func (o Object) Size() int64 { return 0 }
    55  
    56  // Storable says whether this object can be stored
    57  func (o Object) Storable() bool {
    58  	return true
    59  }
    60  
    61  // SetModTime sets the metadata on the object to set the modification date
    62  func (o Object) SetModTime(ctx context.Context, t time.Time) error {
    63  	return errNotImpl
    64  }
    65  
    66  // Open opens the file for read.  Call Close() on the returned io.ReadCloser
    67  func (o Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
    68  	return nil, errNotImpl
    69  }
    70  
    71  // Update in to the object with the modTime given of the given size
    72  func (o Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
    73  	return errNotImpl
    74  }
    75  
    76  // Remove this object
    77  func (o Object) Remove(ctx context.Context) error {
    78  	return errNotImpl
    79  }
    80  
    81  // SeekMode specifies the optional Seek interface for the ReadCloser returned by Open
    82  type SeekMode int
    83  
    84  const (
    85  	// SeekModeNone specifies no seek interface
    86  	SeekModeNone SeekMode = iota
    87  	// SeekModeRegular specifies the regular io.Seek interface
    88  	SeekModeRegular
    89  	// SeekModeRange specifies the fs.RangeSeek interface
    90  	SeekModeRange
    91  )
    92  
    93  // SeekModes contains all valid SeekMode's
    94  var SeekModes = []SeekMode{SeekModeNone, SeekModeRegular, SeekModeRange}
    95  
    96  // ContentMockObject mocks an fs.Object and has content, mod time
    97  type ContentMockObject struct {
    98  	Object
    99  	content     []byte
   100  	seekMode    SeekMode
   101  	f           fs.Fs
   102  	unknownSize bool
   103  	modTime     time.Time
   104  }
   105  
   106  // WithContent returns an fs.Object with the given content.
   107  func (o Object) WithContent(content []byte, mode SeekMode) *ContentMockObject {
   108  	return &ContentMockObject{
   109  		Object:   o,
   110  		content:  content,
   111  		seekMode: mode,
   112  	}
   113  }
   114  
   115  // SetFs sets the return value of the Fs() call
   116  func (o *ContentMockObject) SetFs(f fs.Fs) {
   117  	o.f = f
   118  }
   119  
   120  // SetUnknownSize makes the mock object return -1 for size if true
   121  func (o *ContentMockObject) SetUnknownSize(unknownSize bool) {
   122  	o.unknownSize = unknownSize
   123  }
   124  
   125  // Fs returns read only access to the Fs that this object is part of
   126  //
   127  // This is nil unless SetFs has been called
   128  func (o *ContentMockObject) Fs() fs.Info {
   129  	return o.f
   130  }
   131  
   132  // Open opens the file for read.  Call Close() on the returned io.ReadCloser
   133  func (o *ContentMockObject) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
   134  	size := int64(len(o.content))
   135  	var offset, limit int64 = 0, -1
   136  	for _, option := range options {
   137  		switch x := option.(type) {
   138  		case *fs.SeekOption:
   139  			offset = x.Offset
   140  		case *fs.RangeOption:
   141  			offset, limit = x.Decode(size)
   142  		default:
   143  			if option.Mandatory() {
   144  				return nil, fmt.Errorf("unsupported mandatory option: %v", option)
   145  			}
   146  		}
   147  	}
   148  	if limit == -1 || offset+limit > size {
   149  		limit = size - offset
   150  	}
   151  
   152  	var r *bytes.Reader
   153  	if o.seekMode == SeekModeNone {
   154  		r = bytes.NewReader(o.content[offset : offset+limit])
   155  	} else {
   156  		r = bytes.NewReader(o.content)
   157  		_, err := r.Seek(offset, io.SeekStart)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  	}
   162  	switch o.seekMode {
   163  	case SeekModeNone:
   164  		return &readCloser{r}, nil
   165  	case SeekModeRegular:
   166  		return &readSeekCloser{r}, nil
   167  	case SeekModeRange:
   168  		return &readRangeSeekCloser{r}, nil
   169  	default:
   170  		return nil, errors.New(o.seekMode.String())
   171  	}
   172  }
   173  
   174  // Size returns the size of the file
   175  func (o *ContentMockObject) Size() int64 {
   176  	if o.unknownSize {
   177  		return -1
   178  	}
   179  	return int64(len(o.content))
   180  }
   181  
   182  // Hash returns the selected checksum of the file
   183  // If no checksum is available it returns ""
   184  func (o *ContentMockObject) Hash(ctx context.Context, t hash.Type) (string, error) {
   185  	hasher, err := hash.NewMultiHasherTypes(hash.NewHashSet(t))
   186  	if err != nil {
   187  		return "", err
   188  	}
   189  	_, err = hasher.Write(o.content)
   190  	if err != nil {
   191  		return "", err
   192  	}
   193  	return hasher.Sums()[t], nil
   194  }
   195  
   196  // ModTime returns the modification date of the file
   197  // It should return a best guess if one isn't available
   198  func (o *ContentMockObject) ModTime(ctx context.Context) time.Time {
   199  	return o.modTime
   200  }
   201  
   202  // SetModTime sets the metadata on the object to set the modification date
   203  func (o *ContentMockObject) SetModTime(ctx context.Context, t time.Time) error {
   204  	o.modTime = t
   205  	return nil
   206  }
   207  
   208  type readCloser struct{ io.Reader }
   209  
   210  func (r *readCloser) Close() error { return nil }
   211  
   212  type readSeekCloser struct{ io.ReadSeeker }
   213  
   214  func (r *readSeekCloser) Close() error { return nil }
   215  
   216  type readRangeSeekCloser struct{ io.ReadSeeker }
   217  
   218  func (r *readRangeSeekCloser) RangeSeek(offset int64, whence int, length int64) (int64, error) {
   219  	return r.ReadSeeker.Seek(offset, whence)
   220  }
   221  
   222  func (r *readRangeSeekCloser) Close() error { return nil }
   223  
   224  func (m SeekMode) String() string {
   225  	switch m {
   226  	case SeekModeNone:
   227  		return "SeekModeNone"
   228  	case SeekModeRegular:
   229  		return "SeekModeRegular"
   230  	case SeekModeRange:
   231  		return "SeekModeRange"
   232  	default:
   233  		return fmt.Sprintf("SeekModeInvalid(%d)", m)
   234  	}
   235  }