question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

New file picker API

See original GitHub issue

Our current file dialogs are desktop-centric and assume that the app has direct access to the file system, which is not the case for sandboxed environments.

While keeping the current API for desktop platforms, we need to design a new one that would work well with WebAssembly, macOS App Store apps, iOS and Android (possibly with UWP too, but I’m not sure how relevant it is since we don’t support XBox and HoloLens anyway).

Main differences of the API would be:

  1. the lack of direct access to the file system, so the API should provide Streams instead of file paths
  2. apps might need to implement extra functionality to conform with the OS guidelines (e. g. subscribe to file changes via NSFilePresenter in case of iOS
  3. directory access would be more complicated (e. g. https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories?language=objc), so we might want to skip it in the initial implementation
  4. some sandboxes allow to save a file “bookmark” that allows the app to access the same file after restart (see https://github.com/AvaloniaUI/Avalonia/pull/6540)
  5. more complicated required file type information rather than simply a file extension. For example iOS requires to specify a uniform type identifier that are not MIME-types (e. g. com.adobe.pdf, org.idpf.epub-container, public.mp3
  6. sometimes it’s only possible to “export” a file. In case of WebAssembly saving file means creating an object URI from ReadableStream-backed Response object and waiting for browser to start a download), we can only report the intended file name and file type via a virtual Content-Disposition header. In case of iOS that would be the Share function
  7. Some platforms (e. g. iOS) has different APIs for accessing documents (UIDocumentPickerViewController) and photos/videos (UIImagePickerController)
class FilePickerOpenOptions
{
    string Title { get; set; }
    string DefaultFileName { get; set; }
    IList<FilePickerFileType> FileTypes { get; set; }
}

class FilePickerOpenOptions
{
    string Title { get; set; }
    string DefaultFileName { get; set; }
    FilePickerFileType DefaultFileType {get; set; }
    IList<FilePickerFileType> FileTypes { get; set; }
}


class FilePickerFileType
{
    // For desktop
    string Title { get; init; }
    IReadOnlyList<string> Extensions {get; init; }
    // For web
    IReadOnlyList<string> MimeTypes {get; init; }
    // For Apple platforms
    IReadOnlyList<string> AppleUniformTypeIdentifiers {get; init; }

}

// We should provide some built-in types for common file types
class FilePickerFileTypes
{
     static FilePickerFileType Pdf {get;} = new FilePickerFileType
     {
        Title = "PDF document",
        Extensions = new [] {"pdf"},
        AppleUniformTypeIdentifiers = new[] {"com.adobe.pdf"},
        MimeTypes = new [] { "application/pdf" }
     };
}

interface IFilePicker
{
    bool CanOpen { get; }
    Task<IFilePickerFile> OpenAsync(FilePickerOptions options);
    bool CanSave { get; }
    Task<IFilePickerFile> SaveAsync(FilePickerOptions options);
    bool CanExport { get; }
    Task Export(FilePickerOptions options, Func<Stream, FilePickerFileType, Task> writer);
    IFilePickerBookmark OpenBookmark(string bookmark);
}

interface IFilePickerWriteContext
{
    Stream Stream {get;}
    FilePickerFileType FileType {get;}
}

interface IFilePickerBookmark : IDisposable
{
    bool CanOpenRead {get;}
    bool CanOpenWrite {get;}
    Task<Stream> OpenRead();
    Task<Stream> OpenWrite();
}

interface IFilePickerFile : IDisposable
{
     Stream Stream {get;}
     bool CanBookmark{get;}
     Task<string> SaveBookmark();
}

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:3
  • Comments:12 (12 by maintainers)

github_iconTop GitHub Comments

2reactions
workgroupengineeringcommented, Dec 23, 2021

I think a good design to start defining the API surface is Windows.Storage and Windows.Storage.Pickers.

we can deifine like code this:


// We may have various providers eg. AppleLocalStorage, DropBoxStorage, ..
public interface IStorageProvider
{
   ....
}

public interface IStorageItemMeta
{
   string Name { get ;}    
}

public interface IStorageItemWritableMeta
{
  Task SaveAsync(IStorageItem item, CancellationToken token = default);
}

public interface IStoregeItemStringMeta : IStorageItemWritableMeta
{
   string Value {get; set; }
}

public interface IStoregeItemDateTimeMeta : IStorageItemWritableMeta
{
   DateTimeOffset  Value {get; set; }
}

public interface IStoregeItemLongMeta : IStorageItemWritableMeta
{
   long Value {get; set; }
}

public interface IContentTypeMeta : IStorageItemMeta
{
}


public interface IStorageItem
{
   
  IStorageProvider Provider { get; }
  // Gets the name of the item including the file name extension if there is one.
  string Name {get; }
  // Gets the full file-system path of the item, if the item has a path.
  string? Path {get;}
  // Indicates if the file is local, is cached locally, or can be downloaded.
  bool IsAvailable {get;}

  IReadOnlyList<IStorageItem>  Owners {get;}
  
  DateTimeOffset  DateCreated { get;}
  DateTimeOffset  DateModified { get;}
   
  Task<IReadOnlyList<IStoregeItemMeta>> GetMetaAsync(CancellationToken token = default);

  bool TryGetMeta(string metaName, out IStoregeItemMeta? meta);

  Task DeleteAsync(CancellationToken token = default);
  Task RenamAsync(string desiredName, CancellationToken token = default);
}


public interface IFileStorage: IStorageItem
{   
    Task<Stream> OpenOrCreate(CancellationToken token = default);
    bool CanRead {get; }
    bool CanWrite {get; }
    // Can be AppleUniformTypeIdentifiers , MimeTypes , Extension,
    IContentTypeMeta ContentType { get; }
}

public interface IFolderStorage: IStorageItem
{
    Task<IStorageItem> GetsItemsAsync (CancellationToken token = default);    
    Task<bool> TryAddItemAsync(IStorageItem item,CancellationToken token = default);
}

public interface IPickerOperation
{
  srting Title {get;set;}
  Environment.SpecialFolder SuggestedStartLocation  {get;set;}
  IIdentity  User {get;set;}
}

public interface IFilePicker:IPickerOperation
{
  string SuggestedFileName {get;set;}
}

public interface IFileOpenPicker:IFilePicker
{   
   IEnumerable<IContentTypeMeta> FilterBy {get;set;}
   bool CheckExist {get;set;}
   Task<IFileStorage?> PickSingleFileAsync(CancellationToken token = default);
   Task<IRealOnlyList<IFileStorage>?> PickMultipleFileAsync(CancellationToken token = default);
}

public interface IFileSavePicker:IFilePicker
{
   bool CanOverwrite {get;set;}
   IReadOnlyList<IContentTypeMeta> FileTypeChoices {get;set;}
   Task<IFileStorage?> PickSaveFileAsync(CancellationToken token = default);
}

public interface IFolderPicker:IPickerOperation
{
   bool CanCreateNew {get;set;}
   Task<IFolderStorage?> PickFolderAsync(CancellationToken token = default);
}

1reaction
workgroupengineeringcommented, Dec 23, 2021

I don’t see how exactly does it map to macOS/iOS/Android sandboxes and WASM platform limitations.

The mapping is done via IStorageProvider and IContentTypeMeta. IStorageProvider deals with the creation of the appropriate concrete types of IStorageItem with the respective metadata.

UWP sandbox is not relevant because it’s essentially a dead platform anyway. Note that UWP API compatibility is a non-goal for us while properly mapping to various platform’s APIs is.

The fact that she is dead does not mean that we cannot learn from good things. It is not necessary to re-invent the wheel every time. Uno supports macOS/iOS/Android/WASM/Linux(GTK/Framebuffer) using UWP API design.

Your design seems to me very connected to the macOS world.

Having two models for file system access is confusing to the developer. If I make a desktop app I have to use Avalonia.Dialogs. *, If the app has to run in a sandbox I have to use IFilePicker.

Read more comments on GitHub >

github_iconTop Results From Across the Web

The Google Picker API | Google Drive
The Google Picker is a "File Open" dialog for information stored on Google Drive. The Google Picker has these features: A similar look-and-feel...
Read more >
Display the Google Picker | Google Drive
The Google Picker is a "File Open" dialog for information stored on Google servers. You can use the Google Picker API to allow...
Read more >
Window: showOpenFilePicker() method - Web APIs | MDN
The showOpenFilePicker() method of the Window interface shows a file picker that allows a user to select a file or multiple files and...
Read more >
Complete Google Drive File Picker example
This is an example of how to use the Google Drive file picker and Google Drive API to retrieve files from Google Drive...
Read more >
File Picker
File Picker. The File Picker is an open-source, plug-and-play, component that connects to many cloud storage APIs and offers easy file uploads and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found