Palm Programmer's Laboratory
Palm OS Programmer's Companion Volume I/6-1
6-1 データマネージャ
伝統的なファイルシステムでは、まずディスクからメモリ上のバッファにファイルの全部または一部を読み込み、次にそのバッファ上の情報を参照したり更新(あるいはその両方を)し、最後に更新されたメモリバッファをディスクに書き戻します。Palm ハンドヘルドはダイナミックRAMの大きさに制限があり、またディスクストレージのかわりに不揮発性のRAMを使用しているため、伝統的なファイルシステムは Palm OS のユーザーデータを保存したり読み出したりするのに適してはいません。
Palm OS は全ての情報に対してインプレイスでアクセス、および編集を行ないます。これにより、ダイナミックメモリの必要量を減らし、ファイルシステムを含むメモリバッファ間のデータ転送のオーバーヘッドを取り除くことができます。
さらに利点として、Palm ハンドヘルド内のデータは、メモリ空間全体に散在させられる有限サイズの複数のレコードに分割することができます。これにより、レコードの追加、削除、リサイズにおいてメモリ中の他のレコードを移動させる必要がありません。データベース内の各レコードは、実際にはメモリマネージャのチャンクです。データマネージャはデータベースレコードのアロケート、削除、リサイズを行なうためにメモリマネージャ関数を使用します。
このセクションではデータマネージャの使い方を説明します。以下のトピックがあります。
- レコードとデータベース
- データベースヘッダの構造
- データマネージャの使用
レコードとデータベース
データベースは関連するレコードをまとめています。全てのレコードは1つの、そして1つだけのデータベースに所属します。データベースはアドレス帳や予定表などの全エントリのコレクションであったりします。Palm OS アプリケーションは、伝統的なファイルシステムにおいてファイルの作成、削除、オープン、クローズができるように、必要に応じてデータベースの作成、削除、オープン、クローズを行なうことができます。同じメモリカード上にある限り、特定のデータベースのレコードを配置する場所に制限はありません。あるデータベースのレコードが、メモリ内で1つ以上の他のデータベースのレコードと一緒に散らばっていることもできます。
データベースによってデータを保存するのは、Palm OS メモリマネージャのデザインにうまくフィットしています。ダイナミックヒープを除く全てのヒープは不揮発性なので、データベースのレコードはダイナミックヒープ以外の全てのヒープに保存することができます(「5 メモリ」の“ヒープの概要”を参照して下さい)。レコードはメモリカード上のどこにでも保存できるので、データベースは物理RAM上の不連続な複数の領域に分散させることができます。
ローカルIDを使ったデータアクセス
データベースは自身に含まれる全レコードのリストを、データベースヘッダに各レコードのレコードIDを保存することで保持しています。ローカルIDが使われているため、メモリカードをPalmハンドヘルドのどのメモリスロットにでも挿すことができます。アプリケーションはデータベース内のレコードをインデックスによって検索します。アプリケーションがあるレコードをリクエストすると、データマネージャはデータベースヘッダからインデックスによってレコードのローカルIDを取得し、データベースヘッダのカードナンバーを使用してそれをハンドルに変換して返します。
データベースヘッダの構造
データベースヘッダは、いくつかの基本的なデータベース情報とデータベース内のレコードのリストで構成されています。ヘッダ内の各レコードのエントリは、レコードのローカルID、属性を示す8ビット値、および3バイトのレコードのユニークIDからなっています。
このセクションではデータベースヘッダについて説明します。以下のトピックがあります。
- データベースヘッダのフィールド
- データベース内のレコードエントリの構造
- IMPORTANT
- データベースヘッダの構造は将来変更される可能性があります。データベースの操作にはAPIを使用して下さい。
データベースヘッダのフィールド
データベースヘッダには以下のフィールドがあります。
- データベース名を格納する name フィールド。
- データベースの属性フラグを格納する attributes フィールド。
- アプリケーション指定のバージョン番号を格納する version フィールド。
- データベース内のレコードが削除、追加、あるいは変更されるたびにインクリメントされる modificationNumber フィールド。これにより、アプリケーションは共有データベースが他のプロセスによって変更されたことを素早く検出できます。
- データベースに関するアプリケーション固有情報の保存にオプションとして使用できる appInfoID フィールド。例えば、特定のデータベースに関するユーザの表示設定を保存するのに使用できます。
- sortInfoID はもうひとつのオプションフィールドで、アプリケーションがデータベースのためのソートテーブルのローカルIDを保存するのに使用できます。
- データベースのタイプとクリエータを格納する 4 バイトの type フィールドと creator フィールド。システムはこれらのフィールドを使ってアプリケーションデータベースとそれ以外のデータベースを区別し、データベースを適切なアプリケーションに関連付けます。
- データベースヘッダが保持しているレコードのエントリ数を格納する numRecords フィールド。全レコードエントリがヘッダに収まらない場合、 次のレコードのセットを格納する recordList のローカルIDが nextRecordList に格納されます。レコードリストに格納されている各レコードエントリは 3 つのフィールドをもち、長さは 8 バイトになっています。それぞれのエントリは合計で 4 バイト幅になるレコードのローカル ID を保持しています。1 バイトの属性と 3 バイトのユニーク ID です。属性フィールドは図 6.1 に示すように、8 ビット幅の中に 4 つのフラグと 4 ビットのカテゴリ番号が含まれています。カテゴリ番号は、“ビジネス”や“パーソナル”といったユーザー定義カテゴリにレコードを配置するのに使います。
図 6.1 レコード属性
データベースヘッダ内のレコードエントリの構造
それぞれのレコードエントリはレコードのローカル ID、8 ビットからなる属性、およびレコードのユニーク ID ( 3 バイト)を保有しています。
- ローカル ID は(メモリカードの)スロットへのデータベースの依存をなくします。データベースのレコードは全て同じメモリカード上に存在するため、どのレコードのハンドルでも素早く算出することができます。アプリケーションがデータベースのあるレコードをリクエストすると、データマネージャは保存されているローカルIDからレコードのハンドルを算出して返します。ROM ベースのデータベースでは特殊な状況が発生します。ROM ベースのヒープは移動不可能なチャンクを排他的に使用するため、ROM 上のデータベースのレコードのローカル ID はポインタのローカル ID であり、ハンドルのローカル ID ではありません。そのため、アプリケーションが ROM ベースのデータベースをオープンすると、データマネージャは各レコードのフェイクハンドルをアロケートして初期化し、アプリケーションがレコードをリクエストすると適切なフェイクハンドルを返します。これにより、アプリケーションは RAM 上か ROM 上かに関わらず、データベースレコードのアクセスにハンドルを使用することができます。
- ユニーク ID はデータベース内のレコード間で一意でなければなりません。何回変更されたかに関わらず、レコードのユニーク ID は同じ値であり続けます。この値はデスクトップとの同期の際、Palmハンドヘルドとデスクトップ PC 間のレコードの対応付けのために使用されます。
- Palm OS 上でレコードを削除またはアーカイブすると、attributes フラグの delete ビットがセットされます。しかし、データベースヘッダ内のエントリは次にPCと同期するまでは削除されません。
- レコードが変更された場合、常に dirty ビットがセットされます。
- アプリケーションが読み取りまたは書込みのためにレコードをロックしている状態では、busy ビットがセットされます。
- ハンドヘルド上でパスワードを入力しないと表示されないレコードには secret ビットがセットされます。
Palmハンドヘルド上でレコードを“削除”すると、レコードのデータチャンクは開放され、レコードエントリのローカルIDには 0 がセットされます。また、attributesフィールドの delete ビットがセットされます。レコードをアーカイブした場合、delete ビットはセットされますがチャンクは開放されず、ローカル IDも保存されます。これにより、デスクトップと次に同期した際、デスクトップは(まだレコードエントリがPalmハンドヘルド上に残っているために)どのレコードが削除されたのかを素早く知ることができます。レコードをアーカイブした場合は、デスクトップはPalmハンドヘルドからレコードエントリとデータが完全に削除される前にレコードデータをPCに保存します。レコードの削除の場合、PCはPalmハンドヘルドからレコードエントリが完全に削除される前に対応するレコードを削除しなければなりません。
データマネージャの使用
データマネージャの使い方は、データがひと続きの連続するチャンクに保存されずに複数のレコードに分割されることを除けば、伝統的なファイルマネージャと同じです。データベースの作成および削除には、DmCreateDatabase と DmDeleteDatabase をコールします。
それぞれのメモリカードはディスクドライブのようなもので、複数のデータベースを格納することができます。読み取りまたは書込みのためにデータベースをオープンするには、まずデータベースIDを取得しなければなりません。これは単純にデータベースヘッダのローカルIDです。DmFindDatabase をコールすることでメモリカード上のデータベースを名前で検索し、データベースヘッダのローカルIDを返します。別の方法として、DmGetDatabase をコールすると、指定されたカード上のインデックスを持つデータベースの ID を取得できます。
データベースIDを取得したら、データベースを読み取り専用または読み書き両用でオープンすることができます。データベースをオープンすると、システムはデータベースヘッダをロックし、データベースアクセス構造体の参照を返します。これはオープンしているデータベースの情報を管理し、パフォーマンスを最適化するために情報をキャッシュするものです。データベースアクセス構造体はダイナミックヒープ上にアロケートされる比較的小さな構造体(100 バイト未満)で、データベースをクローズした時点で破棄されます。
データベースの名前、サイズ、作成日や更新日、属性、クリエータといった情報を問合せ、あるいは設定するには、DmDatabaseInfo、DmSetDatabaseInfo、および DmDatabaseSize をコールします。
データベースを参照・更新するには、DmGetRecord、DmQueryRecord、および DmReleaseRecord をコールします。
- DmGetRecord はレコードインデックスをパラメータとして取り、レコードに busy マークをつけてレコードハンドルを返します。DmGetRecord がコールされた時点で既にレコードが busy の場合、エラーが返されます。
- レコードを参照するだけであれば DmQueryRecord の方が高速です。この関数は busy ビットのチェックやセットを行ないません。そのため、レコードの参照が完了した時点で DmReleaseRecord をコールする必要がありません。
- DmReleaseRecord は busy ビットをクリアし、dirty パラメータが true であればデータベースの modification number を更新してレコードに dirty マークをつけます。
内容を増減させるためにレコードをリサイズするには、DmResizeRecord をコールします。この関数は現在のヒープに十分な空き領域がなければ、自動的に同一カード内の別のヒープにレコードをアロケートしなおします。データマネージャがリサイズのためにレコードを別のヒープに移動させる場合、レコードのハンドルが変わることに注意して下さい。DmResizeRecord はレコードの新しいハンドルを返します。
データベースに新しいレコードを追加するには、DmNewRecord をコールします。この関数は新しいレコードをどのインデックス位置にでも追加できますし、末尾に追加することもできます。また、インデックス位置にある既存のレコードを置き換えることもできます。この関数は新しいレコードのハンドルを返します。
レコードを削除する関数は3種類あります。DmRemoveRecord と DmDeleteRecord、および DmArchiveRecord です。
- DmRemoveRecord はデータベースヘッダからレコードエントリを削除し、レコードデータを破棄します。
- DmDeleteRecord もレコードデータを破棄しますが、データベースヘッダからレコードエントリを削除するかわりにレコードエントリの属性フィールドに deleted ビットをセットし、ローカルチャンクIDをクリアします。
- DmArchiveRecord はレコードデータを破棄しませず、レコードエントリの deleted ビットだけをセットします。
DmDeleteRecord と DmArchiveRecord は、ともにデスクトップ PC との情報の同期に有用です。削除またはアーカイブされたレコードのユニーク ID はデータベースヘッダにまだ保存されているため、デスクトップ PC は Palm OS データベースからレコードが完全に削除される前に、自身が保有するデータベースのコピーに対して必要な処理を行なうことができます。
データベースヘッダに保存されているレコードの属性、ユニークID、およびローカルIDといった情報を取得あるいは指定するには、DmRecordInfo と DmSetRecordInfo をコールします。通常これらのルーチンは、attribute フィールドの下位 4 ビットに保存されているレコードのカテゴリを取得または設定するために使用します(リスト 6.1 を参照)。
Listing 6.1 Determining the category for a record
UInt16 category; DmRecordInfo (MyDB, CurrentRecord, &attr, NULL, NULL); category = attr & dmRecAttrCategoryMask; //category now contains the index of the category to which // CurrentRecord belongs.
レコードをあるインデックスから他の位置へ、あるいはあるデータベースから他のデータベースへ移動するには、DmMoveRecord、DmAttachRecord、および DmDetachRecord をコールします。DmDetachRecord はデータベースヘッダからレコードエントリを削除し、レコードハンドルを返します。新しいレコードのハンドルを与えると、DmAttachRecord は新しいレコードをデータベースに追加または挿入するか、あるいは既存のレコードを新しいレコードで置き換えます。DmMoveRecord は同じデータベース内であるインデックスから別のインデックスにレコードを移動するための最適な方法です。
- NOTE
- Palm OS Cobalt では、PIM アプリケーションのデータベースは“スキーマ”データベースです。互換性のために、Palm OS Cobalt で PIM アプリケーションデータベースのいずれかにアクセスする場合、多くのデータマネージャコールは PACE にインターセプトされ、PIM アプリケーションのデータベースがクラシックな Palm データベースと同じに見えるようにします。この問題と Palm OS Cobalt が採用している解決法の詳細については、「 Palm OS Programmer's API Reference 」の“Accessing the PIM Application Databases”を参照して下さい。
データベースの自動バックアップおよびレストア
様々なバージョンの Palm OS で、RAM ストレージヒープの内容をなんらかの不揮発性 NAND フラッシュにバックアップするようにライセンシーによって設定することができます。RAM ストレージヒープがなんらかの理由で汚損したり消失した場合に、ストレージヒープを保存した時の状態にレストアすることができます。これにより、既に HotSync によって提供されているものを越えるレベルの信頼性が提供されます。バックアップバッテリを持たないデバイスは、このバックアップとレストアの機能により、電源の on/off 間にデータを失うことを防止できます。
セキュリティのために、バックアップはデータマネージャだけがアクセスできるプライベートな内部 VFS ボリュームに対して行なわれます。このボリュームはバックアップとレストアのためだけに使用されます。
バックアップは限られたイベントによって起動されます。
- データベースのクローズ。データベースがクローズされる度にそのデータベースは不揮発性ストレージにバックアップされます。
- データベースの作成。作成時にもデータベースはバックアップされます。これはインストール後変更されないために結果としてバックアップされないデータベースのための措置です。
- DmSetDatabaseInfo コールの成功。DmSetDatabaseInfo 関数のコールが成功するたびに、データベース情報が不揮発性ストレージにバックアップされます。
- デバイスのスリープ。通常のシステムスリープ機能の結果としてデバイスがスリープに入るたびに、データマネージャは全てのオープンされているデータベースを不揮発性ストレージにバックアップします。これはアプリケーションによってオープンされ、アプリケーション終了までクローズされないデータベースと、システムがスリープ状態に入る際に実行されるバックグラウンドスレッドがオープンするデータベースのためのものです。
- 明示的な DmSyncDatabase 関数呼び出し。
デバイスのリセット時に RAM の内容が失われた形跡があると、バックアップボリュームの内容が RAM にレストアされます。バックアップをレストアする前に、整合性チェックがバックアップに対して行なわれ、不整合を回復する試みが行なわれます。これら全てを通じてのみバックアップはレストアされます。開発者はプログラム的にデータベースのレストアを起動することはできません。
デバイスがこの自動バックアップ機能を備えているかどうかを調べるには、sysFtrNumDmAutoBackup フィーチャが存在するかどうかをチェックします。
データマネージャのヒント
データベースを正しく処理できていれば、アプリケーションは高速に動作し、問題なく同期できるはずです。以下の忠告に従うようにして下さい。
- データベース名は長さ 31 文字までで、ハンドヘルド上では有効な ASCII キャラクタで構成することができます。しかし、コンジット ── 特にバックアップコンジット ── には追加の制約があります。文字「 * + , . / : ; < = > ? [ ] | \ ^ " 」は、バックアップコンジットがデータベースをデスクトップに転送する際、アンダースコア(“_”)に変換されます。その上、バックアップコンジットはデータベースを大文字・小文字を区別しないフォーマットで保存するため、ファイル名の区別が大文字・小文字の区別に依存するような名前を避けなければなりません。慣習として、ハンドヘルド上ではファイル名に拡張子は使用しません。そのかわり、データベースの種類や分類を識別するようなためにデータベースタイプが使用されます。バックアップコンジットがファイルをデスクトップに転送する際、データベースのファイル名に自動的に .pdb や .prc といった拡張子を適切に付加することに注意して下さい。この拡張子は、ファイルがハンドヘルドに戻される際には除去されます。
- ユーザーがレコードを削除したら、DmRemoveRecord でレコード自体を削除するのではなく、DmDeleteRecord をコールしてレコードのデータを削除して下さい。これにより、デスクトップアプリケーションは次の HotSync の時にレコードが削除されたという情報を得ることができます。(注意:アプリケーションに対応するコンジットがない場合、DmRemoveRecord はレコードを完全に削除してしまいます。)
- データベースレコードのデータはコンパクトにしておきます。パフォーマンス上の問題を避けるために、Palm OS のデータベースは圧縮されていませんが、全てのデータはタイトに格納されています。これはストレージと HotSync に効果があります。
- データベースの全てのレコードは同じタイプとフォーマットにしておくべきです。これは要求ではありませんが、処理上のオーバーヘッドを避けるために強く推奨します。
- ユーザーが情報の変更や削除をしたら、データベースヘッダのフラグを適切に変更して下さい。このフラグ変更が要求されるのは、Palm PIM アプリケーションの同期をする場合にのみ要求されます。
- 削除されたレコードを表示しないようにして下さい。
- データベースの作成時に DmSetDatabaseInfo をコールしてバージョン番号を設定します。バージョン番号が明示的に設定されない場合、デフォルトでバージョン 0 になります。
- アプリケーションのスタートアップ時に DmDatabaseInfo をコールしてデータベースのバージョンをチェックして下さい。