rclone/vendor/github.com/pengsrc/go-shared/reopen/reopen.go

176 lines
3.8 KiB
Go

package reopen
import (
"bufio"
"io"
"os"
"sync"
"time"
)
// Reopener interface defines something that can be reopened.
type Reopener interface {
Reopen() error
}
// Writer is a writer that also can be reopened.
type Writer interface {
Reopener
io.Writer
}
// WriteCloser is a io.WriteCloser that can also be reopened.
type WriteCloser interface {
Reopener
io.WriteCloser
}
// FileWriter that can also be reopened.
type FileWriter struct {
// Ensures close/reopen/write are not called at the same time, protects f
mu sync.Mutex
f *os.File
mode os.FileMode
name string
}
// Close calls the under lying File.Close().
func (f *FileWriter) Close() error {
f.mu.Lock()
err := f.f.Close()
f.mu.Unlock()
return err
}
// Reopen the file.
func (f *FileWriter) Reopen() error {
f.mu.Lock()
err := f.reopen()
f.mu.Unlock()
return err
}
// Write implements the stander io.Writer interface.
func (f *FileWriter) Write(p []byte) (int, error) {
f.mu.Lock()
n, err := f.f.Write(p)
f.mu.Unlock()
return n, err
}
// reopen with mutex free.
func (f *FileWriter) reopen() error {
if f.f != nil {
f.f.Close()
f.f = nil
}
ff, err := os.OpenFile(f.name, os.O_WRONLY|os.O_APPEND|os.O_CREATE, f.mode)
if err != nil {
f.f = nil
return err
}
f.f = ff
return nil
}
// NewFileWriter opens a file for appending and writing and can be reopened.
// It is a ReopenWriteCloser...
func NewFileWriter(name string) (*FileWriter, error) {
// Standard default mode
return NewFileWriterMode(name, 0644)
}
// NewFileWriterMode opens a Reopener file with a specific permission.
func NewFileWriterMode(name string, mode os.FileMode) (*FileWriter, error) {
writer := FileWriter{
f: nil,
name: name,
mode: mode,
}
err := writer.reopen()
if err != nil {
return nil, err
}
return &writer, nil
}
// BufferedFileWriter is buffer writer than can be reopened.
type BufferedFileWriter struct {
mu sync.Mutex
OrigWriter *FileWriter
BufWriter *bufio.Writer
}
// Reopen implement Reopener.
func (bw *BufferedFileWriter) Reopen() error {
bw.mu.Lock()
bw.BufWriter.Flush()
// Use non-mutex version since we are using this one.
err := bw.OrigWriter.reopen()
bw.BufWriter.Reset(io.Writer(bw.OrigWriter))
bw.mu.Unlock()
return err
}
// Close flushes the internal buffer and closes the destination file.
func (bw *BufferedFileWriter) Close() error {
bw.mu.Lock()
bw.BufWriter.Flush()
bw.OrigWriter.f.Close()
bw.mu.Unlock()
return nil
}
// Write implements io.Writer (and reopen.Writer).
func (bw *BufferedFileWriter) Write(p []byte) (int, error) {
bw.mu.Lock()
n, err := bw.BufWriter.Write(p)
// Special Case... if the used space in the buffer is LESS than
// the input, then we did a flush in the middle of the line
// and the full log line was not sent on its way.
if bw.BufWriter.Buffered() < len(p) {
bw.BufWriter.Flush()
}
bw.mu.Unlock()
return n, err
}
// Flush flushes the buffer.
func (bw *BufferedFileWriter) Flush() {
bw.mu.Lock()
bw.BufWriter.Flush()
bw.OrigWriter.f.Sync()
bw.mu.Unlock()
}
// flushDaemon periodically flushes the log file buffers.
func (bw *BufferedFileWriter) flushDaemon(interval time.Duration) {
for range time.NewTicker(interval).C {
bw.Flush()
}
}
const bufferSize = 256 * 1024
const flushInterval = 30 * time.Second
// NewBufferedFileWriter opens a buffered file that is periodically flushed.
func NewBufferedFileWriter(w *FileWriter) *BufferedFileWriter {
return NewBufferedFileWriterSize(w, bufferSize, flushInterval)
}
// NewBufferedFileWriterSize opens a buffered file with the given size that is periodically
// flushed on the given interval.
func NewBufferedFileWriterSize(w *FileWriter, size int, flush time.Duration) *BufferedFileWriter {
bw := BufferedFileWriter{
OrigWriter: w,
BufWriter: bufio.NewWriterSize(w, size),
}
go bw.flushDaemon(flush)
return &bw
}