File indexing completed on 2024-05-05 16:13:04

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
0004     SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0005     SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "connection_p.h"
0011 #include "connectionbackend_p.h"
0012 #include "kiocoredebug.h"
0013 #include <QDebug>
0014 
0015 #include <cerrno>
0016 
0017 using namespace KIO;
0018 
0019 void ConnectionPrivate::dequeue()
0020 {
0021     if (!backend || suspended) {
0022         return;
0023     }
0024 
0025     for (const Task &task : std::as_const(outgoingTasks)) {
0026         q->sendnow(task.cmd, task.data);
0027     }
0028     outgoingTasks.clear();
0029 
0030     if (!incomingTasks.isEmpty()) {
0031         Q_EMIT q->readyRead();
0032     }
0033 }
0034 
0035 void ConnectionPrivate::commandReceived(const Task &task)
0036 {
0037     // qDebug() << this << "Command" << task.cmd << "added to the queue";
0038     if (!suspended && incomingTasks.isEmpty() && readMode == Connection::ReadMode::EventDriven) {
0039         auto dequeueFunc = [this]() {
0040             dequeue();
0041         };
0042         QMetaObject::invokeMethod(q, dequeueFunc, Qt::QueuedConnection);
0043     }
0044     incomingTasks.append(task);
0045 }
0046 
0047 void ConnectionPrivate::disconnected()
0048 {
0049     q->close();
0050     if (readMode == Connection::ReadMode::EventDriven) {
0051         QMetaObject::invokeMethod(q, &Connection::readyRead, Qt::QueuedConnection);
0052     }
0053 }
0054 
0055 void ConnectionPrivate::setBackend(ConnectionBackend *b)
0056 {
0057     delete backend;
0058     backend = b;
0059     if (backend) {
0060         q->connect(backend, &ConnectionBackend::commandReceived, q, [this](const Task &task) {
0061             commandReceived(task);
0062         });
0063         q->connect(backend, &ConnectionBackend::disconnected, q, [this]() {
0064             disconnected();
0065         });
0066         backend->setSuspended(suspended);
0067     }
0068 }
0069 
0070 Connection::Connection(QObject *parent)
0071     : QObject(parent)
0072     , d(new ConnectionPrivate)
0073 {
0074     d->q = this;
0075 }
0076 
0077 Connection::~Connection()
0078 {
0079     close();
0080 }
0081 
0082 void Connection::suspend()
0083 {
0084     // qDebug() << this << "Suspended";
0085     d->suspended = true;
0086     if (d->backend) {
0087         d->backend->setSuspended(true);
0088     }
0089 }
0090 
0091 void Connection::resume()
0092 {
0093     // send any outgoing or incoming commands that may be in queue
0094     if (d->readMode == Connection::ReadMode::EventDriven) {
0095         auto dequeueFunc = [this]() {
0096             d->dequeue();
0097         };
0098         QMetaObject::invokeMethod(this, dequeueFunc, Qt::QueuedConnection);
0099     }
0100 
0101     // qDebug() << this << "Resumed";
0102     d->suspended = false;
0103     if (d->backend) {
0104         d->backend->setSuspended(false);
0105     }
0106 }
0107 
0108 void Connection::close()
0109 {
0110     if (d->backend) {
0111         d->backend->disconnect(this);
0112         d->backend->deleteLater();
0113         d->backend = nullptr;
0114     }
0115     d->outgoingTasks.clear();
0116     d->incomingTasks.clear();
0117 }
0118 
0119 bool Connection::isConnected() const
0120 {
0121     return d->backend && d->backend->state == ConnectionBackend::Connected;
0122 }
0123 
0124 bool Connection::inited() const
0125 {
0126     return d->backend;
0127 }
0128 
0129 bool Connection::suspended() const
0130 {
0131     return d->suspended;
0132 }
0133 
0134 void Connection::connectToRemote(const QUrl &address)
0135 {
0136     // qDebug() << "Connection requested to" << address;
0137     const QString scheme = address.scheme();
0138 
0139     if (scheme == QLatin1String("local")) {
0140         d->setBackend(new ConnectionBackend(this));
0141     } else {
0142         qCWarning(KIO_CORE) << "Unknown protocol requested:" << scheme << "(" << address << ")";
0143         Q_ASSERT(0);
0144         return;
0145     }
0146 
0147     // connection succeeded
0148     if (!d->backend->connectToRemote(address)) {
0149         // qCWarning(KIO_CORE) << "could not connect to" << address << "using scheme" << scheme;
0150         delete d->backend;
0151         d->backend = nullptr;
0152         return;
0153     }
0154 
0155     d->dequeue();
0156 }
0157 
0158 QString Connection::errorString() const
0159 {
0160     if (d->backend) {
0161         return d->backend->errorString;
0162     }
0163     return QString();
0164 }
0165 
0166 bool Connection::send(int cmd, const QByteArray &data)
0167 {
0168     if (!inited() || !d->outgoingTasks.isEmpty()) {
0169         Task task;
0170         task.cmd = cmd;
0171         task.data = data;
0172         d->outgoingTasks.append(std::move(task));
0173         return true;
0174     } else {
0175         return sendnow(cmd, data);
0176     }
0177 }
0178 
0179 bool Connection::sendnow(int cmd, const QByteArray &data)
0180 {
0181     if (!d->backend || data.size() > 0xffffff || !isConnected()) {
0182         return false;
0183     }
0184 
0185     // qDebug() << this << "Sending command" << cmd << "of size" << data.size();
0186     return d->backend->sendCommand(cmd, data);
0187 }
0188 
0189 bool Connection::hasTaskAvailable() const
0190 {
0191     return !d->incomingTasks.isEmpty();
0192 }
0193 
0194 bool Connection::waitForIncomingTask(int ms)
0195 {
0196     if (!isConnected()) {
0197         return false;
0198     }
0199 
0200     if (d->backend) {
0201         return d->backend->waitForIncomingTask(ms);
0202     }
0203     return false;
0204 }
0205 
0206 int Connection::read(int *_cmd, QByteArray &data)
0207 {
0208     // if it's still empty, then it's an error
0209     if (d->incomingTasks.isEmpty()) {
0210         // qCWarning(KIO_CORE) << this << "Task list is empty!";
0211         return -1;
0212     }
0213     const Task &task = d->incomingTasks.constFirst();
0214     // qDebug() << this << "Command" << task.cmd << "removed from the queue (size" << task.data.size() << ")";
0215     *_cmd = task.cmd;
0216     data = task.data;
0217 
0218     d->incomingTasks.removeFirst();
0219 
0220     // if we didn't empty our reading queue, emit again
0221     if (!d->suspended && !d->incomingTasks.isEmpty() && d->readMode == Connection::ReadMode::EventDriven) {
0222         auto dequeueFunc = [this]() {
0223             d->dequeue();
0224         };
0225         QMetaObject::invokeMethod(this, dequeueFunc, Qt::QueuedConnection);
0226     }
0227 
0228     return data.size();
0229 }
0230 
0231 void Connection::setReadMode(ReadMode readMode)
0232 {
0233     d->readMode = readMode;
0234 }
0235 
0236 #include "moc_connection_p.cpp"