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 {
private var window : NSWindow!
private var window: NSWindow!
private var button: NSButton!
func applicationDidFinishLaunching(_ aNotification: Notification) {
button = NSButton(title: "Press me!", target: self, action: #selector(pressMe))
button.isEnabled = false
ElixirKit.API.start(
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
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()
menuItemOne.submenu = NSMenu(title: "Demo")
menuItemOne.submenu?.items = [
@ -48,8 +58,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
defer: true)
window.orderFrontRegardless()
window.title = "Demo"
let button = NSButton(title: "Press me!", target: self, action: #selector(pressMe))
window.contentView!.subviews.append(button)
NSApp.setActivationPolicy(.regular)

View file

@ -2,22 +2,27 @@ class Demo
{
public static void Main()
{
ElixirKit.API.Start(name: "demo");
ElixirKit.API.Publish("log", "Hello from C#!");
ElixirKit.API.Subscribe((name, data) =>
{
switch (name)
ElixirKit.API.Start(
name: "demo",
ready: () =>
{
case "log":
Console.WriteLine($"[client] {data}");
break;
ElixirKit.API.Publish("log", "Hello from C#!");
default:
throw new Exception($"unknown event {name}");
ElixirKit.API.Subscribe((name, data) =>
{
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)
}
ElixirKit.API.start(name: "demo")
ElixirKit.API.publish("log", "Hello from Swift!")
ElixirKit.API.start(
name: "demo",
readyHandler: {
ElixirKit.API.publish("log", "Hello from Swift!")
ElixirKit.API.addObserver(queue: .main) { (name, data) in
switch name {
case "log":
print("[client] " + data)
default:
fatalError("unknown event \(name)")
ElixirKit.API.addObserver(queue: .main) { (name, data) in
switch name {
case "log":
print("[client] " + data)
default:
fatalError("unknown event \(name)")
}
}
}
}
)
ElixirKit.API.waitUntilExit()
}

View file

@ -7,31 +7,36 @@ static class DemoMain
{
if (ElixirKit.API.IsMainInstance("com.example.Demo"))
{
ElixirKit.API.Start(name: "demo", exited: (exitCode) =>
{
Application.Exit();
});
ElixirKit.API.Start(
name: "demo",
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) =>
{
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();
Application.Run(new DemoForm());
}

View file

@ -10,6 +10,8 @@ using System.Net.Sockets;
namespace ElixirKit;
public delegate void ReadyHandler();
public delegate void ExitHandler(int ExitCode);
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();
release = new Release(name, exited, logPath);
release = new Release(name, ready, exited, logPath);
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);
listener = new Listener();
@ -187,8 +189,11 @@ internal class Release
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var tcpClient = listener.TcpListener.AcceptTcpClient();
client = new Client(tcpClient);
Task.Run(() => {
var tcpClient = listener.TcpListener.AcceptTcpClient();
client = new Client(tcpClient);
ready();
});
}
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) {
release = Release(name: name, logPath: logPath, terminationHandler: terminationHandler)
public static func start(
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) {
release?.publish(name, data)
release!.publish(name, data)
}
public static func stop() {
release?.stop();
release!.stop();
}
public static func waitUntilExit() {
release?.waitUntilExit();
release!.waitUntilExit();
}
public static func addObserver(queue: OperationQueue?, using: @escaping (((String, String)) -> Void)) {
@ -42,6 +47,7 @@ private class Release {
let logger: Logger
let listener: NWListener
var connection: Connection?
let readyHandler: () -> Void
let semaphore = DispatchSemaphore(value: 0)
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)
listener = try! NWListener(using: .tcp, on: .any)
@ -77,7 +89,7 @@ private class Release {
listener.newConnectionHandler = didAccept(conn:)
listener.start(queue: .global())
let seconds = 15
let seconds = 5
let timeout = DispatchTime.now() + DispatchTimeInterval.seconds(seconds)
if semaphore.wait(timeout: timeout) == .timedOut {
@ -86,7 +98,7 @@ private class Release {
}
public func stop() {
connection!.cancel()
connection?.cancel()
listener.cancel()
waitUntilExit()
}
@ -117,11 +129,12 @@ private class Release {
env["ELIXIRKIT_PORT"] = port
process.environment = env
try! process.run()
semaphore.signal()
}
private func didAccept(conn: NWConnection) {
self.connection = Connection(conn: conn, logger: logger)
semaphore.signal()
readyHandler()
}
}

View file

@ -21,35 +21,40 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
logPath = "\(NSHomeDirectory())/Library/Logs/Livebook.log"
ElixirKit.API.start(name: "app", logPath: logPath) { 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")
switch alert.runModal() {
case .alertSecondButtonReturn:
self.viewLogs()
default:
()
ElixirKit.API.start(
name: "app",
logPath: logPath,
readyHandler: {
if (self.initialURLs == []) {
ElixirKit.API.publish("open", "")
}
else {
for url in self.initialURLs {
ElixirKit.API.publish("open", url.absoluteString)
}
}
}
},
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 == []) {
ElixirKit.API.publish("open", "")
}
else {
for url in self.initialURLs {
ElixirKit.API.publish("open", url.absoluteString)
NSApp.terminate(nil)
}
}
)
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let button = statusItem.button!
@ -81,7 +86,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
fatalError("unknown event \(name)")
}
}
}
func applicationWillTerminate(_ aNotification: Notification) {

View file

@ -88,27 +88,28 @@ class LivebookApp : ApplicationContext
ElixirKit.API.Start(
name: "app",
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) =>
{
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)