Files
YMhut-box-C-/src/YMhut.Box.Core/Downloads/DownloadModels.cs
T
QWQLwToo 7ecc6a8923
build-winui / winui (push) Has been cancelled
Add WinUI and core source
2026-06-26 13:27:13 +08:00

259 lines
7.7 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
namespace YMhut.Box.Core.Downloads;
public enum DownloadState
{
Queued,
Running,
Paused,
Completed,
Failed,
Canceled
}
public sealed record DownloadSource(
string Url,
string DisplayName,
string FileName,
string SourceKind = "Official",
string? Sha256 = null,
string SourceLabel = "",
string MirrorRegion = "",
long? SizeBytes = null,
string OriginalUrl = "",
string ResolvedUrl = "",
string ValidatedContentType = "",
string PackageId = "")
{
public string EffectiveLabel => string.IsNullOrWhiteSpace(SourceLabel) ? SourceKind : SourceLabel;
public string EffectiveUrl => string.IsNullOrWhiteSpace(ResolvedUrl) ? Url : ResolvedUrl;
}
public sealed record DownloadOptions(
string? TargetDirectory = null,
string? TargetPath = null,
string? InstallCommand = null,
string? InstallArguments = null,
bool IsInstaller = false,
bool DeleteAfterInstall = false);
public sealed record DownloadSettings(
string DefaultDirectory,
int MaxConcurrentDownloads = 5)
{
public int EffectiveMaxConcurrentDownloads => Math.Clamp(MaxConcurrentDownloads, 1, 5);
}
public sealed record DownloadItem(
string Id,
DownloadSource Source,
string TargetPath,
DownloadState State,
long ReceivedBytes = 0,
long? TotalBytes = null,
double BytesPerSecond = 0,
DateTimeOffset CreatedAt = default,
DateTimeOffset UpdatedAt = default,
string? Error = null,
string? InstallCommand = null,
string? InstallArguments = null,
bool IsInstaller = false,
bool DeleteAfterInstall = false,
bool InstallLaunched = false,
bool InstallCleanupCompleted = false,
string PartialPath = "",
string ETag = "",
string LastModified = "",
string AcceptRanges = "",
long? ContentLength = null,
string FinalUrl = "",
bool ResumeSupported = false)
{
[JsonIgnore]
public string EffectivePartialPath => string.IsNullOrWhiteSpace(PartialPath) ? TargetPath + ".partial" : PartialPath;
public static DownloadItem Create(
DownloadSource source,
string targetPath,
string? installCommand = null,
string? installArguments = null,
bool isInstaller = false,
bool deleteAfterInstall = false)
{
var now = DateTimeOffset.UtcNow;
return new DownloadItem(
Guid.NewGuid().ToString("N"),
source,
targetPath,
DownloadState.Queued,
CreatedAt: now,
UpdatedAt: now,
InstallCommand: installCommand,
InstallArguments: installArguments,
IsInstaller: isInstaller,
DeleteAfterInstall: deleteAfterInstall,
PartialPath: targetPath + ".partial");
}
public DownloadItem WithProgress(DownloadState state, long receivedBytes, long? totalBytes, double bytesPerSecond, string? error = null)
{
return this with
{
State = state,
ReceivedBytes = Math.Max(0, receivedBytes),
TotalBytes = totalBytes is > 0 ? totalBytes : null,
BytesPerSecond = Math.Max(0, bytesPerSecond),
UpdatedAt = DateTimeOffset.UtcNow,
Error = error
};
}
public DownloadItem WithResumeMetadata(
string? eTag = null,
string? lastModified = null,
string? acceptRanges = null,
long? contentLength = null,
string? finalUrl = null,
bool? resumeSupported = null)
{
return this with
{
PartialPath = string.IsNullOrWhiteSpace(PartialPath) ? TargetPath + ".partial" : PartialPath,
ETag = string.IsNullOrWhiteSpace(eTag) ? ETag : eTag,
LastModified = string.IsNullOrWhiteSpace(lastModified) ? LastModified : lastModified,
AcceptRanges = string.IsNullOrWhiteSpace(acceptRanges) ? AcceptRanges : acceptRanges,
ContentLength = contentLength is > 0 ? contentLength : ContentLength,
FinalUrl = string.IsNullOrWhiteSpace(finalUrl) ? FinalUrl : finalUrl,
ResumeSupported = resumeSupported ?? ResumeSupported,
UpdatedAt = DateTimeOffset.UtcNow
};
}
public DownloadItem WithInstallState(bool launched, bool cleanupCompleted = false)
{
return this with
{
InstallLaunched = launched,
InstallCleanupCompleted = cleanupCompleted,
UpdatedAt = DateTimeOffset.UtcNow
};
}
}
public sealed record DownloadProgressSnapshot(
string Id,
DownloadState State,
long ReceivedBytes,
long? TotalBytes,
double BytesPerSecond,
string? Error = null,
string ETag = "",
string LastModified = "",
string AcceptRanges = "",
long? ContentLength = null,
string FinalUrl = "",
bool RestartedFromZero = false)
{
public double Progress
{
get
{
if (TotalBytes is not > 0)
{
return 0;
}
return Math.Clamp(ReceivedBytes / (double)TotalBytes.Value, 0, 1);
}
}
}
public static class DownloadFormat
{
public static string FormatBytes(long bytes)
{
string[] units = ["B", "KB", "MB", "GB", "TB"];
var value = Math.Max(0, bytes);
var unit = 0;
var scaled = (double)value;
while (scaled >= 1024 && unit < units.Length - 1)
{
scaled /= 1024;
unit++;
}
return unit == 0 ? $"{value} {units[unit]}" : $"{scaled:0.#} {units[unit]}";
}
public static string FormatSpeed(double bytesPerSecond)
{
return $"{FormatBytes((long)Math.Max(0, bytesPerSecond))}/s";
}
public static string FormatEta(long receivedBytes, long? totalBytes, double bytesPerSecond)
{
if (totalBytes is not > 0 ||
receivedBytes <= 0 ||
receivedBytes >= totalBytes.Value ||
bytesPerSecond <= 1)
{
return "-";
}
var remaining = Math.Max(0, totalBytes.Value - receivedBytes);
var eta = TimeSpan.FromSeconds(remaining / bytesPerSecond);
if (eta.TotalHours >= 1)
{
return $"{(int)eta.TotalHours:0}h {eta.Minutes:00}m";
}
return eta.TotalMinutes >= 1
? $"{eta.Minutes:0}m {eta.Seconds:00}s"
: $"{Math.Max(1, eta.Seconds):0}s";
}
}
public static class DownloadHostProtocol
{
public const string Version = "1";
public const string Ready = "ready";
public const string Start = "start";
public const string Pause = "pause";
public const string Resume = "resume";
public const string Cancel = "cancel";
public const string Progress = "progress";
public const string Completed = "completed";
public const string Failed = "failed";
public const string Paused = "paused";
public const string Canceled = "canceled";
public const string Shutdown = "shutdown";
public const string Ping = "ping";
public const string Pong = "pong";
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
Converters = { new JsonStringEnumConverter<DownloadState>() }
};
public static string Serialize(DownloadHostMessage message)
{
return JsonSerializer.Serialize(message, JsonOptions);
}
public static DownloadHostMessage? Deserialize(string line)
{
return JsonSerializer.Deserialize<DownloadHostMessage>(line, JsonOptions);
}
}
public sealed record DownloadHostMessage(
string Type,
string RequestId = "",
string Version = "",
DownloadItem? Item = null,
DownloadProgressSnapshot? Progress = null,
string? Error = null);