From 37bb38fb144b59a8dee4cbb5df816bc601dd1d7f Mon Sep 17 00:00:00 2001 From: Vitalii Budnik Date: Thu, 26 Jun 2025 15:23:39 +0300 Subject: [PATCH] chore: add ChecksumCalculatorProtocol --- .../Utils/ChecksumCalculatorProtocol.swift | 9 ++++ Sources/Utils/SHA256ChecksumCalculator.swift | 47 +++++++++++++++++++ .../ChecksumCalculatorProtocolMock.swift | 29 ++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 Sources/BinaryDependencyManager/Utils/ChecksumCalculatorProtocol.swift create mode 100644 Sources/Utils/SHA256ChecksumCalculator.swift create mode 100644 Tests/BinaryDependencyManagerTests/Mocks/ChecksumCalculatorProtocolMock.swift diff --git a/Sources/BinaryDependencyManager/Utils/ChecksumCalculatorProtocol.swift b/Sources/BinaryDependencyManager/Utils/ChecksumCalculatorProtocol.swift new file mode 100644 index 0000000..600eec3 --- /dev/null +++ b/Sources/BinaryDependencyManager/Utils/ChecksumCalculatorProtocol.swift @@ -0,0 +1,9 @@ + +import Foundation +import Utils + +public protocol ChecksumCalculatorProtocol { + func calculateChecksum(fileURL: URL) throws -> String +} + +extension SHA256ChecksumCalculator: ChecksumCalculatorProtocol {} diff --git a/Sources/Utils/SHA256ChecksumCalculator.swift b/Sources/Utils/SHA256ChecksumCalculator.swift new file mode 100644 index 0000000..8e7aa05 --- /dev/null +++ b/Sources/Utils/SHA256ChecksumCalculator.swift @@ -0,0 +1,47 @@ +import Crypto +import Foundation + +public struct SHA256ChecksumCalculator { + public init() {} + + public func calculateChecksum(fileURL: URL) throws -> String { + // Read the file in chunks to avoid RAM usage issues + + let handle: FileHandle = try FileHandle(forReadingFrom: fileURL) + var hasher: SHA256 = SHA256() + + #if os(macOS) + + while autoreleasepool(invoking: { + let nextChunk = handle.readData(ofLength: 1024 * 1024) + guard !nextChunk.isEmpty else { return false } + hasher.update(data: nextChunk) + return true + }) {} + + #elseif os(Linux) + + var eof: Bool = false + var nextChunk: Data + + while !eof { + nextChunk = handle.readData(ofLength: 1024 * 1024) + eof = nextChunk.isEmpty + if !eof { + hasher.update(data: nextChunk) + } + } + + #endif + + let digest: SHA256.Digest = hasher.finalize() + + return digest.hexadecimalString + } +} + +extension SHA256.Digest { + fileprivate var hexadecimalString: String { + map { String(format: "%02hhx", $0) }.joined() + } +} diff --git a/Tests/BinaryDependencyManagerTests/Mocks/ChecksumCalculatorProtocolMock.swift b/Tests/BinaryDependencyManagerTests/Mocks/ChecksumCalculatorProtocolMock.swift new file mode 100644 index 0000000..9ff3477 --- /dev/null +++ b/Tests/BinaryDependencyManagerTests/Mocks/ChecksumCalculatorProtocolMock.swift @@ -0,0 +1,29 @@ +import Foundation + +@testable import BinaryDependencyManager +import Utils + +/// Mock implementation for UnarchiverProtocol for use in unit tests. +class ChecksumCalculatorProtocolMock: ChecksumCalculatorProtocol { + /// Records the arguments with which unzip was called. + public private(set) var unzipCalls: [(archivePath: String, outputFilePath: String)] = [] + /// If set, unzip will throw this error when called. + public var errorToThrow: Error? + + public init(errorToThrow: Error? = nil) { + self.errorToThrow = errorToThrow + } + + var checksums: [URL: String] = [:] + var checksumCalls: [URL] = [] + public func calculateChecksum(fileURL: URL) throws -> String { + checksumCalls.append(fileURL) + if let errorToThrow { + throw errorToThrow + } + guard let checksum = checksums[fileURL] else { + throw GenericError("no checksum provided for \(fileURL.relativeFilePath)") + } + return checksum + } +}