PocketScore開発記 #1:Tauri v2でSQLiteデータベースをバックアップ・復元する


PocketScore開発記 #1:Tauri v2でSQLiteデータベースをバックアップ・復元する

ミュージシャンをしながら、エンジニアとして「自分のための道具」を作っています。 現在開発しているのは、PDF楽譜管理アプリ 「PocketScore」

今回は、Androidアプリとしても展開する上で避けて通れない**「データのポータビリティ(バックアップと復元)」**をTauri v2でどう実装したか、その備忘録を残します。

なぜバックアップが必要か

PocketScoreは、プライバシーとオフラインでの動作を重視し、データはすべてローカルの SQLite に保存しています。 しかし、Androidアプリとして運用する場合、端末の紛失や機種変更への備えが必須です。ユーザーが自分のデータを「資産」として持ち出せる仕組みを目指しました。

実装のポイント:ZIP形式でのエクスポート

バックアップファイル(拡張子 .pscore)の実体は、SQLiteのDBファイルと設定ファイルを固めたZIPアーカイブです。

Rust(Backend)での処理

Rustの zip クレートと walkdir を組み合わせ、アプリのデータディレクトリを圧縮するコマンドを実装しました。

use std::fs::File;
use std::io::{Write, Read};
use zip::write::FileOptions;
use walkdir::WalkDir;

#[tauri::command]
pub async fn export_pscore(handle: tauri::AppHandle) -> Result<String, String> {
    // アプリ専用のデータディレクトリを取得
    let app_dir = handle.path().app_data_dir().map_err(|e| e.to_string())?;
    let db_path = app_dir.join("pocketscore.db");
    
    // 出力先のパス(例としてドキュメントフォルダ)
    let docs_dir = handle.path().document_dir().map_err(|e| e.to_string())?;
    let backup_path = docs_dir.join("backup.pscore");

    let file = File::create(&backup_path).map_err(|e| e.to_string())?;
    let mut zip = zip::ZipWriter::new(file);
    let options = FileOptions::default()
        .compression_method(zip::CompressionMethod::Stored);

    // DBファイルをZIPに追加
    zip.start_file("pocketscore.db", options).map_err(|e| e.to_string())?;
    let mut f = File::open(db_path).map_err(|e| e.to_string())?;
    let mut buffer = Vec::new();
    f.read_to_end(&mut buffer).map_err(|e| e.to_string())?;
    zip.write_all(&buffer).map_err(|e| e.to_string())?;

    zip.finish().map_err(|e| e.to_string())?;
    
    Ok(backup_path.to_string_lossy().into_owned())
}

フロントエンド(React)での呼び出し

ユーザー体験を損なわないよう、保存完了時にトースト通知を出すなど工夫しています。

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

const handleExport = async () => {
  setLoading(true);
  try {
    const path = await invoke<string>("export_pscore");
    alert(`バックアップが完了しました!\n保存先: ${path}`);
  } catch (e) {
    console.error(e);
    alert("エクスポート中にエラーが発生しました。");
  } finally {
    setLoading(false);
  }
};

実装上のハマりどころ:Androidのファイル権限

Androidで実装する際、最も苦労したのが**「ファイルシステムへのアクセス権限」**です。 Tauri v2では capabilities の設定が厳密になっており、path プラグインや fs プラグインの権限を正しく設定しないと、ファイルを作成した瞬間にアプリがクラッシュします。

また、保存先も app_data_dir(アプリ専用領域)から、ユーザーが見える document_dir へ移動させる際、Android 13以降のスコープドストレージの仕様を意識する必要がありました。

生活の文脈の中で作るということ

最近は冷え込み、仕事の合間に豆炭を熾して暖を取りながらコードを書いています。

次は、Android実機でのビルドと広告実装について書く予定です。