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"