diff --git a/Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs index 34d5282e..05b7959e 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs @@ -9,7 +9,8 @@ namespace Meadow.CLI.Commands.DeviceManagement; public class AppDeployCommand : BaseDeviceCommand { private readonly IPackageManager _packageManager; - private string? _lastFile; + + private readonly string AppFileName = "App.dll"; [CommandOption('c', Description = Strings.BuildConfiguration, IsRequired = false)] public string? Configuration { get; private set; } @@ -60,11 +61,11 @@ private FileInfo GetMeadowAppFile(string path) } // does the directory have an App.dll in it? - file = new FileInfo(System.IO.Path.Combine(path, "App.dll")); + file = new FileInfo(System.IO.Path.Combine(path, AppFileName)); if (!file.Exists) { // it's a directory - we need to determine the latest build (they might have a Debug and a Release config) - var candidates = PackageManager.GetAvailableBuiltConfigurations(path, "App.dll"); + var candidates = PackageManager.GetAvailableBuiltConfigurations(path, AppFileName); if (candidates.Length == 0) { @@ -76,7 +77,7 @@ private FileInfo GetMeadowAppFile(string path) } else { - if (System.IO.Path.GetFileName(path) != "App.dll") + if (System.IO.Path.GetFileName(path) != AppFileName) { throw new CommandException($"The file '{path}' is not a compiled Meadow application", CommandExitCode.FileNotFound); } @@ -90,7 +91,7 @@ private async Task DeployApplication(IMeadowConnection connection, string { connection.FileWriteProgress += OnFileWriteProgress; - var candidates = PackageManager.GetAvailableBuiltConfigurations(path, "App.dll"); + var candidates = PackageManager.GetAvailableBuiltConfigurations(path, AppFileName); if (candidates.Length == 0) { @@ -106,6 +107,8 @@ private async Task DeployApplication(IMeadowConnection connection, string connection.FileWriteProgress -= OnFileWriteProgress; + Logger?.LogInformation($"{Strings.AppDeployedSuccessfully}"); + return true; } @@ -113,13 +116,10 @@ private void OnFileWriteProgress(object? sender, (string fileName, long complete { var p = e.completed / (double)e.total * 100d; - if (e.fileName != _lastFile) + if (!double.IsNaN(p)) { - Console?.Output.Write("\n"); - _lastFile = e.fileName; + // Console instead of Logger due to line breaking for progress bar + Console?.Output.Write($"Writing {e.fileName}: {p:0}% \r"); } - - // Console instead of Logger due to line breaking for progress bar - Console?.Output.Write($"Writing {e.fileName}: {p:0}% \r"); } } \ No newline at end of file diff --git a/Source/v2/Meadow.Cli/Commands/Current/App/AppRunCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/App/AppRunCommand.cs index 7a12c5f7..eafefe56 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/App/AppRunCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/App/AppRunCommand.cs @@ -10,7 +10,6 @@ namespace Meadow.CLI.Commands.DeviceManagement; public class AppRunCommand : BaseDeviceCommand { private readonly IPackageManager _packageManager; - private string? _lastFile; [CommandOption("no-prefix", 'n', Description = "When set, the message source prefix (e.g. 'stdout>') is suppressed during 'listen'", IsRequired = false)] public bool NoPrefix { get; init; } @@ -134,14 +133,10 @@ private void OnFileWriteProgress(object? sender, (string fileName, long complete { var p = e.completed / (double)e.total * 100d; - if (e.fileName != _lastFile) - { - Console?.Output.Write("\n"); - _lastFile = e.fileName; + if (!double.IsNaN(p)) + { // Console instead of Logger due to line breaking for progress bar + Console?.Output.Write($"Writing {e.fileName}: {p:0}% \r"); } - - // Console instead of Logger due to line breaking for progress bar - Console?.Output.Write($"Writing {e.fileName}: {p:0}% \r"); } private void OnDeviceMessageReceived(object? sender, (string message, string? source) e) diff --git a/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs index 7382e528..fdfc941f 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/File/FileDeleteCommand.cs @@ -82,8 +82,8 @@ private async Task DeleteFileRecursive(IMeadowDevice device, string directorynam return; } - Logger?.LogInformation($"Deleting file '{meadowFile}' from device..."); - + Logger?.LogInformation($"Deleting file '{meadowFile}' from device..."); + await device.DeleteFile(meadowFile, cancellationToken); } } \ No newline at end of file diff --git a/Source/v2/Meadow.Cli/Commands/Current/File/FileListCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/File/FileListCommand.cs index 876b9c3d..8061345b 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/File/FileListCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/File/FileListCommand.cs @@ -101,7 +101,7 @@ protected override async ValueTask ExecuteCommand() { foreach (var file in files) { - Logger?.LogInformation(file.Name + (file.IsDirectory ? FolderLabel : string.Empty)); + Logger?.LogInformation(file.Name + '\t' + (file.IsDirectory ? FolderLabel : string.Empty)); } Logger?.LogInformation($"\t{files.Length} file(s)"); diff --git a/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareWriteCommand.cs b/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareWriteCommand.cs index 0e0f080e..5c0d5647 100644 --- a/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareWriteCommand.cs +++ b/Source/v2/Meadow.Cli/Commands/Current/Firmware/FirmwareWriteCommand.cs @@ -213,7 +213,7 @@ protected override async ValueTask ExecuteCommand() } else { - await connection!.Device!.WriteFile(osFileWithoutBootloader, $"/meadow0/update/os/{package.OsWithoutBootloader}"); + await connection!.Device!.WriteFile(osFileWithoutBootloader, $"/{AppTools.MeadowRootFolder}/update/os/{package.OsWithoutBootloader}"); } } @@ -351,7 +351,7 @@ private async Task FindMeadowConnection(IList portsTo } else { - await connection.Device!.WriteFile(runtimePath, $"/meadow0/update/os/{destinationFilename}"); + await connection.Device!.WriteFile(runtimePath, $"/{AppTools.MeadowRootFolder}/update/os/{destinationFilename}"); } return connection; @@ -393,7 +393,7 @@ private async Task WriteEspFiles(IMeadowConnection? connection, DeviceInfo? devi { foreach (var file in fileList) { - await connection!.Device!.WriteFile(file, $"/meadow0/update/os/{Path.GetFileName(file)}"); + await connection!.Device!.WriteFile(file, $"/{AppTools.MeadowRootFolder}/update/os/{Path.GetFileName(file)}"); } } } diff --git a/Source/v2/Meadow.Cli/Meadow.CLI.csproj b/Source/v2/Meadow.Cli/Meadow.CLI.csproj index 7073dfe0..3e00959d 100644 --- a/Source/v2/Meadow.Cli/Meadow.CLI.csproj +++ b/Source/v2/Meadow.Cli/Meadow.CLI.csproj @@ -10,7 +10,7 @@ Wilderness Labs, Inc Wilderness Labs, Inc true - 2.0.41.0 + 2.0.42.0-beta AnyCPU http://developer.wildernesslabs.co/Meadow/Meadow.CLI/ https://github.com/WildernessLabs/Meadow.CLI diff --git a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs index 5de8b06d..4fc1f7ca 100644 --- a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs +++ b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs @@ -6,5 +6,5 @@ namespace Meadow.CLI; public static class Constants { - public const string CLI_VERSION = "2.0.41.0"; + public const string CLI_VERSION = "2.0.42.0"; } \ No newline at end of file diff --git a/Source/v2/Meadow.Cli/Strings.cs b/Source/v2/Meadow.Cli/Strings.cs index d329ad15..f293eb27 100644 --- a/Source/v2/Meadow.Cli/Strings.cs +++ b/Source/v2/Meadow.Cli/Strings.cs @@ -67,6 +67,10 @@ public static class Strings public const string NewMeadowDeviceNotFound = "New Meadow device not found"; public const string NoFirmwarePackagesFound = "No firmware packages found, run 'meadow firmware download' to download the latest firmware"; public const string NoDefaultFirmwarePackageSet = "No default firmware package set, run 'meadow firmware default' to set the default firmware"; + public const string AppDeployFailed = "Application deploy failed"; + public const string AppDeployedSuccessfully = "Application deployed successfully"; + public const string AppTrimFailed = "Application trimming failed"; + public static class Telemetry { @@ -82,7 +86,4 @@ public static class Telemetry public const string AskToParticipate = "Would you like to participate?"; } - - public const string AppDeployFailed = "Application deploy failed"; - public const string AppTrimFailed = "Application trimming failed"; } \ No newline at end of file diff --git a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs index baf0fcb3..9a607bd3 100755 --- a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs +++ b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs @@ -157,12 +157,11 @@ private void Open() } catch (UnauthorizedAccessException uae) { - throw new Exception($"{uae.Message} Another application may have access to '{_port.PortName}'. "); + throw new Exception($"{uae.Message}"); } catch (Exception ex) { - // We don't know what happened, best to bail and let the user know. - throw new Exception($"Unable to open port '{_port.PortName}'. {ex.Message}"); + throw new Exception($"Unable to open port '{_port.PortName}' - {ex.Message}"); } State = ConnectionState.Connected; @@ -855,7 +854,7 @@ public override async Task ResetDevice(CancellationToken? cancellationToken = nu foreach (var candidate in _textList) { - var fi = MeadowFileInfo.Parse(candidate); + var fi = MeadowFileInfo.Parse(candidate, folder); if (fi != null) { list.Add(fi); diff --git a/Source/v2/Meadow.Hcom/MeadowFileInfo.cs b/Source/v2/Meadow.Hcom/MeadowFileInfo.cs index a4a06981..7f367d70 100644 --- a/Source/v2/Meadow.Hcom/MeadowFileInfo.cs +++ b/Source/v2/Meadow.Hcom/MeadowFileInfo.cs @@ -1,6 +1,8 @@ public class MeadowFileInfo { public string Name { get; private set; } = default!; + public string Path { get; private set; } = string.Empty; + public string FullName => System.IO.Path.Combine(Path, Name).Replace("\\", "/"); public long? Size { get; private set; } public string? Crc { get; private set; } public bool IsDirectory { get; private set; } @@ -10,22 +12,25 @@ public override string ToString() return $"{(IsDirectory ? "/" : "")}{Name}"; } - public static MeadowFileInfo? Parse(string info) + public static MeadowFileInfo? Parse(string info, string folder) { + folder = folder.Replace("\\", "/"); + MeadowFileInfo? mfi = null; // parse the input to a file info if (info.StartsWith("/")) { - mfi = new MeadowFileInfo(); - - mfi.Name = info.Substring(1); - mfi.IsDirectory = true; + mfi = new MeadowFileInfo + { + Name = info.Substring(1), + Path = folder, + IsDirectory = true + }; } else { // v2 file lists have changed - if (info.StartsWith("Directory:")) { // this is the first line and contains the directory name being parsed @@ -51,6 +56,7 @@ public override string ToString() var end = info.IndexOf(' ', indexOfParen); mfi.Size = int.Parse(info.Substring(indexOfParen + 1, end - indexOfParen)); } + mfi.Path = folder; } return mfi; diff --git a/Source/v2/Meadow.Tooling.Core/App/AppManager.cs b/Source/v2/Meadow.Tooling.Core/App/AppManager.cs index bb45beb0..ed5febb1 100644 --- a/Source/v2/Meadow.Tooling.Core/App/AppManager.cs +++ b/Source/v2/Meadow.Tooling.Core/App/AppManager.cs @@ -14,6 +14,15 @@ namespace Meadow.CLI; public static class AppManager { + static readonly string MeadowRootFolder = "meadow0"; + + static readonly string[] PersistantFolders = new string[] + { + "Data", + "Documents", + "update-store", + }; + private static bool MatchingDllExists(string file) { return File.Exists(Path.ChangeExtension(file, ".dll")); @@ -53,6 +62,8 @@ public static async Task DeployApplication( //check if there's a post link folder if (Directory.Exists(Path.Combine(localBinaryDirectory, MeadowLinker.PostLinkDirectoryName))) { + logger?.LogInformation($"Found trimmed binaries in post link folder..."); + processedAppPath = Path.Combine(localBinaryDirectory, MeadowLinker.PostLinkDirectoryName); //add all dlls from the postlink_bin folder to the dependencies @@ -68,6 +79,8 @@ public static async Task DeployApplication( } else { + logger?.LogInformation($"Did not find trimmed binaries folder..."); + dependencies = packageManager.GetDependencies(new FileInfo(Path.Combine(processedAppPath, "App.dll")), osVersion); } dependencies.Add(Path.Combine(localBinaryDirectory, "App.dll")); @@ -87,7 +100,7 @@ public static async Task DeployApplication( foreach (var file in dependencies) { - // TODO: add any other filtering capability here + // Add any other filtering capability here if (!includePdbs && IsPdb(file)) { continue; } if (!includeXmlDocs && IsXmlDoc(file)) { continue; } @@ -109,38 +122,39 @@ public static async Task DeployApplication( } // get a list of files on-device, with CRCs - var deviceFiles = await connection.GetFileList("/meadow0/", true, cancellationToken) ?? Array.Empty(); + var deviceFiles = await GetFilesInFolder(connection, $"/{MeadowRootFolder}/", cancellationToken); - // get a list of files of the device files that are not in the list we intend to deploy + // get a list of MeadowFileInfo of the device files that are not in the list we intend to deploy var removeFiles = deviceFiles - .Select(f => Path.GetFileName(f.Name)) - .Except(localFiles.Keys - .Select(f => Path.GetFileName(f))).ToList(); + .Where(f => !localFiles.Keys.Select(f => Path.GetFileName(f)).Contains(Path.GetFileName(f.Name))) + .ToList(); // delete those files foreach (var file in removeFiles) { logger?.LogInformation($"Deleting file '{file}'..."); - await connection.DeleteFile(file, cancellationToken); + var folder = string.IsNullOrEmpty(file.Path) ? $"/{MeadowRootFolder}/" : $"/{MeadowRootFolder}/{file.Path}/"; + + await connection.DeleteFile($"{folder}{file.Name}", cancellationToken); } // now send all files with differing CRCs foreach (var localFile in localFiles) { string? meadowFilename = string.Empty; - if (!localFile.Key.Contains(MeadowLinker.PreLinkDirectoryName) - && !localFile.Key.Contains(MeadowLinker.PostLinkDirectoryName)) + if (localFile.Key.Contains(PackageManager.PreLinkDirectoryName) || + localFile.Key.Contains(PackageManager.PackageOutputDirectoryName)) { - meadowFilename = GetRelativePath(localBinaryDirectory, localFile.Key); - if (!meadowFilename.StartsWith("/meadow0/")) - { - meadowFilename = "/meadow0/" + meadowFilename; - } + continue; } - else - { + else if (localFile.Key.Contains(PackageManager.PostLinkDirectoryName)) + { //we want to transfer the file but we can let the API find the file name meadowFilename = null; } + else + { //may have a sub folder so we manually process the file name + path + meadowFilename = GetTargetMeadowFileName(localBinaryDirectory, localFile.Key); + } var existing = deviceFiles.FirstOrDefault(f => Path.GetFileName(f.Name) == Path.GetFileName(localFile.Key)); if (existing != null && existing.Crc != null) @@ -149,6 +163,7 @@ public static async Task DeployApplication( if (crc == localFile.Value) { // exists and has a matching CRC, skip it + logger?.LogInformation($"Skipping '{localFile.Key}'"); continue; } } @@ -168,18 +183,65 @@ public static async Task DeployApplication( logger?.LogInformation(string.Empty); } - // Path.GetRelativePath is only available in .NET 8 but we also need to support netstandard2.0, hence using this - static string GetRelativePath(string relativeTo, string path) + static async Task> GetFilesInFolder(IMeadowConnection connection, string folder, CancellationToken? cancellationToken) { - // Determine the difference - var relativePath = path.Substring(relativeTo.Length); - //remove leading slash - if (relativePath.StartsWith(Path.DirectorySeparatorChar.ToString()) || - relativePath.StartsWith("/") || - relativePath.StartsWith("\\")) + var deviceFiles = new List(); + + var rootFiles = await connection.GetFileList(folder, true, cancellationToken) ?? Array.Empty(); + + foreach (var file in rootFiles) { - relativePath = relativePath.Substring(1); + if (file.IsDirectory) + { + if (PersistantFolders.Contains(file.Name)) + { + continue; + } + + //call recursively + var subfolderFiles = await GetFilesInFolder(connection, file.Name, cancellationToken); + + if (subfolderFiles != null) + { + deviceFiles.AddRange(subfolderFiles); + } + } + else + { + deviceFiles.Add(file); + } + } + + return deviceFiles; + } + + static string GetTargetMeadowFileName(string localBinaryFolder, string fullyQualifiedFilePath) + { + string relativePath = string.Empty; + string fileName = Path.GetFileName(fullyQualifiedFilePath); + string? filePath = Path.GetDirectoryName(fullyQualifiedFilePath); + + if (filePath is not null && filePath.StartsWith(localBinaryFolder)) + { + relativePath = filePath.Substring(localBinaryFolder.Length); + + relativePath = relativePath.Replace("\\", "/"); + + //remove leading slash + if (relativePath.StartsWith(Path.DirectorySeparatorChar.ToString()) || + relativePath.StartsWith("/") || + relativePath.StartsWith("\\")) + { + relativePath = relativePath.Substring(1); + } + + if (!string.IsNullOrWhiteSpace(relativePath)) + { + //add trailing slash + relativePath += "/"; + } } - return relativePath; + + return $"/{MeadowRootFolder}/" + relativePath + fileName; } } \ No newline at end of file