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    Copyright (C) 2012 Aish Raj Dahal <dahalaishraj@gmail.com>
0007 
0008    This program is free software; you can redistribute it and/or
0009    modify it under the terms of the GNU General Public
0010    License as published by the Free Software Foundation; either
0011    version 2 of the License, or (at your option) any later version.
0012 */
0013 
0014 #include "metalinkxml.h"
0015 #include "fileselectiondlg.h"
0016 #include "metalinksettings.h"
0017 
0018 #include "core/download.h"
0019 #include "core/filemodel.h"
0020 #include "core/kget.h"
0021 #include "core/signature.h"
0022 #include "core/transferdatasource.h"
0023 #include "core/transfergroup.h"
0024 #include "core/urlchecker.h"
0025 #include "core/verifier.h"
0026 
0027 #include "kget_debug.h"
0028 
0029 #include <algorithm>
0030 
0031 #include <KConfigGroup>
0032 #include <KIO/DeleteJob>
0033 #include <KIO/RenameDialog>
0034 #include <KLocalizedString>
0035 #include <KMessageBox>
0036 
0037 #include <QDialog>
0038 #include <QDir>
0039 #include <QDomElement>
0040 #include <QFile>
0041 #include <QStandardPaths>
0042 
0043 MetalinkXml::MetalinkXml(TransferGroup *parent, TransferFactory *factory, Scheduler *scheduler, const QUrl &source, const QUrl &dest, const QDomElement *e)
0044     : AbstractMetalink(parent, factory, scheduler, source, dest, e)
0045 
0046 {
0047 }
0048 
0049 MetalinkXml::~MetalinkXml()
0050 {
0051 }
0052 
0053 void MetalinkXml::start()
0054 {
0055     qCDebug(KGET_DEBUG) << "metalinkxml::start";
0056 
0057     if (!m_ready) {
0058         if (m_localMetalinkLocation.isValid() && metalinkInit()) {
0059             startMetalink();
0060         } else {
0061             downloadMetalink();
0062         }
0063     } else {
0064         startMetalink();
0065     }
0066 }
0067 
0068 void MetalinkXml::downloadMetalink()
0069 {
0070     m_metalinkJustDownloaded = true;
0071 
0072     setStatus(Job::Running, i18n("Downloading Metalink File...."), "document-save");
0073     setTransferChange(Tc_Status, true);
0074     // make sure that the DataLocation directory exists (earlier this used to be handled by KStandardDirs)
0075     if (!QFileInfo::exists(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) {
0076         QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
0077     }
0078     const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/metalinks/") + m_source.fileName();
0079     auto *download = new Download(m_source, QUrl::fromLocalFile(path));
0080     connect(download, &Download::finishedSuccessfully, this, &MetalinkXml::metalinkInit);
0081 }
0082 
0083 bool MetalinkXml::metalinkInit(const QUrl &src, const QByteArray &data)
0084 {
0085     qCDebug(KGET_DEBUG) << "MetalinkXml::metalinkInit";
0086 
0087     if (!src.isEmpty()) {
0088         m_localMetalinkLocation = src;
0089     }
0090 
0091     // use the downloaded metalink-file data directly if possible
0092     if (!data.isEmpty()) {
0093         KGetMetalink::HandleMetalink::load(data, &m_metalink);
0094     }
0095 
0096     // try to parse the locally stored metalink-file
0097     if (!m_metalink.isValid() && m_localMetalinkLocation.isValid()) {
0098         KGetMetalink::HandleMetalink::load(m_localMetalinkLocation, &m_metalink);
0099     }
0100 
0101     if (!m_metalink.isValid()) {
0102         qCCritical(KGET_DEBUG) << "Unknown error when trying to load the .metalink-file. Metalink is not valid.";
0103         setStatus(Job::Aborted);
0104         setTransferChange(Tc_Status, true);
0105         return false;
0106     }
0107 
0108     // offers a dialog to download the newest version of a dynamic metalink
0109     if ((m_source.isLocalFile() || !m_metalinkJustDownloaded) && m_metalink.dynamic && (UrlChecker::checkSource(m_metalink.origin) == UrlChecker::NoError)) {
0110         if (KMessageBox::questionTwoActions(nullptr,
0111                                             i18n("A newer version of this Metalink might exist, do you want to download it?"),
0112                                             i18n("Redownload Metalink"),
0113                                             KGuiItem(i18nc("@action:button", "Download Again"), QStringLiteral("view-refresh")),
0114                                             KGuiItem(i18nc("@action:button", "Ignore"), QStringLiteral("dialog-cancel")))
0115             == KMessageBox::PrimaryAction) {
0116             m_localMetalinkLocation.clear();
0117             m_source = m_metalink.origin;
0118             downloadMetalink();
0119             return false;
0120         }
0121     }
0122 
0123     QList<KGetMetalink::File>::const_iterator it;
0124     QList<KGetMetalink::File>::const_iterator itEnd = m_metalink.files.files.constEnd();
0125     m_totalSize = 0;
0126     KIO::fileoffset_t segSize = 500 * 1024; // TODO use config here!
0127     const QUrl tempDest = QUrl(m_dest.adjusted(QUrl::RemoveFilename));
0128     QUrl dest;
0129     for (it = m_metalink.files.files.constBegin(); it != itEnd; ++it) {
0130         dest = tempDest;
0131         dest.setPath(tempDest.path() + (*it).name);
0132 
0133         QList<KGetMetalink::Url> urlList = (*it).resources.urls;
0134         // sort the urls according to their priority (highest first)
0135         std::sort(urlList.begin(), urlList.end(), [](const KGetMetalink::Url &a, const KGetMetalink::Url &b) {
0136             return b < a;
0137         });
0138 
0139         KIO::filesize_t fileSize = (*it).size;
0140         m_totalSize += fileSize;
0141 
0142         // create a DataSourceFactory for each separate file
0143         auto *dataFactory = new DataSourceFactory(this, dest, fileSize, segSize);
0144         dataFactory->setMaxMirrorsUsed(MetalinkSettings::mirrorsPerFile());
0145 
0146         // TODO compare available file size (<size>) with the sizes of the server while downloading?
0147 
0148         connect(dataFactory, &DataSourceFactory::capabilitiesChanged, this, &MetalinkXml::slotUpdateCapabilities);
0149         connect(dataFactory, &DataSourceFactory::dataSourceFactoryChange, this, &MetalinkXml::slotDataSourceFactoryChange);
0150         connect(dataFactory->verifier(), &Verifier::verified, this, &MetalinkXml::slotVerified);
0151         connect(dataFactory->signature(), &Signature::verified, this, &MetalinkXml::slotSignatureVerified);
0152         connect(dataFactory, &DataSourceFactory::log, this, &Transfer::setLog);
0153 
0154         // add the DataSources
0155         for (int i = 0; i < urlList.size(); ++i) {
0156             const QUrl url = urlList[i].url;
0157             if (url.isValid()) {
0158                 dataFactory->addMirror(url, MetalinkSettings::connectionsPerUrl());
0159             }
0160         }
0161         // no datasource has been created, so remove the datasource factory
0162         if (dataFactory->mirrors().isEmpty()) {
0163             delete dataFactory;
0164         } else {
0165             dataFactory->verifier()->addChecksums(it->verification.hashes);
0166 
0167             const auto pieces = it->verification.pieces;
0168             for (const KGetMetalink::Pieces &piece : pieces) {
0169                 dataFactory->verifier()->addPartialChecksums(piece.type, piece.length, piece.hashes);
0170             }
0171 
0172             const auto signatures = it->verification.signatures;
0173             for (auto it = signatures.constBegin(), itEnd = signatures.constEnd(); it != itEnd; ++it) {
0174                 if (it.key().toLower() == "pgp") {
0175                     dataFactory->signature()->setAsciiDetachedSignature(*it);
0176                 }
0177             }
0178 
0179             m_dataSourceFactory[dataFactory->dest()] = dataFactory;
0180         }
0181     }
0182 
0183     if ((m_metalink.files.files.size() == 1) && m_dataSourceFactory.size()) {
0184         m_dest = dest;
0185     }
0186 
0187     if (!m_dataSourceFactory.size()) {
0188         // TODO make this via log in the future + do not display the KMessageBox
0189         qCWarning(KGET_DEBUG) << "Download of" << m_source << "failed, no working URLs were found.";
0190         KMessageBox::error(nullptr, i18n("Download failed, no working URLs were found."), i18n("Error"));
0191         setStatus(Job::Aborted);
0192         setTransferChange(Tc_Status, true);
0193         return false;
0194     }
0195 
0196     m_ready = !m_dataSourceFactory.isEmpty();
0197     slotUpdateCapabilities();
0198 
0199     // the metalink-file has just been downloaded, so ask the user to choose the files that
0200     //  should be downloaded
0201     /* TODO this portion seems not to be working. Need to ask boom1992 */
0202     if (m_metalinkJustDownloaded) {
0203         QDialog *dialog = new FileSelectionDlg(fileModel());
0204         dialog->setAttribute(Qt::WA_DeleteOnClose);
0205         connect(dialog, &QDialog::finished, this, &MetalinkXml::fileDlgFinished);
0206 
0207         dialog->show();
0208     }
0209 
0210     return true;
0211 }
0212 
0213 void MetalinkXml::startMetalink()
0214 {
0215     if (m_ready) {
0216         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0217             // specified number of files is downloaded simultaneously
0218             if (m_currentFiles < MetalinkSettings::simultaneousFiles()) {
0219                 const int status = factory->status();
0220                 // only start factories that should be downloaded
0221                 if (factory->doDownload() && (status != Job::Finished) && (status != Job::FinishedKeepAlive) && (status != Job::Running)) {
0222                     ++m_currentFiles;
0223                     factory->start();
0224                 }
0225             } else {
0226                 break;
0227             }
0228         }
0229     }
0230 }
0231 
0232 void MetalinkXml::deinit(Transfer::DeleteOptions options)
0233 {
0234     foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0235         if (options & Transfer::DeleteFiles) {
0236             factory->deinit();
0237         }
0238     } // TODO: Ask the user if he/she wants to delete the *.part-file? To discuss (boom1992)
0239 
0240     // FIXME does that mean, that the metalink file is always removed, even if
0241     // downloaded by the user?
0242     if ((options & Transfer::DeleteTemporaryFiles) && m_localMetalinkLocation.isLocalFile()) {
0243         KIO::Job *del = KIO::del(m_localMetalinkLocation, KIO::HideProgressInfo);
0244         if (!del->exec()) {
0245             qCDebug(KGET_DEBUG) << "Could not delete " << m_localMetalinkLocation.path();
0246         }
0247     }
0248 }
0249 
0250 void MetalinkXml::load(const QDomElement *element)
0251 {
0252     Transfer::load(element);
0253 
0254     if (!element) {
0255         return;
0256     }
0257 
0258     const QDomElement e = *element;
0259     m_localMetalinkLocation = QUrl(e.attribute("LocalMetalinkLocation"));
0260     QDomNodeList factories = e.firstChildElement("factories").elementsByTagName("factory");
0261 
0262     // no stored information found, stop right here
0263     if (!factories.count()) {
0264         return;
0265     }
0266 
0267     while (factories.count()) {
0268         QDomDocument doc;
0269         QDomElement factory = doc.createElement("factories");
0270         factory.appendChild(factories.item(0).toElement());
0271         doc.appendChild(factory);
0272 
0273         auto *file = new DataSourceFactory(this);
0274         file->load(&factory);
0275         connect(file, &DataSourceFactory::capabilitiesChanged, this, &MetalinkXml::slotUpdateCapabilities);
0276         connect(file, &DataSourceFactory::dataSourceFactoryChange, this, &MetalinkXml::slotDataSourceFactoryChange);
0277         m_dataSourceFactory[file->dest()] = file;
0278         connect(file->verifier(), &Verifier::verified, this, &MetalinkXml::slotVerified);
0279         connect(file->signature(), &Signature::verified, this, &MetalinkXml::slotSignatureVerified);
0280         connect(file, &DataSourceFactory::log, this, &Transfer::setLog);
0281 
0282         // start the DataSourceFactories that were Started when KGet was closed
0283         if (file->status() == Job::Running) {
0284             if (m_currentFiles < MetalinkSettings::simultaneousFiles()) {
0285                 ++m_currentFiles;
0286                 file->start();
0287             } else {
0288                 // enough simultaneous files already, so increase the number and set file to stop --> that will decrease the number again
0289                 file->stop();
0290             }
0291         }
0292     }
0293     m_ready = !m_dataSourceFactory.isEmpty();
0294     slotUpdateCapabilities();
0295 }
0296 
0297 void MetalinkXml::save(const QDomElement &element)
0298 {
0299     Transfer::save(element);
0300 
0301     QDomElement e = element;
0302     e.setAttribute("LocalMetalinkLocation", m_localMetalinkLocation.url());
0303 
0304     for (DataSourceFactory *factory : std::as_const(m_dataSourceFactory)) {
0305         factory->save(e);
0306     }
0307 }
0308 
0309 #include "moc_metalinkxml.cpp"