GoogleChromeLabs / browser-fs-access

File System Access API with legacy fallback in the browser
https://googlechromelabs.github.io/browser-fs-access/demo/
Apache License 2.0
1.38k stars 84 forks source link

Explore adding a File and Directory Entries API backend #22

Closed RReverser closed 3 years ago

RReverser commented 3 years ago

The old File and Directory Entries API is very similar to the new API and allows most of thethings that File System Access API allows, too (e.g. iterating over directories, representing hierarchies, reading / creating files and dirs and so on).

While the FileWriter part of the API has been implemented only in Chrome, all the read-only bits are supported in Firefox and Safari too (https://caniuse.com/?search=filesystem), making it a feasible fallback backend to cover even more of the API surface and to make almost feature-complete polyfill.

The only downside is that the File and Directory Entries API only returns entries for drag&dropped items, not for those selected via <input type="file">. But perhaps this would be enough to cover some common use-cases?

jimmywarting commented 3 years ago

The extensive library called native-file-system-adapter is a alternative that i have been working on, it follows the same syntax used by native file system. I have made it possible to work with drag and drop and even the old Entries api and have even added more file system backends to choose from. currently working on supporting NodeJS also

import {
  showDirectoryPicker,
  showOpenFilePicker,
  getOriginPrivateDirectory
} from 'https://cdn.jsdelivr.net/gh/jimmywarting/native-file-system-adapter/src/es6.js'

// the getOriginPrivateDirectory is a legacy name that
// native filesystem added, have not bother to change it

getOriginPrivateDirectory() // same as calling navigator.storage.getDirectory()
// Blink's good old sandboxed file system API, can choose between persistent and temporary 
getOriginPrivateDirectory(import('../src/adapters/sandbox.js'))
getOriginPrivateDirectory(import('../src/adapters/memory.js'))
getOriginPrivateDirectory(import('../src/adapters/indexeddb.js'))
getOriginPrivateDirectory(import('../src/adapters/cache.js'))
getOriginPrivateDirectory(import('../src/adapters/node.js'), './starting-path')

// The polyfilled (file input) version will turn into a memory adapter
// You will have readwrite permission on the memory adapter,
// you might want to transfer (copy) the handle to another adapter
showOpenFilePicker({_preferPolyfill: boolean, ...sameOpts}).then(fileHandle => {})
showDirectoryPicker({_preferPolyfill: boolean, ...sameOpts}).then(directoryHandle => {})

ondrop = evt => {
  evt.preventDefault()
  getOriginPrivateDirectory(evt.dataTransfer).then(directoryHandle => {
    // This is kind of a hybrid memory & sandboxed (Entry api) adapter
    // it works together with old Entry API rather then transferring all of it to a memory adapter 
    // This would allow you to monitor file changes by calling getFile()
    // and compare the last modified date or file size
    // You will have read access but, requesting write permission will reject.
  })
}

All of this method will give you the same kind of native file system api flavor syntax to work with

An in depth testing site is available at https://jimmywarting.github.io/native-file-system-adapter/example/test.html

tomayac commented 3 years ago

@RReverser I guess you're in great hands with @jimmywarting's way more extensive ponyfill then. Mine aims to cater for fewer and simpler use cases. I close this present Issue. Thanks for chiming in with the tip, @jimmywarting!

jimmywarting commented 3 years ago

Fyi, i just found out that the api added a DataTransferItem.prototype.getAsFileSystemHandle() method

Example taken directly from the spec

elem.addEventListener('dragover', (e) => {
  // Prevent navigation.
  e.preventDefault();
});
elem.addEventListener('drop', async (e) => {
  // Prevent navigation.
  e.preventDefault();

  // Process all of the items.
  for (const item of e.dataTransfer.items) {
    // kind will be 'file' for file/directory entries.
    if (item.kind === 'file') {
      const entry = await item.getAsFileSystemHandle();
      if (entry.kind == 'file') {
        handleDirectoryEntry(entry);
      } else if (entry.kind == 'directory') {
        handleFileEntry(entry);
      }
    }
  }
});

I will try to polyfill this method directly onto the DataTransferItem.