diff --git a/Aki.Core/Tasks/DownloadTask.cs b/Aki.Core/Tasks/DownloadTask.cs index c49373f..52815e5 100644 --- a/Aki.Core/Tasks/DownloadTask.cs +++ b/Aki.Core/Tasks/DownloadTask.cs @@ -20,22 +20,20 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks private async Task BuildMirrorList() { - var mirrorListInfo = new FileInfo(Path.Join(_data.TargetInstallPath, "mirrors.json")); - SetStatus("Downloading Mirror List", false); var progress = new Progress((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>(File.ReadAllText(mirrorListInfo.FullName)); + var mirrorsList = JsonConvert.DeserializeObject>(File.ReadAllText(file.FullName)); - if (blah is List mirrors) + if (mirrorsList is List 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 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((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(); diff --git a/Aki.Core/Tasks/SetupClientTask.cs b/Aki.Core/Tasks/SetupClientTask.cs index 2c9582f..0657e9d 100644 --- a/Aki.Core/Tasks/SetupClientTask.cs +++ b/Aki.Core/Tasks/SetupClientTask.cs @@ -31,8 +31,6 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks var extractPatcherProgress = new Progress((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!"); } diff --git a/Aki.Helper/DownloadCacheHelper.cs b/Aki.Helper/DownloadCacheHelper.cs new file mode 100644 index 0000000..e1e3c04 --- /dev/null +++ b/Aki.Helper/DownloadCacheHelper.cs @@ -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 DownloadFile(FileInfo outputFile, string targetLink, IProgress 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 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 ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress 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 GetOrDownloadFileAsync(string fileName, string targetLink, IProgress 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 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; + } + } + } +} diff --git a/Aki.Helper/DownloadHelper.cs b/Aki.Helper/DownloadHelper.cs deleted file mode 100644 index 6e48c30..0000000 --- a/Aki.Helper/DownloadHelper.cs +++ /dev/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 DownloadFile(FileInfo outputFile, string targetLink, IProgress 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); - } - } - } -} diff --git a/Aki.Helper/FileHashHelper.cs b/Aki.Helper/FileHashHelper.cs new file mode 100644 index 0000000..dead830 --- /dev/null +++ b/Aki.Helper/FileHashHelper.cs @@ -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; + } + } + } +}