using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityMCP.Editor.Helpers; // For Response class
using System.Globalization;
namespace UnityMCP.Editor.Tools
{
///
/// Handles reading and clearing Unity Editor console log entries.
/// Uses reflection to access internal LogEntry methods/properties.
///
public static class ReadConsole
{
// Reflection members for accessing internal LogEntry data
private static MethodInfo _getEntriesMethod;
private static MethodInfo _startGettingEntriesMethod;
private static MethodInfo _stopGettingEntriesMethod;
private static MethodInfo _clearMethod;
private static MethodInfo _getCountMethod;
private static MethodInfo _getEntryMethod;
private static FieldInfo _modeField;
private static FieldInfo _messageField;
private static FieldInfo _fileField;
private static FieldInfo _lineField;
private static FieldInfo _instanceIdField;
// Note: Timestamp is not directly available in LogEntry; need to parse message or find alternative?
// Static constructor for reflection setup
static ReadConsole()
{
try {
Type logEntriesType = typeof(EditorApplication).Assembly.GetType("UnityEditor.LogEntries");
if (logEntriesType == null) throw new Exception("Could not find internal type UnityEditor.LogEntries");
_getEntriesMethod = logEntriesType.GetMethod("GetEntries", BindingFlags.Static | BindingFlags.Public);
_startGettingEntriesMethod = logEntriesType.GetMethod("StartGettingEntries", BindingFlags.Static | BindingFlags.Public);
_stopGettingEntriesMethod = logEntriesType.GetMethod("StopGettingEntries", BindingFlags.Static | BindingFlags.Public);
_clearMethod = logEntriesType.GetMethod("Clear", BindingFlags.Static | BindingFlags.Public);
_getCountMethod = logEntriesType.GetMethod("GetCount", BindingFlags.Static | BindingFlags.Public);
_getEntryMethod = logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);
Type logEntryType = typeof(EditorApplication).Assembly.GetType("UnityEditor.LogEntry");
if (logEntryType == null) throw new Exception("Could not find internal type UnityEditor.LogEntry");
_modeField = logEntryType.GetField("mode", BindingFlags.Instance | BindingFlags.Public);
_messageField = logEntryType.GetField("message", BindingFlags.Instance | BindingFlags.Public);
_fileField = logEntryType.GetField("file", BindingFlags.Instance | BindingFlags.Public);
_lineField = logEntryType.GetField("line", BindingFlags.Instance | BindingFlags.Public);
_instanceIdField = logEntryType.GetField("instanceID", BindingFlags.Instance | BindingFlags.Public);
// Basic check if reflection worked
if (_getEntriesMethod == null || _clearMethod == null || _modeField == null || _messageField == null)
{
throw new Exception("Failed to get required reflection members for LogEntries/LogEntry.");
}
}
catch (Exception e)
{
Debug.LogError($"[ReadConsole] Static Initialization Failed: Could not setup reflection for LogEntries. Console reading/clearing will likely fail. Error: {e}");
// Set members to null to prevent NullReferenceExceptions later, HandleCommand should check this.
_getEntriesMethod = _startGettingEntriesMethod = _stopGettingEntriesMethod = _clearMethod = _getCountMethod = _getEntryMethod = null;
_modeField = _messageField = _fileField = _lineField = _instanceIdField = null;
}
}
// --- Main Handler ---
public static object HandleCommand(JObject @params)
{
// Check if reflection setup failed in static constructor
if (_clearMethod == null || _getEntriesMethod == null || _startGettingEntriesMethod == null || _stopGettingEntriesMethod == null || _getCountMethod == null || _getEntryMethod == null || _modeField == null || _messageField == null)
{
return Response.Error("ReadConsole handler failed to initialize due to reflection errors. Cannot access console logs.");
}
string action = @params["action"]?.ToString().ToLower() ?? "get";
try
{
if (action == "clear")
{
return ClearConsole();
}
else if (action == "get")
{
// Extract parameters for 'get'
var types = (@params["types"] as JArray)?.Select(t => t.ToString().ToLower()).ToList() ?? new List { "error", "warning", "log" };
int? count = @params["count"]?.ToObject();
string filterText = @params["filterText"]?.ToString();
string sinceTimestampStr = @params["sinceTimestamp"]?.ToString(); // TODO: Implement timestamp filtering
string format = (@params["format"]?.ToString() ?? "detailed").ToLower();
bool includeStacktrace = @params["includeStacktrace"]?.ToObject() ?? true;
if (types.Contains("all")) {
types = new List { "error", "warning", "log" }; // Expand 'all'
}
if (!string.IsNullOrEmpty(sinceTimestampStr))
{
Debug.LogWarning("[ReadConsole] Filtering by 'since_timestamp' is not currently implemented.");
// Need a way to get timestamp per log entry.
}
return GetConsoleEntries(types, count, filterText, format, includeStacktrace);
}
else
{
return Response.Error($"Unknown action: '{action}'. Valid actions are 'get' or 'clear'.");
}
}
catch (Exception e)
{
Debug.LogError($"[ReadConsole] Action '{action}' failed: {e}");
return Response.Error($"Internal error processing action '{action}': {e.Message}");
}
}
// --- Action Implementations ---
private static object ClearConsole()
{
try
{
_clearMethod.Invoke(null, null); // Static method, no instance, no parameters
return Response.Success("Console cleared successfully.");
}
catch (Exception e)
{
Debug.LogError($"[ReadConsole] Failed to clear console: {e}");
return Response.Error($"Failed to clear console: {e.Message}");
}
}
private static object GetConsoleEntries(List types, int? count, string filterText, string format, bool includeStacktrace)
{
List