made file downloading and caching smarter

tries eTag and ContentDisposition headers for file name+format
catches offline and server error/url error scenarios
creates a "recent" file copy if successful, and returns that as a fallback in an error scenario, if it exists
This commit is contained in:
Bailey Eaton 2023-06-05 07:39:10 +10:00
parent 230b7338a6
commit ba0cbbd5c8

View File

@ -12,49 +12,96 @@ namespace TED.Utils
internal class FileUtilities
{
/// <summary>
/// Downloads a file from a specified URL and saves it to a cache.
/// Downloads a file from a specified URL and saves it to a local cache.
/// If the file already exists in the cache, the function returns the path to the cached file.
/// If the file does not exist in the cache, the function downloads the file, saves it to the cache, and then returns the path to the cached file.
/// In the event of an error with file retrieval, the most recently used file will be returned, if it exists.
/// </summary>
/// <param name="url">The URL of the file to download.</param>
/// <returns>A task that represents the asynchronous operation. The task result is the path to the downloaded (or cached) file.</returns>
public static async Task<string> DownloadAndCacheFileAsync(string url)
{
using (var client = new HttpClient()
var tedDirectory = Path.Combine(Path.GetTempPath(), "TED");
var recentPath = Path.Combine(tedDirectory, "recent.png");
try
{
Timeout = TimeSpan.FromSeconds(10)
})
{
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var etag = response.Headers.ETag?.Tag.Replace("\"", string.Empty) ?? "untagged";
if (!Directory.Exists(Path.Combine(Path.GetTempPath(), "TED")))
using (var client = new HttpClient()
{
Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "TED"));
}
var path = Path.Combine(Path.GetTempPath(), "TED", $"{etag}.png");
if (File.Exists(path))
Timeout = TimeSpan.FromSeconds(10)
})
{
return path;
}
var response = await client.GetAsync(url);
var filesToDelete = Directory.GetFiles(Path.Combine(Path.GetTempPath(), "TED"), "*.png")
.Where(filePath => Path.GetFileNameWithoutExtension(filePath) != etag);
foreach (var fileToDelete in filesToDelete)
{
File.Delete(fileToDelete);
}
using (var stream = await response.Content.ReadAsStreamAsync())
{
using (var fileStream = new FileStream(path, FileMode.CreateNew))
try
{
await stream.CopyToAsync(fileStream);
return path;
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException e)
{
// If we catch here, it's a URL error or server-side issue.
if (File.Exists(recentPath))
{
return recentPath;
}
return string.Empty;
}
var fileName = "ted.png";
if (response.Headers.ETag != null)
{
fileName = $"{response.Headers.ETag?.Tag.Replace("\"", string.Empty)}.png";
}
else if (response.Content.Headers.ContentDisposition != null)
{
fileName = $"{response.Content.Headers.ContentDisposition.FileName}";
}
if (!Directory.Exists(tedDirectory))
{
Directory.CreateDirectory(tedDirectory);
}
var downloadPath = Path.Combine(tedDirectory, fileName);
if (File.Exists(downloadPath))
{
return downloadPath;
}
var filesToDelete = Directory.GetFiles(tedDirectory)
.Where(filePath => Path.GetFileNameWithoutExtension(filePath) != fileName && Path.GetFileNameWithoutExtension(filePath) != "recent");
foreach (var fileToDelete in filesToDelete)
{
File.Delete(fileToDelete);
}
using (var stream = await response.Content.ReadAsStreamAsync())
{
using (var fileStream = new FileStream(downloadPath, FileMode.CreateNew))
{
await stream.CopyToAsync(fileStream);
if(File.Exists(recentPath))
{
File.Delete(recentPath);
}
File.Copy(downloadPath, recentPath);
return downloadPath;
}
}
}
} catch(HttpRequestException e)
{
// If we catch here, we're offline.
if (File.Exists(recentPath))
{
return recentPath;
}
return string.Empty;
}
}