Created
October 17, 2016 08:48
-
-
Save scorredoira/cdcf062955be832f88673d792bde5933 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package files | |
import ( | |
"bytes" | |
"os" | |
) | |
func NewBackScanner(f *os.File) (*BackScanner, error) { | |
s, err := f.Stat() | |
if err != nil { | |
return nil, err | |
} | |
size := s.Size() | |
return &BackScanner{file: f, size: size, index: size}, nil | |
} | |
// BackScanner reads lines from the end of a file to de beginning. | |
type BackScanner struct { | |
file *os.File | |
size int64 | |
index int64 | |
buf []byte | |
err error | |
} | |
func (s *BackScanner) Scan() bool { | |
if s.index == -1 { | |
return false | |
} | |
i, err := s.prev() | |
if err != nil { | |
s.err = err | |
return false | |
} | |
last := s.index | |
s.index = i | |
if i == -1 { | |
i = 0 | |
} | |
l := last - i | |
if l > 0 { | |
buf := make([]byte, l) | |
if _, err := s.file.ReadAt(buf, i); err != nil { | |
s.err = err | |
return false | |
} | |
if i > 0 { | |
buf = buf[1:] // trim the linebreak | |
} | |
s.buf = buf | |
} | |
return true | |
} | |
func (s *BackScanner) Error() error { | |
return s.err | |
} | |
func (s *BackScanner) Bytes() []byte { | |
return s.buf | |
} | |
func (s *BackScanner) Text() string { | |
return string(s.buf) | |
} | |
func (s *BackScanner) Close() { | |
s.file.Close() | |
} | |
func (s *BackScanner) prev() (int64, error) { | |
// this block size arbitrary value but most lines would be < 200B. | |
block := int64(400) | |
start := s.index | |
// check if the file is smaller than a block | |
if block > start { | |
block = start | |
} | |
for { | |
// go backwards one block to start reading | |
start -= block | |
if start < 0 { | |
// if we are before the beginning of the file just read the remaining part. | |
block += start | |
start = 0 | |
if block <= 0 { | |
// nothing left to read | |
return -1, nil | |
} | |
} | |
// read the current blok | |
buf := make([]byte, block) | |
if _, err := s.file.ReadAt(buf, start); err != nil { | |
return 0, err | |
} | |
// return the last linebreak | |
if i := bytes.LastIndexByte(buf, '\n'); i != -1 { | |
return start + int64(i), nil | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment