BREW スケジューラー 〜 BREW C++ vCalendar プログラミング 〜
データ構造
本アプリでは VCalendar クラスに、ファイル入出力機能を付けた VCalendarFile クラスを使います。 VCalendarFile クラスは VCalendar を継承したクラスで、ファイル名と状態フラグをメンバ変数として、ファイル入出力処理をメンバ関数として持ちます。
class VCalendarFile : public VCalendar { private: SFXPath _fileName; // ファイル名 SFXAnsiString _flag; // 新規作成なら "n "、修正なら "m "、削除なら "d " public: Void SetFileName(SFXPathConstRef fileName); Void SetFlag(SFXAnsiStringConstRef flag); SFXPath GetFileName(Void) const; SFXAnsiString GetFlag(Void) const; SFCError SaveToFile(Void) const; // ファイルに書き込む SFCError LoadFromFile(Void); // ファイルから読み込む // 以下省略 };
VCalendar のデータ 1 件に対して1つのファイルを作るようにします。
さらに、VCalendarFile クラスのインスタンスを複数管理する VCalCollection クラスを使います。このクラスはアプリクラスの変数として定義しておきます。
class VCalCollection { private: // 複数のVCalendarFileを管理する。 // このリストは日付順(_startDT)で常にソートされているものとする SFXList<VCalendarFilePtr> _list; SFXAnsiString _lastSyncTime; // 最後にサーバと同期を行った時刻 public: SFCError ReadIndexFile(Void); // ファイルから読み込む SFCError WriteIndexFile(Void); // ファイルに書き込む // 以下省略 }
複数の VCalendarFile インスタンスを保持するため、SFXList を使います。このリストに要素を挿入するときは、_startDate が日付順になるように挿入します。データをソートしておくのは、カレンダーの表示などで連続した日付のデータにアクセスしたり、ある日付のデータの個数を数えたりするためです。
要素の挿入のコード
// VCalCollection にデータを加える Void VCalCollection::Append(VCalendarFilePtr vcalendar) { SFXDateConst date1(vcalendar->GetStartDate()); Bool isInsert = false; SIntN i; vcalendar->SetFlag("n"); // 新規作成を表すフラグをセット // データを挿入する場所を探す。 // データをソートしておく必要があるため。 for (i = 0; i < _list.GetSize(); ++i) { // i番目のデータの時刻を得る SFXDate date2 = _list.Get(i)->GetStartDate(); if (date1 <= date2) { // 時刻の比較 _list.Insert(i, vcalendar); // データを挿入する isInsert = true; break; } } if (!isInsert) { // 挿入する場所が見つからなかったら _list.Append(vcalendar); // 最後尾に追加 } vcalendar->SaveToFile(); // データをファイルに保存 WriteIndexFile(); // index を更新 return; }
処理の流れは、
- 挿入する要素の時刻 ( startDate ) を、現在 _list にある各要素と順に比較していき、挿入する場所を探す
- 挿入する場所が見つかると Insert 関数で _list に挿入する
- 挿入する場所が見つからないなら Append 関数で _list の末尾に追加する
- 挿入したデータをファイルに保存する ( VCalendar の SaveToFile 関数)
- インデックスファイルを更新する ( WriteIndexFile 関数)
日付の比較は比較演算子 ( date1 <= date2 ) で行います。
VCalendarFile の SaveToFile 関数の実装
// データをファイルに保存する SFCError VCalendarFile::SaveToFile(Void) const { SFCError error; SFXFile file; SFXAnsiStringStreamWriter writer; SFXPath fileName(DIR_NAME + _fileName.Get()); SFXPath temp; SFXAnsiString content; // 一時ファイル名を取得 error = SFXFile::GetTemporaryPath(SFXPath(DIR_NAME), &temp); if (error == SFERR_NO_ERROR) { // 一時ファイルを作成 error = file.OpenReadWrite(temp); if (error == SFERR_NO_ERROR) { // VCalendarクラスからvCalendar形式文字列に変換 Export(&content); // 書き込みストリームを取得 error = file.GetStreamWriter(content.GetLength(), &writer); if (error == SFERR_NO_ERROR) { // ファイルに書き込み writer << content; writer.Flush(); } // ファイルを閉じる file.Close(); } } if (error == SFERR_NO_ERROR) { // 旧ファイルを削除 SFXFile::Remove(fileName); // 一時ファイルを本来のファイルにリネーム error = SFXFile::Rename(temp, fileName); } return error; }
WriteIndexFile() は、VCalendar データを保存するファイル名をインデックスとしてファイルに保存する関数で、以下のフォーマットで保存します。
20080324T031654Z // 最終更新時刻 filename1 - // ファイル名、タブ (\t)、フラグ filename2 d filename3 f ...
VCalCollection の WriteIndexFile 関数の実装
// ファイルに書き込む SFCError VCalCollection::WriteIndexFile(Void) { SFCError error; SFXFile file; SFXAnsiStringStreamWriter writer; SFXPath fileName(DIR_NAME INDEX_FILE_NAME); SFXPath temp; SFXAnsiString content; // 一時ファイル名を取得 error = SFXFile::GetTemporaryPath(SFXPath(DIR_NAME), &temp); if (error == SFERR_NO_ERROR) { // 一時ファイルを作成 error = file.OpenReadWrite(temp); if (error == SFERR_NO_ERROR) { // 書き込みストリームを取得 error = file.GetStreamWriter(1024, &writer); if (error == SFERR_NO_ERROR) { // ファイルに書き込み writer << _lastSyncTime << "\r\n"; writer.Flush(); SFXList<VCalendarFilePtr>::Enumerator etor = _list.GetEnumerator(); while(etor.HasNext()) { VCalendarFilePtr vcal = etor.GetNext(); writer << vcal->GetFileName().Get() << " " << vcal->GetFlag() << "\r\n"; writer.Flush(); } } // ファイルを閉じる file.Close(); } } if (error == SFERR_NO_ERROR) { // 旧ファイルを削除 SFXFile::Remove(fileName); // 一時ファイルを本来のファイルにリネーム error = SFXFile::Rename(temp, fileName); } return error; }
SFXList の各要素に対する処理のため、Enumrator を使っています。HasNext 関数で次の要素が存在するかを調べ、GetNext 関数で要素を取得します。
VCalCollection のインスタンス
VCalCollection のインスタンスは本アプリに 1 つ存在し、様々なクラスから参照されます。このようなアプリ全体で使われる変数はアプリクラス (新規プロジェクト作成時にウィザードが自動で生成するクラス) に置くのが普通です。
SyncScheduler アプリクラス
class SyncScheduler : public SFRApplication { private: VCalCollection _vcalCollection; // 以下省略 public: static VCalCollectionPtr GetVCalCollection(Void); // 以下省略 }; // VCalCollection のインスタンスを取得 VCalCollectionPtr SyncScheduler::GetVCalCollection(Void) { return &static_cast<SyncSchedulerPtr>(GetInstance())->_vcalCollection; }
このように書くことで、どの場所でも次のようにインスタンスを取得できます。
VCalCollectionPtr collection = SyncScheduler::GetVCalCollection();
データの読み込み
アプリを起動したときにファイルからデータを読み込むようにします。
// コンストラクタ SyncScheduler::SyncScheduler(Void) static_throws { // ... これより上は省略 _vcalCollection.ReadIndexFile(); // ... 以下省略 }
インデックスファイルからデータを読み込む ReadIndexFile 関数
// 全データをファイルから読み込む。 // まず、indexファイルから読み込み、 // 次に読み込んだindexファイルを解析し、各ファイルから // VCalCollection にデータを読み込む。 SFCError VCalCollection::ReadIndexFile(Void) { SFCError error; SFXFile file; SFXAnsiStringStreamReader reader; UInt32 size; SFXAnsiString string; // ファイルサイズを取得 error = SFXFile::GetSize(SFXPath(DIR_NAME INDEX_FILE_NAME), &size); if (error == SFERR_NO_ERROR) { // ファイルを開く error = file.OpenReadOnly(SFXPath(DIR_NAME INDEX_FILE_NAME)); if (error == SFERR_NO_ERROR) { // 読み込みストリームを取得 error = file.GetStreamReader(size, &reader); if (error == SFERR_NO_ERROR) { reader.Fetch(); // ファイルから読み込み reader >> string; } file.Close(); } } if (error == SFERR_NO_ERROR) { SInt32 c1 = 0; SInt32 c2 = 0; c2 = string.IndexOf('\n'); _lastSyncTime = string.Substring(0, c2).Trim(); ++c2; while ((c1 = string.IndexOf(' ', c2)) > -1) { SFXPath filename(string.Substring(c2, c1)); c2 = string.IndexOf("\r\n", c1); SFXAnsiString flag(string.Substring(c1 + 1, c2)); c2 += 2; // \r\nの長さ分 VCalendarFilePtr vcal = ::new VCalendarFile(filename, flag); vcal->LoadFromFile(); // vCalendar データをファイルから読み込む _list.Append(vcal); } } return error; }
1 件の VCalendar ファイルを読み込む LoadFromFile 関数
// ファイルからデータを読み出す SFCError VCalendarFile::LoadFromFile(Void) { SFCError error; SFXFile file; SFXAnsiStringStreamReader reader; UInt32 size; SFXAnsiString string; SFXPath filename(SFXPath(DIR_NAME + _fileName.Get())); // ファイルサイズを取得 error = SFXFile::GetSize(filename, &size); if (error == SFERR_NO_ERROR) { // ファイルを開く error = file.OpenReadOnly(filename); if (error == SFERR_NO_ERROR) { // 読み込みストリームを取得 error = file.GetStreamReader(size, &reader); if (error == SFERR_NO_ERROR) { reader.Fetch(); // ファイルから読み込み reader >> string; } file.Close(); } } if (error == SFERR_NO_ERROR) { Import(string); // vCalendar データを解析して各メンバ変数に代入 } return error; }