Ejemplo n.º 1
0
        public void Setup(PolyCacheConfig config)
        {
            // Create all the buffer holders. They are all initially idle.
            for (int i = 0; i < MAX_CONCURRENT_DOWNLOADS; i++)
            {
                idleBuffers.Add(new BufferHolder());
            }

            // Caching is only supported on Windows/Mac for now.
            bool cacheSupported = Application.platform == RuntimePlatform.WindowsEditor ||
                                  Application.platform == RuntimePlatform.WindowsPlayer ||
                                  Application.platform == RuntimePlatform.OSXEditor ||
                                  Application.platform == RuntimePlatform.OSXPlayer;

            PtDebug.LogFormat("Platform: {0}, cache supported: {1}", Application.platform, cacheSupported);

            if (cacheSupported && config.cacheEnabled)
            {
                //string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
                //string defaultCachePath = Path.Combine(Path.Combine(Path.Combine(
                //  appDataPath, Application.companyName), Application.productName), "WebRequestCache");
                string defaultCachePath = Path.Combine(Application.persistentDataPath, "WebRequestCache");

                string cachePath = config.cachePathOverride;
                if (string.IsNullOrEmpty(cachePath))
                {
                    cachePath = defaultCachePath;
                }
                // Note: Directory.CreateDirectory creates all directories in the path.
                Directory.CreateDirectory(cachePath);

                cache = gameObject.AddComponent <PersistentBlobCache>();
                cache.Setup(cachePath, config.maxCacheEntries, config.maxCacheSizeMb * 1024 * 1024);
            }
        }
        /// <summary>
        /// Checks for pending deliveries and delivers them.
        /// </summary>
        private void Update()
        {
            if (!setupDone)
            {
                return;
            }

            // To avoid locking the queue on every frame, exit early if the volatile count is 0.
            if (requestsPendingDelivery.VolatileCount == 0)
            {
                return;
            }

            // Check for a pending delivery.
            // Note that for performance reasons, we limit ourselves to delivering one result per frame.
            CacheRequest delivery;

            if (!requestsPendingDelivery.Dequeue(out delivery))
            {
                return;
            }

            PtDebug.LogVerboseFormat("PBC: delivering result on {0} ({1}, {2} bytes).",
                                     delivery.hash, delivery.success ? "SUCCESS" : "FAILURE", delivery.data != null ? delivery.data.Length : -1);

            // Deliver the results to the callback.
            delivery.readCallback(delivery.success, delivery.data);

            // Recycle the request for reuse.
            delivery.Reset();
            requestsRecyclePool.Enqueue(delivery);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Called when sign in finishes.
        /// </summary>
        /// <param name="wasInteractive">If true, this was the interactive (browser-based) sign-in flow.</param>
        /// <param name="status">The result of the sign in process.</param>
        private void OnSignInFinished(bool wasInteractive, PolyStatus status)
        {
            if (status.ok)
            {
                string tok = PolyApi.AccessToken;
                PtDebug.LogFormat("ABM: Sign in success. Access token: {0}",
                                  (tok != null && tok.Length > 6) ? tok.Substring(0, 6) + "..." : "INVALID");
                PtAnalytics.SendEvent(PtAnalytics.Action.ACCOUNT_SIGN_IN_SUCCESS);
            }
            else if (wasInteractive)
            {
                Debug.LogErrorFormat("Failed to sign in. Please try again: " + status);
                PtAnalytics.SendEvent(PtAnalytics.Action.ACCOUNT_SIGN_IN_FAILURE, status.ToString());
            }
            if (null != refreshCallback)
            {
                refreshCallback();
            }

            // If we had a deferred request that was waiting for auth, send it now.
            if (requestToSendAfterAuth != null)
            {
                PtDebug.Log("Sending deferred request that was waiting for auth.");
                PolyRequest request = requestToSendAfterAuth;
                requestToSendAfterAuth = null;
                StartRequest(request);
            }
        }
        /// <summary>
        /// Sets up a cache with the given characteristics.
        /// </summary>
        /// <param name="rootPath">The absolute path to the root of the cache.</param>
        /// <param name="maxEntries">The maximum number of entries in the cache.</param>
        /// <param name="maxSizeBytes">The maximum combined size of all entries in the cache.</param>
        public void Setup(string rootPath, int maxEntries, long maxSizeBytes)
        {
            this.rootPath     = rootPath;
            this.maxEntries   = maxEntries;
            this.maxSizeBytes = maxSizeBytes;

            // Check that we have a reasonable config:
            PolyUtils.AssertNotNullOrEmpty(rootPath, "rootPath can't be null or empty");
            PolyUtils.AssertTrue(Directory.Exists(rootPath), "rootPath must be an existing directory: " + rootPath);
            PolyUtils.AssertTrue(maxEntries >= 256, "maxEntries must be >= 256");
            PolyUtils.AssertTrue(maxSizeBytes >= 1048576, "maxSizeBytes must be >= 1MB");

            PtDebug.LogVerboseFormat("PBC initializing, root {0}, max entries {1}, max size {2}",
                                     rootPath, maxEntries, maxSizeBytes);

            md5 = MD5.Create();
            InitializeCache();

            setupDone = true;

            Thread backgroundThread = new Thread(BackgroundThreadMain);

            backgroundThread.IsBackground = true;
            backgroundThread.Start();
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Callback invoked when we get request results.
        /// </summary>
        private void OnRequestResult(PolyStatusOr <PolyListAssetsResult> result)
        {
            if (result.Ok)
            {
                PtDebug.LogFormat("ABM: request results received ({0} assets).", result.Value.assets.Count);
                this.listAssetsResult = result;
                resultHasMorePages    = result.Value.nextPageToken != null;
            }
            else
            {
                Debug.LogError("Asset request failed. Try again later: " + result.Status);
                this.listAssetsResult = result;
            }
            querying = false;
            if (null != refreshCallback)
            {
                refreshCallback();
            }

            if (result.Ok)
            {
                assetsInUse = GetAssetsInUse();
                FinishFetchingThumbnails(result);
            }
        }
        /// <summary>
        /// (Background thread). Handles a write request. Writes the data to disk.
        /// </summary>
        /// <param name="writeRequest">The write request to execute.</param>
        private void BackgroundHandleWriteRequest(CacheRequest writeRequest)
        {
            PtDebug.LogVerboseFormat("PBC: executing write request for {0}", writeRequest.hash);

            string fullPath = HashToFullPath(writeRequest.hash);
            string tempPath = Path.Combine(Path.GetDirectoryName(fullPath), "temp.dat");

            // In the event of a crash or hardware issues -- e.g., user trips on the power cord, our write
            // to disk might be interrupted in an inconsistent state. So instead of writing directly to
            // the destination file, we write to a temporary file and then move.
            File.WriteAllBytes(tempPath, writeRequest.data);
            if (File.Exists(fullPath))
            {
                File.Delete(fullPath);
            }
            File.Move(tempPath, fullPath);

            // Update the file size and last used time information in the cache.
            CacheEntry entry;

            if (!cacheEntries.TryGetValue(writeRequest.hash, out entry))
            {
                entry = cacheEntries[writeRequest.hash] = new CacheEntry(writeRequest.hash);
            }
            entry.fileSize             = writeRequest.data.Length;
            entry.writeTimestampMillis = TicksToMillis(DateTime.UtcNow.Ticks);

            // We are done with writeRequest, so we can recycle it.
            writeRequest.Reset();
            requestsRecyclePool.Enqueue(writeRequest);

            // Check if the cache needs trimming.
            TrimCache();
        }
Ejemplo n.º 7
0
        public AssetBrowserManager()
        {
            PtDebug.Log("ABM initializing...");
            EnsurePolyIsReady();

            // Initially, show the featured assets home page.
            StartRequest(PolyListAssetsRequest.Featured());
        }
Ejemplo n.º 8
0
 /// <summary>
 /// Clears the current request. Also cancels any pending request.
 /// </summary>
 public void ClearRequest()
 {
     PtDebug.Log("ABM: clearing request...");
     querying = false;
     // Increasing the ID will cause us to ignore the results of any pending requests
     // (we will know they are obsolete by their query ID).
     queryId++;
     listAssetsResult   = null;
     resultHasMorePages = false;
 }
 /// <summary>
 /// (Background thread). Clears the entire cache.
 /// </summary>
 private void BackgroundHandleClearRequest(CacheRequest clearRequest)
 {
     PtDebug.LogVerboseFormat("Clearing the cache.");
     foreach (string file in Directory.GetFiles(rootPath, "*" + BLOB_FILE_EXT))
     {
         File.Delete(file);
     }
     cacheEntries.Clear();
     clearRequest.Reset();
     requestsRecyclePool.Enqueue(clearRequest);
 }
        /// <summary>
        /// Requests that the cache be cleared. The cache will be cleared asynchronously.
        /// </summary>
        public void RequestClear()
        {
            CacheRequest request;

            if (!requestsRecyclePool.Dequeue(out request))
            {
                request = new CacheRequest();
            }
            request.type = RequestType.CLEAR;
            PtDebug.LogVerboseFormat("PBC: enqueing CLEAR request.");
            requestsPendingWork.Enqueue(request);
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Get the next page of assets from the current request.
        /// </summary>
        public void GetNextPageRequest()
        {
            PtDebug.Log("ABM: getting next page of current request...");

            if (CurrentResult == null || !CurrentResult.Ok)
            {
                Debug.LogError("Request failed, no valid current result to get next page of.");
            }

            currentRequest.pageToken = CurrentResult.Value.nextPageToken;
            StartRequest(currentRequest, OnNextPageRequestResult);
        }
 /// <summary>
 /// Initializes the cache (reads the cache state from disk).
 /// </summary>
 private void InitializeCache()
 {
     foreach (string file in Directory.GetFiles(rootPath))
     {
         if (file.EndsWith(BLOB_FILE_EXT))
         {
             FileInfo finfo = new FileInfo(file);
             string   hash  = Path.GetFileNameWithoutExtension(file).ToLowerInvariant();
             cacheEntries[hash] = new CacheEntry(hash, finfo.Length, TicksToMillis(finfo.LastWriteTimeUtc.Ticks));
             PtDebug.LogVerboseFormat("PBC: loaded existing cache item: {0} => {1} bytes",
                                      hash, cacheEntries[hash].fileSize);
         }
     }
 }
Ejemplo n.º 13
0
        private bool PrepareDownload(PolyAsset asset, out string baseName, out string downloadLocalPath)
        {
            assetsBeingDownloaded.Remove(asset);
            PtDebug.LogFormat("ABM: Preparing to download {0}", asset);

            // basePath is something like Assets/Poly/Sources.
            string baseLocalPath = PtUtils.NormalizeLocalPath(PtSettings.Instance.assetSourcesPath);

            if (!baseLocalPath.StartsWith("Assets/"))
            {
                Debug.LogErrorFormat("Invalid asset sources folder {0}. Must be under Assets folder.");
                baseName = downloadLocalPath = null;
                return(false);
            }

            // basePathAbs is something like C:\Users\foo\bar\MyUnityProject\Assets\Poly\Sources
            string baseFullPath = PtUtils.ToAbsolutePath(baseLocalPath);

            if (!Directory.Exists(baseFullPath))
            {
                Directory.CreateDirectory(baseFullPath);
            }

            baseName = PtUtils.GetPtAssetBaseName(asset);
            PtDebug.LogFormat("Import name: {0}", baseName);

            // downloadLocalPath is something like Assets/Poly/Sources/assetTitle_assetId
            downloadLocalPath = baseLocalPath + "/" + baseName;
            string downloadFullPath = PtUtils.ToAbsolutePath(downloadLocalPath);

            if (Directory.Exists(downloadFullPath))
            {
                if (PtSettings.Instance.warnOnSourceOverwrite &&
                    !EditorUtility.DisplayDialog("Warning: Overwriting asset source folder",
                                                 string.Format("The asset source folder '{0}' will be deleted and created again. " +
                                                               "This should be safe *unless* you have manually made changes to its contents, " +
                                                               "in which case you will lose those changes.\n\n" +
                                                               "(You can silence this warning in Poly Toolkit settings)",
                                                               asset.displayName, downloadLocalPath), "OK", "Cancel"))
                {
                    return(false);
                }
                Directory.Delete(downloadFullPath, /* recursive */ true);
            }

            // Create the download folder.
            // Something like C:\Users\foo\bar\MyUnityProject\Assets\Poly\Sources\assetTitle_assetId
            Directory.CreateDirectory(downloadFullPath);
            return(true);
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Parses a single asset.
        /// </summary>
        public static PolyStatus ParseAsset(JObject asset, out PolyAsset polyAsset)
        {
            polyAsset = new PolyAsset();

            if (asset["visibility"] == null)
            {
                return(PolyStatus.Error("Asset has no visibility set."));
            }

            polyAsset.name       = asset["name"].ToString();
            polyAsset.authorName = asset["authorName"].ToString();
            if (asset["thumbnail"] != null)
            {
                var thumbnailElements = asset["thumbnail"].ToObject <JObject>();
                //IJEnumerable<JToken> thumbnailElements = asset["thumbnail"].AsJEnumerable();
                polyAsset.thumbnail = new PolyFile(thumbnailElements["relativePath"].ToString(),
                                                   thumbnailElements["url"].ToString(), thumbnailElements["contentType"].ToString());
            }

            if (asset["formats"] == null)
            {
                Debug.LogError("No formats found");
            }
            else
            {
                foreach (var format in asset["formats"].ToObject <List <JObject> >())
                //foreach (JToken format in asset["formats"])
                {
                    PolyFormat newFormat = ParseAssetsPackage(format);
                    newFormat.formatType = ParsePolyFormatType(format["formatType"]);
                    if (newFormat.formatType == PolyFormatType.UNKNOWN)
                    {
                        PtDebug.Log("Did not recognize format type: " + format["formatType"].ToString());
                    }
                    polyAsset.formats.Add(newFormat);
                }
            }
            polyAsset.displayName = asset["displayName"].ToString();
            polyAsset.createTime  = DateTime.Parse(asset["createTime"].ToString());
            polyAsset.updateTime  = DateTime.Parse(asset["updateTime"].ToString());
            polyAsset.visibility  = ParsePolyVisibility(asset["visibility"]);
            polyAsset.license     = ParsePolyAssetLicense(asset["license"]);
            if (asset["isCurated"] != null)
            {
                polyAsset.isCurated = bool.Parse(asset["isCurated"].ToString());
            }
            return(PolyStatus.Success());
        }
        /// <summary>
        /// Requests a write to the cache. The data will be written asynchronously.
        /// </summary>
        /// <param name="key">The key to write.</param>
        /// <param name="data">The data to write.</param>
        public void RequestWrite(string key, byte[] data)
        {
            string       hash = GetHash(key);
            CacheRequest request;

            if (!requestsRecyclePool.Dequeue(out request))
            {
                request = new CacheRequest();
            }
            request.type = RequestType.WRITE;
            request.key  = key;
            request.hash = hash;
            request.data = data;
            PtDebug.LogVerboseFormat("PBC: enqueing WRITE request for {0}", key);
            requestsPendingWork.Enqueue(request);
        }
        private void TrimCache()
        {
            long totalSize = 0;

            foreach (CacheEntry cacheEntry in cacheEntries.Values)
            {
                totalSize += cacheEntry.fileSize;
            }

            if (totalSize <= maxSizeBytes && cacheEntries.Count <= maxEntries)
            {
                // We're within budget, no need to trim the cache.
                return;
            }

            // Sort the entries from oldest to newest. This is the order in which we will evict them.
            Queue <CacheEntry> entriesOldestToNewest =
                new Queue <CacheEntry>(cacheEntries.Values.OrderBy(entry => entry.writeTimestampMillis));

            // Each iteration evicts the oldest item, until we're back under budget.
            while (totalSize > maxSizeBytes || cacheEntries.Count > maxEntries)
            {
                PtDebug.LogVerboseFormat("PBC: trimming cache, bytes {0}/{1}, entries {2}/{3}",
                                         totalSize, maxSizeBytes, cacheEntries.Values.Count, maxEntries);

                // What's the oldest file?
                if (entriesOldestToNewest.Count == 0)
                {
                    break;
                }
                CacheEntry oldest = entriesOldestToNewest.Dequeue();

                // Delete this file.
                string filePath = HashToFullPath(oldest.hash);
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                }
                cacheEntries.Remove(oldest.hash);

                // Update our accounting
                totalSize -= oldest.fileSize;
            }

            PtDebug.LogVerboseFormat("PBC: end of trim, bytes {0}/{1}, entries {2}/{3}",
                                     totalSize, maxSizeBytes, cacheEntries.Count, maxEntries);
        }
        /// <summary>
        /// Requests a read from the cache.
        /// </summary>
        /// <param name="key">The key to read.</param>
        /// <param name="maxAgeMillis">Maximum age for a cache hit. If the copy we have on cache is older
        /// than this, the request will fail. Use -1 to mean "any age".</param>
        /// <param name="callback">The callback that is to be called (asynchronously) when the read operation
        /// finishes. This callback will be called on the MAIN thread.</param>
        public void RequestRead(string key, long maxAgeMillis, CacheReadCallback callback)
        {
            string       hash = GetHash(key);
            CacheRequest request;

            if (!requestsRecyclePool.Dequeue(out request))
            {
                request = new CacheRequest();
            }
            request.type         = RequestType.READ;
            request.key          = key;
            request.hash         = hash;
            request.readCallback = callback;
            request.maxAgeMillis = maxAgeMillis;
            PtDebug.LogVerboseFormat("PBC: enqueing READ request for {0}", key);
            requestsPendingWork.Enqueue(request);
        }
Ejemplo n.º 18
0
 /// <summary>
 /// Because Poly doesn't live in the Editor/ space (and couldn't, since it uses GameObjects and
 /// MonoBehaviours), it will die every time the user enters or exits play mode. This means
 /// that all of its state and objects will get wiped. So we have to check if it needs initialization
 /// every time we need to use it.
 /// </summary>
 public void EnsurePolyIsReady()
 {
     if (!PolyApi.IsInitialized)
     {
         PtDebug.Log("ABM: Initializing Poly.");
         // We need to set a service name for our auth config because we want to keep our auth credentials
         // separate in a different "silo", so they don't get confused with the runtime credentials
         // the user might be using in their project. Regular users would not set a service name, so they
         // use the default silo.
         authConfig.serviceName = "PolyToolkitEditor";
         PolyApi.Init(authConfig, cacheConfig);
         waitingForSilentAuth = true;
         //PolyApi.Authenticate(interactive: false, callback: (PolyStatus status) => {
         //  waitingForSilentAuth = false;
         //  OnSignInFinished(/* wasInteractive */ false, status);
         //});
     }
 }
Ejemplo n.º 19
0
        /// <summary>
        /// Starts downloading and importing the given asset (in the background). When done, the asset will
        /// be saved to the user's Assets folder.
        /// </summary>
        /// <param name="asset">The asset to download and import.</param>
        /// <param name="ptAssetLocalPath">Path to the PtAsset that should be created (or replaced).</param>
        /// <param name="options">Import options.</param>
        public void StartDownloadAndImport(PolyAsset asset, string ptAssetLocalPath, EditTimeImportOptions options)
        {
            if (!assetsBeingDownloaded.Add(asset))
            {
                return;
            }
            PtDebug.LogFormat("ABM: starting to fetch asset {0} ({1}) -> {2}", asset.name, asset.displayName,
                              ptAssetLocalPath);

            // Prefer glTF1 to glTF2.
            // It used to be that no Poly assets had both formats, so the ordering did not matter.
            // Blocks assets now have both glTF1 and glTF2. PT does not understand the glTF2 version,
            // so the ordering matters a great deal.
            PolyFormat glTF2format = asset.GetFormatIfExists(PolyFormatType.GLTF_2);
            PolyFormat glTFformat  = asset.GetFormatIfExists(PolyFormatType.GLTF);

            PolyMainInternal.FetchProgressCallback progressCallback = (PolyAsset assetBeingFetched, float progress) => {
                EditorUtility.DisplayProgressBar(DOWNLOAD_PROGRESS_TITLE, DOWNLOAD_PROGRESS_TEXT, progress);
            };

            if (glTFformat != null)
            {
                EditorUtility.DisplayProgressBar(DOWNLOAD_PROGRESS_TITLE, DOWNLOAD_PROGRESS_TEXT, 0.0f);
                PolyMainInternal.Instance.FetchFormatFiles(asset, PolyFormatType.GLTF,
                                                           (PolyAsset resultAsset, PolyStatus status) => {
                    EditorUtility.ClearProgressBar();
                    OnFetchFinished(status, resultAsset, /*isGltf2*/ false, ptAssetLocalPath, options);
                }, progressCallback);
            }
            else if (glTF2format != null)
            {
                EditorUtility.DisplayProgressBar(DOWNLOAD_PROGRESS_TITLE, DOWNLOAD_PROGRESS_TEXT, 0.0f);
                PolyMainInternal.Instance.FetchFormatFiles(asset, PolyFormatType.GLTF_2,
                                                           (PolyAsset resultAsset, PolyStatus status) => {
                    EditorUtility.ClearProgressBar();
                    OnFetchFinished(status, resultAsset, /*isGltf2*/ true, ptAssetLocalPath, options);
                }, progressCallback);
            }
            else
            {
                Debug.LogError("Asset not in GLTF_2 or GLTF format. Can't import.");
                PtAnalytics.SendEvent(PtAnalytics.Action.IMPORT_FAILED, "Unsupported format");
            }
        }
Ejemplo n.º 20
0
        public AssetBrowserManager(PolyRequest request)
        {
            PtDebug.Log("ABM initializing...");
            EnsurePolyIsReady();

            // If this is a request that needs authentication and we are in the process of authenticating,
            // wait until we're finished.
            bool needAuth = request is PolyListLikedAssetsRequest || request is PolyListUserAssetsRequest;

            if (needAuth && waitingForSilentAuth)
            {
                // Defer the request. Wait until auth is complete.
                PtDebug.Log("ABM: Deferring request until after auth.");
                requestToSendAfterAuth = request;
                return;
            }

            StartRequest(request);
        }
Ejemplo n.º 21
0
        private void OnFetchFinished(PolyStatus status, PolyAsset asset, bool isGltf2,
                                     string ptAssetLocalPath, EditTimeImportOptions options)
        {
            if (!status.ok)
            {
                Debug.LogErrorFormat("Error fetching asset {0} ({1}): {2}", asset.name, asset.displayName, status);
                EditorUtility.DisplayDialog("Download Error",
                                            string.Format("*** Error downloading asset '{0}'. Try again later.", asset.displayName), "OK");
                PtAnalytics.SendEvent(PtAnalytics.Action.IMPORT_FAILED, "Asset fetch failed");
                return;
            }

            string baseName, downloadLocalPath;

            if (!PrepareDownload(asset, out baseName, out downloadLocalPath))
            {
                return;
            }

            string absPath = PtUtils.ToAbsolutePath(downloadLocalPath);

            string extension = isGltf2 ? ".gltf2" : ".gltf";
            string fileName  = baseName + extension;

            // We have to place an import request so that PolyImporter does the right thing when it sees the new file.
            PolyImporter.AddImportRequest(new PolyImporter.ImportRequest(
                                              downloadLocalPath + "/" + fileName, ptAssetLocalPath, options, asset));

            // Now unpackage it. GltfProcessor will pick it up automatically.
            UnpackPackageToFolder(isGltf2 ? asset.GetFormatIfExists(PolyFormatType.GLTF_2) :
                                  asset.GetFormatIfExists(PolyFormatType.GLTF), absPath, fileName);

            PtDebug.LogFormat("ABM: Successfully downloaded {0} to {1}", asset, absPath);
            AssetDatabase.Refresh();
            if (null != refreshCallback)
            {
                refreshCallback();
            }
        }
Ejemplo n.º 22
0
        /// <summary>
        /// Callback invoked when we receive the result of a request for a specific asset.
        /// </summary>
        private void OnRequestForSpecificAssetResult(PolyStatusOr <PolyAsset> result)
        {
            if (result.Ok)
            {
                PtDebug.Log("ABM: get asset request received result.");
                assetResult = result.Value;

                if (!thumbnailCache.TryGet(assetResult.name, out assetResult.thumbnailTexture))
                {
                    PolyApi.FetchThumbnail(assetResult, OnThumbnailFetched);
                }
            }
            else
            {
                Debug.LogError("Error: " + result.Status.errorMessage);
            }
            querying = false;
            if (null != refreshCallback)
            {
                refreshCallback();
            }
        }
        /// <summary>
        /// (Background thread). Handles a read request, reading it from disk and scheduling the delivery
        /// of the results to the caller.
        /// </summary>
        /// <param name="readRequest">The read request to execute.</param>
        private void BackgroundHandleReadRequest(CacheRequest readRequest)
        {
            PtDebug.LogVerboseFormat("PBC: executing read request for {0} ({1})", readRequest.key,
                                     readRequest.hash);

            string fullPath = HashToFullPath(readRequest.hash);

            CacheEntry entry;

            if (!cacheEntries.TryGetValue(readRequest.hash, out entry))
            {
                // Not in the cache
                readRequest.data    = null;
                readRequest.success = false;
            }
            else if (readRequest.maxAgeMillis > 0 && entry.AgeMillis > readRequest.maxAgeMillis)
            {
                // Too old.
                readRequest.data    = null;
                readRequest.success = false;
            }
            else if (!File.Exists(fullPath))
            {
                // Too old.
                readRequest.data    = null;
                readRequest.success = false;
            }
            else
            {
                // Found it.
                readRequest.data    = File.ReadAllBytes(fullPath);
                readRequest.success = true;
                // Update the read timestamp.
                entry.readTimestampMillis = TicksToMillis(DateTime.UtcNow.Ticks);
            }

            // Schedule the result for delivery to the caller.
            requestsPendingDelivery.Enqueue(readRequest);
        }
Ejemplo n.º 24
0
        /// <summary>
        /// Sends a hit to Google Analytics.
        /// </summary>
        /// <param name="fields">Key-value pairs that make up the properties of the hit. These
        /// are assumed to be in the appropriate Google Analytics format.</param>
        private void SendHit(Dictionary <string, string> fields)
        {
            StringBuilder sb = new StringBuilder(prefix);

            foreach (KeyValuePair <string, string> pair in fields)
            {
                sb.AppendFormat("&{0}={1}", UnityWebRequest.EscapeURL(pair.Key), UnityWebRequest.EscapeURL(pair.Value));
            }
            string payload = sb.ToString();

            try {
                UnityWebRequest request = new UnityWebRequest("https://www.google-analytics.com/collect", "POST");
                request.uploadHandler             = new UploadHandlerRaw(Encoding.UTF8.GetBytes(payload));
                request.uploadHandler.contentType = "application/x-www-form-urlencoded";
                UnityCompat.SendWebRequest(request);
                PtDebug.LogFormat("ANALYTICS: sent hit: {0}", payload);
            } catch (Exception ex) {
                // Reporting these as errors would be noisy and annoying. We don't want to do that -- maybe the user is
                // offline. Not being able to send analytics isn't a big deal. So only log this error if
                // PtDebug verbose logging is on.
                PtDebug.LogFormat("*** Error sending analytics: {0}", ex);
            }
        }
Ejemplo n.º 25
0
        /// <summary>
        /// Co-routine that services one PendingRequest. This method must be called with StartCoroutine.
        /// </summary>
        /// <param name="request">The request to service.</param>
        private IEnumerator HandleWebRequest(PendingRequest request, BufferHolder bufferHolder)
        {
            // NOTE: This method runs on the main thread, but never blocks -- the blocking part of the work is
            // done by yielding the UnityWebRequest, which releases the main thread for other tasks while we
            // are waiting for the web request to complete (by the miracle of coroutines).

            // Let the caller create the UnityWebRequest, configuring it as they want. The caller can set the URL,
            // method, headers, anything they want. The only thing they can't do is call Send(), as we're in charge
            // of doing that.
            UnityWebRequest webRequest = request.creationCallback();

            PtDebug.LogVerboseFormat("Web request: {0} {1}", webRequest.method, webRequest.url);

            bool cacheAllowed = cache != null && webRequest.method == "GET" && request.maxAgeMillis != CACHE_NONE;

            // Check the cache (if it's a GET request and cache is enabled).
            if (cacheAllowed)
            {
                bool   cacheHit      = false;
                byte[] cacheData     = null;
                bool   cacheReadDone = false;
                cache.RequestRead(webRequest.url, request.maxAgeMillis, (bool success, byte[] data) =>
                {
                    cacheHit      = success;
                    cacheData     = data;
                    cacheReadDone = true;
                });
                while (!cacheReadDone)
                {
                    yield return(null);
                }
                if (cacheHit)
                {
                    PtDebug.LogVerboseFormat("Web request CACHE HIT: {0}, response: {1} bytes",
                                             webRequest.url, cacheData.Length);
                    request.completionCallback(PolyStatus.Success(), /* responseCode */ 200, cacheData);

                    // Return the buffer to the pool for reuse.
                    CleanUpAfterWebRequest(bufferHolder);

                    yield break;
                }
                else
                {
                    PtDebug.LogVerboseFormat("Web request CACHE MISS: {0}.", webRequest.url);
                }
            }

            DownloadHandlerBuffer handler = new DownloadHandlerBuffer();

            webRequest.downloadHandler = handler;

            // We need to asset that we actually succeeded in setting the download handler, because this can fail
            // if, for example, the creation callback mistakenly called Send().
            PolyUtils.AssertTrue(webRequest.downloadHandler == handler,
                                 "Couldn't set download handler. It's either disposed of, or the creation callback mistakenly called Send().");

            // Start the web request. This will suspend this coroutine until the request is done.
            PtDebug.LogVerboseFormat("Sending web request: {0}", webRequest.url);
            yield return(UnityCompat.SendWebRequest(webRequest));

            // Request is finished. Call user-supplied callback.
            PtDebug.LogVerboseFormat("Web request finished: {0}, HTTP response code {1}, response: {2}",
                                     webRequest.url, webRequest.responseCode, webRequest.downloadHandler.text);
            PolyStatus status = UnityCompat.IsNetworkError(webRequest) ? PolyStatus.Error(webRequest.error) : PolyStatus.Success();

            request.completionCallback(status, (int)webRequest.responseCode, webRequest.downloadHandler.data);

            // Cache the result, if applicable.
            if (!UnityCompat.IsNetworkError(webRequest) && cacheAllowed)
            {
                byte[] data = webRequest.downloadHandler.data;
                if (data != null && data.Length > 0)
                {
                    byte[] copy = new byte[data.Length];
                    Buffer.BlockCopy(data, 0, copy, 0, data.Length);
                    cache.RequestWrite(webRequest.url, copy);
                }
            }

            // Clean up.
            webRequest.Dispose();
            CleanUpAfterWebRequest(bufferHolder);
        }
Ejemplo n.º 26
0
        /// <summary>
        /// Executes the given import request, producing a PtAsset and a prefab.
        /// </summary>
        /// <param name="request">The request to perform.</param>
        private static void ExecuteImportRequest(ImportRequest request)
        {
            PtDebug.LogFormat("Executing import request: {0}", request);

            string gltfFullPath   = PtUtils.ToAbsolutePath(request.gltfLocalPath);
            string assetLocalPath = request.ptAssetLocalPath;
            string assetFullPath  = PtUtils.ToAbsolutePath(assetLocalPath);

            PtAsset assetToReplace = AssetDatabase.LoadAssetAtPath <PtAsset>(assetLocalPath);

            GameObject prefabToReplace = null;

            if (assetToReplace != null)
            {
                if (assetToReplace.assetPrefab == null)
                {
                    Debug.LogErrorFormat("Couldn't find prefab for asset {0}.", assetToReplace);
                    PtAnalytics.SendEvent(PtAnalytics.Action.IMPORT_FAILED, "Prefab not found");
                    return;
                }
                prefabToReplace = assetToReplace.assetPrefab;
            }

            // Determine if file is glTF2 or glTF1.
            bool isGltf2 = Path.GetExtension(request.gltfLocalPath) == ".gltf2";

            // First, import the GLTF and build a GameObject from it.
            EditorUtility.DisplayProgressBar(PROGRESS_BAR_TITLE, PROGRESS_BAR_TEXT, 0.5f);
            // Use a SanitizedPath stream loader because any format file we have downloaded and saved to disk we
            // have replaced the original relative path string with the MD5 string hash. This custom stream loader
            // will always convert uris passed to it to this hash value, and read them from there.
            IUriLoader binLoader = new HashedPathBufferedStreamLoader(Path.GetDirectoryName(gltfFullPath));

            ImportGltf.GltfImportResult result = null;
            using (TextReader reader = new StreamReader(gltfFullPath)) {
                result = ImportGltf.Import(isGltf2 ? GltfSchemaVersion.GLTF2 : GltfSchemaVersion.GLTF1,
                                           reader, binLoader, request.options.baseOptions);
            }
            EditorUtility.ClearProgressBar();
            string baseName = PtUtils.GetPtAssetBaseName(request.polyAsset);

            result.root.name = baseName;

            // Create the asset (delete it first if it exists).
            if (File.Exists(assetFullPath))
            {
                AssetDatabase.DeleteAsset(assetLocalPath);

                // If we are replacing an existing asset, we should rename the replacement to the new name,
                // since the name reflects the identity of the asset. So if the user is importing the asset
                // dog_a381b3g to replace what was previously cat_v81938.asset, the replacement file should
                // be named dog_a381b3g.asset, not cat_v81938.asset.
                assetLocalPath = PtUtils.GetDefaultPtAssetPath(request.polyAsset);
                assetFullPath  = PtUtils.ToAbsolutePath(assetLocalPath);
            }
            Directory.CreateDirectory(Path.GetDirectoryName(assetFullPath));

            // Create the new PtAsset and fill it in.
            AssetDatabase.CreateAsset(ScriptableObject.CreateInstance <PtAsset>(), assetLocalPath);
            PtAsset newAsset = AssetDatabase.LoadAssetAtPath <PtAsset>(assetLocalPath);

            newAsset.name    = baseName;
            newAsset.title   = request.polyAsset.displayName ?? "";
            newAsset.author  = request.polyAsset.authorName ?? "";
            newAsset.license = request.polyAsset.license;
            newAsset.url     = request.polyAsset.Url;

            // Ensure the imported object has a PtAssetObject component which references the PtAsset.
            result.root.AddComponent <PtAssetObject>().asset = newAsset;

            // Add all the meshes to the PtAsset.
            SaveMeshes(result.meshes, newAsset);

            // If the asset has materials, save those to the PtAsset.
            if (result.materials != null)
            {
                SaveMaterials(result.materials, newAsset);
            }

            // If the asset has textures, save those to the PtAsset.
            if (result.textures != null)
            {
                SaveTextures(result.textures, newAsset);
            }

            // Reimport is required to ensure custom asset displays correctly.
            AssetDatabase.ImportAsset(assetLocalPath);

            GameObject newPrefab;

            if (prefabToReplace)
            {
                // Replace the existing prefab with our new object, without breaking prefab connections.
                newPrefab = PrefabUtility.ReplacePrefab(result.root, prefabToReplace, ReplacePrefabOptions.ReplaceNameBased);
                AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(newPrefab), baseName);
            }
            else
            {
                // Create a new prefab.
                // Prefab path is the same as the asset path but with the extension changed to '.prefab'.
                string prefabLocalPath = Regex.Replace(assetLocalPath, "\\.asset$", ".prefab");
                if (!prefabLocalPath.EndsWith(".prefab"))
                {
                    Debug.LogErrorFormat("Error: failed to compute prefab path for {0}", assetLocalPath);
                    PtAnalytics.SendEvent(PtAnalytics.Action.IMPORT_FAILED, "Prefab path error");
                    return;
                }
                newPrefab = PrefabUtility.CreatePrefab(prefabLocalPath, result.root);
            }

            // Now ensure the asset points to the prefab.
            newAsset.assetPrefab = newPrefab;
            if (newAsset.assetPrefab == null)
            {
                Debug.LogErrorFormat("Could not get asset prefab reference for asset {0}", newAsset);
                PtAnalytics.SendEvent(PtAnalytics.Action.IMPORT_FAILED, "Prefab ref error");
            }

            GameObject.DestroyImmediate(result.root);

            AssetDatabase.Refresh();

            if (request.options.alsoInstantiate)
            {
                PrefabUtility.InstantiatePrefab(newPrefab);
            }

            PtDebug.LogFormat("GLTF import complete: {0}", request);

            PtAnalytics.SendEvent(PtAnalytics.Action.IMPORT_SUCCESSFUL, isGltf2 ? "GLTF2" : "GLTF1");

            // If this is a third-party asset, we need to update the attributions file.
            AttributionFileGenerator.Generate(/* showUi */ false);

            EditorWindow.GetWindow <AssetBrowserWindow>().HandleAssetImported(request.polyAsset.name);

            // Select the prefab in the editor so the user knows where it is.
            AssetDatabase.Refresh();
            Selection.activeObject = newPrefab;
            EditorGUIUtility.PingObject(newPrefab);
        }