File indexing completed on 2023-10-03 07:53:57
0001 /* This file is part of the KDE project 0002 0003 Copyright (C) 2008 Lukas Appelhans <l.appelhans@gmx.de> 0004 Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net> 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public 0008 License as published by the Free Software Foundation; either 0009 version 2 of the License, or (at your option) any later version. 0010 */ 0011 #include "datasourcefactory.h" 0012 #include "bitset.h" 0013 #include "settings.h" 0014 0015 #include "core/filedeleter.h" 0016 #include "core/kget.h" 0017 #include "core/signature.h" 0018 #include "core/verifier.h" 0019 0020 #include <cmath> 0021 0022 #include <QDir> 0023 #include <QDomText> 0024 #include <QTimer> 0025 #include <QVarLengthArray> 0026 0027 #include <KIO/FileJob> 0028 #include <KLocalizedString> 0029 #include <KMessageBox> 0030 #include <KMountPoint> 0031 0032 #include "kget_debug.h" 0033 0034 #include <qplatformdefs.h> 0035 0036 const int SPEEDTIMER = 1000; // 1 second... 0037 0038 DataSourceFactory::DataSourceFactory(QObject *parent, const QUrl &dest, KIO::filesize_t size, KIO::fileoffset_t segSize) 0039 : QObject(parent) 0040 , m_capabilities() 0041 , m_dest(dest) 0042 , m_size(size) 0043 , m_downloadedSize(0) 0044 , m_segSize(segSize) 0045 , m_speed(0) 0046 , m_percent(0) 0047 , m_tempOffset(0) 0048 , m_startedChunks(nullptr) 0049 , m_finishedChunks(nullptr) 0050 , m_putJob(nullptr) 0051 , m_doDownload(true) 0052 , m_open(false) 0053 , m_blocked(false) 0054 , m_startTried(false) 0055 , m_findFilesizeTried(false) 0056 , m_assignTried(false) 0057 , m_movingFile(false) 0058 , m_finished(false) 0059 , m_downloadInitialized(false) 0060 , m_sizeInitiallyDefined(m_size) 0061 , m_sizeFoundOnFinish(false) 0062 , m_maxMirrorsUsed(3) 0063 , m_speedTimer(nullptr) 0064 , m_status(Job::Stopped) 0065 , m_statusBeforeMove(m_status) 0066 , m_verifier(nullptr) 0067 , m_signature(nullptr) 0068 { 0069 qCDebug(KGET_DEBUG) << "Initialize DataSourceFactory: Dest: " + m_dest.toLocalFile() + "Size: " + QString::number(m_size) 0070 + "SegSize: " + QString::number(m_segSize); 0071 0072 m_prevDownloadedSizes.append(0); 0073 } 0074 0075 DataSourceFactory::~DataSourceFactory() 0076 { 0077 killPutJob(); 0078 delete m_startedChunks; 0079 delete m_finishedChunks; 0080 } 0081 0082 void DataSourceFactory::init() 0083 { 0084 if (!m_doDownload) { 0085 return; 0086 } 0087 0088 if (!m_speedTimer) { 0089 m_speedTimer = new QTimer(this); 0090 m_speedTimer->setInterval(SPEEDTIMER); 0091 connect(m_speedTimer, &QTimer::timeout, this, &DataSourceFactory::speedChanged); 0092 } 0093 0094 if (m_segSize && m_size) { 0095 const int hasRemainder = (m_size % m_segSize == 0) ? 0 : 1; 0096 const int bitSetSize = (m_size / m_segSize) + hasRemainder; // round up if needed 0097 if (!m_startedChunks && bitSetSize) { 0098 m_startedChunks = new BitSet(bitSetSize); 0099 } 0100 if (!m_finishedChunks && bitSetSize) { 0101 m_finishedChunks = new BitSet(bitSetSize); 0102 } 0103 } 0104 } 0105 0106 void DataSourceFactory::deinit() 0107 { 0108 if (m_downloadInitialized && QFile::exists(m_dest.toLocalFile())) { 0109 FileDeleter::deleteFile(m_dest); 0110 } 0111 } 0112 0113 void DataSourceFactory::findFileSize() 0114 { 0115 qCDebug(KGET_DEBUG) << "Find the filesize" << this; 0116 if (!m_size && !m_dest.isEmpty() && !m_sources.isEmpty()) { 0117 foreach (TransferDataSource *source, m_sources) { 0118 if (source->capabilities() & Transfer::Cap_FindFilesize) { 0119 connect(source, &TransferDataSource::foundFileSize, this, &DataSourceFactory::slotFoundFileSize); 0120 connect(source, &TransferDataSource::finishedDownload, this, &DataSourceFactory::slotFinishedDownload); 0121 0122 m_speedTimer->start(); 0123 source->findFileSize(m_segSize); 0124 changeStatus(Job::Running); 0125 slotUpdateCapabilities(); 0126 return; 0127 } 0128 } 0129 } 0130 } 0131 0132 void DataSourceFactory::slotFoundFileSize(TransferDataSource *source, KIO::filesize_t fileSize, const QPair<int, int> &segmentRange) 0133 { 0134 m_size = fileSize; 0135 qCDebug(KGET_DEBUG) << source << "found size" << m_size << "and is assigned segments" << segmentRange << this; 0136 Q_EMIT dataSourceFactoryChange(Transfer::Tc_TotalSize); 0137 0138 init(); 0139 0140 if ((segmentRange.first != -1) && (segmentRange.second != -1)) { 0141 m_startedChunks->setRange(segmentRange.first, segmentRange.second, true); 0142 } 0143 0144 if (m_startTried) { 0145 start(); 0146 } 0147 } 0148 0149 void DataSourceFactory::slotFinishedDownload(TransferDataSource *source, KIO::filesize_t size) 0150 { 0151 Q_UNUSED(source) 0152 Q_UNUSED(size) 0153 0154 m_speedTimer->stop(); 0155 m_finished = true; 0156 } 0157 0158 bool DataSourceFactory::checkLocalFile() 0159 { 0160 QString dest_orig = m_dest.toLocalFile(); 0161 QString _dest_part(dest_orig); 0162 0163 QT_STATBUF buff_part; 0164 bool bPartExists = (QT_STAT(_dest_part.toUtf8().constData(), &buff_part) != -1); 0165 if (!bPartExists) { 0166 QString _dest = dest_orig; 0167 int fd = -1; 0168 mode_t initialMode = 0666; 0169 0170 fd = QT_OPEN(_dest.toUtf8().constData(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); 0171 if (fd < 0) { 0172 qCDebug(KGET_DEBUG) << " error"; 0173 return false; 0174 } else { 0175 close(fd); 0176 } 0177 } // TODO: Port to use Qt functions maybe 0178 0179 qCDebug(KGET_DEBUG) << "success"; 0180 return true; 0181 } 0182 0183 void DataSourceFactory::start() 0184 { 0185 qCDebug(KGET_DEBUG) << "Start DataSourceFactory"; 0186 if (m_movingFile || (m_status == Job::Finished)) { 0187 return; 0188 } 0189 if (!m_doDownload) { 0190 m_startTried = true; 0191 return; 0192 } 0193 0194 // the file already exists, even though DataSourceFactory has not been initialized remove it 0195 // to avoid problems like over methods not finished removing it because of a redownload 0196 if (!m_downloadInitialized && QFile::exists(m_dest.toLocalFile())) { 0197 qCDebug(KGET_DEBUG) << "Removing existing file."; 0198 m_startTried = true; 0199 FileDeleter::deleteFile(m_dest, this, SLOT(slotRemovedFile())); 0200 return; 0201 } 0202 0203 m_downloadInitialized = true; 0204 0205 // create all dirs needed 0206 QDir dir; 0207 dir.mkpath(m_dest.adjusted(QUrl::RemoveFilename).toLocalFile()); 0208 if (checkLocalFile()) { 0209 if (!m_putJob) { 0210 m_putJob = KIO::open(m_dest, QIODevice::WriteOnly | QIODevice::ReadOnly); 0211 connect(m_putJob, &KIO::FileJob::open, this, &DataSourceFactory::slotOpen); 0212 connect(m_putJob, &QObject::destroyed, this, &DataSourceFactory::slotPutJobDestroyed); 0213 m_startTried = true; 0214 return; 0215 } 0216 } else { 0217 // could not create file, maybe device not mounted so abort 0218 m_startTried = true; 0219 changeStatus(Job::Aborted); 0220 return; 0221 } 0222 0223 init(); 0224 0225 if (!m_open) { 0226 m_startTried = true; 0227 return; 0228 } 0229 0230 if (!m_size) { 0231 if (!m_findFilesizeTried && m_sources.count()) { 0232 m_findFilesizeTried = true; 0233 findFileSize(); 0234 } 0235 m_startTried = true; 0236 return; 0237 } 0238 0239 if (assignNeeded()) { 0240 if (m_sources.count()) { 0241 qCDebug(KGET_DEBUG) << "Assigning a TransferDataSource."; 0242 // simply assign a TransferDataSource 0243 assignSegments(*m_sources.begin()); 0244 } else if (m_unusedUrls.count()) { 0245 qCDebug(KGET_DEBUG) << "Assigning an unused mirror"; 0246 // takes the first unused mirror 0247 addMirror(m_unusedUrls.takeFirst(), true, m_unusedConnections.takeFirst()); 0248 } 0249 } 0250 0251 if (m_assignTried) { 0252 m_assignTried = false; 0253 0254 foreach (TransferDataSource *source, m_sources) { 0255 assignSegments(source); 0256 } 0257 } 0258 0259 if (m_open) { 0260 // check if the filesystem supports a file of m_size 0261 const static KIO::filesize_t maxFatSize = 4294967295; 0262 if (m_size > maxFatSize) { 0263 KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(m_dest.adjusted(QUrl::RemoveFilename).toLocalFile()); 0264 if (mountPoint) { 0265 if (mountPoint->mountType() == "vfat") { // TODO check what is reported on Windows for vfat 0266 stop(); 0267 KMessageBox::error(nullptr, i18n("Filesize is larger than maximum file size supported by VFAT."), i18n("Error")); 0268 return; 0269 } 0270 } 0271 } 0272 0273 QFile::resize(m_dest.toLocalFile(), m_size); // TODO should we keep that? 0274 m_speedTimer->start(); 0275 0276 foreach (TransferDataSource *source, m_sources) { 0277 source->start(); 0278 } 0279 0280 m_startTried = false; 0281 changeStatus(Job::Running); 0282 } 0283 slotUpdateCapabilities(); 0284 } 0285 0286 void DataSourceFactory::slotRemovedFile() 0287 { 0288 qCDebug(KGET_DEBUG) << "File has been removed" << this; 0289 if (m_startTried) { 0290 m_startTried = false; 0291 start(); 0292 } 0293 } 0294 0295 void DataSourceFactory::slotOpen(KIO::Job *job) 0296 { 0297 Q_UNUSED(job) 0298 qCDebug(KGET_DEBUG) << "File opened" << this; 0299 0300 if (!m_speedTimer) { 0301 init(); 0302 } 0303 0304 connect(m_putJob, &KIO::FileJob::position, this, &DataSourceFactory::slotOffset); 0305 connect(m_putJob, &KIO::FileJob::written, this, &DataSourceFactory::slotDataWritten); 0306 m_open = true; 0307 0308 if (m_startTried) { 0309 start(); 0310 } 0311 } 0312 0313 void DataSourceFactory::stop() 0314 { 0315 qCDebug(KGET_DEBUG) << "Stopping" << this; 0316 if (m_movingFile || (m_status == Job::Finished)) { 0317 return; 0318 } 0319 0320 if (m_speedTimer) { 0321 m_speedTimer->stop(); 0322 } 0323 0324 foreach (TransferDataSource *source, m_sources) { 0325 source->stop(); 0326 } 0327 m_startTried = false; 0328 m_findFilesizeTried = false; 0329 changeStatus(Job::Stopped); 0330 0331 slotUpdateCapabilities(); 0332 } 0333 0334 void DataSourceFactory::setDoDownload(bool doDownload) 0335 { 0336 if (m_doDownload == doDownload) { 0337 return; 0338 } 0339 0340 m_doDownload = doDownload; 0341 if (m_doDownload) { 0342 if (m_startTried) { 0343 start(); 0344 } 0345 } else { 0346 if (m_status == Job::Running) { 0347 stop(); 0348 } 0349 } 0350 } 0351 0352 void DataSourceFactory::addMirror(const QUrl &url, int numParallelConnections) 0353 { 0354 addMirror(url, true, numParallelConnections, false); 0355 } 0356 0357 void DataSourceFactory::addMirror(const QUrl &url, bool used, int numParallelConnections) 0358 { 0359 addMirror(url, used, numParallelConnections, true); 0360 } 0361 0362 void DataSourceFactory::addMirror(const QUrl &url, bool used, int numParallelConnections, bool usedDefined) 0363 { 0364 if (!url.isValid() || url.isEmpty()) { 0365 qCDebug(KGET_DEBUG) << "Url is not usable: " << url.toString(); 0366 return; 0367 } 0368 if (numParallelConnections <= 0) { 0369 numParallelConnections = 1; 0370 } 0371 if (!usedDefined) { 0372 used = true; 0373 } 0374 0375 if (used) { 0376 // there is already a TransferDataSource with that url, reuse it and modify numParallelSegments 0377 if (m_sources.contains(url)) { 0378 TransferDataSource *source = m_sources[url]; 0379 source->setParallelSegments(numParallelConnections); 0380 if (source->changeNeeded() > 0) { 0381 assignSegments(source); 0382 } else { 0383 for (int i = source->changeNeeded(); i < 0; ++i) { 0384 const QPair<int, int> removed = source->removeConnection(); 0385 qCDebug(KGET_DEBUG) << "Remove connection with segments" << removed; 0386 const int start = removed.first; 0387 const int end = removed.second; 0388 if ((start != -1) && (end != -1)) { 0389 m_startedChunks->setRange(start, end, false); 0390 } 0391 } 0392 } 0393 } else { 0394 if (m_sources.count() < m_maxMirrorsUsed) { 0395 TransferDataSource *source = KGet::createTransferDataSource(url, QDomElement(), this); 0396 if (source) { 0397 qCDebug(KGET_DEBUG) << "Successfully created a TransferDataSource for " << url.toString() << this; 0398 0399 // url might have been an unused Mirror, so remove it in any case 0400 const int index = m_unusedUrls.indexOf(url); 0401 if (index > -1) { 0402 m_unusedUrls.removeAt(index); 0403 m_unusedConnections.removeAt(index); 0404 } 0405 0406 m_sources[url] = source; 0407 m_sources[url]->setParallelSegments(numParallelConnections); 0408 if (m_sizeInitiallyDefined) { 0409 source->setSupposedSize(m_size); 0410 } 0411 0412 connect(source, &TransferDataSource::capabilitiesChanged, this, &DataSourceFactory::slotUpdateCapabilities); 0413 connect(source, 0414 SIGNAL(brokenSegments(TransferDataSource *, QPair<int, int>)), 0415 this, 0416 SLOT(brokenSegments(TransferDataSource *, QPair<int, int>))); 0417 connect(source, &TransferDataSource::broken, this, &DataSourceFactory::broken); 0418 connect(source, &TransferDataSource::finishedSegment, this, &DataSourceFactory::finishedSegment); 0419 connect(source, SIGNAL(data(KIO::fileoffset_t, QByteArray, bool &)), this, SLOT(slotWriteData(KIO::fileoffset_t, QByteArray, bool &))); 0420 connect(source, 0421 SIGNAL(freeSegments(TransferDataSource *, QPair<int, int>)), 0422 this, 0423 SLOT(slotFreeSegments(TransferDataSource *, QPair<int, int>))); 0424 connect(source, &TransferDataSource::log, this, &DataSourceFactory::log); 0425 connect(source, &TransferDataSource::urlChanged, this, &DataSourceFactory::slotUrlChanged); 0426 0427 slotUpdateCapabilities(); 0428 0429 assignSegments(source); 0430 0431 // the job is already running, so also start the TransferDataSource 0432 if (!m_assignTried && !m_startTried && m_putJob && m_open && (m_status == Job::Running)) { 0433 source->start(); 0434 } 0435 } else { 0436 qCDebug(KGET_DEBUG) << "A TransferDataSource could not be created for " << url.toString(); 0437 } 0438 } else if (usedDefined) { 0439 // increase the number of allowed mirrors as the user wants to use this one! 0440 ++m_maxMirrorsUsed; 0441 addMirror(url, used, numParallelConnections, usedDefined); 0442 return; 0443 } else { 0444 m_unusedUrls.append(url); 0445 m_unusedConnections.append(numParallelConnections); 0446 } 0447 } 0448 } else { 0449 if (m_sources.contains(url)) { 0450 removeMirror(url); 0451 } else { 0452 m_unusedUrls.append(url); 0453 m_unusedConnections.append(numParallelConnections); 0454 } 0455 } 0456 } 0457 0458 void DataSourceFactory::slotUrlChanged(const QUrl &old, const QUrl &newUrl) 0459 { 0460 TransferDataSource *ds = m_sources.take(old); 0461 m_sources[newUrl] = ds; 0462 Q_EMIT dataSourceFactoryChange(Transfer::Tc_Source | Transfer::Tc_FileName); 0463 } 0464 0465 void DataSourceFactory::removeMirror(const QUrl &url) 0466 { 0467 qCDebug(KGET_DEBUG) << "Removing mirror: " << url; 0468 if (m_sources.contains(url)) { 0469 TransferDataSource *source = m_sources[url]; 0470 source->stop(); 0471 const QList<QPair<int, int>> assigned = source->assignedSegments(); 0472 m_sources.remove(url); 0473 m_unusedUrls.append(url); 0474 m_unusedConnections.append(source->parallelSegments()); 0475 delete source; 0476 0477 for (int i = 0; i < assigned.count(); ++i) { 0478 const int start = assigned[i].first; 0479 const int end = assigned[i].second; 0480 if ((start != -1) && (end != -1)) { 0481 m_startedChunks->setRange(start, end, false); 0482 qCDebug(KGET_DEBUG) << "Segmentrange" << start << '-' << end << "not assigned anymore."; 0483 } 0484 } 0485 } 0486 0487 if ((m_status == Job::Running) && assignNeeded()) { 0488 // here we only handle the case when there are existing TransferDataSources, 0489 // the other case is triggered when stopping and then starting again 0490 if (m_sources.count()) { 0491 qCDebug(KGET_DEBUG) << "Assigning a TransferDataSource."; 0492 // simply assign a TransferDataSource 0493 assignSegments(*m_sources.begin()); 0494 } 0495 } 0496 } 0497 0498 void DataSourceFactory::setMirrors(const QHash<QUrl, QPair<bool, int>> &mirrors) 0499 { 0500 // first remove the not set DataSources 0501 QList<QUrl> oldUrls = m_sources.keys(); 0502 QList<QUrl> newUrls = mirrors.keys(); 0503 0504 foreach (const QUrl &url, oldUrls) { 0505 if (!newUrls.contains(url)) { 0506 removeMirror(url); 0507 } 0508 } 0509 0510 // remove all unused Mirrors and simply readd them below 0511 m_unusedUrls.clear(); 0512 m_unusedConnections.clear(); 0513 0514 // second modify the existing DataSources and add the new ones 0515 QHash<QUrl, QPair<bool, int>>::const_iterator it; 0516 QHash<QUrl, QPair<bool, int>>::const_iterator itEnd = mirrors.constEnd(); 0517 for (it = mirrors.constBegin(); it != itEnd; ++it) { 0518 addMirror(it.key(), it.value().first, it.value().second, true); 0519 } 0520 } 0521 0522 QHash<QUrl, QPair<bool, int>> DataSourceFactory::mirrors() const 0523 { 0524 QHash<QUrl, QPair<bool, int>> mirrors; 0525 0526 QHash<QUrl, TransferDataSource *>::const_iterator it; 0527 QHash<QUrl, TransferDataSource *>::const_iterator itEnd = m_sources.constEnd(); 0528 for (it = m_sources.constBegin(); it != itEnd; ++it) { 0529 mirrors[it.key()] = QPair<bool, int>(true, (*it)->parallelSegments()); 0530 } 0531 0532 for (int i = 0; i < m_unusedUrls.count(); ++i) { 0533 mirrors[m_unusedUrls[i]] = QPair<bool, int>(false, m_unusedConnections[i]); 0534 } 0535 0536 return mirrors; 0537 } 0538 0539 bool DataSourceFactory::assignNeeded() const 0540 { 0541 bool assignNeeded = true; 0542 QHash<QUrl, TransferDataSource *>::const_iterator it; 0543 QHash<QUrl, TransferDataSource *>::const_iterator itEnd = m_sources.constEnd(); 0544 for (it = m_sources.constBegin(); it != itEnd; ++it) { 0545 if ((*it)->currentSegments()) { 0546 // at least one TransferDataSource is still running, so no assign needed 0547 assignNeeded = false; 0548 break; 0549 } 0550 } 0551 return assignNeeded; 0552 } 0553 0554 void DataSourceFactory::brokenSegments(TransferDataSource *source, const QPair<int, int> &segmentRange) 0555 { 0556 qCDebug(KGET_DEBUG) << "Segments" << segmentRange << "broken," << source; 0557 if (!source || !m_startedChunks || !m_finishedChunks || (segmentRange.first < 0) || (segmentRange.second < 0) 0558 || (static_cast<quint32>(segmentRange.second) > m_finishedChunks->getNumBits())) { 0559 return; 0560 } 0561 0562 const int start = segmentRange.first; 0563 const int end = segmentRange.second; 0564 if ((start != -1) && (end != -1)) { 0565 m_startedChunks->setRange(start, end, false); 0566 } 0567 0568 removeMirror(source->sourceUrl()); 0569 } 0570 0571 void DataSourceFactory::broken(TransferDataSource *source, TransferDataSource::Error error) 0572 { 0573 qCDebug(KGET_DEBUG) << source << "is broken with error" << error; 0574 const QString url = source->sourceUrl().toString(); 0575 0576 removeMirror(source->sourceUrl()); 0577 0578 if (error == TransferDataSource::WrongDownloadSize) { 0579 KMessageBox::error(nullptr, 0580 i18nc("A mirror is removed when the file has the wrong download size", "%1 removed as it did report a wrong file size.", url), 0581 i18n("Error")); 0582 } 0583 } 0584 0585 void DataSourceFactory::slotFreeSegments(TransferDataSource *source, QPair<int, int> segmentRange) 0586 { 0587 qCDebug(KGET_DEBUG) << "Segments freed:" << source << segmentRange; 0588 0589 const int start = segmentRange.first; 0590 const int end = segmentRange.second; 0591 if ((start != -1) && (end != -1)) { 0592 m_startedChunks->setRange(start, end, false); 0593 qCDebug(KGET_DEBUG) << "Segmentrange" << start << '-' << end << "not assigned anymore."; 0594 } 0595 0596 assignSegments(source); 0597 } 0598 0599 void DataSourceFactory::finishedSegment(TransferDataSource *source, int segmentNumber, bool connectionFinished) 0600 { 0601 if (!source || (segmentNumber < 0) || (static_cast<quint32>(segmentNumber) > m_finishedChunks->getNumBits())) { 0602 qCDebug(KGET_DEBUG) << "Incorrect data"; 0603 return; 0604 } 0605 0606 m_finishedChunks->set(segmentNumber, true); 0607 0608 if (!connectionFinished) { 0609 qCDebug(KGET_DEBUG) << "Some segments still not finished"; 0610 return; 0611 } 0612 0613 m_finished = m_finishedChunks->allOn(); 0614 if (m_finished) { 0615 qDebug() << "All segments have been downloaded."; 0616 return; 0617 } 0618 0619 assignSegments(source); 0620 } 0621 0622 void DataSourceFactory::assignSegments(TransferDataSource *source) 0623 { 0624 if (!m_startedChunks || !m_finishedChunks) { 0625 qCDebug(KGET_DEBUG) << "Assign tried"; 0626 m_assignTried = true; 0627 return; 0628 } 0629 if (m_finishedChunks->allOn()) { 0630 qCDebug(KGET_DEBUG) << "All segments are finished already."; 0631 return; 0632 } 0633 0634 // no more segments needed for that TransferDataSource 0635 if (source->changeNeeded() <= 0) { 0636 qCDebug(KGET_DEBUG) << "No change needed for" << source; 0637 return; 0638 } 0639 0640 // find the segments that should be assigned to the transferDataSource 0641 int newStart = -1; 0642 int newEnd = -1; 0643 0644 // a split needed 0645 if (m_startedChunks->allOn()) { 0646 int unfinished = 0; 0647 TransferDataSource *target = nullptr; 0648 foreach (TransferDataSource *source, m_sources) { 0649 int temp = source->countUnfinishedSegments(); 0650 if (temp > unfinished) { 0651 unfinished = temp; 0652 target = source; 0653 } 0654 } 0655 if (!unfinished || !target) { 0656 return; 0657 } 0658 0659 const QPair<int, int> splitResult = target->split(); 0660 newStart = splitResult.first; 0661 newEnd = splitResult.second; 0662 } else { 0663 m_startedChunks->getContinuousRange(&newStart, &newEnd, false); 0664 } 0665 0666 if ((newStart == -1) || (newEnd == -1)) { 0667 qCDebug(KGET_DEBUG) << "No segment can be assigned."; 0668 return; 0669 } 0670 0671 const KIO::fileoffset_t rest = m_size % m_segSize; 0672 0673 // the lastSegsize is rest, but only if there is a rest and it is the last segment of the download 0674 const KIO::fileoffset_t lastSegSize = ((static_cast<uint>(newEnd + 1) == m_startedChunks->getNumBits() && rest) ? rest : m_segSize); 0675 0676 qCDebug(KGET_DEBUG) << "Segments assigned:" << newStart << "-" << newEnd << "segment-size:" << m_segSize << "rest:" << rest; 0677 m_startedChunks->setRange(newStart, newEnd, true); 0678 source->addSegments(qMakePair(m_segSize, lastSegSize), qMakePair(newStart, newEnd)); 0679 0680 // there should still be segments added to this transfer 0681 if (source->changeNeeded() > 0) { 0682 assignSegments(source); 0683 } 0684 } 0685 0686 // TODO implement checks if the correct offsets etc. are used + error recovering e.g. when something else 0687 // touches the file 0688 void DataSourceFactory::slotWriteData(KIO::fileoffset_t offset, const QByteArray &data, bool &worked) 0689 { 0690 worked = !m_blocked && !m_movingFile && m_open; 0691 if (m_blocked || m_movingFile || !m_open) { 0692 return; 0693 } 0694 0695 m_blocked = true; 0696 m_tempOffset = offset; 0697 m_tempData = data; 0698 m_putJob->seek(offset); 0699 } 0700 0701 void DataSourceFactory::slotOffset(KIO::Job *job, KIO::filesize_t offset) 0702 { 0703 Q_UNUSED(job) 0704 Q_UNUSED(offset) 0705 0706 m_putJob->write(m_tempData); 0707 } 0708 0709 void DataSourceFactory::slotDataWritten(KIO::Job *job, KIO::filesize_t written) 0710 { 0711 Q_UNUSED(job) 0712 0713 auto tempSize = static_cast<KIO::filesize_t>(m_tempData.size()); 0714 // the complete data has been written 0715 if (written == tempSize) // TODO if not same cache it temporarily! 0716 { 0717 m_downloadedSize += written; 0718 Q_EMIT dataSourceFactoryChange(Transfer::Tc_DownloadedSize); 0719 // m_tempCache.clear(); 0720 } 0721 0722 if (m_finished) { 0723 m_speedTimer->stop(); 0724 killPutJob(); 0725 changeStatus(Job::Finished); 0726 } 0727 m_tempData.clear(); 0728 m_blocked = false; 0729 } 0730 0731 void DataSourceFactory::slotPercent(KJob *job, ulong p) 0732 { 0733 Q_UNUSED(job) 0734 m_percent = p; 0735 Q_EMIT dataSourceFactoryChange(Transfer::Tc_Percent); 0736 } 0737 0738 void DataSourceFactory::speedChanged() 0739 { 0740 m_speed = (m_downloadedSize - m_prevDownloadedSizes.first()) / (SPEEDTIMER * m_prevDownloadedSizes.size() / 1000); // downloaded in 1 second 0741 0742 m_prevDownloadedSizes.append(m_downloadedSize); 0743 if (m_prevDownloadedSizes.size() > 10) 0744 m_prevDownloadedSizes.removeFirst(); 0745 0746 ulong percent = (m_size ? (m_downloadedSize * 100 / m_size) : 0); 0747 const bool percentChanged = (percent != m_percent); 0748 m_percent = percent; 0749 0750 Transfer::ChangesFlags change = (percentChanged ? (Transfer::Tc_DownloadSpeed | Transfer::Tc_Percent) : Transfer::Tc_DownloadSpeed); 0751 Q_EMIT dataSourceFactoryChange(change); 0752 } 0753 0754 void DataSourceFactory::killPutJob() 0755 { 0756 if (m_putJob) { 0757 qCDebug(KGET_DEBUG) << "Closing the file"; 0758 m_open = false; 0759 m_putJob->close(); 0760 m_putJob = nullptr; 0761 } 0762 } 0763 0764 void DataSourceFactory::slotPutJobDestroyed(QObject *job) 0765 { 0766 Q_UNUSED(job) 0767 0768 m_putJob = nullptr; 0769 } 0770 0771 bool DataSourceFactory::setNewDestination(const QUrl &newDestination) 0772 { 0773 m_newDest = newDestination; 0774 if (m_newDest.isValid() && (m_newDest != m_dest)) { 0775 // No files created yet, simply change the urls 0776 if (!m_downloadInitialized) { 0777 m_dest = m_newDest; 0778 if (m_verifier) { 0779 verifier()->setDestination(m_dest); 0780 } 0781 if (m_signature) { 0782 signature()->setDestination(m_dest); 0783 } 0784 0785 return true; 0786 } else if (QFile::exists(m_dest.toString())) { 0787 // create all dirs needed 0788 QDir dir; 0789 dir.mkpath(m_newDest.adjusted(QUrl::RemoveFilename).toString()); 0790 0791 m_statusBeforeMove = m_status; 0792 stop(); 0793 changeStatus(Job::Moving); 0794 m_movingFile = true; 0795 0796 // still a write in progress 0797 if (m_blocked) { 0798 QTimer::singleShot(1000, this, &DataSourceFactory::startMove); 0799 } else { 0800 startMove(); 0801 } 0802 return true; 0803 } 0804 } 0805 return false; 0806 } 0807 0808 void DataSourceFactory::startMove() 0809 { 0810 killPutJob(); 0811 0812 KIO::Job *move = KIO::file_move(m_dest, m_newDest, -1, KIO::HideProgressInfo); 0813 connect(move, &KJob::result, this, &DataSourceFactory::newDestResult); 0814 connect(move, SIGNAL(percent(KJob *, ulong)), this, SLOT(slotPercent(KJob *, ulong))); 0815 0816 m_dest = m_newDest; 0817 verifier()->setDestination(m_dest); 0818 signature()->setDestination(m_dest); 0819 } 0820 0821 void DataSourceFactory::newDestResult(KJob *job) 0822 { 0823 Q_UNUSED(job) // TODO handle errors etc.? 0824 0825 m_movingFile = false; 0826 changeStatus(m_statusBeforeMove); 0827 if (m_status == Job::Running) { 0828 start(); 0829 } 0830 } 0831 0832 void DataSourceFactory::repair() 0833 { 0834 if (verifier()->status() != Verifier::NotVerified) { 0835 return; 0836 } 0837 0838 m_finished = false; 0839 0840 connect(verifier(), SIGNAL(brokenPieces(QList<KIO::fileoffset_t>, KIO::filesize_t)), this, SLOT(slotRepair(QList<KIO::fileoffset_t>, KIO::filesize_t))); 0841 0842 verifier()->brokenPieces(); 0843 } 0844 0845 void DataSourceFactory::slotRepair(const QList<KIO::fileoffset_t> &offsets, KIO::filesize_t length) 0846 { 0847 disconnect(verifier(), SIGNAL(brokenPieces(QList<KIO::fileoffset_t>, KIO::filesize_t)), this, SLOT(slotRepair(QList<KIO::fileoffset_t>, KIO::filesize_t))); 0848 0849 if (!m_startedChunks || !m_finishedChunks) { 0850 qCDebug(KGET_DEBUG) << "Redownload everything"; 0851 m_downloadedSize = 0; 0852 } else { 0853 if (offsets.isEmpty()) { 0854 m_startedChunks->clear(); 0855 m_finishedChunks->clear(); 0856 } 0857 qCDebug(KGET_DEBUG) << "Redownload broken pieces"; 0858 for (int i = 0; i < offsets.count(); ++i) { 0859 const int start = offsets[i] / m_segSize; 0860 const int end = std::ceil(length / static_cast<double>(m_segSize)) - 1 + start; 0861 m_startedChunks->setRange(start, end, false); 0862 m_finishedChunks->setRange(start, end, false); 0863 } 0864 0865 m_downloadedSize = m_segSize * m_finishedChunks->numOnBits(); 0866 } 0867 m_prevDownloadedSizes.clear(); 0868 m_prevDownloadedSizes.append(m_downloadedSize); 0869 0870 // remove all current mirrors and readd the first unused mirror 0871 const QList<QUrl> mirrors = 0872 m_sources.keys(); // FIXME only remove the mirrors of the broken segments?! --> for that m_assignedChunks would need to be saved was well 0873 foreach (const QUrl &mirror, mirrors) { 0874 removeMirror(mirror); 0875 } 0876 addMirror(m_unusedUrls.takeFirst(), true, m_unusedConnections.takeFirst()); 0877 0878 m_speed = 0; 0879 0880 Transfer::ChangesFlags change = (Transfer::Tc_DownloadSpeed | Transfer::Tc_DownloadedSize); 0881 if (m_size) { 0882 change |= Transfer::Tc_Percent; 0883 m_percent = (m_downloadedSize * 100 / m_size); 0884 } 0885 Q_EMIT dataSourceFactoryChange(change); 0886 m_status = Job::Stopped; 0887 0888 start(); 0889 } 0890 0891 void DataSourceFactory::load(const QDomElement *element) 0892 { 0893 if (!element) { 0894 return; 0895 } 0896 0897 QDomElement e = element->firstChildElement("factory"); 0898 if (e.isNull()) { 0899 e = element->firstChildElement("factories").firstChildElement("factory"); 0900 } 0901 0902 // only try to load values if they haven't been set 0903 if (m_dest.isEmpty()) { 0904 m_dest = QUrl(e.attribute("dest")); 0905 } 0906 0907 verifier()->load(e); 0908 signature()->load(e); 0909 0910 Transfer::ChangesFlags change = Transfer::Tc_None; 0911 0912 if (!m_size) { 0913 m_size = e.attribute("size").toULongLong(); 0914 change |= Transfer::Tc_TotalSize; 0915 } 0916 KIO::fileoffset_t tempSegSize = e.attribute("segmentSize").toLongLong(); 0917 if (tempSegSize) { 0918 m_segSize = tempSegSize; 0919 } 0920 if (!m_downloadedSize) { 0921 m_downloadedSize = e.attribute("processedSize").toULongLong(); 0922 change |= Transfer::Tc_DownloadedSize; 0923 if (m_size) { 0924 m_percent = (m_downloadedSize * 100 / m_size); 0925 change |= Transfer::Tc_Percent; 0926 } 0927 } 0928 if (e.hasAttribute("doDownload")) { 0929 m_doDownload = QVariant(e.attribute("doDownload")).toBool(); 0930 } 0931 if (e.hasAttribute("downloadInitialized")) { 0932 m_downloadInitialized = QVariant(e.attribute("downloadInitialized")).toBool(); 0933 } 0934 if (e.hasAttribute("maxMirrorsUsed")) { 0935 bool worked; 0936 m_maxMirrorsUsed = e.attribute("maxMirrorsUsed").toInt(&worked); 0937 m_maxMirrorsUsed = (worked ? m_maxMirrorsUsed : 3); 0938 } 0939 m_sizeInitiallyDefined = QVariant(e.attribute("sizeInitiallyDefined", "false")).toBool(); 0940 m_sizeFoundOnFinish = QVariant(e.attribute("sizeFoundOnFinish", "false")).toBool(); 0941 0942 // load the finishedChunks 0943 const QDomElement chunks = e.firstChildElement("chunks"); 0944 const QDomNodeList chunkList = chunks.elementsByTagName("chunk"); 0945 0946 const quint32 numBits = chunks.attribute("numBits").toInt(); 0947 const int numBytes = chunks.attribute("numBytes").toInt(); 0948 QVarLengthArray<quint8> data(numBytes); 0949 0950 if (numBytes && (numBytes == chunkList.length())) { 0951 for (int i = 0; i < numBytes; ++i) { 0952 const quint8 value = chunkList.at(i).toElement().text().toInt(); 0953 data[i] = value; 0954 } 0955 0956 if (!m_finishedChunks) { 0957 m_finishedChunks = new BitSet(data.data(), numBits); 0958 qCDebug(KGET_DEBUG) << m_finishedChunks->numOnBits() << " bits on of " << numBits << " bits."; 0959 } 0960 0961 // set the finished chunks to started 0962 if (!m_startedChunks) { 0963 m_startedChunks = new BitSet(data.data(), numBits); 0964 } 0965 } 0966 m_prevDownloadedSizes.clear(); 0967 m_prevDownloadedSizes.append(m_downloadedSize); 0968 0969 init(); 0970 0971 // add the used urls 0972 const QDomNodeList urls = e.firstChildElement("urls").elementsByTagName("url"); 0973 for (int i = 0; i < urls.count(); ++i) { 0974 const QDomElement urlElement = urls.at(i).toElement(); 0975 const QUrl url = QUrl(urlElement.text()); 0976 const int connections = urlElement.attribute("numParallelSegments").toInt(); 0977 addMirror(url, true, connections, true); 0978 } 0979 0980 // add the unused urls 0981 const QDomNodeList unusedUrls = e.firstChildElement("unusedUrls").elementsByTagName("url"); 0982 for (int i = 0; i < unusedUrls.count(); ++i) { 0983 const QDomElement urlElement = unusedUrls.at(i).toElement(); 0984 const QUrl url = QUrl(urlElement.text()); 0985 const int connections = urlElement.attribute("numParallelSegments").toInt(); 0986 addMirror(url, false, connections, true); 0987 } 0988 0989 // m_status = static_cast<Job::Status>(e.attribute("status").toInt()); 0990 0991 if (change != Transfer::Tc_None) { 0992 Q_EMIT dataSourceFactoryChange(change); 0993 } 0994 } 0995 0996 void DataSourceFactory::changeStatus(Job::Status status) 0997 { 0998 Transfer::ChangesFlags change = Transfer::Tc_Status; 0999 m_status = status; 1000 1001 switch (m_status) { 1002 case Job::Aborted: 1003 case Job::Moving: 1004 case Job::Stopped: 1005 m_speed = 0; 1006 change |= Transfer::Tc_DownloadSpeed; 1007 break; 1008 case Job::Running: 1009 break; 1010 case Job::Finished: 1011 m_speed = 0; 1012 m_percent = 100; 1013 1014 if (m_size) { 1015 m_downloadedSize = m_size; 1016 change |= Transfer::Tc_DownloadedSize; 1017 } else if (m_downloadedSize) { 1018 m_sizeFoundOnFinish = true; 1019 m_size = m_downloadedSize; 1020 change |= Transfer::Tc_TotalSize; 1021 } 1022 1023 change |= Transfer::Tc_DownloadSpeed | Transfer::Tc_Percent; 1024 1025 if (Settings::checksumAutomaticVerification() && verifier()->isVerifyable()) { 1026 verifier()->verify(); 1027 } 1028 if (Settings::signatureAutomaticVerification() && signature()->isVerifyable()) { 1029 signature()->verify(); 1030 } 1031 1032 slotUpdateCapabilities(); 1033 break; 1034 default: 1035 // TODO handle Delayed 1036 break; 1037 } 1038 1039 Q_EMIT dataSourceFactoryChange(change); 1040 } 1041 1042 void DataSourceFactory::save(const QDomElement &element) 1043 { 1044 if (element.isNull()) { 1045 return; 1046 } 1047 1048 QDomElement e = element; 1049 QDomDocument doc(e.ownerDocument()); 1050 1051 QDomElement factories = e.firstChildElement("factories"); 1052 if (factories.isNull()) { 1053 factories = doc.createElement("factories"); 1054 } 1055 1056 QDomElement factory = doc.createElement("factory"); 1057 factory.setAttribute("dest", m_dest.url()); 1058 1059 if (!m_finishedChunks || m_sizeFoundOnFinish) { 1060 factory.setAttribute("processedSize", m_downloadedSize); 1061 } 1062 factory.setAttribute("size", m_size); 1063 factory.setAttribute("segmentSize", m_segSize); 1064 factory.setAttribute("status", m_status); 1065 factory.setAttribute("doDownload", m_doDownload); 1066 factory.setAttribute("downloadInitialized", m_downloadInitialized); 1067 factory.setAttribute("maxMirrorsUsed", m_maxMirrorsUsed); 1068 factory.setAttribute("sizeInitiallyDefined", m_sizeInitiallyDefined); 1069 factory.setAttribute("sizeFoundOnFinish", m_sizeFoundOnFinish); 1070 1071 verifier()->save(factory); 1072 signature()->save(factory); 1073 1074 // set the finished chunks, but only if chunks were actually used 1075 if (m_finishedChunks && !m_sizeFoundOnFinish) { 1076 const KIO::fileoffset_t rest = m_size % m_segSize; 1077 // the lastSegsize is rest, but only if there is a rest and it is the last segment of the download 1078 const KIO::fileoffset_t lastSegSize = (rest ? rest : m_segSize); 1079 1080 // not m_downloadedSize is stored, but the bytes that do not have to be redownloaded 1081 const bool lastOn = m_finishedChunks->get(m_finishedChunks->getNumBits() - 1); 1082 factory.setAttribute("processedSize", m_segSize * (m_finishedChunks->numOnBits() - lastOn) + lastOn * lastSegSize); 1083 1084 QDomElement chunks = doc.createElement("chunks"); 1085 chunks.setAttribute("numBits", m_finishedChunks->getNumBits()); 1086 chunks.setAttribute("numBytes", m_finishedChunks->getNumBytes()); 1087 1088 const quint8 *data = m_finishedChunks->getData(); 1089 for (quint32 i = 0; i < m_finishedChunks->getNumBytes(); ++i) { 1090 QDomElement chunk = doc.createElement("chunk"); 1091 QDomText num = doc.createTextNode(QString::number(data[i])); 1092 chunk.setAttribute("number", i); 1093 chunk.appendChild(num); 1094 chunks.appendChild(chunk); 1095 } 1096 factory.appendChild(chunks); 1097 } 1098 1099 // set the used urls 1100 QDomElement urls = doc.createElement("urls"); 1101 QHash<QUrl, TransferDataSource *>::const_iterator it; 1102 QHash<QUrl, TransferDataSource *>::const_iterator itEnd = m_sources.constEnd(); 1103 for (it = m_sources.constBegin(); it != itEnd; ++it) { 1104 QDomElement url = doc.createElement("url"); 1105 const QDomText text = doc.createTextNode(it.key().url()); 1106 url.appendChild(text); 1107 url.setAttribute("numParallelSegments", (*it)->parallelSegments()); 1108 urls.appendChild(url); 1109 factory.appendChild(urls); 1110 } 1111 1112 // set the unused urls 1113 urls = doc.createElement("unusedUrls"); 1114 for (int i = 0; i < m_unusedUrls.count(); ++i) { 1115 QDomElement url = doc.createElement("url"); 1116 const QDomText text = doc.createTextNode(m_unusedUrls.at(i).url()); 1117 url.appendChild(text); 1118 url.setAttribute("numParallelSegments", m_unusedConnections.at(i)); 1119 urls.appendChild(url); 1120 factory.appendChild(urls); 1121 } 1122 1123 factories.appendChild(factory); 1124 e.appendChild(factories); 1125 } 1126 1127 bool DataSourceFactory::isValid() const 1128 { 1129 // FIXME 1130 return true; 1131 bool valid = (m_size && m_segSize && m_dest.isValid() && !m_sources.isEmpty()); 1132 1133 // if the download is finished the it is valid no matter what is set or not 1134 if (m_status == Job::Finished) { 1135 valid = true; 1136 } 1137 qDebug() << m_size << m_segSize << m_dest.isValid() << !m_sources.isEmpty(); 1138 return valid; 1139 } 1140 1141 Verifier *DataSourceFactory::verifier() 1142 { 1143 if (!m_verifier) { 1144 m_verifier = new Verifier(m_dest, this); 1145 } 1146 1147 return m_verifier; 1148 } 1149 1150 Signature *DataSourceFactory::signature() 1151 { 1152 if (!m_signature) { 1153 m_signature = new Signature(m_dest, this); 1154 } 1155 1156 return m_signature; 1157 } 1158 1159 void DataSourceFactory::slotUpdateCapabilities() 1160 { 1161 const Transfer::Capabilities oldCaps = capabilities(); 1162 Transfer::Capabilities newCaps = {}; 1163 1164 if ((status() == Job::Finished) || (status() == Job::Stopped)) { 1165 newCaps |= Transfer::Cap_Moving | Transfer::Cap_Renaming; 1166 } else { 1167 foreach (TransferDataSource *source, m_sources) { 1168 if (!source->assignedSegments().isEmpty()) { 1169 if (newCaps) { 1170 newCaps &= source->capabilities(); 1171 } else { 1172 newCaps = source->capabilities(); 1173 } 1174 } 1175 } 1176 } 1177 1178 if (newCaps & Transfer::Cap_Resuming) { 1179 newCaps |= Transfer::Cap_Moving | Transfer::Cap_Renaming; 1180 } 1181 1182 newCaps |= Transfer::Cap_MultipleMirrors; 1183 1184 if (oldCaps != newCaps) { 1185 m_capabilities = newCaps; 1186 Q_EMIT capabilitiesChanged(); 1187 } 1188 } 1189 1190 #include "moc_datasourcefactory.cpp"