private async Task<Message> RunLoop(CancellationToken loopCt) {
TaskUtilities.AssertIsOnBackgroundThread();
var cancelAllCtsLink = CancellationTokenSource.CreateLinkedTokenSource(loopCt, _cancelAllCts.Token);
var ct = cancelAllCtsLink.Token;
try {
_log.EnterRLoop(_rLoopDepth++);
while (!loopCt.IsCancellationRequested) {
var message = await ReceiveMessageAsync(loopCt);
if (message == null) {
return null;
} else if (message.IsResponse) {
Request request;
if (!_requests.TryRemove(message.RequestId, out request)) {
throw ProtocolError($"Mismatched response - no request with such ID:", message);
} else if (message.Name != ":" + request.MessageName.Substring(1)) {
throw ProtocolError($"Mismatched response - message name does not match request '{request.MessageName}':", message);
}
request.Handle(this, message);
continue;
}
try {
switch (message.Name) {
case "!End":
message.ExpectArguments(1);
await _callbacks.Shutdown(message.GetBoolean(0, "rdataSaved"));
break;
case "!CanceledAll":
CancelAll();
break;
case "?YesNoCancel":
ShowDialog(message, MessageButtons.YesNoCancel, ct)
.SilenceException<MessageTransportException>()
.DoNotWait();
break;
case "?YesNo":
ShowDialog(message, MessageButtons.YesNo, ct)
.SilenceException<MessageTransportException>()
.DoNotWait();
break;
case "?OkCancel":
ShowDialog(message, MessageButtons.OKCancel, ct)
.SilenceException<MessageTransportException>()
.DoNotWait();
break;
case "?>":
ReadConsole(message, ct)
.SilenceException<MessageTransportException>()
.DoNotWait();
break;
case "!":
case "!!":
message.ExpectArguments(1);
await _callbacks.WriteConsoleEx(
message.GetString(0, "buf", allowNull: true),
message.Name.Length == 1 ? OutputType.Output : OutputType.Error,
ct);
break;
case "!ShowMessage":
message.ExpectArguments(1);
await _callbacks.ShowMessage(message.GetString(0, "s", allowNull: true), ct);
break;
case "!+":
await _callbacks.Busy(true, ct);
break;
case "!-":
await _callbacks.Busy(false, ct);
break;
case "!SetWD":
_callbacks.DirectoryChanged();
break;
case "!Library":
await _callbacks.ViewLibrary(ct);
break;
case "!ShowFile":
message.ExpectArguments(3);
// Do not await since it blocks callback from calling the host again
_callbacks.ShowFile(
message.GetString(0, "file"),
message.GetString(1, "tabName"),
message.GetBoolean(2, "delete.file"),
ct).DoNotWait();
break;
case "!View":
message.ExpectArguments(2);
_callbacks.ViewObject(message.GetString(0, "x"), message.GetString(1, "title"), ct)
.SilenceException<MessageTransportException>()
.DoNotWait();
break;
case "!Plot":
await _callbacks.Plot(
new PlotMessage(
Guid.Parse(message.GetString(0, "device_id")),
Guid.Parse(message.GetString(1, "plot_id")),
message.GetString(2, "file_path"),
message.GetInt32(3, "device_num"),
message.GetInt32(4, "active_plot_index"),
message.GetInt32(5, "plot_count"),
message.Blob),
ct);
break;
case "?Locator":
var locatorResult = await _callbacks.Locator(Guid.Parse(message.GetString(0, "device_id")), ct);
ct.ThrowIfCancellationRequested();
await RespondAsync(message, ct, locatorResult.Clicked, locatorResult.X, locatorResult.Y);
break;
case "?PlotDeviceCreate":
var plotDeviceResult = await _callbacks.PlotDeviceCreate(Guid.Parse(message.GetString(0, "device_id")), ct);
ct.ThrowIfCancellationRequested();
await RespondAsync(message, ct, plotDeviceResult.Width, plotDeviceResult.Height, plotDeviceResult.Resolution);
break;
case "!PlotDeviceDestroy":
await _callbacks.PlotDeviceDestroy(Guid.Parse(message.GetString(0, "device_id")), ct);
break;
case "!WebBrowser":
_callbacks.WebBrowser(message.GetString(0, "url"), ct)
.DoNotWait();
break;
case "!PackagesInstalled":
_callbacks.PackagesInstalled();
break;
case "!PackagesRemoved":
_callbacks.PackagesRemoved();
break;
case "!FetchFile":
var remoteFileName = message.GetString(0, "file_remote_name");
var localPath = message.GetString(1, "file_local_path");
var destPath = await _callbacks.SaveFileAsync(remoteFileName, localPath, message.Blob, ct);
if (!message.GetBoolean(2, "silent")) {
await _callbacks.WriteConsoleEx(destPath, OutputType.Error, ct);
}
break;
default:
throw ProtocolError($"Unrecognized host message name:", message);
}
if (_cancelAllCts.IsCancellationRequested) {
ct = UpdateCancelAllCtsLink(ref cancelAllCtsLink, loopCt);
}
} catch (OperationCanceledException) when (_cancelAllCts.IsCancellationRequested) {
// Canceled via _cancelAllCts - update cancelAllCtsLink and move on
ct = UpdateCancelAllCtsLink(ref cancelAllCtsLink, loopCt);
}
}
} finally {
// asyncronously-running handlers like ReadConsole and ShowDialog that were started in the loop should be canceled
cancelAllCtsLink.Cancel();
cancelAllCtsLink.Dispose();
_log.ExitRLoop(--_rLoopDepth);
}
return null;
}