public string StoreStream(Stream stream, string extension)
        {
            // Compute SHA1
            string sha1 = Object3D.ComputeSHA1(stream);

            string fileName  = $"{sha1}{extension}";
            string assetPath = Path.Combine(Object3D.AssetsPath, fileName);

            // Load cache
            if (!File.Exists(assetPath))
            {
                stream.Position = 0;

                using (var outstream = File.OpenWrite(assetPath))
                {
                    stream.CopyTo(outstream);
                }
            }

            return(fileName);
        }
        public async Task <string> StoreMcx(IObject3D object3D, bool publishAfterSave)
        {
            // TODO: Track SHA1 of persisted asset
            // TODO: Skip if cached sha1 exists in assets

            // Serialize object3D to in memory mcx/json stream
            using (var memoryStream = new MemoryStream())
            {
                // Write JSON
                object3D.SaveTo(memoryStream);

                // Reposition
                memoryStream.Position = 0;

                Directory.CreateDirectory(Object3D.AssetsPath);

                // Calculate
                string sha1 = Object3D.ComputeSHA1(memoryStream);
                string sha1PlusExtension = sha1 + ".mcx";
                string assetPath         = Path.Combine(Object3D.AssetsPath, sha1PlusExtension);

                if (!File.Exists(assetPath))
                {
                    memoryStream.Position = 0;

                    using (var outStream = File.Create(assetPath))
                    {
                        memoryStream.CopyTo(outStream);
                    }
                }

                await ConditionalPublish(
                    sha1PlusExtension,
                    publishAfterSave,
                    CancellationToken.None,
                    null);

                return(assetPath);
            }
        }