static void ImportScene(string sceneName, string jsonText, AssetPack assetPack, string path = null)
        {
            var sceneModule = MARSSceneModule.instance;
            var wasBlockingEnsureSession = sceneModule.BlockEnsureSession;

            sceneModule.BlockEnsureSession = true;
            if (string.IsNullOrEmpty(path))
            {
                var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
                scene.name = sceneName;
            }
            else
            {
                var scene = EditorSceneManager.OpenScene(path);
                foreach (var gameObject in scene.GetRootGameObjects().ToList())
                {
                    UnityObject.DestroyImmediate(gameObject);
                }
            }

            // TODO: preload prefabs
            CompanionSceneUtils.ImportScene(jsonText, assetPack);

            sceneModule.BlockEnsureSession = wasBlockingEnsureSession;
        }
        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);
                        }
                    }));
                }
            }
        }
        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;
            }
        }