File: ImageSources\UriImageSourceService\UriImageSourceService.iOS.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
#nullable enable
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Foundation;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Storage;
using UIKit;
 
namespace Microsoft.Maui
{
	public partial class UriImageSourceService
	{
		internal static readonly string CacheDirectory = Path.Combine(FileSystem.CacheDirectory, "com.microsoft.maui", "MauiUriImages");
 
		public override Task<IImageSourceServiceResult<UIImage>?> GetImageAsync(IImageSource imageSource, float scale = 1, CancellationToken cancellationToken = default) =>
			GetImageAsync((IUriImageSource)imageSource, scale, cancellationToken);
 
		public async Task<IImageSourceServiceResult<UIImage>?> GetImageAsync(IUriImageSource imageSource, float scale = 1, CancellationToken cancellationToken = default)
		{
			if (imageSource.IsEmpty)
				return null;
 
			try
			{
				var imageData = await DownloadAndCacheImageAsync(imageSource, cancellationToken);
 
				using var cgImageSource = imageData.GetPlatformImageSource();
				if (cgImageSource is null)
					throw new InvalidOperationException("Unable to load image file.");
 
				var image = cgImageSource.GetPlatformImage();
 
				var result = new ImageSourceServiceResult(image, () => image.Dispose());
 
				return result;
			}
			catch (Exception ex)
			{
				Logger?.LogWarning(ex, "Unable to load image URI '{Uri}'.", imageSource.Uri);
				throw;
			}
		}
 
		internal async Task<NSData> DownloadAndCacheImageAsync(IUriImageSource imageSource, CancellationToken cancellationToken)
		{
			// TODO: use a real caching library with the URI
 
			var filename = GetCachedFileName(imageSource);
			var pathToImageCache = Path.Combine(CacheDirectory, filename);
 
			NSData? imageData;
 
			if (imageSource.CachingEnabled && IsImageCached(pathToImageCache))
			{
				imageData = GetCachedImage(pathToImageCache);
			}
			else
			{
				imageData = await DownloadImageAsync(imageSource, cancellationToken);
				if (imageSource.CachingEnabled)
					CacheImage(imageData, pathToImageCache);
			}
 
			return imageData;
		}
 
		internal static async Task<NSData> DownloadImageAsync(IUriImageSource imageSource, CancellationToken cancellationToken)
		{
			if (imageSource is not IStreamImageSource streamImageSource)
				throw new InvalidOperationException($"Unable to load image stream from image source type '{imageSource.GetType()}'.");
 
			using var stream = await streamImageSource.GetStreamAsync(cancellationToken).ConfigureAwait(false);
			if (stream is null)
				throw new InvalidOperationException($"Unable to load image stream from URI '{imageSource.Uri}'.");
 
			var imageData = NSData.FromStream(stream);
 
			if (imageData is null)
				throw new InvalidOperationException("Unable to load image stream data.");
 
			return imageData;
		}
 
#pragma warning disable CA1822 // Mark members as static; Disabling because these methods are public and changing them to static is potentially a breaking change
		public void CacheImage(NSData imageData, string path)
		{
			var directory = Path.GetDirectoryName(path);
 
			if (string.IsNullOrEmpty(directory))
				throw new InvalidOperationException($"Unable to get directory path name '{path}'.");
 
			Directory.CreateDirectory(directory);
 
#pragma warning disable CA1416, CA1422 // https://github.com/xamarin/xamarin-macios/issues/14619
			var result = imageData.Save(path, true);
#pragma warning restore CA1416, CA1422
 
			if (result == false)
				throw new InvalidOperationException($"Unable to cache image at '{path}'.");
		}
 
		public bool IsImageCached(string path)
		{
			return File.Exists(path);
		}
 
		public NSData GetCachedImage(string path)
		{
			var imageData = NSData.FromFile(path);
 
			if (imageData == null)
				throw new InvalidOperationException($"Unable to load image stream data from '{path}'.");
 
			return imageData;
		}
 
		internal string GetCachedFileName(IUriImageSource imageSource)
		{
			var hash = Crc64.ComputeHashString(imageSource.Uri.OriginalString);
			var ext = Path.GetExtension(imageSource.Uri.AbsolutePath);
			var filename = $"{hash}{ext}";
			return filename;
		}
#pragma warning restore CA1822 // Mark members as static
	}
}