51 lines
1.8 KiB
Swift
51 lines
1.8 KiB
Swift
|
|
import Foundation
|
||
|
|
|
||
|
|
/// Offload report renderers: CSV (machine) + HTML (producer handoff).
|
||
|
|
public enum OffloadReportWriter {
|
||
|
|
|
||
|
|
public static func csv(_ report: OffloadReport) -> String {
|
||
|
|
var out = "relative_path,size,xxh64,verified,skipped\n"
|
||
|
|
for f in report.files {
|
||
|
|
out += "\(f.relativePath),\(f.size),\(f.xxh64),\(f.verified),\(f.skipped)\n"
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
public static func formatBytes(_ bytes: UInt64) -> String {
|
||
|
|
let mb = Double(bytes) / 1_000_000
|
||
|
|
if mb >= 1000 {
|
||
|
|
return String(format: "%.2f GB", mb / 1000)
|
||
|
|
}
|
||
|
|
return String(format: "%.1f MB", mb)
|
||
|
|
}
|
||
|
|
|
||
|
|
public static func html(_ report: OffloadReport) -> String {
|
||
|
|
let allVerified = report.files.allSatisfy(\.verified)
|
||
|
|
var rows = ""
|
||
|
|
for f in report.files {
|
||
|
|
rows += """
|
||
|
|
<tr><td>\(f.relativePath)</td><td>\(formatBytes(f.size))</td>\
|
||
|
|
<td><code>\(f.xxh64)</code></td>\
|
||
|
|
<td>\(f.verified ? "OK" : "FAIL")</td>\
|
||
|
|
<td>\(f.skipped ? "skipped" : "copied")</td></tr>\n
|
||
|
|
"""
|
||
|
|
}
|
||
|
|
return """
|
||
|
|
<html>
|
||
|
|
<head><title>Forge Offload Report</title>
|
||
|
|
<style>body{font-family:sans-serif}table{border-collapse:collapse}td,th{border:1px solid #ccc;padding:4px 8px}</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<h1>Offload Report — \(allVerified ? "VERIFIED" : "FAILURES PRESENT")</h1>
|
||
|
|
<p>Source: \(report.source)</p>
|
||
|
|
<p>Destinations: \(report.destinations.joined(separator: ", "))</p>
|
||
|
|
<p>\(report.files.count) files, \(formatBytes(report.totalBytes)), \(String(format: "%.1f", report.duration))s</p>
|
||
|
|
<table>
|
||
|
|
<tr><th>File</th><th>Size</th><th>xxHash64</th><th>Verify</th><th>Action</th></tr>
|
||
|
|
\(rows)</table>
|
||
|
|
</body>
|
||
|
|
</html>
|
||
|
|
"""
|
||
|
|
}
|
||
|
|
}
|