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
オブジェクトを取得します。ここでは変数 root
に FileSystemDirectoryHandle
オブジェクトを代入しています。
ディレクトリハンドルとは、ディレクトリの情報を読み取ったり操作するためのオブジェクトのことで、このオブジェクトに組み込まれているプロパティやメソッドを使って、そのディレクトリを扱います。ここではディレクトリハンドルの 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
オブジェクトのどちらも、次のプロパティを持っています。
プロパティ | 型 | 説明 |
---|---|---|
kind | String | 対象がファイルなら "file" 、ディレクトリなら "directory" |
name | String | ファイル名またはディレクトリ名 |
先ほどのサンプルでは 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()
メソッドの第一引数にはファイル名またはディレクトリ名を指定します。第二引数には recursive
に true
をセットしたオブジェクトを指定していますが、これは削除したいディレクトリの中にファイルやディレクトリがあったとしてもすべて削除します。
この第二引数ですが、ファイルを削除する場合に指定したとしても無視されるだけでエラーにはなりません。また、第一引数に存在しないファイル名やディレクトリ名を指定してもエラーにはなりません。
ファイルのパスを取得
ディレクトリハンドルである 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)
child
はFileSystemFileHandle
または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 に限定して解説しましたが、ファイルにデータを書き込む方法がメインスレッドとワーカースレッドとで異なるなど、使い勝手が悪いところが残っています。仕様策定もまだ途中ですから、その進捗に期待したいところです。
今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。