Tauriの基礎 - ファイルシステム

提供: MochiuWiki : SUSE, EC, PCB

概要

Tauri v2のファイルシステムプラグイン (fs) は、デスクトップアプリケーションからローカルファイルシステムに安全にアクセスするための機能を提供する。
このプラグインは、テキストファイルやバイナリファイルの読み書き、ディレクトリの操作、ファイルの存在確認等の基本的なファイル操作をサポートしている。

セキュリティを考慮して、デフォルトではアプリケーション専用のディレクトリ内のみにアクセスが制限されており、Capabilities設定でスコープを拡張することができる。

ベースディレクトリという概念により、プラットフォーム間で異なるファイルパスを抽象化し、クロスプラットフォームなファイル操作を容易にしている。

主な機能には、readTextFilewriteTextFile によるテキストファイルの読み書き、readDir によるディレクトリ一覧の取得、mkdirremove によるディレクトリ操作がある。

非同期APIとして設計されており、React等のモダンなフロントエンドフレームワークと組み合わせて使用するのに適している。


基本的なファイル操作

インストール

ファイルシステムプラグインを使用するには、まずインストールを行う。

npm run tauri add fs


テキストファイルの読み込み

readTextFile 関数を使用して、テキストファイルの内容を読み込むことができる。

 import { readTextFile, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // ファイルを読み込む関数
 async function loadConfig(): Promise<string> {
   try {
     // アプリケーション設定ディレクトリから設定ファイルを読み込む
     const content = await readTextFile('config.json', {
       baseDir: BaseDirectory.AppConfig
     });
     console.log('設定を読み込みました:', content);
     return content;
   }
   catch (error) {
     console.error('設定ファイルの読み込みに失敗しました:', error);
     throw error;
   }
 }


テキストファイルの書き込み

writeTextFile 関数を使用して、テキストファイルに内容を書き込むことができる。

 import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // 設定を保存する関数
 async function saveConfig(config: object): Promise<void> {
   try {
     const content = JSON.stringify(config, null, 2);
 
     // アプリケーションデータディレクトリに設定ファイルを保存
     await writeTextFile('settings.json', content, {
       baseDir: BaseDirectory.AppData
     });
     console.log('設定を保存しました');
   }
   catch (error) {
     console.error('設定ファイルの保存に失敗しました:', error);
     throw error;
   }
 }


ファイルの存在確認

exists 関数を使用して、ファイルまたはディレクトリが存在するかどうかを確認できる。

 import { exists, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // 設定ファイルの存在を確認する関数
 async function checkConfigExists(): Promise<boolean> {
   const fileExists = await exists('config.json', {
     baseDir: BaseDirectory.AppConfig
   });
 
   if (fileExists) {
     console.log('設定ファイルが存在します');
   }
   else {
     console.log('設定ファイルが存在しません。デフォルト設定を使用します。');
   }
 
   return fileExists;
 }



ディレクトリ操作

ディレクトリの作成

mkdir 関数を使用して、新しいディレクトリを作成できる。

 import { mkdir, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // キャッシュディレクトリを作成する関数
 async function createCacheDir(): Promise<void> {
   try {
     // 再帰的にディレクトリを作成
     await mkdir('cache/images/thumbnails', {
       baseDir: BaseDirectory.AppCache,
       recursive: true  // 親ディレクトリも必要に応じて作成
     });
     console.log('キャッシュディレクトリを作成しました');
   }
   catch (error) {
     console.error('ディレクトリの作成に失敗しました:', error);
     throw error;
   }
 }


ディレクトリ内容の読み込み

readDir 関数を使用して、ディレクトリ内のファイルとサブディレクトリの一覧を取得できる。

 import { readDir, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // ドキュメントディレクトリの内容を一覧表示する関数
 async function listDocuments(): Promise<void> {
   try {
     const entries = await readDir('documents', {
       baseDir: BaseDirectory.AppData
     });
 
     for (const entry of entries) {
       if (entry.isFile) {
         console.log(`ファイル: ${entry.name}`);
       }
       else if (entry.isDirectory) {
         console.log(`ディレクトリ: ${entry.name}/`);
       }
     }
   }
   catch (error) {
     console.error('ディレクトリの読み込みに失敗しました:', error);
   }
 }


ファイルとディレクトリの削除

remove 関数を使用して、ファイルまたはディレクトリを削除できる。

 import { remove, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // 一時ファイルを削除する関数
 async function cleanupTempFiles(): Promise<void> {
   try {
     // 単一のファイルを削除
     await remove('temp/cache.tmp', {
       baseDir: BaseDirectory.Temp
     });
 
     // ディレクトリを再帰的に削除
     await remove('temp/old-cache', {
       baseDir: BaseDirectory.Temp,
       recursive: true  // 中身ごと削除
     });
 
     console.log('一時ファイルを削除しました');
   }
   catch (error) {
     console.error('削除に失敗しました:', error);
   }
 }



BaseDirectory (ベースディレクトリ)

BaseDirectoryは、ファイル操作の基準となるディレクトリを指定するための仕組みである。

これにより、プラットフォーム間で異なるパスを意識せずにファイル操作を行うことができる。

利用可能なBaseDirectoryの一覧

BaseDirectoryの一覧
定数 説明 Windowsのパス例 MacOS / Linuxのパス例
AppConfig アプリケーション設定ディレクトリ %APPDATA%/{app} ~/.config/{app}
AppData アプリケーションデータディレクトリ %APPDATA%/{app} ~/.local/share/{app}
AppLocalData ローカルアプリケーションデータ %LOCALAPPDATA%/{app} ~/.local/share/{app}
AppCache アプリケーションキャッシュディレクトリ %LOCALAPPDATA%/{app}/cache ~/.cache/{app}
AppLog アプリケーションログディレクトリ %LOCALAPPDATA%/{app}/logs ~/.local/state/{app}/logs
Resource アプリケーションリソースディレクトリ (実行ファイルと同階層) (アプリバンドル内)
Temp 一時ディレクトリ %TEMP% /tmp
Desktop デスクトップディレクトリ %USERPROFILE%/Desktop ~/Desktop
Document ドキュメントディレクトリ %USERPROFILE%/Documents ~/Documents
Download ダウンロードディレクトリ %USERPROFILE%/Downloads ~/Downloads
Picture ピクチャディレクトリ %USERPROFILE%/Pictures ~/Pictures
Public パブリックディレクトリ %PUBLIC% ~/Public
Video ビデオディレクトリ %USERPROFILE%/Videos ~/Videos
Audio オーディオディレクトリ %USERPROFILE%/Music ~/Music
Font フォントディレクトリ %LOCALAPPDATA%/Microsoft/Windows/Fonts ~/.fonts
Home ホームディレクトリ %USERPROFILE% ~
Runtime ランタイムディレクトリ (なし) $XDG_RUNTIME_DIR


{app} はアプリケーションの識別子で、tauri.conf.json ファイルの productName に基づく。

使用例

 import { readTextFile, writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // ユーザのドキュメントディレクトリにファイルを保存
 async function saveToDocuments(content: string): Promise<void> {
   await writeTextFile('my-app/export.txt', content, {
     baseDir: BaseDirectory.Document
   });
 }
 
 // デスクトップからファイルを読み込む
 async function loadFromDesktop(): Promise<string> {
   return await readTextFile('notes.txt', {
     baseDir: BaseDirectory.Desktop
   });
 }



パーミッションスコープ設定

ファイルシステムプラグインは、セキュリティのためにCapabilities設定でアクセス範囲を制御する。

デフォルトパーミッション

fs:default パーミッションは、アプリケーション専用ディレクトリへの読み取りアクセスと、ディレクトリの作成を許可する。

 {
   "permissions": [
     "fs:default"
   ]
 }


個別パーミッション

より詳細な制御が必要な場合は、個別のパーミッションを指定できる。

fs プラグインのパーミッション一覧
パーミッション 説明
fs:default デフォルトのパーミッションセット
fs:allow-read ファイルの読み取りを許可
fs:allow-write ファイルの書き込みを許可
fs:allow-exists ファイル存在確認を許可
fs:allow-mkdir ディレクトリ作成を許可
fs:allow-read-dir ディレクトリ読み込みを許可
fs:allow-remove ファイル削除を許可


スコープ設定

特定のパスへのみアクセスを許可する場合、スコープ設定を使用する。

 {
   "permissions": [
     {
       "identifier": "fs:allow-read",
       "allow": [
         { "path": "$APPDATA/**" },
         { "path": "$RESOURCE/**" }
       ]
     },
     {
       "identifier": "fs:allow-write",
       "allow": [
         { "path": "$APPDATA" },
         { "path": "$APPDATA/**" }
       ]
     }
   ]
 }


下表に、パスパターンの説明を示す。

パスパターンの説明
パターン 説明
$APPDATA アプリケーションデータディレクトリを表す変数。
** 任意の深さのサブディレクトリとファイルにマッチする。
* 単一レベルのディレクトリまたはファイルにマッチする。


包括的な設定例

 {
   "$schema": "../gen/schemas/desktop-schema.json",
   "identifier": "default",
   "description": "File system access capability",
   "windows": ["main"],
   "permissions": [
     "fs:default",
     {
       "identifier": "fs:scope",
       "allow": [
         { "path": "$APPDATA" },
         { "path": "$APPDATA/**" },
         { "path": "$RESOURCE" },
         { "path": "$RESOURCE/**" },
         { "path": "$TEMP" },
         { "path": "$TEMP/**" }
       ]
     }
   ]
 }



バイナリファイルの操作

テキストファイル以外にも、バイナリファイルの読み書きが可能である。

バイナリファイルの読み込み

readFile 関数は、ファイルの内容を Uint8Array として返す。

 import { readFile, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // 画像ファイルを読み込む関数
 async function loadImage(imagePath: string): Promise<Uint8Array> {
   try {
     const imageData = await readFile(imagePath, {
       baseDir: BaseDirectory.Resource
     });
     console.log(`画像を読み込みました: ${imageData.length} bytes`);
     return imageData;
   }
   catch (error) {
     console.error('画像の読み込みに失敗しました:', error);
     throw error;
   }
 }
 
 // Data URLとして使用する例
 async function loadImageAsDataUrl(path: string): Promise<string> {
   const data = await readFile(path, { baseDir: BaseDirectory.Resource });
   const base64 = btoa(String.fromCharCode(...data));
   return `data:image/png;base64,${base64}`;
 }


バイナリファイルの書き込み

writeFile 関数は、Uint8Array または ArrayBuffer をファイルに書き込む。

 import { writeFile, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // ダウンロードしたデータを保存する関数
 async function saveDownloadedFile(
   filename: string,
   data: Uint8Array
 ): Promise<void> {
   try {
     await writeFile(`downloads/${filename}`, data, {
       baseDir: BaseDirectory.AppData
     });
     console.log(`ファイルを保存しました: ${filename}`);
   }
   catch (error) {
     console.error('ファイルの保存に失敗しました:', error);
     throw error;
   }
 }



サンプルコード

設定管理クラス

アプリケーションの設定を管理するクラスの定義例を示す。

 import { readTextFile, writeTextFile, exists, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // アプリケーション設定の型定義
 interface AppConfig {
   theme: 'light' | 'dark';
   language: string;
   autoSave: boolean;
   recentFiles: string[];
 }
 
 // デフォルト設定
 const defaultConfig: AppConfig = {
   theme: 'light',
   language: 'ja',
   autoSave: true,
   recentFiles: []
 };
 
 // 設定管理クラス
 class ConfigManager {
   private configPath = 'config.json';
   private config: AppConfig | null = null;
 
   // 設定を読み込む
   async load(): Promise<AppConfig> {
     // 既に読み込み済みの場合はキャッシュを返す
     if (this.config) {
       return this.config;
     }
 
     // 設定ファイルが存在するか確認
     const configExists = await exists(this.configPath, {
       baseDir: BaseDirectory.AppConfig
     });
 
     if (configExists) {
       try {
         const content = await readTextFile(this.configPath, {
           baseDir: BaseDirectory.AppConfig
         });
         this.config = { ...defaultConfig, ...JSON.parse(content) };
       }
       catch (error) {
         console.warn('設定ファイルの読み込みに失敗。デフォルトを使用:', error);
         this.config = { ...defaultConfig };
       }
     }
     else {
       this.config = { ...defaultConfig };
     }
 
     return this.config;
   }
 
   // 設定を保存する
   async save(config: Partial<AppConfig>): Promise<void> {
     if (!this.config) {
       await this.load();
     }
 
     // 設定をマージ
     this.config = { ...this.config!, ...config };
 
     // ファイルに書き込む
     await writeTextFile(
       this.configPath,
       JSON.stringify(this.config, null, 2),
       { baseDir: BaseDirectory.AppConfig }
     );
   }
 
   // 現在の設定を取得
   get(): AppConfig | null {
     return this.config;
   }
 }
 
 // シングルトンとしてエクスポート
 export const configManager = new ConfigManager();


Reactコンポーネントでの使用

 import { useEffect, useState } from 'react';
 import { configManager, AppConfig } from './config-manager';
 
 // 設定パネルコンポーネント
 function SettingsPanel() {
   const [config, setConfig] = useState<AppConfig | null>(null);
   const [saving, setSaving] = useState(false);
 
   // 初期読み込み
   useEffect(() => {
     configManager.load().then(setConfig);
   }, []);
 
   // 設定を変更して保存
   const updateConfig = async (updates: Partial<AppConfig>) => {
     setSaving(true);
     try {
       await configManager.save(updates);
       setConfig(prev => prev ? { ...prev, ...updates } : null);
     }
     catch (error) {
       console.error('設定の保存に失敗しました:', error);
     }
     finally {
       setSaving(false);
     }
   };
 
   if (!config) {
     return <div>読み込み中...</div>;
   }
 
   return (
     <div className="settings-panel">
       <h2>設定</h2>
 
       <div className="setting-item">
         <label>テーマ</label>
         <select
           value={config.theme}
           onChange={(e) => updateConfig({ 
             theme: e.target.value as 'light' | 'dark' 
           })}
           disabled={saving}
         >
           <option value="light">ライト</option>
           <option value="dark">ダーク</option>
         </select>
       </div>
 
       <div className="setting-item">
         <label>自動保存</label>
         <input
           type="checkbox"
           checked={config.autoSave}
           onChange={(e) => updateConfig({ autoSave: e.target.checked })}
           disabled={saving}
         />
       </div>
     </div>
   );
 }
 
 export default SettingsPanel;



エラーハンドリング

ファイル操作では、様々なエラーが発生する可能性があるため、適切なエラーハンドリングが重要である。

よくあるエラー

ファイル操作のエラー一覧
エラー 原因 対処法
NotFound ファイルまたはディレクトリが存在しない exists() で事前に確認する。
PermissionDenied アクセス権限がない Capabilities設定を確認する。
AlreadyExists 作成しようとしたファイルが既に存在する exists() で事前に確認する。
InvalidData ファイルの内容が期待する形式と異なる データの検証を行う。
StorageFull ディスク容量が不足している ユーザに通知する。


エラーハンドリングの定義例

 import { readTextFile, BaseDirectory } from '@tauri-apps/plugin-fs';
 
 // エラーハンドリングを含むファイル読み込み
 async function safeReadFile(path: string): Promise<string | null> {
   try {
     const content = await readTextFile(path, {
       baseDir: BaseDirectory.AppData
     });
     return content;
   }
   catch (error) {
     // エラーの種類に応じた処理
     if (error instanceof Error) {
       if (error.message.includes('not found')) {
         console.warn(`ファイルが見つかりません: ${path}`);
         return null;
       }
 
       if (error.message.includes('permission')) {
         console.error(`アクセス権限がありません: ${path}`);
         throw new Error('このファイルにアクセスする権限がありません。');
       }
     }
 
     // 予期しないエラー
     console.error('ファイルの読み込み中にエラーが発生しました:', error);
     throw error;
   }
 }



トラブルシューティング

ファイルが見つからない (NotFoundエラー)

  • パスの確認
    ファイルパスが正しいか確認する。

  • ベースディレクトリの確認
    正しい baseDir を指定しているか確認する。

  • exists()の使用
    ファイル操作の前に exists() 関数で存在確認を行う。


パーミッションエラー (PermissionDeniedエラー)

  • Capabilities設定の確認
    src-tauri/capabilities/default.json ファイルで適切なパーミッションが設定されているか確認する。

  • スコープ設定の確認
    アクセスしようとしているパスがスコープに含まれているか確認する。

  • 再ビルド
    Capabilities設定を変更した後、アプリケーションを再ビルドする。


エンコーディングの問題 (テキストファイルの読み書きで文字化けが発生)

  • ファイルのエンコーディング確認
    TauriのfsプラグインはUTF-8を前提としている。
    異なるエンコーディングのファイルは、バイナリとして読み込んで変換する必要がある。



参考リンク