Skip to content

Instantly share code, notes, and snippets.

@alecthomas
Created July 8, 2015 13:38
Show Gist options
  • Save alecthomas/4ec13a414efdf6420c94 to your computer and use it in GitHub Desktop.
Save alecthomas/4ec13a414efdf6420c94 to your computer and use it in GitHub Desktop.
Swift IO
//
// Swift.IO
//
// Created by Alec Thomas on 25/02/2015.
// Copyright (c) 2015 SwapOff. All rights reserved.
//
//
// This file provides a consistent, simple interface to stream-based data sources.
// It is based on Go's I/O library.
//
// All implementations should be concurrent safe unless stated otherwise.
//
// Currently supported are: file descriptors, NSData, NSInputStream and NSOutputStream.
import Foundation
private let CEOF = EOF
// EOF is returned for an end of stream.
public let EOF = NSError(domain: "IO", code: 0, userInfo: ["localizedDescription": "EOF"])
public protocol Reader {
// Read *at most* size bytes.
func read(size: Int) -> (NSData, NSError?)
}
public protocol ByteReader {
func readByte() -> (UInt8, NSError?)
}
public protocol ByteScanner: ByteReader {
func unreadByte(b: UInt8) -> NSError?
}
public protocol Writer {
// Write data. Returns the number of bytes written and any error,
// including the reason for a short write, if any.
func write(data: NSData) -> (Int, NSError?)
}
public protocol ReadWriter: Reader, Writer {}
public protocol Closer {
func close() -> NSError?
}
public protocol ReadCloser: Reader, Closer {}
public protocol WriteCloser: Writer, Closer {}
public protocol ReadWriteCloser: ReadWriter, Reader, Writer, Closer {}
public enum SeekWhence: Int {
case Start = 0
case Current = 1
case End = 2
}
// Implementations of this protocol provide a seek function.
public protocol Seeker {
func seek(offset: Int, whence: SeekWhence) -> (Int, NSError?)
}
public protocol WriteSeeker: Writer, Seeker {}
public protocol ReadSeeker: Reader, Seeker {}
public protocol ReadWriteSeeker: ReadWriter, Seeker {}
// Converts a Reader into a ReadCloser with a no-op close() method.
public class NopCloser: ReadCloser {
public var reader: Reader
public init(_ reader: Reader) {
self.reader = reader
}
public func read(size: Int) -> (NSData, NSError?) {
return reader.read(size)
}
public func close() -> NSError? {
return nil
}
}
//public class BufferedReader: Reader, ByteReader {
// private var r: Reader
// private var byte: Byte?
//
// public init(reader: Reader) {
// self.r = reader
// }
//
// public func read(size: Int) -> (NSData, NSError?) {
// if let b = byte {
// var data = NSMutableData(capacity: size)!
// data.bytes[0] = byte!
// }
// }
//
// public func readByte() -> (Byte, NSError?) {
// return (0, nil)
// }
//}
public let DEFAULT_FILE_MODE: mode_t = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP
// A ReadWriteCloser implementation for C file descriptors.
public class File: ReadWriteCloser, Seeker {
private var lock = NSLock()
// The underlying FD.
private(set) public var fd: Int32
// Filename (if known)
private(set) public var name: String?
// Create a File attached to an existing FD.
public init(fd: CInt, name: String? = nil) {
self.fd = fd
self.name = name
}
// Open a file at the given path. See open(2).
public class func open(path: String, oflag: CInt, mode: mode_t = DEFAULT_FILE_MODE) -> (File?, NSError?) {
let fd = Darwin.open(path, oflag, mode)
if fd == -1 {
return (nil, getError())
}
return (File(fd: fd, name: path), nil)
}
// Create a new file at the given path. See creat(2).
public class func create(path: String, mode: mode_t = DEFAULT_FILE_MODE) -> (File?, NSError?) {
let fd = Darwin.creat(path, mode)
if fd == -1 {
return (nil, getError())
}
return (File(fd: fd), nil)
}
// Create a new temporary file.
public class func temporary(mode: mode_t = DEFAULT_FILE_MODE) -> (File?, NSError?) {
let template = strdup(NSTemporaryDirectory().stringByAppendingPathComponent("XXXXX").fileSystemRepresentation())
let fd = mkstemp(template)
let name = String.fromCString(template)
free(template)
if fd == -1 {
return (nil, File.getError())
}
return (File(fd: fd, name: name), nil)
}
public func read(size: Int) -> (NSData, NSError?) {
lock.lock()
var buf = NSMutableData(length: size)!
var err: NSError?
var n = Darwin.read(fd, buf.mutableBytes, buf.length)
if n == -1 {
err = File.getError() ?? newError("unknown error")
n = 0
} else if n == 0 {
err = EOF
}
buf.length = n
lock.unlock()
return (buf, err)
}
public func write(data: NSData) -> (Int, NSError?) {
lock.lock()
var err: NSError?
var n = Darwin.write(fd, data.bytes, data.length)
if n == -1 {
err = File.getError() ?? newError("unknown error")
n = 0
}
lock.unlock()
return (Int(n), err)
}
// Close the File and set its fd to -1.
public func close() -> NSError? {
var err: NSError?
lock.lock()
if fd == -1 {
err = newError("already closed")
} else if Darwin.close(fd) == -1 {
err = File.getError()
}
fd = -1
lock.unlock()
return err
}
public func seek(offset: Int, whence: SeekWhence) -> (Int, NSError?) {
lock.lock()
let offset = Int(Darwin.lseek(fd, off_t(offset), Int32(whence.rawValue)))
lock.unlock()
if offset == -1 {
return (offset, File.getError())
}
return (offset, nil)
}
// Get last error from errno as an NSError.
private class func getError() -> NSError {
var message = String.fromCString(strerror(errno))
return newError(message!)
}
deinit {
close()
}
private struct Static {
static var stdin = File(fd: 0, name: "/dev/stdin")
static var stdout = File(fd: 1, name: "/dev/stdout")
static var stderr = File(fd: 2, name: "/dev/stderr")
}
public class var stdin: File { return Static.stdin }
public class var stdout: File { return Static.stdout }
public class var stderr: File { return Static.stderr }
}
// A Reader over NSData.
public class BufferReader: Reader, Seeker {
let data: NSData
// Cursor into NSData object.
private(set) public var cursor = 0
public init(let _ data: NSData) {
self.data = data
}
public func read(size: Int) -> (NSData, NSError?) {
let count = size > data.length - cursor ? data.length - cursor : size
var bytes = NSData(bytes: data.bytes + cursor, length: count)
cursor += count
return (bytes, nil)
}
public func seek(offset: Int, whence: SeekWhence) -> (Int, NSError?) {
switch whence {
case .Current:
self.cursor += Int(offset)
case .End:
self.cursor = data.length + Int(offset)
case .Start:
self.cursor = Int(offset)
}
if self.cursor < 0 || self.cursor > self.data.length {
return (-1, newError("invalid seek offset"))
}
return (self.cursor, nil)
}
}
// A Buffer provides read and write functions over raw bytes.
// Writes will grow the NSMutableData buffer.
public class Buffer: ReadWriter {
private var lock = NSLock()
private(set) public var data: NSMutableData
private(set) public var cursor: Int = 0
public init(data: NSMutableData) {
self.data = data
}
public convenience init(capacity: Int) {
self.init(data: NSMutableData(capacity: capacity)!)
}
public convenience init() {
self.init(data: NSMutableData())
}
public func read(size: Int) -> (NSData, NSError?) {
lock.lock()
let count = size > data.length - cursor ? data.length - cursor : size
var bytes = NSData(bytes: data.bytes + cursor, length: count)
cursor += count
lock.unlock()
return (bytes, nil)
}
public func write(data: NSData) -> (Int, NSError?) {
lock.lock()
self.data.appendData(data)
let length = data.length
lock.unlock()
return (length, nil)
}
}
// Dedicated IO thread for Swift.IO.
class IOThreadRunLoop: NSObject {
private var ready: dispatch_semaphore_t = dispatch_semaphore_create(0)
private var thread: NSThread?
internal var runloop: NSRunLoop = NSRunLoop()
override init() {
super.init()
NSThread.detachNewThreadSelector("run", toTarget: self, withObject: nil)
dispatch_semaphore_wait(ready, DISPATCH_TIME_FOREVER)
}
@objc private func run() {
NSThread.currentThread().name = "Swift.IO"
self.runloop = NSRunLoop.currentRunLoop()
dispatch_semaphore_signal(ready)
while (true) {
self.runloop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: 0.1))
}
}
}
// Global dedicated IO thread for Swift.IO.
internal var io = IOThreadRunLoop()
// Resumes a queue whenever events are sent on a stream.
public class StreamEventNotifier: NSObject, NSStreamDelegate {
var ready: dispatch_semaphore_t
public init(_ q: dispatch_semaphore_t) {
self.ready = q
super.init()
}
public func stream(theStream: NSStream, handleEvent streamEvent: NSStreamEvent) {
dispatch_semaphore_signal(ready)
}
}
public class NSStreamNotifierMixin {
private var ready: dispatch_semaphore_t = dispatch_semaphore_create(0)
private var notifier: StreamEventNotifier
init() {
notifier = StreamEventNotifier(ready)
}
}
// IO.Reader adapter for an NSInputStream. The stream should NOT already be open.
public class NSInputStreamReader: ReadCloser {
private var istream: NSInputStream
public init(_ stream: NSInputStream) {
self.istream = stream
self.istream.open()
}
public func read(size: Int) -> (NSData, NSError?) {
var bytes = UnsafeMutablePointer<UInt8>.alloc(size)
let count = istream.read(bytes, maxLength: size)
var data = NSData(bytes: bytes, length: count)
return (data, count == 0 ? EOF : istream.streamError)
}
deinit {
close()
}
public func close() -> NSError? {
istream.close()
return istream.streamError
}
}
public class NSOutputStreamWriter: WriteCloser {
private var ostream: NSOutputStream
public init(_ stream: NSOutputStream) {
self.ostream = stream
self.ostream.open()
}
public func write(data: NSData) -> (Int, NSError?) {
let count = ostream.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)
return (count, ostream.streamError)
}
deinit {
close()
}
public func close() -> NSError? {
ostream.close()
return ostream.streamError
}
}
public class ReadWriterFromReaderAndWriter: ReadWriter {
var reader: Reader
var writer: Writer
public init(reader: Reader, writer: Writer) {
self.reader = reader
self.writer = writer
}
public func read(size: Int) -> (NSData, NSError?) {
return reader.read(size)
}
public func write(data: NSData) -> (Int, NSError?) {
return writer.write(data)
}
}
// A ReadWriteCloser around an NSInputStream and NSOutputStream pair.
public class NSStreamReadWriteCloser: ReadWriterFromReaderAndWriter, ReadWriteCloser {
var input: NSInputStreamReader
var output: NSOutputStreamWriter
public init(input: NSInputStream, output: NSOutputStream) {
self.input = NSInputStreamReader(input)
self.output = NSOutputStreamWriter(output)
super.init(reader: self.input, writer: self.output)
}
deinit {
close()
}
public func close() -> NSError? {
var rerr = input.close()
var werr = output.close()
return rerr ?? werr
}
}
// Copies from src to dst until either EOF is reached on src or an error occurs.
// If EOF is reached it does not return an error.
public func Copy(dst: Writer, src: Reader, size: Int = Int.max) -> (Int, NSError?) {
var written = 0
while written < size {
let chunk = min(size - written, 1024)
let (data, err) = src.read(chunk)
written += data.length
if data.length != 0 {
let (wn, werr) = dst.write(data)
if werr != nil || wn < data.length {
return (written + wn, werr)
}
} else if data.length == 0 || err != nil {
if err == EOF {
return (written, nil)
}
return (written, err)
}
}
return (written, nil)
}
// Convenience construct for dealing with (Closer?, NSError?) return values.
//
// Use like so:
//
// with (File.create("/tmp/foo.txt")) {file in
// // Do something with "file"
// }
//
// And to handle errors:
//
// with (File.create("/tmp/foo.txt")) {file in
// // Do something with "file"
// }.error {err in
// // .create() failed with err
// }
public class with {
private var err: NSError?
public init<T: Closer>(_ tuple: (value: T?, err: NSError?), _ success: (T) -> Void) {
self.err = tuple.err
if let value = tuple.value {
success(value)
value.close()
}
}
public init<T: Closer, U>(_ tuple: (value: T?, err: NSError?), _ success: (T) -> Void) {
self.err = tuple.err
if let value = tuple.value {
success(value)
value.close()
}
}
public func error<R>(f: (NSError) -> R) -> Self {
if let err = err {
f(err)
}
return self
}
}
// public class PipeReader: ReadCloser {
// private var ch: Channel<NSData>
//
// public init(ch: Channel<NSData>) {
// self.ch = ch
// }
//
// public func read(size: Int) -> (NSData, NSError?) {
// if let data = <-ch {
// return (data, nil)
// }
// return (NSData(), EOF)
// }
//
// public func close() -> NSError? {
// return nil
// }
// }
//
//
// public class PipeWriter: WriteCloser {
// private var ch: Channel<NSData>
//
// public init(ch: Channel<NSData>) {
// self.ch = ch
// }
//
// public func write(data: NSData) -> (Int, NSError?) {
// ch <- data
// return (data.length, nil)
// }
//
// public func close() -> NSError? {
// return nil
// }
// }
//
//
// public func pipe() -> (PipeReader, PipeWriter) {
// var ch = Channel<NSData>()
// return (PipeReader(ch: ch), PipeWriter(ch: ch))
// }
@johndpope
Copy link

johndpope commented May 27, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment