Tropy 1.18 のプロジェクトファイルと HTTP API をスクリプトから検証する
Tropy 1.18.0-beta.1 のプロジェクトファイル (.tpy / .tropy) は実体が SQLite データベースで、内蔵 HTTP API も使えます。Node スクリプトから両方を網羅的に検証して、見つかった 2 つの不具合を upstream に PR / Issue として送るまでの記録です。
tropysqliteelectronhttp-apidigital-humanities
台本(フルテキスト)
動画の掛け合いを書き起こしたものです。音声を再生しづらい場合はこちらをお読みください。
オープニング
- Tropy は人文系研究者向けの写真整理アプリ
- コマンドラインから操作できるかを 3 レイヤーで検証
- うさぎ
- 今日は Tropy という写真整理アプリを、スクリプトから操作する方法を 3 つのレイヤーで検証した記録を紹介します。
- WhiteCUL
- Tropy って何ですか?
- うさぎ
- 人文系の研究者が史料の写真を整理するためのデスクトップアプリです。Electron 製で、画像にメタデータやノートを付けて束ねる用途に使われます。
- WhiteCUL
- 3 つのレイヤーって?
- うさぎ
- プロジェクトファイルの SQLite を直接書き込むレイヤー、内蔵 HTTP API を叩くレイヤー、そして Tropy 内部の Redux ストアを操作するレイヤーの 3 つです。
- WhiteCUL
- それぞれ順番に見ていきましょう。
レイヤー 1: .tpy は SQLite データベース
- .tpy / .tropy は SQLite ファイル
- schema は db/schema/project.sql に約 400 行で記述
- WhiteCUL
- プロジェクトファイルって、どんな形式なんですか?
- うさぎ
- Tropy のプロジェクトファイルは、拡張子が .tpy または .tropy ですが、実体は SQLite データベースです。
- WhiteCUL
- スキーマはどこにあるんですか?
- うさぎ
- ソースの db スラッシュ schema スラッシュ project.sql に約 400 行で dump されています。これを sqlite3 コマンドで流し込めば、空のプロジェクトが作れます。
- WhiteCUL
- 特徴的な設計はありますか?
- うさぎ
- subjects というテーブルが面白くて、items も photos も selections も全部この共通の id 空間を共有しています。整数の id を見たら、それが何のサブジェクトかは別テーブルで判別する設計です。
managed 形式と MD5 の罠
- .tropy 形式は assets/ に content-addressable で画像を保存
- Tropy のチェックサムは MD5 (SHA-1 ではない)
- WhiteCUL
- .tropy 形式は中身が違うんですか?
- うさぎ
- .tropy はディレクトリで、中に project.tpy と assets フォルダを持ちます。画像は content-addressable で、ファイル名がチェックサムプラス拡張子になっています。
- WhiteCUL
- ここで何か罠を踏みましたか?
- うさぎ
- はい。最初 SHA-1 でハッシュを計算してファイル名にしていたのですが、Tropy が再起動時にチェックサムを再計算したところ合わず、ファイルを再 import してしまいました。
- WhiteCUL
- Tropy のチェックサムは何なんですか?
- うさぎ
- src スラッシュ asset スラッシュ asset.js を見ると MD5 でした。SHA-1 名のファイルと MD5 名のファイルが二重に残るという、無害ですが残念な結果になりました。
データモデルを一通り触る
- items / photos / metadata / tags / lists / selections / notes / transcriptions
- ProseMirror state でリッチテキスト、ALTO XML で OCR データ
- WhiteCUL
- 他にはどんなテーブルがあるんですか?
- うさぎ
- 代表的なところで、items、photos、metadata、tags、lists、selections、notes、transcriptions です。これらを全部 SQL から作って、Tropy で見えるところまで確認しました。
- WhiteCUL
- ノートはリッチテキストでしたよね?
- うさぎ
- はい、ProseMirror の state を JSON 文字列で持っていて、italic や bold、リンクなど 8 種類のマークが使えます。SQL から直接書き込んでも、Tropy 側で表示・編集できました。
- WhiteCUL
- metadata_values のテーブルが特殊と聞きました。
- うさぎ
- update_metadata_values_abort というトリガで UPDATE が禁止されていて、同じ値は 1 行しか持てません。INSERT OR IGNORE で重複を吸収しながら value_id を引く形になります。
レイヤー 2: 内蔵 HTTP API
- --port を付けて起動すると Koa サーバが立つ
- 30 程度のルートが公開されている
- WhiteCUL
- 次の層、HTTP API の話を聞かせてください。
- うさぎ
- Tropy を起動するときに --port というオプションを付けると、内蔵の Koa 製 HTTP サーバが指定ポートで立ち上がります。30 程度のルートが定義されています。
- WhiteCUL
- 起動中の Tropy には反映されますか?
- うさぎ
- はい、API 経由で書いた変更は即座に UI に反映されます。これが SQL 直書きと違う最大のメリットで、プロジェクトを開きっぱなしのまま、外からノートを追加したりメタデータを書き換えたりできます。
- WhiteCUL
- POST 系で注意点はありますか?
- うさぎ
- metadata 更新だけ Content-Type が application/json で、それ以外はフォーム形式です。あと items にタグを付ける時、未知のタグ名を渡すと内部で NaN になって 500 を返します。
副次的に分かったこと
- transcription はバージョン履歴 UI 対応
- FTS5 の unicode61 は CJK で 1 トークン扱い
- WhiteCUL
- 検証中に気付いた副次的なことはありますか?
- うさぎ
- transcription にはバージョン履歴の UI があります。同じ写真に複数の transcription を紐付けると、UI 側で切り替えられるようになっていて、OCR 再実行や校訂テキストを並列に持てる設計でした。
- WhiteCUL
- FTS5 で何かありましたか?
- うさぎ
- Tropy の FTS5 は unicode61 トークナイザを使っていて、空白の無い CJK の連続は 1 トークン扱いになります。京都市中心部のような連続文字列は、京都だけでは検索ヒットしません。
- WhiteCUL
- どうすれば対応できますか?
- うさぎ
- 京都アスタリスクでプレフィックスマッチを使うか、トークナイザを trigram に切り替える migration を別途用意するのが現実的な対応です。
Bug を 2 つ見つけて upstream へ
- Bug 1: act.transcriptions vs act.transcription の typo → PR #965
- Bug 2: TranscriptionCreate が Redux store を更新しない → Issue #966
- WhiteCUL
- API を全部叩いていく中で、不具合も見つかったんですか?
- うさぎ
- 2 つ見つかりました。1 つ目は POST /project/transcriptions が常に 500 を返すバグで、src/common/api.js の中で act.transcriptions と複数形になっているのが原因でした。
- WhiteCUL
- 修正は簡単だったんですか?
- うさぎ
- 1 文字を削るだけです。tropy リポジトリに PR ナンバー 965 として提出しました。2 つ目はその後に気付いたバグで、POST が DB には書き込むものの Redux store には反映されないという別問題でした。
- WhiteCUL
- そちらも PR にしたんですか?
- うさぎ
- こちらは修正方針に判断の余地があるので、Issue ナンバー 966 として方針相談という形で投稿しました。NoteCreate との非対称も合わせて報告しています。
まとめ
- デスクトップアプリでも SQLite なら外から制御可能
- 層の使い分けが鍵 — バッチは SQL、ライブ反映は API、UI 操作は DevTools
- WhiteCUL
- 今回の検証で得た学びを教えてください。
- うさぎ
- デスクトップアプリでも、データの実体が SQLite なら、大抵のことは外から制御できるという確認ができました。Electron プラス SQLite の構成は他のアプリでも同じパターンが通用すると思います。
- WhiteCUL
- 層の使い分けはどうすべきですか?
- うさぎ
- バッチで大量データを投入するなら SQL 直書き、UI を生かしたまま自動化したいなら HTTP API、UI 寄りの状態だけ触りたいなら DevTools の Console から dispatch、と用途で分けるのが現実的です。
- WhiteCUL
- 詳細はブログ記事をご覧ください。
- うさぎ
- PR と Issue へのリンクもブログから辿れます。OSS の検証は副産物として upstream に貢献できる、というのが今回の最大の収穫でした。