// 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;
}
}
}