Skip to content

Commit

Permalink
Merge pull request #12596 from MartinZikmund/dev/mazi/packageid-skia
Browse files Browse the repository at this point in the history
feat: App-specific `ApplicationData` on Skia
  • Loading branch information
MartinZikmund authored Jul 27, 2023
2 parents 33b35eb + 389c5df commit 4cad602
Show file tree
Hide file tree
Showing 33 changed files with 813 additions and 439 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions doc/articles/feature-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,7 @@ Uno.UI.FeatureConfiguration.ToolTip.UseToolTips = true;
```

It is also possible to adjust the delay in milliseconds (`Uno.UI.FeatureConfiguration.ToolTip.ShowDelay` - defaults to `1000`) and show duration in milliseconds (`Uno.UI.FeatureConfiguration.ToolTip.ShowDuration` - defaults to `5000`). This configuration only applies to Uno Platform targets. Windows App SDK/UWP will not adhere to this configuration.

## `ApplicationData`

On GTK and WPF it is possible to override the default `ApplicationData` folder locations using `WinRTFeatureConfiguration.ApplicationData` properties. For more information see [related docs here](/articles/features/applicationdata.md#data-location-on-gtk-and-wpf)
96 changes: 96 additions & 0 deletions doc/articles/features/applicationdata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
uid: Uno.Features.ApplicationData
---

# Application Data and Settings

![Application Data and Preferences](../Assets/features/applicationdata/appdata.jpeg)

To store persistent application data and user settings, you can utilize the `Windows.Storage.ApplicationData` class in Uno Platform.

Legend
- ✔️ Supported

| Picker | WinUI/UWP | WebAssembly | Android | iOS/Mac Catalyst | macOS | WPF | GTK |
|----------------|-------|-------------|---------|-------|-------|-----|-----|
| `LocalFolder` | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| `RoamingFolder` | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| `LocalCacheFolder` | ✔️ | ✔️ | ✔️ | ✔️| ✔️ | ✔️ | ✔️ |
| `TemporaryFolder` | ✔️ | ✔️ | ✔️ | ✔️| ✔️ | ✔️ | ✔️ |
| `LocalSettings` | ✔️ | ✔️ | ✔️ | ✔️| ✔️ | ✔️ | ✔️ |
| `RoamingSettings` | ✔️ | ✔️ | ✔️ | ✔️| ✔️ | ✔️ | ✔️ |

Please note that `RoamingFolder` and `RoamingSettings` are not roamed automatically across devices, they only provide a logical separation between data that you intend to roam and that you intend to keep local.

## Storing application data

There are several folders where persistent application data can be stored:

- `LocalFolder/RoamingFolder` - general-use application files
- `TemporaryFolder` - files with limited lifetime
- `LocalCacheFolder` - cached files retrieved from external services

In the case of `TemporaryFolder` and `LocalCacheFolder` it is crucial to remember that the user or operating system may purge files stored in these locations to reclaim storage space. To store persistent files prefer the `LocalFolder` or `RoamingFolder`.

The following example shows how you can create a file in `LocalFolder` and then read the contents back:

```csharp
StorageFolder folder = ApplicationData.Current.LocalFolder;

// Create a file in the root of LocalFolder.
StorageFile file = await folder.CreateFileAsync("file.txt", CreationCollisionOption.ReplaceExisting);

// Write text in the newly created file.
await FileIO.WriteTextAsync(file, "Hello, Uno Platform!");

// Read the text from file.
string text = await FileIO.ReadTextAsync(file);
```

## Storing settings

The `LocalSettings` and `RoamingSettings` properties provide access to simple key-value containers that allow storage of lightweight user and application preferences. The values stored in settings should be simple serializable types. To store more complex data structures, it is preferred to serialize them first into a string (for example using a JSON serializer).

``` csharp
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;

// Save a setting.
localSettings.Values["name"] = "Martin";

// Read a setting.
string value = (string)localSettings.Values["name"];
```

## Data location on GTK and WPF

In case of GTK and WPF targets the data are stored in application- and user-specific locations on the hard drive. The default path to the various folders depends on the runtime operating system:

**Windows**

- `LocalFolder` - `C:\Users\UserName>\AppData\Local\<Publisher>\<ApplicationName>\LocalState`
- `RoamingFolder` - `C:\Users\<UserName>\AppData\Local\<Publisher>\<ApplicationName>\RoamingState`
- `LocalCaheFolder` - `C:\Users\<UserName>\AppData\Local\<Publisher>\<ApplicationName>\LocalCache`
- `TemporaryFolder` - `C:\Users\<UserName>\AppData\Local\Temp\<Publisher>\<ApplicationName>\TempState`
- `LocalSettings` - `C:\Users\<UserName>\AppData\Local\<Publisher>\<ApplicationName>\Settings\Local.dat`
- `RoamingSettings` - `C:\Users\<UserName>\AppData\Local\<Publisher>\<ApplicationName>\Settings\Roaming.dat`

**Unix-based systems**

- `LocalFolder` - `/home/<UserName>/.local/share/<Publisher>/<ApplicationName>/LocalState`
- `RoamingFolder` - `/home/<UserName>/.local/share/<Publisher>/<ApplicationName>/RoamingState`
- `TemporaryFolder` - `/tmp/<Publisher>/<ApplicationName>/TempState`
- `LocalCache` - `/home/<UserName>/.cache/<Publisher>/<ApplicationName>/LocalCache`
- `LocalSettings` - `/home/<UserName>/.local/share/<Publisher>/<ApplicationName>/Settings/Local.dat`
- `RoamingSettings` - `/home/<UserName>/.local/share/<Publisher>/<ApplicationName>/Settings/Roaming.dat`

Where `<UserName>` is the name of the currently logged-in user and `<Publisher>` and `<ApplicationName>` are values coming from the `<Identity>` node of the `Package.appxmanifest` (note that the publisher value is prefixed by `CN=` in the manifest, but this is excluded from the folder name).

The default paths above can be overridden using the following feature flags:

- `WinRTFeatureConfiguration.ApplicationData.TemporaryFolderPathOverride` - affects `TemporaryFolder` location
- `WinRTFeatureConfiguration.ApplicationData.LocalCacheFolderPathOverride` - affects `LocalCacheFolder` location
- `WinRTFeatureConfiguration.ApplicationData.ApplicationDataPathOverride` - affects `LocalFolder`, `RoamingFolder`, `LocalCaheFolder`, `LocalSettings` and `RoamingSettings`

These properties need to be set before the application is initialized. The best place for this is `Program.cs`, before the `WpfHost` or `GtkHost` instance is created.

If you intend to support both Windows and Unix-based systems for GTK target, make the path conditional utilizing `RuntimeInformation.IsOSPlatform(OSPlatform.Windows)`.
4 changes: 1 addition & 3 deletions doc/articles/features/file-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ uid: Uno.Features.FileManagement

## Supported features

| Feature | Windows | Android | iOS | Web (WASM) | macOS | Linux (Skia) | Win 7 (Skia) |
| Feature | WinUI/UWP | Android | iOS | Web (WASM) | macOS | Linux (Skia) | WPF (Skia) |
|---------------|-------|-------|-------|-------|-------|-------|-|
| `StorageFile` ||||||||
| `StorageFolder` ||||||||
| `ApplicationData.Current.LocalFolder` ||||||||
| `ApplicationData.Current.RoamingFolder` ||||||||
| `CachedFileManager` || partial | partial | partial | partial | partial | partial |
| `StorageFileHelper` ||||||||

Expand Down
3 changes: 3 additions & 0 deletions doc/articles/migrating-from-previous-releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Uno Platform 5.0 continues to supports both UWP and WinUI API sets.
#### Migrating from Xamarin to net7.0-* targets
If your current project is built on Xamarin.* targets, you can upgrade by [following this guide](xref:Uno.Development.MigratingFromXamarinToNet6).

#### Migrating `ApplicationData` on Skia targets
Previously, `ApplicationData` were stored directly in `Environment.SpecialFolder.LocalApplicationData` folder, and all Uno Platform apps shared this single location. Starting with Uno Platform 5.0, application data are stored in application specific folders under the `LocalApplicationData` root. For more details see the [docs](features/applicationdata.md). To perform the initial migration of existing data you need to make sure to copy the files from the root of the `LocalApplicationData` folder to `ApplicationData.Current.LocalFolder` manually using `System.IO`.

#### `ShouldWriteErrorOnInvalidXaml` now defaults to true.
Invalid XAML, such as unknown properties or unknown x:Bind targets will generate a compiler error. Those errors must now be fixed as they are no longer ignored.

Expand Down
2 changes: 2 additions & 0 deletions doc/articles/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@
href: features/app-close-handler.md
- name: App Suspension
href: features/windows-ui-xaml-application.md
- name: Application Data and Settings
href: features/applicationdata.md
- name: Badge Notifications
href: features/windows-ui-notifications.md
- name: Barometer
Expand Down
62 changes: 62 additions & 0 deletions src/SamplesApp/SamplesApp.Shared/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
using Uno.UI.Xaml.Controls.Extensions;
using Uno.Foundation.Extensibility;
using MUXControlsTestApp.Utilities;
using Windows.Storage;
#endif

#if !HAS_UNO
Expand Down Expand Up @@ -90,6 +91,7 @@ public App()
ConfigureFeatureFlags();

AssertIssue1790ApplicationSettingsUsable();
AssertApplicationData();

this.InitializeComponent();
this.Suspending += OnSuspending;
Expand Down Expand Up @@ -673,6 +675,66 @@ public void AssertIssue8641NativeOverlayInitialized()
var textBoxView = new TextBoxView(textBox);
ApiExtensibility.CreateInstance<IOverlayTextBoxViewExtension>(textBoxView, out var textBoxViewExtension);
Assert.IsTrue(textBoxViewExtension.IsOverlayLayerInitialized(rootFrame.XamlRoot));
#endif
}

/// <summary>
/// Verifies that ApplicationData are available immediately after the application class is created
/// and the data are stored in proper application specific lcoations.
/// </summary>
public void AssertApplicationData()
{
#if __SKIA__
var appName = Package.Current.Id.Name;
var publisher = string.IsNullOrEmpty(Package.Current.Id.Publisher) ? "" : "Uno Platform";

AssertForFolder(ApplicationData.Current.LocalFolder);
AssertForFolder(ApplicationData.Current.RoamingFolder);
AssertForFolder(ApplicationData.Current.TemporaryFolder);
AssertForFolder(ApplicationData.Current.LocalCacheFolder);
AssertSettings(ApplicationData.Current.LocalSettings);
AssertSettings(ApplicationData.Current.RoamingSettings);

void AssertForFolder(StorageFolder folder)
{
AssertContainsIdProps(folder);
AssertCanCreateFile(folder);
}

void AssertSettings(ApplicationDataContainer container)
{
var key = Guid.NewGuid().ToString();
var value = Guid.NewGuid().ToString();

container.Values[key] = value;
Assert.IsTrue(container.Values.ContainsKey(key));
Assert.AreEqual(value, container.Values[key]);
container.Values.Remove(key);
}

void AssertContainsIdProps(StorageFolder folder)
{
Assert.IsTrue(folder.Path.Contains(appName, StringComparison.Ordinal));
Assert.IsTrue(folder.Path.Contains(publisher, StringComparison.Ordinal));
}

void AssertCanCreateFile(StorageFolder folder)
{
var filename = Guid.NewGuid() + ".txt";
var path = Path.Combine(folder.Path, filename);
var expectedContent = "Test";
try
{
File.WriteAllText(path, expectedContent);
var actualContent = File.ReadAllText(path);

Assert.AreEqual(expectedContent, actualContent);
}
finally
{
File.Delete(path);
}
}
#endif
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/SamplesApp/SamplesApp.UWP/Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
IgnorableNamespaces="uap mp rescap uap2">

<Identity
Name="6c25beb3-e332-48d3-b4de-20a0b71935cd"
Publisher="CN=Uno"
Name="SamplesApp"
Publisher="CN=Uno Platform"
Version="1.0.0.0" />

<mp:PhoneIdentity PhoneProductId="1897f84c-689e-44d3-a8b7-c521a62818ae" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

<Properties>
<DisplayName>SamplesApp</DisplayName>
<PublisherDisplayName>SamplesApp</PublisherDisplayName>
<PublisherDisplayName>Uno Platform</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>

Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI.Runtime.Skia.Gtk/GtkHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public partial class GtkHost : ISkiaApplicationHost
{
private const int UnoThemePriority = 800;

private readonly Func<WUX.Application> _appBuilder;
private readonly Func<WinUIApplication> _appBuilder;

[ThreadStatic] private static bool _isDispatcherThread;
[ThreadStatic] private static GtkHost? _current;
Expand Down
5 changes: 2 additions & 3 deletions src/Uno.UI/UI/Xaml/Application.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.Globalization;
using Windows.UI.Xaml.Controls.Primitives;

#if HAS_UNO_WINUI
Expand All @@ -18,11 +19,9 @@ namespace Windows.UI.Xaml
{
public partial class Application
{
public Application()
partial void InitializePartial()
{
Window.Current.ToString();
Current = this;
InitializeSystemTheme();
PermissionsHelper.Initialize();
}

Expand Down
21 changes: 20 additions & 1 deletion src/Uno.UI/UI/Xaml/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@

namespace Windows.UI.Xaml
{
/// <summary>
/// Encapsulates the app and its available services.
/// </summary>
public partial class Application
{
private bool _initializationComplete;
Expand Down Expand Up @@ -77,6 +80,23 @@ static Application()
InitializePartialStatic();
}

/// <summary>
/// Initializes a new instance of the Application class.
/// </summary>
public Application()
{
#if __SKIA__ || __WASM__
Package.SetEntryAssembly(this.GetType().Assembly);
#endif
Current = this;
ApplicationLanguages.ApplyCulture();
InitializeSystemTheme();

InitializePartial();
}

partial void InitializePartial();

private static void RegisterExtensions()
{
ApiExtensibility.Register<MessageDialog>(typeof(IMessageDialogExtension), dialog => new MessageDialogExtension(dialog));
Expand Down Expand Up @@ -231,7 +251,6 @@ public void Exit()

public static void Start(global::Windows.UI.Xaml.ApplicationInitializationCallback callback)
{
ApplicationLanguages.ApplyCulture();
StartPartial(callback);
}

Expand Down
5 changes: 2 additions & 3 deletions src/Uno.UI/UI/Xaml/Application.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel;
using ObjCRuntime;
using Windows.Globalization;
using Windows.Graphics.Display;
using Uno.Extensions;
using Windows.UI.Core;
Expand All @@ -28,11 +29,9 @@ public partial class Application : UIApplicationDelegate

private bool _preventSecondaryActivationHandling;

public Application()
partial void InitializePartial()
{
Current = this;
SetCurrentLanguage();
InitializeSystemTheme();

SubscribeBackgroundNotifications();
}
Expand Down
5 changes: 2 additions & 3 deletions src/Uno.UI/UI/Xaml/Application.macOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using AppKit;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel;
using Windows.Globalization;
using System.Globalization;
using Uno.Foundation.Logging;
using System.Linq;
Expand Down Expand Up @@ -32,11 +33,9 @@ static partial void InitializePartialStatic()
ApiExtensibility.Register(typeof(IUnoCorePointerInputSource), host => new MacOSPointerInputSource((Uno.UI.Controls.Window)((Windows.UI.Xaml.Window)host).NativeWindow));
}

public Application()
partial void InitializePartial()
{
Current = this;
SetCurrentLanguage();
InitializeSystemTheme();

SubscribeBackgroundNotifications();
}
Expand Down
14 changes: 0 additions & 14 deletions src/Uno.UI/UI/Xaml/Application.reference.cs

This file was deleted.

7 changes: 2 additions & 5 deletions src/Uno.UI/UI/Xaml/Application.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Threading;
using System.Globalization;
using Windows.ApplicationModel.Core;
using Windows.Globalization;
using Uno.UI.Xaml.Core;

namespace Windows.UI.Xaml
Expand All @@ -21,13 +22,9 @@ public partial class Application : IApplicationEvents
private static bool _startInvoked;
private static string _arguments = "";

public Application()
partial void InitializePartial()
{
Current = this;
SetCurrentLanguage();
InitializeSystemTheme();

Package.SetEntryAssembly(this.GetType().Assembly);

if (!_startInvoked)
{
Expand Down
Loading

0 comments on commit 4cad602

Please sign in to comment.