// Copyright (C) 2022 Marius Schellenberger package fs import ( "cachefs/pkg/provider" "context" "errors" "io" "os" "github.com/go-logr/logr" ) type File struct { log logr.Logger f provider.File md *Metadata offset int64 offline bool } type preload struct { f *File skipped int64 written int64 ctx context.Context } func (p *preload) Read(data []byte) (n int, err error) { select { case <-p.ctx.Done(): return 0, p.ctx.Err() default: } if p.f.offset >= p.f.size() { return 0, io.EOF } n = len(data) if p.f.hasChunk(n) { p.skipped++ _, err = p.f.Seek(int64(n), io.SeekCurrent) return } n, err = p.f.readToCache(data) p.written++ return } func (f *File) Preload(ctx context.Context, unlock func()) { log := f.log defer f.Close() defer unlock() if f.offline { log.V(2).Error(errors.New("no preload in offline mode"), "error preloading file") return } if f.md.FullyCached() { log.V(2).Info("skipped preload for fully cached file") return } log.V(2).Info("preload started") p := &preload{f: f, ctx: ctx} _, err := io.Copy(Discard, p) if err == context.Canceled { log.V(2).Info("preload canceled", "skipped", p.skipped, "written", p.written) return } if err != nil && err != io.EOF { log.Error(err, "error preloading file") } err = f.md.Close() if err != nil { log.Error(err, "error closing cache file") } log.V(2).Info("preload finished", "skipped", p.skipped, "written", p.written) } func (f *File) Read(p []byte) (n int, err error) { log := f.log if f.hasChunk(len(p)) { n, err = f.md.ReadAt(p, f.offset) if err == io.EOF { return } if err != nil { if !IsIOErr(err) { log.Error(err, "error reading cache file") return } n, err = f.readSource(p) if err != nil { log.Error(err, "error reading source file") return } } _, err = f.Seek(int64(n), io.SeekCurrent) if err != nil { log.Error(err, "error seeking source file") } return } return f.readToCache(p) } func (f *File) size() int64 { return f.md.Size } func (f *File) hasChunk(n int) bool { return f.md.HasChunk(f.offset, n) } func (f *File) readSource(p []byte) (n int, err error) { if f.offline { return 0, io.EOF } return f.f.Read(p) } func (f *File) readToCache(p []byte) (n int, err error) { log := f.log n, err = f.readSource(p) if n > 0 { if n, err = f.md.WriteAt(p[:n], f.offset); err != nil { log.Error(err, "error writing cache file") return } } if err != nil && err != io.EOF { log.Error(err, "error reading source file") return } f.md.AddChunk(f.offset, n) f.offset += int64(n) if n > 0 && n < len(p) { _, err = f.Seek(f.offset, io.SeekStart) } return } func (f *File) Seek(offset int64, whence int) (n int64, err error) { if !f.offline { n, err = f.f.Seek(offset, whence) if err != nil { f.log.Error(err, "error seeking source file") return } } switch whence { case io.SeekStart: f.offset = offset case io.SeekCurrent: f.offset += offset } return } func (f *File) Readdir(_ int) ([]os.FileInfo, error) { return nil, nil } func (f *File) Stat() (os.FileInfo, error) { if f.offline { return f.md.Stat() } return f.f.Stat() } func (f *File) Close() error { if f.offline { return nil } return f.f.Close() }