#pragma warning disable SA1512,SA1124 // Single-line comments should not be followed by blank line using System; using System.IO; using JetBrains.Annotations; using NugetForUnity.Configuration; using NugetForUnity.Models; using NugetForUnity.PackageSource; #region No ReShaper // ReSharper disable All // needed because 'JetBrains.Annotations.NotNull' and 'System.Diagnostics.CodeAnalysis.NotNull' collide if this file is compiled with a never version of Unity / C# using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; // ReSharper restore All #endregion namespace NugetForUnity { /// /// Manages the NuGet package cache, which is where .nupkg files are stored after they are downloaded from the server, but before they are installed. /// internal static class PackageCacheManager { private const string CacheEnvironmentVariableName = "NuGetCachePath"; /// /// The path where to put created (packed) and downloaded (not installed yet) .nupkg files. /// [SuppressMessage( "StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Conflicts with other warnings.")] internal static readonly string CacheOutputDirectory = DetermineCacheOutputDirectory(); /// /// Initializes static members of the class. /// Static constructor called only once. /// [SuppressMessage("SonarQube", "S3877:Exceptions should not be thrown from unexpected methods", Justification = "Need to fail here.")] static PackageCacheManager() { try { // create the cache directory, if it doesn't exist Directory.CreateDirectory(CacheOutputDirectory); } catch (Exception exception) { throw new InvalidOperationException( $"Failed to create the cache output directory '{CacheOutputDirectory}'. The cache directory can be changed by specifying the '{CacheEnvironmentVariableName}' environment variable.", exception); } } /// /// Gets a NugetPackage from the NuGet already installed list, the cache or the currently active package source. /// /// The containing the ID and Version of the package to get. /// The retrieved package, if there is one. Null if no matching package was found. [CanBeNull] internal static INugetPackage GetPackageFromCacheOrSource([NotNull] INugetPackageIdentifier packageId) { // First look to see if the package is already installed var package = GetInstalledPackage(packageId); if (package != null) { return package; } // That package isn't installed yet, so look in the cache next package = GetCachedPackage(packageId); if (package != null) { return package; } // It's not in the cache, so we need to look in the active sources return GetOnlinePackage(packageId); } /// /// Tries to find an already cached package that matches the given package ID. /// /// The of the to find. /// The best match, if there is one, otherwise null. [CanBeNull] private static NugetPackageLocal GetCachedPackage([NotNull] INugetPackageIdentifier packageId) { if (!ConfigurationManager.NugetConfigFile.InstallFromCache || packageId.HasVersionRange) { return null; } var cachedPackagePath = Path.Combine(CacheOutputDirectory, packageId.PackageFileName); if (!File.Exists(cachedPackagePath)) { return null; } NugetLogger.LogVerbose("Found exact package in the cache: {0}", cachedPackagePath); return NugetPackageLocal.FromNupkgFile( cachedPackagePath, new NugetPackageSourceLocal( "Nupkg file from cache", Path.GetDirectoryName(cachedPackagePath) ?? throw new InvalidOperationException($"Failed to get directory from '{cachedPackagePath}'"))); } /// /// Tries to find an "online" (in the package sources - which could be local) package that matches (or is in the range of) the given package ID. /// /// The of the to find. /// The best match, if there is one, otherwise null. [CanBeNull] private static INugetPackage GetOnlinePackage([NotNull] INugetPackageIdentifier packageId) { var package = ConfigurationManager.GetSpecificPackage(packageId); if (package != null) { NugetLogger.LogVerbose("{0} {1} not found, using {2}", packageId.Id, packageId.Version, package.Version); } else { NugetLogger.LogVerbose("Failed to find {0} {1}", packageId.Id, packageId.Version); } return package; } /// /// Tries to find an already installed package that matches (or is in the range of) the given package ID. /// /// The of the to find. /// The best match, if there is one, otherwise null. [CanBeNull] private static INugetPackage GetInstalledPackage([NotNull] INugetPackageIdentifier packageId) { if (!InstalledPackagesManager.TryGetById(packageId.Id, out var installedPackage)) { return null; } if (packageId.PackageVersion == installedPackage.PackageVersion) { NugetLogger.LogVerbose("Found exact package already installed: {0} {1}", installedPackage.Id, installedPackage.Version); return installedPackage; } if (!packageId.InRange(installedPackage)) { NugetLogger.LogVerbose( "Requested {0} {1}. {2} is already installed, but it is out of range.", packageId.Id, packageId.Version, installedPackage.Version); return null; } var configPackage = InstalledPackagesManager.GetPackageConfigurationById(packageId.Id); if (configPackage != null && configPackage.PackageVersion < installedPackage.PackageVersion) { NugetLogger.LogVerbose( "Requested {0} {1}. {2} is already installed, but config demands lower version.", packageId.Id, packageId.Version, installedPackage.Version); return null; } NugetLogger.LogVerbose( "Requested {0} {1}, but {2} is already installed, so using that.", packageId.Id, packageId.Version, installedPackage.Version); return installedPackage; } private static string DetermineCacheOutputDirectory() { var cacheFromEnvironment = Environment.GetEnvironmentVariable(CacheEnvironmentVariableName); if (!string.IsNullOrEmpty(cacheFromEnvironment)) { return Path.GetFullPath(cacheFromEnvironment); } var localApplicationDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); if (string.IsNullOrEmpty(localApplicationDataFolder)) { throw new InvalidOperationException( $"There is no system folder specified for '{Environment.SpecialFolder.LocalApplicationData}' you need to either configure it or set the NuGet cache directory by specifying the '{CacheEnvironmentVariableName}' environment variable."); } return Path.Combine(Path.GetFullPath(localApplicationDataFolder), "NuGet", "Cache"); } } }