hpoul / file_picker_writable

Flutter plugin to choose files which can be read, referenced and written back at a later time.
https://pub.dev/packages/file_picker_writable
MIT License
17 stars 13 forks source link

Persistable access to a directory #16

Open amake opened 3 years ago

amake commented 3 years ago

Thanks as always for the great plugin.

I have a use case for selecting a directory, rather than a file: I want to be able to resolve files relative to a file, e.g. images referenced from a document.

For instance I open a file foo/bar/baz.html and find that it references several other files in ./blah. I want to be able to obtain a persistent reference to foo/bar/blah and resolve the files within.

This plugin currently doesn't offer that functionality. Would you be interested in adding it, or would you accept a PR that implements it for iOS and Android only?

I am looking at these APIs:

On the Dart side, it looks like in addition to an openDirectory(path) method we may need some sort of resolveRelative(parent, child) that would take a known security-scoped URL or bookmark and return a new security-scoped URL/bookmark for the child.

bradyt commented 3 years ago

Does ACTION_OPEN_DOCUMENT_TREE even work with Google Drive yet? See articles such as this one:

The file requester opened up by ACTION_OPEN_DOCUMENT_TREE does not list providers such as Google Drive or OneDrive, However, these providers DO show up when using ACTION_OPEN_DOCUMENT.

I worked around this issue by using ACTION_OPEN_DOCUMENT and specifying that the user could select multiple files.

-- https://stackoverflow.com/questions/28663778/android-open-document-tree-intent-root-locations

If Google Drive has not yet implemented ACTION_OPEN_DOCUMENT_TREE, I suspect it is even less likely to be implemented by other document providers.

Perhaps the situation is better on iOS.

amake commented 3 years ago

At the very least it should work for local storage. I won't be holding back on features just because of Google Drive being lazy.

hpoul commented 3 years ago

For instance I open a file foo/bar/baz.html

But the user would first select foo/bar and then your application would show another selector where he chooses the file?

Would you be interested in adding it, or would you accept a PR that implements it for iOS and Android only?

Since i personally don't have a use case for this right now, I don't think i'll have the time to research and implement it right now. But I'd be happy to review and merge a PR. iOS/Android should be fine.. it shouldn't be hard to port it to macos afterwards, if necessary.

amake commented 3 years ago

But the user would first select foo/bar and then your application would show another selector where he chooses the file?

No, sorry I should have given the full scenario.

  1. User chooses a file. The file references other files via relative path.
  2. I want to compute the common root directory of all referenced files, and prompt the user to obtain permissions for that directory. Note that the root could be above or below the original file.
  3. Having obtained permissions for the root, I can then access all referenced files.

There are several reasons why I want the above:

But I'd be happy to review and merge a PR. iOS/Android should be fine..

Thanks. I'll open a PR at some point then.

amake commented 3 years ago

Perhaps the situation is better on iOS.

Neither Google Drive nor Dropbox support persistent access to directories. Local storage and iCloud Drive do.

amake commented 3 years ago

One thing that throws a bit of a wrench in my plans is that it seems that on Android you can't resolve the parent of a known content:// URI. For instance if I have a persistent URI to some directory, I can get URIs for children but I can't get a URI to ...

(On iOS the persistent identifiers are file:// URLs that can be manipulated with standard filesystem semantics.)

If anyone knows a way to solve this on Android I'd love to know.

amake commented 3 years ago

@hpoul Would it be OK with you if I got rid of the deprecated file property on FileInfo?

I find there is a need to resolve a relative path and return FileInfo for it without actually reading the file (because the decision as to whether it should be read as a string or as bytes happens somewhere totally different). So I don't want to copy the file on the native side at all in some cases, but the requirement of a non-null File in FileInfo would mean I would need to make a different class.

(Resolving relative paths needs computation on the native side because Android content URIs are opaque and can't be reasoned about like regular paths.)

amake commented 3 years ago

I am still working on this. It's taken a lot longer than I thought due to the complexity of the Android Storage Access Framework, but I've got it mostly under control now.

@hpoul Some questions about your preferences on nitty-gritty implementation details, if you don't mind:

Directory representation

I want to add these Dart-side methods:

There is a need to represent info for a directory (as opposed to a file, which we have in FileInfo). Do you prefer:

  1. A isDirectory flag on FileInfo?
  2. A separate class, DirectoryInfo?

If you prefer a separate class like DirectoryInfo then it should share a base class with FileInfo because resolveRelativePath could return either a directory or a file. In fact DirectoryInfo, as far as I can tell, would have the exact same properties as FileInfo, so it could look like:

abstract class EntityInfo {
  // Most of current contents of FileInfo
}

class FileInfo extends EntityInfo {
  // Just {de,}serialization stuff
}

class DirectoryInfo extends EntityInfo {
  // Just {de,}serialization stuff
}

Then we're basically using file types as a boolean flag. Though there may be a need for further specialization for either files or directories in the future, so I don't really see it as a problem.

(Note that the Dart standard library types FileSystemEntity, Directory, and File follow this general template.)

Platform support

Currently the plugin supports back to Android API 19, but the directory picking stuff requires API 21.

Do you prefer to:

  1. Drop support for pre-21?
  2. Just let those operations fail without special handling?
  3. Have special handling so the application knows why the operations failed?

I have been assuming (3): I was thinking of adding a specific Dart-side exception such as OperationUnsupported that users could catch if they wanted.