95 lines
1.7 KiB
Go
95 lines
1.7 KiB
Go
package tail
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
)
|
|
|
|
// Unstable, beta
|
|
func New(ctx context.Context, name string, offset int64, whence int) (<-chan *Line, <-chan error, error) {
|
|
w, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if err := w.Add(name); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
lineChan, errChan := make(chan *Line), make(chan error)
|
|
|
|
go singleFile(ctx, w, &Tailable{
|
|
Name: name, Offset: offset, Whence: whence,
|
|
}, lineChan, errChan)
|
|
|
|
return lineChan, errChan, nil
|
|
}
|
|
|
|
// assumes file exists
|
|
func singleFile(ctx context.Context,
|
|
w *fsnotify.Watcher, file *Tailable,
|
|
lineChan chan<- *Line, errChan chan<- error,
|
|
) {
|
|
var wg sync.WaitGroup
|
|
|
|
sctx, cancel := context.WithCancel(ctx)
|
|
file.wakeup = make(chan struct{}, 1)
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
// no need to lock/unlock orderedLineChan, as we only have one same-named file across its life
|
|
fileHandle(sctx, *file, true, &orderedLines{c: lineChan}, errChan)
|
|
wg.Done()
|
|
}()
|
|
|
|
defer func() {
|
|
w.Close()
|
|
|
|
cancel()
|
|
wg.Wait()
|
|
|
|
close(lineChan)
|
|
close(errChan)
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
errChan <- ctx.Err()
|
|
return
|
|
default:
|
|
}
|
|
// There is no priority in select.
|
|
// To prevent race of looping with expired context,
|
|
// ctx.Done() is checked again at the start.
|
|
select {
|
|
case <-ctx.Done():
|
|
continue
|
|
|
|
case err, ok := <-w.Errors:
|
|
if ok {
|
|
errChan <- err
|
|
}
|
|
return
|
|
|
|
case ev, ok := <-w.Events:
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
switch ev.Op {
|
|
case fsnotify.Write:
|
|
select {
|
|
case file.wakeup <- struct{}{}:
|
|
default:
|
|
}
|
|
case fsnotify.Remove, fsnotify.Rename:
|
|
close(file.wakeup)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|