日々の戯言


バックナンバー

1997年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
1998年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
1999年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2000年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2001年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2002年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2003年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2004年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2005年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2006年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2007年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2008年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2009年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2010年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2011年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2012年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2013年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2014年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2015年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2016年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
バックナンバー内のリンクは無保証です

4月13日(土) EDCB 人柱版 10.69 の自動削除に関して [この記事]

EpgDataCap_Bon ツール群の人柱版 10.69 を手元で運用していたところ、EpgTimer の「HDDの空きが少ない場合、古い録画ファイルを削除する」の設定を有効にしていて不思議な挙動を示したので、その件について書いておくことにします。

◇◆◇

まずは症状の説明から。私は次のフォルダ構成で EDCB を運用していました。

folder tree

c:\movie の下に 001, 002, 003, 004 という空フォルダを作り、それぞれ 4TB HDD をマウントして 4〜6 局程度放送局を割り振り、ジャンル指定で自動予約登録&古いものから削除という運用形態です。

自動削除対象のフォルダとして各放送局に割り当てているフォルダ全てを指定しているのですが、c:\movie\001 以下では「BS日テレ」フォルダに保存されているファイルのみが自動削除されていき、「BS朝日」「TVK」「テレ東」のファイルが削除されにくいという不思議な症状が出ていました。

具体的には「BS日テレ」以外のフォルダには昨年12月の番組が残っているのに「BS日テレ」フォルダでは今年4月5日の「はたらく魔王さま」第1話が削除されているという状況でした。

◇◆◇

次は対処方法です。おそらく、次のソースコード変更でこうした症状は出なくなるはずです。src\EpgTimeSrv\EpgTimeSrv\CheckRecFile.cpp の 131行から182行までを全てコメントアウトしてください。変更後のコードは次のような内容となります。

 128 │        }
 129 │    }
 130    /* この次のドライブレベルのチェックだけで十分なので無効化
 131    map<wstring, ULONGLONG>::iterator itr;
 132    for( itr = checkMap.begin(); itr != checkMap.end(); itr++ ){
 133        if( itr->second > 0 ){
 ... 途中省略 ...
 179                }
 180            }
 181        }
 182    } */ // ここまで無効化
 183 184//ドライブレベルでのチェック
 185 │    map<wstring, MOUNT_PATH_INFO>::iterator itrMount;
◇◆◇

ここから先は、何故これで問題が解決するのかという背景事情について知りたい方向けの情報です。興味の無い方にはあまり意味がない情報と思われます。

CCheckRecFile::CheckFreeSpace() は概ね次の 4 つの処理ブロックで構成されていました。

  1. 自動削除対象フォルダ の map (checkMap) の構築と、自動削除対象フォルダをドライブ毎に (ボリュームID を特定して) 集約した map (mountMap) の構築
  2. CheckFreeSpace() 呼び出し時点から 2 時間以内に実行される録画予約が消費するディスク容量を計算し、フォルダ map と ドライブ map に必要容量を登録
  3. フォルダ map (checkMap) の各要素毎に、必要容量と空き容量を比較、空き容量が足りなければ古い TS ファイルを自動削除
  4. ドライブ map (mountMap) の各要素毎に、必要容量と空き容量を比較、空き容量が足りなければ古い TS ファイルを自動削除

上の対策で行ったコメントアウトは、このうち「3.」の「フォルダ map の……自動削除」を無効化して実行しないようにするという変更です。

変更前の CheckFreeSpace() は、「3.」の処理によって「録画予約の保存先フォルダから、最も古い TS ファイルが自動削除される」ことによって空き容量の確保が行われており、「4.」の「ドライブ map の……自動削除」によって自動削除が行われることは、保存先フォルダが空っぽになっても空き容量が不足している場合しか発生しませんでした。

このため、毎日録画が発生する「BS日テレ」のファイルが優先的に削除され、「BS日テレ」の削除&録画(予想よりも少ない容量で済んだ)によって空き容量がある場合には、他の局のフォルダでは自動削除が発動しないという状況が積み重なることによって、冒頭で記述したような不思議な状態となっていたのだろうと理解しています。

恐らく多くのユーザが自動削除に希望する挙動は「同一ドライブ内で、自動削除対象フォルダに設定されているフォルダの中から、録画保存先フォルダに限定せずに最も古い TS ファイルを削除して空き容量を確保する」ことだと思います。

人柱版 10.69 のソースでは、その希望通りの処理が「4.」のブロックに実装されているのですが……残念ながら「3.」のブロックの処理によって「録画保存先フォルダから古い TS ファイルを削除して空き容量を確保する」ことになってしまい、「4.」が発動することはほとんど無いという実装になってしまっています。

であるならば「3.」の処理は無効化して「4.」の処理で自動削除させれば何も問題がなくなるだろうというのが今回の変更の背景です。

◇◆◇

この部分はどうでも良いネタです。CCheckRecFile::CheckFreeSpace() で録画保存先フォルダを優先して自動削除を無効化する場合、フォルダ map (checkMap) はそもそも作る必要がなくなっていたりします。

変更点を最小にするために提示コードではブロックのコメントアウトだけで済むようにしましたが、使わないデータを作成していることが気になる人はそこも削ってみてください。

◇◆◇

ここから先はおまけの情報です。自動削除対象の TS ファイルをリストアップする為に、CCheckRecFile::FindTsFileList() というメソッドが用意されており、次の実装となっていました。

 314void CCheckRecFile::FindTsFileList(wstring findFolder, map<LONGLONG, TS_FILE_INFO>* findList)
 315 │{
 316 │    wstring searchKey = findFolder;
 317 │    searchKey += L"\\*.ts";
 318 319 │    WIN32_FIND_DATA findData;
 320 │    HANDLE find;
 321 322//指定フォルダのファイル一覧取得
 323 │    find = FindFirstFile( searchKey.c_str(), &findData);
 324if ( find == INVALID_HANDLE_VALUE ) {
 325return ;
 326 │    }
 327do{
 328if( (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ){
 329//本当に拡張子DLL?
 330if( IsExt(findData.cFileName, L".ts") == TRUE ){
 331 │                TS_FILE_INFO item;
 332 333 │                Format(item.filePath, L"%s\\%s", findFolder.c_str(), findData.cFileName);
 334 │                transform(item.filePath.begin(), item.filePath.end(), item.filePath.begin(), toupper);
 335 336 │                HANDLE file = _CreateFile( item.filePath.c_str(), GENERIC_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
 337if( file != INVALID_HANDLE_VALUE ){
 338 │                    FILETIME CreationTime;
 339 │                    FILETIME LastAccessTime;
 340 │                    FILETIME LastWriteTime;
 341 │                    GetFileTime(file, &CreationTime, &LastAccessTime, &LastWriteTime);
 342 343 │                    item.fileTime = ((LONGLONG)CreationTime.dwHighDateTime)<<32 | (LONGLONG)CreationTime.dwLowDateTime;
 344 345 │                    DWORD sizeH=0;
 346 │                    DWORD sizeL=0;
 347 │                    sizeL = GetFileSize(file,&sizeH);
 348 349 │                    item.fileSize = ((LONGLONG)sizeH)<<32 | (LONGLONG)sizeL;
 340 351 │                    CloseHandle(file);
 352 353 │                    findList->insert(pair<LONGLONG, TS_FILE_INFO>(item.fileTime, item));
 354 │                }
 355 │            }
 356 │        }
 357 │    }while(FindNextFile(find, &findData));
 358 359 │    FindClose(find);
 360 361 │}

ここでは FindFirstFile() および FindNextFile() で発見した各ファイルの容量と作成日時を確認するために CreateFile() でファイルを逐一開いて確認しているのですが……次のコードのように WIN32_FIND_DATA の findData から直接拾った方が軽いんじゃないかなと思いました。

 314void CCheckRecFile::FindTsFileList(wstring findFolder, map<LONGLONG, TS_FILE_INFO>* findList)
 315 │{
 316 │    wstring searchKey = findFolder;
 317 │    searchKey += L"\\*.ts";
 318 319 │    WIN32_FIND_DATA findData;
 320 │    HANDLE find;
 321 322//指定フォルダのファイル一覧取得
 323 │    find = FindFirstFile( searchKey.c_str(), &findData);
 324if ( find == INVALID_HANDLE_VALUE ) {
 325return ;
 326 │    }
 327do{
 328if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
 329// ディレクトリは対象外
 330continue;
 331 │        }
 332 333if (IsExt(findData.cFileName, L".ts") != TRUE) {
 334// 拡張子 TS でなければ対象外
 335continue;
 336 │        }
 337 338 │        TS_FILE_INFO item;
 339 340 │        Format(item.filePath, L"%s\\%s", findFolder.c_str(), findData.cFileName);
 341 │        transform(item.filePath.begin(), item.filePath.end(), item.filePath.begin(), toupper);
 342 343 │        item.fileTime = ((LONGLONG)(findData.ftCreationTime.dwHighDateTime))<<32 | findData.ftCreationTime.dwLowDateTime;
 344 │        item.fileSize = ((LONGLONG)(findData.nFileSizeHigh))<<32 | findData.nFileSizeLow;
 345 346 │        findList->insert(pair<LONGLONG, TS_FILE_INFO>(item.fileTime, item));
 347 348 │    }while(FindNextFile(find, &findData));
 349 350 │    FindClose(find);
 351 │}

実際には、空き容量が必要容量よりも小さい場合にしか呼ばれない処理なので負荷を気にしてもあまり意味がないのですが、無駄にファイルを開くのはどうなのかなと思ってしまう性分なので、私ならばこう書くという例を載せておきます。

◇◆◇

最後に重箱の隅をつつくような話を。CCheckRecFile::CheckFreeSpace() を約 10 秒周期で呼び出している CReserveManager::BankCheckThread() なのですが……

3096 │UINT WINAPI CReserveManager::BankCheckThread(LPVOID param)
3097 │{
 ... 途中省略 ...
3139//自動削除の確認
3140if( sys->autoDel == TRUE ){
3141 │            countTuijyuChk++;
3142if( countTuijyuChk > 10 ){
3143if( sys->Lock(L"BankCheckThread5") == TRUE){
3144 │                    CCheckRecFile chkFile;
3145 │                    chkFile.SetCheckFolder(&sys->delFolderList);
3146 │                    chkFile.SetDeleteExt(&sys->delExtList);
3147 │                    wstring defRecPath = L"";
3148 │                    GetRecFolderPath(defRecPath);
3149 │                    map<wstring, wstring> protectFile;
3150 │                    sys->recInfoText.GetProtectFiles(&protectFile);
3151 │                    chkFile.CheckFreeSpace(&sys->reserveInfoMap, defRecPath, &protectFile);
3152 │                    sys->UnLock();
3153 │                }
3154 │            }
3155 │            countAutoDelChk = 0;
3156 │        }
 ... 以下省略 ...

本来は次のコードを意図していたように見えます。

3096 │UINT WINAPI CReserveManager::BankCheckThread(LPVOID param)
3097 │{
 ... 途中省略 ...
3139//自動削除の確認
3140if( sys->autoDel == TRUE ){
3141countAutoDelChk++;
3142if( countAutoDelChk > 10 ){
3143if( sys->Lock(L"BankCheckThread5") == TRUE){
3144 │                    CCheckRecFile chkFile;
3145 │                    chkFile.SetCheckFolder(&sys->delFolderList);
3146 │                    chkFile.SetDeleteExt(&sys->delExtList);
3147 │                    wstring defRecPath = L"";
3148 │                    GetRecFolderPath(defRecPath);
3149 │                    map<wstring, wstring> protectFile;
3150 │                    sys->recInfoText.GetProtectFiles(&protectFile);
3151 │                    chkFile.CheckFreeSpace(&sys->reserveInfoMap, defRecPath, &protectFile);
3152 │                    sys->UnLock();
3153countAutoDelChk = 0;
3154 │                }
3155 │            }
3156 │        }
 ... 以下省略 ...

変更点を太字にしています。countTsuijuChk と countAutoDelChk を間違えていると思われる箇所の変更と、countAutoDelChk の 0 へのリセットが実際に自動削除が実行された場合に行われるように移動しています。


1997年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
1998年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
1999年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2000年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2001年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2002年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2003年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2004年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2005年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2006年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2007年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2008年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2009年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2010年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2011年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2012年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2013年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2014年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2015年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2016年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
バックナンバー内のリンクは無保証です

[TOP]