38 lines
1.2 KiB
Swift
38 lines
1.2 KiB
Swift
|
|
import Foundation
|
||
|
|
|
||
|
|
public enum OffloadError: Error, Equatable {
|
||
|
|
case fileNotFound(String)
|
||
|
|
case readFailed(String)
|
||
|
|
case writeFailed(String)
|
||
|
|
case verifyFailed(path: String, expected: String, actual: String)
|
||
|
|
case destinationConflict(String)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Chunked file hashing — constant memory over multi-GB camera files.
|
||
|
|
public enum FileHasher {
|
||
|
|
static let chunkSize = 4 * 1024 * 1024
|
||
|
|
|
||
|
|
public static func xxh64(path: String) throws -> String {
|
||
|
|
var hasher = XXHash64()
|
||
|
|
try stream(path: path) { hasher.update($0) }
|
||
|
|
return XXHash64.hexString(hasher.finalize())
|
||
|
|
}
|
||
|
|
|
||
|
|
public static func md5(path: String) throws -> String {
|
||
|
|
var hasher = MD5()
|
||
|
|
try stream(path: path) { hasher.update($0) }
|
||
|
|
return hasher.finalize().map { String(format: "%02x", $0) }.joined()
|
||
|
|
}
|
||
|
|
|
||
|
|
private static func stream(path: String, _ body: (Data) -> Void) throws {
|
||
|
|
guard let handle = FileHandle(forReadingAtPath: path) else {
|
||
|
|
throw OffloadError.fileNotFound(path)
|
||
|
|
}
|
||
|
|
defer { try? handle.close() }
|
||
|
|
while true {
|
||
|
|
guard let chunk = try handle.read(upToCount: chunkSize), !chunk.isEmpty else { break }
|
||
|
|
body(chunk)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|