using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; using Newtonsoft.Json; using UnityEditor; using UnityEngine; namespace AibisDream.EditorTools { [Serializable] public class TimelineNameCacheEntry { public string guid; public string assetPath; public string fileHash; public string lastModified; public string timelineName; public string gameObjectPath; public bool isInScene; public int lineNumber; } [Serializable] public class TimelineNameCache { public const int CurrentVersion = 1; public int version = CurrentVersion; public string lastFullScanTime; public List entries = new List(); private static readonly string CacheFileName = "timeline_name_cache.json"; public static string CacheFolderFullPath => Path.Combine(Application.dataPath, "Editor/TimelineNameCollector/Cache"); public static string CacheFileFullPath => Path.Combine(CacheFolderFullPath, CacheFileName); public static TimelineNameCache Load() { try { if (!File.Exists(CacheFileFullPath)) { return new TimelineNameCache { version = CurrentVersion, lastFullScanTime = null, entries = new List() }; } var json = File.ReadAllText(CacheFileFullPath, Encoding.UTF8); if (string.IsNullOrEmpty(json)) { return new TimelineNameCache { version = CurrentVersion, lastFullScanTime = null, entries = new List() }; } var cache = JsonConvert.DeserializeObject(json); if (cache == null) { cache = new TimelineNameCache(); } if (cache.version != CurrentVersion) { cache.version = CurrentVersion; cache.lastFullScanTime = null; cache.entries = cache.entries ?? new List(); } return cache; } catch (Exception e) { Debug.LogError($"[TimelineNameCache] Failed to load cache: {e}"); return new TimelineNameCache { version = CurrentVersion, lastFullScanTime = null, entries = new List() }; } } public void Save() { try { if (!Directory.Exists(CacheFolderFullPath)) { Directory.CreateDirectory(CacheFolderFullPath); } entries ??= new List(); var settings = new JsonSerializerSettings { Formatting = Formatting.Indented, StringEscapeHandling = StringEscapeHandling.Default }; var json = JsonConvert.SerializeObject(this, settings); File.WriteAllText(CacheFileFullPath, json, Encoding.UTF8); } catch (Exception e) { Debug.LogError($"[TimelineNameCache] Failed to save cache: {e}"); } } public void SetLastFullScanNow() { lastFullScanTime = DateTime.UtcNow.ToString("o"); } public static string ComputeFileHash(string fullPath) { try { if (!File.Exists(fullPath)) { return null; } using (var stream = File.OpenRead(fullPath)) using (var md5 = MD5.Create()) { var hash = md5.ComputeHash(stream); var sb = new StringBuilder(hash.Length * 2); foreach (var b in hash) { sb.Append(b.ToString("x2")); } return sb.ToString(); } } catch (Exception e) { Debug.LogError($"[TimelineNameCache] Failed to compute file hash for '{fullPath}': {e}"); return null; } } public static string GetAssetFullPath(string assetPath) { if (string.IsNullOrEmpty(assetPath)) return null; if (!assetPath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) && !assetPath.Equals("Assets", StringComparison.OrdinalIgnoreCase)) { return null; } var relative = assetPath.Substring("Assets".Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); return Path.Combine(Application.dataPath, relative); } public void RemoveEntriesForAsset(string assetPath) { if (entries == null || string.IsNullOrEmpty(assetPath)) return; entries.RemoveAll(e => e != null && e.assetPath == assetPath); } public void CleanupDeletedAssets() { if (entries == null) return; entries.RemoveAll(e => { if (e == null || string.IsNullOrEmpty(e.assetPath)) return true; return AssetDatabase.LoadMainAssetAtPath(e.assetPath) == null; }); } } }