chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
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
//            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; }
        
        /// <summary>
        /// Never prompt for user input.
        /// </summary>
        [CommandLineArgument("non-interactive", Description = "Never prompt for user input.")]
        public bool NonInteractive { get; set; } = false;
 
        /// <summary>
        /// Represents the --out command line argument which specifies the path to the output file.
        /// </summary>
        [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;
 
        /// <summary>
        /// This is used when specifying multiple packages with different version numbers. In that case <see cref="Packages"/> can be left null.
        /// </summary>
        public PackageSpecifier[] PackageReferences { get; set; }
 
        /// <summary>
        /// PackageDef of downloaded packages. Value is null until packages have actually been downloaded (after Execute)
        /// </summary>
        public IEnumerable<PackageDef> 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<IPackageRepository> repositories = PackageManagerSettings.Current.GetEnabledRepositories(Repository);
            
            if (NonInteractive)
                UserInput.SetInterface(new NonInteractiveUserInputInterface());
            
            if (!NonInteractive)
                Packages = AutoCorrectPackageNames.Correct(Packages, repositories);
 
            List<PackageDef> 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<PackageDef>() {package},
                                new List<string>() {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;
        }
    }
}