1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qnetworkaccessdebugpipebackend_p.h" |
5 | #include "QtCore/qdatastream.h" |
6 | #include <QCoreApplication> |
7 | #include <QStringList> |
8 | #include <QUrlQuery> |
9 | #include "private/qnoncontiguousbytedevice_p.h" |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | using namespace Qt::StringLiterals; |
14 | |
15 | #ifdef QT_BUILD_INTERNAL |
16 | |
17 | enum { |
18 | ReadBufferSize = 16384, |
19 | WriteBufferSize = ReadBufferSize |
20 | }; |
21 | |
22 | QStringList QNetworkAccessDebugPipeBackendFactory::supportedSchemes() const |
23 | { |
24 | return QStringList(QStringLiteral("debugpipe" )); |
25 | } |
26 | |
27 | QNetworkAccessBackend * |
28 | QNetworkAccessDebugPipeBackendFactory::create(QNetworkAccessManager::Operation op, |
29 | const QNetworkRequest &request) const |
30 | { |
31 | // is it an operation we know of? |
32 | switch (op) { |
33 | case QNetworkAccessManager::GetOperation: |
34 | case QNetworkAccessManager::PutOperation: |
35 | break; |
36 | |
37 | default: |
38 | // no, we can't handle this operation |
39 | return nullptr; |
40 | } |
41 | |
42 | QUrl url = request.url(); |
43 | if (url.scheme() == "debugpipe"_L1 ) |
44 | return new QNetworkAccessDebugPipeBackend; |
45 | return nullptr; |
46 | } |
47 | |
48 | QNetworkAccessDebugPipeBackend::QNetworkAccessDebugPipeBackend() |
49 | : QNetworkAccessBackend(QNetworkAccessBackend::TargetType::Networked), |
50 | bareProtocol(false), |
51 | hasUploadFinished(false), |
52 | hasDownloadFinished(false), |
53 | hasEverythingFinished(false), |
54 | bytesDownloaded(0), |
55 | bytesUploaded(0) |
56 | { |
57 | } |
58 | |
59 | QNetworkAccessDebugPipeBackend::~QNetworkAccessDebugPipeBackend() |
60 | { |
61 | // this is signals disconnect, not network! |
62 | socket.disconnect(receiver: this); // we're not interested in the signals at this point |
63 | } |
64 | |
65 | void QNetworkAccessDebugPipeBackend::open() |
66 | { |
67 | socket.connectToHost(hostName: url().host(), port: url().port(defaultPort: 12345)); |
68 | socket.setReadBufferSize(ReadBufferSize); |
69 | |
70 | // socket ready read -> we can push from socket to downstream |
71 | connect(asender: &socket, SIGNAL(readyRead()), SLOT(socketReadyRead())); |
72 | connect(asender: &socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), SLOT(socketError())); |
73 | connect(asender: &socket, SIGNAL(disconnected()), SLOT(socketDisconnected())); |
74 | connect(asender: &socket, SIGNAL(connected()), SLOT(socketConnected())); |
75 | // socket bytes written -> we can push more from upstream to socket |
76 | connect(asender: &socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); |
77 | |
78 | bareProtocol = QUrlQuery(url()).queryItemValue(key: "bare"_L1 ) == "1"_L1 ; |
79 | |
80 | if (operation() == QNetworkAccessManager::PutOperation) { |
81 | createUploadByteDevice(); |
82 | QObject::connect(sender: uploadByteDevice(), SIGNAL(readyRead()), receiver: this, |
83 | SLOT(uploadReadyReadSlot())); |
84 | QMetaObject::invokeMethod(obj: this, member: "uploadReadyReadSlot" , c: Qt::QueuedConnection); |
85 | } |
86 | } |
87 | |
88 | void QNetworkAccessDebugPipeBackend::socketReadyRead() |
89 | { |
90 | readyRead(); |
91 | } |
92 | |
93 | qint64 QNetworkAccessDebugPipeBackend::read(char *data, qint64 maxlen) |
94 | { |
95 | qint64 haveRead = socket.read(data, maxlen); |
96 | |
97 | if (haveRead == -1) { |
98 | hasDownloadFinished = true; |
99 | // this ensures a good last downloadProgress is emitted |
100 | setHeader(header: QNetworkRequest::ContentLengthHeader, value: QVariant()); |
101 | possiblyFinish(); |
102 | return haveRead; |
103 | } |
104 | |
105 | bytesDownloaded += haveRead; |
106 | return haveRead; |
107 | } |
108 | |
109 | qint64 QNetworkAccessDebugPipeBackend::bytesAvailable() const |
110 | { |
111 | return socket.bytesAvailable(); |
112 | } |
113 | |
114 | void QNetworkAccessDebugPipeBackend::socketBytesWritten(qint64) |
115 | { |
116 | pushFromUpstreamToSocket(); |
117 | } |
118 | |
119 | void QNetworkAccessDebugPipeBackend::uploadReadyReadSlot() |
120 | { |
121 | pushFromUpstreamToSocket(); |
122 | } |
123 | |
124 | void QNetworkAccessDebugPipeBackend::pushFromUpstreamToSocket() |
125 | { |
126 | // FIXME |
127 | if (operation() == QNetworkAccessManager::PutOperation) { |
128 | if (hasUploadFinished) |
129 | return; |
130 | |
131 | forever { |
132 | if (socket.bytesToWrite() >= WriteBufferSize) |
133 | return; |
134 | |
135 | QByteArray data(WriteBufferSize, Qt::Uninitialized); |
136 | qint64 haveRead = uploadByteDevice()->peek(data: data.data(), maxlen: data.size()); |
137 | if (haveRead == -1) { |
138 | // EOF |
139 | hasUploadFinished = true; |
140 | possiblyFinish(); |
141 | break; |
142 | } else if (haveRead == 0) { |
143 | // nothing to read right now, we will be called again later |
144 | break; |
145 | } else { |
146 | qint64 haveWritten; |
147 | data.truncate(pos: haveRead); |
148 | haveWritten = socket.write(data: std::move(data)); |
149 | |
150 | if (haveWritten < 0) { |
151 | // write error! |
152 | QString msg = QCoreApplication::translate(context: "QNetworkAccessDebugPipeBackend" , key: "Write error writing to %1: %2" ) |
153 | .arg(args: url().toString(), args: socket.errorString()); |
154 | error(code: QNetworkReply::ProtocolFailure, errorString: msg); |
155 | finished(); |
156 | return; |
157 | } else { |
158 | uploadByteDevice()->skip(maxSize: haveWritten); |
159 | bytesUploaded += haveWritten; |
160 | } |
161 | |
162 | //QCoreApplication::processEvents(); |
163 | } |
164 | } |
165 | } |
166 | } |
167 | |
168 | void QNetworkAccessDebugPipeBackend::possiblyFinish() |
169 | { |
170 | if (hasEverythingFinished) |
171 | return; |
172 | hasEverythingFinished = true; |
173 | |
174 | if ((operation() == QNetworkAccessManager::GetOperation) && hasDownloadFinished) { |
175 | socket.close(); |
176 | finished(); |
177 | } else if ((operation() == QNetworkAccessManager::PutOperation) && hasUploadFinished) { |
178 | socket.close(); |
179 | finished(); |
180 | } |
181 | |
182 | |
183 | } |
184 | |
185 | void QNetworkAccessDebugPipeBackend::close() |
186 | { |
187 | qWarning(msg: "QNetworkAccessDebugPipeBackend::closeDownstreamChannel() %d" ,operation());; |
188 | //if (operation() == QNetworkAccessManager::GetOperation) |
189 | // socket.disconnectFromHost(); |
190 | } |
191 | |
192 | |
193 | void QNetworkAccessDebugPipeBackend::socketError() |
194 | { |
195 | qWarning(msg: "QNetworkAccessDebugPipeBackend::socketError() %d" ,socket.error()); |
196 | QNetworkReply::NetworkError code; |
197 | switch (socket.error()) { |
198 | case QAbstractSocket::RemoteHostClosedError: |
199 | return; // socketDisconnected will be called |
200 | |
201 | case QAbstractSocket::NetworkError: |
202 | code = QNetworkReply::UnknownNetworkError; |
203 | break; |
204 | |
205 | default: |
206 | code = QNetworkReply::ProtocolFailure; |
207 | break; |
208 | } |
209 | |
210 | error(code, errorString: QNetworkAccessDebugPipeBackend::tr(s: "Socket error on %1: %2" ) |
211 | .arg(args: url().toString(), args: socket.errorString())); |
212 | finished(); |
213 | disconnect(sender: &socket, SIGNAL(disconnected()), receiver: this, SLOT(socketDisconnected())); |
214 | |
215 | } |
216 | |
217 | void QNetworkAccessDebugPipeBackend::socketDisconnected() |
218 | { |
219 | if (socket.bytesToWrite() == 0) { |
220 | // normal close |
221 | } else { |
222 | readyRead(); // @todo this is odd |
223 | // abnormal close |
224 | QString msg = QNetworkAccessDebugPipeBackend::tr(s: "Remote host closed the connection prematurely on %1" ) |
225 | .arg(a: url().toString()); |
226 | error(code: QNetworkReply::RemoteHostClosedError, errorString: msg); |
227 | finished(); |
228 | } |
229 | } |
230 | |
231 | void QNetworkAccessDebugPipeBackend::socketConnected() |
232 | { |
233 | } |
234 | |
235 | |
236 | #endif |
237 | |
238 | QT_END_NAMESPACE |
239 | |
240 | #include "moc_qnetworkaccessdebugpipebackend_p.cpp" |
241 | |