internal import CPlaydate extension File { /// An open file. Wraps `SDFile`. The file is closed on deinit if it has /// not been closed explicitly. public final class Handle { let pointer: UnsafeMutableRawPointer private var isClosed = false /// Opens the file at `path`. public init(path: String, mode: Options) throws(PlaydateError) { let pointer = path.withCString { fileAPI.pointee.open.unsafelyUnwrapped($0, mode.cValue) } guard let pointer else { throw lastFileError() } self.pointer = pointer } deinit { if !isClosed { _ = fileAPI.pointee.close.unsafelyUnwrapped(pointer) } } /// Closes the file. Further operations are invalid. public func close() throws(PlaydateError) { guard !isClosed else { return } isClosed = true if fileAPI.pointee.close.unsafelyUnwrapped(pointer) != 0 { throw lastFileError() } } /// Reads up to `buffer.count` bytes into `buffer`. Returns the number /// of bytes read; 0 indicates end of file. public func read(into buffer: inout MutableSpan) throws(PlaydateError) -> Int { let result = buffer.withUnsafeMutableBufferPointer { buffer in fileAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) } if result < 0 { throw lastFileError() } return Int(result) } /// Reads up to `length` bytes and returns them. public func read(length: Int) throws(PlaydateError) -> [UInt8] { try [UInt8](capacity: length) { output throws(PlaydateError) in let result = output.withUnsafeMutableBufferPointer { buffer, initializedCount in let result = fileAPI.pointee.read.unsafelyUnwrapped( pointer, buffer.baseAddress, UInt32(buffer.count)) initializedCount = max(Int(result), 0) return result } if result < 0 { throw lastFileError() } } } /// Writes the bytes to the file. Returns the number of bytes written. @discardableResult public func write(_ bytes: Span) throws(PlaydateError) -> Int { let result = bytes.withUnsafeBufferPointer { buffer in fileAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) } if result < 0 { throw lastFileError() } return Int(result) } /// Writes the bytes to the file. Returns the number of bytes written. @discardableResult public func write(_ bytes: [UInt8]) throws(PlaydateError) -> Int { try bytes.withUnsafeBufferPointer { buffer throws(PlaydateError) in try write(buffer.span) } } /// Writes the string's UTF-8 to the file. Returns the bytes written. @discardableResult public func write(_ string: String) throws(PlaydateError) -> Int { let result = string.withCString { cString in fileAPI.pointee.write.unsafelyUnwrapped(pointer, cString, UInt32(string.utf8.count)) } if result < 0 { throw lastFileError() } return Int(result) } /// Flushes buffered writes to disk. Returns the bytes written. @discardableResult public func flush() throws(PlaydateError) -> Int { let result = fileAPI.pointee.flush.unsafelyUnwrapped(pointer) if result < 0 { throw lastFileError() } return Int(result) } /// The current read/write offset. public func tell() throws(PlaydateError) -> Int { let result = fileAPI.pointee.tell.unsafelyUnwrapped(pointer) if result < 0 { throw lastFileError() } return Int(result) } /// Moves the read/write offset to `offset` relative to `origin`. public func seek(to offset: Int, from origin: SeekOrigin = .start) throws(PlaydateError) { if fileAPI.pointee.seek.unsafelyUnwrapped(pointer, Int32(offset), origin.rawValue) != 0 { throw lastFileError() } } } }