00001
00002
00003
00004 #include <cmath>
00005 #include <unistd.h>
00006
00007
00008 #include <QFile>
00009 #include <QTextStream>
00010
00011
00012 #include "mythcontext.h"
00013 #include "httpcomms.h"
00014 #include "cardutil.h"
00015 #include "channelutil.h"
00016 #include "iptvchannelfetcher.h"
00017 #include "scanmonitor.h"
00018 #include "mythlogging.h"
00019
00020 #define LOC QString("IPTVChanFetch: ")
00021
00022 static bool parse_chan_info(const QString &rawdata,
00023 IPTVChannelInfo &info,
00024 QString &channum,
00025 uint &lineNum);
00026
00027 static bool parse_extinf(const QString &data,
00028 QString &channum,
00029 QString &name);
00030
00031 IPTVChannelFetcher::IPTVChannelFetcher(
00032 uint cardid, const QString &inputname, uint sourceid,
00033 ScanMonitor *monitor) :
00034 _scan_monitor(monitor),
00035 _cardid(cardid), _inputname(inputname),
00036 _sourceid(sourceid),
00037 _chan_cnt(1), _thread_running(false),
00038 _stop_now(false), _thread(new MThread("IPTVChannelFetcher", this)),
00039 _lock()
00040 {
00041 _inputname.detach();
00042 }
00043
00044 IPTVChannelFetcher::~IPTVChannelFetcher()
00045 {
00046 Stop();
00047 delete _thread;
00048 _thread = NULL;
00049 }
00050
00054 void IPTVChannelFetcher::Stop(void)
00055 {
00056 _lock.lock();
00057
00058 while (_thread_running)
00059 {
00060 _stop_now = true;
00061 _lock.unlock();
00062 _thread->wait(5);
00063 _lock.lock();
00064 }
00065
00066 _lock.unlock();
00067
00068 _thread->wait();
00069 }
00070
00074 bool IPTVChannelFetcher::Scan(void)
00075 {
00076 _lock.lock();
00077 do { _lock.unlock(); Stop(); _lock.lock(); } while (_thread_running);
00078
00079
00080
00081 _stop_now = false;
00082
00083 _thread->start();
00084
00085 while (!_thread_running && !_stop_now)
00086 usleep(5000);
00087
00088 _lock.unlock();
00089
00090 return _thread_running;
00091 }
00092
00093 void IPTVChannelFetcher::run(void)
00094 {
00095 _thread_running = true;
00096
00097
00098 QString url = CardUtil::GetVideoDevice(_cardid);
00099
00100 if (_stop_now || url.isEmpty())
00101 {
00102 _thread_running = false;
00103 _stop_now = true;
00104 return;
00105 }
00106
00107 LOG(VB_CHANNEL, LOG_INFO, QString("Playlist URL: %1").arg(url));
00108
00109
00110 if (_scan_monitor)
00111 {
00112 _scan_monitor->ScanPercentComplete(5);
00113 _scan_monitor->ScanAppendTextToLog(QObject::tr("Downloading Playlist"));
00114 }
00115
00116 QString playlist = DownloadPlaylist(url, true);
00117
00118 if (_stop_now || playlist.isEmpty())
00119 {
00120 _thread_running = false;
00121 _stop_now = true;
00122 return;
00123 }
00124
00125
00126 if (_scan_monitor)
00127 {
00128 _scan_monitor->ScanPercentComplete(35);
00129 _scan_monitor->ScanAppendTextToLog(QObject::tr("Processing Playlist"));
00130 }
00131
00132 const fbox_chan_map_t channels = ParsePlaylist(playlist, this);
00133
00134
00135 if (_scan_monitor)
00136 _scan_monitor->ScanAppendTextToLog(QObject::tr("Adding Channels"));
00137 SetTotalNumChannels(channels.size());
00138 fbox_chan_map_t::const_iterator it = channels.begin();
00139 for (uint i = 1; it != channels.end(); ++it, ++i)
00140 {
00141 QString channum = it.key();
00142 QString name = (*it).m_name;
00143 QString xmltvid = (*it).m_xmltvid.isEmpty() ? "" : (*it).m_xmltvid;
00144 QString msg = QObject::tr("Channel #%1 : %2").arg(channum).arg(name);
00145
00146 int chanid = ChannelUtil::GetChanID(_sourceid, channum);
00147 if (chanid <= 0)
00148 {
00149 if (_scan_monitor)
00150 {
00151 _scan_monitor->ScanAppendTextToLog(
00152 QObject::tr("Adding %1").arg(msg));
00153 }
00154 chanid = ChannelUtil::CreateChanID(_sourceid, channum);
00155 ChannelUtil::CreateChannel(
00156 0, _sourceid, chanid, name, name, channum,
00157 0, 0, 0, false, false, false, QString::null,
00158 QString::null, "Default", xmltvid);
00159 }
00160 else
00161 {
00162 if (_scan_monitor)
00163 {
00164 _scan_monitor->ScanAppendTextToLog(
00165 QObject::tr("Updating %1").arg(msg));
00166 }
00167 ChannelUtil::UpdateChannel(
00168 0, _sourceid, chanid, name, name, channum,
00169 0, 0, 0, false, false, false, QString::null,
00170 QString::null, "Default", xmltvid);
00171 }
00172
00173 SetNumChannelsInserted(i);
00174 }
00175
00176 if (_scan_monitor)
00177 {
00178 _scan_monitor->ScanAppendTextToLog(QObject::tr("Done"));
00179 _scan_monitor->ScanAppendTextToLog("");
00180 _scan_monitor->ScanPercentComplete(100);
00181 _scan_monitor->ScanComplete();
00182 }
00183
00184 _thread_running = false;
00185 _stop_now = true;
00186 }
00187
00188 void IPTVChannelFetcher::SetNumChannelsParsed(uint val)
00189 {
00190 uint minval = 35, range = 70 - minval;
00191 uint pct = minval + (uint) truncf((((float)val) / _chan_cnt) * range);
00192 if (_scan_monitor)
00193 _scan_monitor->ScanPercentComplete(pct);
00194 }
00195
00196 void IPTVChannelFetcher::SetNumChannelsInserted(uint val)
00197 {
00198 uint minval = 70, range = 100 - minval;
00199 uint pct = minval + (uint) truncf((((float)val) / _chan_cnt) * range);
00200 if (_scan_monitor)
00201 _scan_monitor->ScanPercentComplete(pct);
00202 }
00203
00204 void IPTVChannelFetcher::SetMessage(const QString &status)
00205 {
00206 if (_scan_monitor)
00207 _scan_monitor->ScanAppendTextToLog(status);
00208 }
00209
00210 QString IPTVChannelFetcher::DownloadPlaylist(const QString &url,
00211 bool inQtThread)
00212 {
00213 if (url.left(4).toLower() == "file")
00214 {
00215 QString ret = "";
00216 QUrl qurl(url);
00217 QFile file(qurl.toLocalFile());
00218 if (!file.open(QIODevice::ReadOnly))
00219 {
00220 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Opening '%1'")
00221 .arg(qurl.toLocalFile()) + ENO);
00222 return ret;
00223 }
00224
00225 QTextStream stream(&file);
00226 while (!stream.atEnd())
00227 ret += stream.readLine() + "\n";
00228
00229 file.close();
00230 return ret;
00231 }
00232
00233
00234 QString redirected_url = url;
00235
00236 QString tmp = HttpComms::getHttp(
00237 redirected_url,
00238 10000 , 3 ,
00239 3 , true ,
00240 NULL , inQtThread);
00241
00242 if (redirected_url != url)
00243 {
00244 LOG(VB_CHANNEL, LOG_INFO, QString("Channel URL redirected to %1")
00245 .arg(redirected_url));
00246 }
00247
00248 return QString::fromUtf8(tmp.toAscii().constData());
00249 }
00250
00251 static uint estimate_number_of_channels(const QString &rawdata)
00252 {
00253 uint result = 0;
00254 uint numLine = 1;
00255 while (true)
00256 {
00257 QString url = rawdata.section("\n", numLine, numLine);
00258 if (url.isEmpty())
00259 return result;
00260
00261 ++numLine;
00262 if (!url.startsWith("#"))
00263 ++result;
00264 }
00265 }
00266
00267 fbox_chan_map_t IPTVChannelFetcher::ParsePlaylist(
00268 const QString &reallyrawdata, IPTVChannelFetcher *fetcher)
00269 {
00270 fbox_chan_map_t chanmap;
00271
00272 QString rawdata = reallyrawdata;
00273 rawdata.replace("\r\n", "\n");
00274
00275
00276 QString header = rawdata.section("\n", 0, 0);
00277 if (header != "#EXTM3U")
00278 {
00279 LOG(VB_GENERAL, LOG_ERR, LOC +
00280 QString("Invalid channel list header (%1)").arg(header));
00281
00282 if (fetcher)
00283 {
00284 fetcher->SetMessage(
00285 QObject::tr("ERROR: M3U channel list is malformed"));
00286 }
00287
00288 return chanmap;
00289 }
00290
00291
00292 if (fetcher)
00293 {
00294 uint num_channels = estimate_number_of_channels(rawdata);
00295 fetcher->SetTotalNumChannels(num_channels);
00296
00297 LOG(VB_CHANNEL, LOG_INFO,
00298 QString("Estimating there are %1 channels in playlist")
00299 .arg(num_channels));
00300 }
00301
00302
00303 uint lineNum = 1;
00304 for (uint i = 1; true; i++)
00305 {
00306 IPTVChannelInfo info;
00307 QString channum = QString::null;
00308
00309 if (!parse_chan_info(rawdata, info, channum, lineNum))
00310 break;
00311
00312 QString msg = QObject::tr("Encountered malformed channel");
00313 if (!channum.isEmpty())
00314 {
00315 chanmap[channum] = info;
00316
00317 msg = QObject::tr("Parsing Channel #%1 : %2 : %3")
00318 .arg(channum).arg(info.m_name).arg(info.m_url);
00319 LOG(VB_CHANNEL, LOG_INFO, msg);
00320
00321 msg = QString::null;
00322 }
00323
00324 if (fetcher)
00325 {
00326 if (!msg.isEmpty())
00327 fetcher->SetMessage(msg);
00328 fetcher->SetNumChannelsParsed(i);
00329 }
00330 }
00331
00332 return chanmap;
00333 }
00334
00335 static bool parse_chan_info(const QString &rawdata,
00336 IPTVChannelInfo &info,
00337 QString &channum,
00338 uint &lineNum)
00339 {
00340
00341
00342
00343
00344
00345 QString name;
00346 QString xmltvid;
00347 while (true)
00348 {
00349 QString line = rawdata.section("\n", lineNum, lineNum);
00350 if (line.isEmpty())
00351 return false;
00352
00353 ++lineNum;
00354 if (line.startsWith("#"))
00355 {
00356 if (line.startsWith("#EXTINF:"))
00357 {
00358 parse_extinf(line.mid(line.indexOf(':')+1), channum, name);
00359 }
00360 else if (line.startsWith("#EXTMYTHTV:"))
00361 {
00362 QString data = line.mid(line.indexOf(':')+1);
00363 if (data.startsWith("xmltvid="))
00364 {
00365 xmltvid = data.mid(data.indexOf('=')+1);
00366 }
00367 }
00368 else
00369 {
00370
00371 }
00372 }
00373 else
00374 {
00375 if (name.isEmpty())
00376 return false;
00377 QString url = line;
00378 info = IPTVChannelInfo(name, url, xmltvid);
00379 return true;
00380 }
00381 }
00382 }
00383
00384 static bool parse_extinf(const QString &line1,
00385 QString &channum,
00386 QString &name)
00387 {
00388
00389 QString msg = LOC +
00390 QString("Invalid header in channel list line \n\t\t\tEXTINF:%1")
00391 .arg(line1);
00392
00393
00394 int pos = line1.indexOf(",");
00395 if (pos < 0)
00396 {
00397 LOG(VB_GENERAL, LOG_ERR, msg);
00398 return false;
00399 }
00400
00401
00402 int oldpos = pos + 1;
00403 pos = line1.indexOf(" ", pos + 1);
00404 if (pos < 0)
00405 {
00406 LOG(VB_GENERAL, LOG_ERR, msg);
00407 return false;
00408 }
00409 channum = line1.mid(oldpos, pos - oldpos);
00410
00411
00412 pos = line1.indexOf("- ", pos + 1);
00413 if (pos < 0)
00414 {
00415 LOG(VB_GENERAL, LOG_ERR, msg);
00416 return false;
00417 }
00418 name = line1.mid(pos + 2, line1.length());
00419
00420 return true;
00421 }
00422
00423