MythTV  0.26-pre
iptvchannelfetcher.cpp
Go to the documentation of this file.
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: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends