Files
MAGI/unity/Assets/Guildlib/Editor/GuildlibSyncWindow.cs
2026-03-16 21:38:49 +01:00

233 lines
9.4 KiB
C#

#if UNITY_EDITOR
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using Guildlib.Runtime;
using Newtonsoft.Json;
namespace Guildlib.Editor
{
public class GuildlibSyncWindow : EditorWindow
{
GuildlibConfig config;
string log = "";
bool busy = false;
Vector2 scroll;
string statusText = "Not connected";
bool statusOk = false;
// Platform / tag filter toggles for pull operations
bool filterByPlatform = false;
bool filterByTag = false;
string platformFilter = "";
string tagFilter = "";
[MenuItem("Window/Guildlib/Sync Panel")]
static void Open() => GetWindow<GuildlibSyncWindow>("Guildlib").Show();
void OnEnable()
{
config = GuildlibConfig.Load();
if (config == null)
Log("No GuildlibConfig found. Create one via Assets > Create > Guildlib > Config.");
}
void OnGUI()
{
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Guildlib Sync Panel", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
config = (GuildlibConfig)EditorGUILayout.ObjectField("Config", config, typeof(GuildlibConfig), false);
if (config == null)
{
EditorGUILayout.HelpBox("Assign a GuildlibConfig asset.", MessageType.Warning);
return;
}
EditorGUILayout.LabelField("Server", config.serverUrl, EditorStyles.miniLabel);
var style = statusOk ? EditorStyles.boldLabel : EditorStyles.miniLabel;
EditorGUILayout.LabelField("Status", statusText, style);
EditorGUILayout.Space(8);
// ── Filters ──────────────────────────────────────────────────────
EditorGUILayout.LabelField("Pull filters (optional)", EditorStyles.boldLabel);
filterByPlatform = EditorGUILayout.Toggle("Filter by platform", filterByPlatform);
if (filterByPlatform)
{
EditorGUI.indentLevel++;
platformFilter = EditorGUILayout.TextField("Platforms (comma-separated)", platformFilter);
EditorGUILayout.HelpBox(
"Example: pc,ps5\n" +
"Valid values: " + string.Join(", ", GuildPlatformRegistry.All),
MessageType.Info);
EditorGUI.indentLevel--;
}
filterByTag = EditorGUILayout.Toggle("Filter by tag", filterByTag);
if (filterByTag)
{
EditorGUI.indentLevel++;
tagFilter = EditorGUILayout.TextField("Tags (comma-separated)", tagFilter);
EditorGUILayout.HelpBox(
"Example: release,base_game\n" +
"Valid values: " + string.Join(", ", GuildTagRegistry.All),
MessageType.Info);
EditorGUI.indentLevel--;
}
EditorGUILayout.Space(8);
// ── Action buttons ────────────────────────────────────────────────
EditorGUI.BeginDisabledGroup(busy);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Ping Server", GUILayout.Height(28))) RunAsync(PingAsync);
if (GUILayout.Button("Pull All Shards", GUILayout.Height(28))) RunAsync(PullAllAsync);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Export Schema", GUILayout.Height(28))) RunAsync(ExportAsync);
if (GUILayout.Button("Upload Schema", GUILayout.Height(28))) RunAsync(UploadAsync);
EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space(8);
EditorGUILayout.LabelField("Log", EditorStyles.boldLabel);
scroll = EditorGUILayout.BeginScrollView(scroll, GUILayout.Height(240));
EditorGUILayout.TextArea(log, GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
if (GUILayout.Button("Clear", GUILayout.Height(18))) log = "";
}
// ── Handlers ─────────────────────────────────────────────────────────
async Task PingAsync()
{
Log("Pinging...");
try
{
using var http = MakeHttp();
var json = await http.GetStringAsync($"{config.serverUrl}/health");
var data = JsonConvert.DeserializeObject<dynamic>(json);
statusOk = true;
statusText = $"Online — shards: {string.Join(", ", ((Newtonsoft.Json.Linq.JArray)data.shards) ?? new Newtonsoft.Json.Linq.JArray())}";
Log($"OK — {statusText}");
}
catch (Exception e) { statusOk = false; statusText = "Unreachable"; Log($"FAIL: {e.Message}"); }
}
async Task PullAllAsync()
{
var platforms = filterByPlatform && !string.IsNullOrWhiteSpace(platformFilter)
? new System.Collections.Generic.List<string>(platformFilter.Split(',', StringSplitOptions.RemoveEmptyEntries))
: null;
var tagList = filterByTag && !string.IsNullOrWhiteSpace(tagFilter)
? new System.Collections.Generic.List<string>(tagFilter.Split(',', StringSplitOptions.RemoveEmptyEntries))
: null;
if (platforms != null) Log($"Platform filter: {string.Join(", ", platforms)}");
if (tagList != null) Log($"Tag filter: {string.Join(", ", tagList)}");
// Determine which shards to sync
var targets = config.GetSyncTargets();
if (targets == null)
{
// Ask server for all available shards
using var http = MakeHttp();
var json = await http.GetStringAsync($"{config.serverUrl}/health");
var data = JsonConvert.DeserializeObject<dynamic>(json);
var arr = (Newtonsoft.Json.Linq.JArray)data.shards;
targets = arr?.ToObject<string[]>() ?? Array.Empty<string>();
}
Log($"Pulling {targets.Length} shard(s): {string.Join(", ", targets)}");
int totalAdded = 0, totalUpdated = 0, totalSkipped = 0;
foreach (var shard in targets)
{
try
{
var client = new ShardClient(config, shard);
var result = await client.PullAsync(platforms, tagList);
totalAdded += result.added;
totalUpdated += result.updated;
totalSkipped += result.skipped;
Log($" {shard}: +{result.added} added ~{result.updated} updated {result.skipped} unchanged");
}
catch (Exception e) { Log($" {shard}: FAIL — {e.Message}"); }
}
Log($"Done — added:{totalAdded} updated:{totalUpdated} skipped:{totalSkipped}");
AssetDatabase.Refresh();
}
async Task ExportAsync()
{
Log("Exporting schema...");
try
{
var json = SchemaExporter.Export(config.projectId);
var dir = Path.Combine(Application.dataPath, "..", "GuildlibExports");
Directory.CreateDirectory(dir);
var path = Path.Combine(dir, "schema.json");
File.WriteAllText(path, json);
Log($"Exported to: {path}");
}
catch (Exception e) { Log($"FAIL: {e.Message}"); }
await Task.CompletedTask;
}
async Task UploadAsync()
{
Log("Uploading schema...");
try
{
var json = SchemaExporter.Export(config.projectId);
using var http = MakeHttp();
var resp = await http.PostAsync(
$"{config.serverUrl}/schema",
new StringContent(json, Encoding.UTF8, "application/json"));
resp.EnsureSuccessStatusCode();
Log("Schema uploaded.");
}
catch (Exception e) { Log($"FAIL: {e.Message}"); }
}
// ── Helpers ───────────────────────────────────────────────────────────
HttpClient MakeHttp()
{
var h = new HttpClient();
if (!string.IsNullOrEmpty(config.apiKey))
h.DefaultRequestHeaders.Add("X-Api-Key", config.apiKey);
return h;
}
void Log(string msg)
{
log = $"[{DateTime.Now:HH:mm:ss}] {msg}\n{log}";
Repaint();
}
void RunAsync(Func<Task> fn)
{
busy = true;
fn().ContinueWith(_ =>
{
busy = false;
EditorApplication.delayCall += Repaint;
});
}
}
}
#endif