|
MythTV
0.26-pre
|
00001 #include <unistd.h> 00002 00003 // QT headers 00004 #include <QDir> 00005 #include <QFile> 00006 #include <QTextStream> 00007 #include <QTextCodec> 00008 #include <QApplication> 00009 00010 // MythTV headers 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 // MythWeather headers 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 /* Basic logic of this behemouth... 00185 * run script with -v flag, this returns among other things, the version number 00186 * Search the database using the name (also returned from -v). 00187 * if it exists, compare versions from -v and db 00188 * if the same, populate the info struct from db, and we're done 00189 * if they differ, get the rest of the needed information from the script and 00190 * update the database, note, it does not overwrite the existing timeout values. 00191 * if the script is not in the database, we probe it for types and default 00192 * timeout values, and add it to the database 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 // the script exists in the db 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 // compare versions, if equal... be happy 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 // versions differ, change db to match script output 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 // these info values were populated when getting the version number 00245 // we leave the timeout values in 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 // run the script to get supported data types 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 // Script is not in db, probe it and insert it into db 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 // a little annoying, but look at what we just inserted to get the id 00299 // number, not sure if we really need it, but better safe than sorry. 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 * Watch out, we store the parameter as a member variable, don't go deleting it, 00324 * that wouldn't be good. 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(); // disconnects all signals 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; // we don't emit signal 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
1.7.6.1