diff --git a/src/Uno.UWP/Storage/Pickers/FileOpenPicker.iOS.cs b/src/Uno.UWP/Storage/Pickers/FileOpenPicker.iOS.cs index a786d37b018c..fac719e9d94e 100644 --- a/src/Uno.UWP/Storage/Pickers/FileOpenPicker.iOS.cs +++ b/src/Uno.UWP/Storage/Pickers/FileOpenPicker.iOS.cs @@ -79,7 +79,7 @@ private UIViewController GetViewController(bool multiple, int limit, TaskComplet }; return new PHPickerViewController(imageConfiguration) { - Delegate = new PhotoPickerDelegate(completionSource) + Delegate = new PhotoPickerDelegate(completionSource, _isReadOnly) }; case PickerLocationId.VideosLibrary when multiple is true && iOS14AndAbove is true: var videoConfiguration = new PHPickerConfiguration(PHPhotoLibrary.SharedPhotoLibrary) @@ -89,7 +89,7 @@ private UIViewController GetViewController(bool multiple, int limit, TaskComplet }; return new PHPickerViewController(videoConfiguration) { - Delegate = new PhotoPickerDelegate(completionSource) + Delegate = new PhotoPickerDelegate(completionSource, _isReadOnly) }; default: @@ -213,7 +213,7 @@ private async Task> ConvertPickerResults(PHPickerResult foreach (NSItemProvider provider in providers) { - var identifier = GetIdentifier(provider.RegisteredTypeIdentifiers ?? []) ?? "public.data"; + var identifier = StorageFile.GetUTIdentifier(provider.RegisteredTypeIdentifiers ?? []) ?? "public.data"; var data = await provider.LoadDataRepresentationAsync(identifier); if (data is null) @@ -221,7 +221,7 @@ private async Task> ConvertPickerResults(PHPickerResult continue; } - var extension = GetExtension(identifier); + var extension = StorageFile.GetUTFileExtension(identifier); var destinationUrl = NSFileManager.DefaultManager .GetTemporaryDirectory() @@ -233,30 +233,6 @@ private async Task> ConvertPickerResults(PHPickerResult } return storageFiles; } - - private static string? GetIdentifier(string[] identifiers) - { - if (!(identifiers?.Length > 0)) - { - return null; - } - - if (identifiers.Any(i => i.StartsWith(UTType.LivePhoto, StringComparison.InvariantCultureIgnoreCase)) && identifiers.Contains(UTType.JPEG)) - { - return identifiers.FirstOrDefault(i => i == UTType.JPEG); - } - - if (identifiers.Contains(UTType.QuickTimeMovie)) - { - return identifiers.FirstOrDefault(i => i == UTType.QuickTimeMovie); - } - - return identifiers.FirstOrDefault(); - } - - private string? GetExtension(string identifier) - => UTType.CopyAllTags(identifier, UTType.TagClassFilenameExtension)?.FirstOrDefault(); - } private class FileOpenPickerDelegate : UIDocumentPickerDelegate diff --git a/src/Uno.UWP/Storage/StorageFile.iOS.cs b/src/Uno.UWP/Storage/StorageFile.iOS.cs index 636e2aeb48ec..5417fb4baaba 100644 --- a/src/Uno.UWP/Storage/StorageFile.iOS.cs +++ b/src/Uno.UWP/Storage/StorageFile.iOS.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Foundation; +using MobileCoreServices; using Photos; using PhotosUI; using UIKit; @@ -23,7 +24,7 @@ internal static StorageFile GetFromSecurityScopedUrl(NSUrl nsUrl, StorageFolder? new StorageFile(new SecurityScopedFile(nsUrl, parent)); internal static StorageFile GetFromPHPickerResult(PHPickerResult result, PHAsset phAsset, StorageFolder? parent) => - new StorageFile(new PHPickerResultFile(result, phAsset, parent)); + new StorageFile(new PHAssetFile(phAsset, result, parent)); internal class SecurityScopedFile : ImplementationBase { @@ -103,13 +104,19 @@ implementation is SecurityScopedFile file && internal class PHAssetFile : ImplementationBase { private readonly PHAsset _phAsset; + private readonly PHPickerResult _pickerResult; private StorageFolder? _parent; - public PHAssetFile(PHAsset phAsset, StorageFolder? parent) : base(string.Empty) + private NSUrl? _fileUrl; + + public PHAssetFile(PHAsset phAsset, PHPickerResult pickerResult, StorageFolder? parent) : base(string.Empty) { _phAsset = phAsset; + _pickerResult = pickerResult; _parent = parent; - Path = phAsset. + + var resources = PHAssetResource.GetAssetResources(_phAsset); + Path = ((PHAssetResource)resources[0]).OriginalFilename; } public override StorageProvider Provider => StorageProviders.IosPHPicker; @@ -150,14 +157,81 @@ public override Task GetBasicPropertiesAsync(CancellationToken public override Task GetParentAsync(CancellationToken ct) => Task.FromResult(_parent); - public override Task OpenAsync(CancellationToken ct, FileAccessMode accessMode, StorageOpenOptions options) - => Task.FromResult(new RandomAccessStreamWithContentType(FileRandomAccessStream.CreateSecurityScoped(_phAsset.Rea, ToFileAccess(accessMode), ToFileShare(options)), ContentType)); - public override Task OpenTransactedWriteAsync(CancellationToken ct, StorageOpenOptions option) + public override async Task OpenAsync(CancellationToken ct, FileAccessMode accessMode, StorageOpenOptions options) + { + await EnsureLoadedAsync(); + + if (_fileUrl is null) + { + throw new InvalidOperationException("The file could not be loaded."); + } + + return new RandomAccessStreamWithContentType(FileRandomAccessStream.CreateSecurityScoped(_fileUrl, ToFileAccess(accessMode), ToFileShare(options)), ContentType); + } + + public override async Task OpenStreamAsync(CancellationToken ct, FileAccessMode accessMode, StorageOpenOptions options) { + await EnsureLoadedAsync(); + if (_fileUrl is null) + { + throw new InvalidOperationException("The file could not be loaded."); + } + + Func streamBuilder = () => File.Open(Path, FileMode.Open, ToFileAccess(accessMode), ToFileShare(options)); + var streamWrapper = new SecurityScopeStreamWrapper(_fileUrl, streamBuilder); + return streamWrapper; } + public override Task OpenTransactedWriteAsync(CancellationToken ct, StorageOpenOptions option) => throw new NotSupportedException(); + protected override bool IsEqual(ImplementationBase implementation) => implementation is PHAssetFile file && file._phAsset.LocalIdentifier == _phAsset.LocalIdentifier; + + private async Task EnsureLoadedAsync() + { + if (_fileUrl is not null) + { + return; + } + + var identifier = GetUTIdentifier(_pickerResult.ItemProvider.RegisteredTypeIdentifiers ?? []) ?? "public.data"; + var extension = GetUTFileExtension(identifier); + var result = await _pickerResult.ItemProvider.LoadFileRepresentationAsync(identifier); + if (result != null) + { + var destinationUrl = NSFileManager.DefaultManager + .GetTemporaryDirectory() + .Append($"{NSProcessInfo.ProcessInfo.GloballyUniqueString}.{extension}", false); + + using Stream source = File.Open(result.Path!, FileMode.Open); + using Stream destination = File.Create(destinationUrl.Path!); + await source.CopyToAsync(destination); + + _fileUrl = destinationUrl; + } + } } + + internal static string? GetUTIdentifier(string[] identifiers) + { + if (!(identifiers?.Length > 0)) + { + return null; + } + + if (identifiers.Any(i => i.StartsWith(UTType.LivePhoto, StringComparison.InvariantCultureIgnoreCase)) && identifiers.Contains(UTType.JPEG)) + { + return identifiers.FirstOrDefault(i => i == UTType.JPEG); + } + + if (identifiers.Contains(UTType.QuickTimeMovie)) + { + return identifiers.FirstOrDefault(i => i == UTType.QuickTimeMovie); + } + + return identifiers.FirstOrDefault(); + } + + internal static string? GetUTFileExtension(string identifier) => UTType.CopyAllTags(identifier, UTType.TagClassFilenameExtension)?.FirstOrDefault(); } }