github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/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 97 type ContentMockObject struct { 98 Object 99 content []byte 100 seekMode SeekMode 101 f fs.Fs 102 unknownSize bool 103 } 104 105 // WithContent returns a fs.Object with the given content. 106 func (o Object) WithContent(content []byte, mode SeekMode) *ContentMockObject { 107 return &ContentMockObject{ 108 Object: o, 109 content: content, 110 seekMode: mode, 111 } 112 } 113 114 // SetFs sets the return value of the Fs() call 115 func (o *ContentMockObject) SetFs(f fs.Fs) { 116 o.f = f 117 } 118 119 // SetUnknownSize makes the mock object return -1 for size if true 120 func (o *ContentMockObject) SetUnknownSize(unknownSize bool) { 121 o.unknownSize = true 122 } 123 124 // Fs returns read only access to the Fs that this object is part of 125 // 126 // This is nil unless SetFs has been called 127 func (o *ContentMockObject) Fs() fs.Info { 128 return o.f 129 } 130 131 // Open opens the file for read. Call Close() on the returned io.ReadCloser 132 func (o *ContentMockObject) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) { 133 size := int64(len(o.content)) 134 var offset, limit int64 = 0, -1 135 for _, option := range options { 136 switch x := option.(type) { 137 case *fs.SeekOption: 138 offset = x.Offset 139 case *fs.RangeOption: 140 offset, limit = x.Decode(size) 141 default: 142 if option.Mandatory() { 143 return nil, fmt.Errorf("Unsupported mandatory option: %v", option) 144 } 145 } 146 } 147 if limit == -1 || offset+limit > size { 148 limit = size - offset 149 } 150 151 var r *bytes.Reader 152 if o.seekMode == SeekModeNone { 153 r = bytes.NewReader(o.content[offset : offset+limit]) 154 } else { 155 r = bytes.NewReader(o.content) 156 _, err := r.Seek(offset, io.SeekStart) 157 if err != nil { 158 return nil, err 159 } 160 } 161 switch o.seekMode { 162 case SeekModeNone: 163 return &readCloser{r}, nil 164 case SeekModeRegular: 165 return &readSeekCloser{r}, nil 166 case SeekModeRange: 167 return &readRangeSeekCloser{r}, nil 168 default: 169 return nil, errors.New(o.seekMode.String()) 170 } 171 } 172 173 // Size returns the size of the file 174 func (o *ContentMockObject) Size() int64 { 175 if o.unknownSize { 176 return -1 177 } 178 return int64(len(o.content)) 179 } 180 181 type readCloser struct{ io.Reader } 182 183 func (r *readCloser) Close() error { return nil } 184 185 type readSeekCloser struct{ io.ReadSeeker } 186 187 func (r *readSeekCloser) Close() error { return nil } 188 189 type readRangeSeekCloser struct{ io.ReadSeeker } 190 191 func (r *readRangeSeekCloser) RangeSeek(offset int64, whence int, length int64) (int64, error) { 192 return r.ReadSeeker.Seek(offset, whence) 193 } 194 195 func (r *readRangeSeekCloser) Close() error { return nil } 196 197 func (m SeekMode) String() string { 198 switch m { 199 case SeekModeNone: 200 return "SeekModeNone" 201 case SeekModeRegular: 202 return "SeekModeRegular" 203 case SeekModeRange: 204 return "SeekModeRange" 205 default: 206 return fmt.Sprintf("SeekModeInvalid(%d)", m) 207 } 208 }