升级XR插件版本
This commit is contained in:
275
Packages/MCPForUnity/Editor/Services/Server/PidFileManager.cs
Normal file
275
Packages/MCPForUnity/Editor/Services/Server/PidFileManager.cs
Normal file
@@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using MCPForUnity.Editor.Constants;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Editor.Services.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages PID files and handshake state for the local HTTP server.
|
||||
/// Handles persistence of server process information across Unity domain reloads.
|
||||
/// </summary>
|
||||
public class PidFileManager : IPidFileManager
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string GetPidDirectory()
|
||||
{
|
||||
return Path.Combine(GetProjectRootPath(), "Library", "MCPForUnity", "RunState");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetPidFilePath(int port)
|
||||
{
|
||||
string dir = GetPidDirectory();
|
||||
Directory.CreateDirectory(dir);
|
||||
return Path.Combine(dir, $"mcp_http_{port}.pid");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryReadPid(string pidFilePath, out int pid)
|
||||
{
|
||||
pid = 0;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(pidFilePath) || !File.Exists(pidFilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string text = File.ReadAllText(pidFilePath).Trim();
|
||||
if (int.TryParse(text, out pid))
|
||||
{
|
||||
return pid > 0;
|
||||
}
|
||||
|
||||
// Best-effort: tolerate accidental extra whitespace/newlines.
|
||||
var firstLine = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
if (int.TryParse(firstLine, out pid))
|
||||
{
|
||||
return pid > 0;
|
||||
}
|
||||
|
||||
pid = 0;
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
pid = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetPortFromPidFilePath(string pidFilePath, out int port)
|
||||
{
|
||||
port = 0;
|
||||
if (string.IsNullOrEmpty(pidFilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string fileName = Path.GetFileNameWithoutExtension(pidFilePath);
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const string prefix = "mcp_http_";
|
||||
if (!fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string portText = fileName.Substring(prefix.Length);
|
||||
return int.TryParse(portText, out port) && port > 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
port = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DeletePidFile(string pidFilePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pidFilePath) && File.Exists(pidFilePath))
|
||||
{
|
||||
File.Delete(pidFilePath);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StoreHandshake(string pidFilePath, string instanceToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pidFilePath))
|
||||
{
|
||||
EditorPrefs.SetString(EditorPrefKeys.LastLocalHttpServerPidFilePath, pidFilePath);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(instanceToken))
|
||||
{
|
||||
EditorPrefs.SetString(EditorPrefKeys.LastLocalHttpServerInstanceToken, instanceToken);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetHandshake(out string pidFilePath, out string instanceToken)
|
||||
{
|
||||
pidFilePath = null;
|
||||
instanceToken = null;
|
||||
try
|
||||
{
|
||||
pidFilePath = EditorPrefs.GetString(EditorPrefKeys.LastLocalHttpServerPidFilePath, string.Empty);
|
||||
instanceToken = EditorPrefs.GetString(EditorPrefKeys.LastLocalHttpServerInstanceToken, string.Empty);
|
||||
if (string.IsNullOrEmpty(pidFilePath) || string.IsNullOrEmpty(instanceToken))
|
||||
{
|
||||
pidFilePath = null;
|
||||
instanceToken = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
pidFilePath = null;
|
||||
instanceToken = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StoreTracking(int pid, int port, string argsHash = null)
|
||||
{
|
||||
try { EditorPrefs.SetInt(EditorPrefKeys.LastLocalHttpServerPid, pid); } catch { }
|
||||
try { EditorPrefs.SetInt(EditorPrefKeys.LastLocalHttpServerPort, port); } catch { }
|
||||
try { EditorPrefs.SetString(EditorPrefKeys.LastLocalHttpServerStartedUtc, DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)); } catch { }
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(argsHash))
|
||||
{
|
||||
EditorPrefs.SetString(EditorPrefKeys.LastLocalHttpServerPidArgsHash, argsHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorPrefs.DeleteKey(EditorPrefKeys.LastLocalHttpServerPidArgsHash);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetStoredPid(int expectedPort, out int pid)
|
||||
{
|
||||
pid = 0;
|
||||
try
|
||||
{
|
||||
int storedPid = EditorPrefs.GetInt(EditorPrefKeys.LastLocalHttpServerPid, 0);
|
||||
int storedPort = EditorPrefs.GetInt(EditorPrefKeys.LastLocalHttpServerPort, 0);
|
||||
string storedUtc = EditorPrefs.GetString(EditorPrefKeys.LastLocalHttpServerStartedUtc, string.Empty);
|
||||
|
||||
if (storedPid <= 0 || storedPort != expectedPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only trust the stored PID for a short window to avoid PID reuse issues.
|
||||
// (We still verify the PID is listening on the expected port before killing.)
|
||||
if (!string.IsNullOrEmpty(storedUtc)
|
||||
&& DateTime.TryParse(storedUtc, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var startedAt))
|
||||
{
|
||||
if ((DateTime.UtcNow - startedAt) > TimeSpan.FromHours(6))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pid = storedPid;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetStoredArgsHash()
|
||||
{
|
||||
try
|
||||
{
|
||||
return EditorPrefs.GetString(EditorPrefKeys.LastLocalHttpServerPidArgsHash, string.Empty);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ClearTracking()
|
||||
{
|
||||
try { EditorPrefs.DeleteKey(EditorPrefKeys.LastLocalHttpServerPid); } catch { }
|
||||
try { EditorPrefs.DeleteKey(EditorPrefKeys.LastLocalHttpServerPort); } catch { }
|
||||
try { EditorPrefs.DeleteKey(EditorPrefKeys.LastLocalHttpServerStartedUtc); } catch { }
|
||||
try { EditorPrefs.DeleteKey(EditorPrefKeys.LastLocalHttpServerPidArgsHash); } catch { }
|
||||
try { EditorPrefs.DeleteKey(EditorPrefKeys.LastLocalHttpServerPidFilePath); } catch { }
|
||||
try { EditorPrefs.DeleteKey(EditorPrefKeys.LastLocalHttpServerInstanceToken); } catch { }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ComputeShortHash(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return string.Empty;
|
||||
try
|
||||
{
|
||||
using var sha = SHA256.Create();
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(input);
|
||||
byte[] hash = sha.ComputeHash(bytes);
|
||||
// 8 bytes => 16 hex chars is plenty as a stable fingerprint for our purposes.
|
||||
var sb = new StringBuilder(16);
|
||||
for (int i = 0; i < 8 && i < hash.Length; i++)
|
||||
{
|
||||
sb.Append(hash[i].ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetProjectRootPath()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Application.dataPath is ".../<Project>/Assets"
|
||||
return Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Application.dataPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user