-
-
Save mingsai/d78f46949169b6485df7 to your computer and use it in GitHub Desktop.
A Swift class to use low-level I/O efficiently. Requires attached Swift String extensions to compensate for missing API calls in Swift 2.2 (specifically the .NS namespace to link to NSString.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// IO.swift | |
// | |
// | |
// Created by Tommie N. Carter, Jr., MBA on 2/12/16. | |
// Copyright © 2016 MING Technology. All rights reserved. | |
// | |
// Based on work of: | |
// | |
// 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. | |
// | |
// Additons by T.Carter | |
// Added FileError type and replaced callback functions | |
// Fixed Swift 1.0 -> 2.0+ syntax | |
// Fixed other invalid code | |
import Foundation | |
private let CEOF = EOF | |
enum FileError:ErrorType { | |
case InvalidSeekOffsetError, ReadError, UnknownError, AlreadyClosedError, CustomErrorMessage(message:String) | |
} | |
// 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().NS.stringByAppendingPathComponent(NSUUID().UUIDString).NS.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() | |
let buf = NSMutableData(length: size)! | |
var err: NSError? | |
var n = Darwin.read(fd, buf.mutableBytes, buf.length) | |
if n == -1 { | |
err = File.getError() ?? (FileError.UnknownError as NSError) | |
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() ?? (FileError.UnknownError as NSError) | |
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 = (FileError.AlreadyClosedError as NSError) | |
} 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 { | |
let message = String.fromCString(strerror(errno)) | |
return (FileError.CustomErrorMessage(message: message!) as NSError) | |
} | |
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 | |
let 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, (FileError.InvalidSeekOffsetError as NSError)) | |
} | |
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 | |
let 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?) { | |
let bytes = UnsafeMutablePointer<UInt8>.alloc(size) | |
let count = istream.read(bytes, maxLength: size) | |
let 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? { | |
let rerr = input.close() | |
let 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? | |
//NOTE: This is a redeclaration | |
// 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 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 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)) | |
// } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// MNGStringExtensions.swift | |
// | |
// | |
// Created by Tommie Carter on 6/29/15. | |
// Copyright © 2015 MING Technology. All rights reserved. | |
// | |
import Foundation | |
extension String { | |
var NS: NSString { return (self as NSString) } | |
func sliceFrom(start: String, to: String) -> String? { | |
return (rangeOfString(start)?.endIndex).flatMap { sInd in | |
(rangeOfString(to, range: sInd..<endIndex)?.startIndex).map { eInd in | |
substringWithRange(sInd..<eInd) | |
} | |
} | |
} | |
var isEmail: Bool { | |
do { | |
let regex = try NSRegularExpression(pattern: "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$", options: .CaseInsensitive) | |
return regex.firstMatchInString(self, options: NSMatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count)) != nil | |
} catch { | |
return false | |
} | |
} | |
// class func timeStringForTimeInterval (timeInterval:NSTimeInterval) { | |
// | |
// let ti = NSInteger(timeInterval) | |
// let seconds:NSInteger = ti % 60; | |
// let minutes:NSInteger = (ti / 60) % 60; | |
// let hours:NSInteger = (ti / 3600); | |
// | |
// if (hours > 0) | |
// { | |
// | |
// var x = CLong(hours) | |
// | |
// | |
// return NSString("\(hours):\(minutes):\(seconds)") | |
// | |
// } | |
// else | |
// { | |
// return [NSString stringWithFormat:@"%02li:%02li", (long)minutes, (long)seconds]; | |
// } | |
// } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment