/src/swift-nio/Sources/NIOCore/FileRegion.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2017-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 | | #if os(Windows) |
15 | | import ucrt |
16 | | #elseif canImport(Darwin) |
17 | | import Darwin |
18 | | #elseif canImport(Glibc) |
19 | | @preconcurrency import Glibc |
20 | | #elseif canImport(Musl) |
21 | | @preconcurrency import Musl |
22 | | #elseif canImport(Bionic) |
23 | | @preconcurrency import Bionic |
24 | | #elseif canImport(WASILibc) |
25 | | @preconcurrency import WASILibc |
26 | | #else |
27 | | #error("The File Region module was unable to identify your C library.") |
28 | | #endif |
29 | | |
30 | | /// A `FileRegion` represent a readable portion usually created to be sent over the network. |
31 | | /// |
32 | | /// - warning: The `FileRegion` API is deprecated, do not use going forward. It's not marked as `deprecated` yet such |
33 | | /// that users don't get the deprecation warnings affecting their APIs everywhere. For file I/O, please use |
34 | | /// the `NIOFileSystem` API. |
35 | | /// |
36 | | /// Usually a `FileRegion` will allow the underlying transport to use `sendfile` to transfer its content and so allows transferring |
37 | | /// the file content without copying it into user-space at all. If the actual transport implementation really can make use of sendfile |
38 | | /// or if it will need to copy the content to user-space first and use `write` / `writev` is an implementation detail. That said |
39 | | /// using `FileRegion` is the recommended way to transfer file content if possible. |
40 | | /// |
41 | | /// One important note, depending your `ChannelPipeline` setup it may not be possible to use a `FileRegion` as a `ChannelHandler` may |
42 | | /// need access to the bytes (in a `ByteBuffer`) to transform these. |
43 | | /// |
44 | | /// - Note: It is important to manually manage the lifetime of the ``NIOFileHandle`` used to create a ``FileRegion``. |
45 | | /// - Note: As of SwiftNIO 2.77.0, `FileRegion` objects are are thread-safe and the underlying ``NIOFileHandle`` does enforce singular access. |
46 | | public struct FileRegion: Sendable { |
47 | | |
48 | | /// The `NIOFileHandle` that is used by this `FileRegion`. |
49 | | public let fileHandle: NIOFileHandle |
50 | | |
51 | | private let _endIndex: UInt64 |
52 | | private var _readerIndex: _UInt56 |
53 | | |
54 | | /// The current reader index of this `FileRegion` |
55 | | private(set) public var readerIndex: Int { |
56 | 0 | get { |
57 | 0 | Int(self._readerIndex) |
58 | 0 | } |
59 | 0 | set { |
60 | 0 | self._readerIndex = _UInt56(newValue) |
61 | 0 | } |
62 | | } |
63 | | |
64 | | /// The end index of this `FileRegion`. |
65 | 0 | public var endIndex: Int { |
66 | 0 | Int(self._endIndex) |
67 | 0 | } |
68 | | |
69 | | /// Create a new `FileRegion` from an open `NIOFileHandle`. |
70 | | /// |
71 | | /// - Parameters: |
72 | | /// - fileHandle: the `NIOFileHandle` to use. |
73 | | /// - readerIndex: the index (offset) on which the reading will start. |
74 | | /// - endIndex: the index which represent the end of the readable portion. |
75 | 0 | public init(fileHandle: NIOFileHandle, readerIndex: Int, endIndex: Int) { |
76 | 0 | precondition(readerIndex <= endIndex, "readerIndex(\(readerIndex) must be <= endIndex(\(endIndex).") |
77 | 0 |
|
78 | 0 | self.fileHandle = fileHandle |
79 | 0 | self._readerIndex = _UInt56(readerIndex) |
80 | 0 | self._endIndex = UInt64(endIndex) |
81 | 0 | } |
82 | | |
83 | | /// The number of readable bytes within this FileRegion (taking the `readerIndex` and `endIndex` into account). |
84 | 0 | public var readableBytes: Int { |
85 | 0 | endIndex - readerIndex |
86 | 0 | } |
87 | | |
88 | | /// Move the readerIndex forward by `offset`. |
89 | 0 | public mutating func moveReaderIndex(forwardBy offset: Int) { |
90 | 0 | let newIndex = self.readerIndex + offset |
91 | 0 | assert(offset >= 0 && newIndex <= endIndex, "new readerIndex: \(newIndex), expected: range(0, \(endIndex))") |
92 | 0 | self.readerIndex = newIndex |
93 | 0 | } |
94 | | } |
95 | | |
96 | | extension FileRegion { |
97 | | /// Create a new `FileRegion` forming a complete file. |
98 | | /// |
99 | | /// - Parameters: |
100 | | /// - fileHandle: An open `NIOFileHandle` to the file. |
101 | 0 | public init(fileHandle: NIOFileHandle) throws { |
102 | 0 | let eof = try fileHandle.withUnsafeFileDescriptor { (fd: CInt) throws -> off_t in |
103 | 0 | let eof = try SystemCalls.lseek(descriptor: fd, offset: 0, whence: SEEK_END) |
104 | 0 | try SystemCalls.lseek(descriptor: fd, offset: 0, whence: SEEK_SET) |
105 | 0 | return eof |
106 | 0 | } |
107 | 0 | self.init(fileHandle: fileHandle, readerIndex: 0, endIndex: Int(eof)) |
108 | 0 | } |
109 | | |
110 | | } |
111 | | |
112 | | extension FileRegion: Equatable { |
113 | 0 | public static func == (lhs: FileRegion, rhs: FileRegion) -> Bool { |
114 | 0 | lhs.fileHandle === rhs.fileHandle && lhs.readerIndex == rhs.readerIndex && lhs.endIndex == rhs.endIndex |
115 | 0 | } |
116 | | } |
117 | | |
118 | | extension FileRegion: CustomStringConvertible { |
119 | 0 | public var description: String { |
120 | 0 | "FileRegion { handle: \(self.fileHandle), readerIndex: \(self.readerIndex), endIndex: \(self.endIndex) }" |
121 | 0 | } |
122 | | } |