KIO

connectionbackend.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
4 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
6 SPDX-FileCopyrightText: 2024 Harald Sitter <sitter@kde.org>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "connectionbackend_p.h"
12#include <KLocalizedString>
13#include <QCoreApplication>
14#include <QElapsedTimer>
15#include <QFile>
16#include <QLocalServer>
17#include <QLocalSocket>
18#include <QPointer>
19#include <QStandardPaths>
20#include <QTemporaryFile>
21#include <cerrno>
22
23#include "kiocoreconnectiondebug.h"
24
25using namespace KIO;
26
27ConnectionBackend::ConnectionBackend(QObject *parent)
28 : QObject(parent)
29 , state(Idle)
30 , socket(nullptr)
31 , signalEmitted(false)
32{
33 localServer = nullptr;
34}
35
36ConnectionBackend::~ConnectionBackend()
37{
38}
39
40void ConnectionBackend::setSuspended(bool enable)
41{
42 if (state != Connected) {
43 return;
44 }
45 Q_ASSERT(socket);
46 Q_ASSERT(!localServer); // !tcpServer as well
47
48 if (enable) {
49 // qCDebug(KIO_CORE) << socket << "suspending";
50 socket->setReadBufferSize(1);
51 } else {
52 // qCDebug(KIO_CORE) << socket << "resuming";
53 // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119
54 socket->setReadBufferSize(StandardBufferSize);
55 if (socket->bytesAvailable() >= HeaderSize) {
56 // there are bytes available
57 QMetaObject::invokeMethod(this, &ConnectionBackend::socketReadyRead, Qt::QueuedConnection);
58 }
59
60 // We read all bytes here, but we don't use readAll() because we need
61 // to read at least one byte (even if there isn't any) so that the
62 // socket notifier is re-enabled
63 QByteArray data = socket->read(socket->bytesAvailable() + 1);
64 for (int i = data.size(); --i >= 0;) {
65 socket->ungetChar(data[i]);
66 }
67 // Workaround Qt5 bug, readyRead isn't always emitted here...
68 QMetaObject::invokeMethod(this, &ConnectionBackend::socketReadyRead, Qt::QueuedConnection);
69 }
70}
71
72bool ConnectionBackend::connectToRemote(const QUrl &url)
73{
74 Q_ASSERT(state == Idle);
75 Q_ASSERT(!socket);
76 Q_ASSERT(!localServer); // !tcpServer as well
77
78 QLocalSocket *sock = new QLocalSocket(this);
79 QString path = url.path();
80 sock->connectToServer(path);
81 socket = sock;
82
83 connect(socket, &QIODevice::readyRead, this, &ConnectionBackend::socketReadyRead);
84 connect(socket, &QLocalSocket::disconnected, this, &ConnectionBackend::socketDisconnected);
85 state = Connected;
86 return true;
87}
88
89void ConnectionBackend::socketDisconnected()
90{
91 state = Idle;
92 Q_EMIT disconnected();
93}
94
95ConnectionBackend::ConnectionResult ConnectionBackend::listenForRemote()
96{
97 Q_ASSERT(state == Idle);
98 Q_ASSERT(!socket);
99 Q_ASSERT(!localServer); // !tcpServer as well
100
102 static QBasicAtomicInt s_socketCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
104 appName.replace(QLatin1Char('/'), QLatin1Char('_')); // #357499
105 QTemporaryFile socketfile(prefix + QLatin1Char('/') + appName + QStringLiteral("XXXXXX.%1.kioworker.socket").arg(s_socketCounter.fetchAndAddAcquire(1)));
106 if (!socketfile.open()) {
107 return {false, i18n("Unable to create KIO worker: %1", QString::fromUtf8(strerror(errno)))};
108 }
109
110 QString sockname = socketfile.fileName();
111 address.clear();
112 address.setScheme(QStringLiteral("local"));
113 address.setPath(sockname);
114 socketfile.setAutoRemove(false);
115 socketfile.remove(); // can't bind if there is such a file
116
117 localServer = new QLocalServer(this);
118 if (!localServer->listen(sockname)) {
119 errorString = localServer->errorString();
120 delete localServer;
121 localServer = nullptr;
122 return {false, errorString};
123 }
124
125 connect(localServer, &QLocalServer::newConnection, this, &ConnectionBackend::newConnection);
126
127 state = Listening;
128 return {true, QString()};
129}
130
131bool ConnectionBackend::waitForIncomingTask(int ms)
132{
133 Q_ASSERT(state == Connected);
134 Q_ASSERT(socket);
135 if (socket->state() != QLocalSocket::LocalSocketState::ConnectedState) {
136 state = Idle;
137 return false; // socket has probably closed, what do we do?
138 }
139
140 signalEmitted = false;
141 if (socket->bytesAvailable()) {
142 socketReadyRead();
143 }
144 if (signalEmitted) {
145 return true; // there was enough data in the socket
146 }
147
148 // not enough data in the socket, so wait for more
149 QElapsedTimer timer;
150 timer.start();
151
152 while (socket->state() == QLocalSocket::LocalSocketState::ConnectedState && !signalEmitted && (ms == -1 || timer.elapsed() < ms)) {
153 if (!socket->waitForReadyRead(ms == -1 ? -1 : ms - timer.elapsed())) {
154 break;
155 }
156 }
157
158 if (signalEmitted) {
159 return true;
160 }
161 if (socket->state() != QLocalSocket::LocalSocketState::ConnectedState) {
162 state = Idle;
163 }
164 return false;
165}
166
167bool ConnectionBackend::sendCommand(int cmd, const QByteArray &data) const
168{
169 Q_ASSERT(state == Connected);
170 Q_ASSERT(socket);
171
172 char buffer[HeaderSize + 2];
173 sprintf(buffer, "%6zx_%2x_", static_cast<size_t>(data.size()), cmd);
174 socket->write(buffer, HeaderSize);
175 socket->write(data);
176
177 // qCDebug(KIO_CORE) << this << "Sending command" << hex << cmd << "of"
178 // << data.size() << "bytes (" << socket->bytesToWrite()
179 // << "bytes left to write )";
180
181 // blocking mode:
182 while (socket->bytesToWrite() > 0 && socket->state() == QLocalSocket::LocalSocketState::ConnectedState) {
183 socket->waitForBytesWritten(-1);
184 }
185
186 if (socket->state() != QLocalSocket::LocalSocketState::ConnectedState) {
187 qCWarning(KIO_CORE_CONNECTION) << "Socket not connected" << socket->error();
188 }
189
190 return socket->state() == QLocalSocket::LocalSocketState::ConnectedState;
191}
192
193ConnectionBackend *ConnectionBackend::nextPendingConnection()
194{
195 Q_ASSERT(state == Listening);
196 Q_ASSERT(localServer);
197 Q_ASSERT(!socket);
198
199 qCDebug(KIO_CORE_CONNECTION) << "Got a new connection";
200
201 QLocalSocket *newSocket = localServer->nextPendingConnection();
202
203 if (!newSocket) {
204 qCDebug(KIO_CORE_CONNECTION) << "... nevermind";
205 return nullptr; // there was no connection...
206 }
207
208 ConnectionBackend *result = new ConnectionBackend();
209 result->state = Connected;
210 result->socket = newSocket;
211 newSocket->setParent(result);
212 connect(newSocket, &QIODevice::readyRead, result, &ConnectionBackend::socketReadyRead);
213 connect(newSocket, &QLocalSocket::disconnected, result, &ConnectionBackend::socketDisconnected);
214
215 return result;
216}
217
218void ConnectionBackend::socketReadyRead()
219{
220 bool shouldReadAnother;
221 do {
222 if (!socket)
223 // might happen if the invokeMethods were delivered after we disconnected
224 {
225 return;
226 }
227
228 qCDebug(KIO_CORE_CONNECTION) << this << "Got" << socket->bytesAvailable() << "bytes";
229 if (!pendingTask.has_value()) {
230 // We have to read the header
231 char buffer[HeaderSize];
232
233 if (socket->bytesAvailable() < HeaderSize) {
234 return; // wait for more data
235 }
236
237 socket->read(buffer, sizeof buffer);
238 buffer[6] = 0;
239 buffer[9] = 0;
240
241 char *p = buffer;
242 while (*p == ' ') {
243 p++;
244 }
245 auto len = strtol(p, nullptr, 16);
246
247 p = buffer + 7;
248 while (*p == ' ') {
249 p++;
250 }
251 auto cmd = strtol(p, nullptr, 16);
252
253 pendingTask = Task{.cmd = static_cast<int>(cmd), .len = len};
254
255 qCDebug(KIO_CORE_CONNECTION) << this << "Beginning of command" << pendingTask->cmd << "of size" << pendingTask->len;
256 }
257
258 QPointer<ConnectionBackend> that = this;
259
260 const auto toRead = std::min<off_t>(socket->bytesAvailable(), pendingTask->len - pendingTask->data.size());
261 qCDebug(KIO_CORE_CONNECTION) << socket << "Want to read" << toRead << "bytes; appending to already existing bytes" << pendingTask->data.size();
262 pendingTask->data += socket->read(toRead);
263
264 if (pendingTask->data.size() == pendingTask->len) { // read all data of this task -> emit it and reset
265 signalEmitted = true;
266 qCDebug(KIO_CORE_CONNECTION) << "emitting task" << pendingTask->cmd << pendingTask->data.size();
267 Q_EMIT commandReceived(pendingTask.value());
268
269 pendingTask = {};
270 }
271
272 // If we're dead, better don't try anything.
273 if (that.isNull()) {
274 return;
275 }
276
277 // Do we have enough for an another read?
278 if (!pendingTask.has_value()) {
279 shouldReadAnother = socket->bytesAvailable() >= HeaderSize;
280 } else { // NOTE: if we don't have data pending we may still have a pendingTask that gets resumed when we get more data!
281 shouldReadAnother = socket->bytesAvailable();
282 }
283 } while (shouldReadAnother);
284}
285
286#include "moc_connectionbackend_p.cpp"
QString i18n(const char *text, const TYPE &arg...)
A namespace for KIO globals.
PostalAddress address(const QVariant &location)
QString path(const QString &relativePath)
QCA_EXPORT QString appName()
qsizetype size() const const
QCoreApplication * instance()
qint64 elapsed() const const
void readyRead()
void newConnection()
void connectToServer(OpenMode openMode)
void disconnected()
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
void setParent(QObject *parent)
bool isNull() const const
QString writableLocation(StandardLocation type)
QString fromUtf8(QByteArrayView str)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString path(ComponentFormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:11:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.