using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using JetBrains.Annotations;
using NugetForUnity.Configuration;
using NugetForUnity.Models;
using NugetForUnity.PackageSource;
using NugetForUnity.PluginSupport;
using Debug = UnityEngine.Debug;
namespace NugetForUnity
{
///
/// Manages the packages currently installed in the unity project.
///
public static class InstalledPackagesManager
{
///
/// The dictionary of currently installed s keyed off of their ID string.
///
[CanBeNull]
private static Dictionary installedPackages;
///
/// Backing field for the packages.config file.
///
[CanBeNull]
private static PackagesConfigFile packagesConfigFile;
///
/// Gets the loaded packages.config file that hold the dependencies for the project.
///
[NotNull]
public static PackagesConfigFile PackagesConfigFile
{
get
{
if (packagesConfigFile is null)
{
packagesConfigFile = PackagesConfigFile.Load();
}
return packagesConfigFile;
}
}
///
/// Gets the packages that are actually installed in the project.
///
[NotNull]
public static IEnumerable InstalledPackages => InstalledPackagesDictionary.Values;
///
/// Gets the dictionary of packages that are actually installed in the project, keyed off of the ID.
///
[NotNull]
private static Dictionary InstalledPackagesDictionary
{
get
{
if (installedPackages == null)
{
UpdateInstalledPackages();
}
return installedPackages;
}
}
///
/// Gets the configuration of the given package from the packages.config file.
///
/// The package identifier used to search the package in the packages.config ().
/// The found configuration or null if there is no matching package inside the packages.config.
[CanBeNull]
internal static PackageConfig GetPackageConfigurationById([NotNull] string packageIdentifier)
{
return PackagesConfigFile.GetPackageConfigurationById(packageIdentifier);
}
///
/// Returns whether or not the specified package has been marked as manually installed inside the packages.config file.
///
/// The package identifier used to search the package in the packages.config ().
/// True if the package has been manually installed or no configuration was found, false otherwise.
internal static bool GetManuallyInstalledFlagFromConfiguration([NotNull] string packageIdentifier)
{
var packageConfiguration = GetPackageConfigurationById(packageIdentifier);
return packageConfiguration == null || packageConfiguration.IsManuallyInstalled;
}
///
/// Sets the manually installed flag for the given package and stores the change in the .
///
/// The package to mark as manually installed.
internal static void SetManuallyInstalledFlag([NotNull] INugetPackageIdentifier package)
{
PackagesConfigFile.SetManuallyInstalledFlag(package);
PackagesConfigFile.Save();
}
///
/// Invalidates the currently loaded 'packages.config' so it is reloaded when it is accessed the next time.
///
internal static void ReloadPackagesConfig()
{
packagesConfigFile = null;
}
///
/// Removes the package from the 'packages.config' and from the installed packages dictionary ().
///
/// The package to remove.
internal static void RemovePackage([NotNull] INugetPackage package)
{
// update the package.config file
if (PackagesConfigFile.RemovePackage(package))
{
PackagesConfigFile.Save();
}
InstalledPackagesDictionary.Remove(package.Id);
}
///
/// Adds the package to the 'packages.config' file.
///
/// The package to add.
/// The newly added or allready existing config entry from the packages.config file.
[NotNull]
internal static PackageConfig AddPackageToConfig([NotNull] INugetPackage package)
{
var packageConfig = PackagesConfigFile.AddPackage(package);
PackagesConfigFile.Save();
return packageConfig;
}
///
/// Adds the package to the installed packages dictionary ().
///
/// The package to add.
internal static void AddPackageToInstalled([NotNull] INugetPackage package)
{
InstalledPackagesDictionary.Add(package.Id, package);
}
///
/// Gets the package from the installed packages dictionary () by its ID.
///
/// The id of the package to search in the currently installed packages.
/// The found installed package.
/// True if a package was found.
internal static bool TryGetById([NotNull] string id, out INugetPackage package)
{
return InstalledPackagesDictionary.TryGetValue(id, out package);
}
///
/// Updates the dictionary of packages that are actually installed in the project based on the files that are currently installed.
///
internal static void UpdateInstalledPackages()
{
if (installedPackages == null)
{
installedPackages = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
else
{
installedPackages.Clear();
}
// loops through the packages that are actually installed in the project
if (!Directory.Exists(ConfigurationManager.NugetConfigFile.RepositoryPath))
{
return;
}
var stopwatch = Stopwatch.StartNew();
var manuallyInstalledPackagesNumber = 0;
// a package that was installed via a old version of NuGetForUnity will have the .nupkg it came from inside the folder,
// as we updated the behavior to only keep the .nuspec. So we need to extract the .nuspec from the .nupkg
var nupkgFiles = Directory.GetFiles(ConfigurationManager.NugetConfigFile.RepositoryPath, "*.nupkg", SearchOption.AllDirectories);
foreach (var nupkgFile in nupkgFiles)
{
using (var zip = ZipFile.OpenRead(nupkgFile))
{
foreach (var entry in zip.Entries)
{
var entryFullName = entry.FullName;
// extract only .nuspec and .nuspec.meta files
if (entryFullName.EndsWith(".nuspec.meta", StringComparison.Ordinal) ||
entryFullName.EndsWith(".nuspec", StringComparison.Ordinal))
{
PackageContentManager.ExtractPackageEntry(
entry,
Path.GetDirectoryName(nupkgFile) ??
throw new InvalidOperationException($"Failed to get directory from '{nupkgFile}'"));
}
}
}
// delete .nupkg and its .meta file to sync compatibility
File.Delete(nupkgFile);
var metaFile = $"{nupkgFile}.meta";
if (File.Exists(metaFile))
{
File.Delete($"{nupkgFile}.meta");
}
}
var nuspecFiles = Directory.GetFiles(ConfigurationManager.NugetConfigFile.RepositoryPath, "*.nuspec", SearchOption.AllDirectories);
foreach (var nuspecFile in nuspecFiles)
{
var directoryName = Path.GetFileName(Path.GetDirectoryName(nuspecFile));
if (!string.IsNullOrEmpty(directoryName) && directoryName[0] == '.')
{
// Skip nuspec files that are in directories starting with '.' since those are considered hidden and should be ignored.
continue;
}
var package = NugetPackageLocal.FromNuspec(
NuspecFile.Load(nuspecFile),
new NugetPackageSourceLocal(
"Nuspec file from Project",
Path.GetDirectoryName(nuspecFile) ?? throw new InvalidOperationException($"Failed to get directory from '{nuspecFile}'")));
AddPackageToInstalledInternal(package, ref manuallyInstalledPackagesNumber);
}
if (manuallyInstalledPackagesNumber == 0)
{
// set root packages as manually installed if none are marked as such
foreach (var rootPackage in GetInstalledRootPackages())
{
PackagesConfigFile.SetManuallyInstalledFlag(rootPackage);
}
PackagesConfigFile.Save();
}
NugetLogger.LogVerbose("Getting installed packages took {0} ms", stopwatch.ElapsedMilliseconds);
}
///
/// Checks if there are any packages inside the package install directory that are not listed inside the packages.config.
///
/// True if some packages are deleted.
internal static bool RemoveUnnecessaryPackages()
{
if (!Directory.Exists(ConfigurationManager.NugetConfigFile.RepositoryPath))
{
return false;
}
var directories = Directory.GetDirectories(ConfigurationManager.NugetConfigFile.RepositoryPath, "*", SearchOption.TopDirectoryOnly);
var somethingDeleted = false;
foreach (var folder in directories)
{
var folderName = Path.GetFileName(folder);
if (folderName.StartsWith(".", StringComparison.Ordinal))
{
// ignore folders whose name starts with a dot because they are considered hidden
continue;
}
var nuspecPath = Directory.GetFiles(folder, "*.nuspec").FirstOrDefault();
if (!File.Exists(nuspecPath))
{
// ignore folder not containing a nuspec file
continue;
}
var package = NugetPackageLocal.FromNuspecFile(
nuspecPath,
new NugetPackageSourceLocal(
"Nuspec file already installed",
Path.GetDirectoryName(nuspecPath) ?? throw new InvalidOperationException($"Failed to get directory from '{nuspecPath}'")));
var installed = PackagesConfigFile.Packages.Exists(packageId => packageId.Equals(package));
if (!installed)
{
somethingDeleted = true;
NugetLogger.LogVerbose("---DELETE unnecessary package {0}", folder);
PackageContentManager.DeletePackageContentPackage(package);
}
}
if (somethingDeleted)
{
UpdateInstalledPackages();
}
return somethingDeleted;
}
///
/// Checks if a given package is installed.
///
/// The package to check if is installed.
/// Determine if it should check if the package is already imported by unity itself.
/// True if the given package is installed. False if it is not.
internal static bool IsInstalled([NotNull] INugetPackageIdentifier package, bool checkIsAlreadyImportedInEngine)
{
if (checkIsAlreadyImportedInEngine && UnityPreImportedLibraryResolver.IsAlreadyImportedInEngine(package.Id))
{
return true;
}
var isInstalled = false;
if (InstalledPackagesDictionary.TryGetValue(package.Id, out var installedPackage))
{
isInstalled = package.Equals(installedPackage);
}
return isInstalled;
}
///
/// Checks if any version of the given package Id is installed.
///
/// The package to check if is installed.
/// Determine if it should check if the package is already imported by unity itself.
/// True if the given package is installed. False if it is not.
internal static bool IsInstalled([NotNull] string packageId, bool checkIsAlreadyImportedInEngine)
{
if (checkIsAlreadyImportedInEngine && UnityPreImportedLibraryResolver.IsAlreadyImportedInEngine(packageId))
{
return true;
}
return InstalledPackagesDictionary.ContainsKey(packageId);
}
///
/// Gets a list of all root packages that are installed in the project.
/// Root packages are packages that are not depended on by any other package.
///
/// The root packages.
[NotNull]
[ItemNotNull]
internal static List GetInstalledRootPackages()
{
// default all packages to being roots
var roots = new List(InstalledPackages);
// remove a package as a root if another package is dependent on it
foreach (var package in InstalledPackages)
{
var frameworkDependencies = package.CurrentFrameworkDependencies;
foreach (var dependency in frameworkDependencies)
{
roots.RemoveAll(p => p.Id == dependency.Id);
}
}
return roots;
}
private static void AddPackageToInstalledInternal([NotNull] INugetPackage package, ref int manuallyInstalledPackagesNumber)
{
var packages = InstalledPackagesDictionary;
if (!packages.ContainsKey(package.Id))
{
PluginRegistry.Instance.ProcessInstalledPackage(package);
package.IsManuallyInstalled = GetPackageConfigurationById(package.Id)?.IsManuallyInstalled ?? false;
if (package.IsManuallyInstalled)
{
manuallyInstalledPackagesNumber++;
}
packages.Add(package.Id, package);
}
else
{
Debug.LogErrorFormat("Package is already in installed list: {0}", package.Id);
}
}
}
}