using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Avalonia.Logging; using Avalonia.Platform.Storage; using Foundation; using UIKit; #nullable enable namespace Avalonia.iOS.Storage; internal abstract class IOSStorageItem : IStorageBookmarkItem { private readonly string _filePath; protected IOSStorageItem(NSUrl url) { Url = url ?? throw new ArgumentNullException(nameof(url)); using (var doc = new UIDocument(url)) { _filePath = doc.FileUrl?.Path ?? url.FilePathUrl?.Path ?? string.Empty; Name = doc.LocalizedName ?? System.IO.Path.GetFileName(_filePath) ?? url.FilePathUrl?.LastPathComponent ?? string.Empty; } } internal NSUrl Url { get; } public bool CanBookmark => true; public string Name { get; } public Uri Path => Url!; public Task GetBasicPropertiesAsync() { var attributes = NSFileManager.DefaultManager.GetAttributes(_filePath, out var error); if (error is not null) { Logger.TryGet(LogEventLevel.Error, LogArea.IOSPlatform)?. Log(this, "GetBasicPropertiesAsync returned an error: {ErrorCode} {ErrorMessage}", error.Code, error.LocalizedFailureReason); } return Task.FromResult(new StorageItemProperties(attributes?.Size, (DateTime)attributes?.CreationDate, (DateTime)attributes?.ModificationDate)); } public Task GetParentAsync() { return Task.FromResult(new IOSStorageFolder(Url.RemoveLastPathComponent())); } public Task ReleaseBookmarkAsync() { // no-op return Task.CompletedTask; } public Task SaveBookmarkAsync() { try { if (!Url.StartAccessingSecurityScopedResource()) { return Task.FromResult(null); } var newBookmark = Url.CreateBookmarkData(NSUrlBookmarkCreationOptions.SuitableForBookmarkFile, Array.Empty(), null, out var bookmarkError); if (bookmarkError is not null) { Logger.TryGet(LogEventLevel.Error, LogArea.IOSPlatform)?. Log(this, "SaveBookmark returned an error: {ErrorCode} {ErrorMessage}", bookmarkError.Code, bookmarkError.LocalizedFailureReason); return Task.FromResult(null); } return Task.FromResult( newBookmark.GetBase64EncodedString(NSDataBase64EncodingOptions.None)); } finally { Url.StopAccessingSecurityScopedResource(); } } public void Dispose() { } } internal sealed class IOSStorageFile : IOSStorageItem, IStorageBookmarkFile { public IOSStorageFile(NSUrl url) : base(url) { } public Task OpenReadAsync() { return Task.FromResult(new IOSSecurityScopedStream(Url, FileAccess.Read)); } public Task OpenWriteAsync() { return Task.FromResult(new IOSSecurityScopedStream(Url, FileAccess.Write)); } } internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder { public IOSStorageFolder(NSUrl url) : base(url) { } public async Task> GetItemsAsync() { var tcs = new TaskCompletionSource>(); new NSFileCoordinator().CoordinateRead(Url, NSFileCoordinatorReadingOptions.WithoutChanges, out var error, uri => { var content = NSFileManager.DefaultManager.GetDirectoryContent(uri, null, NSDirectoryEnumerationOptions.None, out var error); if (error is not null) { tcs.TrySetException(new NSErrorException(error)); } else { var items = content .Select(u => u.HasDirectoryPath ? (IStorageItem)new IOSStorageFolder(u) : new IOSStorageFile(u)) .ToArray(); tcs.TrySetResult(items); } }); if (error is not null) { throw new NSErrorException(error); } return await tcs.Task; } }