using UnityEngine; using UnityEditor; using System.IO; using System.Collections.Generic; using System.Linq; using System.Collections; using System; using UnityEngine.Networking; namespace Amanotes.PackageManager { [InitializeOnLoad] public class PackageManager : AssetPostprocessor { private const string PACKAGE_LIST_URL = "https://margix.neocities.org/list.json"; private const string RELATIVE_PATH = ""; private static readonly string s_baseDir = Path.Combine(Application.dataPath, RELATIVE_PATH); private static string s_packageListFile = Path.Combine(Application.persistentDataPath, "list.json"); private static string s_packageFile = Path.Combine(s_baseDir, "package.json"); private static string s_installedPackageFile = Path.Combine(Application.dataPath, "installedPackages.json"); private static bool s_isImporting = false; private static string s_currentPackageName; static PackageManager() { // Debug.Log(s_installedPackageFile); if (!Application.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode) { AssetDatabase.importPackageCompleted += ImportPackageCompleted; AssetDatabase.importPackageStarted += ImportPackageStarted; AssetDatabase.importPackageFailed += ImportPackageFailed; StartFileWatcher(s_baseDir); StartFileWatcher(Application.persistentDataPath); if (LoadPackages()) { if (!IsAllPackagesInstalled()) { EditorCoroutine.start(InstallPackages()); } } } } private static bool IsAllPackagesInstalled() { foreach (var package in s_packages.Packages) { if (!s_installedPackages.Packages.Any(e => e.GetPackageName() == package.GetPackageName())) { return false; } } return true; } static void OnPostprocessAllAssets( string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { var packageName = PlayerPrefs.GetString(PACKAGE_NAME, null); if (!string.IsNullOrEmpty(packageName)) { var guids = PlayerPrefs.GetString(PACKAGE_GUIDS, ""); foreach (string str in importedAssets) { var guid = AssetDatabase.AssetPathToGUID(str); if (guids.IndexOf(guid) > 0) { Debug.LogWarning("Ignore labeling: " + str); continue; } var asset = AssetImporter.GetAtPath(str); AssetDatabase.SetLabels(asset, new[] { packageName }); // if(str.IndexOf("installedPackages.json") == -1 // && str.StartsWith("Assets")) { // var asset = AssetImporter.GetAtPath(str); // // Debug.Log($"Labeling: {packageName}, {str}"); // AssetDatabase.SetLabels(asset, new[] { packageName }); // } // else { // Debug.LogWarning("Ignore labeling: " + str); // } } AssetDatabase.SaveAssets(); } } public static bool LoadPackages() { if (File.Exists(s_packageListFile)) { s_packageList = LoadPackageList(s_packageListFile); s_packages = LoadPackageMetaList(s_packageFile); s_installedPackages = LoadPackageMetaList(s_installedPackageFile); return true; } UpdatePackageList((file) => { if (LoadPackages()) { EditorCoroutine.start(InstallPackages()); } else { Debug.LogError("Could not load package list."); } }); return false; } public static void UpdatePackageList(Action callback) { DownloadManager.Download($"{PACKAGE_LIST_URL}?nonce={DateTime.Now.Ticks}", s_packageListFile, callback); } private static void StartFileWatcher(string path) { FileSystemWatcher watcher = new FileSystemWatcher(); watcher.Path = path; // Watch for changes in LastAccess and LastWrite times, and // the renaming of files or directories. watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; // Only watch text files. watcher.Filter = "*.json"; // Add event handlers. watcher.Changed += OnChanged; watcher.Created += OnChanged; watcher.Deleted += OnChanged; // watcher.Renamed += OnRenamed; // Begin watching. watcher.EnableRaisingEvents = true; } public static void ClearInstallPackageMetas() { s_installedPackages.Packages = new PackageMeta[0]; SaveJson(s_installedPackages, s_installedPackageFile); } // Define the event handlers. private static void OnChanged(object source, FileSystemEventArgs e) { // Debug.Log(e.ChangeType.ToString()); if (s_isImporting) return; // Debug.Log($"File: {e.FullPath} {e.ChangeType}"); try { var filename = Path.GetFileName(e.FullPath); if (filename.Equals("package.json", StringComparison.OrdinalIgnoreCase)) { EditorCoroutine.start(InstallPackages()); } else if (filename.Equals("list.json")) { LoadPackages(); } else if (e.ChangeType == WatcherChangeTypes.Deleted && filename.Equals("installedPackages.json")) { EditorCoroutine.start(InstallPackages()); } } catch (System.Exception ex) { Debug.LogError(ex.Message); } } [UnityEditor.Callbacks.DidReloadScripts] private static void OnScriptsReloaded() { // do something // Debug.Log("DidReloadScripts"); } private static PackageInfo[] ResolveDependencies(IEnumerable packages) { var openList = new List(packages); var closedList = new List(packages); while (openList.Count > 0) { var openPkg = openList[0]; openList.RemoveAt(0); var closedPkg = closedList.FirstOrDefault(e => e.Name == openPkg.Name); if (closedPkg == null) { closedList.Add(openPkg); } // else if (closedPkg.Version != openPkg.Version) // { // throw new Exception($"The package {openPkg.Name} conflicts version {openPkg.Version} with {closedPkg.Version} "); // } if (openPkg.Dependencies != null) { var deps = Find(openPkg.Dependencies); foreach (var pkg in deps) { var pkgInList = Find(pkg.Name, pkg.Version); if (pkgInList == null) { throw new Exception($"Could not found the package {pkg.Name} version {pkg.Version} "); } if (!closedList.Any(e => e.Name == pkgInList.Name && e.Version == pkgInList.Version)) { openList.Add(pkgInList); } } } } return closedList.ToArray(); } public static IEnumerator InstallPackages() { yield return null; LoadPackages(); var closedList = new List(); var openList = new List(Find(s_packages.Packages)); var importedPackages = new Dictionary(); foreach (var item in s_packageList.Packages) { importedPackages[item.GetPackageName()] = IsImportedPackage(item); } var toInstalls = new List(); Debug.Log("Resolve packages to install"); while (openList.Count > 0) { var pkg = openList[0]; openList.RemoveAt(0); // open if (pkg.Dependencies != null) { foreach (var item in pkg.Dependencies) { if (!importedPackages.ContainsKey(item.GetPackageName())) { throw new Exception("Does not found package " + item.Name); } var pkg1 = Find(item); openList.Add(pkg1); } } // close if (!closedList.Any(e => e.Name == pkg.Name && e.Version == pkg.Version)) { closedList.Add(pkg); } } var leafPackages = closedList.Where(e => e.Dependencies == null || e.Dependencies.Length == 0).ToList(); foreach (var item in leafPackages) { if (!importedPackages[item.GetPackageName()]) { toInstalls.Add(item); importedPackages[item.GetPackageName()] = true; } } foreach (var item in leafPackages) { closedList.Remove(item); } while (closedList.Count > 0) { PackageInfo pkg = closedList.First(p => p.Dependencies.All(e => importedPackages[e.GetPackageName()])); closedList.Remove(pkg); if (!importedPackages[pkg.GetPackageName()]) { toInstalls.Add(pkg); importedPackages[pkg.GetPackageName()] = true; } } // Debug.Log("Resolve versioning"); var resolvePackages = new List(toInstalls); foreach (var item in toInstalls) { var items = resolvePackages.FindAll(e => e.Name == item.Name); if (items.Count > 1) { var maxVersionItem = items.Aggregate((curItem, x) => (curItem == null || curItem.GetVersionCode() < x.GetVersionCode() ? x : curItem)); foreach (var i in items) { if (i != maxVersionItem) { resolvePackages.Remove(i); } } } } toInstalls = resolvePackages; // Debug.Log("Install packages.."); while (toInstalls.Count > 0) { var requiredPkg = toInstalls[0]; toInstalls.RemoveAt(0); if (IsImportedPackage(requiredPkg)) continue; if (!DownloadManager.IsValidLocalFile(requiredPkg.GetPackageName())) { var task = PackageManager.DownloadPackage(requiredPkg); EditorCoroutine.start(task); while (task.MoveNext()) yield return null; } // Debug.Log("Installing package " + requiredPkg.Name); // if(true) if (PackageManager.ImportPackage(requiredPkg)) { yield return null; while (s_isImporting) { yield return null; } importedPackages[requiredPkg.GetPackageName()] = true; // break; // openList.Clear(); // break; } } Debug.Log("All packages installed"); } private static bool IsImportedPackage(PackageMeta pkg) { return s_installedPackages.Packages.Any(e => e.Name == pkg.Name && e.GetVersionCode() >= pkg.GetVersionCode()); } static void ImportPackageCompleted(string package) { var packageName = Path.GetFileName(package) + ".unitypackage"; Debug.Log("Import done " + packageName); EndInstallPackage(); s_isImporting = false; var deps = Find(s_packages.Packages); var depsPackages = ResolveDependencies(deps); var pkg = depsPackages.FirstOrDefault(e => e.GetPackageName() == packageName); if (pkg != null) { var packages = new List(s_installedPackages.Packages); var index = packages.FindIndex(e => e.Name == pkg.Name); if (index >= 0) { packages[index] = pkg; } else { packages.Add(pkg); } s_installedPackages.Packages = packages.ToArray(); SaveJson(s_installedPackages, s_installedPackageFile); Debug.Log("ImportPackageCompleted: " + package); } } static void ImportPackageStarted(string package) { s_isImporting = true; Debug.Log("ImportPackageStarted: " + package); } public static void ImportPackageFailed(string packageName, string errorMessage) { s_isImporting = false; s_currentPackageName = null; Debug.Log("ImportPackageFailed: " + errorMessage); EndInstallPackage(); } private static PackageList s_packageList; private static PackageMetaList s_packages; private static PackageMetaList s_installedPackages; public static List AllPackages { get { return new List(s_packageList.Packages); } } public static List InstalledPackages { get { return new List(s_installedPackages.Packages); } } public static List Packages { get { return new List(s_packages.Packages); } } private static PackageList LoadPackageList(string path) { PackageList packageList = null; if (File.Exists(path)) { var json = File.ReadAllText(path); packageList = JsonUtility.FromJson(json); } if (packageList == null) { packageList = new PackageList() { Packages = new PackageInfo[0] }; } else if (packageList.Packages == null) { packageList.Packages = new PackageInfo[0]; } return packageList; } private static PackageMetaList LoadPackageMetaList(string path) { PackageMetaList packageList = null; if (File.Exists(path)) { var json = File.ReadAllText(path); packageList = JsonUtility.FromJson(json); } if (packageList == null) { packageList = new PackageMetaList() { Packages = new PackageMeta[0] }; } else if (packageList.Packages == null) { packageList.Packages = new PackageMeta[0]; } return packageList; } private static void SaveJson(object obj, string path) { var json = JsonUtility.ToJson(obj, true); File.WriteAllText(path, json); } public static PackageInfo Find(string name, string version) { var info = s_packageList.Packages.FirstOrDefault(e => e.Name == name && e.Version == version); return info; } public static PackageInfo Find(PackageMeta meta) { var info = s_packageList.Packages.FirstOrDefault(e => e.Name == meta.Name && e.Version == meta.Version); return info; } public static List Find(IEnumerable metaList) { return (from meta in metaList select Find(meta)).ToList(); } public static IEnumerator DownloadAllPackages(PackageInfo[] packages) { for (var i = 0; i < packages.Length; i++) { var pkg = packages[i]; var isValid = DownloadManager.IsValidLocalFile(pkg.GetPackageName()); if (!isValid) { var task = DownloadPackage(pkg); EditorCoroutine.start(task); while (task.MoveNext()) { yield return task; } } } } public static IEnumerator DownloadPackage(PackageInfo info) { var pkg = Find(info.Name, info.Version); if (pkg != null) { return DownloadManager.DownloadFile(pkg.Url, pkg.GetPackageName(), file => { Debug.Log("completed download package " + file); }); } return null; } private const string PACKAGE_NAME = "APM_PACKAGE_NAME"; private const string PACKAGE_GUIDS = "APM_ASSET_GUIDS"; private static void StartInstallPackage(string packageName, string guids) { PlayerPrefs.SetString(PACKAGE_NAME, packageName); PlayerPrefs.SetString(PACKAGE_GUIDS, guids); // PlayerPrefs.Save(); } private static void EndInstallPackage() { PlayerPrefs.SetString(PACKAGE_NAME, ""); PlayerPrefs.SetString(PACKAGE_GUIDS, ""); // PlayerPrefs.Save(); } public static bool ImportPackage(PackageInfo info, bool interactive = false) { if (DownloadManager.IsValidLocalFile(info.GetPackageName())) { var packageName = info.GetPackageName(); var name = Path.GetFileNameWithoutExtension(packageName); var assets = AssetDatabase.FindAssets(""); var guids = String.Join(",", assets); StartInstallPackage(packageName, guids); s_currentPackageName = packageName; AssetDatabase.ImportPackage(DownloadManager.GetLocalFilePath(packageName), interactive); return true; } else { EditorUtility.DisplayDialog("Package Manager", "Package Not Found: " + info.GetPackageName(), "OK"); } return false; } // private static void RemovePackage(PackageMeta package) // { // var toRemove = s_installedPackages.Packages.FirstOrDefault(e => e.Name == package.Name); // if (toRemove == null) // { // Debug.Log($"The package {package.Name} is not installed."); // return; // } // // validate dependencies // var deps = FindDependencies(package); // if (deps.Count > 0) // { // foreach (var p in deps) // { // Debug.Log($"Package {p.Name} deps on {package.Name}"); // } // throw new Exception($"There are {deps.Count} dependencies."); // } // // uninstall // } public static List FindDependencies(PackageMeta meta) { List packages = new List(); foreach (var package in s_installedPackages.Packages) { var p = Find(package); if (p != null && p.Dependencies != null) { if (p.Dependencies.Any(e => e.Name == meta.Name)) { packages.Add(package); } } } return packages; } public static IEnumerator CheckUpdate() { const string URL = "https://api.bitbucket.org/2.0/repositories/lttrieu/test-amanotes-package-manager/commits/master"; UnityWebRequest www = UnityWebRequest.Get(URL); var dh = new DownloadHandlerBuffer(); // dh.contentType= "application/json"; www.downloadHandler = dh; yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { Debug.Log(www.error); } else { while (!dh.isDone || www.isNetworkError) { // Debug.Log(www.downloadProgress.ToString()); EditorUtility.DisplayProgressBar("Package Manager", "Updating...", www.downloadProgress); yield return null; } EditorUtility.ClearProgressBar(); // Show results as text var text = www.downloadHandler.text; var response = JsonUtility.FromJson(text); var headCommit = response.values.FirstOrDefault(); var manifestFile = Path.Combine(Application.dataPath, "../Packages/Manifest.json"); //Debug.Log(headCommit.message); } } [Serializable] public class CommitsReponse { public int pagelen; public CommitResource[] values; } [Serializable] public class CommitResource { public string message; public string hash; } // [Serializable] // public class MessageResource // { // public string raw; // } private static void RemovePackage(PackageMeta packageInfo) { var packageFullname = packageInfo.GetPackageName(); var packageName = Path.GetFileNameWithoutExtension(packageFullname); var filter = $"l:{packageName}"; var guids = AssetDatabase.FindAssets(filter); foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); AssetDatabase.DeleteAsset(path); Debug.Log($"Remove: " + path); } AssetDatabase.SaveAssets(); } public static void RemovePackage(string packageName) { var package = s_installedPackages.Packages.FirstOrDefault(e => e.Name == packageName); if (package != null) { // remove from package list PackageMeta[] pkgs; pkgs = (from p in s_packages.Packages where p.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase) select p).ToArray(); s_packages.Packages = pkgs; SaveJson(s_packages, s_packageFile); RemovePackage(package); // remove from installed pkgs = (from p in s_installedPackages.Packages where !p.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase) select p).ToArray(); s_installedPackages.Packages = pkgs; SaveJson(s_installedPackages, s_installedPackageFile); } } // [MenuItem("Amanotes/Uninstall AmaAppsflyers")] // public static void UninstallAmaAppsflyers() // { // RemovePackage("AmaAppsflyers"); // } } }