static void FinalizePublish(Stack <RequestHandle> requests)
        {
            var subscriber     = CreateSubscriber();
            var resourceFolder = CompanionResourceUtils.GetCurrentResourceFolder();
            var project        = new CompanionProject(Application.cloudProjectId, Application.productName, true);

            requests.Push(subscriber.GetCloudResourceList(project, resourceFolder, (success, resourceList) =>
            {
                foreach (var update in k_ResourceListUpdates)
                {
                    var key = update.ResourceKey;
                    resourceList.AddOrUpdateResource(key, update.Name, update.Type, update.FileSize, update.HasBundle);

                    var thumbnail = update.Thumbnail;
                    if (thumbnail != null)
                    {
                        subscriber.SaveThumbnailImage(project, resourceFolder, key, thumbnail, requests);
                    }
                }

                requests.Push(subscriber.WriteCloudResourceList(project, resourceFolder, resourceList, writeSuccess =>
                {
                    if (success)
                    {
                        CompanionResourceUtils.OnCloudResourceListChanged(resourceList);
                    }
                    else
                    {
                        Debug.LogError("Failed to write resource list");
                    }
                }));
            }));
        }
예제 #2
0
        public static CompanionProject AddNewProject(string name = null)
        {
            if (string.IsNullOrEmpty(name))
            {
                name = k_UntitledProjectName;
            }

            var projectList   = LoadProjects();
            var projects      = projectList.projects;
            var unlinkedCount = 0;

            foreach (var project in projects)
            {
                if (!project.linked)
                {
                    unlinkedCount++;
                }
            }

            var id          = string.Format(k_UnlinkedProjectIDFormat, unlinkedCount);
            var projectData = new CompanionProject(id, name, false);

            projectList.projects.Add(projectData);
            SaveProjects(projectList);

            return(projectData);
        }
        public static void AddCloudResourceToLocalResourceList(CompanionProject project, string resourceFolder, CompanionResource resource)
        {
            var localResourceList = GetLocalResourceList(project, resourceFolder) ?? new ResourceList();

            localResourceList.AddOrOverwriteExistingResource(resource);
            WriteLocalResourceList(project, resourceFolder, localResourceList);
        }
예제 #4
0
        public static void DeleteProject(CompanionProject projectToDelete)
        {
            try
            {
                var projectFolder = projectToDelete.GetLocalPath();
                if (Directory.Exists(projectFolder))
                {
                    Directory.Delete(projectToDelete.GetLocalPath(), true);
                }
            }
            catch (Exception e)
            {
                // TODO: Error feedback
                Debug.LogException(e);
                return;
            }

            var projectList = LoadProjects();
            // TODO: Error if project not found
            var index = projectToDelete.index;

            foreach (var project in projectList.projects.ToList())
            {
                if (project.index == index)
                {
                    projectList.projects.Remove(project);
                    break;
                }
            }

            SaveProjects(projectList);
        }
예제 #5
0
        static bool MoveProjectFolder(string oldId, string newId, out string errorTitle, out string errorBody)
        {
            var localPath = CompanionProject.GetProjectPath(oldId);

            // If local project doesn't exist, do not prevent the link--this shouldn't ever happen, though
            if (!Directory.Exists(localPath))
            {
                errorTitle = null;
                errorBody  = null;
                return(true);
            }

            var linkedPath = CompanionProject.GetProjectPath(newId);

            if (Directory.Exists(linkedPath))
            {
                errorTitle = "Cannot move project folder";
                errorBody  = "Sorry, it appears that another project folder already exists at the destination. This can happen if an error occurred when trying to link or delete a project with the same ID. Please back up and clear local data to resolve this issue.";
                return(false);
            }

            Directory.Move(localPath, linkedPath);
            errorTitle = null;
            errorBody  = null;
            return(true);
        }
 internal static RequestHandle GetScene(this IUsesCloudStorage storageUser, CompanionProject project,
                                        CompanionResource resource, Action <bool, string> callback)
 {
     if (callback == null)
     {
         Debug.LogWarning("Callback is null in GetScene");
         return(default);
        internal static RequestHandle SavePrefab(this IUsesCloudStorage storageUser, CompanionProject project, string resourceFolder,
                                                 GameObject prefab, Action <bool, string> callback = null)
        {
            // Write to local storage in case cloud isn't reachable
            var prefabName = prefab.name;
            var path       = AssetDatabase.GetAssetPath(prefab);

            if (string.IsNullOrEmpty(path))
            {
                Debug.LogError("Cannot get path for " + prefab);
                return(default);
        internal static void AddOrUpdateResource(this IUsesCloudStorage storageUser, CompanionProject project,
                                                 string resourceFolder, string key, string name, ResourceType type, long fileSize, bool hasBundle,
                                                 Stack <RequestHandle> requests, Action <bool, ResourceList> callback = null)
        {
            var localResourceList = GetLocalResourceList(project, resourceFolder) ?? new ResourceList();
            var resource          = localResourceList.AddOrUpdateResource(key, name, type, fileSize, hasBundle);

            WriteLocalResourceList(project, resourceFolder, localResourceList);

            void LocalOnlyUpdate(bool success, ResourceList resourceList)
            {
                resourceList = ResourceList.MergeResourceLists(localResourceList, resourceList);
                ResourceListChanged?.Invoke(false, resourceList);
                callback?.Invoke(success, resourceList);
            }

            if (!project.linked)
            {
                LocalOnlyUpdate(true, new ResourceList());
                return;
            }

            // Get the latest list to make sure we don't lose scenes in case of stale data
            requests.Push(storageUser.GetCloudResourceList(project, resourceFolder, (getListSuccess, cloudResourceList) =>
            {
                if (getListSuccess)
                {
                    cloudResourceList.AddOrOverwriteExistingResource(resource);
                    requests.Push(storageUser.WriteCloudResourceList(project, resourceFolder, cloudResourceList, writeListSuccess =>
                    {
                        if (writeListSuccess)
                        {
#if UNITY_EDITOR
                            CloudResourceListChanged?.Invoke(cloudResourceList);
#endif

                            WriteFallbackCloudResourceList(project, resourceFolder, cloudResourceList);
                            cloudResourceList = ResourceList.MergeResourceLists(localResourceList, cloudResourceList);
                            ResourceListChanged?.Invoke(true, cloudResourceList);
                            callback?.Invoke(true, cloudResourceList);
                        }
                        else
                        {
                            LocalOnlyUpdate(false, cloudResourceList);
                        }
                    }));
                }
                else
                {
                    LocalOnlyUpdate(false, cloudResourceList);
                }
            }));
        }
        static RequestHandle SaveMarker(this IUsesCloudStorage storageUser, CompanionProject project,
                                        string resourceFolder, string guid, Marker marker, Action <bool, string> callback)
        {
            var jsonText = SceneSerialization.ToJson(marker);

            WriteLocalMarker(project, resourceFolder, guid, jsonText);
            var key = GetMarkerKey(resourceFolder, guid);

            if (!project.linked)
            {
                callback?.Invoke(true, key);
                return(default);
        static RequestHandle SaveEnvironment(this IUsesCloudStorage storageUser, CompanionProject project,
                                             string resourceFolder, string guid, Environment environment, Action <bool, string, long> callback)
        {
            var jsonText = SceneSerialization.ToJson(environment);

            // Write to local storage in case cloud isn't reachable
            WriteLocalEnvironment(project, resourceFolder, guid, jsonText);
            var key = GetEnvironmentKey(resourceFolder, guid);

            if (!project.linked)
            {
                callback?.Invoke(true, key, jsonText.Length);
                return(default);
        static RequestHandle SaveDataRecording(this IUsesCloudStorage storageUser, CompanionProject project, string resourceFolder,
                                               string guid, CompanionDataRecording recording, Action <bool, string, long> callback, ProgressCallback progress)
        {
            var jsonText = SceneSerialization.ToJson(recording);

            WriteLocalDataRecording(project, resourceFolder, guid, jsonText);

            var key = GetDataRecordingKey(resourceFolder, guid);

            if (!project.linked)
            {
                callback?.Invoke(true, key, jsonText.Length);
                return(default);
예제 #12
0
 public static void GetPrefab(IUsesCloudStorage storageUser, CompanionProject project, string key, Action <GameObject> callback)
 {
     if (k_CachedPrefabs.TryGetValue(key, out var prefab))
     {
         callback?.Invoke(prefab);
     }
     else
     {
         storageUser.GetPrefabAsset(project, key, (success, downloadedPrefab) =>
         {
             SetPrefab(key, downloadedPrefab);
             callback?.Invoke(downloadedPrefab);
         });
     }
 }
예제 #13
0
        public static void UnlinkProject(string cloudId)
        {
            var projectList = LoadProjects();
            var projects    = projectList.projects;
            CompanionProject cloudProject = null;

            foreach (var project in projects)
            {
                if (project.index == cloudId)
                {
                    cloudProject = project;
                }
            }

            if (cloudProject == null)
            {
                Debug.LogError($"Cloud project with ID {cloudId} now found");
                return;
            }

            var newProjectId = GetNewProjectId(projects);

            try
            {
                if (!MoveProjectFolder(cloudId, newProjectId, out var errorTitle, out var errorBody))
                {
                    Debug.Log($"{errorTitle}\n{errorBody}");
                    return;
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                Debug.LogError("Failed to move project folder");
                return;
            }

            cloudProject.Unlink(newProjectId);
            SaveProjects(projectList);
        }
예제 #14
0
        public static void AddOrUpdateProject(CompanionProject currentProject)
        {
            var projectList = LoadProjects();
            var found       = false;

            foreach (var project in projectList.projects.ToList())
            {
                if (project.index == currentProject.index)
                {
                    found          = true;
                    project.linked = currentProject.linked;
                    project.name   = currentProject.name;
                }
            }

            if (!found)
            {
                projectList.AddProject(currentProject.index, currentProject.name, currentProject.linked);
            }

            SaveProjects(projectList);
        }
 internal static RequestHandle GetCloudResourceList(this IUsesCloudStorage storageUser, CompanionProject project,
                                                    string resourceFolder, Action <bool, ResourceList> callback)
 {
     if (callback == null)
     {
         Debug.LogWarning("Callback is null in GetCloudResourceList");
         return(default);
        internal static void ImportResource(this IUsesCloudStorage storageUser, CompanionProject project,
                                            CompanionResource resource, string platform, ResourceList resourceList, GameObject simulatedPlanePrefab,
                                            Material simulatedPlaneMaterial, Post postPrefab, MeshFilter meshPrefab, GameObject simulatedLightingPrefab,
                                            Stack <RequestHandle> requests, Action <UnityObject> callback)
        {
            string path;
            string cloudGuid;
            var    resourceKey = resource.key;

            switch (resource.type)
            {
            case ResourceType.Scene:
                if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
                {
                    return;
                }

                void ImportSceneCallback(bool bundleSuccess, AssetPack assetPack)
                {
                    storageUser.GetScene(project, resource, (success, jsonText) =>
                    {
                        if (success)
                        {
                            CompanionSceneUtils.SplitSceneKey(resourceKey, out _, out cloudGuid);
                            var sceneGuid = CompanionResourceSync.instance.GetAssetGuid(cloudGuid);
                            var scenePath = AssetDatabase.GUIDToAssetPath(sceneGuid);

                            // GUIDToAssetPath will return a path if the asset was recently deleted
                            if (AssetDatabase.LoadAssetAtPath <SceneAsset>(scenePath) == null)
                            {
                                scenePath = null;
                            }

                            if (!string.IsNullOrEmpty(sceneGuid) && string.IsNullOrEmpty(scenePath))
                            {
                                Debug.LogWarningFormat("Could not find scene with guid {0}. Falling back to new scene", sceneGuid);
                            }

                            var sceneName = CompanionSceneUtils.GetSceneName(resource.name);
                            ImportScene(sceneName, jsonText, assetPack, scenePath);

                            // Store cloud ID in temp scene object if original scene not found
                            if (string.IsNullOrEmpty(scenePath))
                            {
                                var cloudIdObject       = new GameObject("__Temp");
                                cloudIdObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSaveInBuild;
                                var cloudIdSaver        = cloudIdObject.AddComponent <CloudGuidSaver>();
                                cloudIdSaver.CloudGuid  = cloudGuid;
                            }
                        }
                    });
                }

                if (resourceList.TryGetAssetBundleKey(resource.key, platform, out var assetBundleKey))
                {
                    requests.Push(storageUser.GetAssetPack(project, assetBundleKey, ImportSceneCallback));
                }
                else
                {
                    ImportSceneCallback(true, null);
                }

                return;

            case ResourceType.Environment:
                requests.Push(storageUser.CloudLoadAsync(resourceKey, (success, responseCode, response) =>
                {
                    if (success)
                    {
                        var environmentName = resource.name;
                        path = EditorUtility.SaveFilePanelInProject(k_SaveEnvironmentDialogTitle, environmentName,
                                                                    k_PrefabExtension, string.Empty);

                        if (string.IsNullOrEmpty(path))
                        {
                            return;
                        }

                        ImportEnvironment(environmentName, response, path, simulatedPlanePrefab,
                                          simulatedPlaneMaterial, postPrefab, meshPrefab, simulatedLightingPrefab, callback);

                        var resourceSync = CompanionResourceSync.instance;
                        CompanionEnvironmentUtils.SplitEnvironmentKey(resourceKey, out _, out cloudGuid);
                        resourceSync.SetAssetGuid(AssetDatabase.AssetPathToGUID(path), cloudGuid);
                    }
                }));

                return;

            case ResourceType.Recording:
                path = EditorUtility.SaveFilePanelInProject(k_SaveRecordingDialogTitle, resource.name,
                                                            k_RecordingExtension, string.Empty);

                if (string.IsNullOrEmpty(path))
                {
                    return;
                }

                requests.Push(storageUser.CloudLoadAsync(resourceKey, (success, responseCode, response) =>
                {
                    if (success)
                    {
                        CoroutineUtils.StartCoroutine(storageUser.ImportDataRecording(path,
                                                                                      resourceKey, resource.name, response, requests, callback));
                    }
                }));

                return;

            case ResourceType.Marker:
                path = EditorUtility.SaveFilePanelInProject(k_SaveImageDialogTitle, resource.name,
                                                            k_ImageExtension, string.Empty);

                if (string.IsNullOrEmpty(path))
                {
                    return;
                }

                var markerKey = resourceKey;
                requests.Push(storageUser.CloudLoadAsync(markerKey, (success, responseCode, response) =>
                {
                    if (success)
                    {
                        requests.Push(storageUser.ImportMarker(path, markerKey, response, callback));
                    }
                }));

                return;

            case ResourceType.Prefab:
                requests.Push(storageUser.GetPrefabAsset(project, resourceKey, platform,
                                                         (bundleSuccess, prefab) =>
                {
                    UnityObject.Instantiate(prefab);
                }));
                return;
            }
        }
        static void Publish()
        {
            k_ResourceListUpdates.Clear();
            s_UploadCount = 0;
            var subscriber     = CreateSubscriber();
            var resourceFolder = CompanionResourceUtils.GetCurrentResourceFolder();
            var project        = new CompanionProject(Application.cloudProjectId, Application.productName, true);

            foreach (var selected in Selection.objects)
            {
                if (selected is SceneAsset sceneAsset)
                {
                    var path = AssetDatabase.GetAssetPath(sceneAsset);
                    if (string.IsNullOrEmpty(path))
                    {
                        Debug.LogError("Selected SceneAsset path is invalid");
                        continue;
                    }

                    var sceneAssetPack = CompanionSceneUtils.GetOrTryCreateAssetPackForSceneAsset(sceneAsset);
                    sceneAssetPack.Clear();
                    sceneAssetPack.SceneAsset = sceneAsset;
                    var removeScene = true;
                    var unload      = true;
                    var sceneCount  = SceneManager.sceneCount;
                    for (var i = 0; i < sceneCount; i++)
                    {
                        var existingScene = SceneManager.GetSceneAt(i);
                        if (existingScene.path == path)
                        {
                            if (existingScene.isLoaded)
                            {
                                unload = false;
                            }

                            removeScene = false;
                            break;
                        }
                    }

                    var scene     = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive);
                    var assetGuid = AssetDatabase.AssetPathToGUID(path);
                    if (string.IsNullOrEmpty(assetGuid))
                    {
                        Debug.LogError("Could not get valid guid for asset at path " + path);
                        continue;
                    }

                    // Hide the OnDestroyNotifier so it is not saved
                    foreach (var root in scene.GetRootGameObjects())
                    {
                        var notifier = root.GetComponentInChildren <OnDestroyNotifier>();
                        if (notifier != null)
                        {
                            notifier.gameObject.hideFlags = HideFlags.HideAndDontSave;
                        }
                    }

                    // generate and save thumbnail image for the scene by running a one-shot simulation
                    var thumbnail = SimulateSceneAndCaptureThumbnail();
                    var cloudGuid = CompanionResourceSync.instance.GetCloudGuid(assetGuid);
                    s_UploadCount++;
                    k_Requests.Clear();
                    k_Requests.Push(subscriber.SaveScene(project, resourceFolder, cloudGuid, scene, sceneAssetPack,
                                                         (success, key, size) =>
                    {
                        if (scene.IsValid())
                        {
                            // Restore OnDestroyNotifier HideFlags so that it is discoverable
                            foreach (var root in scene.GetRootGameObjects())
                            {
                                var notifier = root.GetComponentInChildren <OnDestroyNotifier>();
                                if (notifier != null)
                                {
                                    notifier.gameObject.hideFlags = HideFlags.HideInHierarchy;
                                }
                            }
                        }

                        var sceneName = sceneAsset.name;
                        if (success)
                        {
                            k_ResourceListUpdates.Add(new ResourceListUpdate
                            {
                                ResourceKey = key,
                                Name        = sceneName,
                                Type        = ResourceType.Scene,
                                FileSize    = size,
                                HasBundle   = sceneAssetPack.AssetCount > 0,
                                Thumbnail   = thumbnail
                            });

                            Debug.Log($"Published {sceneName}");
                        }
                        else
                        {
                            Debug.LogError($"Failed to publish {sceneName}");
                        }

                        if (--s_UploadCount == 0)
                        {
                            FinalizePublish(k_Requests);
                        }
                    }));

                    // Scene serialization is synchronous, so we can close the scene while the upload completes
                    if (unload)
                    {
                        EditorSceneManager.CloseScene(scene, removeScene);
                    }

                    continue;
                }

                if (selected is GameObject prefab)
                {
                    var path = AssetDatabase.GetAssetPath(prefab);
                    if (string.IsNullOrEmpty(path))
                    {
                        Debug.LogError("SceneAsset for selected AssetPack path is invalid");
                        continue;
                    }

                    var guid = AssetDatabase.AssetPathToGUID(path);
                    if (string.IsNullOrEmpty(guid))
                    {
                        Debug.LogError("Could not get valid guid for asset at path " + path);
                        continue;
                    }

                    // Check existing asset bundle name to avoid overwriting it
                    var assetImporter = AssetImporter.GetAtPath(path);
                    if (!string.IsNullOrEmpty(assetImporter.assetBundleName) && assetImporter.assetBundleName != guid)
                    {
                        Debug.LogError(prefab.name + " is already part of an AssetBundle, and cannot be published to the cloud without overwriting its AssetBundle name");
                        continue;
                    }

                    var bounds = BoundsUtils.GetBounds(prefab.transform);
                    if (bounds.extents.MinComponent() < k_MinPrefabBound)
                    {
                        Debug.LogError(prefab.name + " is too small to be selectable. MARS prefabs must be selectable.");
                        continue;
                    }

                    var thumbnail = ThumbnailUtility.GeneratePrefabThumbnail(prefab);
                    if (thumbnail == null)
                    {
                        Debug.LogError(prefab.name + " is not a visible asset. MARS prefabs must be visible.");
                        continue;
                    }

                    s_UploadCount++;
                    k_Requests.Clear();
                    k_Requests.Push(subscriber.SavePrefab(project, resourceFolder, prefab, (success, key) =>
                    {
                        if (success)
                        {
                            k_ResourceListUpdates.Add(new ResourceListUpdate
                            {
                                ResourceKey = key,
                                Name        = prefab.name,
                                Type        = ResourceType.Prefab,
                                HasBundle   = true,
                                Thumbnail   = thumbnail
                            });

                            Debug.Log($"Published {prefab.name}");
                        }
                        else
                        {
                            Debug.LogError($"Failed to publish {prefab.name}");
                        }

                        if (--s_UploadCount == 0)
                        {
                            FinalizePublish(k_Requests);
                        }
                    }));
                }
            }
        }
예제 #18
0
        static RequestHandle GetAssetBundleAsset(this IUsesCloudStorage storageUser, string group, CompanionProject project,
                                                 string resourceFolder, string platform, string guid, Action <bool, UnityObject> callback)
        {
            const int timeout = 0; // Asset bundles may be quite large and take a long time to download

            // Write to local storage in case cloud isn't reachable
            var folder = CompanionResourceUtils.GetLocalResourceFolderPath(project, resourceFolder, group);
            var path   = Path.Combine(folder, guid);

            if (!project.linked)
            {
                UnityObject asset = null;
                if (File.Exists(path))
                {
                    asset = GetLocalAssetBundleAsset(path);
                }

                callback(asset != null, asset);
                return(default);
예제 #19
0
        public static bool TryLinkProject(string id, string name, string localIndex, out string errorTitle, out string errorBody)
        {
            if (string.IsNullOrEmpty(id))
            {
                errorTitle = InvalidIDErrorTitle;
                errorBody  = InvalidIDErrorBody;
                return(false);
            }

            if (string.IsNullOrEmpty(localIndex))
            {
                errorTitle = k_InvalidLocalProjectErrorTitle;
                errorBody  = k_InvalidLocalProjectErrorBody;
                return(false);
            }

            var projectList = LoadProjects();
            CompanionProject localProject = null;

            foreach (var project in projectList.projects)
            {
                if (project.index == id)
                {
                    errorTitle = k_AlreadyImportedErrorTitle;
                    errorBody  = string.Format(k_AlreadyImportedErrorBodyFormat, project.name);
                    return(false);
                }

                if (project.index == localIndex)
                {
                    localProject = project;
                }
            }

            if (localProject == null)
            {
                errorTitle = k_InvalidLocalProjectErrorTitle;
                errorBody  = k_InvalidLocalProjectErrorBody;
                return(false);
            }

            try
            {
                if (!MoveProjectFolder(localIndex, id, out errorTitle, out errorBody))
                {
                    return(false);
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                errorTitle = "Failed to move project folder";
                errorBody  = $"Project save failed with the following message {e.Message}";
                return(false);
            }

            localProject.Link(id, name);

            SaveProjects(projectList);
            errorTitle = null;
            return(true);
        }