/***************************************************************************
* Copyright (C) 2006-2020 by Ilya Kotov *
* forkotov02@ya.ru *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include <QApplication>
#include <QStringList>
#include <QDir>
#include <QSettings>
#include <QTextCodec>
#include <stdint.h>
#include <stdlib.h>
#include <qmmp/qmmpsettings.h>
#include <qmmp/qmmp.h>
#include <qmmp/statehandler.h>
#include <qmmp/inputsource.h>
#include "httpinputsource.h"
#include "httpstreamreader.h"
#define MAX_BUFFER_SIZE 150000000UL //bytes
//curl callbacks
static size_t curl_write_data(void *data, size_t size, size_t nmemb,
void *pointer)
{
HttpStreamReader *dl = static_cast<HttpStreamReader *>(pointer);
dl->mutex()->lock();
if(dl->stream()->buf_fill > MAX_BUFFER_SIZE)
{
qWarning("HttpStreamReader: buffer has reached the maximum size, disconnecting...");
dl->stream()->aborted = true;
dl->mutex()->unlock();
return 0;
}
size_t data_size = size * nmemb;
if(dl->stream()->buf_fill + data_size > dl->stream()->buf_size)
{
char *prev = dl->stream()->buf;
dl->stream()->buf = (char *)realloc(dl->stream()->buf, dl->stream()->buf_fill + data_size);
if(!dl->stream()->buf)
{
qWarning("HttpStreamReader: unable to allocate %zu bytes", dl->stream()->buf_fill + data_size);
if(prev)
free(prev);
dl->stream()->buf_fill = 0;
dl->stream()->buf_size = 0;
dl->stream()->aborted = true;
dl->mutex()->unlock();
return 0;
}
dl->stream()->buf_size = dl->stream()->buf_fill + data_size;
}
memcpy(dl->stream()->buf + dl->stream()->buf_fill, data, data_size);
dl->stream()->buf_fill += data_size;
dl->mutex()->unlock();
dl->checkBuffer();
return data_size;
}
static size_t curl_header(void *data, size_t size, size_t nmemb,
void *pointer)
{
HttpStreamReader *dl = static_cast<HttpStreamReader *>(pointer);
dl->mutex()->lock ();
size_t data_size = size * nmemb;
if (data_size < 3)
{
dl->mutex()->unlock();
return data_size;
}
//qDebug("header received: %s", (char*) data);
QByteArray header((char *) data, data_size);
header = header.trimmed ();
if (header.left(4).contains("HTTP"))
{
qDebug("HttpStreamReader: header received");
//TODO open metadata socket
}
else if (header.left(4).contains("ICY"))
{
qDebug("HttpStreamReader: shoutcast header received");
//dl->stream()->icy_meta_data = true;
}
else
{
QString key = QString::fromLatin1(header.left(header.indexOf(":")).trimmed().toLower());
QByteArray value = header.right(header.size() - header.indexOf(":") - 1).trimmed();
dl->stream()->header.insert(key, value);
qDebug("HttpStreamReader: key=%s, value=%s",qPrintable(key), value.constData());
if (key == "icy-metaint")
{
dl->stream()->icy_metaint = value.toInt();
dl->stream()->icy_meta_data = true;
}
else if(key == "icy-name")
{
dl->stream()->icy_meta_data = true;
}
}
dl->mutex()->unlock();
return data_size;
}
int curl_progress(void *pointer, double dltotal, double dlnow, double ultotal, double ulnow)
{
Q_UNUSED(dltotal);
Q_UNUSED(dlnow);
Q_UNUSED(ultotal);
Q_UNUSED(ulnow);
HttpStreamReader *dl = static_cast<HttpStreamReader *>(pointer);
dl->mutex()->lock ();
bool aborted = dl->stream()->aborted;
dl->mutex()->unlock();
if (aborted)
return -1;
return 0;
}
HttpStreamReader::HttpStreamReader(const QString &url, HTTPInputSource *parent) : QIODevice(parent),
m_url(url),
m_parent(parent)
{
curl_global_init(CURL_GLOBAL_ALL);
m_thread = new DownloadThread(this);
QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
settings.beginGroup("HTTP");
m_codec = QTextCodec::codecForName(settings.value("icy_encoding","UTF-8").toByteArray ());
m_prebuffer_size = settings.value("buffer_size",384).toInt() * 1000;
if(settings.value("override_user_agent",false).toBool())
m_userAgent = settings.value("user_agent").toString();
if(m_userAgent.isEmpty())
m_userAgent = QString("qmmp/%1").arg(Qmmp::strVersion());;
if (!m_codec)
m_codec = QTextCodec::codecForName ("UTF-8");
#ifdef WITH_ENCA
if(settings.value("use_enca", false).toBool())
m_analyser = enca_analyser_alloc(settings.value("enca_lang").toByteArray ().constData());
if(m_analyser)
enca_set_threshold(m_analyser, 1.38);
#endif
settings.endGroup();
}
HttpStreamReader::~HttpStreamReader()
{
abort();
curl_global_cleanup();
m_stream.aborted = true;
m_stream.buf_fill = 0;
m_stream.buf_size = 0;
if (m_stream.buf)
free(m_stream.buf);
m_stream.buf = nullptr;
#ifdef WITH_ENCA
if(m_analyser)
enca_analyser_free(m_analyser);
#endif
}
bool HttpStreamReader::atEnd () const
{
return false;
}
qint64 HttpStreamReader::bytesToWrite () const
{
return -1;
}
void HttpStreamReader::close ()
{
abort();
QIODevice::close();
}
bool HttpStreamReader::isSequential () const
{
return true;
}
bool HttpStreamReader::open(OpenMode mode)
{
if(m_ready && mode == QIODevice::ReadOnly)
{
QIODevice::open(mode);
return true;
}
return false;
}
bool HttpStreamReader::seek (qint64 pos)
{
Q_UNUSED(pos);