Пример #1
0
        /// <inheritdoc/>
        public async Task <ImageCacheMetadata> GetMetaDataAsync()
        {
            using Stream stream = this.metaFileInfo.CreateReadStream();
            this.metadata       = await ImageCacheMetadata.ReadAsync(stream);

            return(this.metadata);
        }
Пример #2
0
        /// <inheritdoc/>
        public async Task <IImageCacheResolver> GetAsync(string key)
        {
            string path = this.ToFilePath(key);

            IFileInfo metaFileInfo = this.fileProvider.GetFileInfo(this.ToMetaDataFilePath(path));

            if (!metaFileInfo.Exists)
            {
                return(null);
            }

            ImageCacheMetadata metadata = default;

            using (Stream stream = metaFileInfo.CreateReadStream())
            {
                metadata = await ImageCacheMetadata.ReadAsync(stream).ConfigureAwait(false);
            }

            IFileInfo fileInfo = this.fileProvider.GetFileInfo(this.ToImageFilePath(path, metadata));

            // Check to see if the file exists.
            if (!fileInfo.Exists)
            {
                return(null);
            }

            return(new PhysicalFileSystemCacheResolver(fileInfo, metadata));
        }
Пример #3
0
 public ImageWorkerResult(ImageCacheMetadata cacheImageMetadata, IImageCacheResolver resolver)
 {
     this.IsNewOrUpdated      = false;
     this.SourceImageMetadata = default;
     this.CacheImageMetadata  = cacheImageMetadata;
     this.Resolver            = resolver;
 }
        /// <inheritdoc/>
        public async Task <ImageCacheMetadata> GetMetaDataAsync()
        {
            if (this.metadata == default)
            {
                using FileStream stream = OpenFileStream(this.metaFileInfo.FullName);
                this.metadata           = await ImageCacheMetadata.ReadAsync(stream);
            }

            return(this.metadata);
        }
        /// <inheritdoc/>
        public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
        {
            BlobClient blob = this.container.GetBlobClient(key);

            var headers = new BlobHttpHeaders
            {
                ContentType = metadata.ContentType,
            };

            return(blob.UploadAsync(stream, httpHeaders: headers, metadata: metadata.ToDictionary()));
        }
Пример #6
0
        /// <inheritdoc/>
        public Task <ImageCacheMetadata> GetMetaDataAsync()
        {
            Dictionary <string, string> dict = new();

            foreach (string key in this.metadata.Keys)
            {
                // Trim automatically added x-amz-meta-
                dict.Add(key.Substring(11).ToUpperInvariant(), this.metadata[key]);
            }

            return(Task.FromResult(ImageCacheMetadata.FromDictionary(dict)));
        }
Пример #7
0
        /// <inheritdoc/>
        public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
        {
            var request = new PutObjectRequest()
            {
                BucketName      = this.bucket,
                Key             = key,
                ContentType     = metadata.ContentType,
                InputStream     = stream,
                AutoCloseStream = false
            };

            foreach (KeyValuePair <string, string> d in metadata.ToDictionary())
            {
                request.Metadata.Add(d.Key, d.Value);
            }

            return(this.amazonS3Client.PutObjectAsync(request));
        }
Пример #8
0
        /// <inheritdoc/>
        public async Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
        {
            string path      = Path.Combine(this.environment.WebRootPath, this.ToFilePath(key));
            string imagePath = this.ToImageFilePath(path, metadata);
            string metaPath  = this.ToMetaDataFilePath(path);
            string directory = Path.GetDirectoryName(path);

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

            using (FileStream fileStream = File.Create(imagePath))
            {
                await stream.CopyToAsync(fileStream).ConfigureAwait(false);
            }

            using (FileStream fileStream = File.Create(metaPath))
            {
                await metadata.WriteAsync(fileStream).ConfigureAwait(false);
            }
        }
Пример #9
0
 public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata) => Task.CompletedTask;
        private async Task SendResponseAsync(ImageContext imageContext, string key, Stream stream, ImageCacheMetadata metadata)
        {
            imageContext.ComprehendRequestHeaders(metadata.CacheLastWriteTimeUtc, stream.Length);

            switch (imageContext.GetPreconditionState())
            {
            case ImageContext.PreconditionState.Unspecified:
            case ImageContext.PreconditionState.ShouldProcess:
                if (imageContext.IsHeadRequest())
                {
                    await imageContext.SendStatusAsync(ResponseConstants.Status200Ok, metadata).ConfigureAwait(false);
                }

                this.logger.LogImageServed(imageContext.GetDisplayUrl(), key);
                await imageContext.SendAsync(stream, metadata).ConfigureAwait(false);

                break;

            case ImageContext.PreconditionState.NotModified:
                this.logger.LogImageNotModified(imageContext.GetDisplayUrl());
                await imageContext.SendStatusAsync(ResponseConstants.Status304NotModified, metadata).ConfigureAwait(false);

                break;

            case ImageContext.PreconditionState.PreconditionFailed:
                this.logger.LogImagePreconditionFailed(imageContext.GetDisplayUrl());
                await imageContext.SendStatusAsync(ResponseConstants.Status412PreconditionFailed, metadata).ConfigureAwait(false);

                break;

            default:
                var exception = new NotImplementedException(imageContext.GetPreconditionState().ToString());
                Debug.Fail(exception.ToString());
                throw exception;
            }
        }
        private async Task ProcessRequestAsync(HttpContext context, bool processRequest, IImageResolver sourceImageResolver, ImageContext imageContext, IDictionary <string, string> commands)
        {
            // Create a cache key based on all the components of the requested url
            string uri = GetUri(context, commands);
            string key = this.cacheHash.Create(uri, this.options.CachedNameLength);

            ImageMetadata sourceImageMetadata = default;

            if (processRequest)
            {
                // Lock any reads when a write is being done for the same key to prevent potential file locks.
                using (await AsyncLock.ReaderLockAsync(key).ConfigureAwait(false))
                {
                    // Check to see if the cache contains this image
                    sourceImageMetadata = await sourceImageResolver.GetMetaDataAsync().ConfigureAwait(false);

                    IImageCacheResolver cachedImageResolver = await this.cache.GetAsync(key).ConfigureAwait(false);

                    if (cachedImageResolver != null)
                    {
                        ImageCacheMetadata cachedImageMetadata = await cachedImageResolver.GetMetaDataAsync().ConfigureAwait(false);

                        if (cachedImageMetadata != default)
                        {
                            // Has the cached image expired or has the source image been updated?
                            if (cachedImageMetadata.SourceLastWriteTimeUtc == sourceImageMetadata.LastWriteTimeUtc &&
                                cachedImageMetadata.CacheLastWriteTimeUtc > DateTimeOffset.Now.AddDays(-this.options.MaxCacheDays))
                            {
                                // We're pulling the image from the cache.
                                using (Stream cachedBuffer = await cachedImageResolver.OpenReadAsync().ConfigureAwait(false))
                                {
                                    await this.SendResponseAsync(imageContext, key, cachedBuffer, cachedImageMetadata).ConfigureAwait(false);
                                }

                                return;
                            }
                        }
                    }
                }

                // Not cached? Let's get it from the image resolver.
                ChunkedMemoryStream outStream = null;
                try
                {
                    if (processRequest)
                    {
                        // Enter a write lock which locks writing and any reads for the same request.
                        // This reduces the overheads of unnecessary processing plus avoids file locks.
                        using (await AsyncLock.WriterLockAsync(key).ConfigureAwait(false))
                        {
                            // No allocations here for inStream since we are passing the raw input stream.
                            // outStream allocation depends on the memory allocator used.
                            ImageCacheMetadata cachedImageMetadata = default;
                            outStream = new ChunkedMemoryStream(this.memoryAllocator);
                            using (Stream inStream = await sourceImageResolver.OpenReadAsync().ConfigureAwait(false))
                                using (var image = FormattedImage.Load(this.options.Configuration, inStream))
                                {
                                    image.Process(this.logger, this.processors, commands);
                                    this.options.OnBeforeSave?.Invoke(image);
                                    image.Save(outStream);

                                    // Check to see if the source metadata has a cachecontrol max-age value and use it to
                                    // override the default max age from our options.
                                    var maxAge = TimeSpan.FromDays(this.options.MaxBrowserCacheDays);
                                    if (!sourceImageMetadata.CacheControlMaxAge.Equals(TimeSpan.MinValue))
                                    {
                                        maxAge = sourceImageMetadata.CacheControlMaxAge;
                                    }

                                    cachedImageMetadata = new ImageCacheMetadata(
                                        sourceImageMetadata.LastWriteTimeUtc,
                                        DateTime.UtcNow,
                                        image.Format.DefaultMimeType,
                                        maxAge);
                                }

                            // Allow for any further optimization of the image. Always reset the position just in case.
                            outStream.Position = 0;
                            string contentType = cachedImageMetadata.ContentType;
                            string extension   = this.formatUtilities.GetExtensionFromContentType(contentType);
                            this.options.OnProcessed?.Invoke(new ImageProcessingContext(context, outStream, commands, contentType, extension));
                            outStream.Position = 0;

                            // Save the image to the cache and send the response to the caller.
                            await this.cache.SetAsync(key, outStream, cachedImageMetadata).ConfigureAwait(false);

                            await this.SendResponseAsync(imageContext, key, outStream, cachedImageMetadata).ConfigureAwait(false);
                        }
                    }
                }
                catch (Exception ex)
                {
                    // Log the error internally then rethrow.
                    // We don't call next here, the pipeline will automatically handle it
                    this.logger.LogImageProcessingFailed(imageContext.GetDisplayUrl(), ex);
                    throw;
                }
                finally
                {
                    outStream?.Dispose();
                }
            }
        }
Пример #12
0
        private async Task ProcessRequestAsync(
            HttpContext context,
            IImageResolver sourceImageResolver,
            ImageContext imageContext,
            IDictionary <string, string> commands)
        {
            // Create a cache key based on all the components of the requested url
            string uri = GetUri(context, commands);
            string key = this.cacheHash.Create(uri, this.options.CachedNameLength);

            // Check the cache, if present, not out of date and not requiring and update
            // we'll simply serve the file from there.
            ImageWorkerResult readResult = default;

            try
            {
                readResult = await this.IsNewOrUpdatedAsync(sourceImageResolver, imageContext, key);
            }
            finally
            {
                ReadWorkers.TryRemove(key, out Task <ImageWorkerResult> _);
            }

            if (!readResult.IsNewOrUpdated)
            {
                await this.SendResponseAsync(imageContext, key, readResult.CacheImageMetadata, readResult.Resolver);

                return;
            }

            // Not cached, or is updated? Let's get it from the image resolver.
            var sourceImageMetadata = readResult.SourceImageMetadata;

            // Enter an asynchronous write worker which prevents multiple writes and delays any reads for the same request.
            // This reduces the overheads of unnecessary processing.
            try
            {
                ImageWorkerResult writeResult = await WriteWorkers.GetOrAddAsync(
                    key,
                    async (key) =>
                {
                    RecyclableMemoryStream outStream = null;
                    try
                    {
                        // Prevent a second request from starting a read during write execution.
                        if (ReadWorkers.TryGetValue(key, out Task <ImageWorkerResult> readWork))
                        {
                            await readWork;
                        }

                        ImageCacheMetadata cachedImageMetadata = default;
                        outStream = new RecyclableMemoryStream(this.options.MemoryStreamManager);
                        IImageFormat format;

                        // 14.9.3 CacheControl Max-Age
                        // Check to see if the source metadata has a CacheControl Max-Age value
                        // and use it to override the default max age from our options.
                        TimeSpan maxAge = this.options.BrowserMaxAge;
                        if (!sourceImageMetadata.CacheControlMaxAge.Equals(TimeSpan.MinValue))
                        {
                            maxAge = sourceImageMetadata.CacheControlMaxAge;
                        }

                        using (Stream inStream = await sourceImageResolver.OpenReadAsync())
                        {
                            // No commands? We simply copy the stream across.
                            if (commands.Count == 0)
                            {
                                await inStream.CopyToAsync(outStream);
                                outStream.Position = 0;
                                format             = await Image.DetectFormatAsync(this.options.Configuration, outStream);
                            }
                            else
                            {
                                using var image = FormattedImage.Load(this.options.Configuration, inStream);

                                image.Process(
                                    this.logger,
                                    this.processors,
                                    commands,
                                    this.commandParser,
                                    this.parserCulture);

                                await this.options.OnBeforeSaveAsync.Invoke(image);

                                image.Save(outStream);
                                format = image.Format;
                            }
                        }

                        // Allow for any further optimization of the image.
                        outStream.Position = 0;
                        string contentType = format.DefaultMimeType;
                        string extension   = this.formatUtilities.GetExtensionFromContentType(contentType);
                        await this.options.OnProcessedAsync.Invoke(new ImageProcessingContext(context, outStream, commands, contentType, extension));
                        outStream.Position = 0;

                        cachedImageMetadata = new ImageCacheMetadata(
                            sourceImageMetadata.LastWriteTimeUtc,
                            DateTime.UtcNow,
                            contentType,
                            maxAge,
                            outStream.Length);

                        // Save the image to the cache and send the response to the caller.
                        await this.cache.SetAsync(key, outStream, cachedImageMetadata);

                        // Remove any resolver from the cache so we always resolve next request
                        // for the same key.
                        CacheResolverLru.TryRemove(key);

                        // Place the resolver in the lru cache.
                        (IImageCacheResolver ImageCacheResolver, ImageCacheMetadata ImageCacheMetadata)cachedImage = await
                                                                                                                     CacheResolverLru.GetOrAddAsync(
                            key,
                            async k =>
                        {
                            IImageCacheResolver resolver = await this.cache.GetAsync(k);
                            ImageCacheMetadata metadata  = default;
                            if (resolver != null)
                            {
                                metadata = await resolver.GetMetaDataAsync();
                            }

                            return(resolver, metadata);
                        });

                        return(new ImageWorkerResult(cachedImage.ImageCacheMetadata, cachedImage.ImageCacheResolver));
                    }
                    catch (Exception ex)
                    {
                        // Log the error internally then rethrow.
                        // We don't call next here, the pipeline will automatically handle it
                        this.logger.LogImageProcessingFailed(imageContext.GetDisplayUrl(), ex);
                        throw;
                    }
                    finally
                    {
                        await this.StreamDisposeAsync(outStream);
                    }
                });

                await this.SendResponseAsync(imageContext, key, writeResult.CacheImageMetadata, writeResult.Resolver);
            }
            finally
            {
                // As soon as we have sent a response from a writer the result is available from a reader so we remove this task.
                // Any existing awaiters will continue to await.
                WriteWorkers.TryRemove(key, out Task <ImageWorkerResult> _);
            }
        }
Пример #13
0
        private async Task ProcessRequestAsync(
            HttpContext context,
            IImageResolver sourceImageResolver,
            ImageContext imageContext,
            IDictionary <string, string> commands)
        {
            // Create a cache key based on all the components of the requested url
            string uri = GetUri(context, commands);
            string key = this.cacheHash.Create(uri, this.options.CachedNameLength);

            // Check the cache, if present, not out of date and not requiring and update
            // we'll simply serve the file from there.
            (bool newOrUpdated, ImageMetadata sourceImageMetadata) =
                await this.IsNewOrUpdatedAsync(sourceImageResolver, imageContext, key);

            if (!newOrUpdated)
            {
                return;
            }

            // Not cached? Let's get it from the image resolver.
            RecyclableMemoryStream outStream = null;

            // Enter a write lock which locks writing and any reads for the same request.
            // This reduces the overheads of unnecessary processing plus avoids file locks.
            await WriteWorkers.GetOrAdd(
                key,
                _ => new Lazy <Task>(
                    async() =>
            {
                try
                {
                    // Prevent a second request from starting a read during write execution.
                    if (ReadWorkers.TryGetValue(key, out Lazy <Task <(bool, ImageMetadata)> > readWork))
                    {
                        await readWork.Value;
                    }

                    ImageCacheMetadata cachedImageMetadata = default;
                    outStream = new RecyclableMemoryStream(this.options.MemoryStreamManager);
                    IImageFormat format;

                    // 14.9.3 CacheControl Max-Age
                    // Check to see if the source metadata has a CacheControl Max-Age value
                    // and use it to override the default max age from our options.
                    TimeSpan maxAge = this.options.BrowserMaxAge;
                    if (!sourceImageMetadata.CacheControlMaxAge.Equals(TimeSpan.MinValue))
                    {
                        maxAge = sourceImageMetadata.CacheControlMaxAge;
                    }

                    using (Stream inStream = await sourceImageResolver.OpenReadAsync())
                    {
                        // No commands? We simply copy the stream across.
                        if (commands.Count == 0)
                        {
                            await inStream.CopyToAsync(outStream);
                            outStream.Position = 0;
                            format             = await Image.DetectFormatAsync(this.options.Configuration, outStream);
                        }
                        else
                        {
                            using var image = FormattedImage.Load(this.options.Configuration, inStream);

                            image.Process(
                                this.logger,
                                this.processors,
                                commands,
                                this.commandParser,
                                this.parserCulture);

                            await this.options.OnBeforeSaveAsync.Invoke(image);

                            image.Save(outStream);
                            format = image.Format;
                        }
                    }

                    // Allow for any further optimization of the image.
                    outStream.Position = 0;
                    string contentType = format.DefaultMimeType;
                    string extension   = this.formatUtilities.GetExtensionFromContentType(contentType);
                    await this.options.OnProcessedAsync.Invoke(new ImageProcessingContext(context, outStream, commands, contentType, extension));
                    outStream.Position = 0;

                    cachedImageMetadata = new ImageCacheMetadata(
                        sourceImageMetadata.LastWriteTimeUtc,
                        DateTime.UtcNow,
                        contentType,
                        maxAge,
                        outStream.Length);

                    // Save the image to the cache and send the response to the caller.
                    await this.cache.SetAsync(key, outStream, cachedImageMetadata);

                    // Remove the resolver from the cache so we always resolve next request
                    // for the same key.
                    CacheResolverLru.TryRemove(key);

                    await this.SendResponseAsync(imageContext, key, cachedImageMetadata, outStream, null);
                }
                catch (Exception ex)
                {
                    // Log the error internally then rethrow.
                    // We don't call next here, the pipeline will automatically handle it
                    this.logger.LogImageProcessingFailed(imageContext.GetDisplayUrl(), ex);
                    throw;
                }
                finally
                {
                    await this.StreamDisposeAsync(outStream);
                    WriteWorkers.TryRemove(key, out Lazy <Task> _);
                }
            }, LazyThreadSafetyMode.ExecutionAndPublication)).Value;
        }
 public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
 {
     return(Task.CompletedTask);
 }