File indexing completed on 2024-11-17 04:32:34
0001 /* 0002 SPDX-FileCopyrightText: 2008 Joris Guisson <joris.guisson@gmail.com> 0003 SPDX-FileCopyrightText: 2008 Ivan Vasic <ivasic@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "webseed.h" 0009 0010 #include "httpconnection.h" 0011 #include <QTimer> 0012 #include <diskio/chunkmanager.h> 0013 #include <diskio/piecedata.h> 0014 #include <klocalizedstring.h> 0015 #include <net/socketmonitor.h> 0016 #include <peer/peermanager.h> 0017 #include <torrent/torrent.h> 0018 #include <util/log.h> 0019 0020 #include <QNetworkProxyFactory> 0021 0022 namespace bt 0023 { 0024 QString WebSeed::proxy_host; 0025 Uint16 WebSeed::proxy_port = 8080; 0026 bool WebSeed::proxy_enabled = false; 0027 0028 const Uint32 RETRY_INTERVAL = 30; 0029 0030 WebSeed::WebSeed(const QUrl &url, bool user, const Torrent &tor, ChunkManager &cman) 0031 : WebSeedInterface(url, user) 0032 , tor(tor) 0033 , cman(cman) 0034 { 0035 first_chunk = last_chunk = tor.getNumChunks() + 1; 0036 num_failures = 0; 0037 conn = nullptr; 0038 downloaded = 0; 0039 current = nullptr; 0040 status = i18n("Not connected"); 0041 up_gid = down_gid = 0; 0042 cur_chunk = -1; 0043 connect(&retry_timer, &QTimer::timeout, this, &WebSeed::reset); 0044 retry_timer.setSingleShot(true); 0045 } 0046 0047 WebSeed::~WebSeed() 0048 { 0049 delete conn; 0050 delete current; 0051 } 0052 0053 void WebSeed::setGroupIDs(Uint32 up, Uint32 down) 0054 { 0055 up_gid = up; 0056 down_gid = down; 0057 if (conn) 0058 conn->setGroupIDs(up, down); 0059 } 0060 0061 void WebSeed::setProxy(const QString &host, bt::Uint16 port) 0062 { 0063 proxy_port = port; 0064 proxy_host = host; 0065 } 0066 0067 void WebSeed::setProxyEnabled(bool on) 0068 { 0069 proxy_enabled = on; 0070 } 0071 0072 void WebSeed::reset() 0073 { 0074 retry_timer.stop(); 0075 if (current) 0076 chunkStopped(); 0077 0078 if (conn) { 0079 conn->deleteLater(); 0080 conn = nullptr; 0081 } 0082 0083 first_chunk = last_chunk = tor.getNumChunks() + 1; 0084 num_failures = 0; 0085 status = i18n("Not connected"); 0086 } 0087 0088 void WebSeed::cancel() 0089 { 0090 reset(); 0091 } 0092 0093 void WebSeed::disable(const QString &reason) 0094 { 0095 setEnabled(false); 0096 status = reason; 0097 Out(SYS_CON | LOG_IMPORTANT) << "Auto disabled webseed " << url.toDisplayString() << endl; 0098 } 0099 0100 bool WebSeed::busy() const 0101 { 0102 return first_chunk < tor.getNumChunks(); 0103 } 0104 0105 Uint32 WebSeed::getDownloadRate() const 0106 { 0107 if (conn) 0108 return conn->getDownloadRate(); 0109 else 0110 return 0; 0111 } 0112 0113 void WebSeed::connectToServer() 0114 { 0115 if (!token) 0116 token = PeerManager::connectionLimits().acquire(tor.getInfoHash()); 0117 0118 if (!token) { 0119 retryLater(); 0120 return; 0121 } 0122 0123 QUrl dst = url; 0124 if (redirected_url.isValid()) 0125 dst = redirected_url; 0126 0127 if (!proxy_enabled) { 0128 QList<QNetworkProxy> proxyList = QNetworkProxyFactory::proxyForQuery(QNetworkProxyQuery(dst)); 0129 0130 if (proxyList.isEmpty()) { 0131 conn->connectTo(dst); // direct connection 0132 } else { 0133 QNetworkProxy proxy = proxyList.first(); 0134 0135 if (proxy.type() == QNetworkProxy::NoProxy) { 0136 conn->connectTo(dst); // direct connection 0137 } else { 0138 conn->connectToProxy(proxy.hostName(), proxy.port()); 0139 } 0140 } 0141 } else { 0142 if (proxy_host.isNull()) 0143 conn->connectTo(dst); // direct connection 0144 else 0145 conn->connectToProxy(proxy_host, proxy_port); // via a proxy 0146 } 0147 status = conn->getStatusString(); 0148 } 0149 0150 void WebSeed::download(Uint32 first, Uint32 last) 0151 { 0152 if (!enabled) 0153 return; 0154 0155 // Out(SYS_CON|LOG_DEBUG) << "WebSeed: downloading " << first << "-" << last << " from " << url.toDisplayString() << endl; 0156 // open connection and connect if needed 0157 if (!conn) { 0158 conn = new HttpConnection(); 0159 conn->setGroupIDs(up_gid, down_gid); 0160 } 0161 0162 if (!conn->connected()) { 0163 connectToServer(); 0164 } 0165 0166 if (first == cur_chunk && last == last_chunk && bytes_of_cur_chunk > 0) { 0167 // we already have some of the data, so reuse it 0168 continueCurChunk(); 0169 return; 0170 } 0171 0172 cur_piece = PieceData::Ptr(nullptr); 0173 first_chunk = first; 0174 last_chunk = last; 0175 cur_chunk = first; 0176 bytes_of_cur_chunk = 0; 0177 0178 QString path = url.path(); 0179 QString query = url.query(); 0180 if (path.endsWith('/')) 0181 path += tor.getNameSuggestion(); 0182 0183 if (tor.isMultiFile()) { 0184 range_queue.clear(); 0185 // make the list of ranges to download 0186 for (Uint32 i = first_chunk; i <= last_chunk; i++) { 0187 fillRangeList(i); 0188 } 0189 0190 if (range_queue.count() > 0) { 0191 // send the first request 0192 Range r = range_queue[0]; 0193 range_queue.pop_front(); 0194 const TorrentFile &tf = tor.getFile(r.file); 0195 QString host = redirected_url.isValid() ? redirected_url.host() : url.host(); 0196 conn->get(host, path + '/' + tf.getPath(), query, r.off, r.len); 0197 } 0198 } else { 0199 Uint64 len = (last_chunk - first_chunk) * tor.getChunkSize(); 0200 // last chunk can have a different size 0201 if (last_chunk == tor.getNumChunks() - 1) 0202 len += tor.getLastChunkSize(); 0203 else 0204 len += tor.getChunkSize(); 0205 0206 QString host = redirected_url.isValid() ? redirected_url.host() : url.host(); 0207 conn->get(host, path, query, first_chunk * tor.getChunkSize(), len); 0208 } 0209 } 0210 0211 void WebSeed::continueCurChunk() 0212 { 0213 QString path = url.path(); 0214 QString query = url.query(); 0215 if (path.endsWith('/') && !isUserCreated()) 0216 path += tor.getNameSuggestion(); 0217 0218 first_chunk = cur_chunk; 0219 if (tor.isMultiFile()) { 0220 range_queue.clear(); 0221 // make the list of ranges to download 0222 for (Uint32 i = first_chunk; i <= last_chunk; i++) { 0223 fillRangeList(i); 0224 } 0225 0226 bt::Uint32 length = 0; 0227 while (range_queue.count() > 0) { 0228 // send the first request, but skip the data we already have 0229 Range r = range_queue[0]; 0230 range_queue.pop_front(); 0231 if (length >= bytes_of_cur_chunk) { 0232 const TorrentFile &tf = tor.getFile(r.file); 0233 QString host = redirected_url.isValid() ? redirected_url.host() : url.host(); 0234 conn->get(host, path + '/' + tf.getPath(), query, r.off, r.len); 0235 break; 0236 } 0237 length += r.len; 0238 } 0239 } else { 0240 Uint64 len = (last_chunk - first_chunk) * tor.getChunkSize(); 0241 // last chunk can have a different size 0242 if (last_chunk == tor.getNumChunks() - 1) 0243 len += tor.getLastChunkSize(); 0244 else 0245 len += tor.getChunkSize(); 0246 0247 QString host = redirected_url.isValid() ? redirected_url.host() : url.host(); 0248 conn->get(host, path, query, first_chunk * tor.getChunkSize() + bytes_of_cur_chunk, len - bytes_of_cur_chunk); 0249 } 0250 chunkStarted(cur_chunk); 0251 } 0252 0253 void WebSeed::chunkStarted(Uint32 chunk) 0254 { 0255 Uint32 csize = cman.getChunk(chunk)->getSize(); 0256 Uint32 pieces_count = csize / MAX_PIECE_LEN; 0257 if (csize % MAX_PIECE_LEN > 0) 0258 pieces_count++; 0259 0260 if (!current) { 0261 current = new WebSeedChunkDownload(this, url.toDisplayString(), chunk, pieces_count); 0262 chunkDownloadStarted(current, chunk); 0263 } else if (current->chunk != chunk) { 0264 chunkStopped(); 0265 current = new WebSeedChunkDownload(this, url.toDisplayString(), chunk, pieces_count); 0266 chunkDownloadStarted(current, chunk); 0267 } 0268 } 0269 0270 void WebSeed::chunkStopped() 0271 { 0272 if (current) { 0273 chunkDownloadFinished(current, current->chunk); 0274 delete current; 0275 current = nullptr; 0276 } 0277 } 0278 0279 Uint32 WebSeed::update() 0280 { 0281 try { 0282 if (!conn || !busy()) 0283 return 0; 0284 0285 if (!conn->ok()) { 0286 readData(); 0287 0288 Out(SYS_CON | LOG_DEBUG) << "WebSeed: connection not OK" << endl; 0289 // shit happened delete connection 0290 status = conn->getStatusString(); 0291 if (conn->responseCode() == 404) { 0292 // if not found then retire this webseed for now 0293 retryLater(); 0294 } 0295 delete conn; 0296 conn = nullptr; 0297 token.clear(); 0298 chunkStopped(); 0299 first_chunk = last_chunk = cur_chunk = tor.getNumChunks() + 1; 0300 num_failures++; 0301 if (num_failures == 3) 0302 retryLater(); 0303 return 0; 0304 } else if (conn->closed()) { 0305 // Make sure we handle all data 0306 readData(); 0307 0308 Out(SYS_CON | LOG_DEBUG) << "WebSeed: connection closed" << endl; 0309 delete conn; 0310 conn = nullptr; 0311 token.clear(); 0312 0313 status = i18n("Connection closed"); 0314 chunkStopped(); 0315 if (last_chunk < tor.getNumChunks()) { 0316 // lets try this again if we have not yet got the full range 0317 download(cur_chunk, last_chunk); 0318 status = conn->getStatusString(); 0319 } 0320 } else if (conn->isRedirected()) { 0321 // Make sure we handle all data 0322 readData(); 0323 redirected(conn->redirectedUrl()); 0324 } else { 0325 readData(); 0326 if (range_queue.count() > 0 && conn->ready()) { 0327 if (conn->closed()) { 0328 // after a redirect it is possible that the connection is closed 0329 // so we need to reconnect to the old url 0330 conn->deleteLater(); 0331 conn = new HttpConnection(); 0332 conn->setGroupIDs(up_gid, down_gid); 0333 connectToServer(); 0334 } 0335 0336 QString path = url.path(); 0337 QString query = url.query(); 0338 if (path.endsWith('/')) 0339 path += tor.getNameSuggestion(); 0340 0341 // ask for the next range 0342 Range r = range_queue[0]; 0343 range_queue.pop_front(); 0344 const TorrentFile &tf = tor.getFile(r.file); 0345 QString host = redirected_url.isValid() ? redirected_url.host() : url.host(); 0346 conn->get(host, path + '/' + tf.getPath(), query, r.off, r.len); 0347 } 0348 status = conn->getStatusString(); 0349 } 0350 } catch (AutoDisabled &) { 0351 } 0352 Uint32 ret = downloaded; 0353 downloaded = 0; 0354 total_downloaded += ret; 0355 return ret; 0356 } 0357 0358 void WebSeed::readData() 0359 { 0360 QByteArray tmp; 0361 while (conn->getData(tmp) && cur_chunk <= last_chunk) { 0362 // Out(SYS_CON|LOG_DEBUG) << "WebSeed: handleData " << tmp.size() << endl; 0363 if (!current) 0364 chunkStarted(cur_chunk); 0365 handleData(tmp); 0366 tmp.clear(); 0367 } 0368 0369 if (cur_chunk > last_chunk) { 0370 // if the current chunk moves past the last chunk, we are done 0371 first_chunk = last_chunk = tor.getNumChunks() + 1; 0372 num_failures = 0; 0373 finished(); 0374 } 0375 } 0376 0377 void WebSeed::handleData(const QByteArray &tmp) 0378 { 0379 Uint32 off = 0; 0380 while (off < (Uint32)tmp.size() && cur_chunk <= last_chunk) { 0381 Chunk *c = cman.getChunk(cur_chunk); 0382 Uint32 bl = c->getSize() - bytes_of_cur_chunk; 0383 if (bl > tmp.size() - off) 0384 bl = tmp.size() - off; 0385 0386 // ignore data if we already have it 0387 if (c->getStatus() != Chunk::ON_DISK) { 0388 if (!cur_piece || cur_piece->parentChunk() != c) 0389 cur_piece = c->getPiece(0, c->getSize(), false); 0390 0391 if (cur_piece) 0392 cur_piece->write((const Uint8 *)tmp.data() + off, bl, bytes_of_cur_chunk); 0393 downloaded += bl; 0394 } 0395 off += bl; 0396 bytes_of_cur_chunk += bl; 0397 current->pieces_downloaded = bytes_of_cur_chunk / MAX_PIECE_LEN; 0398 0399 if (bytes_of_cur_chunk == c->getSize()) { 0400 // we have one ready 0401 bytes_of_cur_chunk = 0; 0402 cur_chunk++; 0403 if (c->getStatus() != Chunk::ON_DISK) { 0404 chunkReady(c); 0405 // It is possible that the webseed has been disabled due receiving a bad chunk 0406 if (!isEnabled()) 0407 throw AutoDisabled(); 0408 } 0409 0410 chunkStopped(); 0411 cur_piece = PieceData::Ptr(nullptr); 0412 if (cur_chunk <= last_chunk) { 0413 c = cman.getChunk(cur_chunk); 0414 cur_piece = c->getPiece(0, c->getSize(), false); 0415 chunkStarted(cur_chunk); 0416 } 0417 } 0418 } 0419 } 0420 0421 void WebSeed::fillRangeList(Uint32 chunk) 0422 { 0423 QList<Uint32> tflist; 0424 tor.calcChunkPos(chunk, tflist); 0425 Chunk *c = cman.getChunk(chunk); 0426 0427 Uint64 passed = 0; // number of bytes of the chunk which we have passed 0428 for (int i = 0; i < tflist.count(); i++) { 0429 const TorrentFile &tf = tor.getFile(tflist[i]); 0430 Range r = {tflist[i], 0, 0}; 0431 if (i == 0) 0432 r.off = tf.fileOffset(chunk, tor.getChunkSize()); 0433 0434 if (tflist.count() == 1) 0435 r.len = c->getSize(); 0436 else if (i == 0) 0437 r.len = tf.getLastChunkSize(); 0438 else if (i == tflist.count() - 1) 0439 r.len = c->getSize() - passed; 0440 else 0441 r.len = tf.getSize(); 0442 0443 // add the range 0444 if (range_queue.count() == 0) 0445 range_queue.append(r); 0446 else if (range_queue.back().file != r.file) 0447 range_queue.append(r); 0448 else { 0449 // the last range and this one are in the same file 0450 // so expand it 0451 Range &l = range_queue.back(); 0452 l.len += r.len; 0453 } 0454 0455 passed += r.len; 0456 } 0457 } 0458 0459 void WebSeed::onExcluded(Uint32 from, Uint32 to) 0460 { 0461 if (from <= first_chunk && first_chunk <= to && from <= last_chunk && last_chunk <= to) { 0462 reset(); 0463 } 0464 } 0465 0466 void WebSeed::chunkDownloaded(Uint32 chunk) 0467 { 0468 // reset if chunk downloaded is in the range we are currently downloading 0469 if (chunk >= cur_chunk) { 0470 reset(); 0471 } 0472 } 0473 0474 void WebSeed::setEnabled(bool on) 0475 { 0476 WebSeedInterface::setEnabled(on); 0477 if (!on) { 0478 reset(); 0479 } 0480 } 0481 0482 void WebSeed::redirected(const QUrl &to_url) 0483 { 0484 delete conn; 0485 conn = nullptr; 0486 token.clear(); 0487 if (to_url.isValid() && to_url.scheme() == QLatin1String("http")) { 0488 redirected_url = to_url; 0489 download(cur_chunk, last_chunk); 0490 status = conn->getStatusString(); 0491 } else { 0492 retryLater(); 0493 cur_chunk = last_chunk = first_chunk = tor.getNumChunks() + 1; 0494 } 0495 } 0496 0497 void WebSeed::retryLater() 0498 { 0499 num_failures = 3; 0500 status = i18np("Unused for %1 second (Too many connection failures)", "Unused for %1 seconds (Too many connection failures)", RETRY_INTERVAL); 0501 retry_timer.start(RETRY_INTERVAL * 1000); 0502 } 0503 0504 //////////////////////////////////////////// 0505 0506 WebSeedChunkDownload::WebSeedChunkDownload(WebSeed *ws, const QString &url, Uint32 index, Uint32 total) 0507 : ws(ws) 0508 , url(url) 0509 , chunk(index) 0510 , total_pieces(total) 0511 , pieces_downloaded(0) 0512 { 0513 } 0514 0515 WebSeedChunkDownload::~WebSeedChunkDownload() 0516 { 0517 } 0518 0519 void WebSeedChunkDownload::getStats(Stats &s) 0520 { 0521 s.current_peer_id = url; 0522 s.chunk_index = chunk; 0523 s.num_downloaders = 1; 0524 s.download_speed = ws->getDownloadRate(); 0525 s.pieces_downloaded = pieces_downloaded; 0526 s.total_pieces = total_pieces; 0527 } 0528 0529 } 0530 0531 #include "moc_webseed.cpp"