208 lines
5.8 KiB
C++
208 lines
5.8 KiB
C++
#include "guppyclient.hpp"
|
|
#include "ioutil.hpp"
|
|
#include "kristall.hpp"
|
|
|
|
GuppyClient::GuppyClient(QObject *parent) : ProtocolHandler(parent)
|
|
{
|
|
connect(&socket, &QUdpSocket::connected, this, &GuppyClient::on_connected);
|
|
connect(&socket, &QUdpSocket::readyRead, this, &GuppyClient::on_readRead);
|
|
connect(&timer, &QTimer::timeout, this, &GuppyClient::on_timerTick);
|
|
|
|
connect(&socket, &QAbstractSocket::hostFound, this, [this]() {
|
|
emit this->requestStateChange(RequestState::HostFound);
|
|
});
|
|
emit this->requestStateChange(RequestState::None);
|
|
}
|
|
|
|
GuppyClient::~GuppyClient()
|
|
{
|
|
|
|
}
|
|
|
|
bool GuppyClient::supportsScheme(const QString &scheme) const
|
|
{
|
|
return (scheme == "guppy");
|
|
}
|
|
|
|
bool GuppyClient::startRequest(const QUrl &url, RequestOptions options)
|
|
{
|
|
Q_UNUSED(options)
|
|
|
|
if(this->isInProgress())
|
|
return false;
|
|
|
|
if(url.scheme() != "guppy")
|
|
return false;
|
|
|
|
emit this->requestStateChange(RequestState::Started);
|
|
|
|
this->requested_url = url;
|
|
this->was_cancelled = false;
|
|
this->prev_seq = 0;
|
|
this->first_seq = 0;
|
|
this->last_seq = 0;
|
|
this->socket.connectToHost(url.host(), url.port(6775));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GuppyClient::isInProgress() const
|
|
{
|
|
return this->socket.isOpen();
|
|
}
|
|
|
|
bool GuppyClient::cancelRequest()
|
|
{
|
|
was_cancelled = true;
|
|
this->socket.close();
|
|
this->timer.stop();
|
|
this->body.clear();
|
|
this->chunks.clear();
|
|
return true;
|
|
}
|
|
|
|
void GuppyClient::on_connected()
|
|
{
|
|
request = (this->requested_url.toString(QUrl::FormattingOptions(QUrl::FullyEncoded)) + "\r\n").toUtf8();
|
|
if(this->socket.write(request.constData(), request.size()) <= 0)
|
|
{
|
|
this->socket.close();
|
|
return;
|
|
}
|
|
|
|
this->timer.start(2000);
|
|
|
|
emit this->requestStateChange(RequestState::Connected);
|
|
}
|
|
|
|
void GuppyClient::on_readRead()
|
|
{
|
|
QByteArray chunk = this->socket.read(65535);
|
|
|
|
if(int crlf = chunk.indexOf("\r\n"); crlf > 0) {
|
|
QByteArray header = chunk.left(crlf);
|
|
|
|
// first response packet (success, error or redirect) header should contain a space
|
|
int seq = -1;
|
|
if(int space = header.indexOf(' '); space > 0) {
|
|
QByteArray meta = header.mid(space + 1);
|
|
seq = chunk.left(space).toInt();
|
|
|
|
if(seq < 6 || seq > 2147483647) {
|
|
this->timer.stop();
|
|
this->body.clear();
|
|
this->chunks.clear();
|
|
emit this->requestStateChange(RequestState::None);
|
|
|
|
if(seq == 4) {
|
|
emit networkError(UnknownError, meta); // error
|
|
}
|
|
else if(seq == 3) { // redirect
|
|
QUrl new_url(meta);
|
|
|
|
if(new_url.isRelative()) new_url = this->requested_url.resolved(new_url);
|
|
assert(not new_url.isRelative());
|
|
|
|
this->socket.close();
|
|
this->timer.stop();
|
|
this->body.clear();
|
|
this->chunks.clear();
|
|
emit this->requestStateChange(RequestState::None);
|
|
|
|
emit redirected(new_url, false);
|
|
}
|
|
else if(seq == 1) { // input prompt
|
|
this->socket.close();
|
|
this->timer.stop();
|
|
this->body.clear();
|
|
this->chunks.clear();
|
|
|
|
emit this->requestStateChange(RequestState::None);
|
|
|
|
emit inputRequired(meta, false);
|
|
}
|
|
else {
|
|
emit networkError(ProtocolViolation, QObject::tr("invalid seq"));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
this->first_seq = seq; // success
|
|
this->mime = meta;
|
|
}
|
|
else {
|
|
seq = header.toInt();
|
|
}
|
|
|
|
if(seq < this->first_seq) {
|
|
return;
|
|
}
|
|
if(chunk.size() == crlf + 2) { // eof
|
|
last_seq = seq;
|
|
}
|
|
else if(seq >= first_seq) { // success or continuation
|
|
if(!this->prev_seq || seq >= this->prev_seq) {
|
|
this->chunks[seq] = chunk.mid(crlf + 2, chunk.size() - crlf - 2);
|
|
}
|
|
|
|
// postpone the timer when we receive the next packet
|
|
if(seq == this->prev_seq + 1) {
|
|
this->timer.start();
|
|
}
|
|
}
|
|
// acknowledge every valid packet we receive
|
|
QByteArray ack = QString("%1\r\n").arg(seq).toUtf8();
|
|
socket.write(ack.constData(), ack.size());
|
|
}
|
|
else {
|
|
emitNetworkError(this->socket.error(), this->socket.errorString());
|
|
return;
|
|
}
|
|
|
|
// append all consequent chunks we have
|
|
int next_seq = this->prev_seq ? this->prev_seq + 1 : this->first_seq;
|
|
while(next_seq != last_seq) {
|
|
QByteArray next = this->chunks.take(next_seq);
|
|
if(next.isNull()) {
|
|
break;
|
|
}
|
|
body.append(next.constData(), next.size());
|
|
this->prev_seq = next_seq++;
|
|
}
|
|
|
|
if(not this->was_cancelled) {
|
|
emit this->requestProgress(body.size());
|
|
}
|
|
|
|
// we're done when the last appended chunk is the one before the eof chunk
|
|
if(this->last_seq && next_seq == this->last_seq) {
|
|
if(not this->was_cancelled) {
|
|
emit this->requestComplete(this->body, this->mime);
|
|
this->was_cancelled = true;
|
|
}
|
|
this->socket.close();
|
|
this->timer.stop();
|
|
this->body.clear();
|
|
this->chunks.clear();
|
|
|
|
emit this->requestStateChange(RequestState::None);
|
|
}
|
|
}
|
|
|
|
void GuppyClient::on_timerTick()
|
|
{
|
|
QByteArray pkt;
|
|
if(this->prev_seq) { // resend the last ack
|
|
pkt = QString("%1\r\n").arg(this->prev_seq).toUtf8();
|
|
}
|
|
else if(this->chunks.empty()) { // resend the request
|
|
pkt = request;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
|
|
this->socket.write(pkt.constData(), pkt.size());
|
|
}
|