github.com/artpar/rclone@v1.67.3/backend/cache/object.go (about) 1 //go:build !plan9 && !js 2 3 package cache 4 5 import ( 6 "context" 7 "fmt" 8 "io" 9 "path" 10 "sync" 11 "time" 12 13 "github.com/artpar/rclone/fs" 14 "github.com/artpar/rclone/fs/hash" 15 "github.com/artpar/rclone/lib/readers" 16 ) 17 18 const ( 19 objectInCache = "Object" 20 objectPendingUpload = "TempObject" 21 ) 22 23 // Object is a generic file like object that stores basic information about it 24 type Object struct { 25 fs.Object `json:"-"` 26 27 ParentFs fs.Fs `json:"-"` // parent fs 28 CacheFs *Fs `json:"-"` // cache fs 29 Name string `json:"name"` // name of the directory 30 Dir string `json:"dir"` // abs path of the object 31 CacheModTime int64 `json:"modTime"` // modification or creation time - IsZero for unknown 32 CacheSize int64 `json:"size"` // size of directory and contents or -1 if unknown 33 CacheStorable bool `json:"storable"` // says whether this object can be stored 34 CacheType string `json:"cacheType"` 35 CacheTs time.Time `json:"cacheTs"` 36 cacheHashesMu sync.Mutex 37 CacheHashes map[hash.Type]string // all supported hashes cached 38 39 refreshMutex sync.Mutex 40 } 41 42 // NewObject builds one from a generic fs.Object 43 func NewObject(f *Fs, remote string) *Object { 44 fullRemote := path.Join(f.Root(), remote) 45 dir, name := path.Split(fullRemote) 46 47 cacheType := objectInCache 48 parentFs := f.UnWrap() 49 if f.opt.TempWritePath != "" { 50 _, err := f.cache.SearchPendingUpload(fullRemote) 51 if err == nil { // queued for upload 52 cacheType = objectPendingUpload 53 parentFs = f.tempFs 54 fs.Debugf(fullRemote, "pending upload found") 55 } 56 } 57 58 co := &Object{ 59 ParentFs: parentFs, 60 CacheFs: f, 61 Name: cleanPath(name), 62 Dir: cleanPath(dir), 63 CacheModTime: time.Now().UnixNano(), 64 CacheSize: 0, 65 CacheStorable: false, 66 CacheType: cacheType, 67 CacheTs: time.Now(), 68 } 69 return co 70 } 71 72 // ObjectFromOriginal builds one from a generic fs.Object 73 func ObjectFromOriginal(ctx context.Context, f *Fs, o fs.Object) *Object { 74 var co *Object 75 fullRemote := cleanPath(path.Join(f.Root(), o.Remote())) 76 dir, name := path.Split(fullRemote) 77 78 cacheType := objectInCache 79 parentFs := f.UnWrap() 80 if f.opt.TempWritePath != "" { 81 _, err := f.cache.SearchPendingUpload(fullRemote) 82 if err == nil { // queued for upload 83 cacheType = objectPendingUpload 84 parentFs = f.tempFs 85 fs.Debugf(fullRemote, "pending upload found") 86 } 87 } 88 89 co = &Object{ 90 ParentFs: parentFs, 91 CacheFs: f, 92 Name: cleanPath(name), 93 Dir: cleanPath(dir), 94 CacheType: cacheType, 95 CacheTs: time.Now(), 96 } 97 co.updateData(ctx, o) 98 return co 99 } 100 101 func (o *Object) updateData(ctx context.Context, source fs.Object) { 102 o.Object = source 103 o.CacheModTime = source.ModTime(ctx).UnixNano() 104 o.CacheSize = source.Size() 105 o.CacheStorable = source.Storable() 106 o.CacheTs = time.Now() 107 o.cacheHashesMu.Lock() 108 o.CacheHashes = make(map[hash.Type]string) 109 o.cacheHashesMu.Unlock() 110 } 111 112 // Fs returns its FS info 113 func (o *Object) Fs() fs.Info { 114 return o.CacheFs 115 } 116 117 // String returns a human friendly name for this object 118 func (o *Object) String() string { 119 if o == nil { 120 return "<nil>" 121 } 122 return o.Remote() 123 } 124 125 // Remote returns the remote path 126 func (o *Object) Remote() string { 127 p := path.Join(o.Dir, o.Name) 128 return o.CacheFs.cleanRootFromPath(p) 129 } 130 131 // abs returns the absolute path to the object 132 func (o *Object) abs() string { 133 return path.Join(o.Dir, o.Name) 134 } 135 136 // ModTime returns the cached ModTime 137 func (o *Object) ModTime(ctx context.Context) time.Time { 138 _ = o.refresh(ctx) 139 return time.Unix(0, o.CacheModTime) 140 } 141 142 // Size returns the cached Size 143 func (o *Object) Size() int64 { 144 _ = o.refresh(context.TODO()) 145 return o.CacheSize 146 } 147 148 // Storable returns the cached Storable 149 func (o *Object) Storable() bool { 150 _ = o.refresh(context.TODO()) 151 return o.CacheStorable 152 } 153 154 // refresh will check if the object info is expired and request the info from source if it is 155 // all these conditions must be true to ignore a refresh 156 // 1. cache ts didn't expire yet 157 // 2. is not pending a notification from the wrapped fs 158 func (o *Object) refresh(ctx context.Context) error { 159 isNotified := o.CacheFs.isNotifiedRemote(o.Remote()) 160 isExpired := time.Now().After(o.CacheTs.Add(time.Duration(o.CacheFs.opt.InfoAge))) 161 if !isExpired && !isNotified { 162 return nil 163 } 164 165 return o.refreshFromSource(ctx, true) 166 } 167 168 // refreshFromSource requests the original FS for the object in case it comes from a cached entry 169 func (o *Object) refreshFromSource(ctx context.Context, force bool) error { 170 o.refreshMutex.Lock() 171 defer o.refreshMutex.Unlock() 172 var err error 173 var liveObject fs.Object 174 175 if o.Object != nil && !force { 176 return nil 177 } 178 if o.isTempFile() { 179 liveObject, err = o.ParentFs.NewObject(ctx, o.Remote()) 180 if err != nil { 181 err = fmt.Errorf("in parent fs %v: %w", o.ParentFs, err) 182 } 183 } else { 184 liveObject, err = o.CacheFs.Fs.NewObject(ctx, o.Remote()) 185 if err != nil { 186 err = fmt.Errorf("in cache fs %v: %w", o.CacheFs.Fs, err) 187 } 188 } 189 if err != nil { 190 fs.Errorf(o, "error refreshing object in : %v", err) 191 return err 192 } 193 o.updateData(ctx, liveObject) 194 o.persist() 195 196 return nil 197 } 198 199 // SetModTime sets the ModTime of this object 200 func (o *Object) SetModTime(ctx context.Context, t time.Time) error { 201 if err := o.refreshFromSource(ctx, false); err != nil { 202 return err 203 } 204 205 err := o.Object.SetModTime(ctx, t) 206 if err != nil { 207 return err 208 } 209 210 o.CacheModTime = t.UnixNano() 211 o.persist() 212 fs.Debugf(o, "updated ModTime: %v", t) 213 214 return nil 215 } 216 217 // Open is used to request a specific part of the file using fs.RangeOption 218 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) { 219 var err error 220 221 if o.Object == nil { 222 err = o.refreshFromSource(ctx, true) 223 } else { 224 err = o.refresh(ctx) 225 } 226 if err != nil { 227 return nil, err 228 } 229 230 cacheReader := NewObjectHandle(ctx, o, o.CacheFs) 231 var offset, limit int64 = 0, -1 232 for _, option := range options { 233 switch x := option.(type) { 234 case *fs.SeekOption: 235 offset = x.Offset 236 case *fs.RangeOption: 237 offset, limit = x.Decode(o.Size()) 238 } 239 _, err = cacheReader.Seek(offset, io.SeekStart) 240 if err != nil { 241 return nil, err 242 } 243 } 244 245 return readers.NewLimitedReadCloser(cacheReader, limit), nil 246 } 247 248 // Update will change the object data 249 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { 250 if err := o.refreshFromSource(ctx, false); err != nil { 251 return err 252 } 253 // pause background uploads if active 254 if o.CacheFs.opt.TempWritePath != "" { 255 o.CacheFs.backgroundRunner.pause() 256 defer o.CacheFs.backgroundRunner.play() 257 // don't allow started uploads 258 if o.isTempFile() && o.tempFileStartedUpload() { 259 return fmt.Errorf("%v is currently uploading, can't update", o) 260 } 261 } 262 fs.Debugf(o, "updating object contents with size %v", src.Size()) 263 264 // FIXME use reliable upload 265 err := o.Object.Update(ctx, in, src, options...) 266 if err != nil { 267 fs.Errorf(o, "error updating source: %v", err) 268 return err 269 } 270 271 // deleting cached chunks and info to be replaced with new ones 272 _ = o.CacheFs.cache.RemoveObject(o.abs()) 273 // advertise to ChangeNotify if wrapped doesn't do that 274 o.CacheFs.notifyChangeUpstreamIfNeeded(o.Remote(), fs.EntryObject) 275 276 o.CacheModTime = src.ModTime(ctx).UnixNano() 277 o.CacheSize = src.Size() 278 o.cacheHashesMu.Lock() 279 o.CacheHashes = make(map[hash.Type]string) 280 o.cacheHashesMu.Unlock() 281 o.CacheTs = time.Now() 282 o.persist() 283 284 return nil 285 } 286 287 // Remove deletes the object from both the cache and the source 288 func (o *Object) Remove(ctx context.Context) error { 289 if err := o.refreshFromSource(ctx, false); err != nil { 290 return err 291 } 292 // pause background uploads if active 293 if o.CacheFs.opt.TempWritePath != "" { 294 o.CacheFs.backgroundRunner.pause() 295 defer o.CacheFs.backgroundRunner.play() 296 // don't allow started uploads 297 if o.isTempFile() && o.tempFileStartedUpload() { 298 return fmt.Errorf("%v is currently uploading, can't delete", o) 299 } 300 } 301 err := o.Object.Remove(ctx) 302 if err != nil { 303 return err 304 } 305 306 fs.Debugf(o, "removing object") 307 _ = o.CacheFs.cache.RemoveObject(o.abs()) 308 _ = o.CacheFs.cache.removePendingUpload(o.abs()) 309 parentCd := NewDirectory(o.CacheFs, cleanPath(path.Dir(o.Remote()))) 310 _ = o.CacheFs.cache.ExpireDir(parentCd) 311 // advertise to ChangeNotify if wrapped doesn't do that 312 o.CacheFs.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory) 313 314 return nil 315 } 316 317 // Hash requests a hash of the object and stores in the cache 318 // since it might or might not be called, this is lazy loaded 319 func (o *Object) Hash(ctx context.Context, ht hash.Type) (string, error) { 320 _ = o.refresh(ctx) 321 o.cacheHashesMu.Lock() 322 if o.CacheHashes == nil { 323 o.CacheHashes = make(map[hash.Type]string) 324 } 325 cachedHash, found := o.CacheHashes[ht] 326 o.cacheHashesMu.Unlock() 327 if found { 328 return cachedHash, nil 329 } 330 if err := o.refreshFromSource(ctx, false); err != nil { 331 return "", err 332 } 333 liveHash, err := o.Object.Hash(ctx, ht) 334 if err != nil { 335 return "", err 336 } 337 o.cacheHashesMu.Lock() 338 o.CacheHashes[ht] = liveHash 339 o.cacheHashesMu.Unlock() 340 341 o.persist() 342 fs.Debugf(o, "object hash cached: %v", liveHash) 343 344 return liveHash, nil 345 } 346 347 // persist adds this object to the persistent cache 348 func (o *Object) persist() *Object { 349 err := o.CacheFs.cache.AddObject(o) 350 if err != nil { 351 fs.Errorf(o, "failed to cache object: %v", err) 352 } 353 return o 354 } 355 356 func (o *Object) isTempFile() bool { 357 _, err := o.CacheFs.cache.SearchPendingUpload(o.abs()) 358 if err != nil { 359 o.CacheType = objectInCache 360 return false 361 } 362 363 o.CacheType = objectPendingUpload 364 return true 365 } 366 367 func (o *Object) tempFileStartedUpload() bool { 368 started, err := o.CacheFs.cache.SearchPendingUpload(o.abs()) 369 if err != nil { 370 return false 371 } 372 return started 373 } 374 375 // UnWrap returns the Object that this Object is wrapping or 376 // nil if it isn't wrapping anything 377 func (o *Object) UnWrap() fs.Object { 378 return o.Object 379 } 380 381 var ( 382 _ fs.Object = (*Object)(nil) 383 _ fs.ObjectUnWrapper = (*Object)(nil) 384 )