00001 #include <unistd.h>
00002
00003
00004 #include <QDir>
00005 #include <QFile>
00006 #include <QTextStream>
00007 #include <QTextCodec>
00008 #include <QApplication>
00009
00010
00011 #include <mythcontext.h>
00012 #include <mythdb.h>
00013 #include <compat.h>
00014 #include <mythdirs.h>
00015 #include <mythsystem.h>
00016 #include <exitcodes.h>
00017
00018
00019 #include "weatherScreen.h"
00020 #include "weatherSource.h"
00021
00022 QStringList WeatherSource::ProbeTypes(QString workingDirectory,
00023 QString program)
00024 {
00025 QStringList arguments("-t");
00026 const QString loc = QString("WeatherSource::ProbeTypes(%1 %2): ")
00027 .arg(program).arg(arguments.join(" "));
00028 QStringList types;
00029
00030 uint flags = kMSRunShell | kMSStdOut | kMSBuffered |
00031 kMSDontDisableDrawing | kMSDontBlockInputDevs;
00032 MythSystem ms(program, arguments, flags);
00033 ms.SetDirectory(workingDirectory);
00034 ms.Run();
00035 if (ms.Wait() != GENERIC_EXIT_OK)
00036 {
00037 LOG(VB_GENERAL, LOG_ERR, loc + "Cannot run script");
00038 return types;
00039 }
00040
00041 QByteArray result = ms.ReadAll();
00042 QTextStream text(result);
00043
00044 while (!text.atEnd())
00045 {
00046 QString tmp = text.readLine();
00047
00048 while (tmp.endsWith('\n') || tmp.endsWith('\r'))
00049 tmp.chop(1);
00050
00051 if (!tmp.isEmpty())
00052 types += tmp;
00053 }
00054
00055 if (types.empty())
00056 LOG(VB_GENERAL, LOG_ERR, loc + "Invalid output from -t option");
00057
00058 return types;
00059 }
00060
00061 bool WeatherSource::ProbeTimeouts(QString workingDirectory,
00062 QString program,
00063 uint &updateTimeout,
00064 uint &scriptTimeout)
00065 {
00066 QStringList arguments("-T");
00067 const QString loc = QString("WeatherSource::ProbeTimeouts(%1 %2): ")
00068 .arg(program).arg(arguments.join(" "));
00069
00070 updateTimeout = DEFAULT_UPDATE_TIMEOUT;
00071 scriptTimeout = DEFAULT_SCRIPT_TIMEOUT;
00072
00073 uint flags = kMSRunShell | kMSStdOut | kMSBuffered |
00074 kMSDontDisableDrawing | kMSDontBlockInputDevs;
00075 MythSystem ms(program, arguments, flags);
00076 ms.SetDirectory(workingDirectory);
00077 ms.Run();
00078 if (ms.Wait() != GENERIC_EXIT_OK)
00079 {
00080 LOG(VB_GENERAL, LOG_ERR, loc + "Cannot run script");
00081 return false;
00082 }
00083
00084 QByteArray result = ms.ReadAll();
00085 QTextStream text(result);
00086
00087 QStringList lines;
00088 while (!text.atEnd())
00089 {
00090 QString tmp = text.readLine();
00091
00092 while (tmp.endsWith('\n') || tmp.endsWith('\r'))
00093 tmp.chop(1);
00094
00095 if (!tmp.isEmpty())
00096 lines << tmp;
00097 }
00098
00099 if (lines.empty())
00100 {
00101 LOG(VB_GENERAL, LOG_ERR, loc + "Invalid Script Output! No Lines");
00102 return false;
00103 }
00104
00105 QStringList temp = lines[0].split(',');
00106 if (temp.size() != 2)
00107 {
00108 LOG(VB_GENERAL, LOG_ERR, loc +
00109 QString("Invalid Script Output! '%1'").arg(lines[0]));
00110 return false;
00111 }
00112
00113 bool isOK[2];
00114 uint ut = temp[0].toUInt(&isOK[0]);
00115 uint st = temp[1].toUInt(&isOK[1]);
00116 if (!isOK[0] || !isOK[1])
00117 {
00118 LOG(VB_GENERAL, LOG_ERR, loc +
00119 QString("Invalid Script Output! '%1'").arg(lines[0]));
00120 return false;
00121 }
00122
00123 updateTimeout = ut * 1000;
00124 scriptTimeout = st;
00125
00126 return true;
00127 }
00128
00129 bool WeatherSource::ProbeInfo(ScriptInfo &info)
00130 {
00131 QStringList arguments("-v");
00132
00133 const QString loc = QString("WeatherSource::ProbeInfo(%1 %2): ")
00134 .arg(info.program).arg(arguments.join(" "));
00135
00136 uint flags = kMSRunShell | kMSStdOut | kMSBuffered |
00137 kMSDontDisableDrawing | kMSDontBlockInputDevs;
00138 MythSystem ms(info.program, arguments, flags);
00139 ms.SetDirectory(info.path);
00140 ms.Run();
00141 if (ms.Wait() != GENERIC_EXIT_OK)
00142 {
00143 LOG(VB_GENERAL, LOG_ERR, loc + "Cannot run script");
00144 return false;
00145 }
00146
00147 QByteArray result = ms.ReadAll();
00148 QTextStream text(result);
00149
00150 QStringList lines;
00151 while (!text.atEnd())
00152 {
00153 QString tmp = text.readLine();
00154
00155 while (tmp.endsWith('\n') || tmp.endsWith('\r'))
00156 tmp.chop(1);
00157
00158 if (!tmp.isEmpty())
00159 lines << tmp;
00160 }
00161
00162 if (lines.empty())
00163 {
00164 LOG(VB_GENERAL, LOG_ERR, loc + "Invalid Script Output! No Lines");
00165 return false;
00166 }
00167
00168 QStringList temp = lines[0].split(',');
00169 if (temp.size() != 4)
00170 {
00171 LOG(VB_GENERAL, LOG_ERR, loc +
00172 QString("Invalid Script Output! '%1'").arg(lines[0]));
00173 return false;
00174 }
00175
00176 info.name = temp[0];
00177 info.version = temp[1];
00178 info.author = temp[2];
00179 info.email = temp[3];
00180
00181 return true;
00182 }
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194 ScriptInfo *WeatherSource::ProbeScript(const QFileInfo &fi)
00195 {
00196 QStringList temp;
00197
00198 if (!fi.isReadable() || !fi.isExecutable())
00199 return NULL;
00200
00201 ScriptInfo info;
00202 info.path = fi.absolutePath();
00203 info.program = fi.absoluteFilePath();
00204
00205 if (!WeatherSource::ProbeInfo(info))
00206 return NULL;
00207
00208 MSqlQuery db(MSqlQuery::InitCon());
00209 QString query =
00210 "SELECT sourceid, source_name, update_timeout, retrieve_timeout, "
00211 "path, author, version, email, types FROM weathersourcesettings "
00212 "WHERE hostname = :HOST AND source_name = :NAME;";
00213 db.prepare(query);
00214 db.bindValue(":HOST", gCoreContext->GetHostName());
00215 db.bindValue(":NAME", info.name);
00216
00217 if (!db.exec())
00218 {
00219 LOG(VB_GENERAL, LOG_ERR, "Invalid response from database");
00220 return NULL;
00221 }
00222
00223
00224 if (db.next())
00225 {
00226 info.id = db.value(0).toInt();
00227 info.updateTimeout = db.value(2).toUInt() * 1000;
00228 info.scriptTimeout = db.value(3).toUInt();
00229
00230
00231 QString dbver = db.value(6).toString();
00232 if (dbver == info.version)
00233 {
00234 info.types = db.value(8).toString().split(",");
00235 }
00236 else
00237 {
00238
00239 LOG(VB_GENERAL, LOG_INFO, "New version of " + info.name + " found");
00240 query = "UPDATE weathersourcesettings SET source_name = :NAME, "
00241 "path = :PATH, author = :AUTHOR, version = :VERSION, "
00242 "email = :EMAIL, types = :TYPES WHERE sourceid = :ID";
00243 db.prepare(query);
00244
00245
00246 db.bindValue(":NAME", info.name);
00247 db.bindValue(":PATH", info.program);
00248 db.bindValue(":AUTHOR", info.author);
00249 db.bindValue(":VERSION", info.version);
00250
00251
00252 info.types = WeatherSource::ProbeTypes(info.path, info.program);
00253
00254 db.bindValue(":TYPES", info.types.join(","));
00255 db.bindValue(":ID", info.id);
00256 db.bindValue(":EMAIL", info.email);
00257 if (!db.exec())
00258 {
00259 MythDB::DBError("Updating weather source settings.", db);
00260 return NULL;
00261 }
00262 }
00263 }
00264 else
00265 {
00266
00267 query = "INSERT INTO weathersourcesettings "
00268 "(hostname, source_name, update_timeout, retrieve_timeout, "
00269 "path, author, version, email, types) "
00270 "VALUES (:HOST, :NAME, :UPDATETO, :RETTO, :PATH, :AUTHOR, "
00271 ":VERSION, :EMAIL, :TYPES);";
00272
00273 if (!WeatherSource::ProbeTimeouts(info.path,
00274 info.program,
00275 info.updateTimeout,
00276 info.scriptTimeout))
00277 {
00278 return NULL;
00279 }
00280 db.prepare(query);
00281 db.bindValue(":NAME", info.name);
00282 db.bindValue(":HOST", gCoreContext->GetHostName());
00283 db.bindValue(":UPDATETO", QString::number(info.updateTimeout/1000));
00284 db.bindValue(":RETTO", QString::number(info.scriptTimeout));
00285 db.bindValue(":PATH", info.program);
00286 db.bindValue(":AUTHOR", info.author);
00287 db.bindValue(":VERSION", info.version);
00288 db.bindValue(":EMAIL", info.email);
00289 info.types = ProbeTypes(info.path, info.program);
00290 db.bindValue(":TYPES", info.types.join(","));
00291 if (!db.exec())
00292 {
00293 MythDB::DBError("Inserting weather source", db);
00294 return NULL;
00295 }
00296 query = "SELECT sourceid FROM weathersourcesettings "
00297 "WHERE source_name = :NAME AND hostname = :HOST;";
00298
00299
00300 db.prepare(query);
00301 db.bindValue(":HOST", gCoreContext->GetHostName());
00302 db.bindValue(":NAME", info.name);
00303 if (!db.exec())
00304 {
00305 MythDB::DBError("Getting weather sourceid", db);
00306 return NULL;
00307 }
00308 else if (!db.next())
00309 {
00310 LOG(VB_GENERAL, LOG_ERR, "Error getting weather sourceid");
00311 return NULL;
00312 }
00313 else
00314 {
00315 info.id = db.value(0).toInt();
00316 }
00317 }
00318
00319 return new ScriptInfo(info);
00320 }
00321
00322
00323
00324
00325
00326 WeatherSource::WeatherSource(ScriptInfo *info)
00327 : m_ready(info ? true : false), m_inuse(info ? true : false),
00328 m_info(info),
00329 m_ms(NULL),
00330 m_locale(""),
00331 m_cachefile(""),
00332 m_units(SI_UNITS),
00333 m_updateTimer(new QTimer(this)), m_connectCnt(0)
00334 {
00335 QDir dir(GetConfDir());
00336 if (!dir.exists("MythWeather"))
00337 dir.mkdir("MythWeather");
00338 dir.cd("MythWeather");
00339 if (!dir.exists(info->name))
00340 dir.mkdir(info->name);
00341 dir.cd(info->name);
00342 m_dir = dir.absolutePath();
00343
00344 connect( m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTimeout()));
00345 }
00346
00347 WeatherSource::~WeatherSource()
00348 {
00349 if (m_ms)
00350 {
00351 m_ms->Kill();
00352 delete m_ms;
00353 }
00354 delete m_updateTimer;
00355 }
00356
00357 void WeatherSource::connectScreen(WeatherScreen *ws)
00358 {
00359 connect(this, SIGNAL(newData(QString, units_t, DataMap)),
00360 ws, SLOT(newData(QString, units_t, DataMap)));
00361 ++m_connectCnt;
00362
00363 if (m_data.size() > 0)
00364 {
00365 emit newData(m_locale, m_units, m_data);
00366 }
00367 }
00368
00369 void WeatherSource::disconnectScreen(WeatherScreen *ws)
00370 {
00371 disconnect(this, 0, ws, 0);
00372 --m_connectCnt;
00373 }
00374
00375 QStringList WeatherSource::getLocationList(const QString &str)
00376 {
00377 QString program = m_info->program;
00378 QStringList args;
00379 args << "-l";
00380 args << str;
00381
00382 const QString loc = QString("WeatherSource::getLocationList(%1 %2): ")
00383 .arg(program).arg(args.join(" "));
00384
00385 uint flags = kMSRunShell | kMSStdOut | kMSBuffered |
00386 kMSDontDisableDrawing | kMSDontBlockInputDevs;
00387 MythSystem ms(program, args, flags);
00388 ms.SetDirectory(m_info->path);
00389 ms.Run();
00390
00391 if (ms.Wait() != GENERIC_EXIT_OK)
00392 {
00393 LOG(VB_GENERAL, LOG_ERR, loc + "Cannot run script");
00394 return QStringList();
00395 }
00396
00397 QStringList locs;
00398 QByteArray result = ms.ReadAll();
00399 QTextStream text(result);
00400
00401 QTextCodec *codec = QTextCodec::codecForName("UTF-8");
00402 while (!text.atEnd())
00403 {
00404 QString tmp = text.readLine();
00405
00406 while (tmp.endsWith('\n') || tmp.endsWith('\r'))
00407 tmp.chop(1);
00408
00409 if (!tmp.isEmpty())
00410 {
00411 QString loc_string = codec->toUnicode(tmp.toUtf8());
00412 locs << loc_string;
00413 }
00414 }
00415
00416 return locs;
00417 }
00418
00419 void WeatherSource::startUpdate(bool forceUpdate)
00420 {
00421 m_buffer.clear();
00422
00423 MSqlQuery db(MSqlQuery::InitCon());
00424 LOG(VB_GENERAL, LOG_INFO, "Starting update of " + m_info->name);
00425
00426 if (m_ms)
00427 {
00428 LOG(VB_GENERAL, LOG_ERR, QString("%1 process exists, skipping.")
00429 .arg(m_info->name));
00430 return;
00431 }
00432
00433 if (!forceUpdate)
00434 {
00435 db.prepare("SELECT updated FROM weathersourcesettings "
00436 "WHERE sourceid = :ID AND "
00437 "TIMESTAMPADD(SECOND,update_timeout-15,updated) > NOW()");
00438 db.bindValue(":ID", getId());
00439 if (db.exec() && db.size() > 0)
00440 {
00441 LOG(VB_GENERAL, LOG_ERR, QString("%1 recently updated, skipping.")
00442 .arg(m_info->name));
00443
00444 if (m_cachefile.isEmpty())
00445 {
00446 QString locale_file(m_locale);
00447 locale_file.replace("/", "-");
00448 m_cachefile = QString("%1/cache_%2").arg(m_dir).arg(locale_file);
00449 }
00450 QFile cache(m_cachefile);
00451 if (cache.exists() && cache.open( QIODevice::ReadOnly ))
00452 {
00453 m_buffer = cache.readAll();
00454 cache.close();
00455
00456 processData();
00457
00458 if (m_connectCnt)
00459 {
00460 emit newData(m_locale, m_units, m_data);
00461 }
00462 return;
00463 }
00464 else
00465 {
00466 LOG(VB_GENERAL, LOG_WARNING,
00467 QString("No cachefile for %1, forcing update.")
00468 .arg(m_info->name));
00469 }
00470 }
00471 }
00472
00473 m_data.clear();
00474 QString program = "nice";
00475 QStringList args;
00476 args << m_info->program;
00477 args << "-u";
00478 args << (m_units == SI_UNITS ? "SI" : "ENG");
00479
00480 if (!m_dir.isEmpty())
00481 {
00482 args << "-d";
00483 args << m_dir;
00484 }
00485 args << m_locale;
00486
00487 uint flags = kMSRunShell | kMSStdOut | kMSBuffered | kMSRunBackground |
00488 kMSDontDisableDrawing | kMSDontBlockInputDevs;
00489 m_ms = new MythSystem(program, args, flags);
00490 m_ms->SetDirectory(m_info->path);
00491
00492 connect(m_ms, SIGNAL(finished()), this, SLOT(processExit()));
00493 connect(m_ms, SIGNAL(error(uint)), this, SLOT(processExit(uint)));
00494
00495 m_ms->Run(m_info->scriptTimeout);
00496 }
00497
00498 void WeatherSource::updateTimeout()
00499 {
00500 startUpdate();
00501 startUpdateTimer();
00502 }
00503
00504 void WeatherSource::processExit(uint status)
00505 {
00506 m_ms->disconnect();
00507
00508 if (status == GENERIC_EXIT_OK)
00509 {
00510 m_buffer = m_ms->ReadAll();
00511 }
00512
00513 delete m_ms;
00514 m_ms = NULL;
00515
00516 if (status != GENERIC_EXIT_OK)
00517 {
00518 LOG(VB_GENERAL, LOG_ERR, QString("script exit status %1").arg(status));
00519 return;
00520 }
00521
00522 if (m_buffer.isEmpty())
00523 {
00524 LOG(VB_GENERAL, LOG_ERR, "Script returned no data");
00525 return;
00526 }
00527
00528 if (m_cachefile.isEmpty())
00529 {
00530 QString locale_file(m_locale);
00531 locale_file.replace("/", "-");
00532 m_cachefile = QString("%1/cache_%2").arg(m_dir).arg(locale_file);
00533 }
00534 QFile cache(m_cachefile);
00535 if (cache.open( QIODevice::WriteOnly ))
00536 {
00537 cache.write(m_buffer);
00538 cache.close();
00539 }
00540 else
00541 {
00542 LOG(VB_GENERAL, LOG_ERR, QString("Unable to save data to cachefile: %1")
00543 .arg(m_cachefile));
00544 }
00545
00546 processData();
00547
00548 MSqlQuery db(MSqlQuery::InitCon());
00549
00550 db.prepare("UPDATE weathersourcesettings "
00551 "SET updated = NOW() WHERE sourceid = :ID;");
00552
00553 db.bindValue(":ID", getId());
00554 if (!db.exec())
00555 {
00556 MythDB::DBError("Updating weather source's last update time", db);
00557 return;
00558 }
00559
00560 if (m_connectCnt)
00561 {
00562 emit newData(m_locale, m_units, m_data);
00563 }
00564 }
00565
00566 void WeatherSource::processData()
00567 {
00568 QTextCodec *codec = QTextCodec::codecForName("UTF-8");
00569 QString unicode_buffer = codec->toUnicode(m_buffer);
00570 QStringList data = unicode_buffer.split('\n', QString::SkipEmptyParts);
00571
00572 m_data.clear();
00573
00574 for (int i = 0; i < data.size(); ++i)
00575 {
00576 QStringList temp = data[i].split("::", QString::SkipEmptyParts);
00577 if (temp.size() > 2)
00578 LOG(VB_GENERAL, LOG_ERR, "Error parsing script file, ignoring");
00579 if (temp.size() < 2)
00580 {
00581 LOG(VB_GENERAL, LOG_ERR,
00582 "Unrecoverable error parsing script output " + temp.size());
00583 LOG(VB_GENERAL, LOG_ERR, QString("data[%1]: '%2'")
00584 .arg(i).arg(data[i]));
00585 return;
00586 }
00587
00588 if (temp[1] != "---")
00589 {
00590 if (!m_data[temp[0]].isEmpty())
00591 {
00592 m_data[temp[0]].append("\n" + temp[1]);
00593 }
00594 else
00595 m_data[temp[0]] = temp[1];
00596 }
00597 }
00598 }
00599