diff --git a/Sources/InfomaniakCore/Chunking/ChunkProvider.swift b/Sources/InfomaniakCore/Chunking/ChunkProvider.swift index f9972ed..98be160 100644 --- a/Sources/InfomaniakCore/Chunking/ChunkProvider.swift +++ b/Sources/InfomaniakCore/Chunking/ChunkProvider.swift @@ -32,31 +32,24 @@ public protocol ChunkProvidable: IteratorProtocol { public final class ChunkProvider: ChunkProvidable { public typealias Element = Data - let fileHandle: FileHandlable + let chunkReader: ChunkReader var ranges: [DataRange] - deinit { - do { - // For the sake of consistency - try fileHandle.close() - } catch {} - } - public init?(fileURL: URL, ranges: [DataRange]) { self.ranges = ranges - do { - fileHandle = try FileHandle(forReadingFrom: fileURL) - } catch { + guard let chunkReader = ChunkReader(fileURL: fileURL) else { return nil } + + self.chunkReader = chunkReader } /// Internal testing method init(mockedHandlable: FileHandlable, ranges: [DataRange]) { self.ranges = ranges - fileHandle = mockedHandlable + chunkReader = ChunkReader(mockedHandlable: mockedHandlable) } /// Will provide chunks one by one, using the IteratorProtocol @@ -69,23 +62,12 @@ public final class ChunkProvider: ChunkProvidable { let range = ranges.removeFirst() do { - let chunk = try readChunk(range: range) + let chunk = try chunkReader.readChunk(range: range) return chunk } catch { return nil } } - - // MARK: Internal - - func readChunk(range: DataRange) throws -> Data? { - let offset = range.lowerBound - try fileHandle.seek(toOffset: offset) - - let byteCount = Int(range.upperBound - range.lowerBound) + 1 - let chunk = try fileHandle.read(upToCount: byteCount) - return chunk - } } /// Print the FileHandle shows the current offset diff --git a/Sources/InfomaniakCore/Chunking/ChunkReader.swift b/Sources/InfomaniakCore/Chunking/ChunkReader.swift new file mode 100644 index 0000000..1ba7b2d --- /dev/null +++ b/Sources/InfomaniakCore/Chunking/ChunkReader.swift @@ -0,0 +1,53 @@ +/* + Infomaniak kDrive - iOS App + Copyright (C) 2024 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import Foundation + +@available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) +public final class ChunkReader: Sendable { + let fileHandle: FileHandlable + + deinit { + do { + // For the sake of consistency + try fileHandle.close() + } catch {} + } + + public init?(fileURL: URL) { + do { + fileHandle = try FileHandle(forReadingFrom: fileURL) + } catch { + return nil + } + } + + /// Internal testing method + init(mockedHandlable: FileHandlable) { + fileHandle = mockedHandlable + } + + public func readChunk(range: DataRange) throws -> Data? { + let offset = range.lowerBound + try fileHandle.seek(toOffset: offset) + + let byteCount = Int(range.upperBound - range.lowerBound) + 1 + let chunk = try fileHandle.read(upToCount: byteCount) + return chunk + } +} diff --git a/Sources/InfomaniakCore/Chunking/FileHandlable.swift b/Sources/InfomaniakCore/Chunking/FileHandlable.swift index 64b4d27..20d9c7d 100644 --- a/Sources/InfomaniakCore/Chunking/FileHandlable.swift +++ b/Sources/InfomaniakCore/Chunking/FileHandlable.swift @@ -20,7 +20,7 @@ import Foundation /// Something that matches most of the FileHandle specification, used for testing @available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) -protocol FileHandlable { +protocol FileHandlable: Sendable { var availableData: Data { get } var description: String { get } diff --git a/Tests/InfomaniakCoreTests/Chunking/UTChunkProvider.swift b/Tests/InfomaniakCoreTests/Chunking/UTChunkProvider.swift index 64db526..b46df9a 100644 --- a/Tests/InfomaniakCoreTests/Chunking/UTChunkProvider.swift +++ b/Tests/InfomaniakCoreTests/Chunking/UTChunkProvider.swift @@ -106,85 +106,4 @@ final class UTChunkProvider: XCTestCase { XCTAssertEqual(mckFileHandle.readUpToCountCallCount, expectedRangesCount) XCTAssertEqual(chunks.count, expectedRangesCount) } - - // MARK: - readChunk(range:) - - func testReadChunk_validChunk() throws { - // GIVEN - let range: DataRange = 0 ... 1 - let mckFileHandle = MCKFileHandlable() - mckFileHandle.readUpToCountClosure = { _ in Data() } - mckFileHandle.seekToOffsetClosure = { index in - XCTAssertEqual(index, range.lowerBound) - } - - let chunkProvider = ChunkProvider(mockedHandlable: mckFileHandle, ranges: []) - - // WHEN - do { - let chunk = try chunkProvider.readChunk(range: range) - XCTAssertNotNil(chunk) - } catch { - XCTFail("Unexpected :\(error)") - return - } - - // THEN - XCTAssertEqual(mckFileHandle.seekToOffsetCallCount, 1) - XCTAssertEqual(mckFileHandle.readUpToCountCallCount, 1) - } - - func testReadChunk_throwErrorOnSeek() throws { - // GIVEN - let range: DataRange = 0 ... 1 - let mckFileHandle = MCKFileHandlable() - mckFileHandle.readUpToCountClosure = { _ in Data() } - mckFileHandle.seekToOffsetError = NSError(domain: "k", code: 1337) - - let chunkProvider = ChunkProvider(mockedHandlable: mckFileHandle, ranges: []) - - // WHEN - do { - _ = try chunkProvider.readChunk(range: range) - XCTFail("Unexpected") - return - } catch { - guard (error as NSError).code == 1337 else { - XCTFail("Unexpected") - return - } - // all good - } - - // THEN - XCTAssertEqual(mckFileHandle.seekToOffsetCallCount, 1) - XCTAssertEqual(mckFileHandle.readUpToCountCallCount, 0) - } - - func testReadChunk_throwErrorOnRead() throws { - // GIVEN - let range: DataRange = 0 ... 1 - let mckFileHandle = MCKFileHandlable() - mckFileHandle.readUpToCountClosure = { _ in Data() } - mckFileHandle.readUpToCountError = NSError(domain: "k", code: 1337) - - let chunkProvider = ChunkProvider(mockedHandlable: mckFileHandle, ranges: []) - - // WHEN - do { - _ = try chunkProvider.readChunk(range: range) - XCTFail("Unexpected") - return - } catch { - guard (error as NSError).code == 1337 else { - XCTFail("Unexpected") - return - } - // all good - } - - // THEN - XCTAssertEqual(mckFileHandle.seekToOffsetCallCount, 1) - XCTAssertEqual(mckFileHandle.readUpToCountCallCount, 1) - } } diff --git a/Tests/InfomaniakCoreTests/Chunking/UTChunkReader.swift b/Tests/InfomaniakCoreTests/Chunking/UTChunkReader.swift new file mode 100644 index 0000000..6676d2b --- /dev/null +++ b/Tests/InfomaniakCoreTests/Chunking/UTChunkReader.swift @@ -0,0 +1,106 @@ +/* + Infomaniak kDrive - iOS App + Copyright (C) 2024 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import Foundation +@testable import InfomaniakCore +import XCTest + +/// Unit Tests of the ChunkReader +@available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) +final class UTChunkReader: XCTestCase { + // MARK: - readChunk(range:) + + func testReadChunk_validChunk() throws { + // GIVEN + let range: DataRange = 0 ... 1 + let mckFileHandle = MCKFileHandlable() + mckFileHandle.readUpToCountClosure = { _ in Data() } + mckFileHandle.seekToOffsetClosure = { index in + XCTAssertEqual(index, range.lowerBound) + } + + let chunkProvider = ChunkReader(mockedHandlable: mckFileHandle) + + // WHEN + do { + let chunk = try chunkProvider.readChunk(range: range) + XCTAssertNotNil(chunk) + } catch { + XCTFail("Unexpected :\(error)") + return + } + + // THEN + XCTAssertEqual(mckFileHandle.seekToOffsetCallCount, 1) + XCTAssertEqual(mckFileHandle.readUpToCountCallCount, 1) + } + + func testReadChunk_throwErrorOnSeek() throws { + // GIVEN + let range: DataRange = 0 ... 1 + let mckFileHandle = MCKFileHandlable() + mckFileHandle.readUpToCountClosure = { _ in Data() } + mckFileHandle.seekToOffsetError = NSError(domain: "k", code: 1337) + + let chunkProvider = ChunkReader(mockedHandlable: mckFileHandle) + + // WHEN + do { + _ = try chunkProvider.readChunk(range: range) + XCTFail("Unexpected") + return + } catch { + guard (error as NSError).code == 1337 else { + XCTFail("Unexpected") + return + } + // all good + } + + // THEN + XCTAssertEqual(mckFileHandle.seekToOffsetCallCount, 1) + XCTAssertEqual(mckFileHandle.readUpToCountCallCount, 0) + } + + func testReadChunk_throwErrorOnRead() throws { + // GIVEN + let range: DataRange = 0 ... 1 + let mckFileHandle = MCKFileHandlable() + mckFileHandle.readUpToCountClosure = { _ in Data() } + mckFileHandle.readUpToCountError = NSError(domain: "k", code: 1337) + + let chunkProvider = ChunkReader(mockedHandlable: mckFileHandle) + + // WHEN + do { + _ = try chunkProvider.readChunk(range: range) + XCTFail("Unexpected") + return + } catch { + guard (error as NSError).code == 1337 else { + XCTFail("Unexpected") + return + } + // all good + } + + // THEN + XCTAssertEqual(mckFileHandle.seekToOffsetCallCount, 1) + XCTAssertEqual(mckFileHandle.readUpToCountCallCount, 1) + } +}