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 }