Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ChunkReader #142

Merged
merged 2 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 6 additions & 24 deletions Sources/InfomaniakCore/Chunking/ChunkProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
53 changes: 53 additions & 0 deletions Sources/InfomaniakCore/Chunking/ChunkReader.swift
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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
}
}
2 changes: 1 addition & 1 deletion Sources/InfomaniakCore/Chunking/FileHandlable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
81 changes: 0 additions & 81 deletions Tests/InfomaniakCoreTests/Chunking/UTChunkProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
106 changes: 106 additions & 0 deletions Tests/InfomaniakCoreTests/Chunking/UTChunkReader.swift
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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)
}
}
Loading