Skip to content

Commit

Permalink
Release 1.10.0
Browse files Browse the repository at this point in the history
Release 1.10.0
  • Loading branch information
Yelo420 authored May 7, 2024
2 parents abe4154 + 4eb8ca6 commit 2bb8fc1
Show file tree
Hide file tree
Showing 19 changed files with 538 additions and 138 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# GameVault App Changelog

## 1.10.0.0
Recommended Gamevault Server Version: `v12.1.0`
### Changes
- Added Encrypted (password protected) archive support. Including a default password setting.
- Added the possibility to pause/resume downloads. Paused downloads are restored after the restart. Also pause is triggered on error.
- Added auto retry after download failed.
- Show system notifications for download/extraction
- Bug fix: Center game view background image on window resize
- Bug fix: Tag names that are too long could not be selected in the filters
- Bug fix: Displayed download speed has adjusted very slowly with larger fluctuations
- Bug fix: Too much offset of the bookmark button if game setting button was not visible

## 1.9.2.0
Recommended Gamevault Server Version: `v12.0.0`
### Changes
Expand Down
2 changes: 1 addition & 1 deletion gamevault/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
[assembly: AssemblyVersion("1.9.2.0")]
[assembly: AssemblyVersion("1.10.0.0")]
[assembly: AssemblyCopyright("© Phalcode™. All Rights Reserved.")]
#if DEBUG
[assembly: XmlnsDefinition("debug-mode", "Namespace")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
{
return Visibility.Visible;
}
return Visibility.Hidden;
return Visibility.Collapsed;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
Expand Down
65 changes: 65 additions & 0 deletions gamevault/Helper/DownloadSpeedCalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace gamevault.Helper
{
public class ExponentialMovingAverage
{
private double alpha;
private double average;

public ExponentialMovingAverage(double alpha)
{
this.alpha = alpha;
average = 0;
}

public void Add(double value)
{
if (average == 0)
average = value;
else
average = alpha * value + (1 - alpha) * average;
}

public double Calculate()
{
return average;
}
}
public class DownloadSpeedCalculator
{
private ExponentialMovingAverage speedAverage;
private long lastDownloadedBytes;
private DateTime lastUpdateTime;

public DownloadSpeedCalculator(double smoothingFactor = 0.3)
{
speedAverage = new ExponentialMovingAverage(smoothingFactor);
lastDownloadedBytes = 0;
lastUpdateTime = DateTime.Now;
}

public void UpdateSpeed(long currentDownloadedBytes)
{
DateTime currentTime = DateTime.Now;
TimeSpan elapsedTime = currentTime - lastUpdateTime;

if (elapsedTime.TotalSeconds > 0)
{
double downloadSpeed = (currentDownloadedBytes - lastDownloadedBytes) / elapsedTime.TotalSeconds;
speedAverage.Add(downloadSpeed);
lastDownloadedBytes = currentDownloadedBytes;
lastUpdateTime = currentTime;
}
}

public double GetCurrentSpeed()
{
return speedAverage.Calculate();
}
}
}
205 changes: 138 additions & 67 deletions gamevault/Helper/HttpClientDownloadWithProgress.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using gamevault.UserControls;
using gamevault.Models;
using gamevault.UserControls;
using gamevault.ViewModels;
using System;
using System.Collections.Generic;
Expand All @@ -14,41 +15,72 @@ namespace gamevault.Helper
{
public class HttpClientDownloadWithProgress : IDisposable
{
private readonly string _downloadUrl;
private readonly string _destinationFolderPath;
private string _fileName;
private string _fallbackFileName;
KeyValuePair<string, string>? _additionalHeader;
private bool _Cancelled = false;
private DateTime lastTime;
private HttpClient _httpClient;

public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
private readonly string DownloadUrl;
private readonly string DestinationFolderPath;
private string FileName;
private string FallbackFileName;
private KeyValuePair<string, string>? AdditionalHeader;
private bool Cancelled = false;
private bool Paused = false;
private long ResumePosition = -1;
private long PreResumeSize = -1;
private DateTime LastTime;
private HttpClient HttpClient;

public delegate void ProgressChangedHandler(long totalFileSize, long currentBytesDownloaded, long totalBytesDownloaded, double? progressPercentage, long resumePosition);

public event ProgressChangedHandler ProgressChanged;

public HttpClientDownloadWithProgress(string downloadUrl, string destinationFolderPath, string fallbackFileName, KeyValuePair<string, string>? additionalHeader = null)
{
_downloadUrl = downloadUrl;
_destinationFolderPath = destinationFolderPath;
_fallbackFileName = fallbackFileName;
_additionalHeader = additionalHeader;
DownloadUrl = downloadUrl;
DestinationFolderPath = destinationFolderPath;
FallbackFileName = fallbackFileName;
AdditionalHeader = additionalHeader;
}

public async Task StartDownload()
public async Task StartDownload(bool tryResume = false)
{
HttpClient = new HttpClient { Timeout = TimeSpan.FromDays(7) };
CreateHeader();
if (tryResume)
{
InitResume();
}
else
{
//Edge case where the Library download overrrides the current download. But if its was a paused download, we have also have to reset the metadata
if (File.Exists($"{DestinationFolderPath}\\gamevault-metadata"))
File.Delete($"{DestinationFolderPath}\\gamevault-metadata");
}

using (var response = await HttpClient.GetAsync(DownloadUrl, HttpCompletionOption.ResponseHeadersRead))
await DownloadFileFromHttpResponseMessage(response);
}
private void CreateHeader()
{
_httpClient = new HttpClient { Timeout = TimeSpan.FromDays(7) };
string[] auth = WebHelper.GetCredentials();
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes($"{auth[0]}:{auth[1]}")));
_httpClient.DefaultRequestHeaders.Add("User-Agent", $"GameVault/{SettingsViewModel.Instance.Version}");
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes($"{auth[0]}:{auth[1]}")));
HttpClient.DefaultRequestHeaders.Add("User-Agent", $"GameVault/{SettingsViewModel.Instance.Version}");

if (_additionalHeader != null)
if (AdditionalHeader != null)
HttpClient.DefaultRequestHeaders.Add(AdditionalHeader.Value.Key, AdditionalHeader.Value.Value);
}
private void InitResume()
{
string resumeData = Preferences.Get(AppConfigKey.DownloadProgress, $"{DestinationFolderPath}\\gamevault-metadata");
if (!string.IsNullOrEmpty(resumeData))
{
_httpClient.DefaultRequestHeaders.Add(_additionalHeader.Value.Key, _additionalHeader.Value.Value);
try
{
string[] resumeDataToProcess = resumeData.Split(";");
ResumePosition = long.Parse(resumeDataToProcess[0]);
PreResumeSize = long.Parse(resumeDataToProcess[1]);
HttpClient.DefaultRequestHeaders.Range = new RangeHeaderValue(long.Parse(resumeDataToProcess[0]), null);
//TriggerProgressChanged(PreResumeSize, 0, ResumePosition);
}
catch { }
}
using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead))
await DownloadFileFromHttpResponseMessage(response);
}

private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)
Expand All @@ -57,88 +89,127 @@ private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage respo

try
{
_fileName = response.Content.Headers.ContentDisposition.FileName.Replace("\"", "");
if (string.IsNullOrEmpty(_fileName))
FileName = response.Content.Headers.ContentDisposition.FileName.Replace("\"", "");
if (string.IsNullOrEmpty(FileName))
{
throw new Exception("Missing response header (Content-Disposition)");
}
}
catch
{
_fileName = _fallbackFileName;
FileName = FallbackFileName;
}
var totalBytes = response.Content.Headers.ContentLength;
if (totalBytes == null || totalBytes == 0)
var responseContentLength = response.Content.Headers.ContentLength;
if (responseContentLength == null || responseContentLength == 0)
{
throw new Exception("Missing response header (Content-Length)");
}

using (var contentStream = await response.Content.ReadAsStreamAsync())
await ProcessContentStream(totalBytes, contentStream);
await ProcessContentStream(responseContentLength.Value, contentStream);
}

private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
private async Task ProcessContentStream(long currentDownloadSize, Stream contentStream)
{
var totalBytesRead = 0L;
var buffer = new byte[8192];
var isMoreToRead = true;
lastTime = DateTime.Now;
string fullFilePath = $"{_destinationFolderPath}\\{_fileName}";
using (var fileStream = new FileStream(fullFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
long currentBytesRead = 0;
byte[] buffer = new byte[8192];
bool isMoreToRead = true;
LastTime = DateTime.Now;
string fullFilePath = $"{DestinationFolderPath}\\{FileName}";
using (var fileStream = new FileStream(fullFilePath, ResumePosition == -1 ? FileMode.Create : FileMode.Open, FileAccess.Write, FileShare.None, 8192, true))
{
do
try
{
if (_Cancelled)
if (ResumePosition != -1)
{
fileStream.Position = ResumePosition;
}
do
{
fileStream.Close();
try
if (Cancelled)
{
File.Delete(fullFilePath);
if (Paused)
{
Preferences.Set(AppConfigKey.DownloadProgress, $"{fileStream.Position};{(PreResumeSize == -1 ? currentDownloadSize : PreResumeSize)}", $"{DestinationFolderPath}\\gamevault-metadata");
TriggerProgressChanged(currentDownloadSize, 0, fileStream.Position);
fileStream.Close();
return;
}
fileStream.Close();
try
{
await Task.Delay(1000);
File.Delete($"{DestinationFolderPath}\\gamevault-metadata");
File.Delete(fullFilePath);
}
catch { }
return;
}
catch { }
return;
}

var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
isMoreToRead = false;
fileStream.Close();
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
continue;
}
var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
isMoreToRead = false;
TriggerProgressChanged(currentDownloadSize, currentBytesRead, fileStream.Position);
fileStream.Close();
continue;
}

await fileStream.WriteAsync(buffer, 0, bytesRead);
await fileStream.WriteAsync(buffer, 0, bytesRead);

totalBytesRead += bytesRead;
if ((DateTime.Now - lastTime).TotalSeconds > 2)
currentBytesRead += bytesRead;
if ((DateTime.Now - LastTime).TotalMilliseconds > 2000)
{
TriggerProgressChanged(currentDownloadSize, currentBytesRead, fileStream.Position);
LastTime = DateTime.Now;
}
}
while (isMoreToRead);
}
catch (Exception ex)//On exception try to save the download progress and forward the exception
{
if (currentBytesRead > 0)
{
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
lastTime = DateTime.Now;
Preferences.Set(AppConfigKey.DownloadProgress, $"{fileStream.Position};{(PreResumeSize == -1 ? currentDownloadSize : PreResumeSize)}", $"{DestinationFolderPath}\\gamevault-metadata");
}
throw;
}
while (isMoreToRead);
}
}

private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)
private void TriggerProgressChanged(long totalDownloadSize, long currentBytesRead, long totalBytesRead)
{
if (ProgressChanged == null)
return;

double? progressPercentage = null;
if (totalDownloadSize.HasValue)
progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 0);

ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
totalDownloadSize = PreResumeSize == -1 ? totalDownloadSize : PreResumeSize;
double progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize * 100, 0);
ProgressChanged(totalDownloadSize, currentBytesRead, totalBytesRead, progressPercentage, ResumePosition);
}
public void Cancel()
{
_Cancelled = true;
if (Paused)
{
try
{
File.Delete($"{DestinationFolderPath}\\gamevault-metadata");
File.Delete($"{DestinationFolderPath}\\{FileName}");
}
catch { }
return;
}
Cancelled = true;
Dispose();
}
public void Pause()
{
Paused = true;
Cancelled = true;
Dispose();
}
public void Dispose()
{
_httpClient?.Dispose();
HttpClient?.Dispose();
}
}
}
Loading

0 comments on commit 2bb8fc1

Please # to comment.