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()
|
||||
{
|
||||
var mirrorListInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "mirrors.json"));
|
||||
|
||||
SetStatus("Downloading Mirror List", false);
|
||||
|
||||
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;
|
||||
|
||||
@ -63,30 +61,26 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
try
|
||||
{
|
||||
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();
|
||||
}
|
||||
catch (Exception)
|
||||
catch
|
||||
{
|
||||
//most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
|
||||
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();
|
||||
}
|
||||
@ -97,13 +91,8 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
|
||||
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.PatcherZipInfo.Exists) _data.PatcherZipInfo.Delete();
|
||||
|
||||
var buildResult = await BuildMirrorList();
|
||||
|
||||
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);
|
||||
|
||||
Progress = 0;
|
||||
|
||||
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();
|
||||
|
@ -31,8 +31,6 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
|
||||
var extractPatcherProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
||||
|
||||
|
||||
|
||||
var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, extractPatcherProgress);
|
||||
|
||||
if (!extractPatcherResult.Succeeded)
|
||||
@ -65,7 +63,6 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
{
|
||||
return patchingResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -92,9 +89,6 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
||||
patcherOutputDir.Delete(true);
|
||||
patcherEXE.Delete();
|
||||
}
|
||||
|
||||
_data.PatcherZipInfo.Delete();
|
||||
_data.AkiZipInfo.Delete();
|
||||
|
||||
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