cachefs/pkg/fs/file.go
2022-04-06 01:08:19 +02:00

166 lines
3.1 KiB
Go

// 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")
}
f.md.Close()
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 != 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 n, err
}
}
if err != nil && err != io.EOF {
log.Error(err, "error reading source file")
return
}
f.md.AddChunk(f.offset, n)
f.offset += int64(n)
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()
}