日曜日にBCB!



Do It Yourselfでひとつプログラムでも作ってみませんか。


第16話「分割サイズカスタマイズ機能 その3」

前回に引き続き分割サイズカスタマイズ機能をプログラミングします。最後は分割スレッドへの変更です。分割手順の変更、分割レポートの対応を行います。


分割スレッドコンストラクタ

メイン画面から分割サイズ更新、分割リスト更新、結合バッチ作成要求のメッセージのIDが新たに引き渡されます。まずは分割スレッドのコンストラクタでこれらを分割情報を設定します。

改造分割スレッドクラス

分割サイズ更新、分割リスト更新、結合バッチ作成要求のメッセージIDを保持するメンバー変数を追加します。また、分割リスト更新時に分割中ファイル情報を通知する必要があるため、更新情報も保持します。
//---------------------------------------------------------------------------
class TDivideThread : public TThread
{
private:
    TForm        *OwnerForm;            // 呼出しフォーム
    unsigned int ReportMessage;         // 分割進捗状況報告メッセージ
    unsigned int NoSpaceMessage;        // 分割容量不足メッセージ
    unsigned int UpdateSizeMessage;     // 分割サイズ更新通知メッセージ
    unsigned int UpdateListMessage;     // 分割リスト追加通知メッセージ
    unsigned int MakeBatchMessage;      // 結合バッチ作成要求メッセージ
    AnsiString   SourceFile;            // 分割元ファイル
    AnsiString   DstPath;               // 分割先パス
    TStringList  *DstFiles;             // 分割先ファイルリスト
    AnsiString   ConnectBatch;          // 結合バッチコマンド
    int          DivideIndex;           // 分割ファイルインデックス番号
    int          DivideStatus;          // 分割実行ステータス
    cDivideFile  *DivideFile;           // 分割ファイルリスト更新情報
protected:
    :
};
//---------------------------------------------------------------------------

改造コンストラクタ

分割スレッドのコンストラクタでは新たに分割サイズ更新、分割リスト更新、結合バッチ作成要求のメッセージIDを保持します。
//---------------------------------------------------------------------------
__fastcall TDivideThread::TDivideThread(TForm *_OwnerForm,          // 呼出しフォーム
                                        unsigned int _Messages[],   // 通知メッセージID
                                        AnsiString _SourceFile,     // 分割元ファイル
                                        AnsiString _DstPath,        // 分割先パス
                                        TStringList *_DstFiles,     // 分割先ファイルリスト
                                        AnsiString _ConnectBatch)   // 結合バッチコマンド
    : TThread(true)
{
    FreeOnTerminate    = true;             // スレッド終了時にスレッドオブジェクトを自動的に破棄
    OwnerForm          = _OwnerForm;       // 呼出しフォーム設定
    ReportMessage      = _Messages[0];     // 分割進捗状況通知メッセージ設定
    NoSpaceMessage     = _Messages[1];     // 分割先容量不足メッセージ設定
    UpdateSizeMessage  = _Messages[2];     // 分割サイズ更新通知
    UpdateListMessage  = _Messages[3];     // 分割リスト追加通知
    MakeBatchMessage   = _Messages[4];     // 結合バッチ作成要求
    SourceFile         = _SourceFile;      // 分割元ファイル設定
    DstPath            = _DstPath;         // 分割先パス設定
    DstFiles           = _DstFiles;        // 分割先ファイルリスト(名前+分割オブジェクト)のコピー設定
    ConnectBatch       = _ConnectBatch;    // 結合バッチコマンド設定
}
//---------------------------------------------------------------------------


分割手順の変更

空き容量全て分割を考慮して分割手順を変更します。変更点は以下の通りです。
  1. 分割バッファの取り扱い
    空き容量全ての分割を行う場合には、分割サイズは分割を実行するまで決まりません。
  2. 分割ファイルリストの取り扱い
    空き容量全てに分割を行った場合、ファイルを全て分割するまで分割ファイルを追加します。分割ファイル追加を追加し、分割サイズ、分割リストの更新をGUIスレッドに通知します。
  3. 結合バッチコマンドの取り扱い
    空き容量全ての分割を行う場合には、結合バッチコマンドは全てのファイルの分割を完了した時に決定します。結合バッチ作成をGUIスレッドに要求します。
分割スレッドに対する改造はほとんどがExecute関数に対する改造です。

改造Execute関数

//---------------------------------------------------------------------------
void __fastcall TDivideThread::Execute()
{
// 分割元ファイル読込みオープン
    :

// 分割バッファ準備→処理手順を分割ループ内へ変更

// 分割数繰り返し
    int TotalCutSize = 0;          // 分割サイズ合計
    for (int i=0;i>DstFiles->Count;i++){
        DivideIndex = i;               // 分割中ファイルインデックス

    // 分割先ファイル情報取得
        cDivideFile *File = (cDivideFile*)DstFiles->Objects[i];     // 分割ファイルオブジェクト取得
        int CutSize = File->GetCutSize();                           // 分割サイズ取得
        if (ExtractFileExt(DstFiles->Strings[i])==".bat"){          // 結合バッチファイル?
            if (ConnectBatch==""){              // 残り全容量分割?
                Synchronize(MakeConnectBatch);  // 結合バッチファイル作成
                CutSize = ConnectBatch.Length()+1;
                File->SetCutSize(CutSize);      // バッチファイルサイズ設定
            }
        }
        AnsiString DriveName = ExtractFileDrive(DstPath) + "\\";    // ドライブ名取得
        bool AviableDivide;             // 分割残り容量あり?
        do{
            AviableDivide = true;
            if (File->GetCutSize()==0){                                  // 空き容量全て分割
                CutSize = File->GetCutSize((int)GetDiskSize(DriveName), (int)GetDiskFree(DriveName));  // 分割サイズ取得
                if (CutSize>(GetFileSize(SourceFile)-TotalCutSize)){     // 残りサイズが分割サイズよりも小さい
                    CutSize = GetFileSize(SourceFile)-TotalCutSize;      // 分割サイズ調整
                }
            }
            if ((CutSize<=0) ||                             // 空き容量0?
                (GetDiskFree(DriveName)<0) ||               // ドライブエラー?
                ((double)CutSize>GetDiskFree(DriveName))){  // 分割空き容量不足?
                Synchronize(NoDivideSpace);                     // 分割先容量不足通知
                if (DivideStatus<dsSuccess){     // 分割中断?
                    DivideStatus = dsUserBreak;     // ユーザー中断
                    fclose(SrcFP);                  // 分割先ファイルクローズ
                    return;                         // 分割中断
                }
                AviableDivide = false;   // 分割残り容量不足
            }
        }while(!AviableDivide);      // 分割残り容量を満たすまで繰り返し

    // 分割バッファ確保→処理手順を分割ループ外から変更
        char *buffer = new char [CutSize/dfMaxNumDiv];             // 分割バッファメモリ確保
        if (Terminated){        // ユーザー中断?
            DivideStatus = dsUserBreak;     // ユーザー中断
            fclose(SrcFP);                  // 分割先ファイルクローズ
            return;                         // 分割中断
        }

    // 出力ファイルタイプ取得
        if (ExtractFileExt(DstFiles->Strings[i])!=".bat"){    // 分割ファイル?
        // 分割開始オフセット移動
            :
        // 分割先ファイル作成
            int BlockSize = CutSize/dfMaxNumDiv;                           // 書き込みブロックサイズ取得
            AnsiString DstFilePath = DstPath + DstFiles->Strings[i];        // 分割先ファイル名作成
            FILE *DstFP = fopen(DstFilePath.c_str(), "wb");       // 分割先ファイル作成オープン
            :

        // ブロック書きこみループ
            :

            int ProgressSize = 0;               // 書き込み進捗サイズクリア
            while(ProgressSize<CutSize){        // 分割進捗サイズが分割サイズに達するまで繰り返し
                if ((ProgressSize+BlockSize)>CutSize) BlockSize = CutSize - ProgressSize;    // 1ブロックサイズ計算
                if (fread(buffer, BlockSize, 1, SrcFP)!=1){    // 1ブロック読みこみ
                    :
                }
                ProgressSize += BlockSize;          // 分割進捗サイズ更新
                if (fwrite(buffer, BlockSize, 1, DstFP)!=1){    // 1ブロック書きこみ
                    :
                }
                DivideStatus = (int)((double)ProgressSize*100.0/(double)CutSize+0.5);   // 分割進捗状況計算(整数オーバーフロー回避)
                Synchronize(ReportDividing);        // 分割進捗状況報告
                if (Terminated){                    // ユーザー中断
                    :
                }

        // 空き容量全て分割時分割サイズ更新
            if (File->GetCutSize()==0){             // 空き容量全て分割
                DivideStatus = CutSize;             // GUIファイル分割サイズ設定
                Synchronize(UpdateDivideSize);      // ファイル分割サイズ更新通知
                File->SetCutSize(CutSize);          // 分割ファイルサイズ更新
                TotalCutSize += CutSize;            // 分割サイズ合計更新
                if (TotalCutSize<GetFileSize(SourceFile)){  // 分割ファイルリスト追加
                    cDivideFile *AddFile = new cDivideFile(0);     // 分割ファイルオブジェクトインスタンス
                    AddFile->SetSkipSize(File->GetSkipSize(), File->GetSkipUnit());  // スキップサイズ登録
                    AddFile->SetOffset(TotalCutSize);                                // 分割オフセット登録
                    AnsiString ExtendStr = DstFiles->Strings[i].SubString(DstFiles->Strings[i].Length()-2, 3);
                    int ExtendNum = ExtendStr.ToInt();                               // 分割ファイル拡張子番号取得
                    ExtendStr = DstFiles->Strings[i].SubString(1, DstFiles->Strings[i].Length()-4) + MakeOrderExt(ExtendNum);    // 追加分割ファイル名作成
                    if (i==(DstFiles->Count-1)) DstFiles->AddObject(ExtendStr, (TObject*)AddFile);          // 末尾に追加
                    else                        DstFiles->InsertObject(i+1, ExtendStr, (TObject*)AddFile);  // 途中に挿入
                    DivideFile = AddFile;               // GUIファイル分割リスト更新
                    Synchronize(UpdateDivideList);      // 分割ファイルリスト更新通知
                }
            }

        }
        else{               // バッチファイル?
          :
            DivideStatus = 100;                 // 分割進捗状況更新(バッチファイルは0と100のみ)
            Synchronize(ReportDividing);        // 分割進捗状況通知

            DivideStatus = ConnectBatch.Length();   // GUIファイル分割サイズ設定
            Synchronize(UpdateDivideSize);          // ファイル分割サイズ更新通知
            File->SetCutSize(CutSize);              // 分割ファイルサイズ更新

            DivideStatus = dsSuccess;           // 分割成功

        }

    // 分割バッファ開放
        delete buffer;

    }

// 分割元ファイルクローズ
    fclose(SrcFP);
}
//---------------------------------------------------------------------------

残りはGUIスレッドとのメッセージのやり取りを追加します。

UpdateDivideSize関数

空き容量全てで分割した分割サイズをGUIスレッドに通知します。
//---------------------------------------------------------------------------
void __fastcall TDivideThread::UpdateDivideSize()
{
    OwnerForm->Perform(UpdateSizeMessage, DivideIndex, DivideStatus);      // 分割ファイルインデックス、サイズを通知
}
//---------------------------------------------------------------------------

UpdateDIvideList関数

空き容量全てで追加した分割リストをGUIスレッドに通知します。
//---------------------------------------------------------------------------
void __fastcall TDivideThread::UpdateDivideList()
{
    OwnerForm->Perform(UpdateListMessage, DivideIndex, (int)DivideFile);           // 分割ファイルインデックス、追加ファイル名を通知
}
//---------------------------------------------------------------------------

MakeConnectBatch関数

結合バッチファイル作成をGUIスレッドに要求します。
//---------------------------------------------------------------------------
void __fastcall TDivideThread::MakeConnectBatch()
{
    int BatchLength = OwnerForm->Perform(MakeBatchMessage, 0, 0);     // 結合バチファイル長取得

    char *Buffer = new char[BatchLength];                             // 結合バッチファイル作成バッファ確保
    OwnerForm->Perform(MakeBatchMessage, (int)Buffer, BatchLength);   // 結合バッチファイル作成
    ConnectBatch = AnsiString(Buffer);                                // AnsiString(VCL)型に変換
    free(Buffer);                                                     // 結合バッチファイル作成バッファ解放
}
//---------------------------------------------------------------------------

以上で分割サイズカスタマイズ機能のプログラミング作業は完了です。試しに640Mのデータを230MのMO3枚に分割しました。結構時間はかかりますが、ちゃんと結合しました。


今回のバグ修正

今回はちょっとバグ修正が多いです。(^^;

設定画面フォントサンプル

設定画面のフォントサンプルが他のDDTソフトウェアから流用したため、ドクターUnitsと同じになっていました。かなり格好悪いのでこれを変更します。設定画面のSampleEDTのコントロールのプロパティーを変更します。

分割中ファイル選択表示

分割を実行した際、分割中のファイルが選択表示されない場合がありました。分割実行直前に分割ファイル一覧以外にフォーカスがあると、分割中のファイルを選択しても反転表示されません。分割実行時に分割一覧にフォーカスを設定します。

改造GoDivideMNUClick関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::GoDivideMNUClick(TObject *Sender)
{
    :

// 分割実行中画面設定
    :
    StatusBAR->Visible = false;                       // ステータスバー非表示

    ListVEW->SetFocus();                              // 分割一覧にフォーカス設定

// 分割スレッド実行
   :
}
//---------------------------------------------------------------------------

分割実行時の致命的エラー発生

一つの分割ファイル出力を中断した直後に致命的エラーが発生することがまれにあります。調べてみると分割完了時に「未出力」と表示する処理でファイルが必ず選択されていることが前提となっています。しかし、タイミングによっては未だファイルが選択されていない場合があります。従って、ファイルが未選択の場合には「未出力」と表示しない様に変更します。

改造OnDivideTRDTerminate関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::OnDivideTRDTerminate(TObject *Sender)
{
// 分割完了報告
    :
    if (Message!="") Application->MessageBox(Message.c_str(), "分割実行", MB_ICONEXCLAMATION);
                                                    // エラー発生時、メッセージ表示
    if (ListVEW->Selected!=NULL){                   // 分割ファイル一覧が選択されていたら
        if (DivideTRD->GetDividedStatus()<0) ListVEW->Selected->SubItems->Strings[1] = "未出力";
    }

// 分割開始画面設定
    :
}
//---------------------------------------------------------------------------

メディア交換判定不具合

リムーバブルメディアの分割容量不足の際、メディア交換をNoDivideSpace関数で処理していますが、メディアが正しく交換された場合、分割ステータスのメンバ変数分の分割実行ステータスに正常終了を設定していませんでした。DivideStatus分割情報クラスのメンバ変数でありdsSuccessが列挙形で設定した定数0に初期化されており、問題はありませんでした。しかし、明示的に設定していない値を参照するのは今後の改造を考えても危険です。NoDivideSpace関数にメディア交換成功のステータス設定を追加します。

改造NoDivideSpace関数

//---------------------------------------------------------------------------
void __fastcall TDivideThread::NoDivideSpace()
{
    int ChangeMedia = OwnerForm->Perform(NoSpaceMessage, 0, 0);      // 分割先容量不足をメッセージ通知
    switch (ChangeMedia){                                            // 返答を解読
        case dsSuccess:     DivideStatus = dsSuccess;   break;       // メディア交換成功
        case dsNoSpace:     DivideStatus = dsNoSpace;   break;       // 分割容量不足
        case dsUserBreak:   DivideStatus = dsUserBreak; break;       // ユーザー中断
    }
}
//---------------------------------------------------------------------------

以上でバグ修正は終了です。


以上で分割サイズカスタマイズ機能の追加は完了です。自分で言うのは何ですが、便利ですね。仕事柄何枚か常にフロッピーディスケットを持ち歩いていますが、なんだか訳のわからないデータがちょっとずつ入っていたりします。空き容量全てに分割できると、いちいちフロッピーディスケットの中身を整理しなくても済むので、不精な私にはとても便利です。(^^;
こちらから今回作成したBCBのプロジェクト一式をダウンロードできます(ヘルプ一式は含みません)。プロジェクトダウンロード(47,585バイト) BCBのバージョン3、4、5でビルドできます。BCBバージョン5ではの修正を行ってください。
一つだけ補足事項があります。私の愛猫は常時フロッピーディスクドライブは付いてません。開発用に空き容量全て分割を一時的にRAMドライブに出力しています。従ってプロジェクト中のソースプログラムにはRAMドライブを使用するためのプリプロセッサ命令が何箇所かに入っています。

#ifdef dfUSERAMDRIVE
    (RAMドライブがあるときの処理)
#endif
寿分割開発の流れには直接関係ありませんので、ご注意ください。

ヘルププロジェクト一式のダウンロードはこちら。ヘルプダウンロード(17,785バイト) 参考にしてください。

次回は圧縮分割機能の追加を行いたいと思います。寿分割を使う前にファイルを圧縮してから分割実行することを頻繁に行います。だったら一発操作で処理してしまいたいものです。アーカイバDLLの利用等いろいろと課題はありそうです。お楽しみに。

今回のプログラミング行数: 101行
今回までのプログラミング行数: 1,871行


目次に戻る目次に戻る トップに戻るトップに戻る