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"