FileAccessAPIは限られたブラウザでのみ使用可能。

ブツ

eyecatch0.png
eyecatch1.png
eyecatch2.png
eyecatch3.png

前回まで

情報源

FileSystemAccessAPI ブラウザ対応表 - mozillaをみると、FirefoxやIEは未対応。IEはもうサポートが切れたからいいとして、Firefoxが残念。Chrome、Edge, Opera, Safariなら使えるが、バージョンが高くないと使えない。

私のマシンはラズパイでブラウザがChromium 92。FileSystemAccessAPI ブラウザ対応表 - mozillaによるとChrome 86以降で使えるらしい。セーフ。というわけで使ってみる。

ポイント

  • Chromium 86以降でないと使えない
  • FileSystemAccessAPI - mozillaはボタンを押すなど、何らかのユーザ操作が必要であり自動化できない
    • さらにそのファイルを操作するために許可を出すUI操作が必要
    • さらにそのファイルを読み込むためのパスを指定するためのディレクトリ選択操作が必要

無断でローカルファイルを操作されてしまうセキュリティ的なリスクがある。それを回避するための措置として、ユーザ操作を必要としている。

えー、それじゃ自動化できないじゃん。ダウンロードしていたときより逆に手間がかかるようになってしまうのでは? とくに毎回パス選択とか面倒すぎるんですけど。そこだけはIndexedDBを使って保存すれば省略できるかもしれない。でも、それ以外のボタン押しと許可については省略できないっぽい。

stackoverflowによると、IndexedDBでディレクトリパスを保存することはできないらしい。IndexedDBは文字列でなく型として保存してくれるためイケるかと思ったが、セキュリティ的な理由でダメみたい。

とにかく実際にやってみる。

コード抜粋

<script src="lib/sql.js/1.6.2/sql-wasm.min.js"></script>
class Sqlite3DbFile {
    #dirHandle = null
    constructor() {
        //this.PATH_WASM = `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.7.0`
        this.PATH_WASM = `lib/sql.js/1.7.0`
        this.name = 'users.db'
    }
    get DirHandle() { return this.#dirHandle }
    async write() {
        const dirHandle = await this.#getDirectoryPicker()
        if (!dirHandle) return
        try {
            const fileHandle = await dirHandle.getFileHandle(this.name, {
                create: true,
            })
            const writable = await fileHandle.createWritable()
            await writable.write(this.db.export())
            await writable.close()
        } catch (e) {
            console.error(e)
        }
    }
    async read() {
        const dirHandle = await this.#getDirectoryPicker()
        if (!dirHandle) { return }
        console.debug(dirHandle)
        const fileHandle = await dirHandle.getFileHandle(this.name)
        const file = await fileHandle.getFile()
        const arrayBuffer = await file.arrayBuffer()
        const dbAsUint8Array = new Uint8Array(arrayBuffer)
        if (!this.SQL) {
            //this.SQL = await initSqlJs({locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.6.2/${file}`})
            this.SQL = await initSqlJs({locateFile: file => `${this.PATH_WASM}/${file}`})
        }
        this.db = new this.SQL.Database(dbAsUint8Array)
        document.getElementById(`update`).disabled = false
        return this.db
    }
    async #getDirectoryPicker() {
        if (this.#dirHandle) { return this.#dirHandle }
        try {
            this.#dirHandle = await window.showDirectoryPicker()
            return this.#dirHandle
        } catch (e) {
            console.error(e)
        }
    }
}

今回はなぜか1.7.0で実行できた。何が違うんだ? まいっか。動けばいいや。

これをボタンのクリックやらドラッグ&ドロップやらのイベントにより実行させる。

以下のような手順で、ファイルに上書きできたことが確認できる。

  1. DEMOページにアクセスする
  2. ダウンロードボタンをクリックする
  3. 2のZIPファイルを展開しアドレス.dbファイルをドラッグ&ドロップでロードする
  4. ディレクトリ選択ダイアログが出る(ダウンロードしたusers.dbファイルが存在するローカルのディレクトリパスを選ぶ)
  5. 読み取り許可ダイアログが出るので許可する
    eyecatch1.png
  6. ytyaruhogeのレコードが表示される
  7. ファイル上書きボタンをクリックする(ytyaruレコードが追記される)
  8. もう一度3を行う
    eyecatch3.png
  9. ytyaruhogeytyaruのレコードが表示される
  10. 末尾にytyaruレコードが追加されていたことが確認できた

ローカルにダウンロードしたファイルなのに、DEMOページのファイル上書きボタンに反応して、レコードが追記された。これが今回のポイント。なんと、ローカルファイルを読み書きできてしまった。

頑張ればネイティブアプリのようなことができるかも。けれど操作が面倒だし、使えるブラウザも限られているため、今の所は微妙かな。

ネイティブアプリをHTML,CSS,JSで作るならElectronなど専用のライブラリだかフレームワークだかを使って作ったほうがいいのだろう。たしかあれはブラウザのHTML描画エンジンやJavaScript実行エンジンやらをファイルに含めることでネイティブアプリとして稼働するようになるんだったかな?

前にらいうさんがExpo + NativeBaseでプロジェクト作成という記事をあげてらしたので参考になるかも。でもExpoはReactのことを知っている必要があるっぽい。私にはまだ無理ぽ。

というわけで、今はダウンロードやFileSystemAccessAPI - mozillaで妥協しておく。