TauriでRustからファイル操作をしてみよう📝

本記事では、Tauri製アプリでRustからファイルを操作し、データを保存する方法について紹介します。
リボンのファイルからデータを読み込んできて・・・っていうのは紹介しないのであしからず。
なお、Rust・Node.jsの環境が入っていることは前提条件とします。

# Tauriを使ってみよう
皆さんアプリ作ってますか？

最近は、敢えてローカル環境にインストールせずサイトとしてアプリを動作させるWebアプリが主流ですが、ここ数年で逆にWebアプリの技術を利用したネイティブアプリ用のフレームワークも出てきています。
特にElectronやReact Nativeが流行っていますが、Rustでも同様のフレームワークが出てきました。それが**Tauri**です！

# What's Tauri ！？
TauriはRust製のフレームワークで、WebViewを通してUIの表示させてコマンドという形でRustの処理を呼び出すと言ったことが可能なので早い動作が実現できるフレームワークです！
特に魅力なのはReactやSvelte、Rust製のYewなんかでもUIの構築ができるそうで、かなりWebを触ったことがある人は入りやすいのではないでしょうか！？

また、クロスコンパイルや最近ではモバイルにも対応したので多用途での利用が期待されているフレームワークです（だと思いたいです）。

さらに、Pluginが存在しておりReactやSvelteなどUI部分でほとんど処理が書けるため、Rustを学ぶという遥か高い壁を乗り越えなくて済むというメリットもあります。~~勿論Rustで書くよなぁ！？~~

ちなみに私はTauriを利用してこのような掲示板を作成しました！
https://news.yahoo.co.jp/articles/f752a2040e8bc9603576f174a12e7a05ec670f84

# Rustでファイル操作
いくらTSやJSで書けるとはいえ、Rust言語が好きでRustで書きたいんだ！という需要もあると思います（かく言う私がそうです）。
そして、私が個人的に作成しているアプリの中でなかなか困ったわりに日本語記事が出てこなかったRustでのファイル操作について本記事では紹介します！

今回は、アプリのデータを保存する、Windowsだと
```plaintext
C:Users\hoge-user\AppData\Local\hoge.app
```
に該当する場所へのデータ保存を取り扱っていこうと思います！

## アプリの作成

では、アプリのプロジェクトを作成していきましょう！
まずtauri-cliとcreate-tauri-appをインストールします
```bash
cargo install tauri-cli create-tauri-app
```
それが完了したら次にプロジェクトを作成します
ここら辺は基本お好みで行きましょう～
```bash
cargo create-tauri-app
```
それが出来たら作成されたディレクトリに移って、UI部のパッケージをインストール！

動作確認のために実行してみましょう
```bash
cargo tauri dev
```

無事起動出来たら初期設定が完了です！

## データの保存先を取得
データの保存先は上記の通り
```plaintext
C:Users\hoge-user\AppData\Local\hoge.app
```
のディレクトリ以下に保存したいためAppDataのLocal内にあるアプリ用ディレクトリを取得したいです。
そのために、アプリの立ち上げ時にディレクトリを取得するコードをsrc-tauri/lib.rsに書いていきます！
```diff_rust:src-tauri/lib.rs
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

+ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
+     let base_dir = app.path().app_local_data_dir()?;
+     let file = "file.txt";
+     let path = format!("{}\\{}", base_dir.to_str().unwrap(), file);
+     println!("{}", &path);
+ 
+     Ok(())
+ }

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
+         .setup(setup)
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

```

これを書いて実行すると、コンソール上に
```plaintext
C:Users\hoge-user\AppData\Local\hoge.app\file.txt
```
が出力されているはずです！

これで、あとはfsでファイル操作をして・・・って言いたいところなんですが、このままではsetupでしかファイル操作が出来ないので次にRustで書くコマンド上でファイル操作をやってみる方法を共有します！

## コマンド上でのファイル操作
コマンド上でファイルを操作する場合、setupで取得したパスをコマンドの処理でも使いたいですよね。そこで活躍するのが**State**！
アプリ上で状態を共有するために使用するのがこのStateになります。
感のいい方はお気づきでしょうが、取得できたパスをStateで共有して使いまわすということを行います。
では、そのやり方を見ていきましょう！

まずは、何を共有したいのかを定義するためにAppState構造体を作成します！
一番上にpath変数を持つAppDataを定義しましょう。
```rust:src-tauri/lib.rs
struct AppData {
    path: String,
}
```

そして、ファイルが作成済みであればいいですし、なければファイルを作成する処理を記述した初期化用のnew関数をAppData構造体に定義しましょう。
```rust:src-tauri/lib.rs
impl AppData {
    fn new(path: &str) -> std::result::Result<Self, Box<dyn std::error::Error>> {
        std::fs::File::create(path)?;
        Ok(AppData {
            path: path.to_string(),
        })
    }
}
```

これで、パスを共有する準備は整いました！
ここからはパスを共有して実際にファイルを操作していきましょう！

まず、せっかく共有するデータを定義した構造体を作ったのでsetup関数に、アプリへ共有データを登録して、欲しい時に共有できるよう処理を書いていきましょう
```diff_rust:src-tauri/lib.rs
+ use tauri::Manager;

fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
    let base_dir = app.path().app_local_data_dir()?;
    let file = "file.txt";
    let path = format!("{}\\{}", base_dir.to_str().unwrap(), file);
    println!("{}", &path);
+
+     app.manage(AppData::new(&path)?);

    Ok(())
}
```

これでデータを共有することが可能になったので、コマンドの方でデータを受け取ってファイル操作をする処理を、面倒なので生成されているgreet関数を変更して書いていきましょう！
```diff_rust:src-tauri/lib.rs
#[tauri::command]
- fn greet(name: &str) -> String {
-     format!("Hello, {}! You've been greeted from Rust!", name)
+ fn greet(state: tauri::State<AppData>, name: &str) -> Result<String, String> {
+     let byte_data = std::fs::read(&state.path).map_err(|e| e.to_string())?;
+     let mut data = String::from_utf8(byte_data).map_err(|e| e.to_string())?;
+     data.push_str(&format!(", {}", name));
+     std::fs::write(&state.path, &data).map_err(|e| e.to_string())?;
+     Ok(data)
}
```
先述のStateは、greet関数で引数として受け取っているStateのことで、Stateの型引数として渡した型に合致した値を取得してくれます。
ちなみに、この書き方以外にもAppHandleを受け取ってそこからStateを取得することも可能ですがそれはまた別の話・・・

そして、あとは普段のファイル操作と同じ要領で記述していけばファイル操作が行えます！
この処理は、受け取った名前をファイルの末尾に追加していくだけの面白味のない処理ですのでお気になさらず（）

## 動作確認
では、実際に動かして確認してみましょう！
まずは、アプリ上の動作から！

![スクリーンショット 2024-12-16 041813.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474462/912c63d8-8018-bd01-8e26-6fee658693bf.png)

無事に出力されていますね！
追加していく動作だったのでもう一回やってみましょう。

![スクリーンショット 2024-12-16 041948.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474462/3c87eb4b-7f71-f3b4-1892-698600448fe5.png)

後ろに連結されていることが確認できました！
では、今度は実際にファイルが保存されているかをエクスプローラーから覗いてみましょう！

![スクリーンショット 2024-12-16 042132.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474462/892a3a0f-e531-0601-2213-9e92d511618a.png)


file.txtが確認できましたね！
そしてその中身は・・・？

![スクリーンショット 2024-12-16 042501.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474462/b6c079b2-ad91-10cc-f074-bc25ae741fa7.png)

見事入力したデータがファイルに出力されていることが確認できました！
これでやりたいこと完了です！

# まとめ
本記事では、TauriでRustからファイル操作をする方法について紹介していきました！

アプリとしてデータを保存しておくために、JSONであったりCSVであったりいろいろなニーズがあり、かく言う私もJSONを扱いたかったので今回調べて（というか記事がないのでほぼ独力で）やってみました！

短時間でやったことをまとめて書いたので、分からない点や抜けがあればご指摘いただきたいですが、少しでも参考になれば幸いです！