diff --git a/SPTInstaller/Helpers/DownloadCacheHelper.cs b/SPTInstaller/Helpers/DownloadCacheHelper.cs
index 3e51d7f..a11a055 100644
--- a/SPTInstaller/Helpers/DownloadCacheHelper.cs
+++ b/SPTInstaller/Helpers/DownloadCacheHelper.cs
@@ -26,23 +26,32 @@ public static class DownloadCacheHelper
return DirectorySizeHelper.SizeSuffix(cacheSize);
}
- private static bool CheckCache(FileInfo cacheFile, string expectedHash = null)
+ ///
+ /// Check if a file in the cache already exists
+ ///
+ /// The name of the file to check for
+ /// The expected hash of the file in the cache
+ /// The file found in the cache; null if no file is found
+ /// True if the file is in the cache and its hash matches the expected hash, otherwise false
+ public static bool CheckCache(string fileName, string expectedHash, out FileInfo cachedFile)
+ => CheckCache(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);
+
+ private static bool CheckCache(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
{
+ fileInCache = cacheFile;
+
try
{
cacheFile.Refresh();
Directory.CreateDirectory(CachePath);
- if (cacheFile.Exists)
- {
- if (expectedHash != null && FileHashHelper.CheckHash(cacheFile, expectedHash))
- {
- Log.Information($"Using cached file: {cacheFile.Name} - Hash: {expectedHash}");
- return true;
- }
+ if (!cacheFile.Exists || expectedHash == null)
+ return false;
- cacheFile.Delete();
- cacheFile.Refresh();
+ if (FileHashHelper.CheckHash(cacheFile, expectedHash))
+ {
+ fileInCache = cacheFile;
+ return true;
}
return false;
@@ -53,10 +62,23 @@ public static class DownloadCacheHelper
}
}
- private static async Task DownloadFile(FileInfo outputFile, string targetLink, IProgress progress, string expectedHash = null)
+ ///
+ /// Download a file to the cache folder
+ ///
+ /// The file name to save the file as
+ /// The url to download the file from
+ /// A provider for progress updates
+ /// A object of the cached file
+ /// If the file exists, it is deleted before downloading
+ public static async Task DownloadFileAsync(string outputFileName, string targetLink, IProgress progress)
{
+ var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
+
try
{
+ 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);
@@ -65,90 +87,100 @@ public static class DownloadCacheHelper
if (!outputFile.Exists)
{
- return Result.FromError($"Failed to download {outputFile.Name}");
+ Log.Error("Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
+ return null;
}
- if (expectedHash != null && !FileHashHelper.CheckHash(outputFile, expectedHash))
- {
- return Result.FromError("Hash mismatch");
- }
-
- return Result.FromSuccess();
+ return outputFile;
}
catch (Exception ex)
{
- return Result.FromError(ex.Message);
+ Log.Error(ex, "Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
+ return null;
}
}
- private static async Task ProcessInboundStreamAsync(FileInfo cacheFile, Stream downloadStream, string expectedHash = null)
+ ///
+ /// Download a file to the cache folder
+ ///
+ /// The file name to save the file as
+ /// The stream the download the file from
+ /// A object of the cached file
+ /// If the file exists, it is deleted before downloading
+ public static async Task DownloadFileAsync(string outputFileName, Stream downloadStream)
{
+ var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
+
try
{
- if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
+ if (outputFile.Exists)
+ outputFile.Delete();
- using var patcherFileStream = cacheFile.Open(FileMode.Create);
+ using var patcherFileStream = outputFile.Open(FileMode.Create);
{
await downloadStream.CopyToAsync(patcherFileStream);
}
patcherFileStream.Close();
- if (expectedHash != null && !FileHashHelper.CheckHash(cacheFile, expectedHash))
+ if (!outputFile.Exists)
{
- return Result.FromError("Hash mismatch");
+ Log.Error("Failed to download file from stream: {name}", outputFileName);
+ return null;
}
- return Result.FromSuccess();
+ return outputFile;
}
catch(Exception ex)
{
- return Result.FromError(ex.Message);
+ Log.Error(ex, "Failed to download file from stream: {fileName}", outputFileName);
+ return null;
}
}
- private static async Task ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress progress, string expectedHash = null)
+ ///
+ /// Get the file from cache or download it
+ ///
+ /// The name of the file to check for in the cache
+ /// The url to download from if the file doesn't exist in the cache
+ /// A provider for progress updates
+ /// The expected hash of the cached file
+ /// A object of the cached file
+ /// Use if you don't have an expected cache file hash
+ public static async Task GetOrDownloadFileAsync(string fileName, string targetLink, IProgress progress, string expectedHash)
{
try
{
- if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
+ if (CheckCache(fileName, expectedHash, out var cacheFile))
+ return cacheFile;
- return await DownloadFile(cacheFile, targetLink, progress, expectedHash);
+ return await DownloadFileAsync(fileName, targetLink, progress);
}
- catch(Exception ex)
- {
- return Result.FromError(ex.Message);
- }
- }
-
- public static async Task GetOrDownloadFileAsync(string fileName, string targetLink, IProgress progress, string expectedHash = null)
- {
- var cacheFile = new FileInfo(Path.Join(CachePath, fileName));
-
- try
- {
- var result = await ProcessInboundFileAsync(cacheFile, targetLink, progress, expectedHash);
-
- return result.Succeeded ? cacheFile : null;
- }
- catch(Exception ex)
+ catch (Exception ex)
{
Log.Error(ex, $"Error while getting file: {fileName}");
return null;
}
}
- public static async Task GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null)
+ ///
+ /// Get the file from cache or download it
+ ///
+ /// The name of the file to check for in the cache
+ /// The stream to download from if the file doesn't exist in the cache
+ /// The expected hash of the cached file
+ /// A object of the cached file
+ /// Use if you don't have an expected cache file hash
+ public static async Task GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash)
{
- var cacheFile = new FileInfo(Path.Join(CachePath, fileName));
-
try
{
- var result = await ProcessInboundStreamAsync(cacheFile, fileDownloadStream, expectedHash);
+ if (CheckCache(fileName, expectedHash, out var cacheFile))
+ return cacheFile;
- return result.Succeeded ? cacheFile : null;
+ return await DownloadFileAsync(fileName, fileDownloadStream);
}
- catch(Exception ex)
+ catch (Exception ex)
{
Log.Error(ex, $"Error while getting file: {fileName}");
return null;
diff --git a/SPTInstaller/Installer Tasks/DownloadTask.cs b/SPTInstaller/Installer Tasks/DownloadTask.cs
index 15e651b..374f44e 100644
--- a/SPTInstaller/Installer Tasks/DownloadTask.cs
+++ b/SPTInstaller/Installer Tasks/DownloadTask.cs
@@ -1,16 +1,20 @@
-using CG.Web.MegaApiClient;
-using Newtonsoft.Json;
+using Newtonsoft.Json;
using SPTInstaller.Interfaces;
using SPTInstaller.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
using SPTInstaller.Helpers;
+using SPTInstaller.Models.Mirrors;
+using SPTInstaller.Models.Mirrors.Downloaders;
+using Serilog;
namespace SPTInstaller.Installer_Tasks;
public class DownloadTask : InstallerTaskBase
{
private InternalData _data;
+ private List _mirrors = new List();
+ private string _expectedPatcherHash = "";
public DownloadTask(InternalData data) : base("Download Files")
{
@@ -19,9 +23,9 @@ public class DownloadTask : InstallerTaskBase
private async Task BuildMirrorList()
{
- var progress = new Progress((d) => { SetStatus("Downloading Mirror List", "", (int)Math.Floor(d));});
+ var progress = new Progress((d) => { SetStatus("Downloading Mirror List", "", (int)Math.Floor(d), ProgressStyle.Shown);});
- var file = await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress);
+ var file = await DownloadCacheHelper.DownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress);
if (file == null)
{
@@ -30,52 +34,43 @@ public class DownloadTask : InstallerTaskBase
var mirrorsList = JsonConvert.DeserializeObject>(File.ReadAllText(file.FullName));
- if (mirrorsList is List mirrors)
- {
- _data.PatcherReleaseMirrors = mirrors;
+ if (mirrorsList == null)
+ return Result.FromError("Failed to deserialize mirrors list");
- return Result.FromSuccess();
+ foreach (var mirror in mirrorsList)
+ {
+ _expectedPatcherHash = mirror.Hash;
+
+ switch (mirror.Link)
+ {
+ case string l when l.StartsWith("https://mega"):
+ _mirrors.Add(new MegaMirrorDownloader(mirror));
+ break;
+ default:
+ _mirrors.Add(new HttpMirrorDownloader(mirror));
+ break;
+ }
}
- return Result.FromError("Failed to deserialize mirrors list");
+ return Result.FromSuccess("Mirrors list ready");
}
private async Task DownloadPatcherFromMirrors(IProgress progress)
{
- foreach (var mirror in _data.PatcherReleaseMirrors)
+ SetStatus("Downloading Patcher", "Verifying cached patcher ...", progressStyle: ProgressStyle.Indeterminate);
+
+ if (DownloadCacheHelper.CheckCache("patcher.zip", _expectedPatcherHash, out var cacheFile))
{
- SetStatus($"Downloading Patcher", mirror.Link);
+ _data.PatcherZipInfo = cacheFile;
+ Log.Information("Using cached file {fileName} - Hash: {hash}", _data.PatcherZipInfo.Name, _expectedPatcherHash);
+ return Result.FromSuccess();
+ }
- // mega is a little weird since they use encryption, but thankfully there is a great library for their api :)
- if (mirror.Link.StartsWith("https://mega"))
- {
- var megaClient = new MegaApiClient();
- await megaClient.LoginAnonymousAsync();
+ foreach (var mirror in _mirrors)
+ {
+ SetStatus("Downloading Patcher", mirror.MirrorInfo.Link, progressStyle: ProgressStyle.Indeterminate);
- // if mega fails to connect, try the next mirror
- if (!megaClient.IsLoggedIn) continue;
-
- try
- {
- using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(mirror.Link), progress);
-
- _data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", megaDownloadStream, mirror.Hash);
-
- if(_data.PatcherZipInfo == null)
- {
- continue;
- }
-
- return Result.FromSuccess();
- }
- catch
- {
- //most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
- continue;
- }
- }
-
- _data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", mirror.Link, progress, mirror.Hash);
+ _data.PatcherZipInfo = await mirror.Download(progress);
if (_data.PatcherZipInfo != null)
{
diff --git a/SPTInstaller/Interfaces/IMirrorDownloader.cs b/SPTInstaller/Interfaces/IMirrorDownloader.cs
new file mode 100644
index 0000000..1540620
--- /dev/null
+++ b/SPTInstaller/Interfaces/IMirrorDownloader.cs
@@ -0,0 +1,9 @@
+using SPTInstaller.Models.Mirrors;
+using System.Threading.Tasks;
+
+namespace SPTInstaller.Interfaces;
+public interface IMirrorDownloader
+{
+ public DownloadMirror MirrorInfo { get; }
+ public Task Download(IProgress progress);
+}
diff --git a/SPTInstaller/Models/InstallerUpdateInfo.cs b/SPTInstaller/Models/InstallerUpdateInfo.cs
index 2b6e956..bb5769c 100644
--- a/SPTInstaller/Models/InstallerUpdateInfo.cs
+++ b/SPTInstaller/Models/InstallerUpdateInfo.cs
@@ -88,7 +88,7 @@ public class InstallerUpdateInfo : ReactiveObject
var progress = new Progress(x => DownloadProgress = (int)x);
- var file = await DownloadCacheHelper.GetOrDownloadFileAsync("SPTInstller.exe", NewInstallerUrl, progress);
+ var file = await DownloadCacheHelper.DownloadFileAsync("SPTInstller.exe", NewInstallerUrl, progress);
if (file == null || !file.Exists)
{
diff --git a/SPTInstaller/Models/InternalData.cs b/SPTInstaller/Models/InternalData.cs
index 3f9f423..e81a2b7 100644
--- a/SPTInstaller/Models/InternalData.cs
+++ b/SPTInstaller/Models/InternalData.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using SPTInstaller.Models.Mirrors;
namespace SPTInstaller.Models;
@@ -44,11 +45,6 @@ public class InternalData
///
public string PatcherMirrorsLink { get; set; }
- ///
- /// The release download mirrors for the patcher
- ///
- public List PatcherReleaseMirrors { get; set; } = null;
-
///
/// Whether or not a patch is needed to downgrade the client files
///
diff --git a/SPTInstaller/Models/DownloadMirror.cs b/SPTInstaller/Models/Mirrors/DownloadMirror.cs
similarity index 71%
rename from SPTInstaller/Models/DownloadMirror.cs
rename to SPTInstaller/Models/Mirrors/DownloadMirror.cs
index 40a83fd..b44a00a 100644
--- a/SPTInstaller/Models/DownloadMirror.cs
+++ b/SPTInstaller/Models/Mirrors/DownloadMirror.cs
@@ -1,4 +1,4 @@
-namespace SPTInstaller.Models;
+namespace SPTInstaller.Models.Mirrors;
public class DownloadMirror
{
diff --git a/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs b/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs
new file mode 100644
index 0000000..584fe1b
--- /dev/null
+++ b/SPTInstaller/Models/Mirrors/Downloaders/HttpMirrorDownloader.cs
@@ -0,0 +1,20 @@
+using SPTInstaller.Helpers;
+using System.Threading.Tasks;
+
+namespace SPTInstaller.Models.Mirrors.Downloaders;
+public class HttpMirrorDownloader : MirrorDownloaderBase
+{
+ public HttpMirrorDownloader(DownloadMirror mirror) : base(mirror)
+ {
+ }
+
+ public override async Task Download(IProgress progress)
+ {
+ var file = await DownloadCacheHelper.DownloadFileAsync("patcher.zip", MirrorInfo.Link, progress);
+
+ if (file == null)
+ return null;
+
+ return FileHashHelper.CheckHash(file, MirrorInfo.Hash) ? file : null;
+ }
+}
diff --git a/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs b/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs
new file mode 100644
index 0000000..fb79e35
--- /dev/null
+++ b/SPTInstaller/Models/Mirrors/Downloaders/MegaMirrorDownloader.cs
@@ -0,0 +1,38 @@
+using CG.Web.MegaApiClient;
+using SPTInstaller.Helpers;
+using System.Threading.Tasks;
+
+namespace SPTInstaller.Models.Mirrors.Downloaders;
+public class MegaMirrorDownloader : MirrorDownloaderBase
+{
+ public MegaMirrorDownloader(DownloadMirror mirrorInfo) : base(mirrorInfo)
+ {
+ }
+
+ public override async Task Download(IProgress progress)
+ {
+ var megaClient = new MegaApiClient();
+ await megaClient.LoginAnonymousAsync();
+
+ // if mega fails to connect, just return
+ if (!megaClient.IsLoggedIn)
+ return null;
+
+ try
+ {
+ using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(MirrorInfo.Link), progress);
+
+ var file = await DownloadCacheHelper.DownloadFileAsync("patcher.zip", megaDownloadStream);
+
+ if (file == null)
+ return null;
+
+ return FileHashHelper.CheckHash(file, MirrorInfo.Hash) ? file : null;
+ }
+ catch
+ {
+ //most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
+ return null;
+ }
+ }
+}
diff --git a/SPTInstaller/Models/Mirrors/Downloaders/MirrorDownloaderBase.cs b/SPTInstaller/Models/Mirrors/Downloaders/MirrorDownloaderBase.cs
new file mode 100644
index 0000000..2fb6dcb
--- /dev/null
+++ b/SPTInstaller/Models/Mirrors/Downloaders/MirrorDownloaderBase.cs
@@ -0,0 +1,13 @@
+using SPTInstaller.Interfaces;
+using System.Threading.Tasks;
+
+namespace SPTInstaller.Models.Mirrors.Downloaders;
+public abstract class MirrorDownloaderBase : IMirrorDownloader
+{
+ public DownloadMirror MirrorInfo { get; private set; }
+ public abstract Task Download(IProgress progress);
+ public MirrorDownloaderBase(DownloadMirror mirrorInfo)
+ {
+ MirrorInfo = mirrorInfo;
+ }
+}
diff --git a/SPTInstaller/Resources/update.ps1 b/SPTInstaller/Resources/update.ps1
index 7e75bec..5cb9e24 100644
--- a/SPTInstaller/Resources/update.ps1
+++ b/SPTInstaller/Resources/update.ps1
@@ -22,6 +22,9 @@ Start-BitsTransfer -Source $source -Destination $destination -DisplayName "Updat
Remove-Module -Name BitsTransfer
+# remove the new installer from the cache folder after it is copied
+Remove-Item -Path $source
+
Start-Process $destination
Write-Host "Done"
\ No newline at end of file
diff --git a/SPTInstaller/SPTInstaller.csproj b/SPTInstaller/SPTInstaller.csproj
index b919a07..0951158 100644
--- a/SPTInstaller/SPTInstaller.csproj
+++ b/SPTInstaller/SPTInstaller.csproj
@@ -9,8 +9,8 @@
icon.ico
Assets\icon.ico
Debug;Release;TEST
- 2.13
- 2.13
+ 2.14
+ 2.14
@@ -35,12 +35,12 @@
-
+
-
+