// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using OpenTap.Cli; #pragma warning disable 1591 // TODO: Add XML Comments in this file, then remove this namespace OpenTap.Package { [Display("download", Group: "package", Description: "Download one or more packages.")] public class PackageDownloadAction : LockingPackageAction { [CommandLineArgument("force", Description = "Download packages even if it results in some being broken.", ShortName = "f")] public bool ForceInstall { get; set; } [CommandLineArgument("dependencies", Description = "Download dependencies without asking.", ShortName = "y")] public bool InstallDependencies { get; set; } [CommandLineArgument("repository", Description = CommandLineArgumentRepositoryDescription, ShortName = "r")] public string[] Repository { get; set; } [CommandLineArgument("no-cache", Description = CommandLineArgumentNoCacheDescription)] public bool NoCache { get; set; } [CommandLineArgument("version", Description = CommandLineArgumentVersionDescription)] public string Version { get; set; } [CommandLineArgument("os", Description = CommandLineArgumentOsDescription)] public string OS { get; set; } [CommandLineArgument("architecture", Description = CommandLineArgumentArchitectureDescription)] public CpuArchitecture Architecture { get; set; } [UnnamedCommandLineArgument("package(s)", Required = true, Description = "One or more packages to download.")] public string[] Packages { get; set; } /// /// Never prompt for user input. /// [CommandLineArgument("non-interactive", Description = "Never prompt for user input.")] public bool NonInteractive { get; set; } = false; /// /// Represents the --out command line argument which specifies the path to the output file. /// [CommandLineArgument("out", Description = "Path to the output files. Can a file or a folder.", ShortName = "o")] public string OutputPath { get; set; } [CommandLineArgument("dry-run", Description = "Initiate the command and check for errors, but don't download any packages.")] public bool DryRun { get; set; } = false; /// /// This is used when specifying multiple packages with different version numbers. In that case can be left null. /// public PackageSpecifier[] PackageReferences { get; set; } /// /// PackageDef of downloaded packages. Value is null until packages have actually been downloaded (after Execute) /// public IEnumerable DownloadedPackages { get; private set; } = null; static PackageDownloadAction() { log = OpenTap.Log.CreateSource("Download"); } public PackageDownloadAction() { Architecture = ArchitectureHelper.GuessBaseArchitecture; OS = GuessHostOS(); } protected override int LockedExecute(CancellationToken cancellationToken) { string destinationDir = Target ?? Directory.GetCurrentDirectory(); Installation destinationInstallation = new Installation(destinationDir); if (NoCache) PackageManagerSettings.Current.UseLocalPackageCache = false; Repository = ExtractRepositoryTokens(Repository, true); List repositories = PackageManagerSettings.Current.GetEnabledRepositories(Repository); if (NonInteractive) UserInput.SetInterface(new NonInteractiveUserInputInterface()); if (!NonInteractive) Packages = AutoCorrectPackageNames.Correct(Packages, repositories); List PackagesToDownload = PackageActionHelpers.GatherPackagesAndDependencyDefs( destinationInstallation, PackageReferences, Packages, Version, Architecture, OS, repositories, ForceInstall, false); if (PackagesToDownload?.Any() != true) return (int)ExitCodes.ArgumentError; var progressPercentage = 0.0f; if (!DryRun) { if (OutputPath != null) { if (OutputPath.EndsWith("/") || OutputPath.EndsWith(Path.DirectorySeparatorChar.ToString())) Directory.CreateDirectory(OutputPath); if (Directory.Exists(OutputPath)) { destinationDir = OutputPath; } else { // If a filename is specified, name the first Package argument destinationDir = new FileInfo(OutputPath).DirectoryName; Directory.CreateDirectory(destinationDir); // In this case, 'OutputPath' is a specific filename. If we are downloading multiple packages, // this should be the filename of the first package specified. After this file is downloaded, // the rest of the packages to be downloaded, if any, will be processed normally. var firstPackage = Packages?.FirstOrDefault() ?? PackageReferences?.FirstOrDefault()?.Name; if (firstPackage != null) { var package = PackagesToDownload.First(p => p.Name == firstPackage || p.PackageSource is FilePackageDefSource s && s.PackageFilePath == Path.GetFullPath(firstPackage)); // The total progress of downloading 1 package var packageProgressAmount = 1.0f / PackagesToDownload.Count; PackageActionHelpers.DownloadPackages(destinationDir, new List() {package}, new List() {OutputPath}, (percent, msg) => RaiseProgressUpdate((int) (packageProgressAmount * percent), msg)); progressPercentage = packageProgressAmount * 100; RaiseProgressUpdate((int) progressPercentage, $"Downloaded {package.Name}"); PackagesToDownload.Remove(package); } } } // The total remaining progress - 100.0 if not using the --out parameter - ((nPackages - 1) / nPackages) otherwise var remainingPercentage = 100.0f - progressPercentage; try { // Download the remaining packages PackageActionHelpers.DownloadPackages(destinationDir, PackagesToDownload, ignoreCache: NoCache, progressUpdate: (partialPercent, message) => { var partialProgressPercentage = partialPercent * (remainingPercentage / 100); RaiseProgressUpdate((int)(progressPercentage + partialProgressPercentage), message); }); } catch(OperationCanceledException) { log.Debug("Download canceled."); return (int)ExitCodes.UserCancelled; } } else log.Info("Dry run completed. Specified packages are available."); DownloadedPackages = PackagesToDownload; return (int)ExitCodes.Success; } } }