#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("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(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(platformFilter.Split(',', StringSplitOptions.RemoveEmptyEntries)) : null; var tagList = filterByTag && !string.IsNullOrWhiteSpace(tagFilter) ? new System.Collections.Generic.List(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(json); var arr = (Newtonsoft.Json.Linq.JArray)data.shards; targets = arr?.ToObject() ?? Array.Empty(); } 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 fn) { busy = true; fn().ContinueWith(_ => { busy = false; EditorApplication.delayCall += Repaint; }); } } } #endif