Fix displaying error dialog and logging errors (#1848)

This commit is contained in:
Wojtek Mach 2023-04-04 12:57:28 +02:00 committed by GitHub
parent dcf7297f38
commit e3a72d0dbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 110 deletions

View file

@ -12,27 +12,37 @@ public struct Demo {
} }
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
private var window : NSWindow! private var window: NSWindow!
private var button: NSButton!
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
button = NSButton(title: "Press me!", target: self, action: #selector(pressMe))
button.isEnabled = false
ElixirKit.API.start( ElixirKit.API.start(
name: "demo", name: "demo",
readyHandler: {
// GUI updates need to happen on the main thread.
DispatchQueue.main.sync {
self.button.isEnabled = true
}
ElixirKit.API.publish("log", "Hello from AppKit!")
ElixirKit.API.addObserver(queue: .main) { (name, data) in
switch name {
case "log":
print("[client] " + data)
default:
fatalError("unknown event \(name)")
}
}
},
terminationHandler: { _ in terminationHandler: { _ in
NSApp.terminate(nil) NSApp.terminate(nil)
} }
) )
ElixirKit.API.publish("log", "Hello from AppKit!")
ElixirKit.API.addObserver(queue: .main) { (name, data) in
switch name {
case "log":
print("[client] " + data)
default:
fatalError("unknown event \(name)")
}
}
let menuItemOne = NSMenuItem() let menuItemOne = NSMenuItem()
menuItemOne.submenu = NSMenu(title: "Demo") menuItemOne.submenu = NSMenu(title: "Demo")
menuItemOne.submenu?.items = [ menuItemOne.submenu?.items = [
@ -48,8 +58,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
defer: true) defer: true)
window.orderFrontRegardless() window.orderFrontRegardless()
window.title = "Demo" window.title = "Demo"
let button = NSButton(title: "Press me!", target: self, action: #selector(pressMe))
window.contentView!.subviews.append(button) window.contentView!.subviews.append(button)
NSApp.setActivationPolicy(.regular) NSApp.setActivationPolicy(.regular)

View file

@ -2,22 +2,27 @@ class Demo
{ {
public static void Main() public static void Main()
{ {
ElixirKit.API.Start(name: "demo"); ElixirKit.API.Start(
ElixirKit.API.Publish("log", "Hello from C#!"); name: "demo",
ready: () =>
ElixirKit.API.Subscribe((name, data) =>
{
switch (name)
{ {
case "log": ElixirKit.API.Publish("log", "Hello from C#!");
Console.WriteLine($"[client] {data}");
break;
default: ElixirKit.API.Subscribe((name, data) =>
throw new Exception($"unknown event {name}"); {
switch (name)
{
case "log":
Console.WriteLine($"[client] {data}");
break;
default:
throw new Exception($"unknown event {name}");
}
});
} }
}); );
ElixirKit.API.WaitForExit(); System.Console.WriteLine($"{ElixirKit.API.WaitForExit()}");
} }
} }

View file

@ -10,17 +10,21 @@ struct Demo {
exit(signal) exit(signal)
} }
ElixirKit.API.start(name: "demo") ElixirKit.API.start(
ElixirKit.API.publish("log", "Hello from Swift!") name: "demo",
readyHandler: {
ElixirKit.API.publish("log", "Hello from Swift!")
ElixirKit.API.addObserver(queue: .main) { (name, data) in ElixirKit.API.addObserver(queue: .main) { (name, data) in
switch name { switch name {
case "log": case "log":
print("[client] " + data) print("[client] " + data)
default: default:
fatalError("unknown event \(name)") fatalError("unknown event \(name)")
}
}
} }
} )
ElixirKit.API.waitUntilExit() ElixirKit.API.waitUntilExit()
} }

View file

@ -7,31 +7,36 @@ static class DemoMain
{ {
if (ElixirKit.API.IsMainInstance("com.example.Demo")) if (ElixirKit.API.IsMainInstance("com.example.Demo"))
{ {
ElixirKit.API.Start(name: "demo", exited: (exitCode) => ElixirKit.API.Start(
{ name: "demo",
Application.Exit(); ready: () =>
}); {
ElixirKit.API.Publish("log", "Hello from Windows Forms!");
ElixirKit.API.Subscribe((name, data) =>
{
switch (name)
{
case "log":
Console.WriteLine($"[client] {data}");
break;
default:
throw new Exception($"unknown event {name}");
}
});
},
exited: (exitCode) =>
{
Application.Exit();
}
);
Application.ApplicationExit += (sender, args) => Application.ApplicationExit += (sender, args) =>
{ {
ElixirKit.API.Stop(); ElixirKit.API.Stop();
}; };
ElixirKit.API.Publish("log", "Hello from Windows Forms!");
ElixirKit.API.Subscribe((name, data) =>
{
switch (name)
{
case "log":
Console.WriteLine($"[client] {data}");
break;
default:
throw new Exception($"unknown event {name}");
}
});
ApplicationConfiguration.Initialize(); ApplicationConfiguration.Initialize();
Application.Run(new DemoForm()); Application.Run(new DemoForm());
} }

View file

@ -10,6 +10,8 @@ using System.Net.Sockets;
namespace ElixirKit; namespace ElixirKit;
public delegate void ReadyHandler();
public delegate void ExitHandler(int ExitCode); public delegate void ExitHandler(int ExitCode);
public delegate void EventHandler(string Name, string Data); public delegate void EventHandler(string Name, string Data);
@ -43,11 +45,11 @@ public static class API
} }
} }
public static void Start(string name, ExitHandler? exited = null, string? logPath = null) public static void Start(string name, ReadyHandler ready, ExitHandler? exited = null, string? logPath = null)
{ {
ensureMainInstance(); ensureMainInstance();
release = new Release(name, exited, logPath); release = new Release(name, ready, exited, logPath);
if (mutex != null) if (mutex != null)
{ {
@ -136,7 +138,7 @@ internal class Release
} }
} }
public Release(string name, ExitHandler? exited = null, string? logPath = null) public Release(string name, ReadyHandler ready, ExitHandler? exited = null, string? logPath = null)
{ {
logger = new Logger(logPath); logger = new Logger(logPath);
listener = new Listener(); listener = new Listener();
@ -187,8 +189,11 @@ internal class Release
process.BeginOutputReadLine(); process.BeginOutputReadLine();
process.BeginErrorReadLine(); process.BeginErrorReadLine();
var tcpClient = listener.TcpListener.AcceptTcpClient(); Task.Run(() => {
client = new Client(tcpClient); var tcpClient = listener.TcpListener.AcceptTcpClient();
client = new Client(tcpClient);
ready();
});
} }
public void Send(string message) public void Send(string message)

View file

@ -13,20 +13,25 @@ public class API {
} }
} }
public static func start(name: String, logPath: String? = nil, terminationHandler: ((Process) -> Void)? = nil) { public static func start(
release = Release(name: name, logPath: logPath, terminationHandler: terminationHandler) name: String,
logPath: String? = nil,
readyHandler: @escaping () -> Void,
terminationHandler: ((Process) -> Void)? = nil) {
release = Release(name: name, logPath: logPath, readyHandler: readyHandler, terminationHandler: terminationHandler)
} }
public static func publish(_ name: String, _ data: String) { public static func publish(_ name: String, _ data: String) {
release?.publish(name, data) release!.publish(name, data)
} }
public static func stop() { public static func stop() {
release?.stop(); release!.stop();
} }
public static func waitUntilExit() { public static func waitUntilExit() {
release?.waitUntilExit(); release!.waitUntilExit();
} }
public static func addObserver(queue: OperationQueue?, using: @escaping (((String, String)) -> Void)) { public static func addObserver(queue: OperationQueue?, using: @escaping (((String, String)) -> Void)) {
@ -42,6 +47,7 @@ private class Release {
let logger: Logger let logger: Logger
let listener: NWListener let listener: NWListener
var connection: Connection? var connection: Connection?
let readyHandler: () -> Void
let semaphore = DispatchSemaphore(value: 0) let semaphore = DispatchSemaphore(value: 0)
var isRunning: Bool { var isRunning: Bool {
@ -50,7 +56,13 @@ private class Release {
} }
} }
init(name: String, logPath: String? = nil, terminationHandler: ((Process) -> Void)? = nil) { init(
name: String,
logPath: String? = nil,
readyHandler: @escaping () -> Void,
terminationHandler: ((Process) -> Void)? = nil) {
self.readyHandler = readyHandler
logger = Logger(logPath: logPath) logger = Logger(logPath: logPath)
listener = try! NWListener(using: .tcp, on: .any) listener = try! NWListener(using: .tcp, on: .any)
@ -77,7 +89,7 @@ private class Release {
listener.newConnectionHandler = didAccept(conn:) listener.newConnectionHandler = didAccept(conn:)
listener.start(queue: .global()) listener.start(queue: .global())
let seconds = 15 let seconds = 5
let timeout = DispatchTime.now() + DispatchTimeInterval.seconds(seconds) let timeout = DispatchTime.now() + DispatchTimeInterval.seconds(seconds)
if semaphore.wait(timeout: timeout) == .timedOut { if semaphore.wait(timeout: timeout) == .timedOut {
@ -86,7 +98,7 @@ private class Release {
} }
public func stop() { public func stop() {
connection!.cancel() connection?.cancel()
listener.cancel() listener.cancel()
waitUntilExit() waitUntilExit()
} }
@ -117,11 +129,12 @@ private class Release {
env["ELIXIRKIT_PORT"] = port env["ELIXIRKIT_PORT"] = port
process.environment = env process.environment = env
try! process.run() try! process.run()
semaphore.signal()
} }
private func didAccept(conn: NWConnection) { private func didAccept(conn: NWConnection) {
self.connection = Connection(conn: conn, logger: logger) self.connection = Connection(conn: conn, logger: logger)
semaphore.signal() readyHandler()
} }
} }

View file

@ -21,35 +21,40 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
logPath = "\(NSHomeDirectory())/Library/Logs/Livebook.log" logPath = "\(NSHomeDirectory())/Library/Logs/Livebook.log"
ElixirKit.API.start(name: "app", logPath: logPath) { process in ElixirKit.API.start(
if process.terminationStatus != 0 { name: "app",
DispatchQueue.main.sync { logPath: logPath,
let alert = NSAlert() readyHandler: {
alert.alertStyle = .critical if (self.initialURLs == []) {
alert.messageText = "Livebook exited with error status \(process.terminationStatus)" ElixirKit.API.publish("open", "")
alert.addButton(withTitle: "Dismiss") }
alert.addButton(withTitle: "View Logs") else {
for url in self.initialURLs {
switch alert.runModal() { ElixirKit.API.publish("open", url.absoluteString)
case .alertSecondButtonReturn:
self.viewLogs()
default:
()
} }
} }
} },
terminationHandler: { process in
if process.terminationStatus != 0 {
DispatchQueue.main.sync {
let alert = NSAlert()
alert.alertStyle = .critical
alert.messageText = "Livebook exited with error status \(process.terminationStatus)"
alert.addButton(withTitle: "Dismiss")
alert.addButton(withTitle: "View Logs")
NSApp.terminate(nil) switch alert.runModal() {
} case .alertSecondButtonReturn:
self.viewLogs()
default:
()
}
}
}
if (self.initialURLs == []) { NSApp.terminate(nil)
ElixirKit.API.publish("open", "")
}
else {
for url in self.initialURLs {
ElixirKit.API.publish("open", url.absoluteString)
} }
} )
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let button = statusItem.button! let button = statusItem.button!
@ -81,7 +86,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
fatalError("unknown event \(name)") fatalError("unknown event \(name)")
} }
} }
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_ aNotification: Notification) {

View file

@ -88,27 +88,28 @@ class LivebookApp : ApplicationContext
ElixirKit.API.Start( ElixirKit.API.Start(
name: "app", name: "app",
logPath: logPath, logPath: logPath,
ready: () => {
ElixirKit.API.Subscribe((name, data) =>
{
switch (name)
{
case "url":
copyURLButton.Enabled = true;
this.url = data;
break;
default:
throw new Exception($"unknown event {name}");
}
});
ElixirKit.API.Publish("open", url);
},
exited: (exitCode) => exited: (exitCode) =>
{ {
Application.Exit(); Application.Exit();
} }
); );
ElixirKit.API.Subscribe((name, data) =>
{
switch (name)
{
case "url":
copyURLButton.Enabled = true;
this.url = data;
break;
default:
throw new Exception($"unknown event {name}");
}
});
ElixirKit.API.Publish("open", url);
} }
private void threadExit(object? sender, EventArgs e) private void threadExit(object? sender, EventArgs e)