233 lines
9.4 KiB
C#
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
|