00001 #include <unistd.h>
00002 #include <sys/types.h>
00003 #include <unistd.h>
00004 #include <qsqldatabase.h>
00005 #include <qsqlquery.h>
00006 #include <qstring.h>
00007 #include <qdatetime.h>
00008 #include <qstringlist.h>
00009 #include <qfileinfo.h>
00010
00011 #include <iostream>
00012 #include <cstdlib>
00013 using namespace std;
00014
00015 #include "housekeeper.h"
00016 #include "jobqueue.h"
00017
00018 #include "libmyth/mythcontext.h"
00019 #include "libmyth/mythdbcon.h"
00020 #include "libmyth/util.h"
00021 #include "libmyth/compat.h"
00022
00023 #include "programinfo.h"
00024
00025 #include "libmythtv/eitcache.h"
00026
00027 static bool HouseKeeper_filldb_running = false;
00028
00029 HouseKeeper::HouseKeeper(bool runthread, bool master)
00030 {
00031 isMaster = master;
00032
00033 threadrunning = runthread;
00034 filldbRunning = false;
00035
00036 CleanupMyOldRecordings();
00037
00038 if (runthread)
00039 {
00040 pthread_t hkthread;
00041 pthread_create(&hkthread, NULL, doHouseKeepingThread, this);
00042 }
00043 }
00044
00045 HouseKeeper::~HouseKeeper()
00046 {
00047 }
00048
00049 bool HouseKeeper::wantToRun(const QString &dbTag, int period, int minhour,
00050 int maxhour)
00051 {
00052 bool runOK = false;
00053 unsigned int oneday = 60 * 60 * 24;
00054 int longEnough = 0;
00055
00056 if (period)
00057 longEnough = ((period * oneday) - oneday/2);
00058 else
00059 longEnough = oneday / 8;
00060
00061 QDateTime now = QDateTime::currentDateTime();
00062 QDateTime lastrun;
00063 lastrun.setTime_t(0);
00064
00065 if (minhour < 0)
00066 minhour = 0;
00067 if (maxhour > 23)
00068 maxhour = 23;
00069
00070 MSqlQuery result(MSqlQuery::InitCon());
00071 if (result.isConnected())
00072 {
00073 result.prepare("SELECT lastrun FROM housekeeping WHERE tag = :TAG ;");
00074 result.bindValue(":TAG", dbTag);
00075
00076 if (result.exec() && result.isActive() && result.size() > 0)
00077 {
00078 result.next();
00079 lastrun = result.value(0).toDateTime();
00080
00081 if ((lastrun.secsTo(now) > longEnough) &&
00082 (now.date().day() != lastrun.date().day()))
00083 {
00084 int hour = now.time().hour();
00085
00086 if (((minhour > maxhour) &&
00087 ((hour <= maxhour) || (hour >= minhour))) ||
00088 ((hour >= minhour) && (hour <= maxhour)))
00089 {
00090 int minute = now.time().minute();
00091 if ((hour == maxhour && minute > 30) ||
00092 ((random()%(((maxhour-hour)*12+(60-minute)/5 - 6) + 1)) == 0))
00093 runOK = true;
00094 }
00095 }
00096 }
00097 else
00098 {
00099 result.prepare("INSERT INTO housekeeping(tag,lastrun) "
00100 "values(:TAG ,now());");
00101 result.bindValue(":TAG", dbTag);
00102 result.exec();
00103
00104 runOK = true;
00105 }
00106 }
00107
00108 return runOK;
00109 }
00110
00111 void HouseKeeper::updateLastrun(const QString &dbTag)
00112 {
00113 MSqlQuery result(MSqlQuery::InitCon());
00114 if (result.isConnected())
00115 {
00116 result.prepare("DELETE FROM housekeeping WHERE tag = :TAG ;");
00117 result.bindValue(":TAG", dbTag);
00118 result.exec();
00119
00120 result.prepare("INSERT INTO housekeeping(tag,lastrun) "
00121 "values(:TAG ,now()) ;");
00122 result.bindValue(":TAG", dbTag);
00123 result.exec();
00124 }
00125 }
00126
00127 QDateTime HouseKeeper::getLastRun(const QString &dbTag)
00128 {
00129 QDateTime lastRun;
00130 MSqlQuery result(MSqlQuery::InitCon());
00131
00132 lastRun.setTime_t(0);
00133
00134 result.prepare("SELECT lastrun FROM housekeeping WHERE tag = :TAG ;");
00135 result.bindValue(":TAG", dbTag);
00136
00137 if (result.exec() && result.isActive() && result.size() > 0)
00138 {
00139 result.next();
00140 lastRun = result.value(0).toDateTime();
00141 }
00142
00143 return lastRun;
00144 }
00145
00146 void HouseKeeper::RunHouseKeeping(void)
00147 {
00148 int period, maxhr, minhr;
00149 QString dbTag;
00150
00151 sleep(10);
00152
00153 RunStartupTasks();
00154
00155 while (1)
00156 {
00157 gContext->LogEntry("mythbackend", LP_DEBUG,
00158 "Running housekeeping thread", "");
00159
00160
00161 if (isMaster)
00162 {
00163
00164 if (gContext->GetNumSetting("LogEnabled", 0) &&
00165 gContext->GetNumSetting("LogCleanEnabled", 0))
00166 {
00167 period = gContext->GetNumSetting("LogCleanPeriod",1);
00168 if (wantToRun("LogClean", period, 0, 24))
00169 {
00170 VERBOSE(VB_GENERAL, "Running LogClean");
00171 flushLogs();
00172 updateLastrun("LogClean");
00173 }
00174 }
00175
00176
00177 if (gContext->GetNumSetting("MythFillEnabled", 0))
00178 {
00179 if (HouseKeeper_filldb_running)
00180 {
00181 VERBOSE(VB_GENERAL, "mythfilldatabase still running, "
00182 "skipping checks.");
00183 }
00184 else
00185 {
00186 period = gContext->GetNumSetting("MythFillPeriod", 1);
00187 minhr = gContext->GetNumSetting("MythFillMinHour", -1);
00188 if (minhr == -1)
00189 {
00190 minhr = 0;
00191 maxhr = 24;
00192 }
00193 else
00194 {
00195 maxhr = gContext->GetNumSetting("MythFillMaxHour", 24);
00196 }
00197
00198 bool grabberSupportsNextTime = false;
00199 MSqlQuery result(MSqlQuery::InitCon());
00200 if (result.isConnected())
00201 {
00202 result.prepare("SELECT COUNT(*) FROM videosource "
00203 "WHERE xmltvgrabber IN "
00204 "( 'datadirect', 'technovera',"
00205 " 'schedulesdirect1' );");
00206
00207 if ((result.exec()) &&
00208 (result.isActive()) &&
00209 (result.size() > 0) &&
00210 (result.next()) &&
00211 (result.value(0).toInt() > 0))
00212 grabberSupportsNextTime = true;
00213 }
00214
00215 bool runMythFill = false;
00216 if (grabberSupportsNextTime &&
00217 gContext->GetNumSetting("MythFillGrabberSuggestsTime", 1))
00218 {
00219 QDateTime nextRun = QDateTime::fromString(
00220 gContext->GetSetting("MythFillSuggestedRunTime",
00221 "1970-01-01T00:00:00"), Qt::ISODate);
00222 QDateTime lastRun = getLastRun("MythFillDB");
00223 QDateTime now = QDateTime::currentDateTime();
00224
00225 if ((nextRun < now) &&
00226 (lastRun.secsTo(now) > (3 * 60 * 60)))
00227 runMythFill = true;
00228 }
00229 else if (wantToRun("MythFillDB", period, minhr, maxhr))
00230 {
00231 runMythFill = true;
00232 }
00233
00234 if (runMythFill)
00235 {
00236 QString msg = "Running mythfilldatabase";
00237 gContext->LogEntry("mythbackend", LP_DEBUG, msg, "");
00238 VERBOSE(VB_GENERAL, msg);
00239 runFillDatabase();
00240 updateLastrun("MythFillDB");
00241 }
00242 }
00243 }
00244
00245 if (wantToRun("DailyCleanup", 1, 0, 24)) {
00246 JobQueue::CleanupOldJobsInQueue();
00247 CleanupAllOldInUsePrograms();
00248 CleanupOrphanedLivetvChains();
00249 CleanupRecordedTables();
00250 CleanupProgramListings();
00251 updateLastrun("DailyCleanup");
00252 }
00253 }
00254
00255 dbTag = QString("JobQueueRecover-%1").arg(gContext->GetHostName());
00256 if (wantToRun(dbTag, 1, 0, 24))
00257 {
00258 JobQueue::RecoverOldJobsInQueue();
00259 updateLastrun(dbTag);
00260 }
00261
00262 sleep(300 + (random()%8));
00263 }
00264 }
00265
00266 void HouseKeeper::flushLogs()
00267 {
00268 int numdays = gContext->GetNumSetting("LogCleanDays", 14);
00269 int maxdays = gContext->GetNumSetting("LogCleanMax", 30);
00270
00271 QDateTime days = QDateTime::currentDateTime();
00272 days = days.addDays(0 - numdays);
00273 QDateTime max = QDateTime::currentDateTime();
00274 max = max.addDays(0 - maxdays);
00275
00276 MSqlQuery result(MSqlQuery::InitCon());
00277 if (result.isConnected())
00278 {
00279 result.prepare("DELETE FROM mythlog WHERE "
00280 "acknowledged=1 and logdate < :DAYS ;");
00281 result.bindValue(":DAYS", days);
00282 result.exec();
00283
00284 result.prepare("DELETE FROM mythlog WHERE logdate< :MAX ;");
00285 result.bindValue(":MAX", max);
00286 result.exec();
00287 }
00288 }
00289
00290 void *HouseKeeper::runMFDThread(void *param)
00291 {
00292 HouseKeeper *keep = (HouseKeeper *)param;
00293 keep->RunMFD();
00294 return NULL;
00295 }
00296
00297 void HouseKeeper::RunMFD(void)
00298 {
00299 QString mfpath = gContext->GetSetting("MythFillDatabasePath",
00300 "mythfilldatabase");
00301 QString mfarg = gContext->GetSetting("MythFillDatabaseArgs", "");
00302 QString mflog = gContext->GetSetting("MythFillDatabaseLog",
00303 "/var/log/mythfilldatabase.log");
00304
00305 if (mfpath == "mythfilldatabase")
00306 mfpath = gContext->GetInstallPrefix() + "/bin/mythfilldatabase";
00307
00308 QString command = QString("%1 %2").arg(mfpath).arg(mfarg);
00309
00310 if (mflog.length())
00311 {
00312 bool dir_writable = false;
00313 QFileInfo testFile(mflog);
00314 if (testFile.exists() && testFile.isDir() && testFile.isWritable())
00315 {
00316 mflog += "/mythfilldatabase.log";
00317 testFile.setFile(mflog);
00318 dir_writable = true;
00319 }
00320
00321 if (!dir_writable && !testFile.exists())
00322 {
00323 dir_writable = QFileInfo(testFile.dirPath()).isWritable();
00324 }
00325
00326 if (dir_writable || (testFile.exists() && testFile.isWritable()))
00327 {
00328 command = QString("%1 %2 >>%3 2>&1").arg(mfpath).arg(mfarg)
00329 .arg(mflog);
00330 }
00331 else
00332 {
00333 VERBOSE(VB_IMPORTANT,
00334 QString("Invalid mythfilldatabase log path: %1 is not "
00335 "writable.").arg(mflog));
00336 }
00337 }
00338
00339 myth_system(command.ascii(), MYTH_SYSTEM_DONT_BLOCK_LIRC |
00340 MYTH_SYSTEM_DONT_BLOCK_JOYSTICK_MENU);
00341
00342 HouseKeeper_filldb_running = false;
00343 }
00344
00345 void HouseKeeper::runFillDatabase()
00346 {
00347 if (HouseKeeper_filldb_running)
00348 return;
00349
00350 HouseKeeper_filldb_running = true;
00351
00352 pthread_t housekeep_thread;
00353 pthread_create(&housekeep_thread, NULL, runMFDThread, this);
00354 pthread_detach(housekeep_thread);
00355 }
00356
00357 void HouseKeeper::CleanupMyOldRecordings(void)
00358 {
00359 MSqlQuery query(MSqlQuery::InitCon());
00360
00361 query.prepare("DELETE FROM inuseprograms "
00362 "WHERE hostname = :HOSTNAME AND "
00363 "( recusage = 'recorder' OR recusage LIKE 'Unknown %' );");
00364 query.bindValue(":HOSTNAME", gContext->GetHostName());
00365 query.exec();
00366 }
00367
00368 void HouseKeeper::CleanupAllOldInUsePrograms(void)
00369 {
00370 QDateTime fourHoursAgo = QDateTime::currentDateTime().addSecs(-4 * 60 * 60);
00371 MSqlQuery query(MSqlQuery::InitCon());
00372
00373 query.prepare("DELETE FROM inuseprograms "
00374 "WHERE lastupdatetime < :FOURHOURSAGO ;");
00375 query.bindValue(":FOURHOURSAGO", fourHoursAgo);
00376 query.exec();
00377 }
00378
00379 void HouseKeeper::CleanupOrphanedLivetvChains(void)
00380 {
00381 QDateTime fourHoursAgo = QDateTime::currentDateTime().addSecs(-4 * 60 * 60);
00382 MSqlQuery query(MSqlQuery::InitCon());
00383 MSqlQuery deleteQuery(MSqlQuery::InitCon());
00384
00385
00386 query.prepare("SELECT DISTINCT chainid FROM tvchain "
00387 "WHERE endtime > :FOURHOURSAGO ;");
00388 query.bindValue(":FOURHOURSAGO", fourHoursAgo);
00389
00390 if (!query.exec() || !query.isActive())
00391 {
00392 MythContext::DBError("HouseKeeper Cleaning TVChain Table", query);
00393 return;
00394 }
00395
00396 QString msg, keepChains = "";
00397 while (query.next())
00398 if (keepChains == "")
00399 keepChains = "'" + query.value(0).toString() + "'";
00400 else
00401 keepChains += ", '" + query.value(0).toString() + "'";
00402
00403 if (keepChains.isEmpty())
00404 msg = "DELETE FROM tvchain WHERE endtime < now();";
00405 else
00406 {
00407 msg = QString("DELETE FROM tvchain "
00408 "WHERE chainid NOT IN ( %1 ) AND endtime < now();")
00409 .arg(keepChains);
00410 }
00411 deleteQuery.prepare(msg);
00412 deleteQuery.exec();
00413 }
00414
00415 void HouseKeeper::CleanupRecordedTables(void)
00416 {
00417 MSqlQuery query(MSqlQuery::InitCon());
00418 MSqlQuery deleteQuery(MSqlQuery::InitCon());
00419 int tableIndex = 0;
00420
00421
00422
00423 QString tables[][2] = {
00424 { "recordedprogram", "progstart" },
00425 { "recordedrating", "progstart" },
00426 { "recordedcredits", "progstart" },
00427 { "recordedmarkup", "starttime" },
00428 { "recordedseek", "starttime" },
00429 { "", "" } };
00430 QString table = tables[tableIndex][0];
00431 QString column = tables[tableIndex][1];
00432
00433
00434
00435
00436
00437 QString querystr;
00438 querystr = "CREATE TEMPORARY TABLE IF NOT EXISTS temprecordedcleanup ( "
00439 "chanid int(10) unsigned NOT NULL default '0', "
00440 "starttime datetime NOT NULL default '0000-00-00 00:00:00' "
00441 ");";
00442
00443 if (!query.exec(querystr))
00444 {
00445 MythContext::DBError("Housekeeper Creating Temporary Table", query);
00446 return;
00447 }
00448
00449 while (table != "")
00450 {
00451 query.prepare(QString("TRUNCATE TABLE temprecordedcleanup;"));
00452 if (!query.exec() || !query.isActive())
00453 {
00454 MythContext::DBError("Housekeeper Truncating Temporary Table",
00455 query);
00456 return;
00457 }
00458
00459 query.prepare(QString("INSERT INTO temprecordedcleanup "
00460 "( chanid, starttime ) "
00461 "SELECT DISTINCT chanid, starttime "
00462 "FROM %1;")
00463 .arg(table));
00464
00465 if (!query.exec() || !query.isActive())
00466 {
00467 MythContext::DBError("HouseKeeper Cleaning Recorded Tables", query);
00468 return;
00469 }
00470
00471 query.prepare(QString("SELECT DISTINCT p.chanid, p.starttime "
00472 "FROM temprecordedcleanup p "
00473 "LEFT JOIN recorded r "
00474 "ON p.chanid = r.chanid "
00475 "AND p.starttime = r.%1 "
00476 "WHERE r.chanid IS NULL;").arg(column));
00477 if (!query.exec() || !query.isActive())
00478 {
00479 MythContext::DBError("HouseKeeper Cleaning Recorded Tables", query);
00480 return;
00481 }
00482
00483 deleteQuery.prepare(QString("DELETE FROM %1 "
00484 "WHERE chanid = :CHANID "
00485 "AND starttime = :STARTTIME;")
00486 .arg(table));
00487 while (query.next())
00488 {
00489 deleteQuery.bindValue(":CHANID", query.value(0).toString());
00490 deleteQuery.bindValue(":STARTTIME", query.value(1).toString());
00491 deleteQuery.exec();
00492 }
00493
00494 tableIndex++;
00495 table = tables[tableIndex][0];
00496 column = tables[tableIndex][1];
00497 }
00498
00499 if (!query.exec("DROP TABLE temprecordedcleanup;"))
00500 MythContext::DBError("Housekeeper Dropping Temporary Table", query);
00501
00502 }
00503
00504 void HouseKeeper::CleanupProgramListings(void)
00505 {
00506
00507 MSqlQuery query(MSqlQuery::InitCon());
00508 QString querystr;
00509
00510 int offset = 7;
00511
00512 query.prepare("DELETE FROM oldprogram WHERE airdate < "
00513 "DATE_SUB(CURRENT_DATE, INTERVAL 320 DAY);");
00514 query.exec();
00515
00516 query.prepare("REPLACE INTO oldprogram (oldtitle,airdate) "
00517 "SELECT title,starttime FROM program "
00518 "WHERE starttime < NOW() AND manualid = 0 "
00519 "GROUP BY title;");
00520 query.exec();
00521
00522 query.prepare("DELETE FROM program WHERE starttime <= "
00523 "DATE_SUB(CURRENT_DATE, INTERVAL :OFFSET DAY);");
00524 query.bindValue(":OFFSET", offset);
00525 query.exec();
00526
00527 query.prepare("DELETE FROM programrating WHERE starttime <= "
00528 "DATE_SUB(CURRENT_DATE, INTERVAL :OFFSET DAY);");
00529 query.bindValue(":OFFSET", offset);
00530 query.exec();
00531
00532 query.prepare("DELETE FROM programgenres WHERE starttime <= "
00533 "DATE_SUB(CURRENT_DATE, INTERVAL :OFFSET DAY);");
00534 query.bindValue(":OFFSET", offset);
00535 query.exec();
00536
00537 query.prepare("DELETE FROM credits WHERE starttime <= "
00538 "DATE_SUB(CURRENT_DATE, INTERVAL :OFFSET DAY);");
00539 query.bindValue(":OFFSET", offset);
00540 query.exec();
00541
00542 query.prepare("DELETE FROM record WHERE (type = :SINGLE "
00543 "OR type = :OVERRIDE OR type = :DONTRECORD) "
00544 "AND enddate < CURDATE();");
00545 query.bindValue(":SINGLE", kSingleRecord);
00546 query.bindValue(":OVERRIDE", kOverrideRecord);
00547 query.bindValue(":DONTRECORD", kDontRecord);
00548 query.exec();
00549
00550 MSqlQuery findq(MSqlQuery::InitCon());
00551 findq.prepare("SELECT record.recordid FROM record "
00552 "LEFT JOIN oldfind ON oldfind.recordid = record.recordid "
00553 "WHERE type = :FINDONE AND oldfind.findid IS NOT NULL;");
00554 findq.bindValue(":FINDONE", kFindOneRecord);
00555 findq.exec();
00556
00557 if (findq.isActive() && findq.size() > 0)
00558 {
00559 while (findq.next())
00560 {
00561 query.prepare("DELETE FROM record WHERE recordid = :RECORDID;");
00562 query.bindValue(":RECORDID", findq.value(0).toInt());
00563 query.exec();
00564 }
00565 }
00566 query.prepare("DELETE FROM oldfind WHERE findid < TO_DAYS(NOW()) - 14;");
00567 query.exec();
00568
00569 int cleanOldRecorded = gContext->GetNumSetting( "CleanOldRecorded", 10);
00570
00571 query.prepare("DELETE FROM oldrecorded WHERE "
00572 "recstatus <> :RECORDED AND duplicate = 0 AND "
00573 "endtime < DATE_SUB(CURRENT_DATE, INTERVAL :CLEAN DAY);");
00574 query.bindValue(":RECORDED", rsRecorded);
00575 query.bindValue(":CLEAN", cleanOldRecorded);
00576 query.exec();
00577
00578 }
00579
00580
00581 void HouseKeeper::RunStartupTasks(void)
00582 {
00583 if (isMaster)
00584 EITCache::ClearChannelLocks();
00585 }
00586
00587
00588 void *HouseKeeper::doHouseKeepingThread(void *param)
00589 {
00590 HouseKeeper *hkeeper = (HouseKeeper*)param;
00591 hkeeper->RunHouseKeeping();
00592
00593 return NULL;
00594 }
00595
00596