Files
2026-07-13 12:22:33 +08:00

110 lines
3.3 KiB
Swift

import Foundation
enum CostUsageJsonl {
struct Line {
let bytes: Data
let wasTruncated: Bool
}
@discardableResult
static func scan(
fileURL: URL,
offset: Int64 = 0,
maxLineBytes: Int,
prefixBytes: Int,
onLine: (Line) -> Void) throws
-> Int64
{
try self.scan(
fileURL: fileURL,
offset: offset,
maxLineBytes: maxLineBytes,
prefixBytes: prefixBytes,
checkCancellation: nil,
onLine: onLine)
}
@discardableResult
static func scan(
fileURL: URL,
offset: Int64 = 0,
maxLineBytes: Int,
prefixBytes: Int,
checkCancellation: (() throws -> Void)? = nil,
onLine: (Line) -> Void) throws
-> Int64
{
let handle = try FileHandle(forReadingFrom: fileURL)
defer { try? handle.close() }
let startOffset = max(0, offset)
if startOffset > 0 {
try handle.seek(toOffset: UInt64(startOffset))
}
var current = Data()
current.reserveCapacity(4 * 1024)
var lineBytes = 0
var truncated = false
var bytesRead: Int64 = 0
func appendSegment(_ bytes: UnsafePointer<UInt8>, count: Int) {
guard count > 0 else { return }
lineBytes += count
if current.count < prefixBytes {
let appendCount = min(prefixBytes - current.count, count)
if appendCount > 0 {
current.append(bytes, count: appendCount)
}
}
if lineBytes > maxLineBytes || lineBytes > prefixBytes {
truncated = true
}
}
func flushLine() {
guard lineBytes > 0 else { return }
let line = Line(bytes: current, wasTruncated: truncated)
onLine(line)
current.removeAll(keepingCapacity: true)
lineBytes = 0
truncated = false
}
while true {
try checkCancellation?()
let reachedEOF = try autoreleasepool {
let chunk = try handle.read(upToCount: 256 * 1024) ?? Data()
if chunk.isEmpty {
flushLine()
return true
}
try checkCancellation?()
bytesRead += Int64(chunk.count)
chunk.withUnsafeBytes { rawBuffer in
guard let base = rawBuffer.bindMemory(to: UInt8.self).baseAddress else { return }
var segmentStart = 0
var index = 0
while index < rawBuffer.count {
if base[index] == 0x0A {
appendSegment(base.advanced(by: segmentStart), count: index - segmentStart)
flushLine()
segmentStart = index + 1
}
index += 1
}
if segmentStart < rawBuffer.count {
appendSegment(base.advanced(by: segmentStart), count: rawBuffer.count - segmentStart)
}
}
return false
}
if reachedEOF { break }
try checkCancellation?()
}
return startOffset + bytesRead
}
}