Created
December 18, 2019 14:58
-
-
Save strich/7566f47e5d2ca7d7dc33abff3be1b8bf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Net.Http; | |
using System.Net.Http.Headers; | |
using System.Text.RegularExpressions; | |
using System.Threading.Tasks; | |
using Brightrock.Library.BehaviourTree; | |
using Newtonsoft.Json.Linq; | |
using UnityEditor; | |
using UnityEngine; | |
namespace Game.Tools.Editor.Assets | |
{ | |
using Object = UnityEngine.Object; | |
[InitializeOnLoad] | |
public class GitLFSFileLock | |
{ | |
const float LFSLockUpdateCheckInterval = 60f; | |
const string AssetsSubpath = "/Game/Assets"; | |
const string FilelockTag = "GitLFSLockActive"; | |
public static List<ResourceGraph> LockedFiles = new List<ResourceGraph>(); | |
static int _activeProcesses = 0; | |
static readonly Texture[] _loadingSpinners = new Texture[] | |
{ | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin00.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin01.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin02.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin03.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin04.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin05.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin06.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin07.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin08.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin09.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin10.png"), | |
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin11.png") | |
}; | |
static readonly Dictionary<string, string> _lockedGraphs = new Dictionary<string, string>(); | |
static readonly Stack<string> _warnCannotLock = new Stack<string>(); | |
static List<string> _allGraphsPaths = new List<string>(); | |
static List<string> AllGraphPaths | |
{ | |
get | |
{ | |
if (_allGraphsPaths != null) return _allGraphsPaths; | |
_allGraphsPaths.Clear(); | |
var paths = AssetDatabase.FindAssets("t:resourcegraph", null).ToList(); | |
return _allGraphsPaths = paths; | |
} | |
} | |
static GitLFSFileLock() | |
{ | |
EditorApplication.update -= Update; | |
EditorApplication.update += Update; | |
} | |
public static void QueryCurrentLocks() | |
{ | |
RunGitCommand("git lfs locks --json", QueryCurrentLocksReturn); | |
} | |
public static string CurrentUsername { get; private set; } | |
static bool _gettingCurrentUser; | |
public static void GetCurrentUser() | |
{ | |
_gettingCurrentUser = true; | |
RunGitCommand("git config user.name", (sender, e) => | |
{ | |
_activeProcesses--; | |
CurrentUsername = ( (Process)sender ).StandardOutput.ReadLine(); | |
_gettingCurrentUser = false; | |
}); | |
} | |
public static void LockFile(Object obj) | |
{ | |
var path = AssetDatabase.GetAssetPath(obj); | |
RunGitCommand($"git lfs lock \"Game/{path}\" --json"); | |
} | |
public static void UnlockFile(Object obj) | |
{ | |
var path = AssetDatabase.GetAssetPath(obj); | |
RunGitCommand($"git lfs unlock \"Game/{path}\" --json", null, "Game/" + path); | |
} | |
public static void ForceUnlockFile(Object obj) | |
{ | |
var path = AssetDatabase.GetAssetPath(obj); | |
RunGitCommand($"git lfs unlock \"Game/{path}\" --force --json", null, "Game/" + path); | |
} | |
public static bool IsAssetLocked(Object obj, out bool lockedLocally) | |
{ | |
if (!_gettingCurrentUser && string.IsNullOrEmpty(CurrentUsername)) GetCurrentUser(); | |
var path = AssetDatabase.GetAssetPath(obj); | |
var locked = _lockedGraphs.TryGetValue("Game/" + path, out var locker); | |
if (locked) lockedLocally = locker.Equals(CurrentUsername); | |
else lockedLocally = false; | |
return locked; | |
} | |
public static string GetAssetLocker(Object obj) | |
{ | |
var path = AssetDatabase.GetAssetPath(obj); | |
_lockedGraphs.TryGetValue("Game/" + path, out var name); | |
return name; | |
} | |
public static bool ProcessRunning() | |
{ | |
return _activeProcesses > 0; | |
} | |
static int _currentSpinnerIndex = -1; | |
public static Texture GetSpinner() | |
{ | |
_currentSpinnerIndex++; | |
if (_currentSpinnerIndex >= _loadingSpinners.Length) _currentSpinnerIndex = 0; | |
return _loadingSpinners[_currentSpinnerIndex]; | |
} | |
static bool _queryNextTick; | |
static void Update() | |
{ | |
//Some Unity calls cannot be made from Process.Exited | |
if (_queryNextTick) | |
{ | |
QueryCurrentLocks(); | |
_queryNextTick = false; | |
} | |
if (_warnCannotLock.Count > 0) | |
{ | |
var warning = _warnCannotLock.Pop(); | |
if (warning != null) | |
{ | |
EditorUtility.DisplayDialog("Git LFS File Locking Error", $"{warning}", "Continue"); | |
} | |
} | |
if (!_gettingCurrentUser && string.IsNullOrEmpty(CurrentUsername)) GetCurrentUser(); | |
} | |
static void RunGitCommand(string command, EventHandler result = null, string name = "") | |
{ | |
_activeProcesses++; | |
var gitPath = Application.dataPath.Replace(AssetsSubpath, "").Replace("/", "\\"); | |
var p = new PathedProcess() | |
{ | |
StartInfo = new ProcessStartInfo | |
{ | |
FileName = "cmd.exe", | |
Arguments = "/c " + command, | |
WorkingDirectory = gitPath, | |
UseShellExecute = false, | |
RedirectStandardError = true, | |
RedirectStandardOutput = true, | |
CreateNoWindow = true, | |
}, | |
Path = name, | |
EnableRaisingEvents = true | |
}; | |
if (result != null) p.Exited += result; | |
else p.Exited += GitLockProcess_Exited; | |
p.Start(); | |
} | |
static void GitLockProcess_Exited(object sender, EventArgs e) | |
{ | |
_activeProcesses--; | |
var process = (PathedProcess)sender; | |
process.Exited -= GitLockProcess_Exited; | |
if (process.ExitCode != 0) | |
{ | |
var error = process.StandardError.ReadLine(); | |
_warnCannotLock.Push($"ExitCode: {process.ExitCode.ToString()} | " + error); | |
return; | |
} | |
var path = process.Path; | |
if(!string.IsNullOrEmpty(path) && _lockedGraphs.ContainsKey(path)) _lockedGraphs.Remove(path); | |
_queryNextTick = true; | |
process.Dispose(); | |
} | |
static void QueryCurrentLocksReturn(object sender, EventArgs e) | |
{ | |
_activeProcesses--; | |
var process = (Process)sender; | |
process.Exited -= QueryCurrentLocksReturn; | |
var output = process.StandardOutput.ReadToEnd(); | |
_lockedGraphs.Clear(); | |
dynamic returnObjs = JArray.Parse(output); | |
foreach (var obj in returnObjs) | |
{ | |
var nameOutput = (string)obj.owner.name; | |
var path = (string)obj.path; | |
var match = Regex.Match(nameOutput, @"\d+"); | |
var userName = ""; | |
if (match.Success) | |
{ | |
var user = GetUsernameByID(match.Value); | |
user.Wait(); | |
userName = user.Result; | |
} | |
else userName = nameOutput; | |
_lockedGraphs[path] = userName; | |
//_lockedGraphs.Add(path, userName); | |
} | |
process.Dispose(); | |
} | |
static async Task<string> GetUsernameByID(string userId) | |
{ | |
var uri = new Uri($"https://api.github.com/user/{userId}"); | |
using (var hc = new HttpClient()) | |
{ | |
hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); | |
hc.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "http://developer.github.com/v3/#user-agent-required"); | |
var resultTask = await hc.GetStringAsync(uri); | |
if (!string.IsNullOrEmpty(resultTask)) | |
{ | |
dynamic githubUser = JObject.Parse(resultTask); | |
return (string)githubUser.name; | |
} | |
else | |
{ | |
return userId; | |
} | |
} | |
} | |
} | |
class PathedProcess : Process | |
{ | |
public string Path; | |
public PathedProcess() : base() { } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment