using System.Net.Http; using System.Threading.Tasks; using Serilog; using SPTInstaller.Models; namespace SPTInstaller.Helpers; public static class DownloadCacheHelper { private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromHours(1) }; public static string CachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache"); public static string GetCacheSizeText() { if (!Directory.Exists(CachePath)) { var message = "No cache folder"; Log.Information(message); return message; } var cacheDir = new DirectoryInfo(CachePath); var cacheSize = DirectorySizeHelper.GetSizeOfDirectory(cacheDir); if (cacheSize == -1) { var message = "An error occurred while getting the cache size :("; Log.Error(message); return message; } if (cacheSize == 0) return "Empty"; return DirectorySizeHelper.SizeSuffix(cacheSize); } /// <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> 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 || expectedHash == null) { Log.Information($"{cacheFile.Name} {(cacheFile.Exists ? "is in cache" : "NOT in cache")}"); Log.Information($"Expected hash: {(expectedHash == null ? "not provided" : expectedHash)}"); return false; } if (FileHashHelper.CheckHash(cacheFile, expectedHash)) { fileInCache = cacheFile; return true; } return false; } catch { return false; } } /// <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> /// <remarks>If the file exists, it is deleted before downloading</remarks> public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink, IProgress<double> progress) { Directory.CreateDirectory(CachePath); 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); outputFile.Refresh(); if (!outputFile.Exists) { Log.Error("Failed to download file from url: {name} :: {url}", outputFileName, targetLink); return null; } return outputFile; } catch (Exception ex) { Log.Error(ex, "Failed to download file from url: {name} :: {url}", outputFileName, targetLink); return null; } } /// <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> /// <remarks>If the file exists, it is deleted before downloading</remarks> public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, Stream downloadStream) { Directory.CreateDirectory(CachePath); var outputFile = new FileInfo(Path.Join(CachePath, outputFileName)); try { if (outputFile.Exists) outputFile.Delete(); using var patcherFileStream = outputFile.Open(FileMode.Create); { await downloadStream.CopyToAsync(patcherFileStream); } patcherFileStream.Close(); outputFile.Refresh(); if (!outputFile.Exists) { Log.Error("Failed to download file from stream: {name}", outputFileName); return null; } return outputFile; } catch(Exception ex) { Log.Error(ex, "Failed to download file from stream: {fileName}", outputFileName); return null; } } /// <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> public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash) { try { if (CheckCache(fileName, expectedHash, out var cacheFile)) return cacheFile; return await DownloadFileAsync(fileName, targetLink, progress); } catch (Exception ex) { Log.Error(ex, $"Error while getting file: {fileName}"); return null; } } /// <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> public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash) { try { if (CheckCache(fileName, expectedHash, out var cacheFile)) return cacheFile; return await DownloadFileAsync(fileName, fileDownloadStream); } catch (Exception ex) { Log.Error(ex, $"Error while getting file: {fileName}"); return null; } } }