|
MythTV
0.26-pre
|
00001 // -*- Mode: c++ -*- 00002 00003 // Std C headers 00004 #include <cmath> 00005 #include <unistd.h> 00006 00007 // Qt headers 00008 #include <QFile> 00009 #include <QTextStream> 00010 00011 // MythTV headers 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 // Should now have _lock and no thread should be running. 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 // Step 1/4 : Get info from DB 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 // Step 2/4 : Download 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 // Step 3/4 : Process 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 // Step 4/4 : Finish up 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 // Use Myth HttpComms for http URLs 00234 QString redirected_url = url; 00235 00236 QString tmp = HttpComms::getHttp( 00237 redirected_url, 00238 10000 /* ms */, 3 /* retries */, 00239 3 /* redirects */, true /* allow gzip */, 00240 NULL /* login */, 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("#")) // ignore comments 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 // Verify header is ok 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 // estimate number of channels 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 // Parse each channel 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; // don't tell fetcher 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 // #EXTINF:0,2 - France 2 <-- duration,channum - channame 00341 // #EXTMYTHTV:xmltvid=C2.telepoche.com <-- optional line (myth specific) 00342 // #... <-- ignored comments 00343 // rtsp://maiptv.iptv.fr/iptvtv/201 <-- url 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 // Just ignore other comments 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 // data is supposed to contain the "0,2 - France 2" part 00389 QString msg = LOC + 00390 QString("Invalid header in channel list line \n\t\t\tEXTINF:%1") 00391 .arg(line1); 00392 00393 // Parse extension portion 00394 int pos = line1.indexOf(","); 00395 if (pos < 0) 00396 { 00397 LOG(VB_GENERAL, LOG_ERR, msg); 00398 return false; 00399 } 00400 00401 // Parse iptv channel number 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 // Parse iptv channel name 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 /* vim: set expandtab tabstop=4 shiftwidth=4: */
1.7.6.1