14 |
23 |
15 object Server |
24 object Server |
16 { |
25 { |
17 /* protocol */ |
26 /* protocol */ |
18 |
27 |
19 def split_line(line: String): (String, String) = |
28 def split_message(msg: String): (String, String) = |
20 { |
29 { |
21 val head = line.takeWhile(c => Symbol.is_ascii_letter(c) || Symbol.is_ascii_letdig(c)) |
30 val head = msg.takeWhile(c => Symbol.is_ascii_letter(c) || Symbol.is_ascii_letdig(c)) |
22 val rest = line.substring(head.length).dropWhile(Symbol.is_ascii_blank(_)) |
31 val rest = msg.substring(head.length).dropWhile(Symbol.is_ascii_blank(_)) |
23 (head, proper_string(rest) getOrElse "{}") |
32 (head, proper_string(rest) getOrElse "{}") |
24 } |
33 } |
25 |
34 |
26 val commands: Map[String, PartialFunction[(Server, JSON.T), JSON.T]] = |
35 val commands: Map[String, PartialFunction[(Server, JSON.T), JSON.T]] = |
27 Map( |
36 Map( |
31 |
40 |
32 object Reply extends Enumeration |
41 object Reply extends Enumeration |
33 { |
42 { |
34 val OK, ERROR, NOTE = Value |
43 val OK, ERROR, NOTE = Value |
35 |
44 |
36 def unapply(line: String): Option[(Reply.Value, JSON.T)] = |
45 def unapply(msg: String): Option[(Reply.Value, JSON.T)] = |
37 { |
46 { |
38 if (line == "") None |
47 if (msg == "") None |
39 else { |
48 else { |
40 val (reply, output) = split_line(line) |
49 val (reply, output) = split_message(msg) |
41 try { Some((withName(reply), JSON.parse(output, strict = false))) } |
50 try { Some((withName(reply), JSON.parse(output, strict = false))) } |
42 catch { |
51 catch { |
43 case _: NoSuchElementException => None |
52 case _: NoSuchElementException => None |
44 case Exn.ERROR(_) => None |
53 case Exn.ERROR(_) => None |
45 } |
54 } |
63 def close() { socket.close } |
72 def close() { socket.close } |
64 |
73 |
65 val in = new BufferedInputStream(socket.getInputStream) |
74 val in = new BufferedInputStream(socket.getInputStream) |
66 val out = new BufferedOutputStream(socket.getOutputStream) |
75 val out = new BufferedOutputStream(socket.getOutputStream) |
67 |
76 |
68 def read_line(): Option[String] = |
77 def read_message(): Option[String] = |
69 try { Bytes.read_line(in).map(_.text) } |
78 try { |
|
79 Bytes.read_line(in).map(_.text) match { |
|
80 case Some(Value.Int(n)) => |
|
81 Bytes.read_block(in, n).map(bytes => Library.trim_line(bytes.text)) |
|
82 case res => res |
|
83 } |
|
84 } |
70 catch { case _: SocketException => None } |
85 catch { case _: SocketException => None } |
71 |
86 |
72 def write_line(msg: String) |
87 def write_message(msg: String) |
73 { |
88 { |
74 require(split_lines(msg).length <= 1) |
89 val b = UTF8.bytes(msg) |
75 out.write(UTF8.bytes(msg)) |
90 if (b.length > 100 || b.contains(10)) { |
|
91 out.write(UTF8.bytes((b.length + 1).toString)) |
|
92 out.write(10) |
|
93 } |
|
94 out.write(b) |
76 out.write(10) |
95 out.write(10) |
77 try { out.flush() } catch { case _: SocketException => } |
96 try { out.flush() } catch { case _: SocketException => } |
78 } |
97 } |
79 |
98 |
80 def reply(r: Server.Reply.Value, t: JSON.T) |
99 def reply(r: Server.Reply.Value, t: JSON.T) |
81 { |
100 { |
82 write_line(if (t == JSON.empty) r.toString else r.toString + " " + JSON.Format(t)) |
101 write_message(if (t == JSON.empty) r.toString else r.toString + " " + JSON.Format(t)) |
83 } |
102 } |
84 |
103 |
85 def reply_ok(t: JSON.T) { reply(Server.Reply.OK, t) } |
104 def reply_ok(t: JSON.T) { reply(Server.Reply.OK, t) } |
86 def reply_error(t: JSON.T) { reply(Server.Reply.ERROR, t) } |
105 def reply_error(t: JSON.T) { reply(Server.Reply.ERROR, t) } |
87 def reply_error_message(message: String, more: (String, JSON.T)*): Unit = |
106 def reply_error_message(message: String, more: (String, JSON.T)*): Unit = |
101 "server " + quote(name) + " = " + print(port, password) |
120 "server " + quote(name) + " = " + print(port, password) |
102 |
121 |
103 def connection(): Connection = |
122 def connection(): Connection = |
104 { |
123 { |
105 val connection = Connection(new Socket(InetAddress.getByName("127.0.0.1"), port)) |
124 val connection = Connection(new Socket(InetAddress.getByName("127.0.0.1"), port)) |
106 connection.write_line(password) |
125 connection.write_message(password) |
107 connection |
126 connection |
108 } |
127 } |
109 |
128 |
110 def active(): Boolean = |
129 def active(): Boolean = |
111 try { |
130 try { |
112 using(connection())(connection => |
131 using(connection())(connection => |
113 { |
132 { |
114 connection.socket.setSoTimeout(2000) |
133 connection.socket.setSoTimeout(2000) |
115 connection.read_line() == Some(Reply.OK.toString) |
134 connection.read_message() == Some(Reply.OK.toString) |
116 }) |
135 }) |
117 } |
136 } |
118 catch { |
137 catch { |
119 case _: IOException => false |
138 case _: IOException => false |
120 case _: SocketException => false |
139 case _: SocketException => false |
202 { |
221 { |
203 using(SQLite.open_database(Data.database))(db => |
222 using(SQLite.open_database(Data.database))(db => |
204 db.transaction { |
223 db.transaction { |
205 find(db, name) match { |
224 find(db, name) match { |
206 case Some(server_info) => |
225 case Some(server_info) => |
207 using(server_info.connection())(_.write_line("shutdown")) |
226 using(server_info.connection())(_.write_message("shutdown")) |
208 while(server_info.active) { Thread.sleep(50) } |
227 while(server_info.active) { Thread.sleep(50) } |
209 true |
228 true |
210 case None => false |
229 case None => false |
211 } |
230 } |
212 }) |
231 }) |
271 |
290 |
272 override def toString: String = Server.print(port, password) |
291 override def toString: String = Server.print(port, password) |
273 |
292 |
274 private def handle(connection: Server.Connection) |
293 private def handle(connection: Server.Connection) |
275 { |
294 { |
276 connection.read_line() match { |
295 connection.read_message() match { |
277 case Some(line) if line == password => |
296 case Some(msg) if msg == password => |
278 connection.reply_ok(JSON.empty) |
297 connection.reply_ok(JSON.empty) |
279 var finished = false |
298 var finished = false |
280 while (!finished) { |
299 while (!finished) { |
281 connection.read_line() match { |
300 connection.read_message() match { |
282 case None => finished = true |
301 case None => finished = true |
283 case Some("") => |
302 case Some("") => |
284 connection.notify_message("Command 'help' provides list of commands") |
303 connection.notify_message("Command 'help' provides list of commands") |
285 case Some(line) => |
304 case Some(msg) => |
286 val (cmd, input) = Server.split_line(line) |
305 val (cmd, input) = Server.split_message(msg) |
287 Server.commands.get(cmd) match { |
306 Server.commands.get(cmd) match { |
288 case None => connection.reply_error("Bad command " + quote(cmd)) |
307 case None => connection.reply_error("Bad command " + quote(cmd)) |
289 case Some(body) => |
308 case Some(body) => |
290 input match { |
309 input match { |
291 case JSON.Format(arg) => |
310 case JSON.Format(arg) => |
297 connection.reply_error_message( |
316 connection.reply_error_message( |
298 "Bad argument for command", "command" -> cmd, "argument" -> arg) |
317 "Bad argument for command", "command" -> cmd, "argument" -> arg) |
299 } |
318 } |
300 case _ => |
319 case _ => |
301 connection.reply_error_message( |
320 connection.reply_error_message( |
302 "Malformed command-line", "command" -> cmd, "input" -> input) |
321 "Malformed message", "command" -> cmd, "input" -> input) |
303 } |
322 } |
304 } |
323 } |
305 } |
324 } |
306 } |
325 } |
307 case _ => |
326 case _ => |