diff --git a/src/Starward/Controls/InstallGameController.xaml b/src/Starward/Controls/InstallGameController.xaml
index 5e93c2f74..9dd40055b 100644
--- a/src/Starward/Controls/InstallGameController.xaml
+++ b/src/Starward/Controls/InstallGameController.xaml
@@ -18,22 +18,29 @@
+
+
+ 2
+
+
-
+
-
-
-
-
-
-
+
@@ -41,6 +48,7 @@
+
@@ -54,6 +62,7 @@
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Starward/Controls/InstallGameController.xaml.cs b/src/Starward/Controls/InstallGameController.xaml.cs
index e38c88f3d..c540c6dcf 100644
--- a/src/Starward/Controls/InstallGameController.xaml.cs
+++ b/src/Starward/Controls/InstallGameController.xaml.cs
@@ -46,6 +46,13 @@ public InstallGameController()
private ObservableCollection _installServices = new();
+ [ObservableProperty]
+ private bool _ProgressIsIndeterminate;
+
+
+ [ObservableProperty]
+ private double _ProgressValue;
+
private void _installGameManager_InstallTaskAdded(object? sender, InstallGameStateModel e)
{
@@ -94,9 +101,26 @@ private void _timer_Tick(DispatcherQueueTimer sender, object args)
try
{
_semaphoreSlim.Wait();
+ long totalBytes = 0;
+ long finishedBytes = 0;
+ _installGameManager.UpdateSpeedState();
foreach (var model in InstallServices)
{
model.UpdateState();
+ if (model.Service.State is InstallGameState.Download)
+ {
+ totalBytes += model.Service.TotalBytes;
+ finishedBytes += model.Service.FinishBytes;
+ }
+ }
+ if (totalBytes > 0)
+ {
+ ProgressIsIndeterminate = false;
+ ProgressValue = 100d * finishedBytes / totalBytes;
+ }
+ else
+ {
+ ProgressIsIndeterminate = true;
}
}
finally
@@ -111,7 +135,14 @@ private void Grid_ActionButtonOverlay_PointerEntered(object sender, Microsoft.UI
{
if (sender is Grid grid)
{
- grid.Opacity = 1;
+ if (grid.FindName("StackPanel_ActionButton") is StackPanel stackPanel)
+ {
+ stackPanel.Opacity = 1;
+ }
+ if (grid.FindName("TextBlock_ProgressValue") is TextBlock textBlock)
+ {
+ textBlock.Opacity = 0;
+ }
}
}
@@ -121,9 +152,27 @@ private void Grid_ActionButtonOverlay_PointerExited(object sender, Microsoft.UI.
{
if (sender is Grid grid)
{
- grid.Opacity = 0;
+ if (grid.FindName("StackPanel_ActionButton") is StackPanel stackPanel)
+ {
+ stackPanel.Opacity = 0;
+ }
+ if (grid.FindName("TextBlock_ProgressValue") is TextBlock textBlock)
+ {
+ textBlock.Opacity = 1;
+ }
}
}
+ private void Flyout_InstallGame_Opened(object sender, object e)
+ {
+ _timer.Interval = TimeSpan.FromSeconds(0.1);
+ }
+
+
+ private void Flyout_InstallGame_Closed(object sender, object e)
+ {
+ _timer.Interval = TimeSpan.FromSeconds(1);
+ }
+
}
diff --git a/src/Starward/Services/Download/InstallGameManager.cs b/src/Starward/Services/Download/InstallGameManager.cs
index c41dc10cb..1afb89fe2 100644
--- a/src/Starward/Services/Download/InstallGameManager.cs
+++ b/src/Starward/Services/Download/InstallGameManager.cs
@@ -1,7 +1,9 @@
using Starward.Core;
using System;
using System.Collections.Concurrent;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Threading;
namespace Starward.Services.Download;
@@ -12,7 +14,7 @@ internal class InstallGameManager
private readonly ConcurrentDictionary _services = new();
- public InstallGameManager()
+ private InstallGameManager()
{
_services = new();
}
@@ -23,22 +25,34 @@ public InstallGameManager()
+ public static long DownloadBytesInSecond;
- public double Speed { get; set; }
+ public static long SpeedLimitBytesPerSecond { get; set; } = long.MaxValue;
- public double SpeedLimit { get; set; }
+ public static bool IsExceedSpeedLimit => Interlocked.Read(ref DownloadBytesInSecond) >= SpeedLimitBytesPerSecond;
+ private long _lastTimeStamp;
- public event EventHandler InstallTaskAdded;
+ public void UpdateSpeedState()
+ {
+ long ts = Stopwatch.GetTimestamp();
+ if (ts - _lastTimeStamp >= Stopwatch.Frequency)
+ {
+ DownloadBytesInSecond = 0;
+ }
+ }
- public event EventHandler InstallTaskRemoved;
+
+ public event EventHandler InstallTaskAdded;
+
+ public event EventHandler InstallTaskRemoved;
@@ -59,7 +73,6 @@ public bool TryGetInstallService(GameBiz gameBiz, [NotNullWhen(true)] out Instal
-
public void AddInstallService(InstallGameService service)
{
var model = new InstallGameStateModel(service);
@@ -99,6 +112,7 @@ private void Model_InstallFailed(object? sender, EventArgs e)
}
+
private void Model_InstallCanceled(object? sender, EventArgs e)
{
if (sender is InstallGameStateModel model)
diff --git a/src/Starward/Services/Download/InstallGameService.cs b/src/Starward/Services/Download/InstallGameService.cs
index 199b5cf34..35bf582ec 100644
--- a/src/Starward/Services/Download/InstallGameService.cs
+++ b/src/Starward/Services/Download/InstallGameService.cs
@@ -998,20 +998,8 @@ protected async Task ExecuteTaskItemAsync(CancellationToken cancellationToken =
Interlocked.Increment(ref _finishCount);
break;
case InstallGameItemType.Decompress:
- try
- {
- await _decompressSemaphore.WaitAsync();
- await DecompressItemAsync(item, cancellationToken);
- Interlocked.Increment(ref _finishCount);
- }
- catch
- {
- throw;
- }
- finally
- {
- _decompressSemaphore.Release();
- }
+ await DecompressItemAsync(item, cancellationToken);
+ Interlocked.Increment(ref _finishCount);
break;
default:
break;
@@ -1071,7 +1059,6 @@ protected async Task DownloadItemAsync(InstallGameItem item, CancellationToken c
using var fs = File.Open(file_target, FileMode.OpenOrCreate);
if (fs.Length < item.Size)
{
- _logger.LogInformation("Download: FileName {name}, Url {url}", item.FileName, item.Url);
fs.Position = fs.Length;
var request = new HttpRequestMessage(HttpMethod.Get, item.Url) { Version = HttpVersion.Version11 };
request.Headers.Range = new RangeHeaderValue(fs.Length, null);
@@ -1084,8 +1071,13 @@ protected async Task DownloadItemAsync(InstallGameItem item, CancellationToken c
{
await fs.WriteAsync(buffer.AsMemory(0, length), cancellationToken).ConfigureAwait(false);
Interlocked.Add(ref _finishBytes, length);
+ Interlocked.Add(ref InstallGameManager.DownloadBytesInSecond, length);
+ if (InstallGameManager.IsExceedSpeedLimit)
+ {
+ long t = Stopwatch.GetTimestamp() / (Stopwatch.Frequency / 1000) % 1000;
+ await Task.Delay((int)(1000 - t), cancellationToken);
+ }
}
- _logger.LogInformation("Download Successfully: FileName {name}", item.FileName);
}
}
@@ -1163,54 +1155,67 @@ protected async Task VerifyItemAsync(InstallGameItem item, CancellationToken can
protected async Task DecompressItemAsync(InstallGameItem item, CancellationToken cancellationToken = default)
{
- using var fs = new FileSliceStream(item.PackageFiles);
- if (item.PackageFiles[0].Contains(".7z", StringComparison.CurrentCultureIgnoreCase))
+ try
{
- await Task.Run(() =>
+ await _decompressSemaphore.WaitAsync();
+ using var fs = new FileSliceStream(item.PackageFiles);
+ if (item.PackageFiles[0].Contains(".7z", StringComparison.CurrentCultureIgnoreCase))
{
- using var extra = new ArchiveFile(fs);
- double ratio = (double)fs.Length / extra.Entries.Sum(x => (long)x.Size);
- long sum = 0;
- extra.ExtractProgress += (_, e) =>
+ await Task.Run(() =>
{
- long size = (long)(e.Read * ratio);
- _finishBytes += size;
- sum += size;
- };
- extra.Extract(item.TargetPath, true);
- _finishBytes += fs.Length - sum;
- }).ConfigureAwait(false);
- }
- else
- {
- await Task.Run(async () =>
+ using var extra = new ArchiveFile(fs);
+ double ratio = (double)fs.Length / extra.Entries.Sum(x => (long)x.Size);
+ long sum = 0;
+ extra.ExtractProgress += (_, e) =>
+ {
+ long size = (long)(e.Read * ratio);
+ _finishBytes += size;
+ sum += size;
+ };
+ extra.Extract(item.TargetPath, true);
+ _finishBytes += fs.Length - sum;
+ }).ConfigureAwait(false);
+ }
+ else
{
- long sum = 0;
- using var zip = new ZipArchive(fs, ZipArchiveMode.Read, true);
- foreach (var entry in zip.Entries)
+ await Task.Run(async () =>
{
- if ((entry.ExternalAttributes & 0x10) > 0)
- {
- var target = Path.Combine(item.TargetPath, entry.FullName);
- Directory.CreateDirectory(target);
- }
- else
+ long sum = 0;
+ using var zip = new ZipArchive(fs, ZipArchiveMode.Read, true);
+ foreach (var entry in zip.Entries)
{
- var target = Path.Combine(item.TargetPath, entry.FullName);
- Directory.CreateDirectory(Path.GetDirectoryName(target)!);
- entry.ExtractToFile(target, true);
- _finishBytes += entry.CompressedLength;
- sum += entry.CompressedLength;
+ if ((entry.ExternalAttributes & 0x10) > 0)
+ {
+ var target = Path.Combine(item.TargetPath, entry.FullName);
+ Directory.CreateDirectory(target);
+ }
+ else
+ {
+ var target = Path.Combine(item.TargetPath, entry.FullName);
+ Directory.CreateDirectory(Path.GetDirectoryName(target)!);
+ entry.ExtractToFile(target, true);
+ _finishBytes += entry.CompressedLength;
+ sum += entry.CompressedLength;
+ }
}
- }
- _finishBytes += fs.Length - sum;
- await ApplyDiffFilesAsync(item.TargetPath);
- }).ConfigureAwait(false);
+ _finishBytes += fs.Length - sum;
+ await ApplyDiffFilesAsync(item.TargetPath);
+ }).ConfigureAwait(false);
+ }
+ fs.Dispose();
+ foreach (var file in item.PackageFiles)
+ {
+ File.Delete(file);
+ }
+ }
+ catch
+ {
+ throw;
}
- fs.Dispose();
- foreach (var file in item.PackageFiles)
+ finally
{
- File.Delete(file);
+ _decompressSemaphore.Release();
+
}
}
diff --git a/src/Starward/Services/Download/InstallGameStateModel.cs b/src/Starward/Services/Download/InstallGameStateModel.cs
index 08bd51a81..dd418d9d8 100644
--- a/src/Starward/Services/Download/InstallGameStateModel.cs
+++ b/src/Starward/Services/Download/InstallGameStateModel.cs
@@ -67,7 +67,11 @@ internal InstallGameStateModel(InstallGameService service)
[ObservableProperty]
- private double _Progress;
+ private double _ProgressValue;
+
+
+ [ObservableProperty]
+ private string _ProgressValueText;
[ObservableProperty]
@@ -133,12 +137,12 @@ public void UpdateState()
StateText = Lang.DownloadGamePage_Downloading;
if (Service.TotalBytes == 0)
{
- Progress = 100;
+ ProgressValue = 100;
ProgressText = "";
}
else
{
- Progress = 100d * Service.FinishBytes / Service.TotalBytes;
+ ProgressValue = 100d * Service.FinishBytes / Service.TotalBytes;
ProgressText = $"{Service.FinishBytes / GB:F2}/{Service.TotalBytes / GB:F2} GB";
}
ButtonGlyph = PauseGlyph;
@@ -147,17 +151,17 @@ public void UpdateState()
StateText = Lang.DownloadGamePage_Verifying;
if (Service.TotalCount == 0)
{
- Progress = 100;
+ ProgressValue = 100;
ProgressText = "";
}
else if (Service.InstallTask is InstallGameTask.Repair)
{
- Progress = 100d * Service.FinishCount / Service.TotalCount;
+ ProgressValue = 100d * Service.FinishCount / Service.TotalCount;
ProgressText = $"{Service.FinishCount}/{Service.TotalCount}";
}
else
{
- Progress = 100d * Service.FinishBytes / Service.TotalBytes;
+ ProgressValue = 100d * Service.FinishBytes / Service.TotalBytes;
ProgressText = $"{Service.FinishBytes / GB:F2}/{Service.TotalBytes / GB:F2} GB";
}
ButtonGlyph = PauseGlyph;
@@ -166,12 +170,12 @@ public void UpdateState()
StateText = Lang.DownloadGamePage_Decompressing;
if (Service.TotalBytes == 0)
{
- Progress = 100;
+ ProgressValue = 100;
ProgressText = "";
}
else
{
- Progress = 100d * Service.FinishBytes / Service.TotalBytes;
+ ProgressValue = 100d * Service.FinishBytes / Service.TotalBytes;
ProgressText = $"{Service.FinishBytes / GB:F2}/{Service.TotalBytes / GB:F2} GB";
}
IsContinueOrPauseButtonEnabled = false;
@@ -183,7 +187,7 @@ public void UpdateState()
break;
case InstallGameState.Finish:
StateText = Lang.DownloadGamePage_Finished;
- Progress = 100;
+ ProgressValue = 100;
ProgressText = "";
IsContinueOrPauseButtonEnabled = false;
ButtonGlyph = PlayGlyph;
@@ -197,6 +201,7 @@ public void UpdateState()
default:
break;
}
+ ProgressValueText = $"{ProgressValue / 100:P2}";
ComputeSpeed(Service.State);
}
catch { }
@@ -209,33 +214,36 @@ private void ComputeSpeed(InstallGameState state)
try
{
long ts = Stopwatch.GetTimestamp();
- long bytes = Service.FinishBytes;
- _speedBytesPerSecond = Math.Clamp((double)(bytes - _lastFinishedBytes) / (ts - _lastTimestamp) * Stopwatch.Frequency, 0, long.MaxValue);
- _lastFinishedBytes = bytes;
- _lastTimestamp = ts;
- if (state is InstallGameState.None or InstallGameState.Finish or InstallGameState.Error)
- {
- SpeedText = null;
- RemainingTimeText = null;
- }
- else
+ if (ts - _lastTimestamp >= Stopwatch.Frequency)
{
- if (_speedBytesPerSecond >= MB)
- {
- SpeedText = $"{_speedBytesPerSecond / MB:F2} MB/s";
- }
- else
- {
- SpeedText = $"{_speedBytesPerSecond / KB:F2} KB/s";
- }
- if (_speedBytesPerSecond == 0)
+ long bytes = Service.FinishBytes;
+ _speedBytesPerSecond = Math.Clamp((double)(bytes - _lastFinishedBytes) / (ts - _lastTimestamp) * Stopwatch.Frequency, 0, long.MaxValue);
+ _lastFinishedBytes = bytes;
+ _lastTimestamp = ts;
+ if (state is InstallGameState.None or InstallGameState.Finish or InstallGameState.Error)
{
+ SpeedText = null;
RemainingTimeText = null;
}
else
{
- var seconds = (Service.TotalBytes - Service.FinishBytes) / _speedBytesPerSecond;
- RemainingTimeText = TimeSpan.FromSeconds(seconds).ToString(@"hh\:mm\:ss");
+ if (_speedBytesPerSecond >= MB)
+ {
+ SpeedText = $"{_speedBytesPerSecond / MB:F2} MB/s";
+ }
+ else
+ {
+ SpeedText = $"{_speedBytesPerSecond / KB:F2} KB/s";
+ }
+ if (_speedBytesPerSecond == 0)
+ {
+ RemainingTimeText = null;
+ }
+ else
+ {
+ var seconds = (Service.TotalBytes - Service.FinishBytes) / _speedBytesPerSecond;
+ RemainingTimeText = TimeSpan.FromSeconds(seconds).ToString(@"hh\:mm\:ss");
+ }
}
}
}
@@ -253,7 +261,7 @@ private void _service_StateChanged(object? sender, InstallGameState e)
private void Service_InstallFailed(object? sender, Exception e)
{
- NotificationBehavior.Instance.Error(e, $"Game ({GameBiz.ToGameName()} - {GameBiz.ToGameServer()}) install failed.");
+ NotificationBehavior.Instance.Error(e, $"Game ({GameBiz.ToGameName()} - {GameBiz.ToGameServer()}) install failed.", 0);
}