完善音乐下载、下载管理和开发工具
This commit is contained in:
@@ -73,6 +73,10 @@ public static class AppServices
|
||||
provider.GetRequiredService<IDownloadHostProcessService>(),
|
||||
provider.GetService<ILogService>()));
|
||||
services.AddSingleton<IDevEnvironmentDetectionService>(provider => new DevEnvironmentDetectionService(provider.GetService<ILogService>()));
|
||||
services.AddSingleton<IDevTerminalPlatform>(provider => new WindowsDevTerminalPlatform(provider.GetRequiredService<AppPaths>()));
|
||||
services.AddSingleton<IDevTerminalSetupService>(provider => new DevTerminalSetupService(
|
||||
provider.GetRequiredService<IDevTerminalPlatform>(),
|
||||
provider.GetService<ILogService>()));
|
||||
services.AddSingleton<IDevEnvironmentCatalogService>(provider => new DevEnvironmentCatalogService(
|
||||
provider.GetRequiredService<AppPaths>(),
|
||||
provider.GetRequiredService<IHttpService>(),
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Diagnostics;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
using YMhut.Box.Core.Downloads;
|
||||
|
||||
namespace YMhut.Box.WinUI.Services;
|
||||
|
||||
public sealed record DownloadLaunchResult(bool Succeeded, Process? Process = null, string Error = "");
|
||||
|
||||
public static class DownloadItemLauncher
|
||||
{
|
||||
public static async Task<DownloadLaunchResult> LaunchAsync(DownloadItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
var normalized = DownloadOpenPolicy.Normalize(item);
|
||||
switch (normalized.OpenKind)
|
||||
{
|
||||
case DownloadOpenKind.Installer:
|
||||
var process = DownloadManagerService.LaunchInstaller(normalized);
|
||||
return process is null
|
||||
? new DownloadLaunchResult(false, Error: "The installer file no longer exists.")
|
||||
: new DownloadLaunchResult(true, process);
|
||||
case DownloadOpenKind.ExternalUri:
|
||||
if (!Uri.TryCreate(normalized.Source.Url, UriKind.Absolute, out var uri) || uri.Scheme is not ("http" or "https"))
|
||||
{
|
||||
return new DownloadLaunchResult(false, Error: "The download page address is invalid.");
|
||||
}
|
||||
|
||||
return await Launcher.LaunchUriAsync(uri)
|
||||
? new DownloadLaunchResult(true)
|
||||
: new DownloadLaunchResult(false, Error: "Windows could not open the download page.");
|
||||
case DownloadOpenKind.File:
|
||||
if (!File.Exists(normalized.TargetPath))
|
||||
{
|
||||
return new DownloadLaunchResult(false, Error: "The downloaded file no longer exists.");
|
||||
}
|
||||
|
||||
var file = await StorageFile.GetFileFromPathAsync(normalized.TargetPath);
|
||||
return await Launcher.LaunchFileAsync(file)
|
||||
? new DownloadLaunchResult(true)
|
||||
: new DownloadLaunchResult(false, Error: "Windows has no application associated with this file.");
|
||||
default:
|
||||
return new DownloadLaunchResult(false, Error: "This download item cannot be opened.");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
return new DownloadLaunchResult(false, Error: AppLocalizer.SanitizeSensitiveText(exception.Message, 220));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,8 @@ public sealed class DownloadManagerService : IDownloadManagerService, IDisposabl
|
||||
options.InstallCommand,
|
||||
options.InstallArguments,
|
||||
options.IsInstaller,
|
||||
options.DeleteAfterInstall);
|
||||
options.DeleteAfterInstall,
|
||||
options.OpenKind);
|
||||
|
||||
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
@@ -333,6 +334,13 @@ public sealed class DownloadManagerService : IDownloadManagerService, IDisposabl
|
||||
return null;
|
||||
}
|
||||
|
||||
item = DownloadOpenPolicy.Normalize(item);
|
||||
if (item.OpenKind != DownloadOpenKind.Installer ||
|
||||
!DownloadOpenPolicy.IsSupportedInstaller(item.TargetPath, item.InstallCommand))
|
||||
{
|
||||
throw new InvalidOperationException("The downloaded item is not a supported installer.");
|
||||
}
|
||||
|
||||
var command = item.InstallCommand;
|
||||
var fileName = string.IsNullOrWhiteSpace(command) || string.Equals(command, "installer", StringComparison.OrdinalIgnoreCase)
|
||||
? item.TargetPath
|
||||
@@ -601,9 +609,10 @@ public sealed class DownloadManagerService : IDownloadManagerService, IDisposabl
|
||||
|
||||
private static DownloadItem NormalizeLoadedItem(DownloadItem item)
|
||||
{
|
||||
var withPartialPath = string.IsNullOrWhiteSpace(item.PartialPath)
|
||||
? item with { PartialPath = item.TargetPath + ".partial" }
|
||||
: item;
|
||||
var normalized = DownloadOpenPolicy.Normalize(item);
|
||||
var withPartialPath = string.IsNullOrWhiteSpace(normalized.PartialPath)
|
||||
? normalized with { PartialPath = normalized.TargetPath + ".partial" }
|
||||
: normalized;
|
||||
var partialBytes = FileLength(withPartialPath.EffectivePartialPath);
|
||||
return withPartialPath.State == DownloadState.Running
|
||||
? withPartialPath.WithProgress(DownloadState.Queued, partialBytes, withPartialPath.TotalBytes ?? withPartialPath.ContentLength, 0)
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
using System.IO.Ports;
|
||||
using YMhut.Box.Core.Tools;
|
||||
|
||||
namespace YMhut.Box.WinUI.Services;
|
||||
|
||||
public sealed class SerialPortTransport : ISerialPortTransport
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
private SerialPort? _port;
|
||||
|
||||
public bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
return _port?.IsOpen == true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<YMhut.Box.Core.Tools.SerialDataReceivedEventArgs>? DataReceived;
|
||||
|
||||
public IReadOnlyList<string> GetPortNames()
|
||||
=> SerialPort.GetPortNames().Order(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
|
||||
public Task OpenAsync(SerialConnectionOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
lock (_gate)
|
||||
{
|
||||
CloseCore();
|
||||
var port = new SerialPort(
|
||||
options.PortName,
|
||||
options.BaudRate,
|
||||
MapParity(options.Parity),
|
||||
options.DataBits,
|
||||
MapStopBits(options.StopBits))
|
||||
{
|
||||
Encoding = System.Text.Encoding.UTF8,
|
||||
ReadTimeout = 1000,
|
||||
WriteTimeout = 2000
|
||||
};
|
||||
port.DataReceived += Port_DataReceived;
|
||||
port.Open();
|
||||
_port = port;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
lock (_gate)
|
||||
{
|
||||
if (_port?.IsOpen != true)
|
||||
{
|
||||
throw new InvalidOperationException("The serial port is not connected.");
|
||||
}
|
||||
_port.BaseStream.Write(data.Span);
|
||||
_port.BaseStream.Flush();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CloseAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
lock (_gate)
|
||||
{
|
||||
CloseCore();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
CloseCore();
|
||||
}
|
||||
}
|
||||
|
||||
private void Port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] data;
|
||||
lock (_gate)
|
||||
{
|
||||
if (_port?.IsOpen != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var count = _port.BytesToRead;
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
data = new byte[count];
|
||||
_ = _port.Read(data, 0, data.Length);
|
||||
}
|
||||
DataReceived?.Invoke(this, new YMhut.Box.Core.Tools.SerialDataReceivedEventArgs(data));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseCore()
|
||||
{
|
||||
var port = _port;
|
||||
_port = null;
|
||||
if (port is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
port.DataReceived -= Port_DataReceived;
|
||||
try
|
||||
{
|
||||
if (port.IsOpen)
|
||||
{
|
||||
port.Close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
port.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static Parity MapParity(SerialParityMode value) => value switch
|
||||
{
|
||||
SerialParityMode.Odd => Parity.Odd,
|
||||
SerialParityMode.Even => Parity.Even,
|
||||
SerialParityMode.Mark => Parity.Mark,
|
||||
SerialParityMode.Space => Parity.Space,
|
||||
_ => Parity.None
|
||||
};
|
||||
|
||||
private static StopBits MapStopBits(SerialStopBitsMode value) => value switch
|
||||
{
|
||||
SerialStopBitsMode.OnePointFive => StopBits.OnePointFive,
|
||||
SerialStopBitsMode.Two => StopBits.Two,
|
||||
_ => StopBits.One
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user