File indexing completed on 2024-04-28 04:57:32

0001 /* This file is part of the KDE project
0002 
0003    Copyright (C) 2004 Dario Massarin <nekkar@libero.it>
0004    Copyright (C) 2007 Manolo Valdes <nolis71cu@gmail.com>
0005    Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>
0006 
0007    This program is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU General Public
0009    License as published by the Free Software Foundation; either
0010    version 2 of the License, or (at your option) any later version.
0011 */
0012 
0013 #include "metalink.h"
0014 #include "fileselectiondlg.h"
0015 #include "metalinksettings.h"
0016 
0017 #include "core/download.h"
0018 #include "core/filemodel.h"
0019 #include "core/kget.h"
0020 #include "core/signature.h"
0021 #include "core/transferdatasource.h"
0022 #include "core/transfergroup.h"
0023 #include "core/urlchecker.h"
0024 #include "core/verifier.h"
0025 
0026 #include <algorithm>
0027 
0028 #include <KConfigGroup>
0029 #include <KIO/DeleteJob>
0030 #include <KIO/RenameDialog>
0031 #include <KLocalizedString>
0032 #include <KMessageBox>
0033 #include <kwidgetsaddons_version.h>
0034 
0035 #include <QDebug>
0036 #include <QDialog>
0037 #include <QDomElement>
0038 #include <QFile>
0039 #include <QStandardPaths>
0040 
0041 Metalink::Metalink(TransferGroup *parent, TransferFactory *factory, Scheduler *scheduler, const QUrl &source, const QUrl &dest, const QDomElement *e)
0042     : Transfer(parent, factory, scheduler, source, dest, e)
0043     , m_fileModel(nullptr)
0044     , m_currentFiles(0)
0045     , m_metalinkJustDownloaded(false)
0046     , m_ready(false)
0047     , m_speedCount(0)
0048     , m_tempAverageSpeed(0)
0049     , m_averageSpeed(0)
0050 {
0051 }
0052 
0053 Metalink::~Metalink()
0054 {
0055 }
0056 
0057 void Metalink::start()
0058 {
0059     qCDebug(KGET_DEBUG) << "metalink::start";
0060 
0061     if (!m_ready) {
0062         if (m_localMetalinkLocation.isValid() && metalinkInit()) {
0063             startMetalink();
0064         } else {
0065             downloadMetalink();
0066         }
0067     } else {
0068         startMetalink();
0069     }
0070 }
0071 
0072 void Metalink::downloadMetalink()
0073 {
0074     m_metalinkJustDownloaded = true;
0075 
0076     setStatus(Job::Stopped, i18n("Downloading Metalink File...."), "document-save");
0077     setTransferChange(Tc_Status, true);
0078     // make sure that the DataLocation directory exists (earlier this used to be handled by KStandardDirs)
0079     if (!QFileInfo::exists(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) {
0080         QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
0081     }
0082     Download *download =
0083         new Download(m_source, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/metalinks/") + m_source.fileName());
0084     connect(download, SIGNAL(finishedSuccessfully(QUrl, QByteArray)), SLOT(metalinkInit(QUrl, QByteArray)));
0085 }
0086 
0087 bool Metalink::metalinkInit(const QUrl &src, const QByteArray &data)
0088 {
0089     qCDebug(KGET_DEBUG);
0090 
0091     if (!src.isEmpty()) {
0092         m_localMetalinkLocation = src;
0093     }
0094 
0095     // use the downloaded metalink-file data directly if possible
0096     if (!data.isEmpty()) {
0097         KGetMetalink::HandleMetalink::load(data, &m_metalink);
0098     }
0099 
0100     // try to parse the locally stored metalink-file
0101     if (!m_metalink.isValid() && m_localMetalinkLocation.isValid()) {
0102         KGetMetalink::HandleMetalink::load(m_localMetalinkLocation.toLocalFile(), &m_metalink);
0103     }
0104 
0105     if (!m_metalink.isValid()) {
0106         qCCritical(KGET_DEBUG) << "Unknown error when trying to load the .metalink-file. Metalink is not valid.";
0107         setStatus(Job::Aborted);
0108         setTransferChange(Tc_Status, true);
0109         return false;
0110     }
0111 
0112     // offers a dialog to download the newest version of a dynamic metalink
0113     if ((m_source.isLocalFile() || !m_metalinkJustDownloaded) && m_metalink.dynamic && (UrlChecker::checkSource(m_metalink.origin) == UrlChecker::NoError)) {
0114         if (KMessageBox::questionTwoActions(nullptr,
0115                                             i18n("A newer version of this Metalink might exist, do you want to download it?"),
0116                                             i18n("Redownload Metalink"),
0117                                             KGuiItem(i18nc("@action:button", "Download Again"), QStringLiteral("view-refresh")),
0118                                             KGuiItem(i18nc("@action:button", "Ignore"), QStringLiteral("dialog-cancel")))
0119             == KMessageBox::PrimaryAction) {
0120             m_localMetalinkLocation.clear();
0121             m_source = m_metalink.origin;
0122             downloadMetalink();
0123             return false;
0124         }
0125     }
0126 
0127     QList<KGetMetalink::File>::const_iterator it;
0128     QList<KGetMetalink::File>::const_iterator itEnd = m_metalink.files.files.constEnd();
0129     m_totalSize = 0;
0130     KIO::fileoffset_t segSize = 500 * 1024; // TODO use config here!
0131     const QUrl tempDest = QUrl(m_dest.directory());
0132     QUrl dest;
0133     for (it = m_metalink.files.files.constBegin(); it != itEnd; ++it) {
0134         dest = tempDest;
0135         dest.addPath((*it).name);
0136 
0137         QList<KGetMetalink::Url> urlList = (*it).resources.urls;
0138         // sort the urls according to their priority (highest first)
0139         std::sort(urlList.begin(), urlList.end(), [](const KGetMetalink::Url &a, const KGetMetalink::Url &b) {
0140             return b < a;
0141         });
0142 
0143         KIO::filesize_t fileSize = (*it).size;
0144         m_totalSize += fileSize;
0145 
0146         // create a DataSourceFactory for each separate file
0147         DataSourceFactory *dataFactory = new DataSourceFactory(this, dest, fileSize, segSize);
0148         dataFactory->setMaxMirrorsUsed(MetalinkSettings::mirrorsPerFile());
0149 
0150         // TODO compare available file size (<size>) with the sizes of the server while downloading?
0151 
0152         connect(dataFactory, SIGNAL(capabilitiesChanged()), this, SLOT(slotUpdateCapabilities()));
0153         connect(dataFactory, SIGNAL(dataSourceFactoryChange(Transfer::ChangesFlags)), this, SLOT(slotDataSourceFactoryChange(Transfer::ChangesFlags)));
0154         connect(dataFactory->verifier(), SIGNAL(verified(bool)), this, SLOT(slotVerified(bool)));
0155         connect(dataFactory->signature(), SIGNAL(verified(int)), this, SLOT(slotSignatureVerified()));
0156         connect(dataFactory, SIGNAL(log(QString, Transfer::LogLevel)), this, SLOT(setLog(QString, Transfer::LogLevel)));
0157 
0158         // add the DataSources
0159         for (int i = 0; i < urlList.size(); ++i) {
0160             const QUrl url = urlList[i].url;
0161             if (url.isValid()) {
0162                 dataFactory->addMirror(url, MetalinkSettings::connectionsPerUrl());
0163             }
0164         }
0165         // no datasource has been created, so remove the datasource factory
0166         if (dataFactory->mirrors().isEmpty()) {
0167             delete dataFactory;
0168         } else {
0169             dataFactory->verifier()->addChecksums((*it).verification.hashes);
0170             foreach (const KGetMetalink::Pieces &pieces, (*it).verification.pieces) {
0171                 dataFactory->verifier()->addPartialChecksums(pieces.type, pieces.length, pieces.hashes);
0172             }
0173 
0174             const QHash<QString, QString> signatures = (*it).verification.signatures;
0175             QHash<QString, QString>::const_iterator it;
0176             QHash<QString, QString>::const_iterator itEnd = signatures.constEnd();
0177             for (it = signatures.constBegin(); it != itEnd; ++it) {
0178                 if (it.key().toLower() == "pgp") {
0179                     dataFactory->signature()->setAsciiDetachedSignature(*it);
0180                 }
0181             }
0182 
0183             m_dataSourceFactory[dataFactory->dest()] = dataFactory;
0184         }
0185     }
0186 
0187     if ((m_metalink.files.files.size() == 1) && m_dataSourceFactory.size()) {
0188         m_dest = dest;
0189     }
0190 
0191     if (!m_dataSourceFactory.size()) {
0192         // TODO make this via log in the future + do not display the KMessageBox
0193         qCWarning(KGET_DEBUG) << "Download of" << m_source << "failed, no working URLs were found.";
0194         KMessageBox::error(nullptr, i18n("Download failed, no working URLs were found."), i18n("Error"));
0195         setStatus(Job::Aborted);
0196         setTransferChange(Tc_Status, true);
0197         return false;
0198     }
0199 
0200     m_ready = !m_dataSourceFactory.isEmpty();
0201     slotUpdateCapabilities();
0202 
0203     // the metalink-file has just been downloaded, so ask the user to choose the files that
0204     //  should be downloaded
0205     if (m_metalinkJustDownloaded) {
0206         QDialog *dialog = new FileSelectionDlg(fileModel());
0207         dialog->setAttribute(Qt::WA_DeleteOnClose);
0208         connect(dialog, SIGNAL(finished(int)), this, SLOT(fileDlgFinished(int)));
0209 
0210         dialog->show();
0211     }
0212 
0213     return true;
0214 }
0215 
0216 void Metalink::untickAllFiles()
0217 {
0218     for (int row = 0; row < fileModel()->rowCount(); ++row) {
0219         QModelIndex index = fileModel()->index(row, FileItem::File);
0220         if (index.isValid()) {
0221             fileModel()->setData(index, Qt::Unchecked, Qt::CheckStateRole);
0222         }
0223     }
0224 }
0225 
0226 void Metalink::fileDlgFinished(int result)
0227 {
0228     // the dialog was not accepted untick every file, this ensures that the user does not
0229     // press start by accident without first selecting the desired files
0230     if (result != QDialog::Accepted) {
0231         untickAllFiles();
0232     }
0233 
0234     filesSelected();
0235 
0236     // no files selected to download or dialog rejected, stop the download
0237     if (!m_numFilesSelected || (result != QDialog::Accepted)) {
0238         setStatus(Job::Stopped);
0239         setTransferChange(Tc_Status, true);
0240         return;
0241     }
0242 
0243     startMetalink();
0244 }
0245 
0246 void Metalink::startMetalink()
0247 {
0248     if (m_ready) {
0249         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0250             // specified number of files is downloaded simultaneously
0251             if (m_currentFiles < MetalinkSettings::simultaneousFiles()) {
0252                 const int status = factory->status();
0253                 // only start factories that should be downloaded
0254                 if (factory->doDownload() && (status != Job::Finished) && (status != Job::FinishedKeepAlive) && (status != Job::Running)) {
0255                     ++m_currentFiles;
0256                     factory->start();
0257                 }
0258             } else {
0259                 break;
0260             }
0261         }
0262     }
0263 }
0264 
0265 void Metalink::deinit(Transfer::DeleteOptions options)
0266 {
0267     foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0268         if (options & Transfer::DeleteFiles) {
0269             factory->deinit();
0270         }
0271     } // TODO: Ask the user if he/she wants to delete the *.part-file? To discuss (boom1992)
0272 
0273     // FIXME does that mean, that the metalink file is always removed, even if
0274     // downloaded by the user?
0275     if ((options & Transfer::DeleteTemporaryFiles) && m_localMetalinkLocation.isLocalFile()) {
0276         KIO::Job *del = KIO::del(m_localMetalinkLocation, KIO::HideProgressInfo);
0277         if (!del->exec()) {
0278             qCDebug(KGET_DEBUG) << "Could not delete " << m_localMetalinkLocation.path();
0279         }
0280     }
0281 }
0282 
0283 void Metalink::stop()
0284 {
0285     qCDebug(KGET_DEBUG) << "metalink::Stop";
0286     if (m_ready && status() != Stopped) {
0287         m_currentFiles = 0;
0288         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0289             factory->stop();
0290         }
0291     }
0292 }
0293 
0294 void Metalink::slotDataSourceFactoryChange(Transfer::ChangesFlags change)
0295 {
0296     if ((change & Tc_Status) | (change & Tc_TotalSize)) {
0297         DataSourceFactory *factory = qobject_cast<DataSourceFactory *>(sender());
0298         if (change & Tc_Status) {
0299             bool changeStatus;
0300             updateStatus(factory, &changeStatus);
0301             if (!changeStatus) {
0302                 change &= ~Tc_Status;
0303             }
0304         }
0305         if (change & Tc_TotalSize) {
0306             recalculateTotalSize(factory);
0307         }
0308     }
0309     if (change & Tc_DownloadedSize) {
0310         recalculateProcessedSize();
0311         change |= Tc_Percent;
0312     }
0313     if (change & Tc_DownloadSpeed) {
0314         recalculateSpeed();
0315     }
0316 
0317     setTransferChange(change, true);
0318 }
0319 
0320 void Metalink::recalculateTotalSize(DataSourceFactory *sender)
0321 {
0322     m_totalSize = 0;
0323     foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0324         if (factory->doDownload()) {
0325             m_totalSize += factory->size();
0326         }
0327     }
0328 
0329     if (m_fileModel) {
0330         if (sender) {
0331             QModelIndex sizeIndex = m_fileModel->index(sender->dest(), FileItem::Size);
0332             m_fileModel->setData(sizeIndex, static_cast<qlonglong>(sender->size()));
0333         }
0334     }
0335 }
0336 
0337 void Metalink::recalculateProcessedSize()
0338 {
0339     m_downloadedSize = 0;
0340     foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0341         if (factory->doDownload()) {
0342             m_downloadedSize += factory->downloadedSize();
0343         }
0344     }
0345 
0346     if (m_totalSize) {
0347         m_percent = (m_downloadedSize * 100) / m_totalSize;
0348     } else {
0349         m_percent = 0;
0350     }
0351 }
0352 
0353 void Metalink::recalculateSpeed()
0354 {
0355     m_downloadSpeed = 0;
0356     foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0357         if (factory->doDownload()) {
0358             m_downloadSpeed += factory->currentSpeed();
0359         }
0360     }
0361 
0362     // calculate the average of the last three speeds
0363     m_tempAverageSpeed += m_downloadSpeed;
0364     ++m_speedCount;
0365     if (m_speedCount == 3) {
0366         m_averageSpeed = m_tempAverageSpeed / 3;
0367         m_speedCount = 0;
0368         m_tempAverageSpeed = 0;
0369     }
0370 }
0371 
0372 int Metalink::remainingTime() const
0373 {
0374     if (!m_averageSpeed) {
0375         m_averageSpeed = m_downloadSpeed;
0376     }
0377     return KIO::calculateRemainingSeconds(m_totalSize, m_downloadedSize, m_averageSpeed);
0378 }
0379 
0380 void Metalink::updateStatus(DataSourceFactory *sender, bool *changeStatus)
0381 {
0382     Job::Status status = (sender ? sender->status() : Job::Stopped);
0383     *changeStatus = true;
0384     switch (status) {
0385     case Job::Aborted:
0386     case Job::Stopped: {
0387         m_currentFiles = 0;
0388         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0389             // one factory is still running, do not change the status
0390             if (factory->doDownload() && (factory->status() == Job::Running)) {
0391                 *changeStatus = false;
0392                 ++m_currentFiles;
0393             }
0394         }
0395 
0396         if (*changeStatus) {
0397             setStatus(status);
0398         }
0399         break;
0400     }
0401     case Job::Finished:
0402         // one file that has been downloaded now is finished//FIXME ignore downloads that were finished in the previous download!!!!
0403         if (m_currentFiles) {
0404             --m_currentFiles;
0405             startMetalink();
0406         }
0407         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0408             // one factory is not finished, do not change the status
0409             if (factory->doDownload() && (factory->status() != Job::Finished)) {
0410                 *changeStatus = false;
0411                 break;
0412             }
0413         }
0414 
0415         if (*changeStatus) {
0416             setStatus(Job::Finished);
0417         }
0418         break;
0419 
0420     default:
0421         setStatus(status);
0422         break;
0423     }
0424 
0425     if (m_fileModel) {
0426         if (sender) {
0427             QModelIndex statusIndex = m_fileModel->index(sender->dest(), FileItem::Status);
0428             m_fileModel->setData(statusIndex, status);
0429         }
0430     }
0431 }
0432 
0433 void Metalink::slotVerified(bool isVerified)
0434 {
0435     Q_UNUSED(isVerified)
0436 
0437     if (status() == Job::Finished) {
0438         // see if some files are NotVerified
0439         QStringList brokenFiles;
0440         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0441             if (m_fileModel) {
0442                 QModelIndex checksumVerified = m_fileModel->index(factory->dest(), FileItem::ChecksumVerified);
0443                 m_fileModel->setData(checksumVerified, factory->verifier()->status());
0444             }
0445             if (factory->doDownload() && (factory->verifier()->status() == Verifier::NotVerified)) {
0446                 brokenFiles.append(factory->dest().pathOrUrl());
0447             }
0448         }
0449 
0450         if (brokenFiles.count()) {
0451             if (KMessageBox::warningTwoActionsList(
0452                     nullptr,
0453                     i18n("The download could not be verified, do you want to repair (if repairing does not work the download would be restarted) it?"),
0454                     brokenFiles,
0455                     QString(),
0456                     KGuiItem(i18nc("@action:button", "Repair")),
0457                     KGuiItem(i18nc("@action:button", "Ignore"), QStringLiteral("dialog-cancel")))
0458                 == KMessageBox::PrimaryAction) {
0459                 if (repair()) {
0460                     return;
0461                 }
0462             }
0463         }
0464     }
0465 }
0466 
0467 void Metalink::slotSignatureVerified()
0468 {
0469     if (status() == Job::Finished) {
0470         // see if some files are NotVerified
0471         QStringList brokenFiles;
0472         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0473             if (m_fileModel) {
0474                 QModelIndex signatureVerified = m_fileModel->index(factory->dest(), FileItem::SignatureVerified);
0475                 m_fileModel->setData(signatureVerified, factory->signature()->status());
0476             }
0477             if (factory->doDownload() && (factory->verifier()->status() == Verifier::NotVerified)) {
0478                 brokenFiles.append(factory->dest().pathOrUrl());
0479             }
0480         }
0481         /*
0482                 if (brokenFiles.count())//TODO
0483                 {
0484                     if (KMessageBox::warningYesNoCancelList(nullptr,
0485                         i18n("The download could not be verified, try to repair it?"),
0486                              brokenFiles) == KMessageBox::Yes)
0487                     {
0488                         if (repair())
0489                         {
0490                             return;
0491                         }
0492                     }
0493                 }*/
0494     }
0495 }
0496 
0497 bool Metalink::repair(const QUrl &file)
0498 {
0499     if (file.isValid()) {
0500         if (m_dataSourceFactory.contains(file)) {
0501             DataSourceFactory *broken = m_dataSourceFactory[file];
0502             if (broken->verifier()->status() == Verifier::NotVerified) {
0503                 broken->repair();
0504                 return true;
0505             }
0506         }
0507     } else {
0508         QList<DataSourceFactory *> broken;
0509         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0510             if (factory->doDownload() && (factory->verifier()->status() == Verifier::NotVerified)) {
0511                 broken.append(factory);
0512             }
0513         }
0514         if (broken.count()) {
0515             foreach (DataSourceFactory *factory, broken) {
0516                 factory->repair();
0517             }
0518             return true;
0519         }
0520     }
0521 
0522     return false;
0523 }
0524 
0525 void Metalink::load(const QDomElement *element)
0526 {
0527     Transfer::load(element);
0528 
0529     if (!element) {
0530         return;
0531     }
0532 
0533     const QDomElement e = *element;
0534     m_localMetalinkLocation = QUrl(e.attribute("LocalMetalinkLocation"));
0535     QDomNodeList factories = e.firstChildElement("factories").elementsByTagName("factory");
0536 
0537     // no stored information found, stop right here
0538     if (!factories.count()) {
0539         return;
0540     }
0541 
0542     while (factories.count()) {
0543         QDomDocument doc;
0544         QDomElement factory = doc.createElement("factories");
0545         factory.appendChild(factories.item(0).toElement());
0546         doc.appendChild(factory);
0547 
0548         DataSourceFactory *file = new DataSourceFactory(this);
0549         file->load(&factory);
0550         connect(file, SIGNAL(capabilitiesChanged()), this, SLOT(slotUpdateCapabilities()));
0551         connect(file, SIGNAL(dataSourceFactoryChange(Transfer::ChangesFlags)), this, SLOT(slotDataSourceFactoryChange(Transfer::ChangesFlags)));
0552         m_dataSourceFactory[file->dest()] = file;
0553         connect(file->verifier(), SIGNAL(verified(bool)), this, SLOT(slotVerified(bool)));
0554         connect(file->signature(), SIGNAL(verified(int)), this, SLOT(slotSignatureVerified()));
0555         connect(file, SIGNAL(log(QString, Transfer::LogLevel)), this, SLOT(setLog(QString, Transfer::LogLevel)));
0556 
0557         // start the DataSourceFactories that were Started when KGet was closed
0558         if (file->status() == Job::Running) {
0559             if (m_currentFiles < MetalinkSettings::simultaneousFiles()) {
0560                 ++m_currentFiles;
0561                 file->start();
0562             } else {
0563                 // enough simultaneous files already, so increase the number and set file to stop --> that will decrease the number again
0564                 file->stop();
0565             }
0566         }
0567     }
0568     m_ready = !m_dataSourceFactory.isEmpty();
0569     slotUpdateCapabilities();
0570 }
0571 
0572 void Metalink::save(const QDomElement &element)
0573 {
0574     Transfer::save(element);
0575 
0576     QDomElement e = element;
0577     e.setAttribute("LocalMetalinkLocation", m_localMetalinkLocation.url());
0578 
0579     foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0580         factory->save(e);
0581     }
0582 }
0583 
0584 Verifier *Metalink::verifier(const QUrl &file)
0585 {
0586     if (!m_dataSourceFactory.contains(file)) {
0587         return nullptr;
0588     }
0589 
0590     return m_dataSourceFactory[file]->verifier();
0591 }
0592 
0593 Signature *Metalink::signature(const QUrl &file)
0594 {
0595     if (!m_dataSourceFactory.contains(file)) {
0596         return nullptr;
0597     }
0598 
0599     return m_dataSourceFactory[file]->signature();
0600 }
0601 
0602 QList<QUrl> Metalink::files() const
0603 {
0604     return m_dataSourceFactory.keys();
0605 }
0606 
0607 FileModel *Metalink::fileModel()
0608 {
0609     if (!m_fileModel) {
0610         m_fileModel = new FileModel(files(), directory(), this);
0611         connect(m_fileModel, SIGNAL(rename(QUrl, QUrl)), this, SLOT(slotRename(QUrl, QUrl)));
0612         connect(m_fileModel, SIGNAL(checkStateChanged()), this, SLOT(filesSelected()));
0613 
0614         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0615             const QUrl dest = factory->dest();
0616             QModelIndex size = m_fileModel->index(dest, FileItem::Size);
0617             m_fileModel->setData(size, static_cast<qlonglong>(factory->size()));
0618             QModelIndex status = m_fileModel->index(dest, FileItem::Status);
0619             m_fileModel->setData(status, factory->status());
0620             QModelIndex checksumVerified = m_fileModel->index(dest, FileItem::ChecksumVerified);
0621             m_fileModel->setData(checksumVerified, factory->verifier()->status());
0622             QModelIndex signatureVerified = m_fileModel->index(dest, FileItem::SignatureVerified);
0623             m_fileModel->setData(signatureVerified, factory->signature()->status());
0624             if (!factory->doDownload()) {
0625                 QModelIndex index = m_fileModel->index(factory->dest(), FileItem::File);
0626                 m_fileModel->setData(index, Qt::Unchecked, Qt::CheckStateRole);
0627             }
0628         }
0629     }
0630 
0631     return m_fileModel;
0632 }
0633 
0634 void Metalink::filesSelected()
0635 {
0636     bool overwriteAll = false;
0637     bool autoSkip = false;
0638     bool cancel = false;
0639     QModelIndexList files = fileModel()->fileIndexes(FileItem::File);
0640     m_numFilesSelected = 0;
0641 
0642     // sets the CheckState of the fileModel to the according DataSourceFactories
0643     // and asks the user if there are existing files already
0644     foreach (const QModelIndex &index, files) {
0645         const QUrl dest = fileModel()->getUrl(index);
0646         bool doDownload = index.data(Qt::CheckStateRole).toBool();
0647         if (m_dataSourceFactory.contains(dest)) {
0648             DataSourceFactory *factory = m_dataSourceFactory[dest];
0649             // ignore finished transfers
0650             if ((factory->status() == Job::Finished) || (factory->status() == Job::FinishedKeepAlive)) {
0651                 continue;
0652             }
0653 
0654             // check if the file at dest exists already and ask the user what to do in this case, ignore already running transfers
0655             if (doDownload && (factory->status() != Job::Running) && QFile::exists(dest.toLocalFile())) {
0656                 // user has chosen to skip all files that exist already before
0657                 if (autoSkip) {
0658                     fileModel()->setData(index, Qt::Unchecked, Qt::CheckStateRole);
0659                     doDownload = false;
0660                     // ask the user, unless he has chosen overwriteAll before
0661                 } else if (!overwriteAll) {
0662                     KIO::RenameDialog dlg(nullptr,
0663                                           i18n("File already exists"),
0664                                           index.data().toString(),
0665                                           dest,
0666                                           KIO::RenameDialog_Options(KIO::RenameDialog_MultipleItems | KIO::RenameDialog_Overwrite | KIO::RenameDialog_Skip));
0667                     const int result = dlg.exec();
0668 
0669                     if (result == KIO::Result_Rename) {
0670                         // no reason to use FileModel::rename() since the file does not exist yet, so simply skip it
0671                         // avoids having to deal with signals
0672                         const QUrl newDest = dlg.newDestUrl();
0673                         factory->setDoDownload(doDownload);
0674                         factory->setNewDestination(newDest);
0675                         fileModel()->setData(index, newDest.fileName(), FileItem::File);
0676                         ++m_numFilesSelected;
0677 
0678                         m_dataSourceFactory.remove(dest);
0679                         m_dataSourceFactory[newDest] = factory;
0680                         continue;
0681                     } else if (result == KIO::Result_Skip) {
0682                         fileModel()->setData(index, Qt::Unchecked, Qt::CheckStateRole);
0683                         doDownload = false;
0684                     } else if (result == KIO::Result_Cancel) {
0685                         cancel = true;
0686                         break;
0687                     } else if (result == KIO:Result_AutoSkip:) {
0688                         autoSkip = true;
0689                         fileModel()->setData(index, Qt::Unchecked, Qt::CheckStateRole);
0690                         doDownload = false;
0691                     } else if (result == KIO::Result_OverwriteAll) {
0692                         overwriteAll = true;
0693                     }
0694                 }
0695             }
0696 
0697             factory->setDoDownload(doDownload);
0698             if (doDownload && (factory->status() != Finished) && (factory->status() != FinishedKeepAlive)) {
0699                 ++m_numFilesSelected;
0700             }
0701         }
0702     }
0703 
0704     // the user decided to cancel, so untick all files
0705     if (cancel) {
0706         m_numFilesSelected = 0;
0707         untickAllFiles();
0708         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0709             factory->setDoDownload(false);
0710         }
0711     }
0712 
0713     Transfer::ChangesFlags change = (Tc_TotalSize | Tc_DownloadSpeed);
0714     // some files have been selected that are not finished yet, set them to stop if the transfer is not running (checked in slotStatus)
0715     if (m_numFilesSelected) {
0716         change |= Tc_Status;
0717     }
0718     slotDataSourceFactoryChange(change);
0719 }
0720 
0721 void Metalink::slotRename(const QUrl &oldUrl, const QUrl &newUrl)
0722 {
0723     if (!m_dataSourceFactory.contains(oldUrl)) {
0724         return;
0725     }
0726 
0727     m_dataSourceFactory[newUrl] = m_dataSourceFactory[oldUrl];
0728     m_dataSourceFactory.remove(oldUrl);
0729     m_dataSourceFactory[newUrl]->setNewDestination(newUrl);
0730 
0731     setTransferChange(Tc_FileName);
0732 }
0733 
0734 bool Metalink::setDirectory(const QUrl &new_directory)
0735 {
0736     if (new_directory == directory()) {
0737         return false;
0738     }
0739 
0740     if (m_fileModel) {
0741         m_fileModel->setDirectory(new_directory);
0742     }
0743 
0744     const QString oldDirectory = directory().pathOrUrl(QUrl::AddTrailingSlash);
0745     const QString newDirectory = new_directory.pathOrUrl(QUrl::AddTrailingSlash);
0746     const QString fileName = m_dest.fileName();
0747     m_dest = new_directory;
0748     m_dest.addPath(fileName);
0749 
0750     QHash<QUrl, DataSourceFactory *> newStorage;
0751     foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0752         const QUrl oldUrl = factory->dest();
0753         const QUrl newUrl = QUrl(oldUrl.pathOrUrl().replace(oldDirectory, newDirectory));
0754         factory->setNewDestination(newUrl);
0755         newStorage[newUrl] = factory;
0756     }
0757     m_dataSourceFactory = newStorage;
0758 
0759     setTransferChange(Tc_FileName);
0760     return true;
0761 }
0762 
0763 QHash<QUrl, QPair<bool, int>> Metalink::availableMirrors(const QUrl &file) const
0764 {
0765     QHash<QUrl, QPair<bool, int>> urls;
0766 
0767     if (m_dataSourceFactory.contains(file)) {
0768         urls = m_dataSourceFactory[file]->mirrors();
0769     }
0770 
0771     return urls;
0772 }
0773 
0774 void Metalink::setAvailableMirrors(const QUrl &file, const QHash<QUrl, QPair<bool, int>> &mirrors)
0775 {
0776     if (!m_dataSourceFactory.contains(file)) {
0777         return;
0778     }
0779 
0780     m_dataSourceFactory[file]->setMirrors(mirrors);
0781 }
0782 
0783 void Metalink::slotUpdateCapabilities()
0784 {
0785     Capabilities oldCap = capabilities();
0786     Capabilities newCap = 0;
0787     foreach (DataSourceFactory *file, m_dataSourceFactory) {
0788         if (file->doDownload()) { // FIXME when a download did not start yet it should be moveable!!//FIXME why not working, when only two connections?
0789             if (newCap) {
0790                 newCap &= file->capabilities();
0791             } else {
0792                 newCap = file->capabilities();
0793             }
0794         }
0795     }
0796 
0797     if (newCap != oldCap) {
0798         setCapabilities(newCap);
0799     }
0800 }
0801 
0802 #include "moc_metalink.cpp"