package notes import ( "bufio" "errors" "local/notes-server/filetree" "log" "os" "path" "path/filepath" "regexp" "strings" ) type searcher struct { patterns []*regexp.Regexp } func newSearcher(phrase string) (*searcher, error) { phrases := strings.Split(phrase, " ") patterns := make([]*regexp.Regexp, 0) for _, phrase := range phrases { if len(phrase) == 0 { continue } pattern, err := regexp.Compile("(?i)" + phrase) if err != nil { return nil, err } patterns = append(patterns, pattern) } if len(patterns) < 1 { return nil, errors.New("no search specified") } return &searcher{ patterns: patterns, }, nil } func (s *searcher) matches(input []byte) bool { for _, pattern := range s.patterns { if !pattern.Match(input) { return false } } return true } func (n *Notes) Search(phrase string) (string, error) { searcher, err := newSearcher(phrase) if err != nil { return "", err } files := filetree.NewFiles() err = filepath.Walk(n.root, func(walked string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } if size := info.Size(); size < 1 || size > (5*1024*1024) { return nil } ok, err := grepFile(walked, searcher) if err != nil && err.Error() == "bufio.Scanner: token too long" { err = nil } if err == nil && ok { p := filetree.NewPathFromLocal(path.Dir(walked)) files.Push(p, info) } if err != nil { log.Printf("failed to scan %v: %v", walked, err) } return err }, ) return filetree.Paths(*files).List(true), err } func grepFile(file string, searcher *searcher) (bool, error) { if d := path.Base(path.Dir(file)); strings.HasPrefix(d, ".") && strings.HasSuffix(d, ".attachments") { return false, nil } if strings.Contains(file, "/.git/") { return false, nil } f, err := os.Open(file) if err != nil { return false, err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { if searcher.matches(scanner.Bytes()) { return true, scanner.Err() } } return false, scanner.Err() }