using UnityEditor;
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Perforce.P4;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Text;
using log4net;
namespace P4Connect
{
public enum StorageType
{
Text = 0,
Binary,
Other
}
///
/// These are the only states that P4Connect currently recognizes
///
public enum FileState
{
None = 0,
InDepot,
InDepotDeleted,
MarkedForEdit,
MarkedForAdd,
MarkedForDelete,
MarkedForAddMove,
MarkedForDeleteMove,
IsDirectory,
}
///
/// Lock state of a file
/// Note that a locked file can still be modified, just not submitted
///
public enum LockState
{
None = 0,
OurLock,
TheirLock,
}
public enum DepotState
{
None = 0,
Deleted,
}
public enum RevisionState
{
None = 0,
HasLatest,
OutOfDate,
}
public enum ResolvedState
{
None = 0,
NeedsResolve,
}
// The type of operation the user is attempting, there are only a few recognized operations
public enum AssetOperation
{
None = 0,
Add,
Remove,
Checkout,
Move,
Revert,
RevertIfUnchanged,
GetLatest,
ForceGetLatest,
Lock,
Unlock,
Submit,
}
///
/// Since Unity has .meta files for 'almost' every asset file, files and metas sort of go in pairs
/// BUT because they don't always, we need to keep track of what we're dealing with.
///
public enum FileAndMetaType
{
None = 0,
FileOnly,
MetaOnly,
FileAndMeta,
}
///
/// A file and its associated .meta file (either can be null)
///
public struct FileAndMeta
{
public FileSpec File;
public FileSpec Meta;
public FileAndMeta(FileSpec aFile, FileSpec aMeta)
{
File = aFile;
Meta = aMeta;
}
public override string ToString()
{
return ("file: " + File.ToStringNullSafe() + " meta: " + Meta.ToStringNullSafe());
}
}
///
/// An Asset and its associated .meta file (either can be null)
/// Uses Asset Path strings instead of fileSpecs.
///
public struct AssetAndMeta
{
public string File;
public string Meta;
public AssetAndMeta(string aFile, string aMeta)
{
File = aFile;
Meta = aMeta;
}
public FileAndMeta ToFileAndMeta()
{
FileSpec fsFile = (File == null) ? null : FileSpec.LocalSpec(Utils.AssetPathToLocalPath(File));
FileSpec fsMeta = (Meta == null) ? null : FileSpec.LocalSpec(Utils.AssetPathToLocalPath(Meta));
return new FileAndMeta(fsFile,fsMeta);
}
public override string ToString()
{
return ("file: " + File.ToStringNullSafe() + " meta: " + Meta.ToStringNullSafe());
}
}
///
/// This class is the meat of P4Connect. It grabs lists of files and talks to the server to get stuff done
///
public partial class Engine
{
public delegate void OnOperationPerformedDelegate(PerforceConnection aConnection, IEnumerable aFiles, AssetOperation op);
public static event OnOperationPerformedDelegate OnOperationPerformed;
private static readonly ILog log = LogManager.GetLogger(typeof(Engine));
// The types of operations which can be performed on a Perforce file
// There are more than asset operations because the current state of the file matters.
public enum FileOperation
{
None = 0,
Add,
RevertAndAddNoOverwrite,
Checkout,
Delete,
Revert,
RevertIfUnchanged,
RevertAndDelete,
RevertAndCheckout,
RevertAndCheckoutNoOverwrite,
Move,
RevertAndMove,
RevertAndMoveNoOverwrite,
MoveToNewLocation,
GetLatest,
ForceGetLatest,
RevertAndGetLatest,
Lock,
Unlock,
}
///
/// We try to treat assets and .meta files in pair, but they may not always be,
/// and furthermore, the operations needed on each one may not be the same.
///
public struct FilesAndOp
{
public FileSpec File;
public FileSpec Meta;
public FileSpec MoveToFile;
public FileSpec MoveToMeta;
public FileOperation FileOp;
public FilesAndOp(string file, string meta, string movefile, string movemeta, FileOperation fop)
{
File = Meta = MoveToFile = MoveToMeta = null;
if (file != null)
File = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(file));
if (meta != null)
Meta = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(meta));
if (movefile != null)
MoveToFile = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(movefile));
if (movemeta != null)
MoveToMeta = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(movemeta));
FileOp = fop;
}
}
///
/// Stores all our potential perforce operations and for each, a list of files to perform that operation on
///
class FileListOperations
{
private static readonly ILog log = LogManager.GetLogger(typeof(FileListOperations));
///
/// Stores a list of Perforce files (FileSpec) on which to perform one operation
///
struct FileListOperation
{
public int FileCount
{
get { return _Files.Count; }
}
public string Description
{
get { return _Description; }
}
// The list of files to perform the P4 operation on
List _Files;
List _MoveToFiles;
// The description of the task
string _Description;
// The delegate that performs the operation (quicker to do it this way than to use a class hierarchy).
Func, IList, IList> _Operation;
static List _EmptyList;
static FileListOperation()
{
_EmptyList = new List();
}
///
/// Initializing constructor
///
///
///
public FileListOperation(string aDescription, Func, IList, IList> aOperation)
{
_Files = new List();
_MoveToFiles = new List();
_Description = aDescription;
_Operation = aOperation;
}
///
/// Add a file to the list for this operation
///
public void Add(FileSpec aFile, FileSpec aMoveToFile, FileSpec aMeta, FileSpec aMoveToMeta)
{
_Files.Add(new FileAndMeta(aFile, aMeta));
_MoveToFiles.Add(new FileAndMeta(aMoveToFile, aMoveToMeta));
}
///
/// Runs the perforce operation on the files collected so far.
///
public IList Run(PerforceConnection aConnection)
{
IList ret = null;
if (_Files.Any())
{
ret = _Operation(aConnection, _Files, _MoveToFiles);
_Files.Clear();
}
else
{
ret = _EmptyList;
}
return ret;
}
}
// A map of operations and list of files to perform that operation on
Dictionary _FileOperations;
///
/// Initializing constructor
///
public FileListOperations()
{
_FileOperations = new Dictionary();
_FileOperations.Add(FileOperation.RevertAndDelete,
new FileListOperation("Deleting Files", Operations.RevertAndDelete));
_FileOperations.Add(FileOperation.Delete,
new FileListOperation("Deleting Files", Operations.Delete));
_FileOperations.Add(FileOperation.Revert,
new FileListOperation("Reverting Files", Operations.Revert));
_FileOperations.Add(FileOperation.RevertIfUnchanged,
new FileListOperation("Reverting Unchanged Files", Operations.RevertIfUnchanged));
_FileOperations.Add(FileOperation.RevertAndCheckout,
new FileListOperation("Restoring Files", Operations.RevertAndCheckout));
_FileOperations.Add(FileOperation.RevertAndCheckoutNoOverwrite,
new FileListOperation("Checking out Files", Operations.RevertAndCheckoutNoOverwrite));
_FileOperations.Add(FileOperation.Add,
new FileListOperation("Adding Files", Operations.Add));
_FileOperations.Add(FileOperation.RevertAndAddNoOverwrite,
new FileListOperation("Moving Files", Operations.RevertAndCheckoutNoOverwrite));
_FileOperations.Add(FileOperation.Checkout,
new FileListOperation("Checking out Files", Operations.Checkout));
_FileOperations.Add(FileOperation.Move,
new FileListOperation("Moving Files", Operations.Move));
_FileOperations.Add(FileOperation.RevertAndMove,
new FileListOperation("Moving Files", Operations.RevertAndMove));
_FileOperations.Add(FileOperation.RevertAndMoveNoOverwrite,
new FileListOperation("Moving Files", Operations.RevertAndMoveNoOverwrite));
_FileOperations.Add(FileOperation.MoveToNewLocation,
new FileListOperation("Moving Files", Operations.MoveToNewLocation));
_FileOperations.Add(FileOperation.GetLatest,
new FileListOperation("Syncing Files", Operations.GetLatest));
_FileOperations.Add(FileOperation.ForceGetLatest,
new FileListOperation("Syncing Files", Operations.ForceGetLatest));
_FileOperations.Add(FileOperation.RevertAndGetLatest,
new FileListOperation("Syncing Files", Operations.RevertAndGetLatest));
_FileOperations.Add(FileOperation.Lock,
new FileListOperation("Locking Files", Operations.Lock));
_FileOperations.Add(FileOperation.Unlock,
new FileListOperation("Unlocking Files", Operations.Unlock));
}
///
/// Add a file to the list for this operation
///
public void Add(FilesAndOp aFileAndOp)
{
if (aFileAndOp.FileOp != FileOperation.None)
{
FileListOperation op = _FileOperations[aFileAndOp.FileOp];
op.Add(aFileAndOp.File, aFileAndOp.MoveToFile, aFileAndOp.Meta, aFileAndOp.MoveToMeta);
}
}
///
/// Runs the perforce operation on the files collected so far.
///
public List Run(PerforceConnection aConnection)
{
// Count the files
int totalFiles = 0;
foreach (var op in _FileOperations.Values)
{
totalFiles += op.FileCount;
}
// Perform all operations
int currentCount = 0;
List allSpecs = new List();
foreach (var op in _FileOperations.Values)
{
if (op.FileCount > 2)
{
EditorUtility.DisplayProgressBar("Hold on", "P4Connect - " + op.Description, (float)currentCount / (float)totalFiles);
}
currentCount += op.FileCount;
var opRes = op.Run(aConnection);
if (opRes != null)
{
allSpecs.AddRange(opRes);
}
}
if (totalFiles > 2)
{
EditorUtility.ClearProgressBar();
}
return allSpecs;
}
}
static List EmptyFileAndMeta;
///
/// Initialize the P4Connect Engine
///
public static void Initialize()
{
EmptyFileAndMeta = new List();
}
static bool check_local_ignore(string ignore_line, string path)
{
// string msg = "check_local_ignore: " + ignore_line + " == " + path;
//Debug.Log(msg);
bool caseSensitive = Utils.IsCaseSensitive();
int ignore_ll = ignore_line.Length;
if (ignore_ll == 0)
{
return false;
}
int path_ll = path.Length;
if (ignore_ll == path_ll)
{
if (0 == String.Compare(ignore_line, path, caseSensitive))
{
// Debug.Log("Ignore Line Match Ignored: " + path);
return true;
}
}
else if (ignore_ll < path_ll) // could be a subdirectory
{
if (ignore_line[ignore_ll - 1] == '/') // ignore line ends with slash (match all children)
{
if (0 == String.Compare(ignore_line, 0, path, 0, ignore_ll, caseSensitive))
{
// The path is a child of the ignore line
//Debug.Log("Child Ignored: " + path);
return true;
}
}
}
return false;
}
static bool is_ignored(string path, PerforceConnection aConnection)
{
string[] dels = new string[] { "\n", "\r" };
path = path.Trim();
// Debug.Log("is_ignored: " + path);
// Check the additional ignore list:
if (! String.IsNullOrEmpty(Config.IgnoreLines))
{
foreach (var ipath in Config.IgnoreLines.Split(dels, StringSplitOptions.RemoveEmptyEntries))
{
if (check_local_ignore(ipath.Trim(), path))
return true;
}
}
// Check if in P4IGNORE
if (aConnection.P4Connection.IsFileIgnored(path))
{
//Debug.Log("P4IGNORE Ignored: " + path);
return true;
}
return false;
}
public static string[] StripIgnore(string[] files, PerforceConnection aConnection)
{
string[] result = files.Where(path => !is_ignored(path, aConnection)).ToArray();
//log.DebugFormat("StripIgnore {0} returns {1}", Logger.StringArrayToString(files), Logger.StringArrayToString(result));
return(result);
}
///
/// This method is called by Unity when assets are created BY Unity itself
/// Note: This is not an override because Unity calls it through Invoke()
///
public static List CreateAsset(string arPath)
{
string[] filesToAdd = new string[] { arPath };
return CreateAssets(filesToAdd);
}
///
/// This method is called by Unity when assets are created BY Unity itself
/// Note: This is not an override because Unity calls it through Invoke()
///
public static List CreateAssets(string[] arPaths)
{
// Creation of assets doesn't need to happen right away
return PerformOperation(arPaths, null, AssetOperation.Add);
}
///
/// This method is called by Unity when assets are deleted
///
public static List DeleteAsset(string arPath)
{
// Deletion of assets doesn't need to happen right away
var paths = new String[] { arPath };
return DeleteAssets(paths);
}
///
/// This method is called by Unity when assets are deleted
///
public static List DeleteAssets(string[] arPath)
{
// Deletion of assets doesn't need to happen right away
return PerformOperation(arPath.Where(p => p.Length > 0).ToArray(), null, AssetOperation.Remove);
}
///
/// Called to simply mark a file as modified
///
public static List CheckoutAsset(string arPath)
{
// Checking out assets does need to happen right away
string[] filesToCheckout = new string[] { arPath };
return CheckoutAssets(filesToCheckout);
}
///
/// This method is called by Unity when assets are moved
///
public static List MoveAssets(string[] arPath, string[] arMoveToPath)
{
// Deletion of assets doesn't need to happen right away
return PerformOperation(arPath, arMoveToPath, AssetOperation.Move);
}
///
///
///
public static List MoveAsset(string arPath, string arMoveToPath)
{
// Checking out assets does need to happen right away
string[] filesToMove = new string[] { arPath };
string[] filesToMoveTo = new string[] { arMoveToPath };
return MoveAssets(filesToMove, filesToMoveTo);
}
///
/// This method is called by Unity when assets are deleted
///
public static List RevertAssets(string[] arPath, bool aForce)
{
// Deletion of assets doesn't need to happen right away
if (aForce)
return PerformOperation(arPath, null, AssetOperation.Revert);
else
return PerformOperation(arPath, null, AssetOperation.RevertIfUnchanged);
}
///
/// Called to simply revert a modified file
///
public static List RevertAsset(string arPath, bool aForce)
{
// Checking out assets does need to happen right away
string[] filesToRevert = new string[] { arPath };
return RevertAssets(filesToRevert, aForce);
}
///
/// Called to simply mark a file as modified
///
public static List CheckoutAssets(string[] arPaths)
{
// Checking out assets does need to happen right away
return PerformOperation(arPaths, null, AssetOperation.Checkout);
}
///
/// Called to lock files and Directories
///
public static List LockAssets(string[] arPaths)
{
return PerformOperation(arPaths.AddDirectoryWildcards().ToArray(), null, AssetOperation.Lock);
}
///
/// Called to unlock files and Directories
///
public static List UnlockAssets(string[] arPaths)
{
return PerformOperation(arPaths.AddDirectoryWildcards().ToArray(), null, AssetOperation.Unlock);
}
///
/// This method is called when the user wants to sync files
///
public static List GetLatestAssets(string[] arPath, bool aForce)
{
if (aForce)
return PerformOperation(arPath, null, AssetOperation.ForceGetLatest);
else
return PerformOperation(arPath, null, AssetOperation.GetLatest);
}
///
/// This method is called when the user wants to sync files
///
public static List GetLatestAsset(string arPath, bool aForce)
{
// Checking out assets does need to happen right away
string[] filesToGetLatest = new string[] { arPath };
return GetLatestAssets(filesToGetLatest, aForce);
}
///
/// Returns the lock state of the passed in file
///
public static LockState GetLockState(string assetPath)
{
if (Utils.IsFilePathValid(assetPath))
{
var metaData = AssetStatusCache.GetCachedMetaDataAndUpdateDefaults(assetPath);
return GetLockState(metaData);
}
else
return LockState.None;
}
///
/// Returns the lock state of the passed in file
///
public static LockState GetLockState(IList aMeta)
{
LockState retState = LockState.None;
if (aMeta != null)
{
foreach (var data in aMeta)
{
if (data.OurLock)
retState = LockState.OurLock;
else if (data.OtherLock)
retState = LockState.TheirLock;
}
}
return retState;
}
///
/// Returns the lock state of the passed in file
///
public static LockState GetLockState(FileMetaData aMeta)
{
//log.Debug("aMeta: " + Logger.ToStringNullSafe(aMeta));
LockState retState = LockState.None;
if (aMeta != null)
{
if (aMeta.OurLock)
retState = LockState.OurLock;
else if (aMeta.OtherLock)
retState = LockState.TheirLock;
}
return retState;
}
///
/// Returns the state of the passed in file
///
public static FileState GetFileState(string assetPath)
{
if (Utils.IsFilePathValid(assetPath))
{
FileMetaData meta = AssetStatusCache.GetCachedMetaDataAndUpdateDefaults(assetPath);
return GetOpenAction(meta);
}
else
return FileState.None;
}
///
/// Returns the "open action" from the metadata.
///
public static FileState GetOpenAction(FileMetaData aMetaData)
{
// Debug.Log("GetOpenAction: " + aMetaData.Action.ToString() + " returns: " + ParseFileAction(aMetaData.Action));
return ParseFileAction(aMetaData.Action);
}
public static FileState GetHeadAction(FileMetaData aMetaData)
{
//Debug.Log("GetHeadAction: " + aMetaData.HeadAction.ToString() + " returns: " + ParseFileAction(aMetaData.HeadAction));
return ParseFileAction(aMetaData.HeadAction);
}
///
/// Returns the state of the passed in file on the server (if checked out by someone else for instance)
///
public static FileState GetServerFileState(string arPath, PerforceConnection aConnection)
{
if (Utils.IsFilePathValid(arPath))
return GetServerFileState(arPath);
else
return FileState.None;
}
///
/// Returns the state of the passed in file on the server (if checked out by someone else for instance)
///
//public static FileState GetServerFileState(FileSpec arFile, PerforceConnection aConnection)
//{
// FileState retState = FileState.None;
// IList dataList = GetFileMetaData(aConnection, null, arFile);
// if (dataList != null)
// {
// foreach (var data in dataList)
// {
// if (data.OtherActions != null)
// {
// foreach (var action in data.OtherActions)
// {
// FileState otherState = ParseFileAction(action);
// if (otherState != FileState.InDepot)
// {
// retState = otherState;
// }
// }
// }
// }
// }
// return retState;
//}
///
/// Returns the state of the passed in file on the server (if checked out by someone else for instance)
///
public static FileState GetServerFileState(string assetPath)
{
FileState retState = FileState.None;
var status = AssetStatusCache.GetCachedAssetStatusAndUpdateDefaults(assetPath); // Check asset status for Initialized
if (!status.IsInitialized())
{
var metaData = AssetStatusCache.GetCachedMetaData(assetPath);
if (metaData.OtherActions != null)
{
foreach (var action in metaData.OtherActions)
{
FileState other = ParseFileAction(action);
if (other != FileState.InDepot)
{
retState = other;
break;
}
}
}
}
return retState;
}
///
/// Returns the state of the passed in file on the server (if checked out by someone else for instance)
///
public static RevisionState GetRevisionState(string arPath, PerforceConnection aConnection)
{
if (Utils.IsFilePathValid(arPath))
return GetRevisionState(arPath);
else
return RevisionState.None;
}
///
/// Returns the state of the passed in file on the server (if checked out by someone else for instance)
///
public static RevisionState GetRevisionState(string assetPath)
{
RevisionState retState = RevisionState.None;
var status = AssetStatusCache.GetCachedAssetStatusAndUpdateDefaults(assetPath); // Check asset status for Initialized
if (!status.IsInitialized())
{
var metaData = AssetStatusCache.GetCachedMetaData(assetPath);
retState = metaData.HaveRev == metaData.HeadRev ? RevisionState.HasLatest : RevisionState.OutOfDate;
}
return retState;
}
///
/// Performs an operation after opening a perforce connection
///
public static void PerformConnectionOperation(System.Action aConnectionOperation, bool async = false, bool quiet = false)
{
if (Config.ValidConfiguration)
{
PerforceConnection connection = new PerforceConnection();
try // The try block will make sure the connection is Disposed (i.e. closed)
{
// Perform the operation
aConnectionOperation(connection);
}
catch (Exception ex)
{
log.Error("Operation Error:", ex);
if (!async && !quiet)
{
LogP4Exception(ex);
}
}
finally
{
connection.Dispose();
}
}
}
///
/// Performs an operation after opening a perforce connection and getting the opened files
///
//public static void PerformOpenConnectionOperation(System.Action aConnectionOperation)
//{
// PerformConnectionOperation(con => aConnectionOperation(new OpenedConnection(con)));
//}
///
/// Submits the files to the server
///
public static void SubmitFiles(PerforceConnection aConnection, string aChangeListDescription, List aFiles)
{
#if DEBUG
log.DebugFormat("files: {0}", Logger.FileSpecListToString(aFiles));
#endif
// Move all the files to a new changelist
Changelist changeList = new Changelist();
changeList.Description = aChangeListDescription;
changeList.ClientId = Config.Workspace;
var allFilesMetaRaw = GetFileMetaData(aConnection, aFiles, null);
List allFilesMeta = new List();
Utils.GetMatchingMetaData(aFiles, allFilesMetaRaw, allFilesMeta);
List lockedFiles = new List();
for (int i = 0; i < aFiles.Count; i++)
{
var metaData = allFilesMeta[i];
if (GetLockState(metaData) == LockState.TheirLock)
{
lockedFiles.Add(metaData.LocalPath);
}
else
{
changeList.Files.Add(metaData);
}
}
bool cont = lockedFiles.Count == 0;
if (cont)
{
#if DEBUG
log.Debug("Changelist files: " + Logger.FileSpecListToString(aFiles));
//log.Debug("Changelist has " + aFiles.Count + " files");
//log.Debug("ChangeSpec is: " + changeList.ToString());
#endif
Changelist repList = null;
try
{
repList = aConnection.P4Depot.CreateChangelist(changeList);
}
catch (System.Exception ex)
{
#if DEBUG
log.Debug("CreateChangelist Exception", ex);
#endif
Debug.LogException(ex);
Debug.LogWarning("P4Connect - CreateChangelist failed, open P4V and make sure your files are in the default changelist");
if (ex is Perforce.P4.P4Exception)
{
Debug.LogWarning("Exception caused by this cmd: " + (ex as P4Exception).CmdLine);
}
cont = false;
}
if (cont)
{
Options submitFlags = new Options(SubmitFilesCmdFlags.None, -1, repList, null, null);
SubmitResults sr = null;
try
{
EditorUtility.DisplayProgressBar("Hold on", "Submitting Files", 0.5f);
sr = aConnection.P4Client.SubmitFiles(submitFlags, null);
}
catch(Exception ex)
{
// may fail, cannot submit from non-stream client
// may fail because we need to resolve
#if DEBUG
log.Error("SubmitFiles Exception", ex);
#endif
Debug.LogWarning("P4Connect - Submit failed, You may need to use P4V to resolve conflicts.\n" + ex.ToString());
cont = false;
}
finally
{
EditorUtility.ClearProgressBar();
}
if (cont)
{
List submittedFiles = new List(sr.Files.Select(srec => srec.File));
Utils.LogFiles(submittedFiles, "Submitting {0}");
if (sr.Files.Count != changeList.Files.Count)
{
Debug.LogWarning("P4Connect - Not All files were submitted");
}
}
else
{
EditorUtility.DisplayDialog("Cannot submit files...", "Submit failed3, You may need to use P4V to resolve conflicts.", "Ok");
}
// Notify that things changed either way
if (OnOperationPerformed != null)
{
OnOperationPerformed(aConnection, aFiles.ToAssetPaths(), AssetOperation.Submit);
}
}
else
{
EditorUtility.DisplayDialog("Cannot submit files...", "Submit failed4, open P4V and make sure your files are in the default changelist", "Ok");
}
}
else
{
StringBuilder builder = new StringBuilder();
builder.AppendLine("The following files are locked by someone else and cannot be submitted.");
foreach (var file in lockedFiles)
{
builder.Append("\t" + file.LocalPath.Path);
}
EditorUtility.DisplayDialog("Cannot submit locked files...", builder.ToString(), "Ok");
}
}
///
/// Attempts to perform the operation immediately
///
static List PerformOperation(string[] arPaths, string[] arMoveToPaths, AssetOperation aDesiredOp)
{
#if DEBUG
log.DebugFormat("op: {0}, paths: {1} moveto: {2}",
aDesiredOp.ToString(), Logger.StringArrayToString(arPaths), Logger.StringArrayToString(arMoveToPaths));
#endif
List result = EmptyFileAndMeta;
IEnumerable resFspecs = null;
if (result == null)
log.Error("result is null!");
if (Config.ValidConfiguration)
{
try
{
// The using statement will make sure the connection is Disposed (i.e. closed)
using (PerforceConnection connection = new PerforceConnection())
{
List filesAndOps = AddToFilesAndOpList(connection, arPaths, arMoveToPaths, aDesiredOp);
FileListOperations operations = new FileListOperations();
AddToOperations(filesAndOps, operations);
List fspecs = operations.Run(connection);
// var not_needed = fspecs.JustLocalSpecs();
// var needed = fspecs.NonLocalSpecs().UnversionedSpecs();
// var local_fix = needed.FixLocalPaths(connection);
// var res = not_needed.Union(local_fix);
resFspecs = fspecs.JustLocalSpecs().Union(fspecs.NonLocalSpecs().UnversionedSpecs().FixLocalPaths(connection));
#if DEBUG
//log.Debug("not_needed: " + Logger.FileSpecListToString(not_needed.ToList()));
// log.Debug("needed: " + Logger.FileSpecListToString(needed.ToList()));
// log.Debug("fixed: " + Logger.FileSpecListToString(local_fix.ToList()));
//log.Debug("all: " + Logger.FileSpecListToString(res.ToList()));
//log.Debug("allfam: " + Logger.FileAndMetaListToString(result));
#endif
// Trigger events
if (OnOperationPerformed != null && resFspecs.Any())
{
OnOperationPerformed(connection, resFspecs.ToAssetPaths(), aDesiredOp);
}
}
}
catch (Exception ex)
{
EditorUtility.ClearProgressBar();
log.Error("... Exception ", ex);
LogP4Exception(ex);
}
}
result = resFspecs.ToFileAndMetas().ToList();
// Any result returned from an operation may have it's status changed
// so we should re-queue it
AssetStatusCache.MarkAsDirty(result);
#if DEBUG
log.DebugFormat("results: {0}", Logger.FileAndMetaListToString(result));
#endif
return result;
}
///
/// Given a list of asset file paths and operation, fills a list of perforce files and operation for each
///
/// The perforce connection to use to query current state of files on the depot
/// The list of files (these can be a mix and match of .meta files and regular files)
/// Where moved files are going
/// The operation to perform
/// INOUT: The list of perforce files to fill out
static void AddToFilesAndOpList(PerforceConnection aConnection, string[] arAssetPaths, string[] arMoveToAssetPaths, AssetOperation aDesiredOperation, List aInOutFilesAndOp)
{
// Build the list of files and metas
List fileSpecs = new List(); // there may be null values in here
List metaSpecs = new List(); // there may be null values in here
List moveToFileSpecs = new List(); // there may be null values in here
List moveToMetaSpecs = new List(); // there may be null values in here
List areFolders = new List();
#if DEBUG
log.DebugFormat("op: {0} paths: {1} to: {2} inout: {3}",
aDesiredOperation.ToString(),
Logger.StringArrayToString(arAssetPaths),
Logger.StringArrayToString(arMoveToAssetPaths),
Logger.FilesAndOpListToString(aInOutFilesAndOp));
#endif
log.Debug("arAssetPaths: " + Logger.StringArrayToString(arAssetPaths));
//log.Debug("arAssetPathsNull:" + Logger.StringArrayToString(arAssetPaths.NonNullElements().ToArray()));
// Set up the arrays needed to generate the operations
for (int i = 0; i < arAssetPaths.Length; ++i)
{
string FileName = "";
string MetaName = "";
// Gets local paths for Asset and Meta
Utils.GetFileAndMeta(arAssetPaths[i], out FileName, out MetaName);
string MoveToFileName = "";
string MoveToMetaName = "";
if (arMoveToAssetPaths != null)
{
// Gets local paths for both Asset and Meta
Utils.GetFileAndMeta(arMoveToAssetPaths[i], out MoveToFileName, out MoveToMetaName);
}
// If the operation hasn't happened yet, we can be more strict with our verifications
bool isFolder = Utils.IsDirectory(Utils.LocalPathToAssetPath(FileName));
areFolders.Add(isFolder);
bool isMeta = Utils.IsMetaFile(arAssetPaths[i]);
// Escape filenames
string escapedFileName = FileName;
string escapedMetaName = MetaName;
string escapedMoveToFileName = MoveToFileName;
string escapedMoveToMetaName = MoveToMetaName;
if (isFolder)
{
// Add special spec for "all subdirs"
fileSpecs.Add(FileSpec.LocalSpec(escapedFileName));
if (MoveToFileName != "")
moveToFileSpecs.Add(FileSpec.LocalSpec(System.IO.Path.Combine(escapedMoveToFileName, "...")));
else
moveToFileSpecs.Add(null);
// All Folders should have metas
escapedMetaName = Utils.RemoveDirectoryWildcards(escapedMetaName); // Just the folder name, thank you.
metaSpecs.Add(FileSpec.LocalSpec(escapedMetaName));
if (escapedMoveToMetaName != "")
moveToMetaSpecs.Add(FileSpec.LocalSpec(escapedMoveToMetaName));
else
moveToMetaSpecs.Add(null);
}
else if (!isMeta) // Asset File
{
fileSpecs.Add(FileSpec.LocalSpec(escapedFileName));
if (escapedMoveToFileName != "")
moveToFileSpecs.Add(FileSpec.LocalSpec(escapedMoveToFileName));
else
moveToFileSpecs.Add(null);
if (ShouldFileHaveMetaFile(FileName)) // this includes folders
{
metaSpecs.Add(FileSpec.LocalSpec(escapedMetaName));
if (escapedMoveToMetaName != "")
moveToMetaSpecs.Add(FileSpec.LocalSpec(escapedMoveToMetaName));
else
moveToMetaSpecs.Add(null);
}
else
{
metaSpecs.Add(null);
moveToMetaSpecs.Add(null);
}
}
else // Must be Meta file
{
metaSpecs.Add(FileSpec.LocalSpec(escapedMetaName));
if (escapedMoveToMetaName != "")
moveToMetaSpecs.Add(FileSpec.LocalSpec(escapedMoveToMetaName));
else
moveToMetaSpecs.Add(null);
// Since the non-meta file was not found, add it anyway
fileSpecs.Add(FileSpec.LocalSpec(escapedFileName));
if (escapedMoveToFileName != "")
moveToFileSpecs.Add(FileSpec.LocalSpec(escapedMoveToFileName));
else
moveToFileSpecs.Add(null);
}
}
if (arMoveToAssetPaths != null)
{
log.Debug("moveto: " + Logger.StringArrayToString(arMoveToAssetPaths.NonNullElements().ToArray()));
}
// log.Debug("allFiles: " + Logger.StringArrayToString(allFiles.ToArray()));
//Get a Hashset of all the files we need to query
HashSet statusFiles = new HashSet(fileSpecs.NonNullElements().ToLocalPaths());
statusFiles.UnionWith(moveToFileSpecs.NonNullElements().ToLocalPaths());
statusFiles.UnionWith(metaSpecs.NonNullElements().ToLocalPaths());
statusFiles.UnionWith(moveToMetaSpecs.NonNullElements().ToLocalPaths());
// This routine will check for valid asset statuses (and associated FileMetaData) for each file in the parameters.
// If needed, the server will be queried
//var stats = AssetStatusCache.GetAssetStatusesFromPaths(aConnection, statusFiles.ToList());
// var stats = statusFiles.GetCachedAssetStatuses().ToArray();
#if DEBUG
log.Debug("assets: " + Logger.FileSpecListToString(fileSpecs));
log.Debug("metas: " + Logger.FileSpecListToString(metaSpecs));
#endif
// Now create the file operations
for (int i = 0; i < arAssetPaths.Length; ++i)
{
// Build the FileAndMeta data
FilesAndOp fileAndOp = new FilesAndOp();
fileAndOp.File = fileSpecs[i];
fileAndOp.MoveToFile = moveToFileSpecs[i];
#if DEBUG
//log.DebugFormat("processing files: {0} and {1}", fileSpecs[i].ToStringNullSafe(), metaSpecs[i].ToStringNullSafe());
#endif
FileMetaData filemd = AssetStatusCache.GetCachedMetaData(fileSpecs[i]);
//log.Debug("filemd: " + Logger.FileMetaDataToString(filemd));
fileAndOp.FileOp = GetFileOperation(fileSpecs[i].ToAssetPath(aConnection), aDesiredOperation, filemd);
FileOperation metaOp = FileOperation.None;
// Some files don't have associated metas
if (metaSpecs[i] != null)
{
FileMetaData metamd = AssetStatusCache.GetCachedMetaData(metaSpecs[i]);
//log.Debug("metamd: " + Logger.FileMetaDataToString(metamd));
metaOp = GetFileOperation(metaSpecs[i].ToAssetPath(aConnection), aDesiredOperation, metamd);
}
if (metaOp == FileOperation.None) // No Meta
{
fileAndOp.Meta = null;
fileAndOp.MoveToMeta = null;
}
else if (metaOp == fileAndOp.FileOp && (!areFolders[i] || fileAndOp.FileOp != FileOperation.Add))
{
// Lump it in with the fileOp
fileAndOp.Meta = metaSpecs[i];
fileAndOp.MoveToMeta = moveToMetaSpecs[i];
}
else
{
// Treat the meta separately
FilesAndOp metaAndOp = new FilesAndOp();
metaAndOp.FileOp = metaOp;
metaAndOp.Meta = metaSpecs[i];
metaAndOp.MoveToMeta = moveToMetaSpecs[i];
aInOutFilesAndOp.Add(metaAndOp);
}
// Add the operation
if (!areFolders[i] || fileAndOp.FileOp != FileOperation.Add)
{
aInOutFilesAndOp.Add(fileAndOp);
}
}
#if DEBUG
int n = 1;
foreach (var fao in aInOutFilesAndOp)
{
log.DebugFormat("fao {0}: {1}", n.ToString(), Logger.FileAndOpToString(fao));
n++;
}
#endif
}
static public List AddToFilesAndOpList(PerforceConnection aConnection, string[] arAssetPaths, string[] arMoveToAssetPaths, AssetOperation aDesiredOperation)
{
#if false
log.DebugFormat("op: {0} paths: {1} to: {2}",
aDesiredOperation.ToString(),
Logger.StringArrayToString(arAssetPaths),
Logger.StringArrayToString(arMoveToAssetPaths));
#endif
Collector collector = new Collector(aDesiredOperation);
for(int i = 0; i < arAssetPaths.Length; i++)
{
string path = arAssetPaths[i];
string move = arMoveToAssetPaths == null ? null : arMoveToAssetPaths[i];
collector.Add(path, move);
}
return collector.GetFilesAndOps(aConnection);
}
///
/// Collects files needed for the operation
///
///
public class Collector
{
private static readonly ILog log = LogManager.GetLogger(typeof(Collector));
AssetOperation operation;
///
/// Stores matched Files, Metas, and possible Move locations and folder state.
///
public class FaoEntry
{
public string File;
public string Meta;
public string Mfile;
public string Mmeta;
public bool IsFolder;
public FaoEntry()
{
File = Meta = Mfile = Mmeta = null;
IsFolder = false;
}
///
/// Return a collection of all the non-null paths
///
///
public IEnumerable paths()
{
if (! IsFolder && this.File != null)
yield return this.File;
if (this.Meta != null)
yield return this.Meta;
if (! IsFolder && this.Mfile != null)
yield return this.Mfile;
if (this.Mmeta != null)
yield return this.Mmeta;
}
}
public Dictionary entryDict;
public Collector(AssetOperation op)
{
operation = op;
entryDict = new Dictionary();
}
///
/// Add an asset with it's moveto location
///
///
///
public void Add(string assetPath, string moveToAssetPath)
{
//log.Debug("add: file: " + assetPath.ToStringNullSafe() + " mfile: " + moveToAssetPath.ToStringNullSafe());
FaoEntry fao = new FaoEntry();
string fileName;
string metaName;
Utils.GetFileAndMeta(assetPath, out fileName, out metaName);
if (Utils.IsDirectory(assetPath))
{
if (!assetPath.Equals("...")) // project root isn't considered a folder and doesn't need a meta
fao.IsFolder = true;
assetPath = Utils.AddDirectoryWildcards(assetPath);
fao.File = assetPath;
fao.Mfile = moveToAssetPath == null ? null : Utils.AddDirectoryWildcards(moveToAssetPath);
}
else if (Utils.IsMetaFile(assetPath))
{
fao.Meta = assetPath;
fao.Mmeta = moveToAssetPath;
}
else
{
fao.File = assetPath;
fao.Mfile = moveToAssetPath;
}
// Look it up
FaoEntry entry;
if (entryDict.TryGetValue(fileName, out entry))
{
// Update the entry in the dictionary
if (entry.File == null && fao.File != null)
{
entry.File = fao.File;
}
if (entry.Mfile == null && fao.Mfile != null)
{
entry.Mfile = fao.Mfile;
}
if (entry.Meta == null && fao.Meta != null)
{
entry.Meta = fao.Meta;
}
if (entry.Mmeta == null && fao.Mmeta != null)
{
entry.Mmeta = fao.Mmeta;
}
}
else // Create a new entry in the dictionary
{
entryDict[fileName] = fao;
}
}
///
/// Return the list of FilesAndOp to be executed by the engine
///
///
public List GetFilesAndOps(PerforceConnection aConnection)
{
List results = new List();
HashSet assets = new HashSet();
// Check to see if any metas are missing
foreach(FaoEntry entry in entryDict.Values)
{
// add a meta file if needed
if (entry.File != null && entry.Meta == null && (entry.IsFolder || ShouldFileHaveMetaFile(entry.File)))
{
entry.Meta = Utils.GetMetaFile(Utils.RemoveDirectoryWildcards(entry.File));
}
// add a move to meta file if needed
if (entry.Mfile != null && entry.Mmeta == null && (entry.IsFolder || ShouldFileHaveMetaFile(entry.Mfile)))
{
entry.Mmeta = Utils.GetMetaFile(Utils.RemoveDirectoryWildcards(entry.Mfile));
}
// Collect all paths into the "assets" collection
assets.UnionWith(entry.paths());
}
//List tostat = assets.StripDirectories().ToList();
// This routine will check for valid asset statuses (and associated FileMetaData) for each file in the parameters.
// If needed, the server will be queried
//var stats = AssetStatusCache.GetAssetStatusesFromPaths(aConnection, tostat);
#if false
//log.Debug("stats: " + Logger.AssetStatusListToString(stats));
#endif
// Compute the operation for each entry
foreach (FaoEntry entry in entryDict.Values)
{
FileOperation op = FileOperation.None;
FileOperation metaop = FileOperation.None;
if (entry.File != null)
{
FileMetaData filemd = AssetStatusCache.GetCachedMetaData(entry.File);
//log.Debug("filemd: " + Logger.FileMetaDataToString(filemd));
op = GetFileOperation(entry.File, operation, filemd);
}
if (entry.Meta != null)
{
FileMetaData metamd = AssetStatusCache.GetCachedMetaData(entry.Meta);
//log.Debug("metamd: " + Logger.FileMetaDataToString(metamd));
metaop = GetFileOperation(entry.Meta, operation, metamd);
}
if (op == metaop)
{
// One record sharing an op
results.Add(new FilesAndOp(entry.File, entry.Meta, entry.Mfile, entry.Mmeta, op));
}
else
{
// file and meta file have different ops, so they need different entries
if (entry.File != null)
{
results.Add(new FilesAndOp(entry.File, null, entry.Mfile, null, op));
}
if (entry.Meta != null)
{
results.Add(new FilesAndOp(null, entry.Meta, null , entry.Mmeta, metaop));
}
}
}
results = results.RemoveNoOps().ToList();
#if false
int n = 1;
foreach (var fao in results)
{
log.DebugFormat("fao {0}: {1}", n.ToString(), Logger.FileAndOpToString(fao));
n++;
}
#endif
return results;
}
} // Collector Class
///
/// Adds the list of perforce files (and associated .meta if applicable) to the list of files/operations
///
static void AddToOperations(List aFileAndMetas, FileListOperations aInOutFileOperations)
{
foreach (FilesAndOp fileAndMeta in aFileAndMetas)
{
aInOutFileOperations.Add(fileAndMeta);
}
}
///
/// Parses a metadata file action and translates it into a state for P4connect
///
public static FileState ParseFileAction(FileAction aAction)
{
FileState outOp = FileState.None;
switch (aAction)
{
case FileAction.None:
outOp = FileState.None;
break;
case FileAction.Add:
outOp = FileState.MarkedForAdd;
break;
case FileAction.Delete:
outOp = FileState.MarkedForDelete;
break;
case FileAction.Edit:
outOp = FileState.MarkedForEdit;
break;
case FileAction.MoveAdd:
outOp = FileState.MarkedForAddMove;
break;
case FileAction.MoveDelete:
outOp = FileState.MarkedForDeleteMove;
break;
default:
//We don't really handle anything else
outOp = FileState.InDepot;
break;
}
return outOp;
}
///
/// Gets the default file operation, based on the asset operation
///
static FileOperation GetDefaultFileOperation(AssetOperation aDesiredOperation)
{
FileOperation outOp = FileOperation.None;
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
outOp = FileOperation.Add;
break;
case AssetOperation.Remove:
outOp = FileOperation.Delete;
break;
case AssetOperation.Checkout:
outOp = FileOperation.Checkout;
break;
case AssetOperation.Move:
outOp = FileOperation.Move;
break;
case AssetOperation.Revert:
outOp = FileOperation.Revert;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.RevertIfUnchanged;
break;
case AssetOperation.GetLatest:
outOp = FileOperation.GetLatest;
break;
case AssetOperation.ForceGetLatest:
outOp = FileOperation.ForceGetLatest;
break;
case AssetOperation.Lock:
outOp = FileOperation.Lock;
break;
case AssetOperation.Unlock:
outOp = FileOperation.Unlock;
break;
}
return outOp;
}
///
/// Determines what Perforce operation to perform on the passed in file, given what the user wants to do and what the current state of the file
///
/// FileSpec for file
/// Desired Perforce operation
/// File MetaData from fstat command
/// File Operation to use
static FileOperation GetFileOperation(string aFile, AssetOperation aDesiredOperation, FileMetaData fmd)
{
FileState aOpenAction = FileState.IsDirectory;
FileState aHeadAction = FileState.None;
LockState aCurrentLockState = LockState.None;
if (! Utils.IsDirectory(aFile))
{
aOpenAction = GetOpenAction(fmd);
aHeadAction = GetHeadAction(fmd);
aCurrentLockState = GetLockState(fmd);
}
FileOperation outOp = FileOperation.None;
#if false
log.DebugFormat("desired: {0} file: {1} state: {2} lock: {3} ",
aDesiredOperation.ToString(),
aFile == null ? "null" : aFile.ToString(),
aCurrentAction.ToString(), aCurrentLockState);
#endif
// Mark Files that are in the depot
if (aOpenAction == FileState.None && (fmd.IsMapped || fmd.HeadAction != FileAction.None))
aOpenAction = FileState.InDepot;
// Mark Files that are deleted in their latest revision
if (aOpenAction == FileState.InDepot && (fmd.HeadAction == FileAction.Delete || fmd.HeadAction == FileAction.MoveDelete))
aOpenAction = FileState.InDepotDeleted;
switch (aOpenAction)
{
case FileState.None:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
outOp = FileOperation.Add;
break;
case AssetOperation.Remove:
outOp = FileOperation.None;
break;
case AssetOperation.Checkout:
// trying to checkout something not in the depot,
Utils.LogFileWarning(aFile, " isn't in the depot and cannot be checked out, the file will be marked for add instead");
outOp = FileOperation.Add;
break;
case AssetOperation.Move:
// trying to move something not in the depot, add it instead
Utils.LogFileWarning(aFile, " isn't in the depot and cannot be moved, the file will be marked for add instead");
outOp = FileOperation.Add;
break;
case AssetOperation.Revert:
// trying to revert something not in the depot
Utils.LogFileWarning(aFile, " isn't in the depot and cannot be reverted");
outOp = FileOperation.None;
break;
case AssetOperation.RevertIfUnchanged:
// trying to revert something not in the depot
Utils.LogFileWarning(aFile, " isn't in the depot and cannot be reverted");
outOp = FileOperation.None;
break;
case AssetOperation.GetLatest:
// trying to sync something not in the depot
// Utils.LogFileWarning(aFile, " isn't in the depot and cannot be synced");
outOp = FileOperation.None;
break;
case AssetOperation.ForceGetLatest:
// trying to revert something not in the depot
// Utils.LogFileWarning(aFile, " isn't in the depot and cannot be synced");
outOp = FileOperation.None;
break;
case AssetOperation.Lock:
// trying to lock something not in the depot
Utils.LogFileWarning(aFile, " isn't in the depot and cannot be locked");
outOp = FileOperation.None;
break;
case AssetOperation.Unlock:
// trying to unlock something not in the depot
Utils.LogFileWarning(aFile, " isn't in the depot and cannot be unlocked");
outOp = FileOperation.None;
break;
}
break;
case FileState.InDepot:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add: // The file is already in the depot, Just Check it out
outOp = FileOperation.Checkout;
break;
case AssetOperation.Remove:
outOp = FileOperation.Delete;
break;
case AssetOperation.Checkout:
outOp = FileOperation.Checkout;
break;
case AssetOperation.Move:
outOp = FileOperation.Move;
break;
case AssetOperation.Revert:
outOp = FileOperation.None;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.None;
break;
case AssetOperation.GetLatest:
outOp = FileOperation.GetLatest;
break;
case AssetOperation.ForceGetLatest:
outOp = FileOperation.ForceGetLatest;
break;
case AssetOperation.Lock:
outOp = GetLockOperation(aFile, aCurrentLockState);
break;
case AssetOperation.Unlock:
outOp = GetUnlockOperation(aFile, aCurrentLockState);
break;
}
break;
case FileState.InDepotDeleted:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
outOp = FileOperation.Add;
break;
case AssetOperation.Remove:
outOp = FileOperation.None;
break;
case AssetOperation.Checkout:
outOp = FileOperation.Checkout;
break;
case AssetOperation.Move:
outOp = FileOperation.Move;
break;
case AssetOperation.Revert:
outOp = FileOperation.None;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.None;
break;
case AssetOperation.GetLatest:
outOp = FileOperation.GetLatest;
break;
case AssetOperation.ForceGetLatest:
outOp = FileOperation.ForceGetLatest;
break;
case AssetOperation.Lock:
outOp = GetLockOperation(aFile, aCurrentLockState);
break;
case AssetOperation.Unlock:
outOp = GetUnlockOperation(aFile, aCurrentLockState);
break;
}
break;
case FileState.MarkedForEdit:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
// Already in the depot and checked out, do nothing
outOp = FileOperation.None;
break;
case AssetOperation.Remove:
outOp = FileOperation.RevertAndDelete;
break;
case AssetOperation.Checkout:
outOp = FileOperation.None;
break;
case AssetOperation.Move:
// Already in the depot and checked out, just flag it for a move
outOp = FileOperation.MoveToNewLocation;
break;
case AssetOperation.Revert:
outOp = FileOperation.Revert;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.RevertIfUnchanged;
break;
case AssetOperation.GetLatest:
outOp = FileOperation.GetLatest;
break;
case AssetOperation.ForceGetLatest:
outOp = FileOperation.ForceGetLatest;
break;
case AssetOperation.Lock:
outOp = GetLockOperation(aFile, aCurrentLockState);
break;
case AssetOperation.Unlock:
outOp = GetUnlockOperation(aFile, aCurrentLockState);
break;
}
break;
case FileState.MarkedForAdd:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
outOp = FileOperation.None;
break;
case AssetOperation.Remove:
outOp = FileOperation.Revert;
break;
case AssetOperation.Checkout:
// Already marked for add
outOp = FileOperation.None;
break;
case AssetOperation.Move:
outOp = FileOperation.RevertAndAddNoOverwrite;
break;
case AssetOperation.Revert:
outOp = FileOperation.Revert;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.RevertIfUnchanged;
break;
case AssetOperation.GetLatest:
Utils.LogFileWarning(aFile, " is marked as add and cannot be synced, the file will be skipped");
outOp = FileOperation.None;
break;
case AssetOperation.ForceGetLatest:
Utils.LogFileWarning(aFile, " is marked as add and cannot be synced, the file will be skipped");
outOp = FileOperation.None;
break;
case AssetOperation.Lock:
outOp = GetLockOperation(aFile, aCurrentLockState);
break;
case AssetOperation.Unlock:
outOp = GetUnlockOperation(aFile, aCurrentLockState);
break;
}
break;
case FileState.MarkedForDelete:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
outOp = FileOperation.RevertAndCheckoutNoOverwrite;
break;
case AssetOperation.Remove:
outOp = FileOperation.None;
break;
case AssetOperation.Checkout:
outOp = FileOperation.None;
break;
case AssetOperation.Move:
outOp = FileOperation.RevertAndMove;
break;
case AssetOperation.Revert:
outOp = FileOperation.Revert;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.None;
break;
case AssetOperation.GetLatest:
Utils.LogFileWarning(aFile, " is marked for delete, the file will be skipped");
outOp = FileOperation.None;
break;
case AssetOperation.ForceGetLatest:
Utils.LogFileWarning(aFile, " is marked for delete, the file will be reverted and then synced");
outOp = FileOperation.RevertAndGetLatest;
break;
case AssetOperation.Lock:
outOp = GetLockOperation(aFile, aCurrentLockState);
break;
case AssetOperation.Unlock:
outOp = GetUnlockOperation(aFile, aCurrentLockState);
break;
}
break;
case FileState.MarkedForAddMove:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
outOp = FileOperation.None;
break;
case AssetOperation.Remove:
outOp = FileOperation.RevertAndDelete;
break;
case AssetOperation.Checkout:
outOp = FileOperation.None;
break;
case AssetOperation.Move:
outOp = FileOperation.MoveToNewLocation;
break;
case AssetOperation.Revert:
outOp = FileOperation.Revert;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.None;
break;
case AssetOperation.GetLatest:
Utils.LogFileWarning(aFile, " is marked for move/add, the file will be skipped");
outOp = FileOperation.None;
break;
case AssetOperation.ForceGetLatest:
Utils.LogFileWarning(aFile, " is marked for move/add, the file will be reverted and then synced");
outOp = FileOperation.RevertAndGetLatest;
break;
case AssetOperation.Lock:
outOp = GetLockOperation(aFile, aCurrentLockState);
break;
case AssetOperation.Unlock:
outOp = GetUnlockOperation(aFile, aCurrentLockState);
break;
}
break;
case FileState.MarkedForDeleteMove:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
outOp = FileOperation.RevertAndCheckoutNoOverwrite;
break;
case AssetOperation.Remove:
outOp = FileOperation.None;
break;
case AssetOperation.Checkout:
outOp = FileOperation.None;
break;
case AssetOperation.Move:
outOp = FileOperation.RevertAndMoveNoOverwrite;
break;
case AssetOperation.Revert:
outOp = FileOperation.Revert;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.None;
break;
case AssetOperation.GetLatest:
Utils.LogFileWarning(aFile, " is marked for move/delete, the file will be skipped");
outOp = FileOperation.None;
break;
case AssetOperation.ForceGetLatest:
Utils.LogFileWarning(aFile, " is marked for move/delete, the file will be reverted and then synced");
outOp = FileOperation.RevertAndGetLatest;
break;
case AssetOperation.Lock:
outOp = GetLockOperation(aFile, aCurrentLockState);
break;
case AssetOperation.Unlock:
outOp = GetUnlockOperation(aFile, aCurrentLockState);
break;
}
break;
case FileState.IsDirectory:
switch (aDesiredOperation)
{
case AssetOperation.None:
outOp = FileOperation.None;
break;
case AssetOperation.Add:
outOp = FileOperation.Add;
break;
case AssetOperation.Remove:
outOp = FileOperation.Delete;
break;
case AssetOperation.Checkout:
outOp = FileOperation.Checkout;
break;
case AssetOperation.Move:
outOp = FileOperation.Move;
break;
case AssetOperation.Revert:
outOp = FileOperation.Revert;
break;
case AssetOperation.RevertIfUnchanged:
outOp = FileOperation.RevertIfUnchanged;
break;
case AssetOperation.GetLatest:
outOp = FileOperation.GetLatest;
break;
case AssetOperation.ForceGetLatest:
outOp = FileOperation.ForceGetLatest;
break;
case AssetOperation.Lock:
outOp = GetLockOperation(aFile, aCurrentLockState);
break;
case AssetOperation.Unlock:
outOp = GetUnlockOperation(aFile, aCurrentLockState);
break;
}
break;
}
#if false
log.DebugFormat("OP: {0} <= desired: {1} file: {2} state: {3} lock: {4} ",
outOp.ToString(),
aDesiredOperation.ToString(), aFile.ToString(), aOpenAction.ToString(), aCurrentLockState);
log.DebugFormat("returns: {0}", outOp.ToString());
#endif
return outOp;
}
static FileOperation GetLockOperation(string aFile, LockState aCurrentLockState)
{
if (aCurrentLockState == LockState.None || Utils.IsDirectory(aFile))
return FileOperation.Lock;
else
{
if (aCurrentLockState == LockState.OurLock)
{
Utils.LogFileWarning(aFile, " is already locked, the file will be skipped");
}
else
{
Utils.LogFileWarning(aFile, " is locked by someone else, the file will be skipped");
}
return FileOperation.None;
}
}
static FileOperation GetUnlockOperation(string aFile, LockState aCurrentLockState)
{
if (aCurrentLockState == LockState.OurLock || Utils.IsDirectory(aFile))
return FileOperation.Unlock;
else
{
if (aCurrentLockState == LockState.TheirLock)
{
Utils.LogFileWarning(aFile, " is not locked by you, the file will be skipped");
}
else
{
Utils.LogFileWarning(aFile, " is not locked, the file will be skipped");
}
return FileOperation.None;
}
}
///
/// Logs a P4 exception and display an accompanying message
///
static void LogP4Exception(Exception ex)
{
Debug.LogException(ex);
Debug.LogWarning("P4Connect - P4Connect encountered the following internal exception, your file changes may not be properly checked out/added/deleted.");
// Parse exception text for dialog box
EditorUtility.DisplayDialog("Perforce Exception", "P4Connect encountered the following exception:\n\n" + ex.Message, "Ok");
}
///
/// Helper method that filters files that shouldn't have a .meta file associated with them
///
///
///
static bool ShouldFileHaveMetaFile(string arPath)
{
bool bret = true;
// Maybe this is a project settings file, those don't seem to have .meta files with them
if (arPath.Contains("ProjectSettings"))
bret = false;
if (arPath.Contains("DotSettings"))
bret = false;
if (arPath.Contains(".unityproj"))
bret = false;
if (arPath.Contains(".csproj"))
bret = false;
if (arPath.Contains(".sln"))
bret = false;
if (arPath.EndsWith(".meta"))
bret = false;
if (arPath == "Assets")
bret = false;
if (arPath == "")
bret = false;
if (arPath == "...")
bret = false;
return bret;
}
///
/// Utility method that adds all non null files and meta files from a list of pairs
/// Also removes paths missing filenames.
///
static IEnumerable NonNullFilesAndMeta(IEnumerable aFilesAndMeta)
{
foreach (var fileAndMeta in aFilesAndMeta)
{
if (fileAndMeta.File != null)
{
var assetName = System.IO.Path.GetFileNameWithoutExtension(fileAndMeta.File.ToAssetPath());
if (!string.IsNullOrEmpty(assetName))
yield return fileAndMeta.File;
else
{
Debug.Log("filtered out: " + fileAndMeta.File.ToAssetPath());
}
}
if (fileAndMeta.Meta != null)
{
var metaName = System.IO.Path.GetFileNameWithoutExtension(fileAndMeta.Meta.ToAssetPath());
if (!string.IsNullOrEmpty(metaName))
yield return fileAndMeta.Meta;
else
{
Debug.Log("filtered out: " + fileAndMeta.Meta.ToAssetPath());
}
}
}
}
public enum WildcardCheck
{
None, // No wildcards detected
Force, // Wildcards detected but user agreed to force the operation
Cancel, // Wildcards detected and user canceled
}
///
/// Checks for wildcards and asks the user whether they want to force the operation
///
public static WildcardCheck VerifyWildcards(IEnumerable aFiles)
{
var allFiles = NonNullFilesAndMeta(aFiles);
List filesWithWildCards = new List();
foreach (var file in allFiles)
{
string filename = Utils.GetFilename(file, false);
if (filename.IndexOfAny(new char[] { '*', '%', '#', '@' }) != -1)
{
filesWithWildCards.Add(filename);
}
}
bool hasWildcards = filesWithWildCards.Count > 0;
if (hasWildcards)
{
if (Config.WarnOnSpecialCharacters)
{
StringBuilder builder = new StringBuilder();
builder.AppendLine("The following files have special characters in them, are you sure you want to add them to the depot?");
foreach (string file in filesWithWildCards)
{
builder.AppendLine("\t" + file);
}
builder.AppendLine("Note: You can disable this warning in the Perforce Settings");
if (EditorUtility.DisplayDialog("Special Characters", builder.ToString(), "Ok", "Cancel"))
{
return WildcardCheck.Force;
}
else
{
return WildcardCheck.Cancel;
}
}
else
{
return WildcardCheck.Force;
}
}
else
{
return WildcardCheck.None;
}
}
///
/// Performs a Get-Latest operation, and then checks to see if there are folders that need to be deleted or conflicts
///
static IList PerformGetLastestAndPostChecks(PerforceConnection aConnection, List aOriginals, Options aOptions)
{
var result = aConnection.P4Client.SyncFiles(aOriginals, aOptions);
if (result != null)
{
Utils.LogFiles(result, "Syncing {0}");
List allFolderDepotMetaFiles = new List();
foreach (FileSpec spec in result)
{
string depotPath = spec.DepotPath.Path;
if (depotPath.EndsWith(".meta"))
{
string assetPath = Utils.AssetFromMeta(depotPath);
if (Utils.IsDirectory(Utils.LocalPathToAssetPath(Utils.AssetFromMeta(spec.LocalPath.Path))))
{
// We found the meta of a folder, add it
allFolderDepotMetaFiles.Add(FileSpec.DepotSpec(depotPath));
}
}
}
// Get the folder meta data
if (allFolderDepotMetaFiles.Count > 0)
{
IList allFolderMetaMeta = GetFileMetaData(aConnection, allFolderDepotMetaFiles, null);
if (allFolderMetaMeta != null)
{
for (int i = 0; i < allFolderMetaMeta.Count; ++i)
{
// Check to see if the file has been deleted
if ((allFolderMetaMeta[i].HeadAction == FileAction.Delete || allFolderMetaMeta[i].HeadAction == FileAction.MoveDelete))
{
// Delete the local folder
string fullpath = Utils.DepotPathToFullPath(aConnection, new FileSpec(allFolderMetaMeta[i]));
string folderPath = Utils.AssetFromMeta(fullpath);
if (System.IO.Directory.Exists(folderPath))
{
// Check that the folder is empty of files
var files = System.IO.Directory.GetFiles(folderPath, "*.*", System.IO.SearchOption.AllDirectories);
if (files == null || files.Length == 0)
{
try
{
System.IO.Directory.Delete(folderPath, true);
}
catch (System.IO.DirectoryNotFoundException)
{
// Ignore missing folders, they may have already been deleted
}
}
else
{
Debug.LogWarning("P4Connect - " + Utils.FullPathToAssetPath(fullpath) + " was deleted, but the matching directory still contains undeleted files and so was kept");
}
}
}
}
}
}
// Check for conflicts by getting the state of original files
List originalsAgain = new List();
foreach (FileSpec spec in aOriginals)
{
originalsAgain.Add(FileSpec.LocalSpec(spec.LocalPath.Path));
}
// Get the local meta data
IList allLocalMeta = GetFileMetaData(aConnection, originalsAgain, null);
if (allLocalMeta != null)
{
List unresolvedFiles = new List();
foreach (FileMetaData meta in allLocalMeta)
{
if (meta.Unresolved)
{
unresolvedFiles.Add(meta);
}
}
if (unresolvedFiles.Count > 0)
{
System.Text.StringBuilder builder = new StringBuilder();
builder.AppendLine("The following files have unresolved conflicts as a result of the Get Latest Operation");
foreach (var meta in unresolvedFiles)
{
builder.AppendLine(meta.LocalPath.Path);
}
builder.AppendLine("You should launch P4V and resolve the conflict before continuing your work");
EditorUtility.DisplayDialog("Unresolved Files Detected", builder.ToString(), "Ok");
}
}
}
return result;
}
public static IList GetFileMetaData(PerforceConnection aConnection, Options aOptions, params FileSpec[] aSpecs)
{
if (Config.DisplayP4Timings)
{
DateTime startTimestamp = DateTime.Now;
var ret = aConnection.P4Depot.GetFileMetaData(aOptions, aSpecs);
double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds;
aConnection.AppendTimingInfo("GetFileMetaDataTime " + deltaInnerTime.ToString() + " ms for " + aSpecs.Length + " files (" + (ret != null ? ret.Count : 0).ToString() + " retrieved)");
return ret;
}
else
{
return aConnection.P4Depot.GetFileMetaData(aOptions, aSpecs);
}
}
///
/// Query server to get FileMetaData for each element of the FileSpec list
/// Frequently called in the background
///
/// Connection to Perforce server
/// IList of FileSpec to match
/// p4 command options
/// null or IList of FileMetaData matching the FileSpecs
public static IList GetFileMetaData(PerforceConnection aConnection, IList aSpecs, Options aOptions)
{
IList ret = new List();
if (aSpecs.Any())
{
// Debug.Log("GetFileMetaData: " + Logger.FileSpecListToString(aSpecs));
if (Config.DisplayP4Timings)
{
DateTime startTimestamp = DateTime.Now;
ret = aConnection.P4Depot.GetFileMetaData(aSpecs, aOptions);
double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds;
aConnection.AppendTimingInfo("GetFileMetaDataTime " + deltaInnerTime.ToString() + " ms for " + aSpecs.Count + " files (" + (ret != null ? ret.Count : 0).ToString() + " retrieved)");
}
else
{
ret = aConnection.P4Depot.GetFileMetaData(aSpecs, aOptions);
//log.Debug("metadata: " + Logger.FileMetaDataListToString(ret));
}
}
//Debug.Log("GetFileMetaData Returns: " + Logger.FileMetaDataListToString(ret));
// since we have fresh metadata from the server, we should also update the status cache
if (ret != null)
AssetStatusCache.StorePerforceMetaData(ret);
return ret;
}
///
/// Call "p4 status" or "p4 reconcile" to check if a file needs to be opened
///
///
///
///
public static IList Reconcile(PerforceConnection aConnection, IList aSpecs, Options aOptions)
{
IList ret = new List();
if (aSpecs != null && aSpecs.Count > 0)
{
// Debug.Log("Reconcile: " + Logger.FileSpecListToString(aSpecs));
if (Config.DisplayP4Timings)
{
DateTime startTimestamp = DateTime.Now;
ret = aConnection.P4Depot.DoReconcile(aSpecs, aOptions);
double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds;
aConnection.AppendTimingInfo("ReconcileTime " + deltaInnerTime.ToString() + " ms for " + aSpecs.Count + " files (" + (ret != null ? ret.Count : 0).ToString() + " retrieved)");
}
else
{
ret = aConnection.P4Depot.DoReconcile(aSpecs, aOptions);
}
}
//Debug.Log("Reconcile Returns: " + Logger.ReconcileEntryListToString(ret));
// since we have fresh metadata from the server, we should also update the status cache
//if (ret != null)
// AssetStatusCache.StorePerforceMetaData(ret); // Later
return ret;
}
}
}