| author | paulson <lp15@cam.ac.uk> | 
| Fri, 22 Nov 2024 16:05:42 +0000 | |
| changeset 81473 | 53e61087bc6f | 
| parent 80510 | bbeb2f2049aa | 
| child 82142 | 508a673c87ac | 
| permissions | -rw-r--r-- | 
| 66347 | 1 | /* Title: Pure/Tools/server.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 4 | Resident Isabelle servers. | |
| 67809 | 5 | |
| 6 | Message formats: | |
| 7 | - short message (single line): | |
| 8 | NAME ARGUMENT | |
| 9 | - long message (multiple lines): | |
| 10 | BYTE_LENGTH | |
| 11 | NAME ARGUMENT | |
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 12 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 13 | Argument formats: | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 14 | - Unit as empty string | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 15 | - XML.Elem in YXML notation | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 16 | - JSON.T in standard notation | 
| 66347 | 17 | */ | 
| 18 | ||
| 19 | package isabelle | |
| 20 | ||
| 21 | ||
| 67837 | 22 | import java.io.{BufferedInputStream, BufferedOutputStream, InputStreamReader, OutputStreamWriter,
 | 
| 23 | IOException} | |
| 67797 | 24 | import java.net.{Socket, SocketException, SocketTimeoutException, ServerSocket, InetAddress}
 | 
| 66347 | 25 | |
| 26 | ||
| 75393 | 27 | object Server {
 | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 28 | /* message argument */ | 
| 66927 | 29 | |
| 75393 | 30 |   object Argument {
 | 
| 67903 | 31 | def is_name_char(c: Char): Boolean = | 
| 32 | Symbol.is_ascii_letter(c) || Symbol.is_ascii_digit(c) || c == '_' || c == '.' | |
| 33 | ||
| 75393 | 34 |     def split(msg: String): (String, String) = {
 | 
| 71601 | 35 | val name = msg.takeWhile(is_name_char) | 
| 36 | val argument = msg.substring(name.length).dropWhile(Symbol.is_ascii_blank) | |
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 37 | (name, argument) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 38 | } | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 39 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 40 | def print(arg: Any): String = | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 41 |       arg match {
 | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 42 | case () => "" | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 43 | case t: XML.Elem => YXML.string_of_tree(t) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 44 | case t: JSON.T => JSON.Format(t) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 45 | } | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 46 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 47 | def parse(argument: String): Any = | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 48 | if (argument == "") () | 
| 80480 
972f7a4cdc0e
clarified YXML.Source: more direct support for String and Bytes, instead of CharSequence;
 wenzelm parents: 
79844diff
changeset | 49 | else if (YXML.detect_elem(argument)) YXML.parse_elem(YXML.Source(argument)) | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 50 | else JSON.parse(argument, strict = false) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 51 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 52 | def unapply(argument: String): Option[Any] = | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 53 |       try { Some(parse(argument)) }
 | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 54 |       catch { case ERROR(_) => None }
 | 
| 67794 | 55 | } | 
| 56 | ||
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 57 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 58 | /* input command */ | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 59 | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 60 | type Command_Body = PartialFunction[(Context, Any), Any] | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 61 | |
| 75393 | 62 |   abstract class Command(val command_name: String) {
 | 
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 63 | def command_body: Command_Body | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 64 | override def toString: String = command_name | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 65 | } | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 66 | |
| 75393 | 67 |   class Commands(commands: Command*) extends Isabelle_System.Service {
 | 
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 68 | def entries: List[Command] = commands.toList | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 69 | } | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 70 | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 71 | private lazy val command_table: Map[String, Command] = | 
| 73359 | 72 | Isabelle_System.make_services(classOf[Commands]).flatMap(_.entries). | 
| 73 |       foldLeft(Map.empty[String, Command]) {
 | |
| 74 | case (cmds, cmd) => | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 75 | val name = cmd.command_name | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 76 |           if (cmds.isDefinedAt(name)) error("Duplicate Isabelle server command: " + quote(name))
 | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 77 | else cmds + (name -> cmd) | 
| 73359 | 78 | } | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 79 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 80 | |
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 81 | /* output reply */ | 
| 66929 | 82 | |
| 67857 | 83 | class Error(val message: String, val json: JSON.Object.T = JSON.Object.empty) | 
| 84 | extends RuntimeException(message) | |
| 85 | ||
| 67891 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 86 | def json_error(exn: Throwable): JSON.Object.T = | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 87 |     exn match {
 | 
| 67913 
d58fa3ed236f
proper order of matches: Server.Error is an instance of Exn.ERROR;
 wenzelm parents: 
67911diff
changeset | 88 | case e: Error => Reply.error_message(e.message) ++ e.json | 
| 67901 | 89 | case ERROR(msg) => Reply.error_message(msg) | 
| 90 | case _ if Exn.is_interrupt(exn) => Reply.error_message(Exn.message(exn)) | |
| 67891 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 91 | case _ => JSON.Object.empty | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 92 | } | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 93 | |
| 78596 | 94 |   object Reply {
 | 
| 67901 | 95 | def message(msg: String, kind: String = ""): JSON.Object.T = | 
| 67931 
f7917c15b566
field "kind" is always present, with default "writeln";
 wenzelm parents: 
67920diff
changeset | 96 | JSON.Object(Markup.KIND -> proper_string(kind).getOrElse(Markup.WRITELN), "message" -> msg) | 
| 67901 | 97 | |
| 98 | def error_message(msg: String): JSON.Object.T = | |
| 67911 
3cda747493d8
clarified markup according to common Command.Results;
 wenzelm parents: 
67904diff
changeset | 99 | message(msg, kind = Markup.ERROR) | 
| 67857 | 100 | |
| 78596 | 101 |     def unapply(msg: String): Option[(Reply, Any)] = {
 | 
| 67809 | 102 | if (msg == "") None | 
| 67800 | 103 |       else {
 | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 104 | val (name, argument) = Argument.split(msg) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 105 |         for {
 | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 106 | reply <- | 
| 78596 | 107 |             try { Some(Reply.valueOf(name)) }
 | 
| 108 |             catch { case _: IllegalArgumentException => None }
 | |
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 109 | arg <- Argument.unapply(argument) | 
| 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 110 | } yield (reply, arg) | 
| 67800 | 111 | } | 
| 112 | } | |
| 66927 | 113 | } | 
| 114 | ||
| 78596 | 115 |   enum Reply { case OK, ERROR, FINISHED, FAILED, NOTE }
 | 
| 116 | ||
| 66927 | 117 | |
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 118 | /* handler: port, password, thread */ | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 119 | |
| 75393 | 120 |   abstract class Handler(port0: Int) {
 | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 121 | val socket: ServerSocket = new ServerSocket(port0, 50, Server.localhost) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 122 | def port: Int = socket.getLocalPort | 
| 74145 | 123 | def address: String = print_address(port) | 
| 79717 | 124 | val password: String = UUID.random_string() | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 125 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 126 | override def toString: String = print(port, password) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 127 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 128 | def handle(connection: Server.Connection): Unit | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 129 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 130 | private lazy val thread: Thread = | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 131 |       Isabelle_Thread.fork(name = "server_handler") {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 132 | var finished = false | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 133 |         while (!finished) {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 134 |           Exn.capture(socket.accept) match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 135 | case Exn.Res(client) => | 
| 73179 
f9c71ce29150
tuned name, e.g. relevant for Naproche-SAD debugging in Isabelle/jEdit;
 wenzelm parents: 
73135diff
changeset | 136 |               Isabelle_Thread.fork(name = "client") {
 | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 137 | using(Connection(client))(connection => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 138 | if (connection.read_password(password)) handle(connection)) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 139 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 140 | case Exn.Exn(_) => finished = true | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 141 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 142 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 143 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 144 | |
| 73367 | 145 | def start(): Unit = thread | 
| 74140 | 146 | def join(): Unit = thread.join() | 
| 147 |     def stop(): Unit = { socket.close(); join() }
 | |
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 148 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 149 | |
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 150 | |
| 67786 | 151 | /* socket connection */ | 
| 152 | ||
| 75393 | 153 |   object Connection {
 | 
| 67786 | 154 | def apply(socket: Socket): Connection = | 
| 155 | new Connection(socket) | |
| 156 | } | |
| 157 | ||
| 75393 | 158 |   class Connection private(socket: Socket) extends AutoCloseable {
 | 
| 67786 | 159 | override def toString: String = socket.toString | 
| 160 | ||
| 73367 | 161 | def close(): Unit = socket.close() | 
| 67786 | 162 | |
| 73340 | 163 | def set_timeout(t: Time): Unit = socket.setSoTimeout(t.ms.toInt) | 
| 67832 | 164 | |
| 165 | private val in = new BufferedInputStream(socket.getInputStream) | |
| 166 | private val out = new BufferedOutputStream(socket.getOutputStream) | |
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 167 | private val out_lock: AnyRef = new Object | 
| 67786 | 168 | |
| 71713 | 169 | def tty_loop(): TTY_Loop = | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 170 | new TTY_Loop( | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 171 | new OutputStreamWriter(out), | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 172 | new InputStreamReader(in), | 
| 71713 | 173 | writer_lock = out_lock) | 
| 67837 | 174 | |
| 69464 | 175 | def read_password(password: String): Boolean = | 
| 76324 | 176 |       try { Byte_Message.read_line(in).map(_.text).contains(password) }
 | 
| 69464 | 177 |       catch { case _: IOException => false }
 | 
| 178 | ||
| 74139 | 179 | def read_line_message(): Option[String] = | 
| 69451 | 180 |       try { Byte_Message.read_line_message(in).map(_.text) }
 | 
| 181 |       catch { case _: IOException => None }
 | |
| 67786 | 182 | |
| 74145 | 183 | def read_byte_message(): Option[List[Bytes]] = | 
| 184 |       try { Byte_Message.read_message(in) }
 | |
| 185 |       catch { case _: IOException => None }
 | |
| 186 | ||
| 73133 | 187 | def await_close(): Unit = | 
| 73135 | 188 |       try { Byte_Message.read(in, 1); socket.close() }
 | 
| 73133 | 189 |       catch { case _: IOException => }
 | 
| 190 | ||
| 74139 | 191 | def write_line_message(msg: String): Unit = | 
| 80510 | 192 |       out_lock.synchronized { Byte_Message.write_line_message(out, Bytes(msg)) }
 | 
| 67786 | 193 | |
| 74145 | 194 | def write_byte_message(chunks: List[Bytes]): Unit = | 
| 195 |       out_lock.synchronized { Byte_Message.write_message(out, chunks) }
 | |
| 196 | ||
| 78596 | 197 |     def reply(r: Reply, arg: Any): Unit = {
 | 
| 67820 
e30d6368c7c8
clarified argument formats: explicit Unit, allow XML.Elem as well;
 wenzelm parents: 
67812diff
changeset | 198 | val argument = Argument.print(arg) | 
| 74139 | 199 | write_line_message(if (argument == "") r.toString else r.toString + " " + argument) | 
| 67786 | 200 | } | 
| 201 | ||
| 73340 | 202 | def reply_ok(arg: Any): Unit = reply(Reply.OK, arg) | 
| 203 | def reply_error(arg: Any): Unit = reply(Reply.ERROR, arg) | |
| 67857 | 204 | def reply_error_message(message: String, more: JSON.Object.Entry*): Unit = | 
| 67901 | 205 | reply_error(Reply.error_message(message) ++ more) | 
| 67801 | 206 | |
| 73340 | 207 | def notify(arg: Any): Unit = reply(Reply.NOTE, arg) | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 208 | } | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 209 | |
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 210 | |
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 211 | /* context with output channels */ | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 212 | |
| 67878 | 213 | class Context private[Server](val server: Server, connection: Connection) | 
| 75393 | 214 |   extends AutoCloseable {
 | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 215 | context => | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 216 | |
| 72163 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 217 | def command_list: List[String] = command_table.keys.toList.sorted | 
| 
f5722290a4d0
allow user-defined server commands via isabelle_scala_service;
 wenzelm parents: 
71747diff
changeset | 218 | |
| 78596 | 219 | def reply(r: Reply, arg: Any): Unit = connection.reply(r, arg) | 
| 73340 | 220 | def notify(arg: Any): Unit = connection.notify(arg) | 
| 67857 | 221 | def message(kind: String, msg: String, more: JSON.Object.Entry*): Unit = | 
| 67901 | 222 | notify(Reply.message(msg, kind = kind) ++ more) | 
| 67857 | 223 | def writeln(msg: String, more: JSON.Object.Entry*): Unit = message(Markup.WRITELN, msg, more:_*) | 
| 224 | def warning(msg: String, more: JSON.Object.Entry*): Unit = message(Markup.WARNING, msg, more:_*) | |
| 225 | def error_message(msg: String, more: JSON.Object.Entry*): Unit = | |
| 67911 
3cda747493d8
clarified markup according to common Command.Results;
 wenzelm parents: 
67904diff
changeset | 226 | message(Markup.ERROR, msg, more:_*) | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 227 | |
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 228 | def progress(more: JSON.Object.Entry*): Connection_Progress = | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 229 | new Connection_Progress(context, more:_*) | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 230 | |
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 231 | override def toString: String = connection.toString | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 232 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 233 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 234 | /* asynchronous tasks */ | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 235 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 236 | private val _tasks = Synchronized(Set.empty[Task]) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 237 | |
| 75393 | 238 |     def make_task(body: Task => JSON.Object.T): Task = {
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 239 | val task = new Task(context, body) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 240 | _tasks.change(_ + task) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 241 | task | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 242 | } | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 243 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 244 | def remove_task(task: Task): Unit = | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 245 | _tasks.change(_ - task) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 246 | |
| 69458 | 247 | def cancel_task(id: UUID.T): Unit = | 
| 73367 | 248 |       _tasks.change(tasks => { tasks.find(task => task.id == id).foreach(_.cancel()); tasks })
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 249 | |
| 75393 | 250 |     def close(): Unit = {
 | 
| 251 |       while(_tasks.change_result(tasks => { tasks.foreach(_.cancel()); (tasks.nonEmpty, tasks) })) {
 | |
| 252 | _tasks.value.foreach(_.join()) | |
| 253 | } | |
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 254 | } | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 255 | } | 
| 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 256 | |
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 257 | class Connection_Progress private[Server](context: Context, more: JSON.Object.Entry*) | 
| 75393 | 258 |   extends Progress {
 | 
| 77519 | 259 | override def verbose: Boolean = true | 
| 260 | ||
| 261 |     override def output(message: Progress.Message): Unit = {
 | |
| 262 |       val more1 = ("verbose" -> message.verbose.toString) :: more.toList
 | |
| 263 |       message.kind match {
 | |
| 264 | case Progress.Kind.writeln => context.writeln(message.text, more1:_*) | |
| 265 | case Progress.Kind.warning => context.warning(message.text, more1:_*) | |
| 266 | case Progress.Kind.error_message => context.error_message(message.text, more1:_*) | |
| 77502 
2e2b2bd6b2d2
clarified signature: require just one "override def echo(message: Progress.Message): Unit";
 wenzelm parents: 
77381diff
changeset | 267 | } | 
| 77519 | 268 | } | 
| 68770 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 269 | |
| 75393 | 270 |     override def theory(theory: Progress.Theory): Unit = {
 | 
| 68957 | 271 | val entries: List[JSON.Object.Entry] = | 
| 272 |         List("theory" -> theory.theory, "session" -> theory.session) :::
 | |
| 273 |           (theory.percentage match { case None => Nil case Some(p) => List("percentage" -> p) })
 | |
| 77502 
2e2b2bd6b2d2
clarified signature: require just one "override def echo(message: Progress.Message): Unit";
 wenzelm parents: 
77381diff
changeset | 274 | context.writeln(theory.message.text, entries ::: more.toList:_*) | 
| 68957 | 275 | } | 
| 67839 
0c2ed45ece20
explicit Server.Context with output channels (concurrent write);
 wenzelm parents: 
67838diff
changeset | 276 | |
| 75393 | 277 |     override def nodes_status(nodes_status: Document_Status.Nodes_Status): Unit = {
 | 
| 68903 | 278 | val json = | 
| 76716 
a7602257a825
clarified state document nodes for Theories_Status / Document_Dockable;
 wenzelm parents: 
76324diff
changeset | 279 | for ((name, node_status) <- nodes_status.present() if !node_status.is_empty) | 
| 73132 | 280 |           yield name.json + ("status" -> node_status.json)
 | 
| 68770 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 281 | context.notify(JSON.Object(Markup.KIND -> Markup.NODES_STATUS, Markup.NODES_STATUS -> json)) | 
| 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 282 | } | 
| 
add44e2b8cb0
optional notification of nodes_status (via progress);
 wenzelm parents: 
68530diff
changeset | 283 | |
| 77523 | 284 | override def toString: String = super.toString + ": " + context.toString | 
| 67786 | 285 | } | 
| 286 | ||
| 75393 | 287 |   class Task private[Server](val context: Context, body: Task => JSON.Object.T) {
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 288 | task => | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 289 | |
| 69458 | 290 | val id: UUID.T = UUID.random() | 
| 67885 | 291 |     val ident: JSON.Object.Entry = ("task" -> id.toString)
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 292 | |
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 293 | val progress: Connection_Progress = context.progress(ident) | 
| 73367 | 294 | def cancel(): Unit = progress.stop() | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 295 | |
| 75393 | 296 |     private lazy val thread = Isabelle_Thread.fork(name = "server_task") {
 | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 297 |       Exn.capture { body(task) } match {
 | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 298 | case Exn.Res(res) => | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 299 | context.reply(Reply.FINISHED, res + ident) | 
| 67891 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 300 | case Exn.Exn(exn) => | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 301 | val err = json_error(exn) | 
| 
4f383cd54f69
clarified exception handling: include interrupts;
 wenzelm parents: 
67886diff
changeset | 302 | if (err.isEmpty) throw exn else context.reply(Reply.FAILED, err + ident) | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 303 | } | 
| 73367 | 304 | progress.stop() | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 305 | context.remove_task(task) | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 306 | } | 
| 74140 | 307 | def start(): Unit = thread | 
| 308 | def join(): Unit = thread.join() | |
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 309 | } | 
| 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 310 | |
| 67786 | 311 | |
| 67807 | 312 | /* server info */ | 
| 313 | ||
| 69463 | 314 | val localhost_name: String = "127.0.0.1" | 
| 315 | def localhost: InetAddress = InetAddress.getByName(localhost_name) | |
| 316 | ||
| 317 | def print_address(port: Int): String = localhost_name + ":" + port | |
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 318 | |
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 319 | def print(port: Int, password: String): String = | 
| 69461 | 320 | print_address(port) + " (password " + quote(password) + ")" | 
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 321 | |
| 75393 | 322 |   object Info {
 | 
| 69463 | 323 | private val Pattern = | 
| 324 |       ("""server "([^"]*)" = \Q""" + localhost_name + """\E:(\d+) \(password "([^"]*)"\)""").r
 | |
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 325 | |
| 69463 | 326 | def parse(s: String): Option[Info] = | 
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 327 |       s match {
 | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 328 | case Pattern(name, Value.Int(port), password) => Some(Info(name, port, password)) | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 329 | case _ => None | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 330 | } | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 331 | |
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 332 | def apply(name: String, port: Int, password: String): Info = | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 333 | new Info(name, port, password) | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 334 | } | 
| 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 335 | |
| 75393 | 336 |   class Info private(val name: String, val port: Int, val password: String) {
 | 
| 69461 | 337 | def address: String = print_address(port) | 
| 69460 
5ffe7e17f770
clarified signature, e.g. for re-use by other servers;
 wenzelm parents: 
69458diff
changeset | 338 | |
| 67807 | 339 | override def toString: String = | 
| 67821 
82fb12061069
more uniform output: this may be parsed by another program;
 wenzelm parents: 
67820diff
changeset | 340 | "server " + quote(name) + " = " + print(port, password) | 
| 67807 | 341 | |
| 75393 | 342 |     def connection(): Connection = {
 | 
| 69463 | 343 | val connection = Connection(new Socket(localhost, port)) | 
| 74139 | 344 | connection.write_line_message(password) | 
| 67807 | 345 | connection | 
| 346 | } | |
| 347 | ||
| 73367 | 348 | def active: Boolean = | 
| 67807 | 349 |       try {
 | 
| 75394 | 350 |         using(connection()) { connection =>
 | 
| 75393 | 351 | connection.set_timeout(Time.seconds(2.0)) | 
| 352 |           connection.read_line_message() match {
 | |
| 353 | case Some(Reply(Reply.OK, _)) => true | |
| 354 | case _ => false | |
| 355 | } | |
| 75394 | 356 | } | 
| 67807 | 357 | } | 
| 75426 | 358 |       catch { case _: IOException => false }
 | 
| 67807 | 359 | } | 
| 360 | ||
| 361 | ||
| 66347 | 362 | /* per-user servers */ | 
| 363 | ||
| 67822 | 364 | val default_name = "isabelle" | 
| 365 | ||
| 78396 
7853d9072d1b
renamed object Data to private_data, to emphasize its intended scope (but it is publicly accessible in the database);
 wenzelm parents: 
78375diff
changeset | 366 |   object private_data extends SQL.Data() {
 | 
| 66349 | 367 |     val database = Path.explode("$ISABELLE_HOME_USER/servers.db")
 | 
| 66347 | 368 | |
| 79844 
ac40138234ce
tuned signature: more uniform SQL.Data instances;
 wenzelm parents: 
79777diff
changeset | 369 | override lazy val tables: SQL.Tables = SQL.Tables(Base.table) | 
| 77597 | 370 | |
| 78397 | 371 |     object Base {
 | 
| 372 |       val name = SQL.Column.string("name").make_primary_key
 | |
| 373 |       val port = SQL.Column.int("port")
 | |
| 374 |       val password = SQL.Column.string("password")
 | |
| 375 |       val table = SQL.Table("isabelle_servers", List(name, port, password))
 | |
| 376 | } | |
| 66347 | 377 | |
| 78397 | 378 | def list(db: SQLite.Database): List[Info] = | 
| 379 |       if (db.exists_table(Base.table)) {
 | |
| 380 | db.execute_query_statement(Base.table.select(), | |
| 381 | List.from[Info], | |
| 382 |           { res =>
 | |
| 383 | val name = res.string(Base.name) | |
| 384 | val port = res.int(Base.port) | |
| 385 | val password = res.string(Base.password) | |
| 386 | Info(name, port, password) | |
| 387 | } | |
| 388 | ).sortBy(_.name) | |
| 389 | } | |
| 390 | else Nil | |
| 66347 | 391 | |
| 78397 | 392 | def find(db: SQLite.Database, name: String): Option[Info] = | 
| 393 | list(db).find(server_info => server_info.name == name && server_info.active) | |
| 394 | } | |
| 66347 | 395 | |
| 67870 | 396 | def init( | 
| 397 | name: String = default_name, | |
| 398 | port: Int = 0, | |
| 399 | existing_server: Boolean = false, | |
| 79777 | 400 | log: Logger = new Logger | 
| 75393 | 401 |   ): (Info, Option[Server]) = {
 | 
| 78396 
7853d9072d1b
renamed object Data to private_data, to emphasize its intended scope (but it is publicly accessible in the database);
 wenzelm parents: 
78375diff
changeset | 402 |     using(SQLite.open_database(private_data.database, restrict = true)) { db =>
 | 
| 
7853d9072d1b
renamed object Data to private_data, to emphasize its intended scope (but it is publicly accessible in the database);
 wenzelm parents: 
78375diff
changeset | 403 |       private_data.transaction_lock(db, create = true) {
 | 
| 78397 | 404 | private_data.list(db).filterNot(_.active).foreach(server_info => | 
| 405 | db.execute_statement( | |
| 406 | private_data.Base.table.delete(sql = | |
| 407 | private_data.Base.name.where_equal(server_info.name)))) | |
| 75394 | 408 | } | 
| 78396 
7853d9072d1b
renamed object Data to private_data, to emphasize its intended scope (but it is publicly accessible in the database);
 wenzelm parents: 
78375diff
changeset | 409 |       private_data.transaction_lock(db) {
 | 
| 78397 | 410 |         private_data.find(db, name) match {
 | 
| 75394 | 411 | case Some(server_info) => (server_info, None) | 
| 412 | case None => | |
| 413 |             if (existing_server) error("Isabelle server " + quote(name) + " not running")
 | |
| 67811 
33199d033505
more options: client without implicit server startup;
 wenzelm parents: 
67809diff
changeset | 414 | |
| 75394 | 415 | val server = new Server(port, log) | 
| 416 | val server_info = Info(name, server.port, server.password) | |
| 66347 | 417 | |
| 78397 | 418 | db.execute_statement( | 
| 419 | private_data.Base.table.delete(sql = private_data.Base.name.where_equal(name))) | |
| 420 | db.execute_statement(private_data.Base.table.insert(), body = | |
| 77541 | 421 |               { stmt =>
 | 
| 422 | stmt.string(1) = server_info.name | |
| 423 | stmt.int(2) = server_info.port | |
| 424 | stmt.string(3) = server_info.password | |
| 425 | }) | |
| 66348 | 426 | |
| 75394 | 427 | server.start() | 
| 428 | (server_info, Some(server)) | |
| 66347 | 429 | } | 
| 75394 | 430 | } | 
| 431 | } | |
| 66347 | 432 | } | 
| 433 | ||
| 75393 | 434 |   def exit(name: String = default_name): Boolean = {
 | 
| 78396 
7853d9072d1b
renamed object Data to private_data, to emphasize its intended scope (but it is publicly accessible in the database);
 wenzelm parents: 
78375diff
changeset | 435 |     using(SQLite.open_database(private_data.database)) { db =>
 | 
| 
7853d9072d1b
renamed object Data to private_data, to emphasize its intended scope (but it is publicly accessible in the database);
 wenzelm parents: 
78375diff
changeset | 436 |       private_data.transaction_lock(db) {
 | 
| 78397 | 437 |         private_data.find(db, name) match {
 | 
| 67807 | 438 | case Some(server_info) => | 
| 74139 | 439 |             using(server_info.connection())(_.write_line_message("shutdown"))
 | 
| 73702 
7202e12cb324
tuned signature --- following hints by IntelliJ IDEA;
 wenzelm parents: 
73523diff
changeset | 440 |             while(server_info.active) { Time.seconds(0.05).sleep() }
 | 
| 66347 | 441 | true | 
| 67785 | 442 | case None => false | 
| 66347 | 443 | } | 
| 78162 | 444 | } | 
| 445 | } | |
| 66347 | 446 | } | 
| 447 | ||
| 448 | ||
| 449 | /* Isabelle tool wrapper */ | |
| 450 | ||
| 451 | val isabelle_tool = | |
| 75393 | 452 |     Isabelle_Tool("server", "manage resident Isabelle servers", Scala_Project.here,
 | 
| 75394 | 453 |       { args =>
 | 
| 454 | var console = false | |
| 455 | var log_file: Option[Path] = None | |
| 456 | var operation_list = false | |
| 457 | var operation_exit = false | |
| 458 | var name = default_name | |
| 459 | var port = 0 | |
| 460 | var existing_server = false | |
| 66347 | 461 | |
| 75394 | 462 |         val getopts = Getopts("""
 | 
| 66347 | 463 | Usage: isabelle server [OPTIONS] | 
| 464 | ||
| 465 | Options are: | |
| 67870 | 466 | -L FILE logging on FILE | 
| 67875 | 467 | -c console interaction with specified server | 
| 67904 | 468 | -l list servers (alternative operation) | 
| 67822 | 469 | -n NAME explicit server name (default: """ + default_name + """) | 
| 66348 | 470 | -p PORT explicit server port | 
| 67811 
33199d033505
more options: client without implicit server startup;
 wenzelm parents: 
67809diff
changeset | 471 | -s assume existing server, no implicit startup | 
| 67904 | 472 | -x exit specified server (alternative operation) | 
| 66347 | 473 | |
| 474 | Manage resident Isabelle servers. | |
| 475 | """, | |
| 67870 | 476 | "L:" -> (arg => log_file = Some(Path.explode(File.standard_path(arg)))), | 
| 67875 | 477 | "c" -> (_ => console = true), | 
| 67870 | 478 | "l" -> (_ => operation_list = true), | 
| 66348 | 479 | "n:" -> (arg => name = arg), | 
| 67811 
33199d033505
more options: client without implicit server startup;
 wenzelm parents: 
67809diff
changeset | 480 | "p:" -> (arg => port = Value.Int.parse(arg)), | 
| 67870 | 481 | "s" -> (_ => existing_server = true), | 
| 67876 | 482 | "x" -> (_ => operation_exit = true)) | 
| 66347 | 483 | |
| 75394 | 484 | val more_args = getopts(args) | 
| 485 | if (more_args.nonEmpty) getopts.usage() | |
| 66347 | 486 | |
| 75394 | 487 |         if (operation_list) {
 | 
| 488 |           for {
 | |
| 78397 | 489 | server_info <- using(SQLite.open_database(private_data.database))(private_data.list) | 
| 75394 | 490 | if server_info.active | 
| 491 | } Output.writeln(server_info.toString, stdout = true) | |
| 492 | } | |
| 493 |         else if (operation_exit) {
 | |
| 494 | val ok = Server.exit(name) | |
| 495 | sys.exit(if (ok) Process_Result.RC.ok else Process_Result.RC.failure) | |
| 67834 | 496 | } | 
| 75394 | 497 |         else {
 | 
| 79775 
752806151432
clarified signature: incorporate guard into Logger;
 wenzelm parents: 
79717diff
changeset | 498 | val log = Logger.make_file(log_file) | 
| 75394 | 499 | val (server_info, server) = | 
| 500 | init(name, port = port, existing_server = existing_server, log = log) | |
| 501 | Output.writeln(server_info.toString, stdout = true) | |
| 502 |           if (console) {
 | |
| 503 | using(server_info.connection())(connection => connection.tty_loop().join()) | |
| 504 | } | |
| 505 | server.foreach(_.join()) | |
| 506 | } | |
| 507 | }) | |
| 66347 | 508 | } | 
| 509 | ||
| 75393 | 510 | class Server private(port0: Int, val log: Logger) extends Server.Handler(port0) {
 | 
| 67785 | 511 | server => | 
| 512 | ||
| 69458 | 513 | private val _sessions = Synchronized(Map.empty[UUID.T, Headless.Session]) | 
| 514 |   def err_session(id: UUID.T): Nothing = error("No session " + Library.single_quote(id.toString))
 | |
| 71383 | 515 | def the_session(id: UUID.T): Headless.Session = _sessions.value.getOrElse(id, err_session(id)) | 
| 73340 | 516 | def add_session(entry: (UUID.T, Headless.Session)): Unit = _sessions.change(_ + entry) | 
| 75393 | 517 |   def remove_session(id: UUID.T): Headless.Session = {
 | 
| 67871 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 518 | _sessions.change_result(sessions => | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 519 |       sessions.get(id) match {
 | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 520 | case Some(session) => (session, sessions - id) | 
| 67883 | 521 | case None => err_session(id) | 
| 67871 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 522 | }) | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 523 | } | 
| 
195ff117894c
store session: per Server/Context, not Connection;
 wenzelm parents: 
67870diff
changeset | 524 | |
| 75393 | 525 |   def shutdown(): Unit = {
 | 
| 73367 | 526 | server.socket.close() | 
| 67791 | 527 | |
| 67902 | 528 | val sessions = _sessions.change_result(sessions => (sessions, Map.empty)) | 
| 529 |     for ((_, session) <- sessions) {
 | |
| 530 |       try {
 | |
| 531 | val result = session.stop() | |
| 71747 | 532 |         if (!result.ok) log("Session shutdown failed: " + result.print_rc)
 | 
| 67902 | 533 | } | 
| 534 |       catch { case ERROR(msg) => log("Session shutdown failed: " + msg) }
 | |
| 535 | } | |
| 536 | } | |
| 67791 | 537 | |
| 74140 | 538 |   override def join(): Unit = { super.join(); shutdown() }
 | 
| 66350 | 539 | |
| 75393 | 540 |   override def handle(connection: Server.Connection): Unit = {
 | 
| 75394 | 541 |     using(new Server.Context(server, connection)) { context =>
 | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 542 | connection.reply_ok( | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 543 | JSON.Object( | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 544 | "isabelle_id" -> Isabelle_System.isabelle_id(), | 
| 73523 
2cd23d587db9
further clarification of Isabelle distribution identification -- avoid odd patching of sources;
 wenzelm parents: 
73367diff
changeset | 545 | "isabelle_name" -> Isabelle_System.isabelle_name())) | 
| 67867 | 546 | |
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 547 | var finished = false | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 548 |       while (!finished) {
 | 
| 74139 | 549 |         connection.read_line_message() match {
 | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 550 | case None => finished = true | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 551 |           case Some("") => context.notify("Command 'help' provides list of commands")
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 552 | case Some(msg) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 553 | val (name, argument) = Server.Argument.split(msg) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 554 |             Server.command_table.get(name) match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 555 | case Some(cmd) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 556 |                 argument match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 557 | case Server.Argument(arg) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 558 |                     if (cmd.command_body.isDefinedAt((context, arg))) {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 559 |                       Exn.capture { cmd.command_body((context, arg)) } match {
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 560 | case Exn.Res(task: Server.Task) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 561 | connection.reply_ok(JSON.Object(task.ident)) | 
| 74140 | 562 | task.start() | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 563 | case Exn.Res(res) => connection.reply_ok(res) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 564 | case Exn.Exn(exn) => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 565 | val err = Server.json_error(exn) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 566 | if (err.isEmpty) throw exn else connection.reply_error(err) | 
| 69464 | 567 | } | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 568 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 569 |                     else {
 | 
| 69464 | 570 | connection.reply_error_message( | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 571 | "Bad argument for command " + Library.single_quote(name), | 
| 69464 | 572 | "argument" -> argument) | 
| 73131 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 573 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 574 | case _ => | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 575 | connection.reply_error_message( | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 576 | "Malformed argument for command " + Library.single_quote(name), | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 577 | "argument" -> argument) | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 578 | } | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 579 |               case None => connection.reply_error("Bad command " + Library.single_quote(name))
 | 
| 
ff6b5e468d5f
clarified signature: support more generic server implementations;
 wenzelm parents: 
72763diff
changeset | 580 | } | 
| 69464 | 581 | } | 
| 67860 
5a6c483269f3
support for asynchronous tasks, with "cancel" command;
 wenzelm parents: 
67859diff
changeset | 582 | } | 
| 75394 | 583 | } | 
| 66350 | 584 | } | 
| 66347 | 585 | } |