-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathSession.swift
252 lines (198 loc) · 6.4 KB
/
Session.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
// Noze.io: miniirc
import leftpad // need the most important module!
import net
/// This mapping is not quite right, given a user could be connected with
/// multiple clients. But it'll do for the demo ;-)
var nickToSession : [ String : Session ] = [:]
var sessionCounter = 0
/// One TCP/IP connection to some user.
///
class Session {
var sessionID : Int
var state = SessionState.Initial
let socket : Socket
let serverID : String? = "noze.io"
var channelsJoined = [ Channel ]()
var nick : String? {
switch state {
case .Initial: return nil
case .NickAssigned(let nick): return nick
case .UserSet (let nick, _): return nick
}
}
var userInfo : UserInfo? {
switch state {
case .Initial, .NickAssigned: return nil
case .UserSet(_, let info): return info
}
}
init(socket: Socket) {
sessionCounter += 1
sessionID = sessionCounter
print("setup session #\(sessionID)")
self.socket = socket
_ = socket
.onFinish { self.unregisterSession() }
.onEnd { self.unregisterSession() }
socket | readlines | line2msg | Writable { messages, done in
//print("handle in session #\(self.sessionID)")
for msg in messages {
_ = self.handle(message: msg)
}
done(nil)
}
sendWelcome()
}
// MARK: - Message Handler
func handle(message m: Message) {
// print("HANDLE: \(m)")
switch m.command {
case .NICK(let name):
guard name != nick else { return }
guard nickToSession[name] == nil else {
send(source: serverID, command: 433, "*", name,
"Nickname is already in use.")
return
}
nickToSession[name] = self
if let nick = nick { nickToSession.removeValue(forKey: nick) }
state = .NickAssigned(name)
case .USER(let info):
state = .UserSet(nick ?? "<unknown>", info)
sendIntro()
case .PING(let who):
send(command: "PONG", who)
case .PRIVMSG(let target, let message):
let nick = self.nick ?? "<unknown>"
if let otherUser = nickToSession[target] {
_ = otherUser.send(source: nick, command: "PRIVMSG", target, message)
}
else if let channel = nameToChannel[target] {
channel.sendMessage(source: nick, message: message)
}
else {
send(source: serverID, command: 401, nick, target,
"No such nick/channel")
}
case .ISON(let nicknames):
var on = [String]()
for nick in nicknames {
guard let _ = nickToSession[nick] else { continue }
on.append(nick)
}
send(source: serverID, command: 303, nick ?? "<unknown>",
on.joined(separator: " "))
case .JOIN(let channels, _):
for channel in channels {
joinChannel(name: channel)
}
case .PART(let channels, _):
for channelName in channels {
if let channel = nameToChannel[channelName] {
channel.part(session: self)
#if swift(>=5)
if let idx = channelsJoined.firstIndex(where: { $0 === channel })
{
channelsJoined.remove(at: idx)
}
#else
if let idx = channelsJoined.index(where: { $0 === channel }) {
channelsJoined.remove(at: idx)
}
#endif
}
}
case .LIST(_, _):
let nick = self.nick ?? "<unknown>"
send(source: serverID, command: 321, nick, "Channel", "Users Name")
for ( _, channel ) in nameToChannel {
send(source: serverID, command: 322, nick, channel.name,
channel.memberNicks.joined(separator: " "))
}
send(source: serverID, command: 323, "End of /LIST")
case .QUIT:
unregisterSession()
case .Unsupported(let cmd, _):
send(source: serverID, command: 421, cmd, "Unknown command")
default:
print("Not handling: \(m)")
}
}
// MARK: - Channels
func joinChannel(name n: String) {
let channel : Channel
if nameToChannel[n] == nil {
// auto-create channel if missing
channel = Channel(name: n)
if let nick = nick {
channel.operators.append(nick)
}
nameToChannel[n] = channel
}
else {
channel = nameToChannel[n]!
}
channel.join(session: self)
channelsJoined.append(channel)
}
// MARK: - Sending
func send(source s: String? = nil, command: Int, _ args: String...) {
let cmd = "\(command)".leftpad(3, c: "0")
send(source: s, command: cmd, args: args)
}
func send(source s: String? = nil, command: String, _ args: String...) {
send(source: s, command: "\(command)", args: args)
}
func send(source s: String? = nil, command: String, args: [String]) {
let prefix = s != nil ? ":\(s!) " : ""
let suffix : String
if args.isEmpty {
suffix = ""
}
else if args.count == 1 {
suffix = " :" + args[0]
}
else {
let regular = args[0..<(args.count - 1)].reduce("") { $0 + " \($1)" }
suffix = "\(regular) :\(args[args.count - 1])"
}
let message = "\(prefix)\(command)\(suffix)\r\n"
//print("S: \(message)")
_ = socket.write(message)
}
// MARK: - Welcome
func sendWelcome() {
send(command: "NOTICE", "*", "*** Welcome to Noze.io!")
}
func sendIntro() {
let nick = self.nick ?? "<unknown>"
send(source: serverID, command: 001, nick,
"Welcome to the Noze.io Internet Relay Chat Network \(nick)")
send(source: serverID, command: 251, nick,
"There are \(nickToSession.count) users on 1 server")
send(source: serverID, command: 376, nick,
"End of /MOTD command.")
}
// MARK: - End Handlers
func unregisterSession() {
// print("finishing session: \(self.socket)")
for channel in channelsJoined {
channel.part(session: self)
}
channelsJoined.removeAll()
if let nick = self.nick {
nickToSession.removeValue(forKey: nick)
}
// TODO: socket.close()
}
}
struct UserInfo {
let username : String
let usermask : UInt32
let realname : String
}
enum SessionState {
case Initial
case NickAssigned(String)
case UserSet(String, UserInfo)
}