star/bed/searcher/searcher.go

144 lines
2.6 KiB
Go
Raw Normal View History

2025-04-26 19:33:14 +08:00
package searcher
import (
"bytes"
"errors"
"io"
"sync"
"time"
)
const loadSize = 1024 * 1024
// Searcher represents a searcher.
type Searcher struct {
r io.ReaderAt
bytes []byte
loopCh chan struct{}
cursor int64
pattern string
mu *sync.Mutex
}
// NewSearcher creates a new searcher.
func NewSearcher(r io.ReaderAt) *Searcher {
return &Searcher{r: r, mu: new(sync.Mutex)}
}
type errNotFound string
func (err errNotFound) Error() string {
return "pattern not found: " + string(err)
}
// Search the pattern.
func (s *Searcher) Search(cursor int64, pattern string, forward bool) <-chan any {
s.mu.Lock()
defer s.mu.Unlock()
if s.bytes == nil {
s.bytes = make([]byte, loadSize)
}
s.cursor, s.pattern = cursor, pattern
ch := make(chan any)
if forward {
s.loop(s.forward, ch)
} else {
s.loop(s.backward, ch)
}
return ch
}
func (s *Searcher) forward() (int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
target, err := patternToTarget(s.pattern)
if err != nil {
return -1, err
}
base := s.cursor + 1
n, err := s.r.ReadAt(s.bytes, base)
if err != nil && err != io.EOF {
return -1, err
}
if n == 0 {
return -1, errNotFound(s.pattern)
}
if err == io.EOF {
s.cursor += int64(n)
} else {
s.cursor += int64(n - len(target) + 1)
}
i := bytes.Index(s.bytes[:n], target)
if i >= 0 {
return base + int64(i), nil
}
return -1, nil
}
func (s *Searcher) backward() (int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
target, err := patternToTarget(s.pattern)
if err != nil {
return -1, err
}
base := max(0, s.cursor-int64(loadSize))
size := int(min(int64(loadSize), s.cursor))
n, err := s.r.ReadAt(s.bytes[:size], base)
if err != nil && err != io.EOF {
return -1, err
}
if n == 0 {
return -1, errNotFound(s.pattern)
}
if s.cursor == int64(n) {
s.cursor = 0
} else {
s.cursor = base + int64(len(target)-1)
}
i := bytes.LastIndex(s.bytes[:n], target)
if i >= 0 {
return base + int64(i), nil
}
return -1, nil
}
func (s *Searcher) loop(f func() (int64, error), ch chan<- any) {
if s.loopCh != nil {
close(s.loopCh)
}
loopCh := make(chan struct{})
s.loopCh = loopCh
go func() {
defer close(ch)
for {
select {
case <-loopCh:
return
case <-time.After(10 * time.Millisecond):
idx, err := f()
if err != nil {
ch <- err
return
}
if idx >= 0 {
ch <- idx
return
}
}
}
}()
}
// Abort the searching.
func (s *Searcher) Abort() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.loopCh != nil {
close(s.loopCh)
s.loopCh = nil
return errors.New("search is aborted")
}
return nil
}