File indexing completed on 2024-04-28 04:57:31
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 "abstractmetalink.h" 0015 0016 #include "core/download.h" 0017 #include "core/filemodel.h" 0018 #include "core/kget.h" 0019 #include "core/signature.h" 0020 #include "core/transferdatasource.h" 0021 #include "core/transfergroup.h" 0022 #include "core/urlchecker.h" 0023 #include "core/verifier.h" 0024 0025 #include "kget_debug.h" 0026 0027 #include <KIO/DeleteJob> 0028 #include <KIO/RenameDialog> 0029 #include <KLocalizedString> 0030 #include <KMessageBox> 0031 0032 #include <QDialog> 0033 #include <QDomElement> 0034 #include <QFile> 0035 0036 AbstractMetalink::AbstractMetalink(TransferGroup *parent, 0037 TransferFactory *factory, 0038 Scheduler *scheduler, 0039 const QUrl &source, 0040 const QUrl &dest, 0041 const QDomElement *e) 0042 : Transfer(parent, factory, scheduler, source, dest, e) 0043 , m_fileModel(nullptr) 0044 , m_currentFiles() 0045 , m_ready(false) 0046 , m_speedCount(0) 0047 , m_tempAverageSpeed(0) 0048 , m_averageSpeed(0) 0049 { 0050 } 0051 0052 AbstractMetalink::~AbstractMetalink() 0053 { 0054 } 0055 0056 void AbstractMetalink::slotDataSourceFactoryChange(Transfer::ChangesFlags change) 0057 { 0058 if ((change & Tc_Status) | (change & Tc_TotalSize)) { 0059 auto *factory = qobject_cast<DataSourceFactory *>(sender()); 0060 if (change & Tc_Status) { 0061 bool changeStatus; 0062 updateStatus(factory, &changeStatus); 0063 if (!changeStatus) { 0064 change &= ~Tc_Status; 0065 } 0066 } 0067 if (change & Tc_TotalSize) { 0068 recalculateTotalSize(factory); 0069 } 0070 } 0071 if (change & Tc_DownloadedSize) { 0072 recalculateProcessedSize(); 0073 change |= Tc_Percent; 0074 } 0075 if (change & Tc_DownloadSpeed) { 0076 recalculateSpeed(); 0077 } 0078 0079 setTransferChange(change, true); 0080 } 0081 0082 void AbstractMetalink::recalculateTotalSize(DataSourceFactory *sender) 0083 { 0084 m_totalSize = 0; 0085 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0086 if (factory->doDownload()) { 0087 m_totalSize += factory->size(); 0088 } 0089 } 0090 0091 if (m_fileModel) { 0092 if (sender) { 0093 QModelIndex sizeIndex = m_fileModel->index(sender->dest(), FileItem::Size); 0094 m_fileModel->setData(sizeIndex, static_cast<qlonglong>(sender->size())); 0095 } 0096 } 0097 } 0098 0099 void AbstractMetalink::recalculateProcessedSize() 0100 { 0101 m_downloadedSize = 0; 0102 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0103 if (factory->doDownload()) { 0104 m_downloadedSize += factory->downloadedSize(); 0105 } 0106 } 0107 0108 if (m_totalSize) { 0109 m_percent = (m_downloadedSize * 100) / m_totalSize; 0110 } else { 0111 m_percent = 0; 0112 } 0113 } 0114 0115 void AbstractMetalink::recalculateSpeed() 0116 { 0117 m_downloadSpeed = 0; 0118 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0119 if (factory->doDownload()) { 0120 m_downloadSpeed += factory->currentSpeed(); 0121 } 0122 } 0123 0124 // calculate the average of the last three speeds 0125 m_tempAverageSpeed += m_downloadSpeed; 0126 ++m_speedCount; 0127 if (m_speedCount == 3) { 0128 m_averageSpeed = m_tempAverageSpeed / 3; 0129 m_speedCount = 0; 0130 m_tempAverageSpeed = 0; 0131 } 0132 } 0133 0134 int AbstractMetalink::remainingTime() const 0135 { 0136 if (!m_averageSpeed) { 0137 m_averageSpeed = m_downloadSpeed; 0138 } 0139 return KIO::calculateRemainingSeconds(m_totalSize, m_downloadedSize, m_averageSpeed); 0140 } 0141 0142 void AbstractMetalink::updateStatus(DataSourceFactory *sender, bool *changeStatus) 0143 { 0144 Job::Status status = (sender ? sender->status() : Job::Stopped); 0145 *changeStatus = true; 0146 switch (status) { 0147 case Job::Aborted: 0148 case Job::Stopped: { 0149 m_currentFiles = 0; 0150 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0151 // one factory is still running, do not change the status 0152 if (factory->doDownload() && (factory->status() == Job::Running)) { 0153 *changeStatus = false; 0154 ++m_currentFiles; 0155 } 0156 } 0157 0158 if (*changeStatus) { 0159 setStatus(status); 0160 } 0161 break; 0162 } 0163 case Job::Finished: 0164 // one file that has been downloaded now is finished//FIXME ignore downloads that were finished in the previous download!!!! 0165 if (m_currentFiles) { 0166 --m_currentFiles; 0167 startMetalink(); 0168 } 0169 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0170 // one factory is not finished, do not change the status 0171 if (factory->doDownload() && (factory->status() != Job::Finished)) { 0172 *changeStatus = false; 0173 break; 0174 } 0175 } 0176 0177 if (*changeStatus) { 0178 setStatus(Job::Finished); 0179 } 0180 break; 0181 0182 default: 0183 setStatus(status); 0184 break; 0185 } 0186 0187 if (m_fileModel) { 0188 if (sender) { 0189 QModelIndex statusIndex = m_fileModel->index(sender->dest(), FileItem::Status); 0190 m_fileModel->setData(statusIndex, status); 0191 } 0192 } 0193 } 0194 0195 void AbstractMetalink::slotVerified(bool isVerified) 0196 { 0197 Q_UNUSED(isVerified) 0198 0199 if (status() == Job::Finished) { 0200 // see if some files are NotVerified 0201 QStringList brokenFiles; 0202 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0203 if (m_fileModel) { 0204 QModelIndex checksumVerified = m_fileModel->index(factory->dest(), FileItem::ChecksumVerified); 0205 m_fileModel->setData(checksumVerified, factory->verifier()->status()); 0206 } 0207 if (factory->doDownload() && (factory->verifier()->status() == Verifier::NotVerified)) { 0208 brokenFiles.append(factory->dest().toString()); 0209 } 0210 } 0211 0212 if (brokenFiles.count()) { 0213 if (KMessageBox::warningTwoActionsList( 0214 nullptr, 0215 i18n("The download could not be verified, do you want to repair (if repairing does not work the download would be restarted) it?"), 0216 brokenFiles, 0217 QString(), 0218 KGuiItem(i18nc("@action:button", "Repair")), 0219 KGuiItem(i18nc("@action:button", "Ignore"), QStringLiteral("dialog-cancel"))) 0220 == KMessageBox::PrimaryAction) { 0221 if (repair()) { 0222 return; 0223 } 0224 } 0225 } 0226 } 0227 } 0228 0229 void AbstractMetalink::slotSignatureVerified() 0230 { 0231 if (status() == Job::Finished) { 0232 // see if some files are NotVerified 0233 QStringList brokenFiles; 0234 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0235 if (m_fileModel) { 0236 QModelIndex signatureVerified = m_fileModel->index(factory->dest(), FileItem::SignatureVerified); 0237 m_fileModel->setData(signatureVerified, factory->signature()->status()); 0238 } 0239 if (factory->doDownload() && (factory->verifier()->status() == Verifier::NotVerified)) { 0240 brokenFiles.append(factory->dest().toString()); 0241 } 0242 } 0243 /* 0244 if (brokenFiles.count())//TODO 0245 { 0246 if (KMessageBox::warningYesNoCancelList(0, 0247 i18n("The download could not be verified, try to repair it?"), 0248 brokenFiles) == KMessageBox::Yes) 0249 { 0250 if (repair()) 0251 { 0252 return; 0253 } 0254 } 0255 }*/ 0256 } 0257 } 0258 0259 bool AbstractMetalink::repair(const QUrl &file) 0260 { 0261 if (file.isValid()) { 0262 if (m_dataSourceFactory.contains(file)) { 0263 DataSourceFactory *broken = m_dataSourceFactory[file]; 0264 if (broken->verifier()->status() == Verifier::NotVerified) { 0265 broken->repair(); 0266 return true; 0267 } 0268 } 0269 } else { 0270 QList<DataSourceFactory *> broken; 0271 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0272 if (factory->doDownload() && (factory->verifier()->status() == Verifier::NotVerified)) { 0273 broken.append(factory); 0274 } 0275 } 0276 if (broken.count()) { 0277 foreach (DataSourceFactory *factory, broken) { 0278 factory->repair(); 0279 } 0280 return true; 0281 } 0282 } 0283 0284 return false; 0285 } 0286 0287 Verifier *AbstractMetalink::verifier(const QUrl &file) 0288 { 0289 if (!m_dataSourceFactory.contains(file)) { 0290 return nullptr; 0291 } 0292 0293 return m_dataSourceFactory[file]->verifier(); 0294 } 0295 0296 Signature *AbstractMetalink::signature(const QUrl &file) 0297 { 0298 if (!m_dataSourceFactory.contains(file)) { 0299 return nullptr; 0300 } 0301 0302 return m_dataSourceFactory[file]->signature(); 0303 } 0304 0305 QList<QUrl> AbstractMetalink::files() const 0306 { 0307 return m_dataSourceFactory.keys(); 0308 } 0309 0310 FileModel *AbstractMetalink::fileModel() 0311 { 0312 if (!m_fileModel) { 0313 m_fileModel = new FileModel(files(), directory(), this); 0314 connect(m_fileModel, SIGNAL(rename(QUrl, QUrl)), this, SLOT(slotRename(QUrl, QUrl))); 0315 connect(m_fileModel, &FileModel::checkStateChanged, this, &AbstractMetalink::filesSelected); 0316 0317 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0318 const QUrl dest = factory->dest(); 0319 QModelIndex size = m_fileModel->index(dest, FileItem::Size); 0320 m_fileModel->setData(size, static_cast<qlonglong>(factory->size())); 0321 QModelIndex status = m_fileModel->index(dest, FileItem::Status); 0322 m_fileModel->setData(status, factory->status()); 0323 QModelIndex checksumVerified = m_fileModel->index(dest, FileItem::ChecksumVerified); 0324 m_fileModel->setData(checksumVerified, factory->verifier()->status()); 0325 QModelIndex signatureVerified = m_fileModel->index(dest, FileItem::SignatureVerified); 0326 m_fileModel->setData(signatureVerified, factory->signature()->status()); 0327 if (!factory->doDownload()) { 0328 QModelIndex index = m_fileModel->index(factory->dest(), FileItem::File); 0329 m_fileModel->setData(index, Qt::Unchecked, Qt::CheckStateRole); 0330 } 0331 } 0332 } 0333 0334 return m_fileModel; 0335 } 0336 0337 void AbstractMetalink::slotRename(const QUrl &oldUrl, const QUrl &newUrl) 0338 { 0339 if (!m_dataSourceFactory.contains(oldUrl)) { 0340 return; 0341 } 0342 0343 m_dataSourceFactory[newUrl] = m_dataSourceFactory[oldUrl]; 0344 m_dataSourceFactory.remove(oldUrl); 0345 m_dataSourceFactory[newUrl]->setNewDestination(newUrl); 0346 0347 setTransferChange(Tc_FileName); 0348 } 0349 0350 bool AbstractMetalink::setDirectory(const QUrl &new_directory) 0351 { 0352 if (new_directory == directory()) { 0353 return false; 0354 } 0355 0356 if (m_fileModel) { 0357 m_fileModel->setDirectory(new_directory); 0358 } 0359 0360 const QString oldDirectory = directory().toString(); 0361 const QString newDirectory = new_directory.toString(); 0362 const QString fileName = m_dest.fileName(); 0363 m_dest = new_directory; 0364 m_dest.setPath(m_dest.adjusted(QUrl::RemoveFilename).toString() + fileName); 0365 0366 QHash<QUrl, DataSourceFactory *> newStorage; 0367 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0368 const QUrl oldUrl = factory->dest(); 0369 const QUrl newUrl = QUrl(oldUrl.toString().replace(oldDirectory, newDirectory)); 0370 factory->setNewDestination(newUrl); 0371 newStorage[newUrl] = factory; 0372 } 0373 m_dataSourceFactory = newStorage; 0374 0375 setTransferChange(Tc_FileName); 0376 return true; 0377 } 0378 0379 QHash<QUrl, QPair<bool, int>> AbstractMetalink::availableMirrors(const QUrl &file) const 0380 { 0381 QHash<QUrl, QPair<bool, int>> urls; 0382 0383 if (m_dataSourceFactory.contains(file)) { 0384 urls = m_dataSourceFactory[file]->mirrors(); 0385 } 0386 0387 return urls; 0388 } 0389 0390 void AbstractMetalink::setAvailableMirrors(const QUrl &file, const QHash<QUrl, QPair<bool, int>> &mirrors) 0391 { 0392 if (!m_dataSourceFactory.contains(file)) { 0393 return; 0394 } 0395 0396 m_dataSourceFactory[file]->setMirrors(mirrors); 0397 } 0398 0399 void AbstractMetalink::slotUpdateCapabilities() 0400 { 0401 Capabilities oldCap = capabilities(); 0402 Capabilities newCap = {}; 0403 foreach (DataSourceFactory *file, m_dataSourceFactory) { 0404 if (file->doDownload()) { // FIXME when a download did not start yet it should be moveable!!//FIXME why not working, when only two connections? 0405 if (newCap) { 0406 newCap &= file->capabilities(); 0407 } else { 0408 newCap = file->capabilities(); 0409 } 0410 } 0411 } 0412 0413 if (newCap != oldCap) { 0414 setCapabilities(newCap); 0415 } 0416 } 0417 0418 void AbstractMetalink::untickAllFiles() 0419 { 0420 for (int row = 0; row < fileModel()->rowCount(); ++row) { 0421 QModelIndex index = fileModel()->index(row, FileItem::File); 0422 if (index.isValid()) { 0423 fileModel()->setData(index, Qt::Unchecked, Qt::CheckStateRole); 0424 } 0425 } 0426 } 0427 0428 void AbstractMetalink::fileDlgFinished(int result) 0429 { 0430 // the dialog was not accepted untick every file, this ensures that the user does not 0431 // press start by accident without first selecting the desired files 0432 if (result != QDialog::Accepted) { 0433 untickAllFiles(); 0434 } 0435 0436 filesSelected(); 0437 0438 // no files selected to download or dialog rejected, stop the download 0439 if (!m_numFilesSelected || (result != QDialog::Accepted)) { 0440 setStatus(Job::Stopped); 0441 setTransferChange(Tc_Status, true); 0442 return; 0443 } 0444 0445 startMetalink(); 0446 } 0447 0448 void AbstractMetalink::filesSelected() 0449 { 0450 bool overwriteAll = false; 0451 bool autoSkip = false; 0452 bool cancel = false; 0453 QModelIndexList files = fileModel()->fileIndexes(FileItem::File); 0454 m_numFilesSelected = 0; 0455 0456 // sets the CheckState of the fileModel to the according DataSourceFactories 0457 // and asks the user if there are existing files already 0458 foreach (const QModelIndex &index, files) { 0459 const QUrl dest = fileModel()->getUrl(index); 0460 bool doDownload = index.data(Qt::CheckStateRole).toBool(); 0461 if (m_dataSourceFactory.contains(dest)) { 0462 DataSourceFactory *factory = m_dataSourceFactory[dest]; 0463 // ignore finished transfers 0464 if ((factory->status() == Job::Finished) || (factory->status() == Job::FinishedKeepAlive)) { 0465 continue; 0466 } 0467 0468 // check if the file at dest exists already and ask the user what to do in this case, ignore already running transfers 0469 if (doDownload && (factory->status() != Job::Running) && QFile::exists(dest.toLocalFile())) { 0470 // user has chosen to skip all files that exist already before 0471 if (autoSkip) { 0472 fileModel()->setData(index, Qt::Unchecked, Qt::CheckStateRole); 0473 doDownload = false; 0474 // ask the user, unless he has chosen overwriteAll before 0475 } else if (!overwriteAll) { 0476 KIO::RenameDialog dlg(nullptr, 0477 i18n("File already exists"), 0478 QUrl(index.data().toString()), 0479 dest, 0480 KIO::RenameDialog_Options(KIO::RenameDialog_MultipleItems | KIO::RenameDialog_Overwrite | KIO::RenameDialog_Skip)); 0481 const int result = dlg.exec(); 0482 0483 if (result == KIO::Result_Rename) { 0484 // no reason to use FileModel::rename() since the file does not exist yet, so simply skip it 0485 // avoids having to deal with signals 0486 const QUrl newDest = dlg.newDestUrl(); 0487 factory->setDoDownload(doDownload); 0488 factory->setNewDestination(newDest); 0489 fileModel()->setData(index, newDest.fileName(), FileItem::File); 0490 ++m_numFilesSelected; 0491 0492 m_dataSourceFactory.remove(dest); 0493 m_dataSourceFactory[newDest] = factory; 0494 continue; 0495 } else if (result == KIO::Result_Skip) { 0496 fileModel()->setData(index, Qt::Unchecked, Qt::CheckStateRole); 0497 doDownload = false; 0498 } else if (result == KIO::Result_Cancel) { 0499 cancel = true; 0500 break; 0501 } else if (result == KIO::Result_AutoSkip) { 0502 autoSkip = true; 0503 fileModel()->setData(index, Qt::Unchecked, Qt::CheckStateRole); 0504 doDownload = false; 0505 } else if (result == KIO::Result_OverwriteAll) { 0506 overwriteAll = true; 0507 } 0508 } 0509 } 0510 0511 factory->setDoDownload(doDownload); 0512 if (doDownload && (factory->status() != Finished) && (factory->status() != FinishedKeepAlive)) { 0513 ++m_numFilesSelected; 0514 } 0515 } 0516 } 0517 0518 // the user decided to cancel, so untick all files 0519 if (cancel) { 0520 m_numFilesSelected = 0; 0521 untickAllFiles(); 0522 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0523 factory->setDoDownload(false); 0524 } 0525 } 0526 0527 Transfer::ChangesFlags change = (Tc_TotalSize | Tc_DownloadSpeed); 0528 // some files have been selected that are not finished yet, set them to stop if the transfer is not running (checked in slotStatus) 0529 if (m_numFilesSelected) { 0530 change |= Tc_Status; 0531 } 0532 slotDataSourceFactoryChange(change); 0533 } 0534 0535 void AbstractMetalink::stop() 0536 { 0537 qCDebug(KGET_DEBUG) << "metalink::Stop"; 0538 if (m_ready && ((status() != Stopped) || (status() != Finished))) { 0539 m_currentFiles = 0; 0540 foreach (DataSourceFactory *factory, m_dataSourceFactory) { 0541 factory->stop(); 0542 } 0543 } 0544 } 0545 0546 #include "moc_abstractmetalink.cpp"