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.79k stars 320 forks source link

Allow setting the thumbnail of storage items through StorageItemThumbnail.WriteAsync #726

Open winston-de opened 3 years ago

winston-de commented 3 years ago

Proposal: Allow setting the thumbnail of storage items through StorageItemThumbnail.WriteAsync

Summary

Currently, there is no way for UWP apps to provide thumbnails for storage items. There appears to already be an API for this, StorageItemThumbnail.WriteAsync, however StorageItemThumbnail.CanWrite always seems to be false, and attempting to write to the stream will result in an access denied error. This proposal is to enable setting the thumbnails for items through StorageItemThumbnail.WriteAsync.

Rationale

Scope

Capability Priority
This proposal will allow developers to set a StorageItem's thumbnail using StorageItemThumbnail.WriteAsync Must
This proposal will allow applications to declare a thumbnail provider capability, similar to the one in win32 Won't
This proposal will allow applications to change the thumbnails of applications they don't have access to Won't

Important Notes

Open Questions

lukeblevins commented 3 years ago

Specifically, this API should integrate with the custom desktop.ini-specified icons for a folder (on Windows 10 desktop). It remains unclear how/if this could work for a standard StorageFile.

ptorr-msft commented 3 years ago

FWIW, the object has a WriteAsync method because it implements IRandomAccessStream which requires it. Good news is the CanWrite property is correct.

How do you envision the feature working? For example, you say:

For example a PDF editor could set a thumbnail that consists of a preview of the first page of the document to make it easier for the user to identify the contents of the file

But since the UWP app can only write to files the user has chosen, it can only write thumbnails if they user opens the file in the app (or grants it access to an entire folder, or has broadFilesystemAccess). Typically the process is driven by the Shell, which will create and cache thumbnails as-needed by using IThumbnailProvider.

Setting the desktop.ini content should already be possible I believe, as long as you have access to the folder and are using the CreateFileFromApp filesystem API.

winston-de commented 3 years ago

FWIW, the object has a WriteAsync method because it implements IRandomAccessStream which requires it. Good news is the CanWrite property is correct.

How do you envision the feature working? For example, you say:

For example a PDF editor could set a thumbnail that consists of a preview of the first page of the document to make it easier for the user to identify the contents of the file

But since the UWP app can only write to files the user has chosen, it can only write thumbnails if they user opens the file in the app (or grants it access to an entire folder, or has broadFilesystemAccess). Typically the process is driven by the Shell, which will create and cache thumbnails as-needed by using IThumbnailProvider.

Right, if the user creates a document and then saves it, or opens a document, then the application would have the access it needs to create and write a thumbnail.

I was also thinking that maybe a thumbnail provider capability could be provided to universal apps, perhaps using an app service and passing a file access token as an argument.

App manifest:

<uap:Extension Category="windows.appService" EntryPoint="FileService.Preview">
    <uap3:AppService Name="com.windows.thumbnails" uap4:SupportsMultipleInstances="true"/>
</uap:Extension>

Code:

namespace FileService
{
    public sealed class Preview : IBackgroundTask
    {
        private BackgroundTaskDeferral backgroundTaskDeferral;
        private AppServiceConnection appServiceconnection;

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Get a deferral so that the service isn't terminated.
            this.backgroundTaskDeferral = taskInstance.GetDeferral();

            // Associate a cancellation handler with the background task.
            taskInstance.Canceled += OnTaskCanceled;

            // Retrieve the app service connection and set up a listener for incoming app service requests.
            var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
            appServiceconnection = details.AppServiceConnection;
            appServiceconnection.RequestReceived += OnRequestReceived;
        }

        private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            AppServiceDeferral messageDeferral = args.GetDeferral();
            var message = args.Request.Message;
            var returnMessage = new ValueSet();
            var file = await SharedStorageAccessManager.RedeemTokenForFileAsync(message["fileAccessToken"] as string);
            var thumbnail = await file.GetThumbnailAsync(ThumbnailMode.ListView);
            await thumbnail.WriteAsync(await GetThumbnailForFile(file));

            messageDeferral.Complete();
        }

       private Task<IBuffer> GetThumbnailAsync(StorageFile file) {
           var content = await file.OpenReadAsync();

           IBuffer thumbnail;
           // do some magic to make a thumbnail

           return thumbnail;
       }

        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (this.backgroundTaskDeferral != null)
            {
                // Complete the service deferral.
                this.backgroundTaskDeferral.Complete();
            }
        }
    }
}

Sorry for all the edits, I accidently sent this before I finished writing :)

riverar commented 3 years ago

I think adding a thumbnail provider capability is out of scope for this issue. Instead, I think the ask should simply be: Make StorageItemThumbnail.WriteAsync work for files the app already has access to.

winston-de commented 3 years ago

I think adding a thumbnail provider capability is out of scope for this issue. Instead, I think the ask should simply be: Make StorageItemThumbnail.WriteAsync work for files the app already has access to.

Ok, I updated the scope table

riverar commented 3 years ago

Sounds very fair, 👍 from me. Nice job!

ptorr-msft commented 3 years ago

Setting expectations, this is unlikely to make the priority list (at least in the short-term) but we'll keep it on the backlog and I'll ping a few folks with some ideas. It would require brokering and thus additional complexity.

I was also thinking that maybe a thumbnail provider capability could be provided to universal apps, perhaps using an app service and passing a file access token as an argument.

This isn't feasible because the app would be provided access to arbitrary files on the system without user consent (just open the folder in Explorer, and all the files get sent to the UWP). We could special-case broadFilesystemAccess or only scope it to folders you have access to, etc. but it's a lot of work for a relatively niche feature. Just use runFullTrust instead.

winston-de commented 3 years ago

I was also thinking that maybe a thumbnail provider capability could be provided to universal apps, perhaps using an app service and passing a file access token as an argument.

This isn't feasible because the app would be provided access to arbitrary files on the system without user consent (just open the folder in Explorer, and all the files get sent to the UWP). We could special-case broadFilesystemAccess or only scope it to folders you have access to, etc. but it's a lot of work for a relatively niche feature. Just use runFullTrust instead.

Makes sense, and I already removed it from the scope.

ChrisGuzak commented 3 years ago

FYI, the property system API offers PKEY_Thumbnail and PKEY_ThumbnailStream as a way to read and write thumbnails to file formats that support it (.jpg via EXIF, .mp3 via ID3)