Skip to content

Commit

Permalink
Merge pull request #3 from un000/buffered-line-begin-seeker
Browse files Browse the repository at this point in the history
Buffered line begin seeker
  • Loading branch information
un000 authored Jun 3, 2019
2 parents fc550d6 + 8788c27 commit ab40dd1
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 20 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ go get github.com/un000/tailor
```

## TODO
- [ ] Better Test Code Coverage
- [x] Better Test Code Coverage
- [ ] Benchmarks
- [ ] Rate limiter + Leaky Bucket

Expand Down
4 changes: 4 additions & 0 deletions line_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright 2019 Yegor Myskin. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tailor

import (
Expand Down
67 changes: 67 additions & 0 deletions seeker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package tailor

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"testing"
)

func TestNewLineFinder(t *testing.T) {
var tests = []struct {
content string
offsetFromStart int64
res string
}{
{"", 0, ""},
{"\n", 0, "\n"},
{"\n\n", 1, "\n"},
{"\n\na\n", 3, "a\n"},
{"a", 0, "a"},
{"a\n", 0, "a\n"},
{"abc", 2, "abc"},
{"abc\n", 2, "abc\n"},
{"a\nb", 2, "b"},
{"a\nb\n", 2, "b\n"},
{"aaaaa\nbbbbbbbb\n", 4, "aaaaa\n"},
{"aaaaa\nbbbbbbbb\n", 10, "bbbbbbbb\n"},
{strings.Repeat("a", 300), 280, strings.Repeat("a", 300)},
{strings.Repeat("a", 300) + "\n", 280, strings.Repeat("a", 300) + "\n"},
{strings.Repeat("a", 100) + "\n" + strings.Repeat("a", 200), 280, strings.Repeat("a", 200)},
}

const file = "./tst"
defer os.Remove(file)

for i, data := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
err := ioutil.WriteFile(file, []byte(data.content), os.ModePerm)
if err != nil {
t.Error(err)
return
}

f := New(file)
err = f.openFile(data.offsetFromStart, io.SeekStart)
if err != nil {
t.Errorf("[%d] error executing: %s, data: %+v", i, err, data)
return
}

r := bufio.NewReader(f.file)
line, err := r.ReadString('\n')
if err != nil && err != io.EOF {
t.Errorf("[%d] error reading line: %s, data: %+v", i, err, data)
return
}

if line != data.res {
t.Errorf("[%d] actual: '%s', want: '%s', data: %+v", i, line, data.res, data)
return
}
})
}
}
57 changes: 38 additions & 19 deletions tailor.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,42 +269,61 @@ func (t *Tailor) openFile(offset int64, whence int) (err error) {
return nil
}

// seekToLineStart seeks the cursor at the beginning of a line at offset.
// If the byte at offset equals \n, so next line will be selected.
// seekToLineStart seeks the cursor at the beginning of a line at offset. Internally this function uses a buffer
// to find the beginning of a line. If the byte at offset equals \n, so the next line will be selected.
func (t *Tailor) seekToLineStart(offset int64, whence int) error {
bts := make([]byte, 1)
const (
bufSize int64 = 256
)

offset, err := t.file.Seek(offset, whence)
if err == io.EOF {
initialOffset, err := t.file.Seek(offset, whence)
if initialOffset == 0 {
return nil
}
if err == io.EOF {
err = nil
}
if err != nil {
return err
}

for offset > 0 {
_, err = t.file.Read(bts)
min := func(a, b int64) int64 {
if a < b {
return a
}
return b
}

var current int64 = 0
Loop:
for {
current += min(bufSize, initialOffset-current)
buf := make([]byte, min(current, bufSize))

n, err := t.file.ReadAt(buf, initialOffset-current)
if err != nil && err != io.EOF {
return err
}
buf = buf[:n]

b := bts[0]
if b == '\n' {
return nil
current -= int64(n)
for i := int64(len(buf)) - 1; i >= 0; i-- {
if buf[i] == '\n' {
break Loop
}
current++
}

newOffset := int64(-2)
if offset-2 < 0 {
newOffset = -1
if initialOffset-current == 0 {
break
}
}

offset, err = t.file.Seek(newOffset, io.SeekCurrent)
if err != nil {
return err
}
_, err = t.file.Seek(-current, io.SeekCurrent)
if err == io.EOF {
err = nil
}

return nil
return err
}

// updateFileStatus update a current seek from the file an an actual file size.
Expand Down

0 comments on commit ab40dd1

Please sign in to comment.