Skip to content

Commit

Permalink
Provide APIs to read file into more data types (#2923)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
clintonpi and glbrntt authored Oct 21, 2024
1 parent 8b66b22 commit cc1c57c
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ let package = Package(
.target(
name: "_NIOFileSystemFoundationCompat",
dependencies: [
"_NIOFileSystem"
"_NIOFileSystem",
"NIOFoundationCompat",
],
path: "Sources/NIOFileSystemFoundationCompat"
),
Expand Down
54 changes: 54 additions & 0 deletions Sources/NIOFileSystem/Array+FileSystem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android)
import NIOCore

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension Array where Element == UInt8 {
/// Reads the contents of the file at the path.
///
/// - Parameters:
/// - path: The path of the file to read.
/// - maximumSizeAllowed: The maximum size of file which can be read, in bytes, as a ``ByteCount``.
/// - fileSystem: The ``FileSystemProtocol`` instance to use to read the file.
public init(
contentsOf path: FilePath,
maximumSizeAllowed: ByteCount,
fileSystem: some FileSystemProtocol
) async throws {
let byteBuffer = try await fileSystem.withFileHandle(forReadingAt: path) { handle in
try await handle.readToEnd(maximumSizeAllowed: maximumSizeAllowed)
}

self = Self(buffer: byteBuffer)
}

/// Reads the contents of the file at the path using ``FileSystem``.
///
/// - Parameters:
/// - path: The path of the file to read.
/// - maximumSizeAllowed: The maximum size of file which can be read, as a ``ByteCount``.
public init(
contentsOf path: FilePath,
maximumSizeAllowed: ByteCount
) async throws {
self = try await Self(
contentsOf: path,
maximumSizeAllowed: maximumSizeAllowed,
fileSystem: .shared
)
}
}
#endif
56 changes: 56 additions & 0 deletions Sources/NIOFileSystem/ArraySlice+FileSystem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android)
import NIOCore

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension ArraySlice where Element == UInt8 {
/// Reads the contents of the file at the path.
///
/// - Parameters:
/// - path: The path of the file to read.
/// - maximumSizeAllowed: The maximum size of file which can be read, in bytes, as a ``ByteCount``.
/// - fileSystem: The ``FileSystemProtocol`` instance to use to read the file.
public init(
contentsOf path: FilePath,
maximumSizeAllowed: ByteCount,
fileSystem: some FileSystemProtocol
) async throws {
let bytes = try await Array(
contentsOf: path,
maximumSizeAllowed: maximumSizeAllowed,
fileSystem: fileSystem
)

self = Self(bytes)
}

/// Reads the contents of the file at the path using ``FileSystem``.
///
/// - Parameters:
/// - path: The path of the file to read.
/// - maximumSizeAllowed: The maximum size of file which can be read, as a ``ByteCount``.
public init(
contentsOf path: FilePath,
maximumSizeAllowed: ByteCount
) async throws {
self = try await Self(
contentsOf: path,
maximumSizeAllowed: maximumSizeAllowed,
fileSystem: .shared
)
}
}
#endif
57 changes: 57 additions & 0 deletions Sources/NIOFileSystemFoundationCompat/Data+FileSystem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android)
import _NIOFileSystem
import NIOCore
import NIOFoundationCompat
import struct Foundation.Data

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension Data {
/// Reads the contents of the file at the path.
///
/// - Parameters:
/// - path: The path of the file to read.
/// - maximumSizeAllowed: The maximum size of file which can be read, in bytes, as a ``ByteCount``.
/// - fileSystem: The ``FileSystemProtocol`` instance to use to read the file.
public init(
contentsOf path: FilePath,
maximumSizeAllowed: ByteCount,
fileSystem: some FileSystemProtocol
) async throws {
let byteBuffer = try await fileSystem.withFileHandle(forReadingAt: path) { handle in
try await handle.readToEnd(maximumSizeAllowed: maximumSizeAllowed)
}

self = Data(buffer: byteBuffer)
}

/// Reads the contents of the file at the path using ``FileSystem``.
///
/// - Parameters:
/// - path: The path of the file to read.
/// - maximumSizeAllowed: The maximum size of file which can be read, as a ``ByteCount``.
public init(
contentsOf path: FilePath,
maximumSizeAllowed: ByteCount
) async throws {
self = try await Self(
contentsOf: path,
maximumSizeAllowed: maximumSizeAllowed,
fileSystem: .shared
)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,37 @@ import _NIOFileSystem
import _NIOFileSystemFoundationCompat
import XCTest

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension FileSystem {
func temporaryFilePath(
_ function: String = #function,
inTemporaryDirectory: Bool = true
) async throws -> FilePath {
if inTemporaryDirectory {
let directory = try await self.temporaryDirectory
return self.temporaryFilePath(function, inDirectory: directory)
} else {
return self.temporaryFilePath(function, inDirectory: nil)
}
}

func temporaryFilePath(
_ function: String = #function,
inDirectory directory: FilePath?
) -> FilePath {
let index = function.firstIndex(of: "(")!
let functionName = function.prefix(upTo: index)
let random = UInt32.random(in: .min ... .max)
let fileName = "\(functionName)-\(random)"

if let directory = directory {
return directory.appending(fileName)
} else {
return FilePath(fileName)
}
}
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
final class FileSystemBytesConformanceTests: XCTestCase {
func testTimepecToDate() async throws {
Expand All @@ -33,5 +64,18 @@ final class FileSystemBytesConformanceTests: XCTestCase {
Date(timeIntervalSince1970: 1.000000001)
)
}

func testReadFileIntoData() async throws {
let fs = FileSystem.shared
let path = try await fs.temporaryFilePath()

try await fs.withFileHandle(forReadingAndWritingAt: path) { fileHandle in
_ = try await fileHandle.write(contentsOf: [0, 1, 2], toAbsoluteOffset: 0)
}

let contents = try await Data(contentsOf: path, maximumSizeAllowed: .bytes(1024))

XCTAssertEqual(contents, Data([0, 1, 2]))
}
}
#endif
24 changes: 24 additions & 0 deletions Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1828,6 +1828,30 @@ extension FileSystemTests {
)
}
}

func testReadIntoArray() async throws {
let path = try await self.fs.temporaryFilePath()

try await self.fs.withFileHandle(forReadingAndWritingAt: path) { fileHandle in
_ = try await fileHandle.write(contentsOf: [0, 1, 2], toAbsoluteOffset: 0)
}

let contents = try await Array(contentsOf: path, maximumSizeAllowed: .bytes(1024))

XCTAssertEqual(contents, [0, 1, 2])
}

func testReadIntoArraySlice() async throws {
let path = try await self.fs.temporaryFilePath()

try await self.fs.withFileHandle(forReadingAndWritingAt: path) { fileHandle in
_ = try await fileHandle.write(contentsOf: [0, 1, 2], toAbsoluteOffset: 0)
}

let contents = try await ArraySlice(contentsOf: path, maximumSizeAllowed: .bytes(1024))

XCTAssertEqual(contents, [0, 1, 2])
}
}

#if !canImport(Darwin) && swift(<5.9.2)
Expand Down

0 comments on commit cc1c57c

Please sign in to comment.