@mflrevan/ucp 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +8 -2
- package/bridge/com.ucp.bridge/CHANGELOG.md +0 -128
- package/bridge/com.ucp.bridge/CHANGELOG.md.meta +0 -7
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +0 -576
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Bridge.meta +0 -8
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +0 -530
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +0 -230
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +0 -26
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +0 -18
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +0 -438
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +0 -145
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +0 -319
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +0 -288
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +0 -295
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +0 -93
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +0 -84
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +0 -242
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +0 -533
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +0 -269
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +0 -125
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +0 -104
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +0 -227
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +0 -240
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +0 -611
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Controllers.meta +0 -8
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +0 -53
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +0 -80
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +0 -358
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Protocol.meta +0 -8
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +0 -37
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Editor/Scripts.meta +0 -8
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +0 -16
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +0 -7
- package/bridge/com.ucp.bridge/Editor.meta +0 -8
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +0 -14
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +0 -7
- package/bridge/com.ucp.bridge/Runtime.meta +0 -8
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +0 -670
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +0 -2
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +0 -12
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +0 -7
- package/bridge/com.ucp.bridge/Tests/Editor.meta +0 -8
- package/bridge/com.ucp.bridge/Tests.meta +0 -8
- package/bridge/com.ucp.bridge/package.json +0 -27
- package/bridge/com.ucp.bridge/package.json.meta +0 -7
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
using System;
|
|
2
|
-
using System.Collections.Generic;
|
|
3
|
-
using UnityEditor;
|
|
4
|
-
using UnityEditor.SceneManagement;
|
|
5
|
-
using UnityEngine;
|
|
6
|
-
using UnityEngine.SceneManagement;
|
|
7
|
-
|
|
8
|
-
namespace UCP.Bridge
|
|
9
|
-
{
|
|
10
|
-
public static class HierarchyController
|
|
11
|
-
{
|
|
12
|
-
public static void Register(CommandRouter router)
|
|
13
|
-
{
|
|
14
|
-
router.Register("object/create", HandleCreate);
|
|
15
|
-
router.Register("object/delete", HandleDelete);
|
|
16
|
-
router.Register("object/reparent", HandleReparent);
|
|
17
|
-
router.Register("object/instantiate", HandleInstantiate);
|
|
18
|
-
router.Register("object/add-component", HandleAddComponent);
|
|
19
|
-
router.Register("object/remove-component", HandleRemoveComponent);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private static object HandleCreate(string paramsJson)
|
|
23
|
-
{
|
|
24
|
-
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
25
|
-
var name = "GameObject";
|
|
26
|
-
if (p != null && p.TryGetValue("name", out var nameObj) && nameObj != null)
|
|
27
|
-
name = nameObj.ToString();
|
|
28
|
-
|
|
29
|
-
var go = new GameObject(name);
|
|
30
|
-
Undo.RegisterCreatedObjectUndo(go, "UCP Create GameObject");
|
|
31
|
-
|
|
32
|
-
// Optional parent
|
|
33
|
-
if (p != null && p.TryGetValue("parent", out var parentObj))
|
|
34
|
-
{
|
|
35
|
-
int parentId = Convert.ToInt32(parentObj);
|
|
36
|
-
var parent = FindGameObject(parentId);
|
|
37
|
-
go.transform.SetParent(parent.transform, false);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Optional position
|
|
41
|
-
if (p != null && p.TryGetValue("position", out var posObj) && posObj is List<object> pos && pos.Count >= 3)
|
|
42
|
-
{
|
|
43
|
-
go.transform.localPosition = new Vector3(
|
|
44
|
-
Convert.ToSingle(pos[0]),
|
|
45
|
-
Convert.ToSingle(pos[1]),
|
|
46
|
-
Convert.ToSingle(pos[2]));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Optional rotation (euler angles)
|
|
50
|
-
if (p != null && p.TryGetValue("rotation", out var rotObj) && rotObj is List<object> rot && rot.Count >= 3)
|
|
51
|
-
{
|
|
52
|
-
go.transform.localEulerAngles = new Vector3(
|
|
53
|
-
Convert.ToSingle(rot[0]),
|
|
54
|
-
Convert.ToSingle(rot[1]),
|
|
55
|
-
Convert.ToSingle(rot[2]));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
|
59
|
-
|
|
60
|
-
return new Dictionary<string, object>
|
|
61
|
-
{
|
|
62
|
-
["status"] = "ok",
|
|
63
|
-
["instanceId"] = go.GetInstanceID(),
|
|
64
|
-
["name"] = go.name
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private static object HandleDelete(string paramsJson)
|
|
69
|
-
{
|
|
70
|
-
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
71
|
-
if (p == null || !p.TryGetValue("instanceId", out var idObj))
|
|
72
|
-
throw new ArgumentException("Missing 'instanceId' parameter");
|
|
73
|
-
|
|
74
|
-
int instanceId = Convert.ToInt32(idObj);
|
|
75
|
-
var go = FindGameObject(instanceId);
|
|
76
|
-
string name = go.name;
|
|
77
|
-
|
|
78
|
-
Undo.DestroyObjectImmediate(go);
|
|
79
|
-
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
|
80
|
-
|
|
81
|
-
return new Dictionary<string, object>
|
|
82
|
-
{
|
|
83
|
-
["status"] = "ok",
|
|
84
|
-
["deleted"] = name,
|
|
85
|
-
["instanceId"] = instanceId
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private static object HandleReparent(string paramsJson)
|
|
90
|
-
{
|
|
91
|
-
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
92
|
-
if (p == null || !p.TryGetValue("instanceId", out var idObj))
|
|
93
|
-
throw new ArgumentException("Missing 'instanceId' parameter");
|
|
94
|
-
|
|
95
|
-
int instanceId = Convert.ToInt32(idObj);
|
|
96
|
-
var go = FindGameObject(instanceId);
|
|
97
|
-
|
|
98
|
-
Undo.SetTransformParent(go.transform, null, "UCP Reparent");
|
|
99
|
-
|
|
100
|
-
if (p.TryGetValue("parent", out var parentObj) && parentObj != null)
|
|
101
|
-
{
|
|
102
|
-
int parentId = Convert.ToInt32(parentObj);
|
|
103
|
-
var parent = FindGameObject(parentId);
|
|
104
|
-
Undo.SetTransformParent(go.transform, parent.transform, "UCP Reparent");
|
|
105
|
-
}
|
|
106
|
-
// else: parent = null means move to root
|
|
107
|
-
|
|
108
|
-
// Optional sibling index
|
|
109
|
-
if (p.TryGetValue("siblingIndex", out var sibObj))
|
|
110
|
-
{
|
|
111
|
-
go.transform.SetSiblingIndex(Convert.ToInt32(sibObj));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
|
115
|
-
|
|
116
|
-
return new Dictionary<string, object>
|
|
117
|
-
{
|
|
118
|
-
["status"] = "ok",
|
|
119
|
-
["instanceId"] = instanceId,
|
|
120
|
-
["name"] = go.name,
|
|
121
|
-
["parent"] = go.transform.parent != null ? go.transform.parent.gameObject.name : null
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private static object HandleInstantiate(string paramsJson)
|
|
126
|
-
{
|
|
127
|
-
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
128
|
-
if (p == null)
|
|
129
|
-
throw new ArgumentException("Missing parameters");
|
|
130
|
-
|
|
131
|
-
GameObject source = null;
|
|
132
|
-
|
|
133
|
-
// Instantiate from prefab asset path
|
|
134
|
-
if (p.TryGetValue("prefab", out var prefabObj) && prefabObj != null)
|
|
135
|
-
{
|
|
136
|
-
string prefabPath = prefabObj.ToString();
|
|
137
|
-
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
|
|
138
|
-
if (prefab == null)
|
|
139
|
-
throw new ArgumentException($"Prefab not found: {prefabPath}");
|
|
140
|
-
source = prefab;
|
|
141
|
-
}
|
|
142
|
-
// Instantiate from existing scene object (clone)
|
|
143
|
-
else if (p.TryGetValue("sourceId", out var srcObj))
|
|
144
|
-
{
|
|
145
|
-
int srcId = Convert.ToInt32(srcObj);
|
|
146
|
-
source = FindGameObject(srcId);
|
|
147
|
-
}
|
|
148
|
-
else
|
|
149
|
-
{
|
|
150
|
-
throw new ArgumentException("Must provide 'prefab' (asset path) or 'sourceId' (instanceId)");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
var instance = (GameObject)PrefabUtility.InstantiatePrefab(source);
|
|
154
|
-
if (instance == null)
|
|
155
|
-
{
|
|
156
|
-
// Fallback for non-prefab objects (e.g. cloning scene objects)
|
|
157
|
-
instance = UnityEngine.Object.Instantiate(source);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
Undo.RegisterCreatedObjectUndo(instance, "UCP Instantiate");
|
|
161
|
-
|
|
162
|
-
// Optional parent
|
|
163
|
-
if (p.TryGetValue("parent", out var parentObj) && parentObj != null)
|
|
164
|
-
{
|
|
165
|
-
int parentId = Convert.ToInt32(parentObj);
|
|
166
|
-
var parent = FindGameObject(parentId);
|
|
167
|
-
instance.transform.SetParent(parent.transform, false);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Optional name override
|
|
171
|
-
if (p.TryGetValue("name", out var nameObj) && nameObj != null)
|
|
172
|
-
{
|
|
173
|
-
instance.name = nameObj.ToString();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Optional position
|
|
177
|
-
if (p.TryGetValue("position", out var posObj) && posObj is List<object> pos && pos.Count >= 3)
|
|
178
|
-
{
|
|
179
|
-
instance.transform.localPosition = new Vector3(
|
|
180
|
-
Convert.ToSingle(pos[0]),
|
|
181
|
-
Convert.ToSingle(pos[1]),
|
|
182
|
-
Convert.ToSingle(pos[2]));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
|
186
|
-
|
|
187
|
-
return new Dictionary<string, object>
|
|
188
|
-
{
|
|
189
|
-
["status"] = "ok",
|
|
190
|
-
["instanceId"] = instance.GetInstanceID(),
|
|
191
|
-
["name"] = instance.name
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private static object HandleAddComponent(string paramsJson)
|
|
196
|
-
{
|
|
197
|
-
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
198
|
-
if (p == null || !p.TryGetValue("instanceId", out var idObj))
|
|
199
|
-
throw new ArgumentException("Missing 'instanceId' parameter");
|
|
200
|
-
if (!p.TryGetValue("type", out var typeObj) || typeObj == null)
|
|
201
|
-
throw new ArgumentException("Missing 'type' parameter");
|
|
202
|
-
|
|
203
|
-
int instanceId = Convert.ToInt32(idObj);
|
|
204
|
-
var go = FindGameObject(instanceId);
|
|
205
|
-
string typeName = typeObj.ToString();
|
|
206
|
-
|
|
207
|
-
// Resolve component type
|
|
208
|
-
Type compType = ResolveComponentType(typeName);
|
|
209
|
-
if (compType == null)
|
|
210
|
-
throw new ArgumentException($"Component type not found: {typeName}");
|
|
211
|
-
|
|
212
|
-
var comp = Undo.AddComponent(go, compType);
|
|
213
|
-
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
|
214
|
-
|
|
215
|
-
return new Dictionary<string, object>
|
|
216
|
-
{
|
|
217
|
-
["status"] = "ok",
|
|
218
|
-
["instanceId"] = instanceId,
|
|
219
|
-
["component"] = comp.GetType().Name,
|
|
220
|
-
["componentFullType"] = comp.GetType().FullName
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private static object HandleRemoveComponent(string paramsJson)
|
|
225
|
-
{
|
|
226
|
-
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
227
|
-
if (p == null || !p.TryGetValue("instanceId", out var idObj))
|
|
228
|
-
throw new ArgumentException("Missing 'instanceId' parameter");
|
|
229
|
-
if (!p.TryGetValue("type", out var typeObj) || typeObj == null)
|
|
230
|
-
throw new ArgumentException("Missing 'type' parameter");
|
|
231
|
-
|
|
232
|
-
int instanceId = Convert.ToInt32(idObj);
|
|
233
|
-
var go = FindGameObject(instanceId);
|
|
234
|
-
string typeName = typeObj.ToString();
|
|
235
|
-
|
|
236
|
-
Component target = null;
|
|
237
|
-
foreach (var c in go.GetComponents<Component>())
|
|
238
|
-
{
|
|
239
|
-
if (c == null) continue;
|
|
240
|
-
if (c.GetType().Name == typeName || c.GetType().FullName == typeName)
|
|
241
|
-
{
|
|
242
|
-
target = c;
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (target == null)
|
|
248
|
-
throw new ArgumentException($"Component '{typeName}' not found on '{go.name}'");
|
|
249
|
-
if (target is Transform)
|
|
250
|
-
throw new ArgumentException("Cannot remove Transform component");
|
|
251
|
-
|
|
252
|
-
Undo.DestroyObjectImmediate(target);
|
|
253
|
-
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
|
254
|
-
|
|
255
|
-
return new Dictionary<string, object>
|
|
256
|
-
{
|
|
257
|
-
["status"] = "ok",
|
|
258
|
-
["instanceId"] = instanceId,
|
|
259
|
-
["removed"] = typeName
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
private static Type ResolveComponentType(string typeName)
|
|
264
|
-
{
|
|
265
|
-
// Try exact match first
|
|
266
|
-
var type = Type.GetType(typeName);
|
|
267
|
-
if (type != null && typeof(Component).IsAssignableFrom(type))
|
|
268
|
-
return type;
|
|
269
|
-
|
|
270
|
-
// Search Unity assemblies
|
|
271
|
-
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
272
|
-
{
|
|
273
|
-
foreach (var t in assembly.GetTypes())
|
|
274
|
-
{
|
|
275
|
-
if (!typeof(Component).IsAssignableFrom(t)) continue;
|
|
276
|
-
if (t.Name == typeName || t.FullName == typeName)
|
|
277
|
-
return t;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Common Unity types fallback
|
|
282
|
-
var unityType = Type.GetType($"UnityEngine.{typeName}, UnityEngine.CoreModule");
|
|
283
|
-
if (unityType != null && typeof(Component).IsAssignableFrom(unityType))
|
|
284
|
-
return unityType;
|
|
285
|
-
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
private static GameObject FindGameObject(int instanceId)
|
|
290
|
-
{
|
|
291
|
-
var obj = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
|
|
292
|
-
if (obj != null) return obj;
|
|
293
|
-
|
|
294
|
-
for (int i = 0; i < SceneManager.sceneCount; i++)
|
|
295
|
-
{
|
|
296
|
-
var scene = SceneManager.GetSceneAt(i);
|
|
297
|
-
if (!scene.isLoaded) continue;
|
|
298
|
-
foreach (var root in scene.GetRootGameObjects())
|
|
299
|
-
{
|
|
300
|
-
var found = FindInHierarchy(root, instanceId);
|
|
301
|
-
if (found != null) return found;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
throw new ArgumentException($"GameObject not found: {instanceId}");
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
private static GameObject FindInHierarchy(GameObject go, int instanceId)
|
|
309
|
-
{
|
|
310
|
-
if (go.GetInstanceID() == instanceId) return go;
|
|
311
|
-
for (int i = 0; i < go.transform.childCount; i++)
|
|
312
|
-
{
|
|
313
|
-
var found = FindInHierarchy(go.transform.GetChild(i).gameObject, instanceId);
|
|
314
|
-
if (found != null) return found;
|
|
315
|
-
}
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
using System;
|
|
2
|
-
using System.Collections.Generic;
|
|
3
|
-
using System.Linq;
|
|
4
|
-
using System.Text.RegularExpressions;
|
|
5
|
-
using UnityEngine;
|
|
6
|
-
|
|
7
|
-
namespace UCP.Bridge
|
|
8
|
-
{
|
|
9
|
-
public static class LogsController
|
|
10
|
-
{
|
|
11
|
-
private const int MaxHistoryEntries = 2000;
|
|
12
|
-
private const int DefaultSearchWindow = 200;
|
|
13
|
-
private const int MaxPreviewLength = 200;
|
|
14
|
-
|
|
15
|
-
private static readonly object s_historyLock = new object();
|
|
16
|
-
private static readonly List<LogRecord> s_history = new List<LogRecord>();
|
|
17
|
-
private static long s_nextId = 1;
|
|
18
|
-
|
|
19
|
-
public static void Register(CommandRouter router)
|
|
20
|
-
{
|
|
21
|
-
router.Register("logs/subscribe", _ => new Dictionary<string, object> { ["subscribed"] = true });
|
|
22
|
-
router.Register("logs/unsubscribe", _ => new Dictionary<string, object> { ["unsubscribed"] = true });
|
|
23
|
-
router.Register("logs/tail", HandleTail);
|
|
24
|
-
router.Register("logs/search", HandleSearch);
|
|
25
|
-
router.Register("logs/get", HandleGet);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public static Dictionary<string, object> RecordLog(string message, string stackTrace, LogType type)
|
|
29
|
-
{
|
|
30
|
-
return RecordLog(NormalizeLevel(type), message, stackTrace);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public static void ClearHistoryForTests()
|
|
34
|
-
{
|
|
35
|
-
lock (s_historyLock)
|
|
36
|
-
{
|
|
37
|
-
s_history.Clear();
|
|
38
|
-
s_nextId = 1;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
public static Dictionary<string, object> RecordTestLog(string level, string message, string stackTrace = "")
|
|
43
|
-
{
|
|
44
|
-
return RecordLog(level, message, stackTrace);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private static object HandleTail(string paramsJson)
|
|
48
|
-
{
|
|
49
|
-
var query = ParseQuery(paramsJson, includePattern: false);
|
|
50
|
-
return BuildListResult(QueryHistory(query));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private static object HandleSearch(string paramsJson)
|
|
54
|
-
{
|
|
55
|
-
var query = ParseQuery(paramsJson, includePattern: true);
|
|
56
|
-
return BuildListResult(QueryHistory(query));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private static object HandleGet(string paramsJson)
|
|
60
|
-
{
|
|
61
|
-
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
62
|
-
if (p == null || !p.TryGetValue("id", out var idObj))
|
|
63
|
-
throw new ArgumentException("Missing 'id' parameter");
|
|
64
|
-
|
|
65
|
-
long id = Convert.ToInt64(idObj);
|
|
66
|
-
|
|
67
|
-
lock (s_historyLock)
|
|
68
|
-
{
|
|
69
|
-
var entry = s_history.FirstOrDefault(record => record.Id == id);
|
|
70
|
-
if (entry == null)
|
|
71
|
-
throw new ArgumentException($"Log entry not found: {id}");
|
|
72
|
-
|
|
73
|
-
return SerializeFull(entry);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private static Dictionary<string, object> RecordLog(string level, string message, string stackTrace)
|
|
78
|
-
{
|
|
79
|
-
lock (s_historyLock)
|
|
80
|
-
{
|
|
81
|
-
var entry = new LogRecord
|
|
82
|
-
{
|
|
83
|
-
Id = s_nextId++,
|
|
84
|
-
Level = NormalizeLevel(level),
|
|
85
|
-
Message = message ?? string.Empty,
|
|
86
|
-
StackTrace = stackTrace ?? string.Empty,
|
|
87
|
-
Timestamp = DateTime.UtcNow.ToString("o")
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
s_history.Add(entry);
|
|
91
|
-
if (s_history.Count > MaxHistoryEntries)
|
|
92
|
-
s_history.RemoveAt(0);
|
|
93
|
-
|
|
94
|
-
return SerializeFull(entry);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private static LogQuery ParseQuery(string paramsJson, bool includePattern)
|
|
99
|
-
{
|
|
100
|
-
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
101
|
-
var query = new LogQuery();
|
|
102
|
-
|
|
103
|
-
if (p != null)
|
|
104
|
-
{
|
|
105
|
-
if (p.TryGetValue("level", out var levelObj) && levelObj != null)
|
|
106
|
-
query.Level = NormalizeLevel(levelObj.ToString());
|
|
107
|
-
if (p.TryGetValue("count", out var countObj) && countObj != null)
|
|
108
|
-
query.Count = Math.Max(1, Convert.ToInt32(countObj));
|
|
109
|
-
if (p.TryGetValue("beforeId", out var beforeObj) && beforeObj != null)
|
|
110
|
-
query.BeforeId = Convert.ToInt64(beforeObj);
|
|
111
|
-
if (p.TryGetValue("afterId", out var afterObj) && afterObj != null)
|
|
112
|
-
query.AfterId = Convert.ToInt64(afterObj);
|
|
113
|
-
|
|
114
|
-
if (includePattern && p.TryGetValue("pattern", out var patternObj) && patternObj != null)
|
|
115
|
-
{
|
|
116
|
-
query.Pattern = patternObj.ToString();
|
|
117
|
-
try
|
|
118
|
-
{
|
|
119
|
-
query.Regex = new Regex(query.Pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
120
|
-
}
|
|
121
|
-
catch (Exception ex)
|
|
122
|
-
{
|
|
123
|
-
throw new ArgumentException($"Invalid regex pattern: {ex.Message}");
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (query.Count <= 0)
|
|
129
|
-
query.Count = DefaultSearchWindow;
|
|
130
|
-
|
|
131
|
-
return query;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private static LogQueryResult QueryHistory(LogQuery query)
|
|
135
|
-
{
|
|
136
|
-
lock (s_historyLock)
|
|
137
|
-
{
|
|
138
|
-
IEnumerable<LogRecord> candidates = s_history;
|
|
139
|
-
|
|
140
|
-
if (query.BeforeId.HasValue)
|
|
141
|
-
candidates = candidates.Where(entry => entry.Id < query.BeforeId.Value);
|
|
142
|
-
if (query.AfterId.HasValue)
|
|
143
|
-
candidates = candidates.Where(entry => entry.Id > query.AfterId.Value);
|
|
144
|
-
if (!string.IsNullOrEmpty(query.Level))
|
|
145
|
-
candidates = candidates.Where(entry => PassesLevel(entry.Level, query.Level));
|
|
146
|
-
|
|
147
|
-
if (query.Regex != null)
|
|
148
|
-
{
|
|
149
|
-
candidates = candidates.Where(entry =>
|
|
150
|
-
query.Regex.IsMatch(entry.Message)
|
|
151
|
-
|| (!string.IsNullOrEmpty(entry.StackTrace) && query.Regex.IsMatch(entry.StackTrace))
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
var allMatches = candidates.OrderByDescending(entry => entry.Id).ToList();
|
|
156
|
-
var returned = allMatches.Take(query.Count).Select(SerializeSummary).ToList();
|
|
157
|
-
|
|
158
|
-
return new LogQueryResult
|
|
159
|
-
{
|
|
160
|
-
Total = allMatches.Count,
|
|
161
|
-
Returned = returned,
|
|
162
|
-
Truncated = allMatches.Count > returned.Count
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private static Dictionary<string, object> BuildListResult(LogQueryResult queryResult)
|
|
168
|
-
{
|
|
169
|
-
return new Dictionary<string, object>
|
|
170
|
-
{
|
|
171
|
-
["logs"] = queryResult.Returned.Cast<object>().ToList(),
|
|
172
|
-
["total"] = queryResult.Total,
|
|
173
|
-
["returned"] = queryResult.Returned.Count,
|
|
174
|
-
["truncated"] = queryResult.Truncated
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
private static Dictionary<string, object> SerializeSummary(LogRecord entry)
|
|
179
|
-
{
|
|
180
|
-
return new Dictionary<string, object>
|
|
181
|
-
{
|
|
182
|
-
["id"] = entry.Id,
|
|
183
|
-
["level"] = entry.Level,
|
|
184
|
-
["timestamp"] = entry.Timestamp,
|
|
185
|
-
["messagePreview"] = Preview(entry.Message, MaxPreviewLength),
|
|
186
|
-
["hasStackTrace"] = !string.IsNullOrEmpty(entry.StackTrace)
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private static Dictionary<string, object> SerializeFull(LogRecord entry)
|
|
191
|
-
{
|
|
192
|
-
return new Dictionary<string, object>
|
|
193
|
-
{
|
|
194
|
-
["id"] = entry.Id,
|
|
195
|
-
["level"] = entry.Level,
|
|
196
|
-
["timestamp"] = entry.Timestamp,
|
|
197
|
-
["message"] = entry.Message,
|
|
198
|
-
["stackTrace"] = entry.StackTrace
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private static bool PassesLevel(string value, string threshold)
|
|
203
|
-
{
|
|
204
|
-
return Severity(value) >= Severity(threshold);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private static int Severity(string level)
|
|
208
|
-
{
|
|
209
|
-
switch (NormalizeLevel(level))
|
|
210
|
-
{
|
|
211
|
-
case "error":
|
|
212
|
-
case "exception":
|
|
213
|
-
return 2;
|
|
214
|
-
case "warning":
|
|
215
|
-
return 1;
|
|
216
|
-
default:
|
|
217
|
-
return 0;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private static string NormalizeLevel(LogType type)
|
|
222
|
-
{
|
|
223
|
-
switch (type)
|
|
224
|
-
{
|
|
225
|
-
case LogType.Error:
|
|
226
|
-
case LogType.Assert:
|
|
227
|
-
return "error";
|
|
228
|
-
case LogType.Exception:
|
|
229
|
-
return "exception";
|
|
230
|
-
case LogType.Warning:
|
|
231
|
-
return "warning";
|
|
232
|
-
default:
|
|
233
|
-
return "info";
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
private static string NormalizeLevel(string level)
|
|
238
|
-
{
|
|
239
|
-
if (string.IsNullOrEmpty(level))
|
|
240
|
-
return "info";
|
|
241
|
-
|
|
242
|
-
var normalized = level.Trim().ToLowerInvariant();
|
|
243
|
-
switch (normalized)
|
|
244
|
-
{
|
|
245
|
-
case "warn":
|
|
246
|
-
return "warning";
|
|
247
|
-
case "err":
|
|
248
|
-
return "error";
|
|
249
|
-
default:
|
|
250
|
-
return normalized;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private static string Preview(string value, int maxChars)
|
|
255
|
-
{
|
|
256
|
-
if (string.IsNullOrEmpty(value) || value.Length <= maxChars)
|
|
257
|
-
return value ?? string.Empty;
|
|
258
|
-
|
|
259
|
-
return value.Substring(0, maxChars) + "...";
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private sealed class LogRecord
|
|
263
|
-
{
|
|
264
|
-
public long Id;
|
|
265
|
-
public string Level;
|
|
266
|
-
public string Message;
|
|
267
|
-
public string StackTrace;
|
|
268
|
-
public string Timestamp;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
private sealed class LogQuery
|
|
272
|
-
{
|
|
273
|
-
public string Level;
|
|
274
|
-
public string Pattern;
|
|
275
|
-
public Regex Regex;
|
|
276
|
-
public int Count;
|
|
277
|
-
public long? BeforeId;
|
|
278
|
-
public long? AfterId;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
private sealed class LogQueryResult
|
|
282
|
-
{
|
|
283
|
-
public int Total;
|
|
284
|
-
public List<Dictionary<string, object>> Returned;
|
|
285
|
-
public bool Truncated;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|