Merge pull request 'add basic download caching' (#10) from waffle.lord/SPT-AKI-Installer:master into master
Reviewed-on: CWX/SPT-AKI-Installer#10
This commit is contained in:
commit
113a518599
@ -20,22 +20,20 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
private async Task<GenericResult> BuildMirrorList()
|
private async Task<GenericResult> BuildMirrorList()
|
||||||
{
|
{
|
||||||
var mirrorListInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "mirrors.json"));
|
|
||||||
|
|
||||||
SetStatus("Downloading Mirror List", false);
|
SetStatus("Downloading Mirror List", false);
|
||||||
|
|
||||||
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||||
|
|
||||||
var downloadResult = await DownloadHelper.DownloadFile(mirrorListInfo, _data.PatcherMirrorsLink, progress);
|
var file = await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress);
|
||||||
|
|
||||||
if (!downloadResult.Succeeded)
|
if (file == null)
|
||||||
{
|
{
|
||||||
return downloadResult;
|
return GenericResult.FromError("Failed to download mirror list");
|
||||||
}
|
}
|
||||||
|
|
||||||
var blah = JsonConvert.DeserializeObject<List<DownloadMirror>>(File.ReadAllText(mirrorListInfo.FullName));
|
var mirrorsList = JsonConvert.DeserializeObject<List<DownloadMirror>>(File.ReadAllText(file.FullName));
|
||||||
|
|
||||||
if (blah is List<DownloadMirror> mirrors)
|
if (mirrorsList is List<DownloadMirror> mirrors)
|
||||||
{
|
{
|
||||||
_data.PatcherReleaseMirrors = mirrors;
|
_data.PatcherReleaseMirrors = mirrors;
|
||||||
|
|
||||||
@ -63,30 +61,26 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(mirror.Link), progress);
|
using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(mirror.Link), progress);
|
||||||
using var patcherFileStream = _data.PatcherZipInfo.Open(FileMode.Create);
|
|
||||||
{
|
|
||||||
await megaDownloadStream.CopyToAsync(patcherFileStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
patcherFileStream.Close();
|
_data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", megaDownloadStream, mirror.Hash);
|
||||||
|
|
||||||
if(!DownloadHelper.FileHashCheck(_data.PatcherZipInfo, mirror.Hash))
|
if(_data.PatcherZipInfo == null)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("Hash mismatch");
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
return GenericResult.FromSuccess();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch
|
||||||
{
|
{
|
||||||
//most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
|
//most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await DownloadHelper.DownloadFile(_data.PatcherZipInfo, mirror.Link, progress, mirror.Hash);
|
_data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", mirror.Link, progress, mirror.Hash);
|
||||||
|
|
||||||
if (result.Succeeded)
|
if (_data.PatcherZipInfo != null)
|
||||||
{
|
{
|
||||||
return GenericResult.FromSuccess();
|
return GenericResult.FromSuccess();
|
||||||
}
|
}
|
||||||
@ -97,13 +91,8 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
public override async Task<GenericResult> RunAsync()
|
public override async Task<GenericResult> RunAsync()
|
||||||
{
|
{
|
||||||
_data.PatcherZipInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "patcher.zip"));
|
|
||||||
_data.AkiZipInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "sptaki.zip"));
|
|
||||||
|
|
||||||
if (_data.PatchNeeded)
|
if (_data.PatchNeeded)
|
||||||
{
|
{
|
||||||
if (_data.PatcherZipInfo.Exists) _data.PatcherZipInfo.Delete();
|
|
||||||
|
|
||||||
var buildResult = await BuildMirrorList();
|
var buildResult = await BuildMirrorList();
|
||||||
|
|
||||||
if (!buildResult.Succeeded)
|
if (!buildResult.Succeeded)
|
||||||
@ -122,19 +111,17 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_data.AkiZipInfo.Exists) _data.AkiZipInfo.Delete();
|
|
||||||
|
|
||||||
SetStatus("Downloading SPT-AKI", false);
|
SetStatus("Downloading SPT-AKI", false);
|
||||||
|
|
||||||
Progress = 0;
|
Progress = 0;
|
||||||
|
|
||||||
var akiProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
var akiProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||||
|
|
||||||
var releaseDownloadResult = await DownloadHelper.DownloadFile(_data.AkiZipInfo, _data.AkiReleaseDownloadLink, akiProgress);
|
_data.AkiZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("sptaki.zip", _data.AkiReleaseDownloadLink, akiProgress);
|
||||||
|
|
||||||
if (!releaseDownloadResult.Succeeded)
|
if (_data.AkiZipInfo == null)
|
||||||
{
|
{
|
||||||
return releaseDownloadResult;
|
return GenericResult.FromError("Failed to download spt-aki");
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
return GenericResult.FromSuccess();
|
||||||
|
@ -31,8 +31,6 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
var extractPatcherProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
var extractPatcherProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, extractPatcherProgress);
|
var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, extractPatcherProgress);
|
||||||
|
|
||||||
if (!extractPatcherResult.Succeeded)
|
if (!extractPatcherResult.Succeeded)
|
||||||
@ -65,7 +63,6 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
{
|
{
|
||||||
return patchingResult;
|
return patchingResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,9 +90,6 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
patcherEXE.Delete();
|
patcherEXE.Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
_data.PatcherZipInfo.Delete();
|
|
||||||
_data.AkiZipInfo.Delete();
|
|
||||||
|
|
||||||
return GenericResult.FromSuccess("SPT is Setup. Happy Playing!");
|
return GenericResult.FromSuccess("SPT is Setup. Happy Playing!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
140
Aki.Helper/DownloadCacheHelper.cs
Normal file
140
Aki.Helper/DownloadCacheHelper.cs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
using HttpClientProgress;
|
||||||
|
using SPT_AKI_Installer.Aki.Core.Model;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPT_AKI_Installer.Aki.Helper
|
||||||
|
{
|
||||||
|
public static class DownloadCacheHelper
|
||||||
|
{
|
||||||
|
private static HttpClient _httpClient = new HttpClient() { Timeout = TimeSpan.FromHours(1) };
|
||||||
|
|
||||||
|
private static string _cachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache");
|
||||||
|
|
||||||
|
private static async Task<GenericResult> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use the provided extension method
|
||||||
|
using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||||
|
await _httpClient.DownloadDataAsync(targetLink, file, progress);
|
||||||
|
|
||||||
|
outputFile.Refresh();
|
||||||
|
|
||||||
|
if (!outputFile.Exists)
|
||||||
|
{
|
||||||
|
return GenericResult.FromError($"Failed to download {outputFile.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expectedHash != null && !FileHashHelper.CheckHash(outputFile, expectedHash))
|
||||||
|
{
|
||||||
|
return GenericResult.FromError("Hash mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenericResult.FromSuccess();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return GenericResult.FromError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<GenericResult> ProcessInboundStreamAsync(FileInfo cacheFile, Stream downloadStream, string expectedHash = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cacheFile.Refresh();
|
||||||
|
Directory.CreateDirectory(_cachePath);
|
||||||
|
|
||||||
|
if (cacheFile.Exists)
|
||||||
|
{
|
||||||
|
if (expectedHash != null && FileHashHelper.CheckHash(cacheFile, expectedHash))
|
||||||
|
{
|
||||||
|
return GenericResult.FromSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFile.Delete();
|
||||||
|
cacheFile.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var patcherFileStream = cacheFile.Open(FileMode.Create);
|
||||||
|
{
|
||||||
|
await downloadStream.CopyToAsync(patcherFileStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
patcherFileStream.Close();
|
||||||
|
|
||||||
|
if (expectedHash != null && !FileHashHelper.CheckHash(cacheFile, expectedHash))
|
||||||
|
{
|
||||||
|
return GenericResult.FromError("Hash mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenericResult.FromSuccess();
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
return GenericResult.FromError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<GenericResult> ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cacheFile.Refresh();
|
||||||
|
Directory.CreateDirectory(_cachePath);
|
||||||
|
|
||||||
|
if (cacheFile.Exists)
|
||||||
|
{
|
||||||
|
if (expectedHash != null && FileHashHelper.CheckHash(cacheFile, expectedHash))
|
||||||
|
{
|
||||||
|
return GenericResult.FromSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFile.Delete();
|
||||||
|
cacheFile.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await DownloadFile(cacheFile, targetLink, progress, expectedHash);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
return GenericResult.FromError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<FileInfo> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash = null)
|
||||||
|
{
|
||||||
|
FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await ProcessInboundFileAsync(cacheFile, targetLink, progress, expectedHash);
|
||||||
|
|
||||||
|
return result.Succeeded ? cacheFile : null;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<FileInfo> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null)
|
||||||
|
{
|
||||||
|
FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await ProcessInboundStreamAsync(cacheFile, fileDownloadStream, expectedHash);
|
||||||
|
|
||||||
|
return result.Succeeded ? cacheFile : null;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
using HttpClientProgress;
|
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Helper
|
|
||||||
{
|
|
||||||
public static class DownloadHelper
|
|
||||||
{
|
|
||||||
private static HttpClient _httpClient = new HttpClient() { Timeout = TimeSpan.FromHours(1) };
|
|
||||||
|
|
||||||
public static bool FileHashCheck(FileInfo file, string expectedHash)
|
|
||||||
{
|
|
||||||
using (MD5 md5Service = MD5.Create())
|
|
||||||
using (var sourceStream = file.OpenRead())
|
|
||||||
{
|
|
||||||
byte[] sourceHash = md5Service.ComputeHash(sourceStream);
|
|
||||||
byte[] expectedHashBytes = Convert.FromBase64String(expectedHash);
|
|
||||||
|
|
||||||
bool matched = Enumerable.SequenceEqual(sourceHash, expectedHashBytes);
|
|
||||||
|
|
||||||
return matched;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async Task<GenericResult> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
outputFile.Refresh();
|
|
||||||
|
|
||||||
if (outputFile.Exists) outputFile.Delete();
|
|
||||||
|
|
||||||
// Use the provided extension method
|
|
||||||
using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
|
|
||||||
await _httpClient.DownloadDataAsync(targetLink, file, progress);
|
|
||||||
|
|
||||||
outputFile.Refresh();
|
|
||||||
|
|
||||||
if (!outputFile.Exists)
|
|
||||||
{
|
|
||||||
return GenericResult.FromError($"Failed to download {outputFile.Name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectedHash != null && !FileHashCheck(outputFile, expectedHash))
|
|
||||||
{
|
|
||||||
return GenericResult.FromError("Hash mismatch");
|
|
||||||
}
|
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return GenericResult.FromError(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
24
Aki.Helper/FileHashHelper.cs
Normal file
24
Aki.Helper/FileHashHelper.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace SPT_AKI_Installer.Aki.Helper
|
||||||
|
{
|
||||||
|
public static class FileHashHelper
|
||||||
|
{
|
||||||
|
public static bool CheckHash(FileInfo file, string expectedHash)
|
||||||
|
{
|
||||||
|
using (MD5 md5Service = MD5.Create())
|
||||||
|
using (var sourceStream = file.OpenRead())
|
||||||
|
{
|
||||||
|
byte[] sourceHash = md5Service.ComputeHash(sourceStream);
|
||||||
|
byte[] expectedHashBytes = Convert.FromBase64String(expectedHash);
|
||||||
|
|
||||||
|
bool matched = Enumerable.SequenceEqual(sourceHash, expectedHashBytes);
|
||||||
|
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user