TAURIでファイルダイアログを開き、ローカルファイルにアクセスするには

# TAURIとは

https://tauri.app/

マルチプラットフォームのGUIフレームワークで、
フロントエンドをHTML+JavaScriptで、バックエンドをRustでそれぞれ記述できます。　
Electronのようなものと表現すればよいでしょうか。

# ローカルファイルを取り扱いたい

GUIのスタンドアロンアプリであることがWebアプリと最も違うのは、ローカルファイルへのアクセスです。
一方フロントエンドのHTML/JSでローカルファイルを取り扱うのが難しいことも考えられます。

本記事では以下のような手順を考えます。

1. メニューから「ファイルを開く」を選択してダイアログを開く。
1. ダイアログからファイルを選択する。
1. 選択されたファイルをフロントエンド側で読み込む。

この場合、以下の2パターンが考えられます。

- バックエンド側でメニュー選択からファイルダイアログを経由してファイルパスを取得->フロントエンドに通知する。
- バックエンド側のメニューが選択されたことを->フロントエンド側からファイルダイアログを経由してファイルパスを取得する。

これは、メニュー制御はバックエンド側の担当となるためです。
バックエンドとフロントエンド間の通信も考慮する必要があります。

メニューの取扱については公式のガイドを参照してください。

https://tauri.app/v1/guides/features/menu/

フロントエンド<->バックエンド間の通信はeventを利用するのがいいでしょう。
こちらも公式ガイドが用意されています。

https://tauri.app/v1/guides/features/events

記事執筆時点では、詳細なチュートリアルが公式になかった以下の２つ

- ダイアログの開き方
- フロントエンドからローカルファイルに直接アクセスするための手順

を中心に取り扱います。

なお、Windows10ネイティブ環境で動作確認を行っています。


# ダイアログを開くための設定

`tauri.conf.json`の`allowlist`に設定が必要です。

```json
  "tauri": {
    "allowlist": {
      "all": true,
    }
  }
```

となっていればそのまま開くことができます。個別設定の場合は以下のようにします。

```json
  "tauri": {
    "allowlist": {
      "dialog": {
        "all": true,
        "open": true,
        "save": true
      }
    }
  }
```

ちなみに…

- `all: true`か`dialog`のどちらかを設定していないと、Rust側でダイアログ関連の処理を入れているとコンパイルそのものに失敗します。
- `dialog/all: false` の場合ダイアログを開けません。
- `dialog/all: false` でも `dialog/open: true` の場合ファイルを開くダイアログは開けます(メッセージは開けません)。
- `dialog/all: true` の場合 `dialog/open: false` としてもファイルを開くダイアログは開けます(all優先？)。



# ダイアログの開き方(Rust)

`tauri::api::dialog`を使用します。

https://docs.rs/tauri/latest/tauri/api/dialog/index.html

（ちなみに、`tauri_api::dialog`と`tauri_dialog`というのも存在していますが、
どちらも`tauri_dialog`を利用しており、既にメンテナンスされていない旨ドキュメントに記述されています。）


## メッセージダイアログの開き方

メッセージダイアログを開くための関数が用意されています。
利用方法はドキュメントに詳しく書いてあります。


## ファイルダイアログの開き方

`FileDialogBuilder`を経由して開くことができます。

non-blockingになっていて、
ダイアログを開いている間も元のウィンドウに戻って作業することができます。

```rust
use tauri::api::dialog::FileDialogBuilder;

// non-blockingの例
FileDialogBuilder::new().pick_files(move |file_paths| {
    match file_paths {
        Some(v) => {
            let mut pathstr = "".to_string();
            // 選択されたファイルのうち最初のファイルだけを対象とする
            match v.first() {
                Some(path) => {
                    match path.to_str() {
                        Some(s) => {
                            println!("{:?}", s);
                            pathstr = s.to_string();
                        }
                        _ => {}
                    }
                }
                _ => {}
            }
            // 親ウィンドウに対してファイルパスをイベントで通知
            let window = event.window();
            window.emit("pick_files", Payload {
                file_paths: pathstr
            });
        }
        _ => {}
    }
})
```

一方、blockingも用意されています。
この場合はダイアログを開くとメインウィンドウの処理が止まります。


```rust
use tauri::api::dialog::blocking::FileDialogBuilder;

// blockingの例
let file_paths = FileDialogBuilder::new().pick_files();
match file_paths {
    Some(v) => {
        let mut pathstr = "".to_string();
        // 選択されたファイルのうち最初のファイルだけを対象とする
        match v.first() {
            Some(path) => {
                match path.to_str() {
                    Some(s) => {
                        println!("{:?}", s);
                        pathstr = s.to_string();
                    }
                    _ => {}
                }
            }
            _ => {}
        }
        // 親ウィンドウに対してファイルパスをイベントで通知
        let window = event.window();
        window.emit("pick_files", Payload {
            file_paths: pathstr
        });
    }
    _ => {}
} 
```


どちらの場合であっても、取得できるのはファイルパスであることに注意してください。
この後の処理は以下のような方法が考えられます。

- このままバックエンド側で処理し、eventを経由してフロントエンドに通知する。
- フロントエンドにeventを経由してファイルパスを通知し、フロントエンドから直接アクセスする。

どちらにしても、バックエンドからフロントエンドにeventを経由して通知を行う必要があります。


# ダイアログの開き方(JavaScript)

こちらはapiドキュメントに詳しい使い方が書いてあるのでそちらを参照してください。

https://tauri.app/v1/api/js/modules/dialog/

こちらを利用した場合はブロックされ、ダイアログを開いているとウィンドウ自体にフォーカスできなくなります。


# フロントエンドからローカルファイルに直接アクセスする方法(HTML,JavaScript)

動画ファイルなど、フロントエンドから直接アクセスしたほうが取り扱いやすい場合もあります。
この場合`tauri.conf.json`に次の2つの設定がまず必要になります。

- `security/csp`に`asset.localhost`に対する許可の記述を追加
- `allowlist/protocol`に`asset`関連の記述を追加

その上で、`convertFileSrc`という関数を経由してURIに変換します。

https://tauri.app/v1/api/js/modules/tauri/#convertfilesrc

APIドキュメントは断片的にしか書かれていないため、外部サイトの記述も参考にしています。


## CSP(Content Security Policy)

CSPに関する詳細は例えば以下のページを参考にしてください。

https://developer.mozilla.org/ja/docs/Web/HTTP/CSP

ローカルファイルへアクセスするためには以下の記述が必要です。　

```json
"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost"
```

なお、この記述がないか間違っている場合はcsp errorというエラーが返ってきます。F12でデバッグコンソールを見るとわかります。


## protocolの追加

cspを設定しただけだとローカルファイルのURIにアクセスしても403エラーが返ってきます。
もう一箇所、`allowlist`にassetプロトコルの記述を追加します。

```json
  "tauri": {
    "allowlist": {
      "all": true,
      "protocol": {
        "asset": true,
        "assetScope": ["**"]
      }
    }
  }
```

これでローカルファイルへの直接アクセスができます。
なお、`convertFileSrc`で変換されたURIは
`https://asset.localhost/<ローカルファイルパス>`のようになります。



# 参考文献

https://zenn.dev/bpk_t/scraps/4f9523470ea151

https://github.com/tauri-apps/tauri/blob/dev/examples/api/src-tauri/tauri.conf.json

https://qiita.com/namn1125/items/8153f7c0881b7d4127ed#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%81%AE%E5%8B%95%E7%94%BB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%95%E3%81%9B%E3%82%8B

https://github.com/anotherhollow1125/TauriReactPlayer/blob/v0.1.0/src-tauri/tauri.conf.json