microsoft / WindowsAppSDK

The Windows App SDK empowers all Windows desktop apps with modern Windows UI, APIs, and platform features, including back-compat support, shipped via NuGet.
https://docs.microsoft.com/windows/apps/windows-app-sdk/
MIT License
3.85k stars 327 forks source link

Microsoft.Storage.ApplicationData #2639

Open DrusTheAxe opened 2 years ago

DrusTheAxe commented 2 years ago

Proposal: Microsoft.Storage.ApplicationData

Summary

Provide a modern enhanced version of Windows.Storage.ApplicationData and Windows.Management.Core.ApplicationDataManager

Microsoft-internal task 40050113

Rationale

Windows App SDK can and should provide a modern revision of the ApplicationData API, addressing the issues above (one API supporting both packaged and unpackaged apps).

Scope

Capability Priority
APIs equivalent to Windows.Storage.ApplicationData (minus deprecated APIs) Must
APIs available to packaged and unpackaged applications Must
APIs offer filesystem access without requiring use of StorageFolder Must
Synchronous equivalents to Windows.Storage.ApplicationData.*Async() APIs Must
WinRT API Must

Important Notes

Microsoft.Storage.ApplicationData supports packaged and unpackaged apps. The underlying data stores are different but equivalent(ish):

Packaged Unpackaged
ApplicationData.LocalCacheFolder %LOCALAPPDATA%\<publisher>\<product>
ApplicationData.LocalFolder %LOCALAPPDATA%\<publisher>\<product>
ApplicationData.MachineFolder %ProgramData%\<publisher>\<product>
ApplicationData.RoamingFolder %LOCALAPPDATA%\Roaming
ApplicationData.TemporaryFolder %LOCALAPPDATA%\Temp
ApplicationData.LocalSettings HKCU\SOFTWARE\<publisher>\<product>

WinRT API

Here's a sketch of the general shape of the API to express the intent of the ask. The actual API when designed/spec'd/reviewed/approved may differ.

Microsoft.Storage.ApplicationData.idl

namespace Microsoft.Storage.ApplicationData
{
[contract(ApplicationData, 1)]
enum ApplicationDataLocality
{
    // NOTE: Values compatible with Windows.Storage.ApplicationDataLocality
    Local = 0,
    LocalCache = 3,
    SharedLocal = 4,
    Temporary = 2,
}

[contract(ApplicationData, 1)]
runtimeclass ApplicationData
{
    /// Return an ApplicationData object if the current process has package identity
    /// NOTE: This is equivalent to `ApplicationData.ForPackageFamily(Windows.ApplicationModel.Package.Current.Id.FamilyName)`
    static ApplicationData GetDefault();

    /// NOTE: Requires MediumIL (or higher) if the process lacks package identity.
    /// NOTE: Requires MediumIL (or higher) if the process has package identity but different from the packageFamilyName parameer.
    /// NOTE: GetForPackageFamily(Windows.ApplicationModel.Package.Current.Id.FamilyName) is equivalent to GetDefault()
    static ApplicationData GetForPackageFamily(String packageFamilyName);

    /// NOTE: Requires admin privilege if user != current user
    /// NOTE: GetForUser(Window.System.User.GetDefault) is equivalent to GetDefault()
    static ApplicationData GetForUser(Windows.System.User user);

    /// NOTE: Requires admin privilege if user != current user
    /// NOTE: GetForUser(Window.System.User.GetDefault, packageFamilyName) is equivalent to GetForPackageFamily(packageFamilyName)
    static ApplicationData GetForUser(Windows.System.User user, String packageFamilyName);

    StorageFolder LocalCacheFolder;
    StorageFolder LocalFolder;
    StorageFolder TemporaryFolder;
    StorageFolder SharedLocalFolder;    //NOTE: Returns NULL if process lacks package identity

    String LocalCachePath;
    String LocalPath;
    String TemporaryPath;
    String SharedLocalPath; //NOTE: Returns NULL if process lacks package identity

    ApplicationDataContainer LocalSettings;

    void Clear();
    void Clear(ApplicationDataLocality locality);

    AsyncAction ClearAsync();
    AsyncAction ClearAsync(ApplicationDataLocality locality);
}
}

Open Questions

Q: Is there an unpackaged equivalent to GetPublisherCacheFolder()?

DrusTheAxe commented 2 years ago

See https://github.com/microsoft/WindowsAppSDK/issues/2621#issuecomment-1158341320 for the latest discussion that sparked the creation of this issue

riverar commented 2 years ago

Windows.Storage.ApplicationData.Current only works fir you have package identity AND running in AppContainer

Nit: I'd add the word reliably in there (e.g. only works reliably), just to bring attention to how evil this API truly is. Because the API does work most of the time, it misleads devs and puts them on a path to failure.

Otherwise thanks for writing this up and moving the needle. I've been talking about these tricky APIs/scenarios since around 2017!

DrusTheAxe commented 2 years ago

Windows.Storage.ApplicationData.Current only works if you have package identity AND running in AppContainer

Nit: I'd add the word reliably in there (e.g. only works reliably), just to bring attention to how evil this API truly is. Because the API does work most of the time, it misleads devs and puts them on a path to failure.

Can you elaborate on what you find unreliable about ApplicationData.Current?

Is it really unreliable or rather the complexity of having to understand the right contexts it does/not support (i.e. the Rationale's first 3 bullets)?

riverar commented 2 years ago

Can you elaborate on what you find unreliable about ApplicationData.Current?

ApplicationData.Current.LocalSettings usage outside of AppContainer is on a best effort basis I'm told. Was a top crasher for EarTrumpet (runFullTrust app) until I replaced it recently with ApplicationDataManager.CreateForPackageFamily(...).LocalSettings. 😎

lukeblevins commented 2 years ago
  • ApplicationData.*Folder only provide access to the filesystem via StorageFolder. There's no way to get their .Path without going thrug a StorageFolder incurring significant performance overhead

Can definitely see the importance of getting a filesystem Path without StorageFolder. However, it would be valuable to know whether Microsoft sees the performance overhead as inevitable with StorageFolder/StorageFile. #8 is obviously a separate issue to tackle, but should new APIs really be built around paths if an improved StorageFolder (or something new) is on the backlog?

EDIT: IDL in spec shows that both a StorageFolder and Path will be available for the given locations, so this seems like less of a statement on the future of StorageFolder and more about facilitating interoperability from non Windows.Storage APIs which take a filesystem path.

Gets a 👍🏻 from me

DrusTheAxe commented 2 years ago

EDIT: IDL in spec shows that both a StorageFolder and Path will be available for the given locations, so this seems like less of a statement on the future of StorageFolder and more about facilitating interoperability from non Windows.Storage APIs which take a filesystem path.

Bingo! :-)

anlocnghg commented 2 years ago

Just to let you know what I did (a manual and not an elegant solution) to walkaround the Windows.Storage.ApplicationData for now:

  1. Use System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) to replace the (path to) local storage folder (Windows.Storage.ApplicationData.Current.LocalFolder).
  2. Use XML for local settings (Windows.Storage.ApplicationData.Current.LocalSettings).

    Edited: for my situation only as in discussion #2618

DrusTheAxe commented 11 months ago

Use System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) to replace the (path to) local storage folder (Windows.Storage.ApplicationData.Current.LocalFolder).

BTW that's not equivalent. ApplicationData.LocalFolder is a per-user location in your user profile1,2 but GetExecutingAssembly() is wherever the assembly is located (i.e. where your .exe or .dll lives). That could be in your user profile but could be lots other places, and it's the same answer for the assembly regardless of the user.

Windows.Storage.AppDataPaths.GetDefault().LocalAppData + \publisher\product is the unpackaged equivalent to ApplicationData.Current.LocalFolder -- a per-user location for data for the product/package.

Windows.Storage.AppDataPaths.GetDefault().LocalAppData == SHGetKnownFolderPath(KnownFolderID_LocalAppData...) == %LOCALAPPDATA% == %USERPROFILE%\AppData\Local (but APIs are better than environment variables as the latter can be trivially altered/hacked; they're not reliable or definitive sources of the answer, those APIs are).

1 Maybe. ApplicationData for a package family exists under %USERPROFILE% if the package is installed on %SystemDrive% (i.e. C:) but if you install packages to other drives then their ApplicationData will be created on the same drive e.g. install a packaged app on Q: and ApplicationData.LocalFolder will likewise be located on Q:

2 But that's not absolute. If you then move the package to another drive the package moves but not the data e.g. pkgmgr.MovePackageAsync(foo,...) from Windows.ApplicationModel.Package.InstalledPath=Q:... to M:... but ApplicationData will still be on Q: