using System; using System.IO; using UnityEngine; namespace OnDeviceAgent.Inference { // Returns a readable ABSOLUTE directory for a "Model/" root so the sentis packages can File.* it. // Non-Android (Editor & Standalone): ModelPathResolver already gives a real readable directory - the // owning package's Models~ in the Editor, streamingAssetsPath/ in a player. // Android: StreamingAssets lives inside the APK and File.* can't read it, so the tree is staged once into // persistentDataPath (idempotent, like LiteRtModelProvisioner) from the build-generated files_manifest.txt. public static class ModelRootProvisioner { const string ManifestFile = "files_manifest.txt"; public static string EnsureModelRoot(string subRelative) { var sub = (subRelative ?? string.Empty).Replace('\\', '/').TrimEnd('/'); if (Application.platform != RuntimePlatform.Android) return ModelPathResolver.GetModelFilePath(sub); return StageToPersistentData(sub); } static string StageToPersistentData(string sub) { var destRoot = Path.Combine(Application.persistentDataPath, sub.Replace('/', Path.DirectorySeparatorChar)); var manifest = StreamingAssetsModelLoader.LoadText(sub + "/" + ManifestFile, "[ModelRoot]"); var lines = manifest.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); foreach (var raw in lines) { var rel = raw.Trim().Replace('\\', '/'); if (rel.Length == 0 || rel[0] == '#') continue; var dest = Path.Combine(destRoot, rel.Replace('/', Path.DirectorySeparatorChar)); if (File.Exists(dest) && new FileInfo(dest).Length > 0) continue; try { var bytes = StreamingAssetsModelLoader.LoadBytes(sub + "/" + rel, "[ModelRoot]"); var destDir = Path.GetDirectoryName(dest); if (!string.IsNullOrEmpty(destDir)) Directory.CreateDirectory(destDir); File.WriteAllBytes(dest, bytes); } catch (Exception exception) { Debug.LogWarning($"[ModelRoot] skipped staging '{rel}' under {sub}: {exception.Message}"); } } return destRoot; } } }