#if UNITY_EDITOR using System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Text; using System.Threading.Tasks; using UnityEditor; using UnityEngine; using Debug = UnityEngine.Debug; namespace AOT.Editor.YooAssetTools { /// Explicit, incremental publication of the current WebGL resource packages. public static class CdnPublisher { static bool _busy; static string Repo => Path.GetFullPath(Path.Combine(Application.dataPath, "../..")); static string Config => Path.Combine(Repo, "Tools/cdn_publish.local.json"); static string LogPath => Path.Combine(Repo, "Tools/output/cdn_publish/last-unity.log"); public static string Status { get; private set; } = "idle"; [MenuItem("AIGame/CDN/Check SSH Connection")] public static async void CheckSshMenu() => await Guard(() => RunPython("check")); [MenuItem("AIGame/CDN/Preview Upload Changes")] public static async void PreviewMenu() => await Guard(() => RunPython("plan --compare")); [MenuItem("AIGame/CDN/Upload Staged Resources")] public static async void UploadMenu() => await Guard(() => RunPython("upload")); [MenuItem("AIGame/CDN/Build And Upload (Art + HotUpdate)")] public static async void BuildAndUploadMenu() => await Guard(async () => { await RunPython("check"); BuildPackages(); await RunPython("upload"); }); [MenuItem("AIGame/CDN/Prepare SSH Key")] public static async void PrepareKeyMenu() => await Guard(() => RunPython("init-key")); [MenuItem("AIGame/CDN/Open Publish Folder")] public static void OpenPublishFolder() { Directory.CreateDirectory(Path.GetDirectoryName(LogPath)); EditorUtility.RevealInFinder(Path.GetDirectoryName(LogPath)); } public static string BuildPackages() { if (EditorApplication.isPlaying || EditorApplication.isCompiling) throw new InvalidOperationException("Stop Play Mode and finish compiling before building CDN resources."); if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.WebGL) throw new InvalidOperationException("CDN packages must be built for WebGL. Select that target first."); Status = "building Art"; var art = BuildAndStageArt.BuildConfiguredAndStage(); if (!art.StartsWith("ok", StringComparison.Ordinal)) throw new InvalidOperationException(art); Debug.Log("[CDN] " + art); Status = "building HotUpdate"; var raw = BuildAndStageHotUpdate.BuildAndStage(); if (!raw.StartsWith("ok", StringComparison.Ordinal)) throw new InvalidOperationException(raw); Debug.Log("[CDN] " + raw); Status = "packages staged"; return art + " | " + raw; } static async Task Guard(Func action) { if (_busy) { Debug.LogWarning("[CDN] A publish command is already running."); return; } _busy = true; try { await action(); Status = "completed"; Debug.Log("[CDN] Completed. Log: " + LogPath); } catch (Exception error) { Status = "failed"; Debug.LogError("[CDN] " + error.Message); } finally { _busy = false; } } static async Task RunPython(string command) { if (!File.Exists(Config)) File.Copy(Path.Combine(Repo, "Tools/cdn_publish.example.json"), Config); Status = command; var pending = new ConcurrentQueue(); var start = new ProcessStartInfo { FileName = EditorPrefs.GetString("AIGame.CdnPython", "python"), Arguments = "\"" + Path.Combine(Repo, "Tools/publish_cdn.py") + "\" " + command + " --config \"" + Config + "\"", WorkingDirectory = Repo, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; start.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8"; start.EnvironmentVariables["PYTHONUNBUFFERED"] = "1"; Directory.CreateDirectory(Path.GetDirectoryName(LogPath)); using (var log = new StreamWriter(LogPath, true, new UTF8Encoding(false))) using (var process = new Process { StartInfo = start }) { log.WriteLine(DateTime.UtcNow.ToString("o") + " " + command); process.OutputDataReceived += (_, e) => { if (e.Data != null) pending.Enqueue(e.Data); }; process.ErrorDataReceived += (_, e) => { if (e.Data != null) pending.Enqueue(e.Data); }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); while (!process.HasExited) { while (pending.TryDequeue(out var line)) { Debug.Log("[CDN] " + line); log.WriteLine(line); } log.Flush(); await Task.Delay(150); } process.WaitForExit(); while (pending.TryDequeue(out var line)) { Debug.Log("[CDN] " + line); log.WriteLine(line); } if (process.ExitCode != 0) throw new InvalidOperationException("Publish command failed. See " + LogPath); } } } } #endif