mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Suppress JsonRPC Empty Response Exception and refactor code
This commit is contained in:
parent
b122dc35c6
commit
76d23aba9b
1 changed files with 126 additions and 122 deletions
|
|
@ -69,7 +69,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
private static readonly JsonSerializerOptions options = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
#pragma warning disable SYSLIB0020
|
||||
#pragma warning disable SYSLIB0020
|
||||
// IgnoreNullValues is obsolete, but the replacement JsonIgnoreCondition.WhenWritingNull still
|
||||
// deserializes null, instead of ignoring it and leaving the default (empty list). We can change the behaviour
|
||||
// to accept null and fallback to a default etc, or just keep IgnoreNullValues for now
|
||||
|
|
@ -247,74 +247,75 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default)
|
||||
{
|
||||
Process process = null;
|
||||
using var exitTokenSource = new CancellationTokenSource();
|
||||
try
|
||||
using var process = Process.Start(startInfo);
|
||||
if (process == null)
|
||||
{
|
||||
process = Process.Start(startInfo);
|
||||
if (process == null)
|
||||
{
|
||||
Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process");
|
||||
return Stream.Null;
|
||||
}
|
||||
|
||||
|
||||
await using var source = process.StandardOutput.BaseStream;
|
||||
|
||||
var buffer = BufferManager.GetStream();
|
||||
|
||||
token.Register(() =>
|
||||
{
|
||||
// ReSharper disable once AccessToModifiedClosure
|
||||
// Manually Check whether disposed
|
||||
if (!exitTokenSource.IsCancellationRequested && !process.HasExited)
|
||||
process.Kill();
|
||||
});
|
||||
Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process");
|
||||
return Stream.Null;
|
||||
}
|
||||
|
||||
token.Register(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// token expire won't instantly trigger the exception,
|
||||
// manually kill process at before
|
||||
await source.CopyToAsync(buffer, token);
|
||||
if (!process.HasExited)
|
||||
process.Kill();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
await buffer.DisposeAsync();
|
||||
return Stream.Null;
|
||||
}
|
||||
});
|
||||
|
||||
buffer.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
var sourceBuffer = BufferManager.GetStream();
|
||||
var errorBuffer = BufferManager.GetStream();
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
var errorMessage = process.StandardError.EndOfStream ?
|
||||
"Empty JSONRPC Response" :
|
||||
await process.StandardError.ReadToEndAsync();
|
||||
throw new InvalidDataException($"{context.CurrentPluginMetadata.Name}|{errorMessage}");
|
||||
}
|
||||
var sourceCopyTask = process.StandardOutput.BaseStream.CopyToAsync(sourceBuffer, token);
|
||||
var errorCopyTask = process.StandardError.BaseStream.CopyToAsync(errorBuffer, token);
|
||||
|
||||
if (!process.StandardError.EndOfStream)
|
||||
{
|
||||
using var standardError = process.StandardError;
|
||||
var error = await standardError.ReadToEndAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
Log.Error($"|{context.CurrentPluginMetadata.Name}.{nameof(ExecuteAsync)}|{error}");
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
finally
|
||||
try
|
||||
{
|
||||
exitTokenSource.Cancel();
|
||||
process?.Dispose();
|
||||
// token expire won't instantly trigger the exception,
|
||||
// manually kill process at before
|
||||
await process.WaitForExitAsync(token);
|
||||
await Task.WhenAll(sourceCopyTask, errorCopyTask);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
await sourceBuffer.DisposeAsync();
|
||||
return Stream.Null;
|
||||
}
|
||||
|
||||
sourceBuffer.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (sourceBuffer.Length == 0)
|
||||
{
|
||||
var errorMessage = errorBuffer.Length == 0 ?
|
||||
"Empty JSONRPC Response" :
|
||||
await process.StandardError.ReadToEndAsync();
|
||||
|
||||
Log.Error($"{context.CurrentPluginMetadata.Name}|{errorMessage}");
|
||||
}
|
||||
|
||||
if (errorBuffer.Length != 0)
|
||||
{
|
||||
using var error = new StreamReader(errorBuffer);
|
||||
|
||||
var errorMessage = await error.ReadToEndAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
Log.Error($"|{context.CurrentPluginMetadata.Name}.{nameof(ExecuteAsync)}|{errorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
return sourceBuffer;
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
var request = new JsonRPCRequestModel
|
||||
|
|
@ -366,6 +367,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
private static readonly Thickness settingPanelMargin = new(15, 20, 15, 20);
|
||||
private static readonly Thickness settingTextBlockMargin = new(10, 4, 10, 4);
|
||||
private JsonRpcConfigurationModel _settingsTemplate;
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
if (Settings == null)
|
||||
|
|
@ -397,84 +399,84 @@ namespace Flow.Launcher.Core.Plugin
|
|||
switch (type)
|
||||
{
|
||||
case "textBlock":
|
||||
{
|
||||
contentControl = new TextBlock
|
||||
{
|
||||
contentControl = new TextBlock
|
||||
{
|
||||
Text = attribute.Description.Replace("\\r\\n", "\r\n"),
|
||||
Margin = settingTextBlockMargin,
|
||||
MaxWidth = 500,
|
||||
TextWrapping = TextWrapping.WrapWithOverflow
|
||||
};
|
||||
break;
|
||||
}
|
||||
Text = attribute.Description.Replace("\\r\\n", "\r\n"),
|
||||
Margin = settingTextBlockMargin,
|
||||
MaxWidth = 500,
|
||||
TextWrapping = TextWrapping.WrapWithOverflow
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "input":
|
||||
{
|
||||
var textBox = new TextBox()
|
||||
{
|
||||
var textBox = new TextBox()
|
||||
{
|
||||
Width = 300,
|
||||
Text = Settings[attribute.Name] as string ?? string.Empty,
|
||||
Margin = settingControlMargin,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
textBox.TextChanged += (_, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = textBox.Text;
|
||||
};
|
||||
contentControl = textBox;
|
||||
break;
|
||||
}
|
||||
Width = 300,
|
||||
Text = Settings[attribute.Name] as string ?? string.Empty,
|
||||
Margin = settingControlMargin,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
textBox.TextChanged += (_, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = textBox.Text;
|
||||
};
|
||||
contentControl = textBox;
|
||||
break;
|
||||
}
|
||||
case "textarea":
|
||||
{
|
||||
var textBox = new TextBox()
|
||||
{
|
||||
var textBox = new TextBox()
|
||||
{
|
||||
Width = 300,
|
||||
Height = 120,
|
||||
Margin = settingControlMargin,
|
||||
TextWrapping = TextWrapping.WrapWithOverflow,
|
||||
AcceptsReturn = true,
|
||||
Text = Settings[attribute.Name] as string ?? string.Empty,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
textBox.TextChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = ((TextBox)sender).Text;
|
||||
};
|
||||
contentControl = textBox;
|
||||
break;
|
||||
}
|
||||
Width = 300,
|
||||
Height = 120,
|
||||
Margin = settingControlMargin,
|
||||
TextWrapping = TextWrapping.WrapWithOverflow,
|
||||
AcceptsReturn = true,
|
||||
Text = Settings[attribute.Name] as string ?? string.Empty,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
textBox.TextChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = ((TextBox)sender).Text;
|
||||
};
|
||||
contentControl = textBox;
|
||||
break;
|
||||
}
|
||||
case "passwordBox":
|
||||
{
|
||||
var passwordBox = new PasswordBox()
|
||||
{
|
||||
var passwordBox = new PasswordBox()
|
||||
{
|
||||
Width = 300,
|
||||
Margin = settingControlMargin,
|
||||
Password = Settings[attribute.Name] as string ?? string.Empty,
|
||||
PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
passwordBox.PasswordChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = ((PasswordBox)sender).Password;
|
||||
};
|
||||
contentControl = passwordBox;
|
||||
break;
|
||||
}
|
||||
Width = 300,
|
||||
Margin = settingControlMargin,
|
||||
Password = Settings[attribute.Name] as string ?? string.Empty,
|
||||
PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
passwordBox.PasswordChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = ((PasswordBox)sender).Password;
|
||||
};
|
||||
contentControl = passwordBox;
|
||||
break;
|
||||
}
|
||||
case "dropdown":
|
||||
{
|
||||
var comboBox = new ComboBox()
|
||||
{
|
||||
var comboBox = new ComboBox()
|
||||
{
|
||||
ItemsSource = attribute.Options,
|
||||
SelectedItem = Settings[attribute.Name],
|
||||
Margin = settingControlMargin,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
comboBox.SelectionChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = (string)((ComboBox)sender).SelectedItem;
|
||||
};
|
||||
contentControl = comboBox;
|
||||
break;
|
||||
}
|
||||
ItemsSource = attribute.Options,
|
||||
SelectedItem = Settings[attribute.Name],
|
||||
Margin = settingControlMargin,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
comboBox.SelectionChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = (string)((ComboBox)sender).SelectedItem;
|
||||
};
|
||||
contentControl = comboBox;
|
||||
break;
|
||||
}
|
||||
case "checkbox":
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
|
|
@ -499,6 +501,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
return settingWindow;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (Settings != null)
|
||||
|
|
@ -541,4 +544,5 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue