mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-11-09 13:44:53 +08:00
Fix displaying error dialog and logging errors (#1848)
This commit is contained in:
parent
dcf7297f38
commit
e3a72d0dbe
8 changed files with 155 additions and 110 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue