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