Tightened the source code and README documentations in the library.
This commit is contained in:
@@ -4,11 +4,9 @@ internal import CPlaydate
|
||||
private var httpAPI: UnsafePointer<playdate_http> { Playdate.httpAPI.unsafelyUnwrapped }
|
||||
|
||||
extension Network {
|
||||
/// An HTTP connection to a server. Wraps `HTTPConnection`.
|
||||
///
|
||||
/// The binding stores a back-reference to each wrapper in the
|
||||
/// underlying object's userdata slot so callbacks can recover the
|
||||
/// wrapper; the C userdata slot is therefore reserved by the binding.
|
||||
/// An HTTP connection. Wraps `HTTPConnection`; methods throw `Network.NetError`.
|
||||
/// Callbacks don't retain it: keep it referenced until they fire, as `deinit`
|
||||
/// drops pending callbacks and releases the C connection.
|
||||
public final class HTTPConnection {
|
||||
let pointer: OpaquePointer
|
||||
|
||||
@@ -18,8 +16,8 @@ extension Network {
|
||||
var requestCompleteCallback: ((HTTPConnection) -> Void)?
|
||||
var connectionClosedCallback: ((HTTPConnection) -> Void)?
|
||||
|
||||
/// Requests permission to connect to `server`. If the reply is
|
||||
/// `.ask`, the completion is called later with the user's answer.
|
||||
/// Asks to connect to `server` and its subdomains; call before `init`.
|
||||
/// `purpose` appears in the dialog; `completion` runs only if the reply is `.ask`.
|
||||
@discardableResult
|
||||
public static func requestAccess(server: String, port: Int = 443, useSSL: Bool = true,
|
||||
purpose: String? = nil,
|
||||
@@ -30,8 +28,7 @@ extension Network {
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
/// Opens a connection to `server`. Fails if access has not been
|
||||
/// granted.
|
||||
/// Sends nothing until a request. `nil` if access is denied or not yet granted.
|
||||
public init?(server: String, port: Int = 443, useSSL: Bool = true) {
|
||||
let pointer = server.withCString {
|
||||
httpAPI.pointee.newConnection.unsafelyUnwrapped($0, Int32(port), useSSL)
|
||||
@@ -54,35 +51,35 @@ extension Network {
|
||||
|
||||
// MARK: Configuration
|
||||
|
||||
/// The time to wait for the connection to open, in milliseconds.
|
||||
/// Connect timeout, in ms.
|
||||
public func setConnectTimeout(milliseconds: Int) {
|
||||
httpAPI.pointee.setConnectTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds))
|
||||
}
|
||||
|
||||
/// Whether to keep the connection open after a request completes.
|
||||
/// Whether requests send `Connection: keep-alive`.
|
||||
public func setKeepAlive(_ keepAlive: Bool) {
|
||||
httpAPI.pointee.setKeepAlive.unsafelyUnwrapped(pointer, keepAlive)
|
||||
}
|
||||
|
||||
/// Adds a `Range: bytes=start-end` header to future requests.
|
||||
/// Adds a `Range: bytes=start-end` header.
|
||||
public func setByteRange(start: Int, end: Int) {
|
||||
httpAPI.pointee.setByteRange.unsafelyUnwrapped(pointer, Int32(start), Int32(end))
|
||||
}
|
||||
|
||||
/// The time to wait for incoming data, in milliseconds.
|
||||
/// How long `read` waits for data, in ms (default 1000).
|
||||
public func setReadTimeout(milliseconds: Int) {
|
||||
httpAPI.pointee.setReadTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds))
|
||||
}
|
||||
|
||||
/// The size of the connection's read buffer, in bytes.
|
||||
/// Read buffer size, in bytes (default 64 KB).
|
||||
public func setReadBufferSize(bytes: Int) {
|
||||
httpAPI.pointee.setReadBufferSize.unsafelyUnwrapped(pointer, Int32(bytes))
|
||||
}
|
||||
|
||||
// MARK: Requests
|
||||
|
||||
/// Sends a GET request for `path`. `headers` are raw header lines
|
||||
/// (e.g. "Accept: text/html\r\n").
|
||||
/// GETs `path`, opening the connection if needed. `headers` are extra raw
|
||||
/// header lines (e.g. "Accept: text/html\r\n").
|
||||
public func get(path: String, headers: String = "") throws(NetError) {
|
||||
let error = path.withCString { cPath in
|
||||
headers.withCString { cHeaders in
|
||||
@@ -92,7 +89,7 @@ extension Network {
|
||||
try Network.check(error)
|
||||
}
|
||||
|
||||
/// Sends a POST request for `path` with the given body.
|
||||
/// POSTs `body` to `path`; otherwise like `get`.
|
||||
public func post(path: String, headers: String = "", body: [UInt8]) throws(NetError) {
|
||||
let error = path.withCString { cPath in
|
||||
headers.withCString { cHeaders in
|
||||
@@ -107,7 +104,7 @@ extension Network {
|
||||
try Network.check(error)
|
||||
}
|
||||
|
||||
/// Sends a request with an arbitrary HTTP method.
|
||||
/// Sends a `method` request; otherwise like `post`.
|
||||
public func query(method: String, path: String, headers: String = "",
|
||||
body: [UInt8] = []) throws(NetError) {
|
||||
let error = method.withCString { cMethod in
|
||||
@@ -127,31 +124,30 @@ extension Network {
|
||||
|
||||
// MARK: Response
|
||||
|
||||
/// The last error on the connection, if any.
|
||||
/// The connection's last error, if any.
|
||||
public var error: NetError? {
|
||||
Network.optionalError(httpAPI.pointee.getError.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The number of bytes read of the current response, and the total
|
||||
/// expected (0 if the response has no Content-Length).
|
||||
/// Response bytes read so far, and the total expected if known.
|
||||
public var progress: (read: Int, total: Int) {
|
||||
var read: Int32 = 0, total: Int32 = 0
|
||||
httpAPI.pointee.getProgress.unsafelyUnwrapped(pointer, &read, &total)
|
||||
return (Int(read), Int(total))
|
||||
}
|
||||
|
||||
/// The HTTP status code of the response.
|
||||
/// HTTP status code, valid once headers are parsed.
|
||||
public var responseStatus: Int {
|
||||
Int(httpAPI.pointee.getResponseStatus.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The number of response bytes available to read.
|
||||
/// Response bytes available to read.
|
||||
public var bytesAvailable: Int {
|
||||
Int(httpAPI.pointee.getBytesAvailable.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// Reads up to `buffer.count` response bytes. Returns the number of
|
||||
/// bytes read.
|
||||
/// Reads up to `buffer.count` bytes (capped by the read buffer size), waiting
|
||||
/// up to the read timeout. Returns the count read.
|
||||
public func read(into buffer: inout MutableSpan<UInt8>) throws(NetError) -> Int {
|
||||
let result = buffer.withUnsafeMutableBufferPointer { buffer in
|
||||
httpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count))
|
||||
@@ -162,7 +158,7 @@ extension Network {
|
||||
return Int(result)
|
||||
}
|
||||
|
||||
/// Reads up to `length` available response bytes.
|
||||
/// Like `read(into:)`, returning the bytes read.
|
||||
public func read(length: Int) throws(NetError) -> [UInt8] {
|
||||
try [UInt8](capacity: length) { output throws(NetError) in
|
||||
let result = output.withUnsafeMutableBufferPointer { buffer, initializedCount in
|
||||
@@ -177,14 +173,14 @@ extension Network {
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the connection.
|
||||
/// Closes the connection; it can be reused for another request.
|
||||
public func close() {
|
||||
httpAPI.pointee.close.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
|
||||
// MARK: Callbacks
|
||||
|
||||
/// Called for each header line as it arrives.
|
||||
/// Called per response header line. `nil` removes it.
|
||||
public func setHeaderReceivedCallback(_ callback: ((HTTPConnection, _ key: String, _ value: String) -> Void)?) {
|
||||
headerReceivedCallback = callback
|
||||
if callback != nil {
|
||||
@@ -199,7 +195,8 @@ extension Network {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when all headers have been read.
|
||||
/// Called once headers are parsed, making `responseStatus` and `progress` valid.
|
||||
/// `nil` removes it.
|
||||
public func setHeadersReadCallback(_ callback: ((HTTPConnection) -> Void)?) {
|
||||
headersReadCallback = callback
|
||||
if callback != nil {
|
||||
@@ -212,7 +209,7 @@ extension Network {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when response data is available to read.
|
||||
/// Called when response data is available to read. `nil` removes it.
|
||||
public func setResponseCallback(_ callback: ((HTTPConnection) -> Void)?) {
|
||||
responseCallback = callback
|
||||
if callback != nil {
|
||||
@@ -225,7 +222,7 @@ extension Network {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the request finishes.
|
||||
/// Called when all data arrives (size known) or the request times out. `nil` removes it.
|
||||
public func setRequestCompleteCallback(_ callback: ((HTTPConnection) -> Void)?) {
|
||||
requestCompleteCallback = callback
|
||||
if callback != nil {
|
||||
@@ -238,7 +235,7 @@ extension Network {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the connection closes.
|
||||
/// Called when the server closes the connection. `nil` removes it.
|
||||
public func setConnectionClosedCallback(_ callback: ((HTTPConnection) -> Void)?) {
|
||||
connectionClosedCallback = callback
|
||||
if callback != nil {
|
||||
|
||||
@@ -4,19 +4,17 @@ internal import CPlaydate
|
||||
private var tcpAPI: UnsafePointer<playdate_tcp> { Playdate.tcpAPI.unsafelyUnwrapped }
|
||||
|
||||
extension Network {
|
||||
/// A TCP connection to a server. Wraps `TCPConnection`.
|
||||
///
|
||||
/// The binding stores a back-reference to each wrapper in the
|
||||
/// underlying object's userdata slot so callbacks can recover the
|
||||
/// wrapper; the C userdata slot is therefore reserved by the binding.
|
||||
/// A TCP connection. Wraps `TCPConnection`; methods throw `Network.NetError`.
|
||||
/// Callbacks don't retain it: keep it referenced until they fire, as `deinit`
|
||||
/// drops pending callbacks and releases the C connection.
|
||||
public final class TCPConnection {
|
||||
let pointer: OpaquePointer
|
||||
|
||||
var openCompletion: ((TCPConnection, NetError?) -> Void)?
|
||||
var connectionClosedCallback: ((TCPConnection, NetError?) -> Void)?
|
||||
|
||||
/// Requests permission to connect to `server`. If the reply is
|
||||
/// `.ask`, the completion is called later with the user's answer.
|
||||
/// Asks to connect to `server`; call before `init`. `purpose` appears in
|
||||
/// the dialog; `completion` runs only if the reply is `.ask`.
|
||||
@discardableResult
|
||||
public static func requestAccess(server: String, port: Int, useSSL: Bool = true,
|
||||
purpose: String? = nil,
|
||||
@@ -27,8 +25,7 @@ extension Network {
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
/// Creates a connection to `server`. Fails if access has not been
|
||||
/// granted. Call `open(_:)` to connect.
|
||||
/// Does nothing until `open(_:)`. `nil` if access is denied or not yet granted.
|
||||
public init?(server: String, port: Int, useSSL: Bool = true) {
|
||||
let pointer = server.withCString {
|
||||
tcpAPI.pointee.newConnection.unsafelyUnwrapped($0, Int32(port), useSSL)
|
||||
@@ -49,17 +46,17 @@ extension Network {
|
||||
return Unmanaged<TCPConnection>.fromOpaque(userdata).takeUnretainedValue()
|
||||
}
|
||||
|
||||
/// The last error on the connection, if any.
|
||||
/// The connection's last error, if any.
|
||||
public var error: NetError? {
|
||||
Network.optionalError(tcpAPI.pointee.getError.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The time to wait for the connection to open, in milliseconds.
|
||||
/// Connect timeout, in ms.
|
||||
public func setConnectTimeout(milliseconds: Int) {
|
||||
tcpAPI.pointee.setConnectTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds))
|
||||
}
|
||||
|
||||
/// Opens the connection. The completion receives `nil` on success.
|
||||
/// Errors are thrown immediately or passed to `completion` (`nil` on success).
|
||||
public func open(_ completion: @escaping (TCPConnection, NetError?) -> Void) throws(NetError) {
|
||||
openCompletion = completion
|
||||
let error = tcpAPI.pointee.open.unsafelyUnwrapped(pointer, { connection, error, _ in
|
||||
@@ -71,13 +68,12 @@ extension Network {
|
||||
try Network.check(error)
|
||||
}
|
||||
|
||||
/// Closes the connection.
|
||||
/// Closes the connection; it can be reused.
|
||||
public func close() throws(NetError) {
|
||||
try Network.check(tcpAPI.pointee.close.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// Called when the connection closes, with the reason if it closed
|
||||
/// due to an error.
|
||||
/// Called on close with the error, if any; `nil` removes it.
|
||||
public func setConnectionClosedCallback(_ callback: ((TCPConnection, NetError?) -> Void)?) {
|
||||
connectionClosedCallback = callback
|
||||
if callback != nil {
|
||||
@@ -90,28 +86,27 @@ extension Network {
|
||||
}
|
||||
}
|
||||
|
||||
/// The time to wait for incoming data, in milliseconds.
|
||||
/// How long `read` waits for data, in ms (default 1000).
|
||||
public func setReadTimeout(milliseconds: Int) {
|
||||
tcpAPI.pointee.setReadTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds))
|
||||
}
|
||||
|
||||
/// The size of the connection's read buffer, in bytes.
|
||||
/// Read buffer size, in bytes (default 64 KB).
|
||||
public func setReadBufferSize(bytes: Int) {
|
||||
tcpAPI.pointee.setReadBufferSize.unsafelyUnwrapped(pointer, Int32(bytes))
|
||||
}
|
||||
|
||||
/// The number of bytes available to read.
|
||||
/// Bytes available to read.
|
||||
public var bytesAvailable: Int {
|
||||
Int(tcpAPI.pointee.getBytesAvailable.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The number of written bytes not yet sent on the wire.
|
||||
/// Written bytes not yet sent.
|
||||
public var sentBytesPending: Int {
|
||||
Int(tcpAPI.pointee.getSentBytesPending.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// Reads up to `buffer.count` bytes, waiting up to the read timeout.
|
||||
/// Returns the number of bytes read.
|
||||
/// Reads up to `buffer.count` bytes within the read timeout; returns the count.
|
||||
public func read(into buffer: inout MutableSpan<UInt8>) throws(NetError) -> Int {
|
||||
let result = buffer.withUnsafeMutableBufferPointer { buffer in
|
||||
tcpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count)
|
||||
@@ -122,7 +117,7 @@ extension Network {
|
||||
return Int(result)
|
||||
}
|
||||
|
||||
/// Reads up to `length` bytes, waiting up to the read timeout.
|
||||
/// Like `read(into:)`, returning the bytes read.
|
||||
public func read(length: Int) throws(NetError) -> [UInt8] {
|
||||
try [UInt8](capacity: length) { output throws(NetError) in
|
||||
let result = output.withUnsafeMutableBufferPointer { buffer, initializedCount in
|
||||
@@ -136,8 +131,7 @@ extension Network {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the bytes to the connection. Returns the number of bytes
|
||||
/// accepted.
|
||||
/// Queues `bytes`; returns the count handed to the network stack.
|
||||
@discardableResult
|
||||
public func write(_ bytes: Span<UInt8>) throws(NetError) -> Int {
|
||||
let result = bytes.withUnsafeBufferPointer { buffer in
|
||||
@@ -149,8 +143,7 @@ extension Network {
|
||||
return Int(result)
|
||||
}
|
||||
|
||||
/// Writes the bytes to the connection. Returns the number of bytes
|
||||
/// accepted.
|
||||
/// Same as the `Span` overload.
|
||||
@discardableResult
|
||||
public func write(_ bytes: [UInt8]) throws(NetError) -> Int {
|
||||
try bytes.withUnsafeBufferPointer { buffer throws(NetError) in
|
||||
|
||||
@@ -1,31 +1,47 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Network {
|
||||
/// A network error code (`PDNetErr`).
|
||||
/// A network error. Wraps the negative `PDNetErr` codes.
|
||||
public enum NetError: Int32, Swift.Error, Sendable {
|
||||
/// `NET_NO_DEVICE`.
|
||||
case noDevice = -1
|
||||
/// `NET_BUSY`.
|
||||
case busy = -2
|
||||
/// `NET_WRITE_ERROR`.
|
||||
case writeError = -3
|
||||
/// `NET_WRITE_BUSY`.
|
||||
case writeBusy = -4
|
||||
/// `NET_WRITE_TIMEOUT`.
|
||||
case writeTimeout = -5
|
||||
/// `NET_READ_ERROR`.
|
||||
case readError = -6
|
||||
/// `NET_READ_BUSY`.
|
||||
case readBusy = -7
|
||||
/// `NET_READ_TIMEOUT`.
|
||||
case readTimeout = -8
|
||||
/// `NET_READ_OVERFLOW`.
|
||||
case readOverflow = -9
|
||||
/// `NET_FRAME_ERROR`.
|
||||
case frameError = -10
|
||||
/// `NET_BAD_RESPONSE`.
|
||||
case badResponse = -11
|
||||
/// `NET_ERROR_RESPONSE`.
|
||||
case errorResponse = -12
|
||||
/// `NET_RESET_TIMEOUT`.
|
||||
case resetTimeout = -13
|
||||
/// `NET_BUFFER_TOO_SMALL`.
|
||||
case bufferTooSmall = -14
|
||||
/// `NET_UNEXPECTED_RESPONSE`.
|
||||
case unexpectedResponse = -15
|
||||
/// `NET_NOT_CONNECTED_TO_AP`.
|
||||
case notConnectedToAP = -16
|
||||
/// `NET_NOT_IMPLEMENTED`.
|
||||
case notImplemented = -17
|
||||
/// `NET_CONNECTION_CLOSED`.
|
||||
case connectionClosed = -18
|
||||
/// An error code not covered by `PDNetErr`.
|
||||
/// A code not in `PDNetErr`.
|
||||
case unknown = 1
|
||||
|
||||
/// Creates an error from the C code, or `.unknown` for
|
||||
/// unrecognized codes.
|
||||
init(_ error: PDNetErr) {
|
||||
self = NetError(rawValue: Int32(error.rawValue)) ?? .unknown
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
extension Network {
|
||||
/// The device's wifi status.
|
||||
/// The device's wifi status. Wraps `WifiStatus`.
|
||||
public enum WifiStatus: UInt32, Sendable {
|
||||
case notConnected = 0
|
||||
case connected = 1
|
||||
/// A connection was attempted but no configured access point was
|
||||
/// available.
|
||||
/// A connection was attempted but no configured access point was available.
|
||||
case notAvailable = 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,29 +3,28 @@ internal import CPlaydate
|
||||
/// The cached `playdate->network` C API table.
|
||||
private var networkAPI: UnsafePointer<playdate_network> { Playdate.networkAPI.unsafelyUnwrapped }
|
||||
|
||||
/// The network API: wifi status, HTTP, and TCP.
|
||||
/// Wifi control, HTTP, and TCP. Throwing APIs here throw `Network.NetError`.
|
||||
public enum Network {}
|
||||
|
||||
extension Network {
|
||||
/// Throws unless `error` is `NET_OK`.
|
||||
static func check(_ error: PDNetErr) throws(NetError) {
|
||||
if error != NET_OK {
|
||||
throw NetError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an error code to `nil` (OK) or a `NetError`.
|
||||
static func optionalError(_ error: PDNetErr) -> NetError? {
|
||||
error == NET_OK ? nil : NetError(error)
|
||||
}
|
||||
|
||||
/// The device's current wifi status.
|
||||
/// The current wifi status; `.notConnected` for unrecognized C values.
|
||||
public static var status: WifiStatus {
|
||||
WifiStatus(rawValue: UInt32(networkAPI.pointee.getStatus.unsafelyUnwrapped().rawValue)) ?? .notConnected
|
||||
}
|
||||
|
||||
/// Turns the wifi radio on or off. The completion receives `nil` on
|
||||
/// success. Completions of overlapping calls are delivered in call order.
|
||||
/// `true` connects to the configured access point; `false` turns wifi off before
|
||||
/// the 30 s idle timeout. `completion` (documented for `true` only) gets `nil`
|
||||
/// on success; completions fire in call order.
|
||||
public static func setEnabled(_ enabled: Bool, completion: ((NetError?) -> Void)? = nil) {
|
||||
if let completion {
|
||||
setEnabledCompletions.append(completion)
|
||||
@@ -41,7 +40,8 @@ extension Network {
|
||||
|
||||
nonisolated(unsafe) private static var setEnabledCompletions: [(NetError?) -> Void] = []
|
||||
|
||||
/// Requests permission to connect to `server`. Shared by HTTP and TCP.
|
||||
/// Shared by HTTP and TCP. Retains `completion` until the C callback, which
|
||||
/// fires only for `.ask`.
|
||||
static func requestAccess(
|
||||
rawRequest: (UnsafePointer<CChar>?, Int32, Bool, UnsafePointer<CChar>?,
|
||||
(@convention(c) (Bool, UnsafeMutableRawPointer?) -> Void)?,
|
||||
@@ -67,7 +67,7 @@ extension Network {
|
||||
}
|
||||
}
|
||||
if reply != kAccessAsk {
|
||||
// The callback will not be invoked; balance the retain.
|
||||
// Only `kAccessAsk` invokes the callback; balance the retain now.
|
||||
box.release()
|
||||
}
|
||||
return AccessReply(rawValue: UInt32(reply.rawValue)) ?? .ask
|
||||
|
||||
Reference in New Issue
Block a user