This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user