minesweeper_game/Library/PackageCache/com.unity.timeline@fa3a0bab2b90/Editor/Samples/SampleDependencyImporter.cs
2025-03-15 14:30:26 -04:00

303 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEditor.PackageManager.UI;
namespace UnityEditor.Timeline
{
[InitializeOnLoad]
class SampleDependencyImporter : IPackageManagerExtension
{
static readonly string k_DependenciesDialogTitle = L10n.Tr("Import Sample Package Dependencies");
static readonly string k_DependenciesDialogMessage = L10n.Tr("These samples contain package dependencies that your project does not have: ");
static readonly string k_DependenciesDialogOK = L10n.Tr("Import samples and their dependencies");
static readonly string k_DependenciesDialogCancel = L10n.Tr("Import samples without their dependencies");
static readonly string k_NewLine = "\n";
const string k_TimelinePackageName = "com.unity.timeline";
PackageManager.PackageInfo m_PackageInfo;
IEnumerable<Sample> m_Samples;
SampleConfiguration m_SampleConfiguration;
PackageChecker m_PackageChecker = new PackageChecker();
static SampleDependencyImporter() => PackageManagerExtensions.RegisterExtension(new SampleDependencyImporter());
UnityEngine.UIElements.VisualElement IPackageManagerExtension.CreateExtensionUI() => default;
public void OnPackageAddedOrUpdated(PackageManager.PackageInfo packageInfo) => m_PackageChecker.RefreshPackageCache();
public void OnPackageRemoved(PackageManager.PackageInfo packageInfo) => m_PackageChecker.RefreshPackageCache();
/// <summary>
/// Called when the package selection changes in the Package Manager window.
/// It loads Timeline package info and configuration, when Timeline is selected.
/// </summary>
public void OnPackageSelectionChange(PackageManager.PackageInfo packageInfo)
{
bool timelinePackageInfo = packageInfo != null && packageInfo.name.StartsWith(k_TimelinePackageName);
if (m_PackageInfo == null && timelinePackageInfo)
{
m_PackageInfo = packageInfo;
m_Samples = Sample.FindByPackage(packageInfo.name, packageInfo.version);
if (TryLoadSampleConfiguration(m_PackageInfo, out m_SampleConfiguration))
SamplePostprocessor.AssetImported += LoadAssetDependencies;
}
else if (!timelinePackageInfo)
{
m_PackageInfo = null;
SamplePostprocessor.AssetImported -= LoadAssetDependencies;
}
}
/// <summary>Load the sample configuration for the specified package, if one is available.</summary>
static bool TryLoadSampleConfiguration(PackageManager.PackageInfo packageInfo, out SampleConfiguration configuration)
{
var configurationPath = $"{packageInfo.assetPath}/Samples~/sampleDependencies.json";
if (File.Exists(configurationPath))
{
var configurationText = File.ReadAllText(configurationPath);
configuration = JsonUtility.FromJson<SampleConfiguration>(configurationText);
return true;
}
configuration = null;
return false;
}
AddRequest m_PackageAddRequest;
int m_PackageDependencyIndex;
List<string> m_PackageDependencies = new List<string>();
void LoadAssetDependencies(string assetPath)
{
if (!PackageManagerUIOpen())
{
SamplePostprocessor.AssetImported -= LoadAssetDependencies;
return;
}
if (m_SampleConfiguration != null)
{
var assetsImported = false;
foreach (var sample in m_Samples)
{
if (assetPath.EndsWith(sample.displayName))
{
var sampleEntry = m_SampleConfiguration.GetEntry(sample);
if (sampleEntry != null)
{
// Import common asset dependencies
assetsImported = ImportAssetDependencies(
m_PackageInfo, m_SampleConfiguration.SharedAssetDependencies, out var sharedDestinations);
// Import sample-specific asset dependencies
assetsImported |= ImportAssetDependencies(
m_PackageInfo, sampleEntry.AssetDependencies, out var localDestinations);
// Import common amd sample specific package dependencies
m_PackageDependencyIndex = 0;
m_PackageDependencies = new List<string>(m_SampleConfiguration.SharedPackageDependencies);
m_PackageDependencies.AddRange(sampleEntry.PackageDependencies);
if (m_PackageDependencies.Count != 0 &&
DoDependenciesNeedToBeImported(out var dependenciesToImport))
{
if (PromptUserImportDependencyConfirmation(dependenciesToImport))
{
// only import dependencies that are missing
m_PackageDependencies = dependenciesToImport;
// Import package dependencies using the editor update loop, because
// adding packages need to be done in sequence one after the other
EditorApplication.update += ImportPackageDependencies;
}
}
}
break;
}
}
if (assetsImported)
AssetDatabase.Refresh();
}
// local functions
bool DoDependenciesNeedToBeImported(out List<string> packagesToImport)
{
packagesToImport = new List<string>();
foreach (var packageName in m_PackageDependencies)
{
if (!m_PackageChecker.ContainsPackage(packageName))
packagesToImport.Add(packageName);
}
return packagesToImport.Count != 0;
}
void ImportPackageDependencies()
{
if (m_PackageAddRequest != null && !m_PackageAddRequest.IsCompleted)
return; // wait while we have a request pending
if (m_PackageDependencyIndex < m_PackageDependencies.Count)
m_PackageAddRequest = Client.Add(m_PackageDependencies[m_PackageDependencyIndex++]);
else
{
m_PackageDependencies.Clear();
m_PackageAddRequest = null;
EditorApplication.update -= ImportPackageDependencies;
}
}
static bool ImportAssetDependencies(PackageManager.PackageInfo packageInfo, string[] paths, out List<string> destinations)
{
destinations = new List<string>();
if (paths == null)
return false;
var assetsImported = false;
foreach (var path in paths)
{
var dependencyPath = Path.GetFullPath($"Packages/{packageInfo.name}/Samples~/{path}");
if (Directory.Exists(dependencyPath))
{
var copyTo =
$"{Application.dataPath}/Samples/{packageInfo.displayName}/{packageInfo.version}/{path}";
CopyDirectory(dependencyPath, copyTo);
destinations.Add(copyTo);
assetsImported = true;
}
}
return assetsImported;
}
static bool PromptUserImportDependencyConfirmation(List<string> dependencies)
{
var aggregate = dependencies.Aggregate(string.Empty, (current, dependency) => current + (dependency + k_NewLine));
return EditorUtility.DisplayDialog(
k_DependenciesDialogTitle,
$"{k_DependenciesDialogMessage}{k_NewLine}{aggregate}",
k_DependenciesDialogOK,
k_DependenciesDialogCancel);
}
}
static bool PackageManagerUIOpen() => PackageManagerWindow.instance != null;
/// <summary>Copies a directory from the source to target path. Overwrites existing directories.</summary>
static void CopyDirectory(string sourcePath, string targetPath)
{
// Verify source directory
var source = new DirectoryInfo(sourcePath);
if (!source.Exists)
throw new DirectoryNotFoundException($"{sourcePath} directory not found");
// Delete pre-existing directory at target path
var target = new DirectoryInfo(targetPath);
if (target.Exists)
target.Delete(true);
Directory.CreateDirectory(targetPath);
// Copy all files to target path
foreach (FileInfo file in source.GetFiles())
{
var newFilePath = Path.Combine(targetPath, file.Name);
file.CopyTo(newFilePath);
}
// Recursively copy all subdirectories
foreach (DirectoryInfo child in source.GetDirectories())
{
var newDirectoryPath = Path.Combine(targetPath, child.Name);
CopyDirectory(child.FullName, newDirectoryPath);
}
}
/// <summary>An AssetPostProcessor which will raise an event when a new asset is imported.</summary>
class SamplePostprocessor : AssetPostprocessor
{
public static event Action<string> AssetImported;
static void OnPostprocessAllAssets(string[] importedAssets,
string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (AssetImported == null)
return;
foreach (var importedAsset in importedAssets)
AssetImported.Invoke(importedAsset);
}
}
/// <summary>A configuration class defining information related to samples for the package.</summary>
[Serializable]
class SampleConfiguration
{
/// <summary>This class defines the path and dependencies for a specific sample.</summary>
[Serializable]
public class SampleEntry
{
public string Path;
public string[] AssetDependencies;
public string[] PackageDependencies;
}
public string[] SharedAssetDependencies;
public string[] SharedPackageDependencies;
public SampleEntry[] SampleEntries;
public SampleEntry GetEntry(Sample sample) =>
SampleEntries?.FirstOrDefault(t => sample.resolvedPath.EndsWith(t.Path));
}
class PackageChecker
{
ListRequest m_Request;
PackageCollection m_Packages;
public PackageChecker()
{
RefreshPackageCache();
}
public void RefreshPackageCache()
{
if (m_Request != null && !m_Request.IsCompleted)
return; // need to wait for previous request to finish
m_Request = Client.List(true);
EditorApplication.update += WaitForRequestToComplete;
}
void WaitForRequestToComplete()
{
if (m_Request.IsCompleted)
{
if (m_Request.Status == StatusCode.Success)
m_Packages = m_Request.Result;
EditorApplication.update -= WaitForRequestToComplete;
}
}
public bool ContainsPackage(string packageName)
{
// Check each package and package dependency for packageName
foreach (var package in m_Packages)
{
if (string.Compare(package.name, packageName) == 0)
return true;
if (package.dependencies.Any(dependencyInfo => string.Compare(dependencyInfo.name, packageName) == 0))
return true;
}
return false;
}
}
}
}