Closed21

File System Access API 試す

nbstshnbstsh

The File System Access API (formerly known as Native File System API and prior to that it was called Writeable Files API)

Native File System API は古い呼び名か

nbstshnbstsh

The File System Access API—despite the similar name—is distinct from the FileSystem interface exposed by the File and Directory Entries API, which documents the types and operations made available by browsers to script when a hierarchy of files and directories are dragged and dropped onto a page or selected using form elements or equivalent user actions. It is likewise distinct from the deprecated File API: Directories and System specification, which defines an API to navigate file system hierarchies and a means by which browsers may expose sandboxed sections of a user's local filesystem to web applications.

Fle System Access API と FileSystem は別物なのか

nbstshnbstsh

実際に試してみる

とりあえず vite + React + TS で試してみる

ありがたいことに型も見つかったので、@types/wicg-file-system-access を使っていく

https://github.com/WICG/file-system-access

nbstshnbstsh

File 読み込み

雑ではあるが....
これで選択した file の中身を表示できた

function App() {
  const [content, setContent] = useState<string>('');

  return (
    <div>
      <h1>File System Access API</h1>

      <div>
        <button
          onClick={async () => {
            const [fileHandle] = await window.showOpenFilePicker();
            const file = await fileHandle.getFile();
            const content = await file.text();
            setContent(content);
          }}
        >
          Click Me to select file
        </button>
      </div>

      <div>
        <h2>File Content</h2>
        <div>{content}</div>
      </div>
    </div>
  );
}
nbstshnbstsh

File 書き込み (File の新規作成)

const CreateNewFile = () => {
  const [content, setContent] = useState<string>('');

  return (
    <div>
      <h1>File System Access API - Create a new file</h1>

      <div>
        <textarea
          value={content}
          onChange={(e) => setContent(e.currentTarget.value)}
        />
        <button
          onClick={async () => {
            const handle = await window.showSaveFilePicker({
              types: [
                {
                  description: 'Text Files',
                  accept: {
                    'text/plain': ['.txt'],
                  },
                },
              ],
            });

            const writable = await handle.createWritable();
            await writable.write(content);
            await writable.close();
          }}
        >
          Create a new file
        </button>
      </div>
    </div>
  );
};
nbstshnbstsh

Remove のデータを local の File に保存

Writing data to disk uses a FileSystemWritableFileStream object, a subclass of WritableStream.

The write() method takes a string, which is what's needed for a text editor. But it can also take a BufferSource, or a Blob. For example, you can pipe a stream directly to it:

てなわけで、fetch した response を pipe で流し込めるみたい、便利そう


const WriteFileFromRemoteUrl = () => {
  return (
    <div>
      <h1>File System Access API - Write a file from remote url</h1>

      <div>
        <button
          onClick={async () => {
            const handle = await window.showSaveFilePicker({
              types: [
                {
                  description: 'Text Files',
                  accept: {
                    'text/plain': ['.txt'],
                  },
                },
              ],
            });

            const writable = await handle.createWritable();

            const response = await fetch(
              'https://jsonplaceholder.typicode.com/posts'
            );

            await response.body?.pipeTo(writable);
          }}
        >
          Create a new file
        </button>
      </div>
    </div>
  );
};
nbstshnbstsh

デフォルトの File 名を指定

const handle = await window.showSaveFilePicker({
  suggestedName: 'Untitled Text.txt',
  types: [
    {
      description: 'Text Files',
      accept: {
        'text/plain': ['.txt'],
      },
    },
  ],
});

nbstshnbstsh

Specifying the purpose of different file pickers

id を振って、複数の file handle を扱うことも可能

const fileHandle1 = await self.showSaveFilePicker({
  id: 'openText',
});

const fileHandle2 = await self.showSaveFilePicker({
  id: 'importImage',
});
nbstshnbstsh

Storing file handles or directory handles in IndexedDB

File handles and directory handles are serializable, which means that you can save a file or directory handle to IndexedDB, or call postMessage() to send them between the same top-level origin.

FIle handle は serialize できるので、indexedDB 等に保存することもできる

nbstshnbstsh

Stored file or directory handles and permissions

queryPermission(): すでに権限を許可済みか確認
requestPermission(): 権限をリクエスト

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = 'readwrite';
  }
  // Check if permission was already granted. If so, return true.
  if ((await fileHandle.queryPermission(options)) === 'granted') {
    return true;
  }
  // Request permission. If the user grants permission, return true.
  if ((await fileHandle.requestPermission(options)) === 'granted') {
    return true;
  }
  // The user didn't grant permission, so return false.
  return false;
}

While FileSystemHandle objects can be serialized and stored in IndexedDB, the permissions currently need to be re-granted each time, which is suboptimal. Star crbug.com/1011533 to be notified of work on persisting granted permissions.

FileSystemHandle を serialize して保存した場合でも、権限に関しては保持されないので、毎回許可してもらう必要がある

nbstshnbstsh

Opening a directory and enumerating its contents

To enumerate all files in a directory, call showDirectoryPicker(). The user selects a directory in a picker, after which a FileSystemDirectoryHandle is returned, which lets you enumerate and access the directory's files. By default, you will have read access to the files in the directory, but if you need write access, you can pass { mode: 'readwrite' } to the method.

showDirectoryPicker()FileSystemDirectoryHandle を取得して、 directory 内の file にアクセス

const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
  console.log(entry.kind, entry.name);
}

You can suggest a default start directory by passing a startIn property to the showSaveFilePicker, showDirectoryPicker(), or showOpenFilePicker methods like so.

showDirectoryPicker()startIn でデフォルトの選択 directory を設定できる

const dirHandle = await window.showDirectoryPicker({
  startIn: 'documents',
});
nbstshnbstsh

Creating or accessing files and folders in a directory

FileSystemDirectoryHandle を利用して、

  • 新規 directory の作成
  • 新規 file の作成

が可能。

// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
  create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });
nbstshnbstsh

Resolving the path of an item in a directory

path の取得も可能

// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]
nbstshnbstsh

Deleting files and folders in a directory

If you have obtained access to a directory, you can delete the contained files and folders with the removeEntry() method. For folders, deletion can optionally be recursive and include all subfolders and the files contained therein.

removeEntry() で directory 内の file おそび folder の削除が可能

// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });
nbstshnbstsh

Deleting a file or folder directly

FileSystemFileHandle, FileSystemDirectoryHandle に対して、remove() を直接呼び出せば自身を削除可能

// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();
nbstshnbstsh

Renaming and moving files and folders

file, folder の名前の変更・移動

// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');
nbstshnbstsh

基本的にはこんなもんで、他にもこの辺についても記述あり↓

  • Drag and drop integration
  • Accessing the origin private file system

The origin private file system is a storage endpoint that, as the name suggests, is private to the origin of the page.

origin private file system があるの知らなかった

nbstshnbstsh
nbstshnbstsh

Restricted folders

To help protect users and their data, the browser may limit the user's ability to save to certain folders, for example, core operating system folders like Windows, the macOS Library folders, etc. When this happens, the browser shows a modal prompt and ask the user to choose a different folder.

そもそもアクセスが制限されている folder も存在する
e.g.) Windows, the macOS Library folders

nbstshnbstsh

Permission persistence

The web app can continue to save changes to the file without prompting until all tabs for its origin are closed. Once a tab is closed, the site loses all access. The next time the user uses the web app, they will be re-prompted for access to the files.

origin ごとに、全ての tab が閉じるまでアクセス権限は保持される

このスクラップは2022/11/02にクローズされました