using UnityEditor;
using UnityEngine;
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using Perforce.P4;
using log4net;
namespace P4Connect
{
///
/// Simple asset status class
/// Stores the file state (checked out, modified, etc...) and revision state
///
public class AssetStatus
{
public enum LastUpdateType
{
OnServer,
OffServer,
Default
}
public DateTime TimeStamp;
public FileState LocalState;
public FileState OtherState;
public DepotState DepotState;
public RevisionState RevisionState;
public ResolvedState ResolvedState;
public StorageType StorageType;
public LockState LockState;
public LastUpdateType UpdateType;
private static readonly GUILayoutOption _nameWidth = GUILayout.Width(100);
private static GUILayoutOption _valueWidth = GUILayout.Width(120);
// Use this to create an AssetStatus for a local file (perforce would return null from the fstat call)
public AssetStatus()
{
TimeStamp = default(DateTime);
LocalState = FileState.None;
OtherState = FileState.None;
DepotState = DepotState.None;
RevisionState = P4Connect.RevisionState.None;
ResolvedState = P4Connect.ResolvedState.None;
StorageType = StorageType.Text;
LockState = LockState.None;
UpdateType = LastUpdateType.Default;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
if (TimeStamp == default(DateTime))
{
sb.AppendFormat("Dummy AssetStatus");
}
else
{
sb.AppendFormat("TimeStamp: {0} ", TimeStamp);
sb.AppendFormat("UpdateType: {0} ", UpdateType);
sb.AppendFormat("LocalState: {0} ", LocalState);
sb.AppendFormat("OtherState: {0} ", OtherState);
sb.AppendFormat("DepotState: {0} ", DepotState);
sb.AppendFormat("Revision: {0} ", RevisionState);
sb.AppendFormat("Resolved: {0} ", ResolvedState);
sb.AppendFormat("Storage: {0} ", StorageType);
sb.AppendFormat("Lockstate: {0} ", LockState);
}
return (sb.ToString());
}
public bool IsOnServer()
{
return (LocalState != FileState.None && LocalState != FileState.MarkedForAdd);
}
public bool IsAddable()
{
return (LocalState == FileState.None ||
(LocalState == FileState.InDepot && DepotState == DepotState.Deleted));
}
public bool IsEditable()
{
return (LocalState == FileState.InDepot && (DepotState != DepotState.Deleted));
}
public bool IsLocked()
{
return (LockState == LockState.OurLock || LockState == LockState.TheirLock);
}
public bool IsLockable()
{
return (LockState != LockState.TheirLock && IsOpened() && !IsLocked());
}
public bool IsUnlockable()
{
return (LockState == LockState.OurLock && IsOpened());
}
public bool IsOpened()
{
return !(LocalState == FileState.None || LocalState == FileState.InDepot);
}
public bool IsMarked()
{
return (LocalState == FileState.MarkedForAdd || LocalState == FileState.MarkedForAddMove ||
LocalState == FileState.MarkedForDelete || LocalState == FileState.MarkedForDeleteMove ||
LocalState == FileState.MarkedForEdit);
}
public bool IsInitialized()
{
return (this.TimeStamp != default(DateTime));
}
///
/// Display Status in Editor Window
///
public void OnGUI()
{
GUILayout.BeginVertical("box");
GUILayout.BeginHorizontal();
GUILayout.Label("TimeStamp: ", _nameWidth);
GUILayout.Label(TimeStamp.ToString("u"), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("UpdateType: ", _nameWidth);
GUILayout.Label(UpdateType.ToString(), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("LocalState: ", _nameWidth);
GUILayout.Label(LocalState.ToString(), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("OtherState: ", _nameWidth);
GUILayout.Label(OtherState.ToString(), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("DepotState: ", _nameWidth);
GUILayout.Label(DepotState.ToString(), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("RevisionState: ", _nameWidth);
GUILayout.Label(RevisionState.ToString(), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("ResolvedState: ", _nameWidth);
GUILayout.Label(ResolvedState.ToString(), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("StorageType: ", _nameWidth);
GUILayout.Label(StorageType.ToString(), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("LockState: ", _nameWidth);
GUILayout.Label(LockState.ToString(), _valueWidth);
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
}
///
/// Stores and periodically updates the Perforce status of all the assets
///
///
///
/// This class is used to store the cached results for each "node" we keep track of in this project
///
public class AssetEntry
{
public AssetStatus Status;
public FileMetaData Meta;
public DateTime Update;
public ReconcileEntry Reconcile;
public List OpenedList;
public AssetEntry()
{
Status = new AssetStatus();
Meta = new FileMetaData();
Status.TimeStamp = Update = default(DateTime);
OpenedList = new List();
}
public bool Dirty
{
get { return (Update == default(DateTime)); }
set
{
if (value == true)
{
Status.TimeStamp = Update = default(DateTime);
}
}
}
public void AddOpenedFile(File entry)
{
OpenedList.Add(entry);
}
}
public class AssetStatusCache
{
private static readonly ILog log = LogManager.GetLogger(typeof(AssetStatusCache));
private static object _lockUpdated = new object(); // lock object for thread response.
private static bool _queryDone = false;
private static string[] _queryContent;
private static float _pollRate = 10; // every 10 seconds
private static DateTime _lastRefresh;
///
/// This delegate is triggered when the status of an asset changes
///
public delegate void OnAssetStatusChangedDelegate(PerforceConnection aConnection);
public static event OnAssetStatusChangedDelegate OnAssetStatusChanged
{
add
{
if (_OnAssetStatusChanged == null)
{
// We had no reason to get updated before, so do it now
Engine.OnOperationPerformed += OnEngineOperationPerformed;
}
_OnAssetStatusChanged += value;
}
remove
{
_OnAssetStatusChanged -= value;
if (_OnAssetStatusChanged == null)
{
// We no longer have any reason to be updated
Engine.OnOperationPerformed -= OnEngineOperationPerformed;
}
}
}
// The internal event that is actually registered with
static event OnAssetStatusChangedDelegate _OnAssetStatusChanged;
// The caches
static Dictionary _Entries;
// The background fstat thread
private static MetaDataQueue _dataQueue;
public static void Initialize()
{
_Entries = new Dictionary();
PendingDirty = true;
_dataQueue = new MetaDataQueue();
_queryDone = false;
_lastRefresh = DateTime.Now;
EditorApplication.update += OnUpdate;
}
///
/// Run periodically to see if Icons need to be updated in the project windows.
/// CheckForUpdate() gets set to true when the BG thread is done with some fstat fetches.
///
static void OnUpdate()
{
if (Config.ValidConfiguration && Config.DisplayStatusIcons && Config.IsEditing())
{
if (CheckForUpdate())
{
//log.Debug("AssetStatusCache OnUpdate");
// Update ChangeManager with new fstats
ChangeManager.OnUpdatedFiles(LastUpdateStrings());
// Refresh the Project Window
Icons.UpdateDisplay();
ClearUpdate();
}
}
}
///
/// Used by the Background Thread to signal completion of a file status retrieval
///
public static void SignalUpdate(string[] files)
{
lock (_lockUpdated)
{
_queryDone = true;
_queryContent = files;
}
}
///
/// Returns true if a file status update is pending
/// Also resets the query state at the same time.
///
/// true
public static bool CheckForUpdate()
{
bool rv = false;
lock (_lockUpdated)
{
rv = _queryDone;
//if (rv) log.Debug("Signal!");
}
return rv;
}
public static string[] LastUpdateStrings()
{
return _queryContent;
}
public static void ClearUpdate()
{
lock (_lockUpdated)
{
_queryDone = false;
}
}
///
/// Generic method that fetches the cached AssetEntry which includes
/// AssetStatus (checked out, out of date, etc...)
/// FileMetaData (server "fstat" result)
///
public static AssetEntry GetAssetEntry(string aAssetPath)
{
AssetEntry entry;
string path = Utils.GetEffectivePath(aAssetPath);
if (!_Entries.TryGetValue(path, out entry))
{
//log.Debug("Creating DEFAULT: " + path);
entry = new AssetEntry();
_Entries[path] = entry;
}
return entry;
}
///
/// Get a cached asset status, don't go to the server for it.
///
/// Asset Path
/// AssetStatus
public static AssetStatus GetCachedAssetStatus(string aAssetPath)
{
AssetEntry entry = GetAssetEntry(aAssetPath);
return entry.Status;
}
///
/// Get a cached asset metadata, don't go to the server for it.
///
/// Asset Path
/// FileMetaData
public static FileMetaData GetCachedMetaData(string aAssetPath)
{
AssetEntry entry = GetAssetEntry(aAssetPath);
return entry.Meta;
}
///
/// Get a cached asset metadata, don't go to the server for it.
///
/// FileSpec
/// FileMetaData
public static FileMetaData GetCachedMetaData(FileSpec spec)
{
string path = Utils.LocalPathToAssetPath(spec.LocalPath.Path);
AssetEntry entry = GetAssetEntry(path);
return entry.Meta;
}
public static DateTime GetCacheUpdated(string aAssetPath)
{
AssetEntry entry = GetAssetEntry(aAssetPath);
return entry.Update;
}
///
/// Return cached asset status.
/// If it has never come from the server (been initialized) put in a background
/// request to update it.
///
/// Asset path
/// AssetStatus
public static AssetStatus GetCachedAssetStatusAndUpdateDefaults(string aAssetPath)
{
AssetEntry entry = GetAssetEntry(aAssetPath);
if (!entry.Status.IsInitialized())
{
string[] paths = {aAssetPath};
GetFstatsFromServer(paths);
}
return entry.Status;
}
///
/// Return cached asset MetaData.
/// If it has never come from the server (been initialized) put in a background
/// request to update it.
///
/// Asset path
/// FileMetaData
public static FileMetaData GetCachedMetaDataAndUpdateDefaults(string aAssetPath)
{
AssetEntry entry = GetAssetEntry(aAssetPath);
if (!entry.Status.IsInitialized())
{
string[] paths = { aAssetPath };
GetFstatsFromServer(paths);
}
return entry.Meta;
}
// A flag for pending changes to check if something has changedIgnore file name
public static bool PendingDirty { get; set; }
///
/// Given the meta data, update AssetStatus which drives the P4Connect icon presentation
///
public static void AssetStatusFromMetaData(AssetStatus status, FileMetaData aMeta)
{
// Debug.Log("StoreAssetStatus: " + Logger.FileMetaDataToString(aMeta));
if (aMeta == null)
return;
// Fill out the struct
switch (aMeta.Type.BaseType)
{
case BaseFileType.Text:
status.StorageType = StorageType.Text;
break;
case BaseFileType.Binary:
status.StorageType = StorageType.Binary;
break;
default:
status.StorageType = StorageType.Other;
break;
}
status.LocalState = Engine.ParseFileAction(aMeta.Action);
if (status.LocalState == FileState.None)
{
// Check to see if Perforce knows this file so we can override the localState to InDepot.
if (aMeta.IsMapped || aMeta.HeadAction != FileAction.None)
status.LocalState = FileState.InDepot;
}
if (aMeta.OtherActions != null)
{
status.OtherState = Engine.ParseFileAction(aMeta.OtherActions[0]);
}
if (aMeta.HaveRev >= aMeta.HeadRev)
status.RevisionState = RevisionState.HasLatest;
else
status.RevisionState = RevisionState.OutOfDate;
if (aMeta.Unresolved)
status.ResolvedState = ResolvedState.NeedsResolve;
else
status.ResolvedState = ResolvedState.None;
if (aMeta.HeadAction == FileAction.MoveDelete || aMeta.HeadAction == FileAction.Delete)
status.DepotState = DepotState.Deleted;
else
status.DepotState = DepotState.None;
status.TimeStamp = DateTime.Now;
status.LockState = Engine.GetLockState(aMeta);
status.UpdateType = AssetStatus.LastUpdateType.OnServer;
// Debug.Log("AssetStatus: " + status.ToString());
}
///
/// Given a Reconcile entry, update AssetStatus which drives the P4Connect icon presentation
///
public static void AssetStatusFromReconcileEntry(AssetStatus status, ReconcileEntry entry)
{
// Debug.Log("AssetStatusFromReconcileEntry: " + Logger.ReconcileEntryToString(entry));
if (entry == null)
return;
// Fill out the struct
switch (entry.Type.BaseType)
{
case BaseFileType.Text:
status.StorageType = StorageType.Text;
break;
case BaseFileType.Binary:
status.StorageType = StorageType.Binary;
break;
default:
status.StorageType = StorageType.Other;
break;
}
status.LocalState = Engine.ParseFileAction(entry.Action);
// Otherstate is unknown when generated by ReconcileEntry
//status.OtherState = FileState.None;
if (entry.WorkRev.Equals(entry.Version))
{
status.RevisionState = RevisionState.HasLatest;
}
else
{
status.RevisionState = (entry.WorkRev.Rev == 0) ? RevisionState.None : RevisionState.OutOfDate;
}
if (entry.Action == FileAction.Unresolved)
status.ResolvedState = ResolvedState.NeedsResolve;
else
status.ResolvedState = ResolvedState.None;
if (entry.Action == FileAction.MoveDelete || entry.Action == FileAction.Delete)
status.DepotState = DepotState.Deleted;
else
status.DepotState = DepotState.None;
status.TimeStamp = DateTime.Now;
status.UpdateType = AssetStatus.LastUpdateType.OnServer;
// LockState is unknown when generated by ReconcileEntry
//status.LockState = Engine.GetLockState(aMeta);
//Debug.Log("AssetStatus: " + status.ToString());
}
///
/// Update an asset status from a File entry (result of p4 opened)
///
///
///
///
public static void UpdateAssetStatusFromFile(AssetStatus status, File entry)
{
//Debug.Log("UpdateAssetStatusFromFile: " + entry.ToStringNullSafe());
if (entry == null)
return;
// Fill out the struct
switch (entry.Type.BaseType)
{
case BaseFileType.Text:
status.StorageType = StorageType.Text;
break;
case BaseFileType.Binary:
status.StorageType = StorageType.Binary;
break;
default:
status.StorageType = StorageType.Other;
break;
}
// see if this entry is from our local workspace
if (entry.User == Config.Username && entry.Client == Config.Workspace)
{
status.LocalState = Engine.ParseFileAction(entry.Action);
if (entry.HaveRev.Equals(entry.Version))
{
status.RevisionState = RevisionState.HasLatest;
}
else
{
status.RevisionState = (entry.HaveRev.Rev == 0) ? RevisionState.None : RevisionState.OutOfDate;
}
status.ResolvedState = (entry.Action == FileAction.Unresolved) ? ResolvedState.NeedsResolve : ResolvedState.None;
if (entry.Action == FileAction.MoveDelete || entry.Action == FileAction.Delete)
status.DepotState = DepotState.Deleted;
else
status.DepotState = DepotState.None;
status.LockState = entry.Locked ? LockState.OurLock : LockState.None;
}
else // Must be someone using my files in a remote location
{
status.OtherState = Engine.ParseFileAction(entry.Action);
if (status.LockState == LockState.None && entry.Locked)
status.LockState = LockState.TheirLock;
}
status.TimeStamp = DateTime.Now;
status.UpdateType = AssetStatus.LastUpdateType.OnServer;
if (entry.Locked)
status.LockState = LockState.OurLock;
//Debug.Log("AssetStatus: " + status.ToString());
return;
}
///
/// We have new Metadata so Update the cache
///
/// IEnumerable MetaDataList
public static void UpdateCacheWithMetadata(IEnumerable fmdlist)
{
foreach (var fmd in fmdlist)
{
var entry = GetAssetEntry(fmd.ToAssetPath());
if (entry != null)
{
entry.Meta = fmd;
// Since the Metadata changed, re-create the Asset Status Also
AssetStatusFromMetaData(entry.Status, fmd);
}
}
}
///
/// This functions gets called when P4Connect does *something* to files
/// In this case we update the files status
///
static void OnEngineOperationPerformed(PerforceConnection aConnection, IEnumerable aFiles, AssetOperation op)
{
// ToDo: Look at filtering by operation. Some of these Fstats may have already been updated
GetFstatsFromServer(aFiles); // retrieve new fstats in the background
}
///
/// Given a collection of folders and Asset paths, update file Metadata information from the server
/// Queues an ASYNCHRONOUS request for the asset paths to a background thread
///
/// collection of Asset paths, and folders
public static void GetFstatsFromServer(IEnumerable paths)
{
if (paths.Any())
{
string[] paths1 = paths.AddDirectoryWildcards().ToArray();
//log.Debug("GetFstatsFromServer: " + Logger.StringArrayToString(paths1));
_dataQueue.QueueQuery(paths1);
}
}
///
/// Checks current cached asset status, and if not initialized, retrieves it from the server
/// Updates run ASYNCRONOUSLY
///
/// assetpaths to retrieve from server if still dummies
public static void UpdateDefaultsFromServer(IEnumerable paths)
{
var updatePaths = new HashSet();
foreach (string t in paths)
{
if (! Utils.IsDirectory(t)) // Skip subdirectories
{
var status = GetCachedAssetStatus(t);
if (!status.IsInitialized())
{
updatePaths.Add(t);
}
}
}
if (updatePaths.Count > 0)
{
GetFstatsFromServer(updatePaths);
}
}
///
/// Given a folder, invalidate all cache data for members
///
/// Path to dirty folder
public static void MarkFolderAsDirty(string path)
{
path = Utils.RemoveDirectoryWildcards(path);
log.Debug("dirtypath: " + path);
IEnumerable fullKeys = _Entries.Keys.Where(currentKey => currentKey.StartsWith(path));
foreach (string key in fullKeys)
{
_Entries[key].Dirty = true;
log.Debug("dirt: " + key);
}
}
///
/// Given an asset path, mark this asset as Dirty
/// So it will be re-fstated later.
///
///
public static void MarkAsDirty(string assetPath)
{
//log.Debug("DIRTY: " + assetPath);
var entry = GetAssetEntry(assetPath);
entry.Update = default(DateTime);
entry.Status.TimeStamp = entry.Update;
}
///
/// Mark the contents of a List of FileAndMetas as dirty
///
/// FileAndMetas Collection
static public void MarkAsDirty(IEnumerable fam)
{
if (fam == null)
return;
foreach (var f in fam)
{
if (f.File != null)
{
MarkAsDirty(f.File.ToAssetPath());
}
if (f.Meta != null)
{
MarkAsDirty(f.Meta.ToAssetPath());
}
}
}
///
/// Using the Perforce connection, get a matching AssetStatus for each Assetpath
///
/// Perforce Connection
/// A List of asset paths to retrieve
/// A list of statuses, one per aFile
//public static void GetAssetStatusesFromPaths(PerforceConnection aConnection, List aFiles,
// List aOutStatuses)
//{
// GetAssetStatuses(aConnection, GetEffectiveFileSpecs(aFiles).ToList(), aOutStatuses);
//}
///
/// Using Perforce connection, get a matching AssetStatus for each Assetpath
///
/// Perforce Connection
/// A List of asset paths to retrieve
/// A List of AssetStatus, one for each entry
//static public List GetAssetStatusesFromPaths(PerforceConnection aConnection,
// List aFiles)
//{
// List aOutStatuses = new List();
// GetAssetStatusesFromPaths(aConnection, aFiles, aOutStatuses);
// return aOutStatuses;
//}
///
/// Using the Perforce connection, get a matching AssetStatus for each FileSpec
/// If the metadata for a file is not in Perforce, we provide a "local" AssetStatus instead.
///
/// a valid Perforce Connection
/// A list of FileSpecs to be looked up
/// an AssetStatus is added to this list for each entry
//static void GetAssetStatuses(PerforceConnection aConnection, List aFiles,
// List aOutStatuses)
//{
// IEnumerable assetNames = GetAssetPathsFromFileSpecs(aFiles);
// string[] files = assetNames.ToArray();
// Debug.Log("retrieving: " + Logger.StringArrayToString(files));
// List notInitialized = new List();
// // Collect results here for return later
// AssetStatus[] stats = new AssetStatus[files.Count()];
// // Get The Asset Status for each file, if not initialized add to "not_initialized" list
// for (int i = 0; i < files.Count(); i++)
// {
// stats[i] = GetCachedAssetStatus(files[i]);
// if (!stats[i].IsInitialized())
// {
// notInitialized.Add(files[i]);
// }
// }
// // Now Ask for any uninitialized AssetStatus's from the server
// if (notInitialized.Count > 0)
// {
// log.Debug("Going to server for: " + Logger.StringArrayToString(notInitialized.ToArray()));
// GetFstatsFromServer(notInitialized);
// }
// // Finally update the "holes" in the statuses originally returned
// for (int i = 0; i < files.Count(); i++)
// {
// if (!stats[i].IsInitialized())
// {
// stats[i] = GetCachedAssetStatus(files[i]);
// }
// }
// aOutStatuses.AddRange(stats);
// log.Debug("assetstatuses: " + Logger.AssetStatusListToString(aOutStatuses));
//}
///
/// Goes to the server, updates cache with latest about the files specified
/// Frequently runs in a background thread.
///
/// A valid Perforce connection
/// AssetPath list to retrieve info about
/// enumeration of asset paths which were updated
public static IEnumerable GetAssetMetaData(PerforceConnection aConnection, IEnumerable aFiles)
{
//log.Debug("GetAssetMetaData: " + Logger.StringArrayToString(aFiles.ToArray()));
var returnList = new HashSet();
if (aFiles.Any())
{
HashSet original = new HashSet(aFiles);
HashSet checkedList = null;
DateTime startTimestamp = DateTime.Now;
Options fsopts = new Options();
fsopts["-Op"] = null; // Return "real" clientpaths
var mdlist = Engine.GetFileMetaData(aConnection, original.ToDepotSpecs().ToList(), fsopts);
if (mdlist != null)
{
// with a good response, we store the retrieved meta data in the AssetCache
//StorePerforceMetaData(mdlist); // This is done in Engine.GetFileMetaData() instead
checkedList = new HashSet(mdlist.ToAssetPaths().EffectivePaths());
}
// Remove wildcard entries from original list.
// Create Local MetaData entries for files unknown by Perforce
IEnumerable leftOvers = (checkedList != null) ? original.RemoveWildCards().Except(checkedList) : original;
if (leftOvers.Any())
StoreLocalMetaData(leftOvers);
if (Config.DisplayP4Timings)
{
double deltaInnerTime = (DateTime.Now - startTimestamp).TotalMilliseconds;
aConnection.AppendTimingInfo("GetAssetMetaData " + deltaInnerTime + " ms");
}
if (checkedList != null)
{
returnList.UnionWith(checkedList);
}
returnList.UnionWith(leftOvers);
}
return returnList;
}
///
/// We have fresh metadata from the server
/// Lets store it in the Asset Cache
///
/// List of Metadata to store
public static void StorePerforceMetaData(IList mdlist)
{
if (mdlist == null)
return;
foreach (var md in mdlist)
{
if (md == null || md.LocalPath == null || md.LocalPath.Path == null || md.LocalPath.Path.Length == 0)
{
log.Debug("Missing LocalPath - MetaData: " + Logger.FileMetaDataToString(md));
continue;
}
// Create the AssetStatus and FileMetaData entries for each file known by the server
string fname = Utils.GetEffectivePath(Utils.LocalPathToAssetPath(md.LocalPath.Path));
AssetEntry entry = new AssetEntry
{
Meta = md,
Update = DateTime.Now
};
AssetStatusFromMetaData(entry.Status, md);
_Entries[fname] = entry;
//log.Debug("AssetEntry: " + fname + " CREATED " + entry.Status.ToStringNullSafe());
}
}
///
/// The metadata from the server came back, and did not include these files
/// So we need to make valid Cache entries as if they are Local files only
///
/// Asset Paths of files to cache
public static void StoreLocalMetaData(IEnumerable files)
{
if (files == null)
return;
// For "leftover" files not known by the server, we provide a default
// AssetEntry with the update field set.
foreach (string f in files)
{
AssetEntry entry = new AssetEntry();
entry.Meta = new FileMetaData();
entry.Status = new AssetStatus();
entry.Status.TimeStamp = entry.Update = DateTime.Now;
entry.Status.UpdateType = AssetStatus.LastUpdateType.OffServer; // We verified that the server doesn't have a record of this.
_Entries[f] = entry;
//log.Debug("AssetEntry: " + f + " LOCAL " + entry.Status.ToStringNullSafe());
}
}
///
/// Returns a proper list of file spec given a list of paths, skipping folders, generating folder meta files instead
///
/// Asset Path strings
/// FileSpecs
static IEnumerable GetEffectiveFileSpecs(IEnumerable aAssetPaths)
{
foreach (var path in aAssetPaths)
{
yield return FileSpec.LocalSpec(Utils.AssetPathToLocalPath(Utils.GetEffectivePath(path)));
}
yield break;
}
///
/// Translates a collection of asset paths (including folders) into a collection of LocalSpec FileSpecs
///
/// Asset paths to become FileSpec
/// FileSpecs
public static IEnumerable GetFileSpecs(IEnumerable aAssetPaths)
{
foreach (var path in aAssetPaths)
{
yield return FileSpec.LocalSpec(Utils.AssetPathToLocalPath(path));
}
yield break;
}
///
/// Return a collection of Asset paths given a list of FileSpecs
/// (using the LocalPath of the FileSpec)
///
/// FileSpecs
/// strings
public static IEnumerable GetAssetPathsFromFileSpecs(IEnumerable fs)
{
foreach (var f in fs)
{
yield return Utils.LocalPathToAssetPath(f.LocalPath.Path);
}
yield break;
}
///
/// Return a collection of Asset paths given a list of FileMetaData
///
/// FileMetaData collection
/// string collection
public static IEnumerable GetAssetPathsFromFileMetaData(IEnumerable fmd_list)
{
foreach (var fm in fmd_list)
{
yield return Utils.LocalPathToAssetPath(fm.LocalPath.Path);
}
yield break;
}
#region InspectorGUI
[CustomEditor(typeof(UnityEditor.DefaultAsset), true)]
public class PerforceAssetPropertiesEditor : Editor
{
private AssetEntry _entry;
private string _assetPath;
public override void OnInspectorGUI()
{
//GUI.enabled = false;
DrawDefaultInspector();
//GUI.enabled = true;
//EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Perforce Properties", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
_assetPath = AssetDatabase.GetAssetPath(target.GetInstanceID());
EditorGUILayout.LabelField("Path: " + _assetPath);
_entry = GetAssetEntry(_assetPath);
if (_entry == null)
{
EditorGUILayout.LabelField("No Asset Entry Found");
}
else
{
_entry.Status.OnGUI();
}
var guid = AssetDatabase.AssetPathToGUID(_assetPath);
EditorGUILayout.LabelField("GUID: " + guid);
string[] dependents = AssetDatabase.GetDependencies(_assetPath, false);
foreach (var dep in dependents)
{
EditorGUILayout.LabelField("-- " + dep);
}
EditorGUILayout.EndVertical();
}
}
#endregion
#region MetaDataQueue
///
/// Encapsulates a thread with an input queue of string arrays.
/// Issues "p4 fstat" command for each entry
///
class MetaDataQueue
{
private Thread _worker;
readonly Queue _queue = new Queue();
readonly object _locker = new object();
readonly object _lockUpdated = new object();
private readonly EventWaitHandle _wh = new AutoResetEvent(false);
// private bool queryDone = false;
public MetaDataQueue()
{
_worker = new Thread(Work);
_worker.Start();
}
~MetaDataQueue()
{
QueueQuery(null);
_worker.Join();
_wh.Close();
}
public void QueueQuery(string[] paths)
{
lock (_locker) _queue.Enqueue(paths);
_wh.Set();
}
///
/// Work - This is run by the background thread.
/// It issues a command on the queue
/// Then waits for an Event to proceed.
///
private void Work()
{
while (true)
{
var argument = new HashSet();
lock (_locker)
{
while (_queue.Count > 0)
{
argument.UnionWith(_queue.Dequeue()); // Collect all pending requests into one list.
}
}
if (argument.Count > 0)
{
string[] changedFiles = new string[0];
Engine.PerformConnectionOperation(con =>
{
changedFiles = AssetStatusCache.GetAssetMetaData(con, argument).ToArray();
}, true);
if (changedFiles.Length > 0)
SignalUpdate(changedFiles);
}
else
{
_wh.WaitOne();
}
}
}
}
#endregion
}
}