EpgDataCap_Bon ツール群の人柱版 10.69 を手元で運用していたところ、EpgTimer の「HDDの空きが少ない場合、古い録画ファイルを削除する」の設定を有効にしていて不思議な挙動を示したので、その件について書いておくことにします。
まずは症状の説明から。私は次のフォルダ構成で EDCB を運用していました。
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 つの処理ブロックで構成されていました。
上の対策で行ったコメントアウトは、このうち「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() というメソッドが用意されており、次の実装となっていました。
314 │void 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);
324 │ if ( find == INVALID_HANDLE_VALUE ) {
325 │ return ;
326 │ }
327 │ do{
328 │ if( (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ){
329 │ //本当に拡張子DLL?
330 │ if( 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 );
337 │ if( 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 から直接拾った方が軽いんじゃないかなと思いました。
314 │void 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);
324 │ if ( find == INVALID_HANDLE_VALUE ) {
325 │ return ;
326 │ }
327 │ do{
328 │ if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
329 │ // ディレクトリは対象外
330 │ continue;
331 │ }
332 │
333 │ if (IsExt(findData.cFileName, L".ts") != TRUE) {
334 │ // 拡張子 TS でなければ対象外
335 │ continue;
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 │ //自動削除の確認
3140 │ if( sys->autoDel == TRUE ){
3141 │ countTuijyuChk++;
3142 │ if( countTuijyuChk > 10 ){
3143 │ if( 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 │ //自動削除の確認
3140 │ if( sys->autoDel == TRUE ){
3141 │ countAutoDelChk++;
3142 │ if( countAutoDelChk > 10 ){
3143 │ if( 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 │ countAutoDelChk = 0;
3154 │ }
3155 │ }
3156 │ }
... 以下省略 ...
変更点を太字にしています。countTsuijuChk と countAutoDelChk を間違えていると思われる箇所の変更と、countAutoDelChk の 0 へのリセットが実際に自動削除が実行された場合に行われるように移動しています。