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"