【JavaScript】File System Access API – 仮想的なファイルシステム Origin Private File System 編

JavaScript で扱えるストレージといえば Web Storage や Indexed Database API がありますが、ファイルやディレクトリという概念を使ってデータを保存したい場合もあるでしょう。以前に W3C にて File API: Directories and System という API が策定され、仮想的なファイルシステムを扱えるようになりました。Chrome に実装されましたが、仕様は廃止となり、その利用は非推奨になっています。

そして現在、W3C にて File System Access API が策定中ですが、File API: Directories and System で扱ったような仮想的なファイルシステムだけでなく、PC やスマートフォンの実際のファイルシステムも対象になっています。

今回は、Chrome、Edge、Safari に実装された仮想的なファイルシステムである「Origin Private File System」について詳しく解説します。

Origin Private File System とは

Origin Private File System とは、オリジンごとに用意された仮想的なファイルシステムです。オリジンとは URL のスキームからポート番号までを指します。たとえば、このサイトであれば、https://webfrontend.ninja がオリジンです。Origin Private File System は、JavaScript から自身のオリジン用のファイルシステムにアクセスできますが、異なるオリジンのファイルシステムにはアクセスできません。また、同じオリジンであったとしても、別のブラウザーが生成したファイルシステムにはアクセスできません。このあたりの制約は Web Storage や Indexed Databased API と同じです。

Origin Private File System の最上層のディレクトリをルートディレクトリと呼び、Linux などのファイルシステムのディレクトリ階層と同じ概念を持ちます。このルートディレクトリ配下であれば、自由にディレクトリやファイルを生成することが可能です。

Origin Private File System はあくまでも仮想的なファイルシステムですので、仮に Origin Private File System 内にファイルやディレクトリを生成したとしても、ホストとなる PC やスマートフォンのストレージに該当のファイルやディレクトリが実在するとは限りません。それはブラウザーの実装次第です。

なお、File System Access API で規定されているメソッドのほとんどは非同期のメソッドで Promise オブジェクトを返します。以降のサンプルコードでは、async の関数の中で書かれたコードであることを前提としまます。そのため、await を使ってメソッドを呼び出しています。

ファイルの生成

まずはルートディレクトリ直下に test.txt という名前の空のファイルを生成してみましょう。

// ルートの FileSystemDirectoryHandle オブジェクト
const root = await navigator.storage.getDirectory();

// test.txt を生成して FileSystemFileHandle オブジェクトを取得
const fh = await root.getFileHandle('test.txt', { create: true });

まず、navigator.storage オブジェクトの getDirectory() メソッドでルートディレクトリのディレクトリハンドルである FileSystemDirectoryHandle オブジェクトを取得します。ここでは変数 rootFileSystemDirectoryHandle オブジェクトを代入しています。

ディレクトリハンドルとは、ディレクトリの情報を読み取ったり操作するためのオブジェクトのことで、このオブジェクトに組み込まれているプロパティやメソッドを使って、そのディレクトリを扱います。ここではディレクトリハンドルの getFileHandle() メソッドを使って、test.txt を生成しています。この時点では text.txt は空のファイルです。ファイルへのデータの書き込みについては後述します。

getFileHandle() メソッドは名前の通りファイルハンドルを取得するメソッドですが、第二引数に { create: true } を指定することで、該当のファイルがなければ新規に生成することができます。ここでは変数 fh にファイルハンドルである FileSystemFileHandle オブジェクトを代入しています。

ファイルハンドルとは、ファイルの情報を読み取ったり操作するためのオブジェクトのことで、このオブジェクトに組み込まれているプロパティやメソッドを使って、そのファイルを扱います。

ここで登場したディレクトリハンドルとなる FileSystemDirectoryHandle オブジェクトや、ファイルハンドルとなる FileSystemFileHandle オブジェクトの詳細は後述します。

ディレクトリの生成

ディレクトリは、FileSystemDirectoryHandle オブジェクトの getDirectoryHandle() メソッドを使って生成します。次のサンプルはルートディレクトリ直下にディレクトリ work を生成します。

const root = await navigator.storage.getDirectory();
const dh = await root.getDirectoryHandle('work', { create: true });

getDirectoryHandle() メソッドは、前述のファイルを生成する getFileHandle() メソッドと同じく、第二引数に { create: true } を指定することで、該当のディレクトリがなければ新規に生成することができます。

getDirectoryHandle() メソッドは該当のファイルを表す FileSystemDirectoryHandle オブジェクトを返します。オブジェクトの種類としては、前述のルートディレクトリのオブジェクトと同じです。

先ほど生成したディレクトリ work の中に新たにテキストファイルを作るにはどうしたら良いでしょうか。ルートディレクトリ直下にファイルを生成する手順と同じです。親となるディレクトリのディレクトリハンドルの getFileHandle() を呼び出すだけです。

const fh = await dh.getFileHandle('todo.txt', { create: true });

ファイルとディレクトリの一覧の取得

ディレクトリハンドルである FileSystemDirectoryHandle オブジェクトは、あたかもキーと値を持った一般的な Object オブジェクトのように values(), keys(), entries() メソッドを持っています。これらを使って、そのディレクトリ直下のファイルやディレクトリを取り出すことができます。ただし、values(), keys(), entries() メソッドによる繰り返し処理は、一般的なオブジェクトのそれらとは異なり非同期のため for 文で await を指定する必要があります。

次のサンプルは、ルートディレクトリ直下のファイルやディレクトリの一覧をコンソールに出力します。ただし、ディレクトリの場合は、ディレクトリ名の後ろに / を付け加えています。

const root = await navigator.storage.getDirectory();
for await (const handle of root.values()) {
  if (handle.kind === 'file') {
    console.log(handle.name);
  } else if (handle.kind === 'directory') {
    console.log(handle.name + '/');
  }
}

コンソールには、次のような結果が出力されます。

work/
test.txt

FileSystemDirectoryHandle オブジェクトの values() は、ディレクトリ直下のファイルやディレクトリを表すファイルハンドルまたはディレクトリハンドルのオブジェクトを返します。上記コードでは、変数 handle には FileSystemFileHandle オブジェクトか FileSystemDirectoryHandle オブジェクトのいずれかがセットされることになります。

FileSystemFileHandle オブジェクトか FileSystemDirectoryHandle オブジェクトのどちらも、次のプロパティを持っています。

プロパティ説明
kindString対象がファイルなら "file"、ディレクトリなら "directory"
nameStringファイル名またはディレクトリ名

先ほどのサンプルでは FileSystemDirectoryHandle オブジェクトの values() メソッドを使ってファイルハンドルおよびディレクトリハンドルを取得しましたが、keys() メソッドを使うとファイル名が得られます。

const root = await navigator.storage.getDirectory();
for await (const name of root.keys()) {
  console.log(name);
}

この場合、ファイル名およびディレクトリ名の情報しか得られません。それがファイルなのかディレクトリなのかを区別することもできませんので、利用シーンはかなり限られるでしょう。

一方で、entries() メソッドでは、キーにファイル名またはディレクトリ名が、値にファイルハンドルまたはディレクトリハンドルが得られます。

const root = await navigator.storage.getDirectory();
for await (const [name, handle] of root.entries()) {
  if (handle.kind === 'file') {
    console.log(name);
  } else if (handle.kind === 'directory') {
    console.log(name + '/');
  }
}

ファイルとディレクトリのリネーム

ファイル名およびディレクトリ名のリネームには、FileSystemFileHandle オブジェクトおよび FileSystemDirectoryHandle オブジェクトの move() メソッドを使います。

次のサンプルは test.txt というファイルを hello.txt にリネームします。

// ルートの FileSystemDirectoryHandle オブジェクト
const root = await navigator.storage.getDirectory();

// test.txt の FileSystemFileHandle オブジェクトを取得
const fh = await root.getFileHandle('test.txt');

// text.txt を hello.txt にリネーム
await fh.move('hello.txt');

ファイルとディレクトリの削除

ファイルやディレクトリを削除するのは、その親となるディレクトリのディレクトリハンドル FileSystemDirectoryHandle オブジェクトの removeEntry() メソッドを使います。

次のサンプルは、ルートディレクトリ直下のすべてのファイルとディレクトリを削除します。

const root = await navigator.storage.getDirectory();
for await (const name of root.keys()) {
  root.removeEntry(name, { recursive: true });
}

removeEntry() メソッドの第一引数にはファイル名またはディレクトリ名を指定します。第二引数には recursivetrue をセットしたオブジェクトを指定していますが、これは削除したいディレクトリの中にファイルやディレクトリがあったとしてもすべて削除します。

この第二引数ですが、ファイルを削除する場合に指定したとしても無視されるだけでエラーにはなりません。また、第一引数に存在しないファイル名やディレクトリ名を指定してもエラーにはなりません。

ファイルのパスを取得

ディレクトリハンドルである FileSystemDirectoryHandle オブジェクトの resolve() メソッドを使うと、そのディレクトリから見たファイルのパスを取得することができます。

次のサンプルはルート直下に text.txt というファイルを生成し、ルートから見た text.txt のパスを取得します。

const root = await navigator.storage.getDirectory();
const fh1 = await root.getFileHandle('test.txt', { create: true });
const path1 = await root.resolve(fh1);
console.log(path1); // ['test.txt']

resolve() メソッドには、パスを知りたいファイルの FileSystemFileHandle オブジェクトを引数に指定します。resolve() メソッドはファイル名を含めたパスを配列として返します。この場合は ['test.txt'] コンソールに出力されます。

では、もう少し階層を増やして試してみましょう。次のサンプルはルートディレクトリ直下にディレクトリ projects を生成し、その中にディレクトリ HelloWorld を生成し、さらにその中にファイル readme.txt を生成しています。そして、ルートディレクトリから見た readme.txt のパスを取得します。

const root = await navigator.storage.getDirectory();
const dh1 = await root.getDirectoryHandle('projects', { create: true });
const dh2 = await dh1.getDirectoryHandle('HelloWorld', { create: true });
const fh2 = await dh2.getFileHandle('readme.txt', { create: true });
const path2 = await root.resolve(fh2);
console.log(path2); // ['projects', 'HelloWorld', 'readme.txt']

このコードはコンソールに ['projects', 'HelloWorld', 'readme.txt'] を出力します。

ファイルへのデータ書き込み

ファイルにデータを書き込むには、ファイルハンドルである FileSystemFileHandle オブジェクトの createWritable() メソッドを使って FileSystemWritableFileStream オブジェクトを取得します。そして、FileSystemWritableFileStream オブジェクトの write() メソッドを使って該当のファイルにデータを書き込みます。書き込むデータは、事前に Blob オブジェクトとして作っておきます。書き込んだら、FileSystemWritableFileStream オブジェクトの close() メソッドでファイルを閉じます。

次のサンプルは、ルートディレクトリ直下に test.txt を新規に生成し、「こんにちは」という UTF-8 のテキストデータを Blob オブジェクトとして生成し、それをファイルに書き込みます。

// ルートの FileSystemDirectoryHandle オブジェクト
const root = await navigator.storage.getDirectory();

// test.txt を生成して FileSystemFileHandle オブジェクトを取得
const fh = await root.getFileHandle('test.txt', { create: true });

// FileSystemWritableFileStream オブジェクトを取得
const stream = await fh.createWritable();

// テキストデータの Blob オブジェクトを生成
const blob = new Blob(['こんにちは'], {type: 'text/html;charset=utf-8'});

// テキストデータをファイルに書き込む
await stream.write(blob);

// ファイルを閉じる
await stream.close();

FileSystemWritableFileStream オブジェクトの write() メソッドには Blob オブジェクトを引き渡すわけですが、実はテキストであれば直接テキストを引数に与えることも可能です。

await stream.write('こんにちは');

2022 年 6 月現在、この方法によるファイルへのデータ書き込みは、Chrome および Edge でサポートされていますが、Safari ではサポートされていません。Safari でファイルにデータを書き込むには、Web Workers の専用ワーカーのワーカースレッド内で異なる方法で行う必要があります。それについては後述します。

ファイルのデータ読み取り

ファイルのデータを読み取るには、該当のファイルのファイルハンドルである FileSystemFileHandle オブジェクトを取得し、getFile() メソッドを使って File オブジェクトを取得します。File オブジェクトからデータを読み取る方法の説明は割愛します。

次のサンプルは、前述のサンプルで生成した test.txt を読み取って、その内容をコンソールに出力します。

// ルートの FileSystemDirectoryHandle オブジェクト
const root = await navigator.storage.getDirectory();

// test.txt の FileSystemFileHandle オブジェクトを取得
const fh = await root.getFileHandle('test.txt');

// ファイルハンドルから File オブジェクトを取得
const file = await fh.getFile();

// File オブジェクトからデータを読み取る
const text = await file.text();
console.log(text); // こんにちは

ルートディレクトリの FileSystemDirectoryHandle オブジェクト(変数 root)の getFileHandle() メソッドに注目してください。このメソッドは新規にファイルを生成する際にも使いましたが、そのときのコードは次の通りでした。

const fh = await root.getFileHandle('test.txt', { create: true });

一方で、今回のコードは次の通りです。

const fh = await root.getFileHandle('test.txt');

ご覧の通り、第二引数を指定していません。これはすでに test.txt が存在することを前提にしているからで、新規にファイルを生成する必要がないからです。もし該当のファイルが存在しない場合は、例外 DOMException を投げます。

書き込み位置を指定しながらファイルにデータを書き込む

前述のサンプルでは、ファイルにデータを書き込むには FileSystemWritableFileStream オブジェクトの write() メソッドに書き込むデータを表す文字列または Blob オブジェクトを引き渡ました。

await stream.write('こんにちは');

実は、これは暗黙的にファイルの先頭にデータを書き込むことになります。このコードを厳密に記述すると次のようになります。

await stream.write({ type: 'write', position: 0, data: 'こんにちは' });

position プロパティはファイルの先頭からのバイト数を意味します。そのため、バイト位置を指定しながら分割してデータを書き込むことも可能です。

次の例はファイルを新規に生成し、まずは半角スペースを 50 バイト書き込みます。その後、バイト位置を指定しながら単語を書き込んでいます。

// ルートの FileSystemDirectoryHandle オブジェクト
const root = await navigator.storage.getDirectory();

// test.txt を生成して FileSystemFileHandle オブジェクトを取得
const fh = await root.getFileHandle('test.txt', { create: true });

// FileSystemWritableFileStream オブジェクトを取得
const stream = await fh.createWritable();

// テキストデータをファイルに書き込む
await stream.write(' '.repeat(50));
await stream.write({ type: 'write', position: 0, data: 'Google' });
await stream.write({ type: 'write', position: 10, data: 'Amazon' });
await stream.write({ type: 'write', position: 20, data: 'Facebook' });
await stream.write({ type: 'write', position: 30, data: 'Apple' });
await stream.write({ type: 'write', position: 40, data: 'Microsoft' });

// ファイルを閉じる
await stream.close();

// ファイルを読み取る
const file = await fh.getFile();
const text = await file.text();
console.log(text); // "Google    Amazon    Facebook  Apple     Microsoft "

バイト位置を指定してデータを書き込むには、seek() メソッドを使う方法もあります。テキストデータをファイルに書き込む部分だけを書き換えると次のようになります。

// テキストデータをファイルに書き込む
await stream.write(' '.repeat(50));
await stream.seek(0);
await stream.write('Google');
await stream.seek(10);
await stream.write('Amazon');
await stream.seek(20);
await stream.write('Facebook');
await stream.seek(30);
await stream.write('Apple');
await stream.seek(40);
await stream.write('Microsoft');

バイト位置を指定せずに write() メソッドを呼び出した場合は、現在のファイルカーソル位置にデータを書き込みます。

なお、write() メソッドでも seek() メソッド同じことができます。

await stream.write({ type: 'seek', position: 20 });

これは次のコードと同じことをしていることになります。

await stream.seek(20);

では、先ほど生成したテキストファイルを改めて開いて、20 バイト目の "Facebook""Meta " に書き換えてみましょう。おそらく、次のようなコードを考えるのではないでしょうか。

// ルートの FileSystemDirectoryHandle オブジェクト
const root = await navigator.storage.getDirectory();

// test.txt の FileSystemFileHandle オブジェクトを取得
const fh = await root.getFileHandle('test.txt');

// FileSystemWritableFileStream オブジェクトを取得
const stream = await fh.createWritable();

// 20 バイト目から "Meta    " を書き込む
await stream.write({ type: 'write', position: 20, data: 'Meta    ' });

// ファイルを閉じる
await stream.close();

// ファイルを読み取る
const file = await fh.getFile();
const text = await file.text();
console.log(text); // "                    Meta    "

残念ながらこのコードは期待通りに動作しません。text.txt はいったんクリアされてしまい、20 バイト目から "Meta " が書き込まれ、そこでファイルが終了してしまいます。しかも、ファイルの先頭から 19 バイト目までは半角スペースではなく 0x00(NUL)がセットされます。もはやテキストファイルではありません。

実は、ファイルハンドル FileSystemFileHandle オブジェクトの createWritable() メソッドは、デフォルトでは既存データを保持しないモードで FileSystemWritableFileStream オブジェクトを生成します。

const stream = await fh.createWritable();

このデフォルトの挙動は、既存のファイルといえども空のデータとして処理します。既存データを保持して編集できる FileSystemWritableFileStream オブジェクトを生成するには、createWritable() メソッドに次のようなオブジェクトを引数に引き渡します。

const stream = await fh.createWritable({ keepExistingData: true });

keepExistingData プロパティに true をセットすることで、既存ファイルデータを事前に読み込んでくれます。修正済みのコードを改めて見てみましょう。

// ルートの FileSystemDirectoryHandle オブジェクト
const root = await navigator.storage.getDirectory();

// test.txt を生成して FileSystemFileHandle オブジェクトを取得
const fh = await root.getFileHandle('test.txt');

// FileSystemWritableFileStream オブジェクトを取得
const stream = await fh.createWritable({ keepExistingData: true });

// 20 バイト目から "Meta    " を書き込む
await stream.write({ type: 'write', position: 20, data: 'Meta    ' });

// ファイルを閉じる
await stream.close();

// ファイルを読み取る
const file = await fh.getFile();
const text = await file.text();
console.log(text); // "Google    Amazon    Meta      Apple     Microsoft "

ファイルを切り詰める

生成したファイルを所定のバイト数できりつめるには、FileSystemWritableFileStream オブジェクトの write() メソッドを使う方法と truncate() メソッドを使う方法があります。次のコードは、いずれも 20 バイトで切り詰めます。

await stream.write({ type: 'truncate', size: 20 });
await stream.truncate(20);

前述のサンプルで生成した test.txt は 50 バイトのテキストファイルですが、これを 20 バイトに切り詰めてみましょう。

// ルートの FileSystemDirectoryHandle オブジェクト
const root = await navigator.storage.getDirectory();

// test.txt を生成して FileSystemFileHandle オブジェクトを取得
const fh = await root.getFileHandle('test.txt');

// FileSystemWritableFileStream オブジェクトを取得
const stream = await fh.createWritable({ keepExistingData: true });

// テキストファイルを 20 バイトに切り詰める
await stream.truncate(20);

// ファイルを閉じる
await stream.close();

// ファイルを読み取る
const file = await fh.getFile();
const text = await file.text();
console.log(text); // "Google    Amazon    "

Web Workers でのファイルへのデータ書き込み

Web Workers の専用ワーカー (Dedicated worker) のワーカースレッドでも Origin Private File System を利用することができます。しかし、ファイルへのデータ書き込みの方法に関しては、前述のメインスレッドでの方法とは異なりますので、解説します。

現在のところ、Safari はワーカースレッドでしかファイルへデータを書き込むことができません。もし Safari もサポートしたいなら、今のところ、データ書き込みの処理だけはワーカースレッドで実行する必要があります。もちろん、Chrome や Edge でも動作します。

次のサンプルは、ワーカースレッドでファイルを新規に生成し、ページがロードされる都度、現在日時をファイルに書き込みます。その後はメインスレッドでファイルを読み取ります。

まずはワーカースレッドとなるスクリプトをご覧ください。ファイル名は worker.js とします。

// メインスレッドからメッセージを受け取ったときの処理
self.onmessage = function (event) {
  if (event.data === 'set') {
    setNow();
  }
};

async function setNow() {
  // ルートの FileSystemDirectoryHandle オブジェクト
  const root = await navigator.storage.getDirectory();

  // now.txt を生成して FileSystemFileHandle オブジェクトを取得
  const fh = await root.getFileHandle('now.txt', { create: true });

  // 現在日時文字列の ArrayBuffer を生成
  const now = (new Date()).toLocaleString(); // 2022/6/8 19:51:27
  const encoder = new TextEncoder();
  const buffer = encoder.encode(now);

  // FileSystemSyncAccessHandle オブジェクトを生成して書き込み
  const ah = await fh.createSyncAccessHandle();
  const size = ah.write(buffer, { at: 0 });
  await ah.truncate(size);
  await ah.flush();
  await ah.close();

  // メインスレッドへデータを送信
  self.postMessage('set-completed');
}

ワーカースレッドでは、メインスレッドから "set" というメッセージを受け取ったら、setNow() 関数を実行します。setNow() 関数では、現在日時を now.txt というファイルに書き込みます。その書き込むコードをご覧いただくとお分かりの通り、前述の FileSystemWritableFileStream オブジェクトを使う方法とは全く異なります。

ポイントをまとめると、まず now.txt の FileSystemFileHandle オブジェクト(変数 fh)を取得します。次に、前述の FileSystemWritableFileStream オブジェクトの代わりに、FileSystemFileHandle オブジェクト(変数 fh)の createSyncAccessHandle() メソッドを使って、FileSystemSyncAccessHandle オブジェクト(変数 ah)を生成しています。このオブジェクトは専用ワーカー内でしか生成することができません。

データをファイルに書き込むのは、FileSystemSyncAccessHandle オブジェクト(変数 ah)の write() メソッドですが、書き込むデータは BufferSource オブジェクトでなければいけません。この点も、前述の FileSystemWritableFileStream オブジェクトの write() メソッドと異なります。

FileSystemSyncAccessHandle オブジェクト(変数 ah)の write() メソッドは同期メソッドです。非同期ではありませんので注意が必要です。その代わり、このメソッドが実行されると書き込みが完了したデータのサイズをバイトで返します(変数 size)。

今回は書き込むたびにデータサイズが異なるため、書き込んだデータサイズでファイルを切り詰めています。ファイルを入り詰めるには、FileSystemSyncAccessHandle オブジェクト(変数 ah)の truncate() メソッドを使います。最後に、flush() メソッドと close() メソッドを呼び出して終了です。

では、ワーカースレッドを呼び出すメインスレッドのスクリプトを見てみましょう。

// ワーカースレッドを生成
const worker = new Worker('worker.js');

// ワーカースレッド側からのメッセージを受信したときの処理
worker.onmessage = async (event) => {
  if (event.data === 'set-completed') {
    // ルートの FileSystemDirectoryHandle オブジェクト
    const root = await navigator.storage.getDirectory();

    // now.txt の FileSystemFileHandle オブジェクトを取得
    const fh = await root.getFileHandle('now.txt');

    // now.txt を読み取る
    const file = await fh.getFile();
    const text = await file.text();
    console.log(text); // 2022/6/8 19:51:27
  }
};

// ワーカースレッドに現在日時をファイルに書き込む命令を送信
worker.postMessage('set');

このスクリプトの最後で、ワーカースレッドに対して "set" というメッセージを送ります。先ほどご覧いただいた通り、ワーカースレッドでは "set" というメッセージを受け取ると、now.txt に現在日時を書き込んで "set-completed" というメッセージを返します。

"set-completed" というメッセージを受信したら、now.txt の内容を読み取って、コンソールに出力しています。

このサンプルではテキストを扱いましたが、もちろんバイナリーデータも扱えます。ここでは BufferSource オブジェクトの詳細は割愛します。

API のおさらい

これまで解説してきた File System Access API のメソッドやプロパティを整理しましょう。一通り使い方が分かっていれば、ここだけ見れば使いこなせるでしょう。

ルートディレクトリ
directoryHandle = await navigator.storage.getDirectory()
ルートディレクトリのディレクトリハンドル FileSystemDirectoryHandle オブジェクトを返します。
ディレクトリハンドル FileSystemDirectoryHandle オブジェクト
directoryHandle.kind
文字列 "directory" を返します。
directoryHandle.name
ディレクトリ名を返します。
fileHandle = await directoryHandle.getFileHandle(name)
fileHandle = await directoryHandle.getFileHandle(name, { create: true })
該当のディレクトリの直下から指定のファイル名の FileSystemFileHandle オブジェクトを取得します。create: true を指定すると、もし指定のファイル名のファイルがなければ新規に空のファイルを生成します。
subdirHandle = await directoryHandle.getDirectoryHandle(name)
subdirHandle = await directoryHandle.getDirectoryHandle(name, { create: true })
該当のディレクトリの直下から指定のディレクトリ名の FileSystemDirectoryHandle オブジェクトを取得します。create: true を指定すると、もし指定のディレクトリ名のディレクトリがなければ新規にディレクトリを生成します。
await directoryHandle.removeEntry(name)
await directoryHandle.removeEntry(name, { recursive: true })
該当のディレクトリの直下から指定のファイル名またはディレクトリ名を持つファイルまたはディレクトリを削除します。recursive: true を指定すると、再帰的にディレクトリの中身も含めて削除します。
path = await directoryHandle.resolve(child)
childFileSystemFileHandle または FileSystemDirectoryHandle オブジェクト。該当のディレクトリから見て、指定のファイルまたはディレクトリのパスを構成する階層を配列で返します。
await directoryHandle.move(newName)
ディレクトリ名を newName にリネームします。
ディレクトリハンドル FileSystemDirectoryHandle オブジェクトのイテレーション
for await (let [name, handle] of directoryHandle.entries()) { ... }
for await (let handle of directoryHandle.values()) { ... }
for await (let name of directoryHandle.keys()) { ... }
該当のディレクトリ直下のファイルやディレクトリの繰り返し処理を行います。
ファイルハンドル FileSystemFileHandle オブジェクト
fileHandle.kind
文字列 "file" を返します。
fileHandle.name
ファイル名を返します。
file = await fileHandle.getFile()
該当のファイルを表す File オブジェクトを返します。
stream = await fileHandle.createWritable()
stream = await fileHandle.createWritable({ keepExistingData: true })
FileSystemWritableFileStream オブジェクトを返します。デフォルトでは既存ファイルも空のファイルとして扱います。keepExistingData: true を指定することで既存ファイルのデータを事前に読み込みます。
await fileHandle.move(newName)
ファイル名を newName にリネームします。
ファイル書き込みストリーム FileSystemWritableFileStream オブジェクト
await stream.write(data)
await stream.write({ type: "write", data: data })
現在のファイルカーソル位置から data を書き込みます。data は文字列または Blob オブジェクト。
await stream.write({ type: "write", position: position, data: data })
指定のファイルカーソル位置 position から data を書き込みます。
await stream.write({ type: "seek", position: position })
カーソル位置を position に移動します。
await stream.write({ type: "truncate", size: size })
該当のファイルをサイズ size バイトに切り詰めます。
await stream.seek(position)
カーソル位置を position に移動します。
await stream.truncate(size)
該当のファイルをサイズ size バイトに切り詰めます。
専用ワーカーだけで使えるメソッド
accessHandle = await fileHandle.createSyncAccessHandle()
FileSystemSyncAccessHandle オブジェクトを生成します。
size = accessHandle.write(buffer, { at: position })
指定のファイルカーソル位置 position から buffer を書き込みます。
await accessHandle.truncate(size)
該当のファイルをサイズ size バイトに切り詰めます。
await accessHandle.flush()
書き込みデータをフラッシュします
await accessHandle.close()
ファイルと閉じます。

まとめ

今回は File System Access API のうち、Chrome, Edge, Safari でサポートされている Origin Private File System に限定して解説しましたが、ファイルにデータを書き込む方法がメインスレッドとワーカースレッドとで異なるなど、使い勝手が悪いところが残っています。仕様策定もまだ途中ですから、その進捗に期待したいところです。

今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。

Share