mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-22 16:28:30 +02:00
feat: implement experimental BackScanner for reading files backward and add tests for various scenarios
This commit is contained in:
104
internal/net/gphttp/accesslog/back_scanner.go
Normal file
104
internal/net/gphttp/accesslog/back_scanner.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// BackScanner provides an interface to read a file backward line by line.
|
||||
type BackScanner struct {
|
||||
file AccessLogIO
|
||||
chunkSize int
|
||||
offset int64
|
||||
buffer []byte
|
||||
line []byte
|
||||
err error
|
||||
size int64
|
||||
}
|
||||
|
||||
// NewBackScanner creates a new Scanner to read the file backward.
|
||||
// chunkSize determines the size of each read chunk from the end of the file.
|
||||
func NewBackScanner(file AccessLogIO, chunkSize int) *BackScanner {
|
||||
size, err := file.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return &BackScanner{err: err}
|
||||
}
|
||||
return &BackScanner{
|
||||
file: file,
|
||||
chunkSize: chunkSize,
|
||||
offset: size,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// Scan advances the scanner to the previous line, which will then be available
|
||||
// via the Bytes method. It returns false when there are no more lines.
|
||||
func (s *BackScanner) Scan() bool {
|
||||
if s.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Read chunks until a newline is found or the file is fully read
|
||||
for {
|
||||
// Check if there's a line in the buffer
|
||||
if idx := bytes.LastIndexByte(s.buffer, '\n'); idx >= 0 {
|
||||
s.line = s.buffer[idx+1:]
|
||||
s.buffer = s.buffer[:idx]
|
||||
if len(s.line) > 0 {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for {
|
||||
if s.offset <= 0 {
|
||||
// No more data to read; check remaining buffer
|
||||
if len(s.buffer) > 0 {
|
||||
s.line = s.buffer
|
||||
s.buffer = nil
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
newOffset := max(0, s.offset-int64(s.chunkSize))
|
||||
chunkSize := s.offset - newOffset
|
||||
chunk := make([]byte, chunkSize)
|
||||
|
||||
n, err := s.file.ReadAt(chunk, newOffset)
|
||||
if err != nil && err != io.EOF {
|
||||
s.err = err
|
||||
return false
|
||||
}
|
||||
|
||||
// Prepend the chunk to the buffer
|
||||
s.buffer = append(chunk[:n], s.buffer...)
|
||||
s.offset = newOffset
|
||||
|
||||
// Check for newline in the updated buffer
|
||||
if idx := bytes.LastIndexByte(s.buffer, '\n'); idx >= 0 {
|
||||
s.line = s.buffer[idx+1:]
|
||||
s.buffer = s.buffer[:idx]
|
||||
if len(s.line) > 0 {
|
||||
return true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns the most recent line generated by a call to Scan.
|
||||
func (s *BackScanner) Bytes() []byte {
|
||||
return s.line
|
||||
}
|
||||
|
||||
// FileSize returns the size of the file.
|
||||
func (s *BackScanner) FileSize() int64 {
|
||||
return s.size
|
||||
}
|
||||
|
||||
// Err returns the first non-EOF error encountered by the scanner.
|
||||
func (s *BackScanner) Err() error {
|
||||
return s.err
|
||||
}
|
||||
Reference in New Issue
Block a user