Skip to content

Commit cc1c57c

Browse files
clintonpiglbrntt
andauthored
Provide APIs to read file into more data types (#2923)
Motivation: As requested in issues [#2875](#2875) and [#2876](#2876), it would be convenient to be able to read the contents of a file into more data types such as `Array`, `ArraySlice` & Foundation's `Data`. Modifications: - Extend `Array`, `ArraySlice` & `Data` to be initialisable with the contents of a file. Result: The contents of a file can be read into more data types. --------- Co-authored-by: George Barnett <[email protected]>
1 parent 8b66b22 commit cc1c57c

File tree

6 files changed

+237
-1
lines changed

6 files changed

+237
-1
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ let package = Package(
259259
.target(
260260
name: "_NIOFileSystemFoundationCompat",
261261
dependencies: [
262-
"_NIOFileSystem"
262+
"_NIOFileSystem",
263+
"NIOFoundationCompat",
263264
],
264265
path: "Sources/NIOFileSystemFoundationCompat"
265266
),
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android)
16+
import NIOCore
17+
18+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
19+
extension Array where Element == UInt8 {
20+
/// Reads the contents of the file at the path.
21+
///
22+
/// - Parameters:
23+
/// - path: The path of the file to read.
24+
/// - maximumSizeAllowed: The maximum size of file which can be read, in bytes, as a ``ByteCount``.
25+
/// - fileSystem: The ``FileSystemProtocol`` instance to use to read the file.
26+
public init(
27+
contentsOf path: FilePath,
28+
maximumSizeAllowed: ByteCount,
29+
fileSystem: some FileSystemProtocol
30+
) async throws {
31+
let byteBuffer = try await fileSystem.withFileHandle(forReadingAt: path) { handle in
32+
try await handle.readToEnd(maximumSizeAllowed: maximumSizeAllowed)
33+
}
34+
35+
self = Self(buffer: byteBuffer)
36+
}
37+
38+
/// Reads the contents of the file at the path using ``FileSystem``.
39+
///
40+
/// - Parameters:
41+
/// - path: The path of the file to read.
42+
/// - maximumSizeAllowed: The maximum size of file which can be read, as a ``ByteCount``.
43+
public init(
44+
contentsOf path: FilePath,
45+
maximumSizeAllowed: ByteCount
46+
) async throws {
47+
self = try await Self(
48+
contentsOf: path,
49+
maximumSizeAllowed: maximumSizeAllowed,
50+
fileSystem: .shared
51+
)
52+
}
53+
}
54+
#endif
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android)
16+
import NIOCore
17+
18+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
19+
extension ArraySlice where Element == UInt8 {
20+
/// Reads the contents of the file at the path.
21+
///
22+
/// - Parameters:
23+
/// - path: The path of the file to read.
24+
/// - maximumSizeAllowed: The maximum size of file which can be read, in bytes, as a ``ByteCount``.
25+
/// - fileSystem: The ``FileSystemProtocol`` instance to use to read the file.
26+
public init(
27+
contentsOf path: FilePath,
28+
maximumSizeAllowed: ByteCount,
29+
fileSystem: some FileSystemProtocol
30+
) async throws {
31+
let bytes = try await Array(
32+
contentsOf: path,
33+
maximumSizeAllowed: maximumSizeAllowed,
34+
fileSystem: fileSystem
35+
)
36+
37+
self = Self(bytes)
38+
}
39+
40+
/// Reads the contents of the file at the path using ``FileSystem``.
41+
///
42+
/// - Parameters:
43+
/// - path: The path of the file to read.
44+
/// - maximumSizeAllowed: The maximum size of file which can be read, as a ``ByteCount``.
45+
public init(
46+
contentsOf path: FilePath,
47+
maximumSizeAllowed: ByteCount
48+
) async throws {
49+
self = try await Self(
50+
contentsOf: path,
51+
maximumSizeAllowed: maximumSizeAllowed,
52+
fileSystem: .shared
53+
)
54+
}
55+
}
56+
#endif
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android)
16+
import _NIOFileSystem
17+
import NIOCore
18+
import NIOFoundationCompat
19+
import struct Foundation.Data
20+
21+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
22+
extension Data {
23+
/// Reads the contents of the file at the path.
24+
///
25+
/// - Parameters:
26+
/// - path: The path of the file to read.
27+
/// - maximumSizeAllowed: The maximum size of file which can be read, in bytes, as a ``ByteCount``.
28+
/// - fileSystem: The ``FileSystemProtocol`` instance to use to read the file.
29+
public init(
30+
contentsOf path: FilePath,
31+
maximumSizeAllowed: ByteCount,
32+
fileSystem: some FileSystemProtocol
33+
) async throws {
34+
let byteBuffer = try await fileSystem.withFileHandle(forReadingAt: path) { handle in
35+
try await handle.readToEnd(maximumSizeAllowed: maximumSizeAllowed)
36+
}
37+
38+
self = Data(buffer: byteBuffer)
39+
}
40+
41+
/// Reads the contents of the file at the path using ``FileSystem``.
42+
///
43+
/// - Parameters:
44+
/// - path: The path of the file to read.
45+
/// - maximumSizeAllowed: The maximum size of file which can be read, as a ``ByteCount``.
46+
public init(
47+
contentsOf path: FilePath,
48+
maximumSizeAllowed: ByteCount
49+
) async throws {
50+
self = try await Self(
51+
contentsOf: path,
52+
maximumSizeAllowed: maximumSizeAllowed,
53+
fileSystem: .shared
54+
)
55+
}
56+
}
57+
#endif

Tests/NIOFileSystemFoundationCompatTests/FileSystemFoundationCompatTests.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,37 @@ import _NIOFileSystem
1717
import _NIOFileSystemFoundationCompat
1818
import XCTest
1919

20+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
21+
extension FileSystem {
22+
func temporaryFilePath(
23+
_ function: String = #function,
24+
inTemporaryDirectory: Bool = true
25+
) async throws -> FilePath {
26+
if inTemporaryDirectory {
27+
let directory = try await self.temporaryDirectory
28+
return self.temporaryFilePath(function, inDirectory: directory)
29+
} else {
30+
return self.temporaryFilePath(function, inDirectory: nil)
31+
}
32+
}
33+
34+
func temporaryFilePath(
35+
_ function: String = #function,
36+
inDirectory directory: FilePath?
37+
) -> FilePath {
38+
let index = function.firstIndex(of: "(")!
39+
let functionName = function.prefix(upTo: index)
40+
let random = UInt32.random(in: .min ... .max)
41+
let fileName = "\(functionName)-\(random)"
42+
43+
if let directory = directory {
44+
return directory.appending(fileName)
45+
} else {
46+
return FilePath(fileName)
47+
}
48+
}
49+
}
50+
2051
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
2152
final class FileSystemBytesConformanceTests: XCTestCase {
2253
func testTimepecToDate() async throws {
@@ -33,5 +64,18 @@ final class FileSystemBytesConformanceTests: XCTestCase {
3364
Date(timeIntervalSince1970: 1.000000001)
3465
)
3566
}
67+
68+
func testReadFileIntoData() async throws {
69+
let fs = FileSystem.shared
70+
let path = try await fs.temporaryFilePath()
71+
72+
try await fs.withFileHandle(forReadingAndWritingAt: path) { fileHandle in
73+
_ = try await fileHandle.write(contentsOf: [0, 1, 2], toAbsoluteOffset: 0)
74+
}
75+
76+
let contents = try await Data(contentsOf: path, maximumSizeAllowed: .bytes(1024))
77+
78+
XCTAssertEqual(contents, Data([0, 1, 2]))
79+
}
3680
}
3781
#endif

Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,6 +1828,30 @@ extension FileSystemTests {
18281828
)
18291829
}
18301830
}
1831+
1832+
func testReadIntoArray() async throws {
1833+
let path = try await self.fs.temporaryFilePath()
1834+
1835+
try await self.fs.withFileHandle(forReadingAndWritingAt: path) { fileHandle in
1836+
_ = try await fileHandle.write(contentsOf: [0, 1, 2], toAbsoluteOffset: 0)
1837+
}
1838+
1839+
let contents = try await Array(contentsOf: path, maximumSizeAllowed: .bytes(1024))
1840+
1841+
XCTAssertEqual(contents, [0, 1, 2])
1842+
}
1843+
1844+
func testReadIntoArraySlice() async throws {
1845+
let path = try await self.fs.temporaryFilePath()
1846+
1847+
try await self.fs.withFileHandle(forReadingAndWritingAt: path) { fileHandle in
1848+
_ = try await fileHandle.write(contentsOf: [0, 1, 2], toAbsoluteOffset: 0)
1849+
}
1850+
1851+
let contents = try await ArraySlice(contentsOf: path, maximumSizeAllowed: .bytes(1024))
1852+
1853+
XCTAssertEqual(contents, [0, 1, 2])
1854+
}
18311855
}
18321856

18331857
#if !canImport(Darwin) && swift(<5.9.2)

0 commit comments

Comments
 (0)