6 # include <sys/param.h>
8 # include <sys/mount.h>
44 #define LOC QString("AutoExpire: ")
45 #define LOC_ERR QString("AutoExpire Error: ")
52 #define SPACE_TOO_BIG_KB (3*1024*1024)
74 m_expireThreadRun(
true)
123 LOG(VB_FILE, LOG_INFO,
LOC +
"CalcParams()");
125 QList<FileSystemInfo> fsInfos;
143 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Filesystem Info cache is empty, unable "
144 "to calculate necessary parameters.");
148 uint64_t maxKBperMin = 0;
149 uint64_t extraKB =
static_cast<uint64_t
>
153 QMap<int, uint64_t> fsMap;
154 QMap<int, vector<int> > fsEncoderMap;
163 fsEncoderMap[*ueit].push_back(ueit.key());
168 QList<FileSystemInfo>::iterator fsit;
169 for (fsit = fsInfos.begin(); fsit != fsInfos.end(); ++fsit)
171 if (fsMap.contains(fsit->getFSysID()))
174 fsMap[fsit->getFSysID()] = 0;
175 uint64_t thisKBperMin = 0;
178 for (
auto unknownfs : qAsConst(fsEncoderMap[-1]))
179 fsEncoderMap[fsit->getFSysID()].push_back(unknownfs);
181 if (fsEncoderMap.contains(fsit->getFSysID()))
183 LOG(VB_FILE, LOG_INFO,
184 QString(
"fsID #%1: Total: %2 GB Used: %3 GB Free: %4 GB")
185 .
arg(fsit->getFSysID())
186 .arg(fsit->getTotalSpace() / 1024.0 / 1024.0, 7,
'f', 1)
187 .arg(fsit->getUsedSpace() / 1024.0 / 1024.0, 7,
'f', 1)
188 .arg(fsit->getFreeSpace() / 1024.0 / 1024.0, 7,
'f', 1));
190 for (
auto cardid : qAsConst(fsEncoderMap[fsit->getFSysID()]))
197 LOG(VB_FILE, LOG_INFO,
LOC +
198 QString(
"Cardid %1: is not recording, removing it "
199 "from used list.").
arg(cardid));
208 maxBitrate = 19500000LL;
209 thisKBperMin += (maxBitrate*((uint64_t)15))>>11;
210 LOG(VB_FILE, LOG_INFO, QString(
" Cardid %1: max bitrate "
211 "%2 Kb/sec, fsID %3 max is now %4 KB/min")
214 .arg(fsit->getFSysID())
218 fsMap[fsit->getFSysID()] = thisKBperMin;
220 if (thisKBperMin > maxKBperMin)
222 LOG(VB_FILE, LOG_INFO,
223 QString(
" Max of %1 KB/min for fsID %2 is higher "
224 "than the existing Max of %3 so we'll use this Max instead")
225 .
arg(thisKBperMin).
arg(fsit->getFSysID()).arg(maxKBperMin));
226 maxKBperMin = thisKBperMin;
232 uint expireFreq = 15;
236 expireFreq = std::max(3U, std::min(expireFreq, 15U));
239 double expireMinGB = ((maxKBperMin + maxKBperMin/3)
240 * expireFreq + extraKB) >> 20;
241 LOG(VB_GENERAL, LOG_NOTICE,
LOC +
242 QString(
"CalcParams(): Max required Free Space: %1 GB w/freq: %2 min")
243 .
arg(expireMinGB, 0,
'f', 1).
arg(expireFreq));
249 QMap<int, uint64_t>::iterator it = fsMap.begin();
250 while (it != fsMap.end())
285 if (curTime >= next_expire)
307 if ((curTime.time().minute() % 2) == 0)
311 if (curTime >= next_expire)
313 LOG(VB_FILE, LOG_INFO,
LOC +
"Running now!");
322 else if (maxAge == 0)
332 Sleep(60s - std::chrono::milliseconds(timer.elapsed()));
344 if (sleepTime <= 0ms)
348 std::chrono::milliseconds timeleft = sleepTime;
363 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"ExpireLiveTV(%1)").
arg(
type));
376 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"ExpireOldDeleted()"));
389 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"ExpireQuickDeleted()"));
403 QList<FileSystemInfo> fsInfos;
404 QList<FileSystemInfo>::iterator fsit;
406 LOG(VB_FILE, LOG_INFO,
LOC +
"ExpireRecordings()");
413 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Filesystem Info cache is empty, unable "
414 "to determine what Recordings to expire");
421 QMap <int, bool> truncateMap;
424 "FROM inuseprograms "
425 "WHERE recusage = 'truncatingdelete' "
426 "AND lastupdatetime > DATE_ADD(NOW(), INTERVAL -2 MINUTE);");
439 LOG(VB_FILE, LOG_INFO,
LOC +
440 QString(
"%1:%2 has an in-progress truncating delete.")
441 .
arg(rechost).
arg(recdir));
443 for (fsit = fsInfos.begin(); fsit != fsInfos.end(); ++fsit)
445 if ((fsit->getHostname() == rechost) &&
446 (fsit->getPath() == recdir))
448 truncateMap[fsit->getFSysID()] =
true;
455 QMap <int, bool> fsMap;
456 for (fsit = fsInfos.begin(); fsit != fsInfos.end(); ++fsit)
458 if (fsMap.contains(fsit->getFSysID()))
461 fsMap[fsit->getFSysID()] =
true;
463 LOG(VB_FILE, LOG_INFO,
464 QString(
"fsID #%1: Total: %2 GB Used: %3 GB Free: %4 GB")
465 .
arg(fsit->getFSysID())
466 .arg(fsit->getTotalSpace() / 1024.0 / 1024.0, 7,
'f', 1)
467 .arg(fsit->getUsedSpace() / 1024.0 / 1024.0, 7,
'f', 1)
468 .arg(fsit->getFreeSpace() / 1024.0 / 1024.0, 7,
'f', 1));
470 if ((fsit->getTotalSpace() == -1) || (fsit->getUsedSpace() == -1))
472 LOG(VB_FILE, LOG_ERR,
LOC +
473 QString(
"fsID #%1 has invalid info, AutoExpire cannot run for "
474 "this filesystem. Continuing on to next...")
475 .
arg(fsit->getFSysID()));
476 LOG(VB_FILE, LOG_INFO, QString(
"Directories on filesystem ID %1:")
477 .
arg(fsit->getFSysID()));
478 QList<FileSystemInfo>::iterator fsit2;
479 for (fsit2 = fsInfos.begin(); fsit2 != fsInfos.end(); ++fsit2)
481 if (fsit2->getFSysID() == fsit->getFSysID())
483 LOG(VB_FILE, LOG_INFO, QString(
" %1:%2")
484 .
arg(fsit2->getHostname()).arg(fsit2->getPath()));
491 if (truncateMap.contains(fsit->getFSysID()))
493 LOG(VB_FILE, LOG_INFO,
494 QString(
" fsid %1 has a truncating delete in progress, "
495 "AutoExpire cannot run for this filesystem until the "
496 "delete has finished. Continuing on to next...")
497 .
arg(fsit->getFSysID()));
501 if (std::max((int64_t)0LL, fsit->getFreeSpace()) <
504 LOG(VB_FILE, LOG_INFO,
505 QString(
" Not Enough Free Space! We want %1 MB")
509 QList<FileSystemInfo>::iterator fsit2;
511 LOG(VB_FILE, LOG_INFO,
512 QString(
" Directories on filesystem ID %1:")
513 .
arg(fsit->getFSysID()));
515 for (fsit2 = fsInfos.begin(); fsit2 != fsInfos.end(); ++fsit2)
517 if (fsit2->getFSysID() == fsit->getFSysID())
519 LOG(VB_FILE, LOG_INFO, QString(
" %1:%2")
520 .
arg(fsit2->getHostname()).arg(fsit2->getPath()));
521 dirList[fsit2->getHostname() +
":" + fsit2->getPath()] = 1;
525 LOG(VB_FILE, LOG_INFO,
526 " Searching for files expirable in these directories");
528 auto it = expireList.begin();
529 while ((it != expireList.end()) &&
530 (std::max((int64_t)0LL, fsit->getFreeSpace()) <
536 LOG(VB_FILE, LOG_INFO, QString(
" Checking %1 => %2")
538 .arg(
p->GetTitle()));
542 bool foundFile =
false;
550 ((
p->GetHostname() == myHostName) &&
560 if (!foundFile && (
p->GetHostname() != myHostName))
565 if (
file.startsWith(
"/"))
567 p->SetPathname(
file);
568 p->SetHostname(myHostName);
575 LOG(VB_FILE, LOG_ERR,
LOC +
576 QString(
" ERROR: Can't find file for %1")
582 QFileInfo vidFile(
p->GetPathname());
583 if (
dirList.contains(
p->GetHostname() +
':' + vidFile.path()))
585 fsit->setUsedSpace(fsit->getUsedSpace()
586 - (
p->GetFilesize() / 1024));
587 deleteList.push_back(
p);
589 LOG(VB_FILE, LOG_INFO,
590 QString(
" FOUND file expirable. "
591 "%1 is located at %2 which is on fsID #%3. "
592 "Adding to deleteList. After deleting we "
593 "should have %4 MB free on this filesystem.")
595 .arg(
p->GetPathname()).arg(fsit->getFSysID())
596 .arg(fsit->getFreeSpace() / 1024));
615 if (deleteList.empty())
617 LOG(VB_FILE, LOG_INFO,
LOC +
"SendDeleteMessages. Nothing to expire.");
621 LOG(VB_FILE, LOG_INFO,
LOC +
622 "SendDeleteMessages, cycling through deleteList.");
623 auto it = deleteList.begin();
624 while (it != deleteList.end())
626 msg = QString(
"%1Expiring %2 MB for %3 => %4")
628 .arg(((*it)->GetFilesize() >> 20))
632 LOG(VB_GENERAL, LOG_NOTICE, msg);
635 MythEvent me(QString(
"AUTO_EXPIRE %1 %2").
arg((*it)->GetChanID())
650 QMap<QString, int> maxEpisodes;
651 QMap<QString, int>::Iterator maxIter;
652 QMap<QString, int> episodeParts;
657 "FROM record WHERE maxepisodes > 0 "
658 "ORDER BY recordid ASC, maxepisodes DESC");
662 LOG(VB_FILE, LOG_INFO,
LOC +
663 QString(
"Found %1 record profiles using max episode expiration")
667 LOG(VB_FILE, LOG_INFO, QString(
" %1 (%2 for rec id %3)")
675 LOG(VB_FILE, LOG_INFO,
LOC +
676 "Checking episode count for each recording profile using max episodes");
677 for (maxIter = maxEpisodes.begin(); maxIter != maxEpisodes.end(); ++maxIter)
679 query.
prepare(
"SELECT chanid, starttime, title, progstart, progend, "
682 "WHERE recordid = :RECID AND preserve = 0 "
683 "AND recgroup NOT IN ('LiveTV', 'Deleted') "
684 "ORDER BY starttime DESC;");
693 LOG(VB_FILE, LOG_INFO, QString(
" Recordid %1 has %2 recordings.")
708 episodeKey = QString(
"%1_%2_%3")
714 (!episodeParts.contains(episodeKey)) &&
718 QString(
"%1Deleting %2 at %3 => %4. "
719 "Too many episodes, we only want to keep %5.")
723 .arg(
title).arg(*maxIter);
725 LOG(VB_GENERAL, LOG_NOTICE, msg);
734 msg = QString(
"DELETE_RECORDING %1 %2")
746 if (episodeParts.contains(episodeKey))
748 episodeParts[episodeKey] = episodeParts[episodeKey] + 1;
752 episodeParts[episodeKey] = 1;
794 QString msg =
"MythTV AutoExpire List ";
795 if (expHost !=
"ALL")
796 msg += QString(
"for '%1' ").arg(expHost);
797 msg +=
"(programs listed in order of expiration)";
798 std::cout << msg.toLocal8Bit().constData() << std::endl;
800 for (
auto *first : expireList)
802 if (expHost !=
"ALL" && first->GetHostname() != expHost)
808 QString outstr = QString(
"%1 %2 MB %3 [%4]")
810 .arg(QString::number(first->GetFilesize() >> 20)
811 .rightJustified(5,
' ',
true))
813 .leftJustified(24,
' ',
true))
814 .arg(QString::number(first->GetRecordingPriority())
815 .rightJustified(3,
' ',
true));
816 QByteArray out = outstr.toLocal8Bit();
818 std::cout << out.constData() << std::endl;
840 strList << QString::number(expireList.size());
842 for (
auto & info : expireList)
843 info->ToStringList(strList);
864 for (
auto & info : expireList)
876 while (!expireList.empty())
879 pginfo = expireList.back();
881 expireList.pop_back();
903 msg =
"Adding programs expirable in Oldest First order";
904 where =
"autoexpire > 0";
906 orderby =
"recorded.watched DESC, ";
907 orderby +=
"starttime ASC";
910 msg =
"Adding programs expirable in Lowest Priority First order";
911 where =
"autoexpire > 0";
913 orderby =
"recorded.watched DESC, ";
914 orderby +=
"recorded.recpriority ASC, starttime ASC";
917 msg =
"Adding programs expirable in Weighted Time Priority order";
918 where =
"autoexpire > 0";
920 orderby =
"recorded.watched DESC, ";
921 orderby += QString(
"DATE_ADD(starttime, INTERVAL '%1' * "
922 "recorded.recpriority DAY) ASC")
926 msg =
"Adding Short LiveTV programs in starttime order";
927 where =
"recgroup = 'LiveTV' "
928 "AND endtime < DATE_ADD(starttime, INTERVAL '30' SECOND) "
929 "AND endtime <= DATE_ADD(NOW(), INTERVAL '-5' MINUTE) ";
930 orderby =
"starttime ASC";
933 msg =
"Adding LiveTV programs in starttime order";
934 where = QString(
"recgroup = 'LiveTV' "
935 "AND endtime <= DATE_ADD(NOW(), INTERVAL '-%1' DAY) ")
937 orderby =
"starttime ASC";
942 msg = QString(
"Adding programs deleted more than %1 days ago")
944 where = QString(
"recgroup = 'Deleted' "
945 "AND lastmodified <= DATE_ADD(NOW(), INTERVAL '-%1' DAY) ")
947 orderby =
"starttime ASC";
952 msg = QString(
"Adding programs deleted more than 5 minutes ago");
953 where = QString(
"recgroup = 'Deleted' "
954 "AND lastmodified <= DATE_ADD(NOW(), INTERVAL '-5' MINUTE) ");
955 orderby =
"lastmodified ASC";
958 msg =
"Adding deleted programs in FIFO order";
959 where =
"recgroup = 'Deleted'";
960 orderby =
"lastmodified ASC";
964 LOG(VB_FILE, LOG_INFO,
LOC +
"FillDBOrdered: " + msg);
967 QString querystr = QString(
968 "SELECT recorded.chanid, starttime "
970 "LEFT JOIN channel ON recorded.chanid = channel.chanid "
971 "WHERE %1 AND deletepending = 0 "
972 "ORDER BY autoexpire DESC, %2").arg(where).arg(orderby);
986 LOG(VB_FILE, LOG_INFO,
LOC +
987 QString(
" Skipping %1 at %2 because it is in Don't Expire "
993 LOG(VB_FILE, LOG_INFO,
LOC +
994 QString(
" Skipping %1 at %2 because it is already in Expire "
1000 auto *pginfo =
new ProgramInfo(chanid, recstartts);
1001 if (pginfo->GetChanID())
1003 LOG(VB_FILE, LOG_INFO,
LOC + QString(
" Adding %1 at %2")
1005 expireList.push_back(pginfo);
1009 LOG(VB_FILE, LOG_INFO,
LOC +
1010 QString(
" Skipping %1 at %2 "
1011 "because it could not be loaded from the DB")
1037 QString msg = QString(
"Cardid %1: is starting a recording on")
1040 msg.append(
" an unknown fsID soon.");
1042 msg.append(QString(
" fsID %2 soon.").
arg(fsID));
1043 LOG(VB_FILE, LOG_INFO,
LOC + msg);
1071 "SELECT chanid, starttime, lastupdatetime, recusage, hostname "
1072 "FROM inuseprograms");
1077 LOG(VB_FILE, LOG_INFO,
LOC +
"Adding Programs to 'Do Not Expire' List");
1086 if (lastupdate.secsTo(curTime) < 2 * 60 * 60)
1088 QString key = QString(
"%1_%2")
1089 .arg(chanid).arg(recstartts.toString(
Qt::ISODate));
1091 LOG(VB_FILE, LOG_INFO, QString(
" %1 at %2 in use by %3 on %4")
1102 uint chanid,
const QDateTime &recstartts)
const
1104 QString key = QString(
"%1_%2")
1105 .arg(chanid).arg(recstartts.toString(
Qt::ISODate));
1113 return std::any_of(expireList.cbegin(), expireList.cend(),
1114 [chanid,&recstartts](
auto *info)
1115 { return ((info->GetChanID() == chanid) &&
1116 (info->GetRecordingStartTime() == recstartts)); } );