-
-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathFirmwareArchiveManager.cs
250 lines (232 loc) · 11 KB
/
FirmwareArchiveManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
namespace nanoFramework.Tools.FirmwareFlasher
{
/// <summary>
/// The manager of a firmware archive directory. It supports downloading firmware packages and the runtimes
/// for the Virtual Device to a local directory from the online archive. That directory can then be used as
/// a source for list and deployment operations instead of the online archive.
/// </summary>
public sealed class FirmwareArchiveManager
{
private readonly string _archivePath;
private const string INFOFILE_EXTENSION = ".json";
#region Construction
/// <summary>
/// Create a new manager for the firmware archive
/// </summary>
/// <param name="fwArchivePath">Path to the firmware archive directory</param>
public FirmwareArchiveManager(string fwArchivePath)
{
_archivePath = Path.GetFullPath(fwArchivePath);
}
#endregion
#region Methods
/// <summary>
/// List the targets for which firmware is present in the firmware archive
/// </summary>
/// <param name="preview">Option for preview version.</param>
/// <param name="platform">Platform code to use on search.</param>
/// <param name="verbosity">VerbosityLevel to use when outputting progress and error messages.</param>
/// <returns>List of <see cref="CloudSmithPackageDetail"/> with details on target firmware packages.</returns>
public List<CloudSmithPackageDetail> GetTargetList(
bool preview,
SupportedPlatform? platform,
VerbosityLevel verbosity)
{
List<CloudSmithPackageDetail> targetPackages = [];
if (verbosity > VerbosityLevel.Normal)
{
OutputWriter.ForegroundColor = ConsoleColor.White;
OutputWriter.WriteLine($"Listing {platform} targets from firmware archive '{_archivePath}'...");
}
if (Directory.Exists(_archivePath))
{
foreach (string filePath in Directory.EnumerateFiles(_archivePath, $"*{INFOFILE_EXTENSION}"))
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
PersistedPackageInformation packageInformation = JsonSerializer.Deserialize<PersistedPackageInformation>(File.ReadAllText(filePath), options);
if (packageInformation.IsPreview == preview &&
(platform is null || platform.Value.ToString().Equals(packageInformation.Platform, StringComparison.OrdinalIgnoreCase)))
{
targetPackages.Add(packageInformation);
}
}
}
return targetPackages;
}
/// <summary>
/// Get the latest version present in the firmware archive for the specified target.
/// </summary>
/// <param name="preview">Option for preview version.</param>
/// <param name="target">Target to find the latest version of.</param>
/// <returns>The <see cref="CloudSmithPackageDetail"/> with details on latest firmware package for the target, or <see langword="null"/> if none is present.</returns>
public CloudSmithPackageDetail GetLatestVersion(
bool preview,
string target)
{
CloudSmithPackageDetail result = null;
Version latest = null;
if (Directory.Exists(_archivePath) && !string.IsNullOrEmpty(target))
{
foreach (string filePath in Directory.EnumerateFiles(_archivePath, $"{target}-*{INFOFILE_EXTENSION}"))
{
PersistedPackageInformation packageInformation = JsonSerializer.Deserialize<PersistedPackageInformation>(File.ReadAllText(filePath));
if (packageInformation is not null && packageInformation.IsPreview == preview)
{
if (Version.TryParse(packageInformation.Version, out Version version))
{
if (latest is null || latest < version)
{
latest = version;
result = packageInformation;
}
}
}
}
}
return result;
}
/// <summary>
/// Download a firmware package from the repository and add it to the archive directory
/// </summary>
/// <param name="preview">Option for preview version.</param>
/// <param name="platform">Platform code to use on search. This should not be <c>null</c> otherwise the firmware may not be found by nanoff.</param>
/// <param name="targetName">Name of the target the firmware is created for. Must be assigned.</param>
/// <param name="version">Version of the firmware to download; can be <c>null</c>.</param>
/// <param name="verbosity">VerbosityLevel to use when outputting progress and error messages.</param>
/// <returns>The result of the task</returns>
public async Task<ExitCodes> DownloadFirmwareFromRepository(
bool preview,
SupportedPlatform? platform,
string targetName,
string version,
VerbosityLevel verbosity)
{
// Find the requested firmware in the repository
var remoteTargets = new Dictionary<string, (CloudSmithPackageDetail package, Version version)>();
foreach (bool isCommunityTarget in new bool[] { false, true })
{
if (preview && isCommunityTarget)
{
continue;
}
foreach (CloudSmithPackageDetail targetInfo in FirmwarePackage.GetTargetList(
isCommunityTarget,
preview,
platform,
verbosity))
{
if ((targetName is null || targetInfo.Name == targetName)
&& (version is null || version == targetInfo.Version))
{
var thisVersion = new Version(targetInfo.Version);
if (!remoteTargets.TryGetValue(targetInfo.Name, out (CloudSmithPackageDetail package, Version version) latestVersion)
|| latestVersion.version < thisVersion)
{
remoteTargets[targetInfo.Name] = (targetInfo, thisVersion);
if (targetName is not null && version is not null)
{
break;
}
}
}
}
if (targetName is not null && version is not null && remoteTargets.Count > 0)
{
break;
}
}
if (remoteTargets.Count == 0)
{
return ExitCodes.E9005;
}
ExitCodes result = ExitCodes.OK;
foreach ((CloudSmithPackageDetail remoteTarget, Version _) in remoteTargets.Values)
{
// Download the firmware
var package = new ArchiveFirmwarePackage(remoteTarget.Name, remoteTarget.Version, preview)
{
Verbosity = verbosity
};
(ExitCodes exitCode, string fwFilePath) = await package.DownloadPackageAsync(_archivePath, null, false, false);
if (exitCode != ExitCodes.OK)
{
result = exitCode;
if (verbosity >= VerbosityLevel.Normal)
{
OutputWriter.ForegroundColor = ConsoleColor.Red;
OutputWriter.WriteLine($"Could not download target {remoteTarget.Name} {remoteTarget.Version}");
OutputWriter.ForegroundColor = ConsoleColor.White;
}
continue;
}
else if (verbosity > VerbosityLevel.Normal)
{
OutputWriter.WriteLine($"Added target {remoteTarget.Name} {remoteTarget.Version} to the archive");
}
// Write the file with package information
var packageInformation = new PersistedPackageInformation
{
IsPreview = preview,
Name = remoteTarget.Name,
Platform = remoteTarget.Platform ?? platform?.ToString(),
Version = remoteTarget.Version,
};
File.WriteAllText(
$"{(fwFilePath.EndsWith(".zip") ? fwFilePath : Path.GetDirectoryName(fwFilePath))}{INFOFILE_EXTENSION}",
JsonSerializer.Serialize(packageInformation)
);
}
return result;
}
#endregion
#region Auxiliary classes that should only be used by the archive manager
#region Persisted firmware information
/// <summary>
/// Class with details of a CloudSmith package. The base class is the one
/// used for listing targets. The extra fields are required to select the
/// correct package for the firmware that should be installed.
/// </summary>
private sealed class PersistedPackageInformation : CloudSmithPackageDetail
{
/// <summary>
/// Indicates whether this is a preview
/// </summary>
public bool IsPreview { get; set; }
}
#endregion
#region Firmware package
/// <summary>
/// A lot of business logic for downloading the firmware is coded in the
/// <see cref="FirmwarePackage"/> class. Unfortunately the constructor required
/// for the archive manager is accessible only via a derived class.
/// </summary>
private sealed class ArchiveFirmwarePackage : FirmwarePackage
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="targetName">Target name as designated in the repositories.</param>
/// <param name="fwVersion">The firmware version.</param>
/// <param name="preview">Whether to use preview versions.</param>
internal ArchiveFirmwarePackage(
string targetName,
string fwVersion,
bool preview)
: base(targetName, fwVersion, preview)
{
}
}
#endregion
#endregion
}
}