Skip to content

Commit

Permalink
iOS storage provider implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkatz6 committed Jun 8, 2022
1 parent 3d44e7d commit 355e57e
Show file tree
Hide file tree
Showing 5 changed files with 410 additions and 2 deletions.
5 changes: 5 additions & 0 deletions src/Avalonia.Base/Logging/LogArea.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,10 @@ public static class LogArea
/// The log event comes from X11Platform.
/// </summary>
public const string X11Platform = nameof(X11Platform);

/// <summary>
/// The log event comes from IOSPlatform.
/// </summary>
public const string IOSPlatform = nameof(IOSPlatform);
}
}
9 changes: 7 additions & 2 deletions src/iOS/Avalonia.iOS/AvaloniaView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.iOS.Storage;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using CoreAnimation;
using Foundation;
Expand Down Expand Up @@ -41,9 +43,10 @@ public AvaloniaView()
);
_topLevelImpl.Surfaces = new[] {new EaglLayerSurface(l)};
MultipleTouchEnabled = true;
AddSubviews(new UIView[] { new UIKit.UIButton(UIButtonType.InfoDark) });
}

internal class TopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost
internal class TopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
{
private readonly AvaloniaView _view;
public AvaloniaView View => _view;
Expand All @@ -52,6 +55,7 @@ public TopLevelImpl(AvaloniaView view)
{
_view = view;
NativeControlHost = new NativeControlHostImpl(_view);
StorageProvider = new IOSStorageProvider(view.Window.RootViewController);
}

public void Dispose()
Expand Down Expand Up @@ -113,7 +117,8 @@ public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
new AcrylicPlatformCompensationLevels();

public ITextInputMethodImpl? TextInputMethod => _view;
public INativeControlHostImpl NativeControlHost { get; }
public INativeControlHostImpl NativeControlHost { get; }
public IStorageProvider StorageProvider { get; }
}

[Export("layerClass")]
Expand Down
68 changes: 68 additions & 0 deletions src/iOS/Avalonia.iOS/Storage/IOSSecurityScopedStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.IO;

using Foundation;

using UIKit;

#nullable enable

namespace Avalonia.iOS.Storage
{
internal class IOSSecurityScopedStream : Stream
{
private readonly UIDocument _document;
private readonly FileStream _stream;
private readonly NSUrl _url;

internal IOSSecurityScopedStream(NSUrl url, FileAccess access)
{
_document = new UIDocument(url);
var path = _document.FileUrl.Path;
_stream = File.Open(path, FileMode.Open, access);
_url = url;
_url.StartAccessingSecurityScopedResource();
}

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();
_document.Dispose();
_url.StopAccessingSecurityScopedResource();
}
}
}

}
110 changes: 110 additions & 0 deletions src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Logging;
using Avalonia.Platform.Storage;
using Foundation;

using UIKit;

#nullable enable

namespace Avalonia.iOS.Storage
{
internal class IOSStorageItem : IStorageBookmarkItem
{
private readonly NSUrl _url;
private readonly string _filePath;

public IOSStorageItem(NSUrl url)
{
_url = url;

using (var doc = new UIDocument(url))
{
_filePath = doc.FileUrl?.Path ?? url.FilePathUrl.Path;
Name = doc.LocalizedName ?? Path.GetFileName(_filePath) ?? url.FilePathUrl.LastPathComponent;
}
}

internal NSUrl Url => _url;

public bool CanBookmark => true;

public string Name { get; }

public Task<StorageItemProperties> 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<IStorageFolder?> GetParentAsync()
{
return Task.FromResult<IStorageFolder?>(new IOSStorageFolder(_url.RemoveLastPathComponent()));
}

public Task<string?> SaveBookmark()
{
try
{
_url.StartAccessingSecurityScopedResource();

var newBookmark = _url.CreateBookmarkData(0, Array.Empty<string>(), 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<string?>(null);
}

return Task.FromResult<string?>(
newBookmark.GetBase64EncodedString(NSDataBase64EncodingOptions.None));
}
finally
{
_url.StopAccessingSecurityScopedResource();
}
}

public bool TryGetFullPath([NotNullWhen(true)] out string path)
{
path = _filePath;
return true;
}
}

internal class IOSStorageFile : IOSStorageItem, IStorageBookmarkFile
{
public IOSStorageFile(NSUrl url) : base(url)
{
}

public bool CanOpenRead => true;

public bool CanOpenWrite => true;

public Task<Stream> OpenRead()
{
return Task.FromResult<Stream>(new IOSSecurityScopedStream(Url, FileAccess.Read));
}

public Task<Stream> OpenWrite()
{
return Task.FromResult<Stream>(new IOSSecurityScopedStream(Url, FileAccess.Write));
}
}

internal class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
{
public IOSStorageFolder(NSUrl url) : base(url)
{
}
}
}
Loading

0 comments on commit 355e57e

Please sign in to comment.