Permalink
Browse files

More progress

Added some integration tests to show that everything is hooked up to
receieve channel messages and server messages from the local dev server.
This also showed I was missing a possible type of message: the user
list. Added vague support for it, will need to come back later when I
have example text of a longer user list to be more complete.

Also started to clean up the demo view controller to use the new code
instead of the old experimental stuff.
  • Loading branch information...
sgoodwin committed Aug 17, 2017
1 parent d7f78cb commit 4ffa0cdf95d86712642805f22d59b33a8e956654
@@ -12,6 +12,8 @@
9B9C9F761F1EA61300AFE20B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B9C9F751F1EA61300AFE20B /* Assets.xcassets */; };
9B9C9F791F1EA61300AFE20B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9B9C9F771F1EA61300AFE20B /* Main.storyboard */; };
9B9C9F901F1EA61300AFE20B /* IRCUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B9C9F8F1F1EA61300AFE20B /* IRCUITests.swift */; };
9BA3DB5D1F3EEAFA00B93E2E /* IRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA3DB5C1F3EEAFA00B93E2E /* IRC.swift */; };
9BB23AC51F31A77400F1B747 /* IRCServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB23AC41F31A77400F1B747 /* IRCServerTests.swift */; };
9BE124B41F2331E400860C0A /* IRCServerInputParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE124B31F2331E400860C0A /* IRCServerInputParserTests.swift */; };
9BE124B61F233A5500860C0A /* IRCServerInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE124B51F233A5500860C0A /* IRCServerInputParser.swift */; };
/* End PBXBuildFile section */
@@ -46,6 +48,8 @@
9B9C9F8B1F1EA61300AFE20B /* IRCUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IRCUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9B9C9F8F1F1EA61300AFE20B /* IRCUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IRCUITests.swift; sourceTree = "<group>"; };
9B9C9F911F1EA61300AFE20B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9BA3DB5C1F3EEAFA00B93E2E /* IRC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IRC.swift; sourceTree = "<group>"; };
9BB23AC41F31A77400F1B747 /* IRCServerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IRCServerTests.swift; sourceTree = "<group>"; };
9BE124B31F2331E400860C0A /* IRCServerInputParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IRCServerInputParserTests.swift; sourceTree = "<group>"; };
9BE124B51F233A5500860C0A /* IRCServerInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IRCServerInputParser.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -105,6 +109,7 @@
9B9C9F7B1F1EA61300AFE20B /* IRC.entitlements */,
9B9C9F7A1F1EA61300AFE20B /* Info.plist */,
9BE124B51F233A5500860C0A /* IRCServerInputParser.swift */,
9BA3DB5C1F3EEAFA00B93E2E /* IRC.swift */,
);
path = IRC;
sourceTree = "<group>";
@@ -114,6 +119,7 @@
children = (
9B9C9F861F1EA61300AFE20B /* Info.plist */,
9BE124B31F2331E400860C0A /* IRCServerInputParserTests.swift */,
9BB23AC41F31A77400F1B747 /* IRCServerTests.swift */,
);
path = IRCTests;
sourceTree = "<group>";
@@ -261,6 +267,7 @@
files = (
9B9C9F741F1EA61300AFE20B /* ViewController.swift in Sources */,
9BE124B61F233A5500860C0A /* IRCServerInputParser.swift in Sources */,
9BA3DB5D1F3EEAFA00B93E2E /* IRC.swift in Sources */,
9B9C9F721F1EA61300AFE20B /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -270,6 +277,7 @@
buildActionMask = 2147483647;
files = (
9BE124B41F2331E400860C0A /* IRCServerInputParserTests.swift in Sources */,
9BB23AC51F31A77400F1B747 /* IRCServerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2,4 +2,40 @@
<Bucket
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
<BreakpointContent
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
scope = "0"
stopOnStyle = "0">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SwiftErrorBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../../../../Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSURLSession.h"
timestampString = "524223324.044314"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "555"
endingLineNumber = "555"
landmarkName = "NSURLSessionConfiguration"
landmarkType = "2">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
@@ -27,7 +27,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
View
@@ -1,4 +1,5 @@
//
// AppDelegate.swift
// IRC
//
@@ -18,7 +19,5 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
View
@@ -0,0 +1,160 @@
//
// IRC.swift
// IRC
//
// Created by Samuel Ryan Goodwin on 8/12/17.
// Copyright © 2017 Roundwall Software. All rights reserved.
//
import Foundation
struct IRCUser {
let username: String
let realName: String
let nick: String
}
class IRCChannel {
var delegate: IRCChannelDelegate? = nil {
didSet {
guard let delegate = delegate else {
return
}
buffer.forEach { (line) in
delegate.didRecieveMessage(self, message: line)
}
buffer = []
}
}
let name: String
let server: IRCServer
private var buffer = [String]()
fileprivate init(name: String, server: IRCServer) {
self.name = name
self.server = server
}
fileprivate func receive(_ text: String) {
if let delegate = self.delegate {
delegate.didRecieveMessage(self, message: text)
} else {
buffer.append(text)
}
}
func send(_ text: String) {
}
}
class IRCServer {
var delegate: IRCServerDelegate? {
didSet {
guard let delegate = delegate else {
return
}
buffer.forEach { (line) in
delegate.didRecieveMessage(self, message: line)
}
buffer = []
}
}
private var buffer = [String]()
private var session: URLSession
private var task: URLSessionStreamTask!
private var channels = [IRCChannel]()
private init(hostname: String, port: Int, user: IRCUser, session: URLSession) {
self.session = session
task = session.streamTask(withHostName: hostname, port: port)
task.resume()
read()
send("USER \(user.username) 0 * :\(user.realName)")
send("NICK \(user.nick)")
}
private func read() {
task.readData(ofMinLength: 0, maxLength: 9999, timeout: 0) { (data, atEOF, error) in
guard let data = data, let message = String(data: data, encoding: .utf8) else {
return
}
for line in message.split(separator: "\r\n") {
self.processLine(String(line))
}
self.read()
}
}
private func processLine(_ message: String) {
let input = IRCServerInputParser.parseServerMessage(message)
switch input {
case .serverMessage(_, let message):
print(message)
if let delegate = self.delegate {
delegate.didRecieveMessage(self, message: message)
} else {
self.buffer.append(message)
}
case .joinMessage(let user, let channelName):
self.channels.forEach({ (channel) in
if channel.name == channelName {
channel.receive("\(user) joined \(channelName)")
}
})
case .channelMessage(let channelName, let user, let message):
self.channels.forEach({ (channel) in
if channel.name == channelName {
channel.receive("\(user): \(message)")
}
})
case .userList(let channelName, let users):
self.channels.forEach({ (channel) in
if channel.name == channelName {
users.forEach({ (user) in
channel.receive("\(user) joined")
})
}
})
default:
print("Unknown: \(message)")
}
}
func send(_ message: String) {
task.write((message + "\r\n").data(using: .utf8)!, timeout: 0) { (error) in
if let error = error {
print("Failed to send: \(String(describing: error))")
} else {
print("Sent!")
}
}
}
static func connect(_ hostname: String, port: Int, user: IRCUser, session: URLSession = URLSession.shared) -> IRCServer {
return IRCServer(hostname: hostname, port: port, user: user, session: session)
}
func join(_ channelName: String) -> IRCChannel {
send("JOIN #\(channelName)")
let channel = IRCChannel(name: channelName, server: self)
channels.append(channel)
return channel
}
}
protocol IRCServerDelegate {
func didRecieveMessage(_ server: IRCServer, message: String)
}
protocol IRCChannelDelegate {
func didRecieveMessage(_ channel: IRCChannel, message: String)
}
@@ -37,8 +37,33 @@ struct IRCServerInputParser {
return .joinMessage(user: user, channel: channel)
} else{
let server = source.trimmingCharacters(in: CharacterSet(charactersIn: ": "))
let message = rest.components(separatedBy: ":")[1]
return .serverMessage(server: server, message: message)
// :development.irc.roundwallsoftware.com 353 mukman = #clearlyafakechannel :mukman @sgoodwin\r\n:development.irc.roundwallsoftware.com 366 mukman #clearlyafakechannel :End of /NAMES list.
if rest.hasSuffix(":End of /NAMES list.") {
let scanner = Scanner(string: rest)
scanner.scanUpTo("#", into: nil)
var channel: NSString?
scanner.scanUpTo(" ", into: &channel)
let channelName = (channel as String?)!.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
var users = [String]()
var user: NSString?
scanner.scanUpTo(" ", into: &user)
users.append((user as String?)!.trimmingCharacters(in: CharacterSet(charactersIn: ":")))
return .userList(channel: channelName, users: users)
}
if rest.contains(":") {
let serverMessage = rest.components(separatedBy: ":")[1]
return .serverMessage(server: server, message: serverMessage)
} else {
return .serverMessage(server: server, message: rest)
}
}
}
@@ -52,6 +77,7 @@ enum IRCServerInput: Equatable {
case serverMessage(server: String, message: String)
case channelMessage(channel: String, user: String, message: String)
case joinMessage(user: String, channel: String)
case userList(channel: String, users: [String])
}
func ==(lhs: IRCServerInput, rhs: IRCServerInput) -> Bool{
@@ -66,6 +92,8 @@ func ==(lhs: IRCServerInput, rhs: IRCServerInput) -> Bool{
return lhsServer == rhsServer && lhsMessage == rhsMessage
case (.joinMessage(let lhsUser, let lhsChannel), .joinMessage(let rhsUser, let rhsChannel)):
return lhsUser == rhsUser && lhsChannel == rhsChannel
case (.userList(let lhsChannel, let lhsUsers), .userList(let rhsChannel, let rhsUsers)):
return lhsChannel == rhsChannel && lhsUsers == rhsUsers
default:
return false
}
Oops, something went wrong.

0 comments on commit 4ffa0cd

Please sign in to comment.