00001 #include <unistd.h>
00002
00003 #include <mythtv/mythcontext.h>
00004 #include <mythtv/mythdbcon.h>
00005 #include <mythtv/compat.h>
00006
00007 #include "weatherScreen.h"
00008 #include "weatherSource.h"
00009
00010 QStringList WeatherSource::probeTypes(QProcess *proc)
00011 {
00012 QStringList types;
00013
00014 proc->addArgument("-t");
00015 if (!proc->start())
00016 {
00017 VERBOSE(VB_IMPORTANT,
00018 "cannot run script " + proc->arguments().join(" "));
00019 return 0;
00020 }
00021 while (proc->isRunning());
00022
00023 if (!proc->normalExit() || proc->exitStatus())
00024 {
00025 VERBOSE(VB_IMPORTANT, "Error Running Script");
00026 VERBOSE(VB_IMPORTANT, proc->readStderr());
00027 return 0;
00028 }
00029 QString tempStr;
00030
00031 while (proc->canReadLineStdout())
00032 {
00033 tempStr = proc->readLineStdout();
00034 types << tempStr;
00035 }
00036
00037 if (types.size() == 0)
00038 {
00039 VERBOSE(VB_IMPORTANT, "invalid output from -t option");
00040 return 0;
00041 }
00042
00043 return types;
00044 }
00045
00046 bool WeatherSource::probeTimeouts(QProcess *proc, uint &updateTimeout,
00047 uint &scriptTimeout)
00048 {
00049 proc->addArgument("-T");
00050 bool *ok = new bool;
00051 updateTimeout = 0;
00052 scriptTimeout = 0;
00053
00054 if (!proc->start())
00055 {
00056 VERBOSE(VB_IMPORTANT,
00057 "cannot run script " + proc->arguments().join(" "));
00058 return false;
00059 }
00060
00061 while (proc->isRunning());
00062 if (!proc->normalExit() || proc->exitStatus() )
00063 {
00064 VERBOSE(VB_IMPORTANT, "Error Running Script");
00065 VERBOSE(VB_IMPORTANT, proc->readStderr());
00066 return false;
00067 }
00068
00069 if (!proc->canReadLineStdout())
00070 {
00071 VERBOSE(VB_IMPORTANT, "Invalid Script Output!");
00072 return false;
00073 }
00074
00075 QStringList temp =
00076 QStringList::split(',', QString(proc->readLineStdout()));
00077 if (temp.size() != 2)
00078 {
00079 VERBOSE(VB_IMPORTANT, "Invalid Script Output!");
00080 return false;
00081 }
00082
00083 uint i = temp[0].toUInt(ok);
00084 updateTimeout = *ok ? i * 1000 : DEFAULT_UPDATE_TIMEOUT;
00085
00086 i = temp[1].toUInt(ok);
00087 scriptTimeout = *ok ? i * 1000 : DEFAULT_SCRIPT_TIMEOUT;
00088 delete ok;
00089 return true;
00090 }
00091
00092 bool WeatherSource::probeInfo(QProcess *proc, QString &name, QString &version,
00093 QString &author, QString &email)
00094 {
00095
00096
00097
00098 proc->addArgument("-v");
00099 if (!proc->start())
00100 {
00101 VERBOSE(VB_IMPORTANT,
00102 "cannot run script " + proc->arguments().join(" "));
00103 return false;
00104 }
00105 while (proc->isRunning());
00106
00107 if (!proc->normalExit() || proc->exitStatus())
00108 {
00109 VERBOSE(VB_IMPORTANT, "Error Running Script");
00110 VERBOSE(VB_IMPORTANT, proc->readStderr());
00111 return false;
00112 }
00113
00114 if (!proc->canReadLineStdout())
00115 {
00116 VERBOSE(VB_IMPORTANT, "Invalid Script Output!");
00117 return false;
00118 }
00119
00120 QStringList temp = QStringList::split(',', QString(proc->readLineStdout()));
00121 if (temp.size() != 4)
00122 {
00123 VERBOSE(VB_IMPORTANT, "Invalid Script Output!");
00124 return false;
00125 }
00126
00127 name = temp[0];
00128 version = temp[1];
00129 author = temp[2];
00130 email = temp[3];
00131 return true;
00132 }
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144 ScriptInfo *WeatherSource::probeScript(const QFileInfo &fi)
00145 {
00146 QStringList temp;
00147 QProcess *proc;
00148
00149 if (fi.isReadable() && fi.isExecutable())
00150 proc = new QProcess(fi.absFilePath());
00151 else
00152 return 0;
00153
00154 proc->setWorkingDirectory(fi.dir(true));
00155 ScriptInfo *info = new ScriptInfo;
00156 info->file = new QFileInfo(fi);
00157 if (!WeatherSource::probeInfo(proc, info->name, info->version, info->author,
00158 info->email))
00159 {
00160 delete proc;
00161 delete info->file;
00162 delete info;
00163 return 0;
00164 }
00165
00166 MSqlQuery db(MSqlQuery::InitCon());
00167 QString query =
00168 "SELECT sourceid, source_name, update_timeout, retrieve_timeout, "
00169 "path, author, version, email, types FROM weathersourcesettings "
00170 "WHERE hostname = :HOST AND source_name = :NAME;";
00171 db.prepare(query);
00172 db.bindValue(":HOST", gContext->GetHostName());
00173 db.bindValue(":NAME", info->name);
00174 db.exec();
00175
00176 if (db.size() == 1)
00177 {
00178 db.next();
00179 info->id = db.value(0).toInt();
00180 info->updateTimeout = db.value(2).toUInt() * 1000;
00181 info->scriptTimeout = db.value(3).toUInt() * 1000;
00182
00183
00184 QString dbver = db.value(6).toString();
00185 if (dbver == info->version)
00186 {
00187 info->types = QStringList::split(",", db.value(8).toString());
00188 }
00189 else
00190 {
00191
00192 VERBOSE(VB_GENERAL, "New version of " + info->name + " found");
00193 query = "UPDATE weathersourcesettings SET source_name = :NAME, "
00194 "path = :PATH, author = :AUTHOR, version = :VERSION, "
00195 "email = :EMAIL, types = :TYPES WHERE sourceid = :ID";
00196 db.prepare(query);
00197
00198
00199 db.bindValue(":NAME", info->name);
00200 db.bindValue(":PATH", info->file->absFilePath());
00201 db.bindValue(":AUTHOR", info->author);
00202 db.bindValue(":VERSION", info->version);
00203
00204 proc->clearArguments();
00205 proc->addArgument(fi.absFilePath());
00206 info->types = WeatherSource::probeTypes(proc);
00207 db.bindValue(":TYPES", info->types.join(","));
00208 db.bindValue(":ID", info->id);
00209 db.bindValue(":EMAIL", info->email);
00210 if (!db.exec())
00211 {
00212 VERBOSE(VB_IMPORTANT, db.lastError().text());
00213 delete proc;
00214 delete info->file;
00215 delete info;
00216 return 0;
00217 }
00218 }
00219 }
00220 else if (db.size() == 0)
00221 {
00222
00223 query = "INSERT INTO weathersourcesettings "
00224 "(hostname, source_name, update_timeout, retrieve_timeout, "
00225 "path, author, version, email, types) "
00226 "VALUES (:HOST, :NAME, :UPDATETO, :RETTO, :PATH, :AUTHOR, "
00227 ":VERSION, :EMAIL, :TYPES);";
00228 proc->clearArguments();
00229 proc->addArgument(fi.absFilePath());
00230 if (!WeatherSource::probeTimeouts(proc, info->updateTimeout,
00231 info->scriptTimeout))
00232 {
00233 delete proc;
00234 delete info->file;
00235 delete info;
00236 return 0;
00237 }
00238 db.prepare(query);
00239 db.bindValue(":NAME", info->name);
00240 db.bindValue(":HOST", gContext->GetHostName());
00241 db.bindValue(":UPDATETO", QString::number(info->updateTimeout / 1000));
00242 db.bindValue(":RETTO", QString::number(info->scriptTimeout / 1000));
00243 db.bindValue(":PATH", info->file->absFilePath());
00244 db.bindValue(":AUTHOR", info->author);
00245 db.bindValue(":VERSION", info->version);
00246 db.bindValue(":EMAIL", info->email);
00247 proc->clearArguments();
00248 proc->addArgument(fi.absFilePath());
00249 info->types = WeatherSource::probeTypes(proc);
00250 db.bindValue(":TYPES", info->types.join(","));
00251 if (!db.exec())
00252 {
00253 VERBOSE(VB_IMPORTANT, db.lastError().text());
00254 delete proc;
00255 delete info->file;
00256 delete info;
00257 return 0;
00258 }
00259 query = "SELECT sourceid FROM weathersourcesettings "
00260 "WHERE source_name = :NAME AND hostname = :HOST;";
00261
00262
00263 db.prepare(query);
00264 db.bindValue(":HOST", gContext->GetHostName());
00265 db.bindValue(":NAME", info->name);
00266 if (!db.exec())
00267 {
00268 VERBOSE(VB_IMPORTANT, db.lastError().text());
00269 delete proc;
00270 delete info->file;
00271 delete info;
00272 return 0;
00273 }
00274 db.next();
00275 info->id = db.value(0).toInt();
00276 }
00277 else
00278 {
00279 VERBOSE(VB_IMPORTANT, "Invalid response from database");
00280 delete proc;
00281 delete info->file;
00282 delete info;
00283 return 0;
00284 }
00285 delete proc;
00286 return info;
00287 }
00288
00289
00290
00291
00292
00293 WeatherSource::WeatherSource(ScriptInfo *info)
00294 {
00295 if (!info)
00296 {
00297 m_ready = false;
00298 return;
00299 }
00300 m_ready = true;
00301 m_inuse = true;
00302 m_units = SI_UNITS;
00303 m_info = info;
00304 m_connectCnt = 0;
00305
00306 QDir dir(MythContext::GetConfDir());
00307 if (!dir.exists("MythWeather"))
00308 dir.mkdir("MythWeather");
00309 dir.cd("MythWeather");
00310 if (!dir.exists(info->name))
00311 dir.mkdir(info->name);
00312 dir.cd(info->name);
00313 m_dir = dir.absPath();
00314
00315 m_scriptTimer = new QTimer(this);
00316 connect( m_scriptTimer, SIGNAL(timeout()),
00317 this, SLOT(scriptTimeout()));
00318
00319 m_updateTimer = new QTimer(this);
00320 connect( m_updateTimer, SIGNAL(timeout()),
00321 this, SLOT(updateTimeout()));
00322 m_proc = new QProcess(info->file->absFilePath());
00323 m_proc->setWorkingDirectory(QDir(gContext->GetShareDir() +
00324 "mythweather/scripts/"));
00325 connect(this, SIGNAL(killProcess()), m_proc, SLOT(kill()));
00326 }
00327
00328 WeatherSource::WeatherSource(const QString &filename)
00329 {
00330 m_ready = false;
00331
00332 m_connectCnt = 0;
00333 m_scriptTimer = new QTimer(this);
00334 connect( m_scriptTimer, SIGNAL(timeout()),
00335 this, SLOT(scriptTimeout()));
00336
00337 m_updateTimer = new QTimer(this);
00338 connect( m_updateTimer, SIGNAL(timeout()),
00339 this, SLOT(updateTimeout()));
00340
00341 m_units = SI_UNITS;
00342
00343 const QFileInfo fi(filename);
00344 ScriptInfo *info = WeatherSource::probeScript(fi);
00345
00346 if (info)
00347 {
00348 m_proc = new QProcess(filename);
00349 m_proc->setWorkingDirectory(QDir(gContext->GetShareDir() +
00350 "mythweather/scripts/"));
00351 connect(this, SIGNAL(killProcess()),
00352 m_proc, SLOT(kill()));
00353 m_ready = true;
00354 m_info = info;
00355 }
00356 else
00357 VERBOSE(VB_IMPORTANT, "Error probing script");
00358 }
00359
00360 WeatherSource::~WeatherSource()
00361 {
00362 delete m_proc;
00363 delete m_scriptTimer;
00364 delete m_updateTimer;
00365 }
00366
00367 void WeatherSource::connectScreen(WeatherScreen *ws)
00368 {
00369 connect(this, SIGNAL(newData(QString, units_t, DataMap)),
00370 ws, SLOT(newData(QString, units_t, DataMap)));
00371 ++m_connectCnt;
00372
00373 if (m_data.size() > 0)
00374 {
00375 emit newData(m_locale, m_units, m_data);
00376 }
00377 }
00378
00379 void WeatherSource::disconnectScreen(WeatherScreen *ws)
00380 {
00381 disconnect(this, 0, ws, 0);
00382 --m_connectCnt;
00383 }
00384
00385 QStringList WeatherSource::getLocationList(const QString &str)
00386 {
00387 QStringList locs;
00388
00389 m_proc->clearArguments();
00390 m_proc->setWorkingDirectory(m_info->file->dir(true));
00391 m_proc->addArgument(m_info->file->absFilePath());
00392 m_proc->addArgument("-l");
00393 m_proc->addArgument(str);
00394
00395 if (m_proc->isRunning())
00396 {
00397 VERBOSE(VB_IMPORTANT, "error script already running");
00398 return NULL;
00399 }
00400
00401 if (!m_proc->start())
00402 {
00403 VERBOSE(VB_IMPORTANT, "cannot start script");
00404 return NULL;
00405 }
00406
00407 while (m_proc->isRunning())
00408 {
00409 if (m_proc->canReadLineStdout())
00410 locs << m_proc->readLineStdout();
00411 else
00412 usleep(100);
00413 }
00414
00415 while (m_proc->canReadLineStdout())
00416 locs << m_proc->readLineStdout();
00417
00418 return locs;
00419 }
00420
00421 void WeatherSource::startUpdate()
00422 {
00423 VERBOSE(VB_GENERAL, "Starting update of " + m_info->name);
00424 m_data.clear();
00425 m_proc->clearArguments();
00426 m_proc->setWorkingDirectory(m_info->file->dir(true));
00427 m_proc->addArgument("nice");
00428 m_proc->addArgument(m_info->file->absFilePath());
00429 m_proc->addArgument("-u");
00430 m_proc->addArgument(m_units == SI_UNITS ? "SI" : "ENG");
00431 if (m_dir && m_dir != "")
00432 {
00433 m_proc->addArgument("-d");
00434 m_proc->addArgument(m_dir);
00435 }
00436 m_proc->addArgument(m_locale);
00437
00438 m_buffer = "";
00439 connect( m_proc, SIGNAL(readyReadStdout()),
00440 this, SLOT(readFromStdout()));
00441 connect( m_proc, SIGNAL(processExited()),
00442 this, SLOT(processExit()));
00443 if (!m_proc->start())
00444 {
00445 VERBOSE(VB_IMPORTANT, "Error running script");
00446 }
00447 else
00448 m_scriptTimer->start(m_info->scriptTimeout);
00449 }
00450
00451 void WeatherSource::updateTimeout()
00452 {
00453 if (!isRunning())
00454 {
00455 startUpdate();
00456 startUpdateTimer();
00457 }
00458 }
00459
00460 void WeatherSource::readFromStdout()
00461 {
00462 m_buffer += m_proc->readStdout();
00463 }
00464
00465 void WeatherSource::processExit()
00466 {
00467 VERBOSE(VB_GENERAL, m_proc->arguments().join(" ") + " has exited");
00468 m_proc->disconnect();
00469
00470 m_scriptTimer->stop();
00471 if (!m_proc->normalExit())
00472 {
00473 VERBOSE(VB_IMPORTANT, "script exit status " + m_proc->exitStatus());
00474 return;
00475 }
00476
00477 QString tempStr = m_proc->readStdout();
00478 if (tempStr)
00479 m_buffer += tempStr;
00480
00481 QStringList data = QStringList::split('\n', m_buffer);
00482 QStringList temp;
00483 for (size_t i = 0; i < data.size(); ++i)
00484 {
00485 temp = QStringList::split("::", data[i]);
00486 if (temp.size() > 2)
00487 VERBOSE(VB_IMPORTANT, "Error parsing script file, ignoring");
00488 if (temp.size() < 2)
00489 {
00490 VERBOSE(VB_IMPORTANT, data[i]);
00491 VERBOSE(VB_IMPORTANT,
00492 "Unrecoverable error parsing script output " + temp.size());
00493 return;
00494 }
00495
00496 if (m_data[temp[0]])
00497 {
00498 m_data[temp[0]].append("\n" + temp[1]);
00499 }
00500 else
00501 m_data[temp[0]] = temp[1];
00502 }
00503
00504 if (m_connectCnt)
00505 {
00506 emit newData(m_locale, m_units, m_data);
00507 }
00508 }
00509
00510 void WeatherSource::scriptTimeout()
00511 {
00512 if (m_proc->isRunning())
00513 {
00514 VERBOSE(VB_IMPORTANT,
00515 "Script timeout exceeded, summarily executing it");
00516 emit killProcess();
00517 }
00518 }