2023-07-12 09:19:33 +02:00
|
|
|
|
using System.Net.Http;
|
|
|
|
|
using System.Threading.Tasks;
|
2023-05-18 20:04:00 -04:00
|
|
|
|
using Serilog;
|
2023-03-04 15:33:37 -05:00
|
|
|
|
|
2023-07-12 09:19:33 +02:00
|
|
|
|
namespace SPTInstaller.Helpers;
|
|
|
|
|
|
|
|
|
|
public static class DownloadCacheHelper
|
2023-03-04 15:33:37 -05:00
|
|
|
|
{
|
2024-05-01 15:34:35 -04:00
|
|
|
|
private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromMinutes(15) };
|
2024-05-04 16:18:49 -04:00
|
|
|
|
|
|
|
|
|
public static TimeSpan SuggestedTtl = TimeSpan.FromHours(1);
|
2024-05-01 10:32:22 -04:00
|
|
|
|
public static string CachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
|
|
|
"spt-installer/cache");
|
|
|
|
|
|
2024-04-26 09:52:41 -07:00
|
|
|
|
public static string ReleaseMirrorUrl = "https://spt-releases.modd.in/release.json";
|
2024-04-27 14:51:54 -04:00
|
|
|
|
public static string PatchMirrorUrl = "https://slugma.waffle-lord.net/mirrors.json";
|
2024-05-01 10:32:22 -04:00
|
|
|
|
public static string InstallerUrl = "https://ligma.waffle-lord.net/SPTInstaller.exe";
|
|
|
|
|
public static string InstallerInfoUrl = "https://ligma.waffle-lord.net/installer.json";
|
|
|
|
|
|
2023-08-25 23:46:11 -04:00
|
|
|
|
public static string GetCacheSizeText()
|
|
|
|
|
{
|
|
|
|
|
if (!Directory.Exists(CachePath))
|
2024-01-19 22:51:06 -05:00
|
|
|
|
{
|
|
|
|
|
var message = "No cache folder";
|
|
|
|
|
Log.Information(message);
|
|
|
|
|
return message;
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-08-25 23:46:11 -04:00
|
|
|
|
var cacheDir = new DirectoryInfo(CachePath);
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-08-25 23:46:11 -04:00
|
|
|
|
var cacheSize = DirectorySizeHelper.GetSizeOfDirectory(cacheDir);
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-10-20 22:26:13 -04:00
|
|
|
|
if (cacheSize == -1)
|
|
|
|
|
{
|
2024-01-19 22:51:06 -05:00
|
|
|
|
var message = "An error occurred while getting the cache size :(";
|
|
|
|
|
Log.Error(message);
|
|
|
|
|
return message;
|
2023-10-20 22:26:13 -04:00
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-08-25 23:46:11 -04:00
|
|
|
|
if (cacheSize == 0)
|
|
|
|
|
return "Empty";
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-08-25 23:46:11 -04:00
|
|
|
|
return DirectorySizeHelper.SizeSuffix(cacheSize);
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check if a file in the cache already exists
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fileName">The name of the file to check for</param>
|
|
|
|
|
/// <param name="expectedHash">The expected hash of the file in the cache</param>
|
|
|
|
|
/// <param name="cachedFile">The file found in the cache; null if no file is found</param>
|
|
|
|
|
/// <returns>True if the file is in the cache and its hash matches the expected hash, otherwise false</returns>
|
2024-05-04 16:18:49 -04:00
|
|
|
|
public static bool CheckCacheHash(string fileName, string expectedHash, out FileInfo cachedFile)
|
|
|
|
|
=> CheckCacheHash(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2024-05-04 16:18:49 -04:00
|
|
|
|
private static bool CheckCacheHash(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
2023-09-21 10:52:26 -04:00
|
|
|
|
fileInCache = cacheFile;
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-07-12 09:19:33 +02:00
|
|
|
|
try
|
2023-03-06 18:28:02 -05:00
|
|
|
|
{
|
2023-07-12 09:19:33 +02:00
|
|
|
|
cacheFile.Refresh();
|
2023-07-30 16:15:52 -04:00
|
|
|
|
Directory.CreateDirectory(CachePath);
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
if (!cacheFile.Exists || expectedHash == null)
|
2024-01-19 22:51:06 -05:00
|
|
|
|
{
|
|
|
|
|
Log.Information($"{cacheFile.Name} {(cacheFile.Exists ? "is in cache" : "NOT in cache")}");
|
|
|
|
|
Log.Information($"Expected hash: {(expectedHash == null ? "not provided" : expectedHash)}");
|
2023-09-21 10:52:26 -04:00
|
|
|
|
return false;
|
2024-01-19 22:51:06 -05:00
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
if (FileHashHelper.CheckHash(cacheFile, expectedHash))
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
2023-09-21 10:52:26 -04:00
|
|
|
|
fileInCache = cacheFile;
|
2024-03-25 14:40:09 -04:00
|
|
|
|
Log.Information("Hashes MATCH");
|
2023-09-21 10:52:26 -04:00
|
|
|
|
return true;
|
2023-03-06 18:28:02 -05:00
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2024-03-25 14:40:09 -04:00
|
|
|
|
Log.Warning("Hashes DO NOT MATCH");
|
2023-07-12 09:19:33 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
catch (Exception ex)
|
2023-03-04 15:33:37 -05:00
|
|
|
|
{
|
2024-03-25 14:40:09 -04:00
|
|
|
|
Log.Error(ex, "Something went wrong during hashing");
|
2023-07-12 09:19:33 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-04 16:18:49 -04:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a file in the cache based on a time-to-live from its last modified time
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fileName">The name of the file to look for in the cache</param>
|
|
|
|
|
/// <param name="ttl">The time-to-live to check against</param>
|
|
|
|
|
/// <param name="cachedFile">The file found in the cache if it exists</param>
|
|
|
|
|
/// <returns>Returns true if the file was found in the cache, otherwise false</returns>
|
|
|
|
|
public static bool CheckCacheTTL(string fileName, TimeSpan ttl, out FileInfo cachedFile) =>
|
|
|
|
|
CheckCacheTTL(new FileInfo(Path.Join(CachePath, fileName)), ttl, out cachedFile);
|
|
|
|
|
|
|
|
|
|
private static bool CheckCacheTTL(FileInfo cacheFile, TimeSpan ttl, out FileInfo fileInCache)
|
|
|
|
|
{
|
|
|
|
|
fileInCache = cacheFile;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
cacheFile.Refresh();
|
|
|
|
|
Directory.CreateDirectory(CachePath);
|
|
|
|
|
|
|
|
|
|
if (!cacheFile.Exists)
|
|
|
|
|
{
|
|
|
|
|
Log.Information($"{cacheFile.Name} {(cacheFile.Exists ? "is in cache" : "NOT in cache")}");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var validTimeToLive = cacheFile.LastWriteTime.Add(ttl) > DateTime.Now;
|
|
|
|
|
|
|
|
|
|
Log.Information($"{cacheFile.Name} TTL is {(validTimeToLive ? "OK" : "INVALID")}");
|
|
|
|
|
|
|
|
|
|
return validTimeToLive;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.Error(ex, "Something went wrong during hashing");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Download a file to the cache folder
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="outputFileName">The file name to save the file as</param>
|
|
|
|
|
/// <param name="targetLink">The url to download the file from</param>
|
|
|
|
|
/// <param name="progress">A provider for progress updates</param>
|
|
|
|
|
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
|
2023-09-21 18:52:33 -04:00
|
|
|
|
/// <remarks>If the file exists, it is deleted before downloading</remarks>
|
2024-05-01 10:32:22 -04:00
|
|
|
|
public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink,
|
|
|
|
|
IProgress<double> progress)
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
2023-09-22 18:45:13 -04:00
|
|
|
|
Directory.CreateDirectory(CachePath);
|
2023-09-21 10:52:26 -04:00
|
|
|
|
var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-07-12 09:19:33 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
2023-09-21 18:52:33 -04:00
|
|
|
|
if (outputFile.Exists)
|
|
|
|
|
outputFile.Delete();
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-07-12 09:19:33 +02:00
|
|
|
|
// Use the provided extension method
|
|
|
|
|
using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
|
2024-05-06 15:18:54 -04:00
|
|
|
|
{
|
|
|
|
|
if (!await _httpClient.DownloadDataAsync(targetLink, file, progress))
|
|
|
|
|
{
|
|
|
|
|
Log.Error($"Download failed: {targetLink}");
|
|
|
|
|
|
|
|
|
|
outputFile.Refresh();
|
|
|
|
|
|
|
|
|
|
if (outputFile.Exists)
|
|
|
|
|
{
|
|
|
|
|
outputFile.Delete();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-07-12 09:19:33 +02:00
|
|
|
|
outputFile.Refresh();
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-07-12 09:19:33 +02:00
|
|
|
|
if (!outputFile.Exists)
|
|
|
|
|
{
|
2023-09-21 10:52:26 -04:00
|
|
|
|
Log.Error("Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
|
|
|
|
|
return null;
|
2023-03-04 15:33:37 -05:00
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
return outputFile;
|
2023-07-12 09:19:33 +02:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
2023-03-04 15:33:37 -05:00
|
|
|
|
{
|
2023-09-21 10:52:26 -04:00
|
|
|
|
Log.Error(ex, "Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
|
|
|
|
|
return null;
|
2023-07-12 09:19:33 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Download a file to the cache folder
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="outputFileName">The file name to save the file as</param>
|
|
|
|
|
/// <param name="downloadStream">The stream the download the file from</param>
|
|
|
|
|
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
|
2023-09-21 18:52:33 -04:00
|
|
|
|
/// <remarks>If the file exists, it is deleted before downloading</remarks>
|
2023-09-21 10:52:26 -04:00
|
|
|
|
public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, Stream downloadStream)
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
2023-09-22 18:45:13 -04:00
|
|
|
|
Directory.CreateDirectory(CachePath);
|
2023-09-21 10:52:26 -04:00
|
|
|
|
var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-07-12 09:19:33 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
2023-09-21 18:52:33 -04:00
|
|
|
|
if (outputFile.Exists)
|
|
|
|
|
outputFile.Delete();
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
using var patcherFileStream = outputFile.Open(FileMode.Create);
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
|
|
|
|
await downloadStream.CopyToAsync(patcherFileStream);
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-07-12 09:19:33 +02:00
|
|
|
|
patcherFileStream.Close();
|
2024-03-19 11:29:46 -04:00
|
|
|
|
|
|
|
|
|
outputFile.Refresh();
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
if (!outputFile.Exists)
|
2023-03-04 15:33:37 -05:00
|
|
|
|
{
|
2023-09-21 10:52:26 -04:00
|
|
|
|
Log.Error("Failed to download file from stream: {name}", outputFileName);
|
|
|
|
|
return null;
|
2023-03-04 15:33:37 -05:00
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
return outputFile;
|
2023-03-04 15:33:37 -05:00
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
catch (Exception ex)
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
2023-09-21 10:52:26 -04:00
|
|
|
|
Log.Error(ex, "Failed to download file from stream: {fileName}", outputFileName);
|
|
|
|
|
return null;
|
2023-07-12 09:19:33 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-04 16:18:49 -04:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get or download a file using a time to live
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fileName">The file to get from cache</param>
|
|
|
|
|
/// <param name="targetLink">The link to use for the download</param>
|
|
|
|
|
/// <param name="progress">A progress object for reporting download progress</param>
|
|
|
|
|
/// <param name="timeToLive">The time-to-live to check against in the cache</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink,
|
|
|
|
|
IProgress<double> progress, TimeSpan timeToLive)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (CheckCacheTTL(fileName, timeToLive, out FileInfo cachedFile))
|
|
|
|
|
{
|
|
|
|
|
return cachedFile;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-06 15:18:54 -04:00
|
|
|
|
Log.Information($"Downloading File: {targetLink}");
|
2024-05-04 16:18:49 -04:00
|
|
|
|
return await DownloadFileAsync(fileName, targetLink, progress);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.Error(ex, $"Error while getting file: {fileName}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get the file from cache or download it
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fileName">The name of the file to check for in the cache</param>
|
|
|
|
|
/// <param name="targetLink">The url to download from if the file doesn't exist in the cache</param>
|
|
|
|
|
/// <param name="progress">A provider for progress updates</param>
|
|
|
|
|
/// <param name="expectedHash">The expected hash of the cached file</param>
|
|
|
|
|
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
|
|
|
|
|
/// <remarks>Use <see cref="DownloadFileAsync(string, string, IProgress{double})"/> if you don't have an expected cache file hash</remarks>
|
2024-05-01 10:32:22 -04:00
|
|
|
|
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink,
|
|
|
|
|
IProgress<double> progress, string expectedHash)
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2024-05-04 16:18:49 -04:00
|
|
|
|
if (CheckCacheHash(fileName, expectedHash, out var cacheFile))
|
2023-09-21 10:52:26 -04:00
|
|
|
|
return cacheFile;
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2024-05-06 15:18:54 -04:00
|
|
|
|
Log.Information($"Downloading File: {targetLink}");
|
2023-09-21 10:52:26 -04:00
|
|
|
|
return await DownloadFileAsync(fileName, targetLink, progress);
|
2023-07-12 09:19:33 +02:00
|
|
|
|
}
|
2023-09-21 10:52:26 -04:00
|
|
|
|
catch (Exception ex)
|
2023-03-04 15:33:37 -05:00
|
|
|
|
{
|
2023-07-12 09:19:33 +02:00
|
|
|
|
Log.Error(ex, $"Error while getting file: {fileName}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get the file from cache or download it
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fileName">The name of the file to check for in the cache</param>
|
|
|
|
|
/// <param name="fileDownloadStream">The stream to download from if the file doesn't exist in the cache</param>
|
|
|
|
|
/// <param name="expectedHash">The expected hash of the cached file</param>
|
|
|
|
|
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
|
|
|
|
|
/// <remarks>Use <see cref="DownloadFileAsync(string, Stream)"/> if you don't have an expected cache file hash</remarks>
|
2024-05-01 10:32:22 -04:00
|
|
|
|
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream,
|
|
|
|
|
string expectedHash)
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2024-05-04 16:18:49 -04:00
|
|
|
|
if (CheckCacheHash(fileName, expectedHash, out var cacheFile))
|
2023-09-21 10:52:26 -04:00
|
|
|
|
return cacheFile;
|
2024-05-01 10:32:22 -04:00
|
|
|
|
|
2023-09-21 10:52:26 -04:00
|
|
|
|
return await DownloadFileAsync(fileName, fileDownloadStream);
|
2023-07-12 09:19:33 +02:00
|
|
|
|
}
|
2023-09-21 10:52:26 -04:00
|
|
|
|
catch (Exception ex)
|
2023-07-12 09:19:33 +02:00
|
|
|
|
{
|
|
|
|
|
Log.Error(ex, $"Error while getting file: {fileName}");
|
|
|
|
|
return null;
|
2023-03-04 15:33:37 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 09:19:33 +02:00
|
|
|
|
}
|