go-aptproxy/cache/livereader.go

94 lines
2.1 KiB
Go
Raw Normal View History

2016-05-01 06:00:43 +02:00
package cache
import (
"github.com/fsnotify/fsnotify"
"io"
"os"
)
// liveReader reads a file from disk, synchronizing reads with a downloader.
type liveReader struct {
downloader *downloader
file *os.File
watcher *fsnotify.Watcher
entry *Entry
done chan error
err error
eof bool
}
// newLiveReader creates a reader from the provided downloader and data
// file. fsnotify is used to watch for writes to the file to avoid using a
// spinloop. Invoking this function assumes the existence of the data file.
func newLiveReader(d *downloader, dataFilename string) (*liveReader, error) {
f, err := os.Open(dataFilename)
if err != nil {
return nil, err
}
w, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
if err = w.Add(dataFilename); err != nil {
return nil, err
}
l := &liveReader{
downloader: d,
file: f,
watcher: w,
done: make(chan error),
}
go func() {
defer close(l.done)
l.done <- d.WaitForDone()
}()
return l, err
}
// Read attempts to read as much data as possible into the provided buffer.
// Since data is being downloaded as data is being read, fsnotify is used to
// monitor writes to the file. This function blocks until the requested amount
// of data is read, an error occurs, or EOF is encountered.
func (l *liveReader) Read(p []byte) (int, error) {
if l.err != nil {
return 0, l.err
}
bytesRead := 0
loop:
for bytesRead < len(p) {
n, err := l.file.Read(p[bytesRead:])
bytesRead += n
if err != nil {
if err != io.EOF || l.eof {
l.err = err
break loop
}
for {
select {
case e := <-l.watcher.Events:
if e.Op&fsnotify.Write != fsnotify.Write {
continue
}
case err = <-l.done:
l.err = err
l.eof = true
}
continue loop
}
}
}
return bytesRead, l.err
}
// Close attempts to close the data file (if opened).
func (l *liveReader) Close() error {
return l.file.Close()
}
// GetEntry returns the Entry associated with the file, blocking until either
// the data is available or an error occurs.
func (l *liveReader) GetEntry() (*Entry, error) {
return l.downloader.GetEntry()
}