using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using Codice.Client.Common;
using Codice.LogWrapper;
using Unity.PlasticSCM.Editor.UI;
namespace Unity.PlasticSCM.Editor.AssetUtils.Processor
{
class UVCSAssetPostprocessor : AssetPostprocessor
{
internal struct PathToMove
{
internal readonly string SrcPath;
internal readonly string DstPath;
internal PathToMove(string srcPath, string dstPath)
{
SrcPath = srcPath;
DstPath = dstPath;
}
}
internal static bool IsAutomaticAddEnabled { get; private set; }
static UVCSAssetPostprocessor()
{
IsAutomaticAddEnabled = BoolSetting.Load(
UnityConstants.AUTOMATIC_ADD_KEY_NAME, true);
}
internal static void Enable(
string wkPath,
UVCSAssetsProcessor uvcsAssetsProcessor)
{
mLog.Debug("Enable");
mWkPath = wkPath;
mUVCSAssetsProcessor = uvcsAssetsProcessor;
mIsEnabled = true;
}
internal static void Disable()
{
mLog.Debug("Disable");
mIsEnabled = false;
mWkPath = null;
mUVCSAssetsProcessor = null;
}
internal static void SetAutomaticAddPreference(bool isEnabled)
{
if (IsAutomaticAddEnabled == isEnabled)
return;
IsAutomaticAddEnabled = isEnabled;
BoolSetting.Save(isEnabled, UnityConstants.AUTOMATIC_ADD_KEY_NAME);
}
internal static void SetIsRepaintNeededAfterAssetDatabaseRefresh()
{
mIsRepaintNeededAfterAssetDatabaseRefresh = true;
}
static void OnPostprocessAllAssets(
string[] importedAssets,
string[] deletedAssets,
string[] movedAssets,
string[] movedFromAssetPaths)
{
if (!mIsEnabled)
return;
if (mIsRepaintNeededAfterAssetDatabaseRefresh)
{
mIsRepaintNeededAfterAssetDatabaseRefresh = false;
RepaintEditor.AllWindowsWithOverlays();
}
// Ensure that the MonoFSWatcher is enabled before processing Plastic operations.
// It fixes the following scenario:
// 1. Close UVCS window.
// 2. Create an asset, it appears with the added overlay.
// 3. Open UVCS window, the asset should appear as added instead of deleted locally.
UVCSPlugin.EnableMonoFsWatcherIfNeeded();
List<PathToMove> pathsToMove = GetPathsToMoveOnWorkspace(
mWkPath, movedAssets, movedFromAssetPaths);
mUVCSAssetsProcessor.MoveOnSourceControl(pathsToMove);
List<string> pathsToCheckout;
List<string> pathsToAdd;
GetPathsToCheckoutOrAddOnWorkspace(
mWkPath, importedAssets, pathsToMove, IsAutomaticAddEnabled,
out pathsToCheckout, out pathsToAdd);
mUVCSAssetsProcessor.CheckoutOnSourceControl(pathsToCheckout);
mUVCSAssetsProcessor.DeleteFromSourceControl(
GetPathsContainedOnWorkspace(mWkPath, deletedAssets));
mUVCSAssetsProcessor.AddToSourceControl(pathsToAdd);
// Unity Editor notifies the modified assets through
// UVCSAssetModificationProcessor.OnWillSaveAssets before getting here.
// Known Issue: renamed prefabs not triggering OnWillSaveAssets.
mUVCSAssetsProcessor.CheckoutOnSourceControl(
GetPathsContainedOnWorkspace(
mWkPath, UVCSAssetModificationProcessor.ExtractModifiedAssetsToProcess()));
}
static List<PathToMove> GetPathsToMoveOnWorkspace(
string wkPath,
string[] movedAssets,
string[] movedFromAssetPaths)
{
List<PathToMove> proposedPathsToMove = GetPathsToMoveContainedOnWorkspace(
wkPath, movedAssets, movedFromAssetPaths);
// Unity Editor does not notify the moved assets in order. So, we have
// to process the move operations in hierarchical order, otherwise, that
// will end up with locally moved paths. This also avoids unnecessary
// move operations for children when their parents are also moved.
proposedPathsToMove.Sort((x, y) => PathHelper.
GetPathMatchSorter().Compare(x.SrcPath, y.SrcPath));
List<PathToMove> result = new List<PathToMove>();
foreach (PathToMove proposedPathToMove in proposedPathsToMove)
{
if (result.Any(pathToMove => PathHelper.
IsContainedOn(proposedPathToMove.SrcPath, pathToMove.SrcPath)))
continue;
result.Add(proposedPathToMove);
}
return result;
}
static void GetPathsToCheckoutOrAddOnWorkspace(
string wkPath,
string[] importedAssets,
List<PathToMove> pathsToMove,
bool isAutomaticAddEnabled,
out List<string> pathsToCheckout,
out List<string> pathsToAdd)
{
pathsToCheckout = new List<string>();
pathsToAdd = new List<string>();
// Unity Editor notifies as imported assets a combination of moved+modified
// assets and added assets. To ensure proper version control operations,
// we need to categorize them accordingly:
// • moved+modified assets → check-out them to handle as controlled changes.
// • added assets → add them when the automatic add preference is enabled.
foreach (string asset in importedAssets)
{
string fullPath = AssetsPath.GetFullPathUnderWorkspace.
ForAsset(wkPath, asset);
if (fullPath == null)
continue;
if (pathsToMove.FindIndex(pathToMove =>
pathToMove.DstPath.Equals(fullPath)) != -1)
{
pathsToCheckout.Add(fullPath);
continue;
}
if (isAutomaticAddEnabled)
pathsToAdd.Add(fullPath);
}
}
static List<PathToMove> GetPathsToMoveContainedOnWorkspace(
string wkPath,
string[] movedAssets,
string[] movedFromAssetPaths)
{
List<PathToMove> result = new List<PathToMove>(movedAssets.Length);
for (int i = 0; i < movedAssets.Length; i++)
{
string fullSrcPath = AssetsPath.GetFullPathUnderWorkspace.
ForAsset(wkPath, movedFromAssetPaths[i]);
if (fullSrcPath == null)
continue;
string fullDstPath = AssetsPath.GetFullPathUnderWorkspace.
ForAsset(wkPath, movedAssets[i]);
if (fullDstPath == null)
continue;
result.Add(new PathToMove(
fullSrcPath, fullDstPath));
}
return result;
}
static List<string> GetPathsContainedOnWorkspace(
string wkPath, string[] assets)
{
List<string> result = new List<string>(
assets.Length);
foreach (string asset in assets)
{
string fullPath = AssetsPath.GetFullPathUnderWorkspace.
ForAsset(wkPath, asset);
if (fullPath == null)
continue;
result.Add(fullPath);
}
return result;
}
static bool mIsEnabled;
static bool mIsRepaintNeededAfterAssetDatabaseRefresh;
static UVCSAssetsProcessor mUVCSAssetsProcessor;
static string mWkPath;
static readonly ILog mLog = PlasticApp.GetLogger("UVCSAssetPostprocessor");
}
}