Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download manager simplification #427

Merged
merged 4 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ protected override async ValueTask ExecuteCommand()

if (latest == null)
{
Logger?.LogError($"Unable to get latest version information.");
Logger?.LogError($"Unable to get latest version information");
return;
}

Expand Down Expand Up @@ -65,11 +65,11 @@ protected override async ValueTask ExecuteCommand()

if (!result)
{
Logger?.LogError($"Unable to download package '{Version}'.");
Logger?.LogError($"Unable to download package '{Version}'");
}
else
{
Logger?.LogError($"{Environment.NewLine} Firmware package '{Version}' downloaded.");
Logger?.LogInformation($"Firmware package '{Version}' downloaded");

if (explicitVersion == false)
{
Expand Down
203 changes: 68 additions & 135 deletions Source/v2/Meadow.Hcom/Firmware/DownloadManager.cs
Original file line number Diff line number Diff line change
@@ -1,72 +1,55 @@
using Microsoft.Extensions.Logging;
using System.IO.Compression;
using System.Reflection;
using System.Text.Json;

namespace Meadow.Hcom;

public class DownloadManager
{
public static readonly string FirmwareDownloadsFilePathRoot = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"WildernessLabs",
"Firmware");

public static string FirmwareLatestVersion
{
get
{
string latest_txt = Path.Combine(FirmwareDownloadsFilePathRoot, "latest.txt");
if (File.Exists(latest_txt))
return File.ReadAllText(latest_txt);
else
throw new FileNotFoundException("OS download was not found.");
}
}

public static string FirmwareDownloadsFilePath => FirmwarePathForVersion(FirmwareLatestVersion);

public static string FirmwarePathForVersion(string firmwareVersion)
{
return Path.Combine(FirmwareDownloadsFilePathRoot, firmwareVersion);
}

public static readonly string WildernessLabsTemp = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"WildernessLabs",
"temp");
static readonly string RootFolder = "WildernessLabs";
static readonly string FirmwareFolder = "Firmware";
static readonly string LatestFilename = "latest.txt";

public static readonly string OsFilename = "Meadow.OS.bin";
public static readonly string RuntimeFilename = "Meadow.OS.Runtime.bin";
public static readonly string NetworkBootloaderFilename = "bootloader.bin";
public static readonly string NetworkMeadowCommsFilename = "MeadowComms.bin";
public static readonly string NetworkPartitionTableFilename = "partition-table.bin";
internal static readonly string VersionCheckUrlRoot =
"https://s3-us-west-2.amazonaws.com/downloads.wildernesslabs.co/Meadow_Beta/";
internal static readonly string VersionCheckUrlRoot = "https://s3-us-west-2.amazonaws.com/downloads.wildernesslabs.co/Meadow_Beta/";

public static readonly string UpdateCommand = "dotnet tool update WildernessLabs.Meadow.CLI --global";

private static readonly HttpClient Client = new()
{
Timeout = TimeSpan.FromMinutes(5)
};

private readonly ILogger _logger;
public static readonly string FirmwareDownloadsFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
RootFolder, FirmwareFolder);

public DownloadManager(ILoggerFactory loggerFactory)
public static string FirmwareLatestVersion
{
_logger = loggerFactory.CreateLogger<DownloadManager>();
get
{
string latestPath = Path.Combine(FirmwareDownloadsFolder, LatestFilename);
if (File.Exists(latestPath))
{
return File.ReadAllText(latestPath);
}
throw new FileNotFoundException("Latest firmware not found");
}
}

private static readonly HttpClient Client = new() { Timeout = TimeSpan.FromMinutes(1) };

private readonly ILogger _logger;

public DownloadManager(ILogger logger)
{
_logger = logger;
}

internal async Task<string?> DownloadMeadowOSVersionFile(string? version)
{
string versionCheckUrl;
if (version is null || string.IsNullOrWhiteSpace(version))
string versionCheckUrl, versionCheckFile;

if (string.IsNullOrWhiteSpace(version))
{
_logger.LogInformation("Downloading latest version file" + Environment.NewLine);
versionCheckUrl = VersionCheckUrlRoot + "latest.json";
Expand All @@ -77,8 +60,6 @@ public DownloadManager(ILogger logger)
versionCheckUrl = VersionCheckUrlRoot + version + ".json";
}

string versionCheckFile;

try
{
versionCheckFile = await DownloadFile(new Uri(versionCheckUrl));
Expand All @@ -91,65 +72,57 @@ public DownloadManager(ILogger logger)
return versionCheckFile;
}

//ToDo rename this method - DownloadOSAsync?
public async Task DownloadOsBinaries(string? version = null, bool force = false)
{
var versionCheckFilePath = await DownloadMeadowOSVersionFile(version);
var versionFilePath = await DownloadMeadowOSVersionFile(version);

if (versionCheckFilePath == null)
if (versionFilePath == null)
{
_logger.LogError($"Meadow OS {version} cannot be downloaded or is not available");
return;
}

var payload = File.ReadAllText(versionCheckFilePath);
var payload = File.ReadAllText(versionFilePath);
var release = JsonSerializer.Deserialize<ReleaseMetadata>(payload);

if (release == null)
{
_logger.LogError($"Unable to read release details for Meadow OS {version}. Payload: {payload}");
_logger.LogError($"Unable to read release details for Meadow OS");
return;
}

if (!Directory.Exists(FirmwareDownloadsFilePathRoot))
if (Directory.Exists(FirmwareDownloadsFolder) == false)
{
Directory.CreateDirectory(FirmwareDownloadsFilePathRoot);
Directory.CreateDirectory(FirmwareDownloadsFolder);
//we'll write latest.txt regardless of version if it doesn't exist
File.WriteAllText(Path.Combine(FirmwareDownloadsFilePathRoot, "latest.txt"), release.Version);
File.WriteAllText(Path.Combine(FirmwareDownloadsFolder, LatestFilename), release.Version);
}
else if (version == null)
{ //otherwise only update if we're pulling the latest release OS
File.WriteAllText(Path.Combine(FirmwareDownloadsFilePathRoot, "latest.txt"), release.Version);
{ //otherwise update if we're pulling the latest release OS
File.WriteAllText(Path.Combine(FirmwareDownloadsFolder, LatestFilename), release.Version);
}

if (release.Version.ToVersion() < "0.6.0.0".ToVersion())
{
_logger.LogInformation(
$"Downloading OS version {release.Version} is no longer supported. The minimum OS version is 0.6.0.0." + Environment.NewLine);
return;
}
var firmwareVersionPath = Path.Combine(FirmwareDownloadsFolder, release.Version);

var local_path = Path.Combine(FirmwareDownloadsFilePathRoot, release.Version);

if (Directory.Exists(local_path))
if (Directory.Exists(firmwareVersionPath))
{
if (force)
{
CleanPath(local_path);
DeleteDirectory(firmwareVersionPath);
}
else
{
_logger.LogInformation($"Meadow OS version {release.Version} is already downloaded." + Environment.NewLine);
_logger.LogInformation($"Meadow OS version {release.Version} is already downloaded");
return;
}
}

Directory.CreateDirectory(local_path);
Directory.CreateDirectory(firmwareVersionPath);

try
{
_logger.LogInformation($"Downloading Meadow OS" + Environment.NewLine);
await DownloadAndExtractFile(new Uri(release.DownloadURL), local_path);
_logger.LogInformation($"Downloading Meadow OS");
await DownloadAndUnpack(new Uri(release.DownloadURL), firmwareVersionPath);
}
catch
{
Expand All @@ -159,44 +132,16 @@ public async Task DownloadOsBinaries(string? version = null, bool force = false)

try
{
_logger.LogInformation("Downloading coprocessor firmware" + Environment.NewLine);
await DownloadAndExtractFile(new Uri(release.NetworkDownloadURL), local_path);
_logger.LogInformation("Downloading coprocessor firmware");
await DownloadAndUnpack(new Uri(release.NetworkDownloadURL), firmwareVersionPath);
}
catch
{
_logger.LogError($"Unable to download coprocessor firmware {version}");
return;
}

_logger.LogInformation($"Downloaded and extracted OS version {release.Version} to: {local_path}" + Environment.NewLine);
}

public async Task<(bool updateExists, string latestVersion, string currentVersion)> CheckForUpdates()
{
try
{
var packageId = "WildernessLabs.Meadow.CLI";
var appVersion = Assembly.GetEntryAssembly()!
.GetCustomAttribute<AssemblyFileVersionAttribute>()
.Version;

var json = await Client.GetStringAsync(
$"https://api.nuget.org/v3-flatcontainer/{packageId.ToLower()}/index.json");

var result = JsonSerializer.Deserialize<PackageVersions>(json);

if (!string.IsNullOrEmpty(result?.Versions.LastOrDefault()))
{
var latest = result!.Versions!.Last();
return (latest.ToVersion() > appVersion.ToVersion(), latest, appVersion);
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Error checking for updates to Meadow.CLI");
}

return (false, string.Empty, string.Empty);
_logger.LogInformation($"Downloaded and extracted OS version {release.Version} to: {firmwareVersionPath}");
}

private async Task<string> DownloadFile(Uri uri, CancellationToken cancellationToken = default)
Expand All @@ -207,61 +152,49 @@ private async Task<string> DownloadFile(Uri uri, CancellationToken cancellationT
response.EnsureSuccessStatusCode();

var downloadFileName = Path.GetTempFileName();
_logger.LogDebug("Copying downloaded file to temp file {filename}", downloadFileName);
using (var stream = await response.Content.ReadAsStreamAsync())
using (var downloadFileStream = new DownloadFileStream(stream, _logger))
using (var firmwareFile = File.OpenWrite(downloadFileName))
{
await downloadFileStream.CopyToAsync(firmwareFile);
}
_logger.LogDebug($"Copying downloaded file to temp file {downloadFileName}");

using var stream = await response.Content.ReadAsStreamAsync();
using var downloadFileStream = new DownloadFileStream(stream, _logger);
using var firmwareFile = File.OpenWrite(downloadFileName);

await downloadFileStream.CopyToAsync(firmwareFile);

return downloadFileName;
}

private async Task DownloadAndExtractFile(Uri uri, string target_path, CancellationToken cancellationToken = default)
private async Task DownloadAndUnpack(Uri uri, string targetPath, CancellationToken cancellationToken = default)
{
var downloadFileName = await DownloadFile(uri, cancellationToken);
var file = await DownloadFile(uri, cancellationToken);

_logger.LogDebug($"Extracting {file} to {targetPath}");

ZipFile.ExtractToDirectory(file, targetPath);

_logger.LogDebug("Extracting firmware to {path}", target_path);
ZipFile.ExtractToDirectory(
downloadFileName,
target_path);
try
{
File.Delete(downloadFileName);
File.Delete(file);
}
catch (Exception ex)
{
_logger.LogWarning("Unable to delete temporary file");
_logger.LogDebug(ex, "Unable to delete temporary file");
}
}

private void CleanPath(string path)
/// <summary>
/// Delete all files and sub directorines in a directory
/// </summary>
/// <param name="path">The directory path</param>
/// <param name="logger">Optional ILogger for exception reporting</param>
public static void DeleteDirectory(string path, ILogger? logger = null)
{
var di = new DirectoryInfo(path);
foreach (FileInfo file in di.GetFiles())
try
{
try
{
file.Delete();
}
catch (Exception ex)
{
_logger.LogWarning("Failed to delete file {file} in firmware path", file.FullName);
_logger.LogDebug(ex, "Failed to delete file");
}
Directory.Delete(path, true);
}
foreach (DirectoryInfo dir in di.GetDirectories())
catch (IOException e)
{
try
{
dir.Delete(true);
}
catch (Exception ex)
{
_logger.LogWarning("Failed to delete directory {directory} in firmware path", dir.FullName);
_logger.LogDebug(ex, "Failed to delete directory");
}
logger?.LogWarning($"Failed to delete {path} - {e.Message}");
}
}
}
4 changes: 2 additions & 2 deletions Source/v2/Meadow.Hcom/Firmware/FirmwareManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static async Task<string> GetCloudLatestFirmwareVersion()

public static string GetLocalLatestFirmwareVersion()
{
var di = new DirectoryInfo(DownloadManager.FirmwareDownloadsFilePathRoot);
var di = new DirectoryInfo(DownloadManager.FirmwareDownloadsFolder);
var latest = string.Empty;
var latestFile = di.GetFiles("latest.txt").FirstOrDefault();
if (latestFile != null)
Expand All @@ -60,7 +60,7 @@ public static FirmwareInfo[] GetAllLocalFirmwareBuilds()
{
var list = new List<FirmwareInfo>();

var di = new DirectoryInfo(DownloadManager.FirmwareDownloadsFilePathRoot);
var di = new DirectoryInfo(DownloadManager.FirmwareDownloadsFolder);

var latest = GetLocalLatestFirmwareVersion();

Expand Down
2 changes: 1 addition & 1 deletion Source/v2/Meadow.Hcom/Firmware/PackageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public string CreatePackage(string applicationPath, string osVersion)
throw new ArgumentException($"Invalid applicationPath: {applicationPath}");
}

var osFilePath = Path.Combine(DownloadManager.FirmwareDownloadsFilePathRoot, osVersion);
var osFilePath = Path.Combine(DownloadManager.FirmwareDownloadsFolder, osVersion);
if (!Directory.Exists(osFilePath))
{
throw new ArgumentException($"osVersion {osVersion} not found. Please download.");
Expand Down
Loading