110 lines
3.3 KiB
Swift
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
|
|
}
|
|
}
|