File: FileSystem\FileSystem.ios.cs
Web Access
Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Foundation;
using Microsoft.Maui.Graphics.Platform;
using Photos;
using UIKit;
 
namespace Microsoft.Maui.Storage
{
	static partial class FileSystemUtils
	{
		public static async Task<FileResult[]> EnsurePhysicalFileResultsAsync(params NSUrl[] urls)
		{
			if (urls == null || urls.Length == 0)
				return Array.Empty<FileResult>();
 
			var opts = NSFileCoordinatorReadingOptions.WithoutChanges;
			var intents = urls.Select(x => NSFileAccessIntent.CreateReadingIntent(x, opts)).ToArray();
 
			using var coordinator = new NSFileCoordinator();
 
			var tcs = new TaskCompletionSource<FileResult[]>();
 
			coordinator.CoordinateAccess(intents, new NSOperationQueue(), error =>
			{
				if (error != null)
				{
					tcs.TrySetException(new NSErrorException(error));
					return;
				}
 
				var bookmarks = new List<FileResult>();
 
				foreach (var intent in intents)
				{
					var url = intent.Url;
					var result = new BookmarkDataFileResult(url);
					bookmarks.Add(result);
				}
 
				tcs.TrySetResult(bookmarks.ToArray());
			});
 
			return await tcs.Task;
		}
	}
 
	class BookmarkDataFileResult : FileResult
	{
		NSData bookmark;
 
		internal BookmarkDataFileResult(NSUrl url)
			: base()
		{
			try
			{
				url.StartAccessingSecurityScopedResource();
 
				var newBookmark = url.CreateBookmarkData(0, Array.Empty<string>(), null, out var bookmarkError);
				if (bookmarkError != null)
					throw new NSErrorException(bookmarkError);
 
				UpdateBookmark(url, newBookmark);
			}
			finally
			{
				url.StopAccessingSecurityScopedResource();
			}
		}
 
		void UpdateBookmark(NSUrl url, NSData newBookmark)
		{
			bookmark = newBookmark;
 
			var doc = new UIDocument(url);
			FullPath = doc.FileUrl?.Path;
			FileName = doc.LocalizedName ?? Path.GetFileName(FullPath);
		}
 
		internal override Task<Stream> PlatformOpenReadAsync()
		{
			var url = NSUrl.FromBookmarkData(bookmark, 0, null, out var isStale, out var error);
 
			if (error != null)
				throw new NSErrorException(error);
 
			url.StartAccessingSecurityScopedResource();
 
			if (isStale)
			{
				var newBookmark = url.CreateBookmarkData(NSUrlBookmarkCreationOptions.SuitableForBookmarkFile, Array.Empty<string>(), null, out error);
				if (error != null)
					throw new NSErrorException(error);
 
				UpdateBookmark(url, newBookmark);
			}
 
			var fileStream = File.OpenRead(FullPath);
			Stream stream = new SecurityScopedStream(fileStream, url);
			return Task.FromResult(stream);
		}
 
		class SecurityScopedStream : Stream
		{
			FileStream stream;
			NSUrl url;
 
			internal SecurityScopedStream(FileStream stream, NSUrl url)
			{
				this.stream = stream;
				this.url = url;
			}
 
			public override bool CanRead => stream.CanRead;
 
			public override bool CanSeek => stream.CanSeek;
 
			public override bool CanWrite => stream.CanWrite;
 
			public override long Length => stream.Length;
 
			public override long Position
			{
				get => stream.Position;
				set => stream.Position = value;
			}
 
			public override void Flush() =>
				stream.Flush();
 
			public override int Read(byte[] buffer, int offset, int count) =>
				stream.Read(buffer, offset, count);
 
			public override long Seek(long offset, SeekOrigin origin) =>
				stream.Seek(offset, origin);
 
			public override void SetLength(long value) =>
				stream.SetLength(value);
 
			public override void Write(byte[] buffer, int offset, int count) =>
				stream.Write(buffer, offset, count);
 
			protected override void Dispose(bool disposing)
			{
				base.Dispose(disposing);
 
				if (disposing)
				{
					stream?.Dispose();
					stream = null;
 
					url?.StopAccessingSecurityScopedResource();
					url = null;
				}
			}
		}
	}
 
	class UIDocumentFileResult : FileResult
	{
		internal UIDocumentFileResult(NSUrl url)
			: base()
		{
			var doc = new UIDocument(url);
			FullPath = doc.FileUrl?.Path;
			FileName = doc.LocalizedName ?? Path.GetFileName(FullPath);
		}
 
		internal override Task<Stream> PlatformOpenReadAsync()
		{
			Stream fileStream = File.OpenRead(FullPath);
 
			return Task.FromResult(fileStream);
		}
	}
 
	class UIImageFileResult : FileResult
	{
		readonly UIImage uiImage;
		NSData data;
 
		internal UIImageFileResult(UIImage image)
			: base()
		{
			uiImage = image;
 
			FullPath = Guid.NewGuid().ToString() + FileExtensions.Png;
			FileName = FullPath;
		}
 
		internal override Task<Stream> PlatformOpenReadAsync()
		{
			data ??= uiImage.NormalizeOrientation().AsPNG();
 
			return Task.FromResult(data.AsStream());
		}
	}
 
	class PHAssetFileResult : FileResult
	{
		readonly PHAsset phAsset;
 
		internal PHAssetFileResult(NSUrl url, PHAsset asset, string originalFilename)
			: base()
		{
			phAsset = asset;
 
			FullPath = url?.AbsoluteString;
			FileName = originalFilename;
		}
 
		[System.Runtime.Versioning.UnsupportedOSPlatform("ios13.0")]
		internal override Task<Stream> PlatformOpenReadAsync()
		{
			var tcsStream = new TaskCompletionSource<Stream>();
 
			PHImageManager.DefaultManager.RequestImageData(phAsset, null, new PHImageDataHandler((data, str, orientation, dict) =>
				tcsStream.TrySetResult(data.AsStream())));
 
			return tcsStream.Task;
		}
	}
}