00001
00002 #include <iostream>
00003 using namespace std;
00004
00005
00006 #include <cstdlib>
00007
00008
00009 #include <unistd.h>
00010
00011
00012 #include <QCoreApplication>
00013 #include <QDomElement>
00014 #include <QFileInfo>
00015 #include <QFile>
00016 #include <QTextStream>
00017
00018
00019 #include "mythcorecontext.h"
00020 #include "mythtimer.h"
00021 #include "mythdb.h"
00022
00023
00024 #include "fillutil.h"
00025 #include "icondata.h"
00026
00027 #define LOC QString("IconData: ")
00028 #define LOC_WARN QString("IconData, Warning: ")
00029 #define LOC_ERR QString("IconData, Error: ")
00030
00031 const char * const IM_DOC_TAG = "iconmappings";
00032
00033 const char * const IM_CS_TO_NET_TAG = "callsigntonetwork";
00034 const char * const IM_CS_TAG = "callsign";
00035
00036 const char * const IM_NET_TAG = "network";
00037
00038 const char * const IM_NET_TO_URL_TAG = "networktourl";
00039 const char * const IM_NET_URL_TAG = "url";
00040
00041 const char * const BASEURLMAP_START = "mythfilldatabase.urlmap.";
00042
00043 const char * const IM_BASEURL_TAG = "baseurl";
00044 const char * const IM_BASE_STUB_TAG = "stub";
00045
00046 class DOMException
00047 {
00048 private:
00049 QString message;
00050
00051 protected:
00052 void setMessage(const QString &mes)
00053 {
00054 message = mes;
00055 }
00056
00057 public:
00058 DOMException() : message("Unknown DOMException") {}
00059 virtual ~DOMException() {}
00060 DOMException(const QString &mes) : message(mes) {}
00061 QString getMessage() const { return message; }
00062 };
00063
00064 class DOMBadElementConversion : public DOMException
00065 {
00066 public:
00067 DOMBadElementConversion()
00068 {
00069 setMessage("Unknown DOMBadElementConversion");
00070 }
00071 DOMBadElementConversion(const QString &mes) : DOMException(mes) {}
00072 DOMBadElementConversion(const QDomNode &node)
00073 {
00074 setMessage(QString("Unable to convert node: '%1' to QDomElement.")
00075 .arg(node.nodeName()));
00076 }
00077 };
00078
00079 class DOMUnknownChildElement : public DOMException
00080 {
00081 public:
00082 DOMUnknownChildElement()
00083 {
00084 setMessage("Unknown DOMUnknownChildElement");
00085 }
00086 DOMUnknownChildElement(const QString &mes) : DOMException(mes) {}
00087 DOMUnknownChildElement(const QDomElement &e, QString child_name)
00088 {
00089 setMessage(QString("Unknown child element '%1' of: '%2'")
00090 .arg(child_name)
00091 .arg(e.tagName()));
00092 }
00093 };
00094
00095 static QDomElement nodeToElement(QDomNode &node)
00096 {
00097 QDomElement retval = node.toElement();
00098 if (retval.isNull())
00099 {
00100 throw DOMBadElementConversion(node);
00101 }
00102 return retval;
00103 }
00104
00105 static QString expandURLString(const QString &url)
00106 {
00107 QRegExp expandtarget("\\[([^\\]]+)\\]");
00108 QString retval = url;
00109
00110 int found_at = 0;
00111 int start_index = 0;
00112 while (found_at != -1)
00113 {
00114 found_at = expandtarget.indexIn(retval, start_index);
00115 if (found_at != -1)
00116 {
00117 QString no_mapping("no_URL_mapping");
00118 QString search_string = expandtarget.cap(1);
00119 QString expanded_text = gCoreContext->GetSetting(
00120 QString(BASEURLMAP_START) + search_string, no_mapping);
00121 if (expanded_text != no_mapping)
00122 {
00123 retval.replace(found_at, expandtarget.matchedLength(),
00124 expanded_text);
00125 }
00126 else
00127 {
00128 start_index = found_at + expandtarget.matchedLength();
00129 }
00130 }
00131 }
00132
00133 return retval;
00134 }
00135
00136 static QString getNamedElementText(const QDomElement &e,
00137 const QString &child_element_name)
00138 {
00139 QDomNode child_node = e.namedItem(child_element_name);
00140 if (child_node.isNull())
00141 {
00142 throw DOMUnknownChildElement(e, child_element_name);
00143 }
00144 QDomElement element = nodeToElement(child_node);
00145 return element.text();
00146 }
00147
00148 static void RunSimpleQuery(const QString &query)
00149 {
00150 MSqlQuery q(MSqlQuery::InitCon());
00151 if (!q.exec(query))
00152 MythDB::DBError("RunSimpleQuery ", q);
00153 }
00154
00155
00156 IconData::~IconData()
00157 {
00158 MythHttpPool::GetSingleton()->RemoveListener(this);
00159 }
00160
00161 void IconData::UpdateSourceIcons(uint sourceid)
00162 {
00163 LOG(VB_GENERAL, LOG_INFO, LOC +
00164 QString("Updating icons for sourceid: %1").arg(sourceid));
00165
00166 QString fileprefix = SetupIconCacheDirectory();
00167
00168 MSqlQuery query(MSqlQuery::InitCon());
00169 query.prepare(
00170 "SELECT ch.chanid, nim.url "
00171 "FROM (channel ch, callsignnetworkmap csm) "
00172 "RIGHT JOIN networkiconmap nim ON csm.network = nim.network "
00173 "WHERE ch.callsign = csm.callsign AND "
00174 " (icon = :NOICON OR icon = '') AND "
00175 " ch.sourceid = :SOURCEID");
00176 query.bindValue(":SOURCEID", sourceid);
00177 query.bindValue(":NOICON", "none");
00178
00179 if (!query.exec())
00180 {
00181 MythDB::DBError("Looking for icons to fetch", query);
00182 return;
00183 }
00184
00185 unsigned int count = 0;
00186 while (query.next())
00187 {
00188 count++;
00189
00190 QString icon_url = expandURLString(query.value(1).toString());
00191 QFileInfo qfi(icon_url);
00192 QFile localfile(fileprefix + "/" + qfi.fileName());
00193
00194 if (!localfile.exists() || 0 == localfile.size())
00195 {
00196 LOG(VB_GENERAL, LOG_INFO, LOC +
00197 QString("Attempting to fetch icon at '%1'") .arg(icon_url));
00198
00199 FI fi;
00200 fi.filename = localfile.fileName();
00201 fi.chanid = query.value(0).toUInt();
00202
00203 bool add_request = false;
00204 {
00205 QMutexLocker locker(&m_u2fl_lock);
00206 add_request = m_u2fl[icon_url].empty();
00207 m_u2fl[icon_url].push_back(fi);
00208 }
00209
00210 if (add_request)
00211 MythHttpPool::GetSingleton()->AddUrlRequest(icon_url, this);
00212
00213
00214
00215
00216 qApp->processEvents();
00217
00218 }
00219 }
00220
00221 MythTimer tm; tm.start();
00222 while (true)
00223 {
00224
00225
00226
00227 qApp->processEvents();
00228
00229
00230 QMutexLocker locker(&m_u2fl_lock);
00231 if (m_u2fl.empty())
00232 break;
00233
00234 if ((uint)tm.elapsed() > (count * 500) + 2000)
00235 {
00236 LOG(VB_GENERAL, LOG_WARNING, LOC +
00237 "Timed out waiting for some icons to download, "
00238 "you may wish to try again later.");
00239 break;
00240 }
00241 }
00242 }
00243
00244 void IconData::Update(
00245 QHttp::Error error,
00246 const QString &error_str,
00247 const QUrl &url,
00248 uint http_status_id,
00249 const QString &http_status_str,
00250 const QByteArray &data)
00251 {
00252 bool http_error = false;
00253 if (QHttp::NoError != error)
00254 {
00255 LOG(VB_GENERAL, LOG_ERR, LOC + QString("fetching '%1'\n\t\t\t%2")
00256 .arg(url.toString()).arg(error_str));
00257 http_error = true;
00258 }
00259
00260 if (!data.size())
00261 {
00262 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Did not get any data from '%1'")
00263 .arg((url.toString())));
00264 http_error = true;
00265 }
00266
00267 FIL fil;
00268 {
00269 QMutexLocker locker(&m_u2fl_lock);
00270 Url2FIL::iterator it = m_u2fl.find(url);
00271 if (it == m_u2fl.end())
00272 {
00273 LOG(VB_GENERAL, LOG_ERR, LOC +
00274 QString("Programmer Error, got data for '%1',"
00275 "but have no record of requesting it.")
00276 .arg(url.toString()));
00277 return;
00278 }
00279
00280 fil = *it;
00281 m_u2fl.erase(it);
00282 }
00283
00284 while (!http_error && !fil.empty())
00285 {
00286 Save(fil.back(), data);
00287 fil.pop_back();
00288 }
00289 }
00290
00291 bool IconData::Save(const FI &fi, const QByteArray &data)
00292 {
00293 QFile file(fi.filename);
00294 if (!file.open(QIODevice::WriteOnly))
00295 {
00296 LOG(VB_GENERAL, LOG_ERR, LOC +
00297 QString("Failed to open '%1' for writing") .arg(fi.filename));
00298 return false;
00299 }
00300
00301 if (file.write(data) < data.size())
00302 {
00303 LOG(VB_GENERAL, LOG_ERR, LOC +
00304 QString("Failed to write icon data to '%1'") .arg(fi.filename));
00305 file.remove();
00306 return false;
00307 }
00308
00309 file.flush();
00310
00311 LOG(VB_GENERAL, LOG_INFO, LOC +
00312 QString("Updating channel icon for chanid: %1").arg(fi.chanid));
00313
00314 MSqlQuery update(MSqlQuery::InitCon());
00315 update.prepare(
00316 "UPDATE channel SET icon = :ICON "
00317 "WHERE chanid = :CHANID");
00318
00319 update.bindValue(":ICON", fi.filename);
00320 update.bindValue(":CHANID", fi.chanid);
00321
00322 if (!update.exec())
00323 {
00324 MythDB::DBError("Setting the icon file name", update);
00325 return false;
00326 }
00327
00328 return true;
00329 }
00330
00331 void IconData::ImportIconMap(const QString &filename)
00332 {
00333 LOG(VB_GENERAL, LOG_INFO, LOC +
00334 QString("Importing icon mapping from %1...").arg(filename));
00335
00336 QFile xml_file;
00337
00338 if (dash_open(xml_file, filename, QIODevice::ReadOnly))
00339 {
00340 QDomDocument doc;
00341 QString de_msg;
00342 int de_ln = 0;
00343 int de_column = 0;
00344 if (doc.setContent(&xml_file, false, &de_msg, &de_ln, &de_column))
00345 {
00346 MSqlQuery nm_query(MSqlQuery::InitCon());
00347 nm_query.prepare("REPLACE INTO networkiconmap(network, url) "
00348 "VALUES(:NETWORK, :URL)");
00349 MSqlQuery cm_query(MSqlQuery::InitCon());
00350 cm_query.prepare("REPLACE INTO callsignnetworkmap(callsign, "
00351 "network) VALUES(:CALLSIGN, :NETWORK)");
00352 MSqlQuery su_query(MSqlQuery::InitCon());
00353 su_query.prepare("UPDATE settings SET data = :URL "
00354 "WHERE value = :STUBNAME");
00355 MSqlQuery si_query(MSqlQuery::InitCon());
00356 si_query.prepare("INSERT INTO settings(value, data) "
00357 "VALUES(:STUBNAME, :URL)");
00358
00359 QDomElement element = doc.documentElement();
00360
00361 QDomNode node = element.firstChild();
00362 while (!node.isNull())
00363 {
00364 try
00365 {
00366 QDomElement e = nodeToElement(node);
00367 if (e.tagName() == IM_NET_TO_URL_TAG)
00368 {
00369 QString net = getNamedElementText(e, IM_NET_TAG);
00370 QString u = getNamedElementText(e, IM_NET_URL_TAG);
00371
00372 nm_query.bindValue(":NETWORK", net.trimmed());
00373 nm_query.bindValue(":URL", u.trimmed());
00374 if (!nm_query.exec())
00375 MythDB::DBError(
00376 "Inserting network->url mapping", nm_query);
00377 }
00378 else if (e.tagName() == IM_CS_TO_NET_TAG)
00379 {
00380 QString cs = getNamedElementText(e, IM_CS_TAG);
00381 QString net = getNamedElementText(e, IM_NET_TAG);
00382
00383 cm_query.bindValue(":CALLSIGN", cs.trimmed());
00384 cm_query.bindValue(":NETWORK", net.trimmed());
00385 if (!cm_query.exec())
00386 MythDB::DBError("Inserting callsign->network "
00387 "mapping", cm_query);
00388 }
00389 else if (e.tagName() == IM_BASEURL_TAG)
00390 {
00391 MSqlQuery *qr = &si_query;
00392
00393 QString st(BASEURLMAP_START);
00394 st += getNamedElementText(e, IM_BASE_STUB_TAG);
00395 QString u = getNamedElementText(e, IM_NET_URL_TAG);
00396
00397 MSqlQuery qc(MSqlQuery::InitCon());
00398 qc.prepare("SELECT COUNT(*) FROM settings "
00399 "WHERE value = :STUBNAME");
00400 qc.bindValue(":STUBNAME", st);
00401 if (qc.exec() && qc.next())
00402 {
00403 if (qc.value(0).toInt() != 0)
00404 {
00405 qr = &su_query;
00406 }
00407 }
00408
00409 qr->bindValue(":STUBNAME", st);
00410 qr->bindValue(":URL", u);
00411
00412 if (!qr->exec())
00413 MythDB::DBError(
00414 "Inserting callsign->network mapping", *qr);
00415 }
00416 }
00417 catch (DOMException &e)
00418 {
00419 LOG(VB_GENERAL, LOG_ERR, LOC +
00420 QString("while processing %1: %2")
00421 .arg(node.nodeName()).arg(e.getMessage()));
00422 }
00423 node = node.nextSibling();
00424 }
00425 }
00426 else
00427 {
00428 LOG(VB_GENERAL, LOG_ERR, LOC +
00429 QString("unable to set document content: %1:%2c%3 %4")
00430 .arg(filename).arg(de_ln).arg(de_column).arg(de_msg));
00431 }
00432 }
00433 else
00434 {
00435 LOG(VB_GENERAL, LOG_ERR, LOC +
00436 QString("unable to open '%1' for reading.").arg(filename));
00437 }
00438 }
00439
00440 void IconData::ExportIconMap(const QString &filename)
00441 {
00442 LOG(VB_GENERAL, LOG_INFO, LOC +
00443 QString("Exporting icon mapping to '%1'").arg(filename));
00444
00445 QFile xml_file(filename);
00446 if (dash_open(xml_file, filename, QIODevice::WriteOnly))
00447 {
00448 QTextStream os(&xml_file);
00449 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
00450 os << "<!-- generated by mythfilldatabase -->\n";
00451
00452 QDomDocument iconmap;
00453 QDomElement roote = iconmap.createElement(IM_DOC_TAG);
00454
00455 MSqlQuery query(MSqlQuery::InitCon());
00456
00457 query.prepare("SELECT * FROM callsignnetworkmap ORDER BY callsign;");
00458 if (query.exec())
00459 {
00460 while (query.next())
00461 {
00462 QDomElement cs2nettag = iconmap.createElement(IM_CS_TO_NET_TAG);
00463 QDomElement cstag = iconmap.createElement(IM_CS_TAG);
00464 QDomElement nettag = iconmap.createElement(IM_NET_TAG);
00465 QDomText cs_text = iconmap.createTextNode(
00466 query.value(1).toString());
00467 QDomText net_text = iconmap.createTextNode(
00468 query.value(2).toString());
00469
00470 cstag.appendChild(cs_text);
00471 nettag.appendChild(net_text);
00472
00473 cs2nettag.appendChild(cstag);
00474 cs2nettag.appendChild(nettag);
00475
00476 roote.appendChild(cs2nettag);
00477 }
00478 }
00479
00480 query.prepare("SELECT * FROM networkiconmap ORDER BY network;");
00481 if (query.exec())
00482 {
00483 while (query.next())
00484 {
00485 QDomElement net2urltag = iconmap.createElement(
00486 IM_NET_TO_URL_TAG);
00487 QDomElement nettag = iconmap.createElement(IM_NET_TAG);
00488 QDomElement urltag = iconmap.createElement(IM_NET_URL_TAG);
00489 QDomText net_text = iconmap.createTextNode(
00490 query.value(1).toString());
00491 QDomText url_text = iconmap.createTextNode(
00492 query.value(2).toString());
00493
00494 nettag.appendChild(net_text);
00495 urltag.appendChild(url_text);
00496
00497 net2urltag.appendChild(nettag);
00498 net2urltag.appendChild(urltag);
00499
00500 roote.appendChild(net2urltag);
00501 }
00502 }
00503
00504 query.prepare("SELECT value,data FROM settings WHERE value "
00505 "LIKE :URLMAP");
00506 query.bindValue(":URLMAP", QString(BASEURLMAP_START) + "%");
00507 if (query.exec())
00508 {
00509 QRegExp baseax("\\.([^\\.]+)$");
00510 while (query.next())
00511 {
00512 QString base_stub = query.value(0).toString();
00513 if (baseax.indexIn(base_stub) != -1)
00514 {
00515 base_stub = baseax.cap(1);
00516 }
00517
00518 QDomElement baseurltag = iconmap.createElement(IM_BASEURL_TAG);
00519 QDomElement stubtag = iconmap.createElement(
00520 IM_BASE_STUB_TAG);
00521 QDomElement urltag = iconmap.createElement(IM_NET_URL_TAG);
00522 QDomText base_text = iconmap.createTextNode(base_stub);
00523 QDomText url_text = iconmap.createTextNode(
00524 query.value(1).toString());
00525
00526 stubtag.appendChild(base_text);
00527 urltag.appendChild(url_text);
00528
00529 baseurltag.appendChild(stubtag);
00530 baseurltag.appendChild(urltag);
00531
00532 roote.appendChild(baseurltag);
00533 }
00534 }
00535
00536 iconmap.appendChild(roote);
00537 iconmap.save(os, 4);
00538 }
00539 else
00540 {
00541 LOG(VB_GENERAL, LOG_ERR, LOC +
00542 QString("unable to open '%1' for writing.").arg(filename));
00543 }
00544 }
00545
00546 void IconData::ResetIconMap(bool reset_icons)
00547 {
00548 MSqlQuery query(MSqlQuery::InitCon());
00549 query.prepare("DELETE FROM settings WHERE value LIKE :URLMAPLIKE");
00550 query.bindValue(":URLMAPLIKE", QString(BASEURLMAP_START) + '%');
00551 if (!query.exec())
00552 MythDB::DBError("ResetIconMap", query);
00553
00554 RunSimpleQuery("TRUNCATE TABLE callsignnetworkmap;");
00555 RunSimpleQuery("TRUNCATE TABLE networkiconmap");
00556
00557 if (reset_icons)
00558 {
00559 RunSimpleQuery("UPDATE channel SET icon = 'none'");
00560 }
00561 }