Skip to content

Commit

Permalink
feat(storage): [Wasm] Add support for GetFileFromApplicationUriAsync
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Sep 22, 2020
1 parent 611dc70 commit d3bc03a
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<SomeContent/>
1 change: 1 addition & 0 deletions src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -6073,6 +6073,7 @@
<Content Include="$(MSBuildThisFileDirectory)Assets\MenuFlyout.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\theme-dark\ThemeTestImage.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\theme-light\ThemeTestImage.png" />
<Content Include="$(MSBuildThisFileDirectory)Asset_GetFileFromApplicationUriAsync.xml" />
<EmbeddedResource Include="$(MSBuildThisFileDirectory)EmbeddedResources\LockScreen.png" />
<EmbeddedResource Include="$(MSBuildThisFileDirectory)EmbeddedResources\Wallpaper.png" />
<Content Include="$(MSBuildThisFileDirectory)Lottie\LightBulb.json" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Storage;
using Windows.UI.Xaml.Input;

namespace Uno.UI.RuntimeTests.Tests.Windows_Storage
{
[TestClass]
public class Given_ApplicationStorage
{
[TestMethod]
public async Task When_GetFileFromApplicationUriAsync_RootPath()
{
var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Asset_GetFileFromApplicationUriAsync.xml"));

var content = await FileIO.ReadTextAsync(file);

Assert.AreEqual("<SomeContent/>", content);
}
}
}
6 changes: 6 additions & 0 deletions src/Uno.UI/WasmScripts/Uno.UI.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,12 @@ declare namespace Windows.Storage {
private static getValueByIndex;
}
}
declare namespace Windows.Storage {
class AssetManager {
static DownloadAssetsManifest(path: string): Promise<string>;
static DownloadAsset(path: string): Promise<string>;
}
}
declare namespace Windows.Storage.Pickers {
class FileSavePicker {
static SaveAs(fileName: string, dataPtr: any, size: number): void;
Expand Down
27 changes: 27 additions & 0 deletions src/Uno.UI/WasmScripts/Uno.UI.js
Original file line number Diff line number Diff line change
Expand Up @@ -3073,6 +3073,33 @@ var Windows;
Storage.ApplicationDataContainer = ApplicationDataContainer;
})(Storage = Windows.Storage || (Windows.Storage = {}));
})(Windows || (Windows = {}));
// eslint-disable-next-line @typescript-eslint/no-namespace
var Windows;
(function (Windows) {
var Storage;
(function (Storage) {
class AssetManager {
static DownloadAssetsManifest(path) {
return __awaiter(this, void 0, void 0, function* () {
var response = yield fetch(path);
return response.text();
});
}
static DownloadAsset(path) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield fetch(path);
const arrayBuffer = yield response.blob().then(b => b.arrayBuffer());
const size = arrayBuffer.byteLength;
const responseArray = new Uint8ClampedArray(arrayBuffer);
const pData = Module._malloc(size);
Module.HEAPU8.set(responseArray, pData);
return `${pData};${size}`;
});
}
}
Storage.AssetManager = AssetManager;
})(Storage = Windows.Storage || (Windows.Storage = {}));
})(Windows || (Windows = {}));
var Windows;
(function (Windows) {
var Storage;
Expand Down
24 changes: 24 additions & 0 deletions src/Uno.UI/ts/Windows/Storage/AssetManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Windows.Storage {

export class AssetManager {
public static async DownloadAssetsManifest(path: string): Promise<string> {
var response = await fetch(path);
return response.text();
}

public static async DownloadAsset(path: string): Promise<string> {
const response = await fetch(path);
const arrayBuffer = await response.blob().then(b => <ArrayBuffer>(<any>b).arrayBuffer());
const size = arrayBuffer.byteLength;
const responseArray = new Uint8ClampedArray(arrayBuffer);

const pData = Module._malloc(size);

Module.HEAPU8.set(responseArray, pData);

return `${pData};${size}`;
}
}
}
8 changes: 0 additions & 8 deletions src/Uno.UWP/Generated/3.0.0.0/Windows.Storage/StorageFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,6 @@ public bool IsEqual( global::Windows.Storage.IStorageItem item)
throw new global::System.NotImplementedException("The member IAsyncOperation<StorageFile> StorageFile.GetFileFromPathForUserAsync(User user, string path) is not implemented in Uno.");
}
#endif
// Skipping already declared method Windows.Storage.StorageFile.GetFileFromPathAsync(string)
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static global::Windows.Foundation.IAsyncOperation<global::Windows.Storage.StorageFile> GetFileFromApplicationUriAsync( global::System.Uri uri)
{
throw new global::System.NotImplementedException("The member IAsyncOperation<StorageFile> StorageFile.GetFileFromApplicationUriAsync(Uri uri) is not implemented in Uno.");
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static global::Windows.Foundation.IAsyncOperation<global::Windows.Storage.StorageFile> CreateStreamedFileAsync( string displayNameWithExtension, global::Windows.Storage.StreamedFileDataRequestedHandler dataRequested, global::Windows.Storage.Streams.IRandomAccessStreamReference thumbnail)
Expand Down
133 changes: 133 additions & 0 deletions src/Uno.UWP/Storage/AssetsManager.wasm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Uno.Disposables;
using Uno.Extensions;
using Uno.Foundation;
using Uno.Threading;
using Windows.Devices.AllJoyn;
using Windows.Foundation;
using Windows.Media.Streaming.Adaptive;
using Windows.Security.Cryptography.Core;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
using Windows.UI.WebUI;

namespace Windows.Storage
{
internal partial class AssetsManager
{
private static readonly string UNO_BOOTSTRAP_APP_BASE = Environment.GetEnvironmentVariable(nameof(UNO_BOOTSTRAP_APP_BASE));

private static readonly Lazy<Task<HashSet<string>>> _assets = new Lazy<Task<HashSet<string>>>(() => GetAssets(CancellationToken.None));
private static readonly Dictionary<string, DownloadEntry> _assetsGate = new Dictionary<string, DownloadEntry>();

private static async Task<HashSet<string>> GetAssets(CancellationToken ct)
{
var assetsUri = !string.IsNullOrEmpty(UNO_BOOTSTRAP_APP_BASE) ? $"{UNO_BOOTSTRAP_APP_BASE}/uno-assets.txt" : "uno-assets.txt";

var assets = await WebAssemblyRuntime.InvokeAsync($"Windows.Storage.AssetManager.DownloadAssetsManifest(\'{assetsUri}\')");

return new HashSet<string>(Regex.Split(assets, "\r\n|\r|\n"), StringComparer.OrdinalIgnoreCase);
}

public static async Task<string> DownloadAsset(CancellationToken ct, string assetPath)
{
var updatedPath = assetPath.TrimStart("/");
var assetSet = await _assets.Value;

if (assetSet.Contains(updatedPath))
{
var localPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, ".assetsCache", UNO_BOOTSTRAP_APP_BASE, updatedPath);

using var assetLock = await LockForAsset(ct, updatedPath);

if (!File.Exists(localPath))
{
var assetUri = !string.IsNullOrEmpty(UNO_BOOTSTRAP_APP_BASE) ? $"{UNO_BOOTSTRAP_APP_BASE}/{updatedPath}" : updatedPath;
var assetInfo = await WebAssemblyRuntime.InvokeAsync($"Windows.Storage.AssetManager.DownloadAsset(\'{assetUri}\')");

var parts = assetInfo.Split(';');
if (parts.Length == 2)
{
var ptr = (IntPtr)int.Parse(parts[0], CultureInfo.InvariantCulture);
var length = int.Parse(parts[1], CultureInfo.InvariantCulture);

try
{
var buffer = new byte[length];
Marshal.Copy(ptr, buffer, 0, length);

Directory.CreateDirectory(Path.GetDirectoryName(localPath));
File.WriteAllBytes(localPath, buffer);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
else
{
throw new InvalidOperationException($"Invalid Windows.Storage.AssetManager.DownloadAsset return value");
}
}

return localPath;
}
else
{
throw new FileNotFoundException($"The file [{assetPath}] cannot be found");
}
}

private static async Task<IDisposable> LockForAsset(CancellationToken ct, string updatedPath)
{
DownloadEntry GetEntry()
{
lock (_assetsGate)
{
if (!_assetsGate.TryGetValue(updatedPath, out var entry))
{
_assetsGate[updatedPath] = entry = new DownloadEntry();
}

entry.ReferenceCount++;

return entry;
}
}

var entry = GetEntry();

var disposable = await entry.Gate.LockAsync(ct);

void ReleaseEntry()
{
lock (_assetsGate)
{
disposable.Dispose();

if(--entry.ReferenceCount == 0)
{
_assetsGate.Remove(updatedPath);
}
}
}

return Disposable.Create(ReleaseEntry);
}

private class DownloadEntry
{
public int ReferenceCount;
public AsyncLock Gate { get; } = new AsyncLock();
}
}
}
13 changes: 13 additions & 0 deletions src/Uno.UWP/Storage/StorageFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,18 @@ public async Task<BasicProperties> GetBasicPropertiesAsync(CancellationToken ct)
{
return new BasicProperties(this);
}

[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static global::Windows.Foundation.IAsyncOperation<global::Windows.Storage.StorageFile> GetFileFromApplicationUriAsync( global::System.Uri uri)
{
return AsyncOperation.FromTask(ct => GetFileFromApplicationUriAsyncTask(ct, uri));
}

#if __ANDROID__ || __IOS__ || NET461 || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
private static Task<StorageFile> GetFileFromApplicationUriAsyncTask(CancellationToken ct, Uri uri)
{
throw new NotImplementedException();
}
#endif
}
}
29 changes: 29 additions & 0 deletions src/Uno.UWP/Storage/StorageFile.wasm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously

using System;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Uno.Extensions;
using Windows.Foundation;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;

namespace Windows.Storage
{
public partial class StorageFile : StorageItem, IStorageFile
{
private static async Task<StorageFile> GetFileFromApplicationUriAsyncTask(CancellationToken ct, Uri uri)
{
if(uri.Scheme != "ms-appx")
{
throw new InvalidOperationException("Uri is not using the ms-appx scheme");
}

var path = uri.PathAndQuery;

return await StorageFile.GetFileFromPathAsync(await AssetsManager.DownloadAsset(ct, path));
}
}
}

0 comments on commit d3bc03a

Please sign in to comment.