バイナリデータをファイルに保存する

Recipe ID: fs-004

画像データ、PDF、圧縮ファイル、あるいは独自のバイナリ形式のデータをファイルシステムに保存する方法を紹介します。
テキストデータを保存する writeTextFile とは異なり、writeFile 関数を使用して Uint8Array 形式のデータを書き込みます。

前提条件

このレシピを使用するには、@tauri-apps/plugin-fs プラグインが必要です。

1. プラグインのインストール

プロジェクトのルートディレクトリで以下のコマンドを実行してプラグインを追加します。

npm run tauri add fs

2. Permissions (権限) の設定

src-tauri/capabilities/default.json に以下の権限を追加します。

{
  "permissions": [
    ...,
    {
      "identifier": "fs:allow-write-file",
      "allow": [{ "path": "$APPLOCALDATA/**" }, { "path": "$DOWNLOAD/**" }]
    }
  ]
}

※ 上記の例では $APPLOCALDATABaseDirectory.AppLocalData)および $DOWNLOADBaseDirectory.Download)配下の書き込みを許可しています。アクセスするディレクトリに応じて、パス変数を指定してください。スコープが設定されていないディレクトリにはアクセスできません。

1. フロントエンドから作成する (TypeScript)

@tauri-apps/plugin-fswriteFile 関数を使用します。

基本的なバイナリ書き込み

Uint8Array を作成してファイルに保存する基本的な例です。

import { writeFile, BaseDirectory } from '@tauri-apps/plugin-fs';

async function saveBinaryData() {
  // 例: 0x00 から 0x04 までのバイト列
  const buffer = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04]);

  try {
    await writeFile('data.bin', buffer, {
      baseDir: BaseDirectory.AppLocalData,
    });
    console.log('バイナリファイルの保存に成功しました');
  } catch (err) {
    console.error('保存エラー:', err);
  }
}

Web上の画像をダウンロードして保存する

fetch で取得した画像データを ArrayBuffer として受け取り、それを Uint8Array に変換して保存する実践的な例です。

注意(CORSエラーについて):
通常のブラウザ fetch API を使用して外部サーバーの画像をダウンロードしようとすると、CORS (Cross-Origin Resource Sharing) ポリシーによりブロックされる場合があります。
これを回避するには、Tauri の HTTP プラグイン (@tauri-apps/plugin-http) を使用することをお勧めします。このプラグインは Rust バックエンド経由でリクエストを行うため、CORS の制約を受けません。

1. プラグインの追加: npm run tauri add http
2. 権限の設定: src-tauri/capabilities/default.json に以下の権限を追加します。URLはアクセス先に合わせて変更してください(ワイルドカード * が使用可能)。

{
  "permissions": [
    // ... 他の権限
    {
      "identifier": "http:default",
      "allow": [{ "url": "https://*" }]
    }
  ]
}

3. コードの変更: import { fetch } from '@tauri-apps/plugin-http'; を追加して使用する

// @tauri-apps/plugin-http を使用する場合の例
// import { fetch } from '@tauri-apps/plugin-http';
import { writeFile, BaseDirectory } from '@tauri-apps/plugin-fs';

async function downloadAndSaveImage(imageUrl: string) {
  try {
    // 画像をフェッチ (plugin-http の fetch を推奨)
    const response = await fetch(imageUrl);
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

    // ArrayBuffer として取得
    const buffer = await response.arrayBuffer();
    
    // Uint8Array に変換
    const finalData = new Uint8Array(buffer);

    // ファイルに保存 (download.jpg)
    await writeFile('download.jpg', finalData, {
      baseDir: BaseDirectory.Download, // ダウンロードフォルダに保存
    });
    
    console.log('画像のダウンロードと保存が完了しました');
  } catch (err) {
    console.error('ダウンロード保存エラー:', err);
  }
}

2. バックエンドから作成する (Rust)

Rust では Vec<u8> を受け取ってファイルに書き込みます。

バイナリデータの書き込み

use std::fs;

// フロントエンドからは Uint8Array (または number[]) として渡されます
#[tauri::command]
fn save_binary_file(path: String, data: Vec<u8>) -> Result<(), String> {
    // std::fs::write は &[u8] を受け取れるため、Vec<u8> をそのまま渡せます
    fs::write(path, data).map_err(|e| e.to_string())
}

画像のダウンロードと保存 (Rust側で行う場合)

HTTPクライアント (reqwest等) を使うのが一般的ですが、ここではデータを受け取って保存する部分を示します。

use tauri::Manager;
use std::fs;

#[tauri::command]
fn save_image_to_app_data(app: tauri::AppHandle, image_data: Vec<u8>) -> Result<(), String> {
    let app_data_dir = app.path().app_data_dir()
        .map_err(|e| e.to_string())?;

    if !app_data_dir.exists() {
        fs::create_dir_all(&app_data_dir).map_err(|e| e.to_string())?;
    }

    let file_path = app_data_dir.join("saved_image.jpg");
    fs::write(file_path, image_data).map_err(|e| e.to_string())
}

フロントエンドから呼び出す際は、Uint8Array (または number[]) を渡します。Tauri が自動的に Vec<u8> に変換します。

import { invoke } from '@tauri-apps/api/core';

async function saveImage(data: Uint8Array) {
  try {
    // Rust 側の save_image_to_app_data コマンドを呼び出し
    // 引数名はキャメルケース (imageData) に自動変換されます
    await invoke('save_image_to_app_data', { imageData: data });
    console.log('Image saved successfully via Rust command');
  } catch (error) {
    console.error('Failed to save image:', error);
  }
}

補足

  • 上書き: writeFilefs::write は、ファイルが既に存在する場合、内容を完全に上書きします。
  • データ型: Javascript/TypeScript でバイナリを扱う際は Uint8Array が一般的です。Rust 側では Vec<u8> として受け取ります。
  • 権限の分離: フロントエンドで fs プラグインを使用する場合、テキストファイルとバイナリファイルで権限が分かれています(allow-write-text-file vs allow-write-file)。Rust から操作する場合はこれらの権限設定は不要です。