Files
wehub-resource-sync f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

205 lines
4.5 KiB
Go

package parser
import (
"bufio"
"fmt"
"io"
"os"
"sync"
"github.com/tidwall/gjson"
)
// countingReader wraps an io.Reader and counts bytes read.
type countingReader struct {
r io.Reader
n int64
}
func (cr *countingReader) Read(p []byte) (int, error) {
n, err := cr.r.Read(p)
cr.n += int64(n)
return n, err
}
// lineReader reads JSONL files line by line, skipping lines that
// exceed maxLen rather than aborting. The buffer starts small and
// grows on demand up to maxLen. After iteration, call Err() to
// check for I/O errors (as opposed to normal EOF).
type lineReader struct {
r *bufio.Reader
cr *countingReader
maxLen int
buf []byte
err error
bytesRead int64 // total bytes consumed (from countingReader)
}
const maxPooledLineBufferSize = 256 << 10
var lineReaderPool = sync.Pool{
New: func() any {
cr := new(countingReader)
return &lineReader{
r: bufio.NewReaderSize(cr, initialScanBufSize),
cr: cr,
}
},
}
func newLineReader(r io.Reader, maxLen int) *lineReader {
lr := lineReaderPool.Get().(*lineReader)
lr.cr.r = r
lr.cr.n = 0
lr.r.Reset(lr.cr)
lr.maxLen = maxLen
lr.buf = lr.buf[:0]
lr.err = nil
lr.bytesRead = 0
return lr
}
func releaseLineReader(lr *lineReader) {
// Do not let an exceptional long line pin a multi-megabyte backing
// array. sync.Pool may discard the remaining workspace at any GC.
if cap(lr.buf) > maxPooledLineBufferSize {
lr.buf = nil
} else {
lr.buf = lr.buf[:0]
}
lr.r.Reset(nil)
lr.cr.r = nil
lr.cr.n = 0
lr.maxLen = 0
lr.err = nil
lr.bytesRead = 0
lineReaderPool.Put(lr)
}
// next returns the next line (without trailing newline) and true,
// or ("", false) at EOF or read error. After the loop, call Err()
// to distinguish EOF from I/O failure.
func (lr *lineReader) next() (string, bool) {
for {
line, err := lr.readLine()
if err != nil {
if err != io.EOF {
lr.err = err
}
return "", false
}
if line != "" {
return line, true
}
// Empty line or skipped oversized line — continue.
}
}
// Err returns the first non-EOF read error encountered, or nil.
func (lr *lineReader) Err() error {
return lr.err
}
// readLine reads a full line, returning "" for blank/oversized
// lines and a non-nil error only at EOF or read failure.
// updateBytesRead computes total bytes consumed by
// subtracting any buffered-but-not-consumed data from the
// countingReader total.
func (lr *lineReader) updateBytesRead() {
if lr.cr != nil {
lr.bytesRead = lr.cr.n - int64(lr.r.Buffered())
}
}
func (lr *lineReader) readLine() (string, error) {
lr.buf = lr.buf[:0]
oversized := false
for {
chunk, isPrefix, err := lr.r.ReadLine()
if err != nil {
if len(lr.buf) > 0 && err == io.EOF {
lr.updateBytesRead()
break
}
return "", err
}
if oversized {
if !isPrefix {
lr.updateBytesRead()
return "", nil // done skipping
}
continue
}
// ReadLine's common case is a complete line backed by the
// bufio.Reader buffer. Convert it directly instead of allocating
// a second initialScanBufSize scratch buffer for every file.
if len(lr.buf) == 0 && !isPrefix {
lr.updateBytesRead()
if len(chunk) > lr.maxLen {
return "", nil
}
return string(chunk), nil
}
lr.buf = append(lr.buf, chunk...)
if len(lr.buf) > lr.maxLen {
oversized = true
lr.buf = lr.buf[:0]
if !isPrefix {
lr.updateBytesRead()
return "", nil
}
continue
}
if !isPrefix {
lr.updateBytesRead()
break
}
}
return string(lr.buf), nil
}
// readJSONLFrom opens a JSONL file, seeks to offset, and
// calls fn for each valid JSON line. Returns the number of
// bytes consumed (relative to offset) and any I/O error. The
// returned byte count covers only complete lines, so callers
// can use offset+consumed as a safe resume point even if the
// file had a partially written line at EOF.
func readJSONLFrom(
path string, offset int64, fn func(line string),
) (consumed int64, err error) {
f, err := os.Open(path)
if err != nil {
return 0, fmt.Errorf("open %s: %w", path, err)
}
defer f.Close()
if _, err := f.Seek(offset, io.SeekStart); err != nil {
return 0, fmt.Errorf(
"seek %s to %d: %w", path, offset, err,
)
}
lr := newLineReader(f, maxLineSize)
defer releaseLineReader(lr)
for {
line, ok := lr.next()
if !ok {
break
}
if gjson.Valid(line) {
fn(line)
// Track offset through last valid JSON line
// so partial lines at EOF are not skipped.
consumed = lr.bytesRead
}
}
return consumed, lr.Err()
}