File indexing completed on 2024-04-14 04:51:58

0001 /* This file is part of the KDE project
0002 
0003    Copyright (C) 2005 Dario Massarin <nekkar@libero.it>
0004    Copyright (C) 2007-2009 Lukas Appelhans <l.appelhans@gmx.de>
0005    Copyright (C) 2008 Urs Wolfer <uwolfer @ kde.org>
0006    Copyright (C) 2008 Dario Freddi <drf54321@gmail.com>
0007    Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>
0008 
0009    This program is free software; you can redistribute it and/or
0010    modify it under the terms of the GNU General Public
0011    License as published by the Free Software Foundation; either
0012    version 2 of the License, or (at your option) any later version.
0013 */
0014 
0015 #include "core/kget.h"
0016 
0017 #include "core/kuiserverjobs.h"
0018 #include "core/mostlocalurl.h"
0019 #include "core/plugin/plugin.h"
0020 #include "core/plugin/transferfactory.h"
0021 #include "core/transfer.h"
0022 #include "core/transferdatasource.h"
0023 #include "core/transfergroup.h"
0024 #include "core/transfergrouphandler.h"
0025 #include "core/transfergroupscheduler.h"
0026 #include "core/transferhistorystore.h"
0027 #include "core/transfertreemodel.h"
0028 #include "core/transfertreeselectionmodel.h"
0029 #include "mainwindow.h"
0030 #include "plasmashudown_interface.h"
0031 #include "settings.h"
0032 
0033 #include "kget_debug.h"
0034 
0035 #include <algorithm>
0036 #include <iostream>
0037 
0038 #include <KConfigDialog>
0039 #include <KIO/DeleteJob>
0040 #include <KIO/RenameDialog>
0041 #include <KIO/StoredTransferJob>
0042 #include <KMessageBox>
0043 #include <KPluginMetaData>
0044 #include <KSharedConfig>
0045 
0046 #include <QAbstractItemView>
0047 #include <QApplication>
0048 #include <QClipboard>
0049 #include <QDBusConnection>
0050 #include <QDBusMessage>
0051 #include <QDomElement>
0052 #include <QFileDialog>
0053 #include <QInputDialog>
0054 #include <QSaveFile>
0055 #include <QStandardPaths>
0056 #include <QTemporaryFile>
0057 #include <QTextStream>
0058 #include <QTimer>
0059 
0060 using namespace Qt::Literals::StringLiterals;
0061 
0062 KGet::TransferData::TransferData(const QUrl &source, const QUrl &destination, const QString &group, bool doStart, const QDomElement *element)
0063     : src(source)
0064     , dest(destination)
0065     , groupName(group)
0066     , start(doStart)
0067     , e(element)
0068 {
0069 }
0070 
0071 /**
0072  * This is our KGet class. This is where the user's transfers and searches are
0073  * stored and organized.
0074  * Use this class from the views to add or remove transfers or searches
0075  * In order to organize the transfers inside categories we have a TransferGroup
0076  * class. By definition, a transfer must always belong to a TransferGroup. If we
0077  * don't want it to be displayed by the gui inside a specific group, we will put
0078  * it in the group named "Not grouped" (better name?).
0079  **/
0080 
0081 KGet *KGet::self(MainWindow *mainWindow)
0082 {
0083     if (mainWindow) {
0084         m_mainWindow = mainWindow;
0085         m_jobManager = new KUiServerJobs(m_mainWindow);
0086     }
0087 
0088     static KGet *m = new KGet();
0089 
0090     return m;
0091 }
0092 
0093 bool KGet::addGroup(const QString &groupName)
0094 {
0095     qCDebug(KGET_DEBUG);
0096 
0097     // Check if a group with that name already exists
0098     if (m_transferTreeModel->findGroup(groupName))
0099         return false;
0100 
0101     auto *group = new TransferGroup(m_transferTreeModel, m_scheduler, groupName);
0102     m_transferTreeModel->addGroup(group);
0103 
0104     return true;
0105 }
0106 
0107 void KGet::delGroup(TransferGroupHandler *group, bool askUser)
0108 {
0109     TransferGroup *g = group->m_group;
0110 
0111     if (askUser) {
0112         QWidget *configDialog = KConfigDialog::exists("preferences");
0113         if (KMessageBox::warningTwoActions(configDialog ? configDialog : m_mainWindow,
0114                                            i18n("Are you sure that you want to remove the group named %1?", g->name()),
0115                                            i18n("Remove Group"),
0116                                            KStandardGuiItem::remove(),
0117                                            KStandardGuiItem::cancel())
0118             == KMessageBox::SecondaryAction)
0119             return;
0120     }
0121 
0122     m_transferTreeModel->delGroup(g);
0123     g->deleteLater();
0124 }
0125 
0126 void KGet::delGroups(QList<TransferGroupHandler *> groups, bool askUser)
0127 {
0128     if (groups.isEmpty())
0129         return;
0130     if (groups.count() == 1) {
0131         KGet::delGroup(groups.first(), askUser);
0132         return;
0133     }
0134     bool del = !askUser;
0135     if (askUser) {
0136         QStringList names;
0137         foreach (TransferGroupHandler *handler, groups)
0138             names << handler->name();
0139         QWidget *configDialog = KConfigDialog::exists("preferences");
0140         del = KMessageBox::warningTwoActionsList(configDialog ? configDialog : m_mainWindow,
0141                                                  i18n("Are you sure that you want to remove the following groups?"),
0142                                                  names,
0143                                                  i18n("Remove groups"),
0144                                                  KStandardGuiItem::remove(),
0145                                                  KStandardGuiItem::cancel())
0146             == KMessageBox::PrimaryAction;
0147     }
0148     if (del) {
0149         foreach (TransferGroupHandler *handler, groups)
0150             KGet::delGroup(handler, false);
0151     }
0152 }
0153 
0154 void KGet::renameGroup(const QString &oldName, const QString &newName)
0155 {
0156     TransferGroup *group = m_transferTreeModel->findGroup(oldName);
0157 
0158     if (group) {
0159         group->handler()->setName(newName);
0160     }
0161 }
0162 
0163 QStringList KGet::transferGroupNames()
0164 {
0165     QStringList names;
0166 
0167     foreach (TransferGroup *group, m_transferTreeModel->transferGroups()) {
0168         names << group->name();
0169     }
0170 
0171     return names;
0172 }
0173 
0174 TransferHandler *KGet::addTransfer(QUrl srcUrl,
0175                                    QString destDir,
0176                                    QString suggestedFileName, // krazy:exclude=passbyvalue
0177                                    QString groupName,
0178                                    bool start)
0179 {
0180     srcUrl = mostLocalUrl(srcUrl);
0181     // Note: destDir may actually be a full path to a file :-(
0182     qCDebug(KGET_DEBUG) << "Source:" << srcUrl.url() << ", dest: " << destDir << ", sugg file: " << suggestedFileName;
0183 
0184     QUrl destUrl; // the final destination, including filename
0185 
0186     if (srcUrl.isEmpty()) {
0187         // No src location: we let the user insert it manually
0188         srcUrl = urlInputDialog();
0189         if (srcUrl.isEmpty())
0190             return nullptr;
0191     }
0192 
0193     if (!isValidSource(srcUrl))
0194         return nullptr;
0195 
0196     // when we get a destination directory and suggested filename, we don't
0197     // need to ask for it again
0198     bool confirmDestination = false;
0199     if (destDir.isEmpty()) {
0200         confirmDestination = true;
0201         QList<TransferGroupHandler *> list = groupsFromExceptions(srcUrl);
0202         if (!list.isEmpty()) {
0203             destDir = list.first()->defaultFolder();
0204             groupName = list.first()->name();
0205         }
0206 
0207     } else {
0208         // check whether destDir is actually already the path to a file
0209         QUrl targetUrl = QUrl::fromLocalFile(destDir);
0210         QString directory = targetUrl.adjusted(QUrl::RemoveFilename).path();
0211         QString fileName = targetUrl.fileName(QUrl::PrettyDecoded);
0212         if (QFileInfo(directory).isDir() && !fileName.isEmpty()) {
0213             destDir = directory;
0214             suggestedFileName = fileName;
0215         }
0216     }
0217 
0218     if (suggestedFileName.isEmpty()) {
0219         confirmDestination = true;
0220         suggestedFileName = srcUrl.fileName(QUrl::PrettyDecoded);
0221         if (suggestedFileName.isEmpty()) {
0222             // simply use the full url as filename
0223             suggestedFileName = QUrl::toPercentEncoding(srcUrl.toDisplayString(), "/");
0224         }
0225     }
0226 
0227     // now ask for confirmation of the entire destination url (dir + filename)
0228     if (confirmDestination || !isValidDestDirectory(destDir)) {
0229         do {
0230             destUrl = destFileInputDialog(destDir, suggestedFileName);
0231             if (destUrl.isEmpty())
0232                 return nullptr;
0233 
0234             destDir = destUrl.adjusted(QUrl::RemoveFilename).path();
0235         } while (!isValidDestDirectory(destDir));
0236     } else {
0237         destUrl = QUrl::fromLocalFile(destDir + suggestedFileName);
0238     }
0239     destUrl = getValidDestUrl(destUrl, srcUrl);
0240 
0241     if (destUrl == QUrl())
0242         return nullptr;
0243 
0244     TransferHandler *transfer = createTransfer(srcUrl, destUrl, groupName, start);
0245     if (transfer) {
0246         KGet::showNotification(
0247             u"added"_s,
0248             i18n("<p>The following transfer has been added to the download list:</p><p style=\"font-size: small;\">%1</p>", transfer->source().toString()),
0249             u"kget"_s,
0250             i18n("Download added"));
0251     }
0252 
0253     return transfer;
0254 }
0255 
0256 QList<TransferHandler *> KGet::addTransfers(const QList<QDomElement> &elements, const QString &groupName)
0257 {
0258     QList<TransferData> data;
0259 
0260     foreach (const QDomElement &e, elements) {
0261         // We need to read these attributes now in order to know which transfer
0262         // plugin to use.
0263         QUrl srcUrl = QUrl(e.attribute("Source"));
0264         QUrl destUrl = QUrl(e.attribute("Dest"));
0265         data << TransferData(srcUrl, destUrl, groupName, false, &e);
0266 
0267         qCDebug(KGET_DEBUG) << "src=" << srcUrl << " dest=" << destUrl << " group=" << groupName;
0268     }
0269 
0270     return createTransfers(data);
0271 }
0272 
0273 const QList<TransferHandler *> KGet::addTransfer(QList<QUrl> srcUrls, QString destDir, QString groupName, bool start)
0274 {
0275     QList<QUrl> urlsToDownload;
0276 
0277     QList<QUrl>::iterator it = srcUrls.begin();
0278     QList<QUrl>::iterator itEnd = srcUrls.end();
0279 
0280     QList<TransferHandler *> addedTransfers;
0281 
0282     for (; it != itEnd; ++it) {
0283         *it = mostLocalUrl(*it);
0284         if (isValidSource(*it))
0285             urlsToDownload.append(*it);
0286     }
0287 
0288     if (urlsToDownload.count() == 0)
0289         return addedTransfers;
0290 
0291     if (urlsToDownload.count() == 1) {
0292         // just one file -> ask for filename
0293         TransferHandler *newTransfer = addTransfer(srcUrls.first(), destDir, srcUrls.first().fileName(), groupName, start);
0294 
0295         if (newTransfer) {
0296             addedTransfers.append(newTransfer);
0297         }
0298 
0299         return addedTransfers;
0300     }
0301 
0302     QUrl destUrl;
0303 
0304     // multiple files -> ask for directory, not for every single filename
0305     if (!isValidDestDirectory(destDir)) // TODO: Move that after the for-loop
0306         destDir = destDirInputDialog();
0307 
0308     it = urlsToDownload.begin();
0309     itEnd = urlsToDownload.end();
0310 
0311     QList<TransferData> data;
0312     for (; it != itEnd; ++it) {
0313         if (destDir.isEmpty()) {
0314             // TODO only use groupsFromExceptions if that is allowed in the settings
0315             QList<TransferGroupHandler *> list = groupsFromExceptions(*it);
0316             if (!list.isEmpty()) {
0317                 destDir = list.first()->defaultFolder();
0318                 groupName = list.first()->name();
0319             }
0320         }
0321         destUrl = getValidDestUrl(QUrl::fromLocalFile(destDir), *it);
0322 
0323         if (destUrl == QUrl())
0324             continue;
0325 
0326         data << TransferData(*it, destUrl, groupName, start);
0327     }
0328 
0329     QList<TransferHandler *> transfers = createTransfers(data);
0330     if (!transfers.isEmpty()) {
0331         QString urls = transfers[0]->source().toString();
0332         for (int i = 1; i < transfers.count(); ++i) {
0333             urls += '\n' + transfers[i]->source().toString();
0334         }
0335 
0336         QString message;
0337         if (transfers.count() == 1) {
0338             message = i18n("<p>The following transfer has been added to the download list:</p>");
0339         } else {
0340             message = i18n("<p>The following transfers have been added to the download list:</p>");
0341         }
0342         const QString content = QString("<p style=\"font-size: small;\">%1</p>").arg(urls);
0343         KGet::showNotification("added", message + content, "kget", i18n("Download added"));
0344     }
0345 
0346     return transfers;
0347 }
0348 
0349 bool KGet::delTransfer(TransferHandler *transfer, DeleteMode mode)
0350 {
0351     return delTransfers(QList<TransferHandler *>() << transfer, mode);
0352 }
0353 
0354 bool KGet::delTransfers(const QList<TransferHandler *> &handlers, DeleteMode mode)
0355 {
0356     if (!m_store) {
0357         m_store = TransferHistoryStore::getStore();
0358     }
0359     QList<Transfer *> transfers;
0360     QList<TransferHistoryItem> historyItems;
0361     foreach (TransferHandler *handler, handlers) {
0362         Transfer *transfer = handler->m_transfer;
0363         transfers << transfer;
0364         historyItems << TransferHistoryItem(*transfer);
0365 
0366         // TransferHandler deinitializations
0367         handler->destroy();
0368         // Transfer deinitializations (the deinit function is called by the destroy() function)
0369         if (mode == AutoDelete) {
0370             Transfer::DeleteOptions o = Transfer::DeleteTemporaryFiles;
0371             if (transfer->status() != Job::Finished && transfer->status() != Job::FinishedKeepAlive)
0372                 o |= Transfer::DeleteFiles;
0373             transfer->destroy(o);
0374         } else {
0375             transfer->destroy((Transfer::DeleteTemporaryFiles | Transfer::DeleteFiles));
0376         }
0377     }
0378     m_store->saveItems(historyItems);
0379 
0380     m_transferTreeModel->delTransfers(transfers);
0381     qDeleteAll(transfers);
0382     return true;
0383 }
0384 
0385 void KGet::moveTransfer(TransferHandler *transfer, const QString &groupName)
0386 {
0387     Q_UNUSED(transfer)
0388     Q_UNUSED(groupName)
0389 }
0390 
0391 void KGet::redownloadTransfer(TransferHandler *transfer)
0392 {
0393     QString group = transfer->group()->name();
0394     QUrl src = transfer->source();
0395     QString dest = transfer->dest().toLocalFile();
0396     QString destFile = transfer->dest().fileName();
0397 
0398     KGet::delTransfer(transfer);
0399     KGet::addTransfer(src, dest, destFile, group, true);
0400 }
0401 
0402 QList<TransferHandler *> KGet::selectedTransfers()
0403 {
0404     //     qCDebug(KGET_DEBUG) << "KGet::selectedTransfers";
0405 
0406     QList<TransferHandler *> selectedTransfers;
0407 
0408     QModelIndexList selectedIndexes = m_selectionModel->selectedRows();
0409     // sort the indexes as this can speed up operations like deleting etc.
0410     std::sort(selectedIndexes.begin(), selectedIndexes.end());
0411 
0412     foreach (const QModelIndex &currentIndex, selectedIndexes) {
0413         ModelItem *item = m_transferTreeModel->itemFromIndex(currentIndex);
0414         if (!item->isGroup())
0415             selectedTransfers.append(item->asTransfer()->transferHandler());
0416     }
0417 
0418     return selectedTransfers;
0419 
0420     // This is the code that was used in the old selectedTransfers function
0421     /*    QList<TransferGroup *>::const_iterator it = m_transferTreeModel->transferGroups().begin();
0422         QList<TransferGroup *>::const_iterator itEnd = m_transferTreeModel->transferGroups().end();
0423 
0424         for( ; it!=itEnd ; ++it )
0425         {
0426             TransferGroup::iterator it2 = (*it)->begin();
0427             TransferGroup::iterator it2End = (*it)->end();
0428 
0429             for( ; it2!=it2End ; ++it2 )
0430             {
0431                 Transfer * transfer = (Transfer*) *it2;
0432 
0433                 if( transfer->isSelected() )
0434                     selectedTransfers.append( transfer->handler() );
0435             }
0436         }
0437         return selectedTransfers;*/
0438 }
0439 
0440 QList<TransferHandler *> KGet::finishedTransfers()
0441 {
0442     QList<TransferHandler *> finishedTransfers;
0443 
0444     foreach (TransferHandler *transfer, allTransfers()) {
0445         if (transfer->status() == Job::Finished) {
0446             finishedTransfers << transfer;
0447         }
0448     }
0449     return finishedTransfers;
0450 }
0451 
0452 QList<TransferGroupHandler *> KGet::selectedTransferGroups()
0453 {
0454     QList<TransferGroupHandler *> selectedTransferGroups;
0455 
0456     QModelIndexList selectedIndexes = m_selectionModel->selectedRows();
0457 
0458     foreach (const QModelIndex &currentIndex, selectedIndexes) {
0459         ModelItem *item = m_transferTreeModel->itemFromIndex(currentIndex);
0460         if (item->isGroup()) {
0461             TransferGroupHandler *group = item->asGroup()->groupHandler();
0462             selectedTransferGroups.append(group);
0463         }
0464     }
0465 
0466     return selectedTransferGroups;
0467 }
0468 
0469 TransferTreeModel *KGet::model()
0470 {
0471     return m_transferTreeModel;
0472 }
0473 
0474 TransferTreeSelectionModel *KGet::selectionModel()
0475 {
0476     return m_selectionModel;
0477 }
0478 
0479 void KGet::load(QString filename) // krazy:exclude=passbyvalue
0480 {
0481     qCDebug(KGET_DEBUG) << "(" << filename << ")";
0482 
0483     if (filename.isEmpty()) {
0484         filename = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0485         // make sure that the DataLocation directory exists (earlier this used to be handled by KStandardDirs)
0486         if (!QFileInfo::exists(filename)) {
0487             QDir().mkpath(filename);
0488         }
0489         filename += QStringLiteral("/transfers.kgt");
0490     }
0491 
0492     QTemporaryFile tmpFile;
0493 
0494     QUrl url = QUrl(filename);
0495     if (url.scheme().isEmpty())
0496         url.setScheme("file");
0497     KIO::StoredTransferJob *job = KIO::storedGet(url);
0498     job->exec();
0499     if (job->data().isEmpty() || !tmpFile.open()) {
0500         qCDebug(KGET_DEBUG) << "Transferlist empty or cannot open temporary file";
0501         if (m_transferTreeModel->transferGroups().isEmpty()) // Create the default group
0502             addGroup(i18n("My Downloads"));
0503         return;
0504     }
0505     tmpFile.write(job->data());
0506     tmpFile.close();
0507 
0508     QDomDocument doc;
0509 
0510     qCDebug(KGET_DEBUG) << "file:" << tmpFile.fileName();
0511 
0512     if (doc.setContent(&tmpFile)) {
0513         QDomElement root = doc.documentElement();
0514 
0515         QDomNodeList nodeList = root.elementsByTagName("TransferGroup");
0516         int nItems = nodeList.length();
0517 
0518         for (int i = 0; i < nItems; i++) {
0519             TransferGroup *foundGroup = m_transferTreeModel->findGroup(nodeList.item(i).toElement().attribute("Name"));
0520 
0521             qCDebug(KGET_DEBUG) << "KGet::load  -> group = " << nodeList.item(i).toElement().attribute("Name");
0522 
0523             if (!foundGroup) {
0524                 qCDebug(KGET_DEBUG) << "KGet::load  -> group not found";
0525 
0526                 auto *newGroup = new TransferGroup(m_transferTreeModel, m_scheduler);
0527 
0528                 m_transferTreeModel->addGroup(newGroup);
0529 
0530                 newGroup->load(nodeList.item(i).toElement());
0531             } else {
0532                 qCDebug(KGET_DEBUG) << "KGet::load  -> group found";
0533 
0534                 // A group with this name already exists.
0535                 // Integrate the group's transfers with the ones read from file
0536                 foundGroup->load(nodeList.item(i).toElement());
0537             }
0538         }
0539     } else {
0540         qCWarning(KGET_DEBUG) << "Error reading the transfers file";
0541     }
0542 
0543     if (m_transferTreeModel->transferGroups().isEmpty()) // Create the default group
0544         addGroup(i18n("My Downloads"));
0545 
0546     new GenericObserver(m_mainWindow);
0547 }
0548 
0549 void KGet::save(QString filename, bool plain) // krazy:exclude=passbyvalue
0550 {
0551     if (!filename.isEmpty() && QFile::exists(filename)
0552         && (KMessageBox::questionTwoActions(nullptr,
0553                                             i18n("The file %1 already exists.\nOverwrite?", filename),
0554                                             i18n("Overwrite existing file?"),
0555                                             KStandardGuiItem::overwrite(),
0556                                             KStandardGuiItem::cancel(),
0557                                             "QuestionFilenameExists")
0558             == KMessageBox::SecondaryAction))
0559         return;
0560 
0561     if (filename.isEmpty()) {
0562         filename = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0563         // make sure that the DataLocation directory exists (earlier this used to be handled by KStandardDirs)
0564         if (!QFileInfo::exists(filename)) {
0565             QDir().mkpath(filename);
0566         }
0567         filename += QStringLiteral("/transfers.kgt");
0568     }
0569 
0570     qCDebug(KGET_DEBUG) << "Save transferlist to " << filename;
0571 
0572     QSaveFile file(filename);
0573     if (!file.open(QIODevice::WriteOnly)) {
0574         // qCWarning(KGET_DEBUG)<<"Unable to open output file when saving";
0575         KGet::showNotification("error", i18n("Unable to save to: %1", filename));
0576         return;
0577     }
0578 
0579     if (plain) {
0580         QTextStream out(&file);
0581         foreach (TransferHandler *handler, allTransfers()) {
0582             out << handler->source().toString() << '\n';
0583         }
0584     } else {
0585         QDomDocument doc(QString("KGetTransfers"));
0586         QDomElement root = doc.createElement("Transfers");
0587         doc.appendChild(root);
0588 
0589         foreach (TransferGroup *group, m_transferTreeModel->transferGroups()) {
0590             QDomElement e = doc.createElement("TransferGroup");
0591             root.appendChild(e);
0592             group->save(e);
0593             // KGet::delGroup((*it)->name());
0594         }
0595 
0596         QTextStream stream(&file);
0597         doc.save(stream, 2);
0598     }
0599     file.commit();
0600 }
0601 
0602 QList<TransferFactory *> KGet::factories()
0603 {
0604     return m_transferFactories;
0605 }
0606 
0607 QVector<KPluginMetaData> KGet::plugins()
0608 {
0609     return m_pluginList;
0610 }
0611 
0612 TransferFactory *KGet::factory(TransferHandler *transfer)
0613 {
0614     return transfer->m_transfer->factory();
0615 }
0616 
0617 KActionCollection *KGet::actionCollection()
0618 {
0619     return m_mainWindow->actionCollection();
0620 }
0621 
0622 void KGet::setSchedulerRunning(bool running)
0623 {
0624     if (running) {
0625         m_scheduler->stop(); // stopall first, to have a clean startingpoint
0626         m_scheduler->start();
0627     } else
0628         m_scheduler->stop();
0629 }
0630 
0631 bool KGet::schedulerRunning()
0632 {
0633     return (m_scheduler->hasRunningJobs());
0634 }
0635 
0636 void KGet::setSuspendScheduler(bool isSuspended)
0637 {
0638     m_scheduler->setIsSuspended(isSuspended);
0639 }
0640 
0641 QList<TransferHandler *> KGet::allTransfers()
0642 {
0643     QList<TransferHandler *> transfers;
0644 
0645     foreach (TransferGroup *group, KGet::m_transferTreeModel->transferGroups()) {
0646         transfers << group->handler()->transfers();
0647     }
0648     return transfers;
0649 }
0650 
0651 QList<TransferGroupHandler *> KGet::allTransferGroups()
0652 {
0653     QList<TransferGroupHandler *> transfergroups;
0654 
0655     foreach (TransferGroup *group, KGet::m_transferTreeModel->transferGroups()) {
0656         qDebug() << group->name();
0657         transfergroups << group->handler();
0658     }
0659     return transfergroups;
0660 }
0661 
0662 TransferHandler *KGet::findTransfer(const QUrl &src)
0663 {
0664     Transfer *transfer = KGet::m_transferTreeModel->findTransfer(src);
0665     if (transfer) {
0666         return transfer->handler();
0667     }
0668     return nullptr;
0669 }
0670 
0671 TransferGroupHandler *KGet::findGroup(const QString &name)
0672 {
0673     TransferGroup *group = KGet::m_transferTreeModel->findGroup(name);
0674     if (group) {
0675         return group->handler();
0676     }
0677     return nullptr;
0678 }
0679 
0680 void KGet::checkSystemTray()
0681 {
0682     qCDebug(KGET_DEBUG);
0683     bool running = false;
0684 
0685     foreach (TransferHandler *handler, KGet::allTransfers()) {
0686         if (handler->status() == Job::Running) {
0687             running = true;
0688             break;
0689         }
0690     }
0691 
0692     m_mainWindow->setSystemTrayDownloading(running);
0693 }
0694 
0695 void KGet::settingsChanged()
0696 {
0697     qCDebug(KGET_DEBUG);
0698 
0699     foreach (TransferFactory *factory, m_transferFactories) {
0700         factory->settingsChanged();
0701     }
0702 
0703     m_jobManager->settingsChanged();
0704     m_scheduler->settingsChanged();
0705     if (!m_store)
0706         m_store = TransferHistoryStore::getStore();
0707     m_store->settingsChanged();
0708 }
0709 
0710 QList<TransferGroupHandler *> KGet::groupsFromExceptions(const QUrl &filename)
0711 {
0712     QList<TransferGroupHandler *> handlers;
0713     foreach (TransferGroupHandler *handler, allTransferGroups()) {
0714         const QStringList patterns =
0715             handler->regExp().pattern().split(','); // FIXME 4.5 add a tooltip: "Enter a list of foo separated by ," and then do split(i18nc("used as separator
0716                                                     // in a list, translate to the same thing you translated \"Enter a list of foo separated by ,\"", ","))
0717         if (matchesExceptions(filename, patterns)) {
0718             handlers.append(handler);
0719         }
0720     }
0721 
0722     return handlers;
0723 }
0724 
0725 bool KGet::matchesExceptions(const QUrl &sourceUrl, const QStringList &patterns)
0726 {
0727     for (const QString &pattern : patterns) {
0728         const QString trimmedPattern = pattern.trimmed();
0729         if (trimmedPattern.isEmpty()) {
0730             continue;
0731         }
0732         QRegularExpression regExp(trimmedPattern, QRegularExpression::CaseInsensitiveOption);
0733 
0734         // try with Regular Expression first
0735         if (regExp.match(sourceUrl.url(), 0, QRegularExpression::PartialPreferCompleteMatch).hasMatch()) {
0736             return true;
0737         }
0738 
0739         // now try with wildcards
0740         if (!regExp.pattern().isEmpty() && !regExp.pattern().contains('*')) {
0741             regExp.setPattern('*' + regExp.pattern());
0742         }
0743 
0744         QRegularExpression wildcard = QRegularExpression::fromWildcard(trimmedPattern);
0745         wildcard.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0746 
0747         if (wildcard.match(sourceUrl.url(), 0, QRegularExpression::PartialPreferCompleteMatch).hasMatch()) {
0748             return true;
0749         }
0750     }
0751 
0752     return false;
0753 }
0754 
0755 void KGet::setGlobalDownloadLimit(int limit)
0756 {
0757     m_scheduler->setDownloadLimit(limit);
0758 }
0759 
0760 void KGet::setGlobalUploadLimit(int limit)
0761 {
0762     m_scheduler->setUploadLimit(limit);
0763 }
0764 
0765 void KGet::calculateGlobalSpeedLimits()
0766 {
0767     // if (m_scheduler->downloadLimit())//TODO: Remove this and the both other hacks in the 2 upper functions with a better replacement
0768     m_scheduler->calculateDownloadLimit();
0769     // if (m_scheduler->uploadLimit())
0770     m_scheduler->calculateUploadLimit();
0771 }
0772 
0773 void KGet::calculateGlobalDownloadLimit()
0774 {
0775     m_scheduler->calculateDownloadLimit();
0776 }
0777 
0778 void KGet::calculateGlobalUploadLimit()
0779 {
0780     m_scheduler->calculateUploadLimit();
0781 }
0782 
0783 // ------ STATIC MEMBERS INITIALIZATION ------
0784 TransferTreeModel *KGet::m_transferTreeModel;
0785 TransferTreeSelectionModel *KGet::m_selectionModel;
0786 QVector<KPluginMetaData> KGet::m_pluginList;
0787 QList<TransferFactory *> KGet::m_transferFactories;
0788 TransferGroupScheduler *KGet::m_scheduler = nullptr;
0789 MainWindow *KGet::m_mainWindow = nullptr;
0790 KUiServerJobs *KGet::m_jobManager = nullptr;
0791 TransferHistoryStore *KGet::m_store = nullptr;
0792 bool KGet::m_hasConnection = true;
0793 // ------ PRIVATE FUNCTIONS ------
0794 KGet::KGet()
0795 {
0796     m_scheduler = new TransferGroupScheduler();
0797     m_transferTreeModel = new TransferTreeModel(m_scheduler);
0798     m_selectionModel = new TransferTreeSelectionModel(m_transferTreeModel);
0799 
0800     QObject::connect(m_transferTreeModel,
0801                      SIGNAL(transfersAddedEvent(QList<TransferHandler *>)),
0802                      m_jobManager,
0803                      SLOT(slotTransfersAdded(QList<TransferHandler *>)));
0804     QObject::connect(m_transferTreeModel, &TransferTreeModel::transfersAboutToBeRemovedEvent, m_jobManager, &KUiServerJobs::slotTransfersAboutToBeRemoved);
0805     QObject::connect(m_transferTreeModel,
0806                      SIGNAL(transfersChangedEvent(QMap<TransferHandler *, Transfer::ChangesFlags>)),
0807                      m_jobManager,
0808                      SLOT(slotTransfersChanged(QMap<TransferHandler *, Transfer::ChangesFlags>)));
0809 
0810     // Load all the available plugins
0811     loadPlugins();
0812 }
0813 
0814 KGet::~KGet()
0815 {
0816     qDebug();
0817     delete m_transferTreeModel;
0818     delete m_jobManager; // This one must always be before the scheduler otherwise the job manager can't remove the notifications when deleting.
0819     delete m_scheduler;
0820     delete m_store;
0821 }
0822 
0823 TransferHandler *KGet::createTransfer(const QUrl &src, const QUrl &dest, const QString &groupName, bool start, const QDomElement *e)
0824 {
0825     QList<TransferHandler *> transfer = createTransfers(QList<TransferData>() << TransferData(src, dest, groupName, start, e));
0826     return (transfer.isEmpty() ? nullptr : transfer.first());
0827 }
0828 
0829 QList<TransferHandler *> KGet::createTransfers(const QList<TransferData> &dataItems)
0830 {
0831     QList<TransferHandler *> handlers;
0832     if (dataItems.isEmpty()) {
0833         return handlers;
0834     }
0835 
0836     QList<bool> start;
0837     QHash<TransferGroup *, QList<Transfer *>> groups;
0838 
0839     QStringList urlsFailed;
0840     foreach (const TransferData &data, dataItems) {
0841         qCDebug(KGET_DEBUG) << "srcUrl=" << data.src << " destUrl=" << data.dest << " group=" << data.groupName;
0842 
0843         TransferGroup *group = m_transferTreeModel->findGroup(data.groupName);
0844         if (!group) {
0845             qCDebug(KGET_DEBUG) << "KGet::createTransfer  -> group not found";
0846             group = m_transferTreeModel->transferGroups().first();
0847         }
0848 
0849         Transfer *newTransfer = nullptr;
0850         foreach (TransferFactory *factory, m_transferFactories) {
0851             qCDebug(KGET_DEBUG) << "Trying plugin   n.plugins=" << m_transferFactories.size() << factory->displayName();
0852             if ((newTransfer = factory->createTransfer(data.src, data.dest, group, m_scheduler, data.e))) {
0853                 //             qCDebug(KGET_DEBUG) << "KGet::createTransfer   ->   CREATING NEW TRANSFER ON GROUP: _" << group->name() << "_";
0854                 newTransfer->create();
0855                 newTransfer->load(data.e);
0856                 handlers << newTransfer->handler();
0857                 groups[group] << newTransfer;
0858                 start << data.start;
0859                 break;
0860             }
0861         }
0862         if (!newTransfer) {
0863             urlsFailed << data.src.url();
0864             qCWarning(KGET_DEBUG) << "Warning! No plugin found to handle" << data.src;
0865         }
0866     }
0867 
0868     // show urls that failed
0869     if (!urlsFailed.isEmpty()) {
0870         QString message = i18np("<p>The following URL cannot be downloaded, its protocol is not supported by KGet:</p>",
0871                                 "<p>The following URLs cannot be downloaded, their protocols are not supported by KGet:</p>",
0872                                 urlsFailed.count());
0873 
0874         QString content = urlsFailed.takeFirst();
0875         foreach (const QString &url, urlsFailed) {
0876             content += '\n' + url;
0877         }
0878         content = QString("<p style=\"font-size: small;\">%1</p>").arg(content);
0879 
0880         KGet::showNotification("error", message + content, "dialog-error", i18n("Protocol unsupported"));
0881     }
0882 
0883     // add the created transfers to the model and start them if specified
0884     QHash<TransferGroup *, QList<Transfer *>>::const_iterator it;
0885     QHash<TransferGroup *, QList<Transfer *>>::const_iterator itEnd = groups.constEnd();
0886     for (it = groups.constBegin(); it != itEnd; ++it) {
0887         KGet::model()->addTransfers(it.value(), it.key());
0888     }
0889     for (int i = 0; i < handlers.count(); ++i) {
0890         if (start[i]) {
0891             handlers[i]->start();
0892         }
0893     }
0894 
0895     return handlers; // TODO implement error message if it is 0, or should the addTransfers stuff do that, in case if the numer of returned items does not match
0896                      // the number of sent data?
0897 }
0898 
0899 TransferDataSource *KGet::createTransferDataSource(const QUrl &src, const QDomElement &type, QObject *parent)
0900 {
0901     qCDebug(KGET_DEBUG);
0902 
0903     TransferDataSource *dataSource;
0904     foreach (TransferFactory *factory, m_transferFactories) {
0905         dataSource = factory->createTransferDataSource(src, type, parent);
0906         if (dataSource)
0907             return dataSource;
0908     }
0909     return nullptr;
0910 }
0911 
0912 QString KGet::generalDestDir(bool preferXDGDownloadDir)
0913 {
0914     QString dir = Settings::lastDirectory();
0915 
0916     if (preferXDGDownloadDir) {
0917         dir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
0918     }
0919 
0920     return dir;
0921 }
0922 
0923 QUrl KGet::urlInputDialog()
0924 {
0925     QString newtransfer;
0926     bool ok = false;
0927 
0928     QUrl clipboardUrl = QUrl(QApplication::clipboard()->text(QClipboard::Clipboard).trimmed());
0929     if (clipboardUrl.isValid())
0930         newtransfer = clipboardUrl.url();
0931 
0932     while (!ok) {
0933         newtransfer = QInputDialog::getText(nullptr, i18n("New Download"), i18n("Enter URL:"), QLineEdit::Normal, newtransfer, &ok);
0934         newtransfer = newtransfer.trimmed(); // Remove any unnecessary space at the beginning and/or end
0935 
0936         if (!ok) {
0937             // user pressed cancel
0938             return QUrl();
0939         }
0940 
0941         QUrl src = QUrl(newtransfer);
0942         if (src.isValid())
0943             return src;
0944         else
0945             ok = false;
0946     }
0947     return QUrl();
0948 }
0949 
0950 QString KGet::destDirInputDialog()
0951 {
0952     QString destDir = QFileDialog::getExistingDirectory(nullptr,
0953                                                         i18nc("@title:window", "Choose Directory"),
0954                                                         generalDestDir(),
0955                                                         QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
0956     Settings::setLastDirectory(destDir);
0957 
0958     return destDir;
0959 }
0960 
0961 QUrl KGet::destFileInputDialog(QString destDir, const QString &suggestedFileName) // krazy:exclude=passbyvalue
0962 {
0963     if (destDir.isEmpty())
0964         destDir = generalDestDir();
0965 
0966     // Use the destination name if not empty...
0967     QUrl startLocation;
0968     if (!suggestedFileName.isEmpty()) {
0969         startLocation.setPath(destDir + suggestedFileName);
0970     } else {
0971         startLocation.setPath(destDir);
0972     }
0973 
0974     QUrl destUrl = QFileDialog::getSaveFileUrl(m_mainWindow, i18nc("@title:window", "Save As"), startLocation, QString());
0975     if (!destUrl.isEmpty()) {
0976         Settings::setLastDirectory(destUrl.adjusted(QUrl::RemoveFilename).path());
0977     }
0978 
0979     return destUrl;
0980 }
0981 
0982 bool KGet::isValidSource(const QUrl &source)
0983 {
0984     // Check if the URL is well formed
0985     if (!source.isValid()) {
0986         KGet::showNotification("error", i18n("Malformed URL:\n%1", source.toString()));
0987 
0988         return false;
0989     }
0990     // Check if the URL contains the protocol
0991     if (source.scheme().isEmpty()) {
0992         KGet::showNotification("error", i18n("Malformed URL, protocol missing:\n%1", source.toString()));
0993 
0994         return false;
0995     }
0996     // Check if a transfer with the same url already exists
0997     Transfer *transfer = m_transferTreeModel->findTransfer(source);
0998     if (transfer) {
0999         if (transfer->status() == Job::Finished) {
1000             // transfer is finished, ask if we want to download again
1001             if (KMessageBox::questionTwoActions(
1002                     nullptr,
1003                     i18n("You have already completed a download from the location: \n\n%1\n\nDownload it again?", source.toString()),
1004                     i18n("Download it again?"),
1005                     KGuiItem(i18nc("@action:button", "Download Again"), QStringLiteral("document-save")),
1006                     KGuiItem(i18nc("@action:button", "Skip"), QStringLiteral("dialog-cancel")))
1007                 == KMessageBox::PrimaryAction) {
1008                 transfer->stop();
1009                 KGet::delTransfer(transfer->handler());
1010                 return true;
1011             } else
1012                 return false;
1013         } else {
1014             if (KMessageBox::warningTwoActions(
1015                     nullptr,
1016                     i18n("You have a download in progress from the location: \n\n%1\n\nDelete it and download again?", source.toString()),
1017                     i18n("Delete it and download again?"),
1018                     KGuiItem(i18nc("@action:button", "Download Again"), QStringLiteral("document-save")),
1019                     KGuiItem(i18nc("@action:button", "Skip"), QStringLiteral("dialog-cancel")))
1020                 == KMessageBox::PrimaryAction) {
1021                 transfer->stop();
1022                 KGet::delTransfer(transfer->handler());
1023                 return true;
1024             } else
1025                 return false;
1026         }
1027         return false;
1028     }
1029     return true;
1030 }
1031 
1032 bool KGet::isValidDestDirectory(const QString &destDir)
1033 {
1034     qCDebug(KGET_DEBUG) << destDir;
1035     if (!QFileInfo(destDir).isDir()) {
1036         if (QFileInfo(QUrl(destDir).adjusted(QUrl::RemoveFilename).toString()).isWritable())
1037             return (!destDir.isEmpty());
1038         if (!QFileInfo(QUrl(destDir).adjusted(QUrl::RemoveFilename).toString()).isWritable() && !destDir.isEmpty())
1039             KMessageBox::error(nullptr, i18n("Directory is not writable"));
1040     } else {
1041         if (QFileInfo(destDir).isWritable())
1042             return (!destDir.isEmpty());
1043         if (!QFileInfo(destDir).isWritable() && !destDir.isEmpty())
1044             KMessageBox::error(nullptr, i18n("Directory is not writable"));
1045     }
1046     return false;
1047 }
1048 
1049 QUrl KGet::getValidDestUrl(const QUrl &destDir, const QUrl &srcUrl)
1050 {
1051     qDebug() << "Source Url" << srcUrl << "Destination" << destDir;
1052     if (!isValidDestDirectory(destDir.toLocalFile()))
1053         return QUrl();
1054 
1055     QUrl destUrl = destDir;
1056 
1057     if (QFileInfo(destUrl.toLocalFile()).isDir()) {
1058         QString filename = srcUrl.fileName();
1059         if (filename.isEmpty())
1060             filename = QUrl::toPercentEncoding(srcUrl.toString(), "/");
1061         destUrl = destUrl.adjusted(QUrl::RemoveFilename);
1062         destUrl.setPath(destUrl.path() + filename);
1063     }
1064 
1065     Transfer *existingTransferDest = m_transferTreeModel->findTransferByDestination(destUrl);
1066     QPointer<KIO::RenameDialog> dlg = nullptr;
1067 
1068     if (existingTransferDest) {
1069         if (existingTransferDest->status() == Job::Finished) {
1070             if (KMessageBox::questionTwoActions(nullptr,
1071                                                 i18n("You have already downloaded that file from another location.\n\nDownload and delete the previous one?"),
1072                                                 i18n("File already downloaded. Download anyway?"),
1073                                                 KGuiItem(i18nc("@action:button", "Download Again"), QStringLiteral("document-save")),
1074                                                 KGuiItem(i18nc("@action:button", "Skip"), QStringLiteral("dialog-cancel")))
1075                 == KMessageBox::PrimaryAction) {
1076                 existingTransferDest->stop();
1077                 KGet::delTransfer(existingTransferDest->handler());
1078                 // start = true;
1079             } else
1080                 return QUrl();
1081         } else {
1082             dlg = new KIO::RenameDialog(m_mainWindow,
1083                                         i18n("You are already downloading the same file" /*, destUrl.prettyUrl()*/),
1084                                         srcUrl,
1085                                         destUrl,
1086                                         KIO::RenameDialog_MultipleItems);
1087         }
1088     } else if (srcUrl == destUrl) {
1089         dlg = new KIO::RenameDialog(m_mainWindow, i18n("File already exists"), srcUrl, destUrl, KIO::RenameDialog_MultipleItems);
1090     } else if (destUrl.isLocalFile() && QFile::exists(destUrl.toLocalFile())) {
1091         dlg = new KIO::RenameDialog(m_mainWindow, i18n("File already exists"), srcUrl, destUrl, KIO::RenameDialog_Overwrite);
1092     }
1093 
1094     if (dlg) {
1095         int result = dlg->exec();
1096 
1097         if (result == KIO::Result_Rename || result == KIO::Result_Overwrite)
1098             destUrl = dlg->newDestUrl();
1099         else {
1100             delete (dlg);
1101             return QUrl();
1102         }
1103 
1104         delete (dlg);
1105     }
1106 
1107     return destUrl;
1108 }
1109 
1110 void KGet::loadPlugins()
1111 {
1112     m_transferFactories.clear();
1113     m_pluginList.clear();
1114 
1115     // TransferFactory plugins
1116     const QVector<KPluginMetaData> offers = KPluginMetaData::findPlugins(QStringLiteral("kget"), [](const KPluginMetaData &md) {
1117         return md.value(QStringLiteral("X-KDE-KGet-framework-version")) == QString::number(FrameworkVersion)
1118             && md.value(QStringLiteral("X-KDE-KGet-rank")).toInt() > 0
1119             && md.value(QStringLiteral("X-KDE-KGet-plugintype")) == QStringLiteral("TransferFactory");
1120     });
1121 
1122     qCDebug(KGET_DEBUG) << "Found" << offers.size() << "plugins";
1123 
1124     // Here we use a QMap only to easily sort the plugins by rank
1125     QMap<int, KPluginMetaData> sortedOffers;
1126 
1127     for (const KPluginMetaData &md : offers) {
1128         sortedOffers[md.value("X-KDE-KGet-rank").toInt()] = md;
1129         qCDebug(KGET_DEBUG) << " TransferFactory plugin found:\n"
1130                             << "  rank = " << md.value("X-KDE-KGet-rank").toInt() << '\n'
1131                             << "  plugintype = " << md.value("X-KDE-KGet-plugintype");
1132     }
1133 
1134     // I must fill this pluginList before and my m_transferFactories list after.
1135     // This because calling the KLibLoader::globalLibrary() erases the static
1136     // members of this class (why?), such as the m_transferFactories list.
1137     QList<KGetPlugin *> pluginList;
1138 
1139     const KConfigGroup plugins = KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("Plugins"));
1140 
1141     for (const KPluginMetaData &md : std::as_const(sortedOffers)) {
1142         m_pluginList.prepend(md);
1143         if (!plugins.readEntry(md.pluginId() + QLatin1String("Enabled"), md.isEnabledByDefault())) {
1144             qCDebug(KGET_DEBUG) << "TransferFactory plugin (" << md.fileName() << ") found, but not enabled";
1145             continue;
1146         }
1147 
1148         KGetPlugin *plugin = loadPlugin(md);
1149         if (plugin != nullptr) {
1150             const QString pluginName = md.name();
1151 
1152             pluginList.prepend(plugin);
1153             qCDebug(KGET_DEBUG) << "TransferFactory plugin (" << md.fileName() << ") found and added to the list of available plugins";
1154         } else {
1155             qCDebug(KGET_DEBUG) << "Error loading TransferFactory plugin (" << md.fileName() << ")";
1156         }
1157     }
1158 
1159     foreach (KGetPlugin *plugin, pluginList) {
1160         m_transferFactories.append(qobject_cast<TransferFactory *>(plugin));
1161     }
1162 
1163     qCDebug(KGET_DEBUG) << "Number of factories = " << m_transferFactories.size();
1164 }
1165 
1166 void KGet::setHasNetworkConnection(bool hasConnection)
1167 {
1168     qCDebug(KGET_DEBUG) << "Existing internet connection:" << hasConnection << "old:" << m_hasConnection;
1169     if (hasConnection == m_hasConnection) {
1170         return;
1171     }
1172     m_hasConnection = hasConnection;
1173     const bool initialState = m_scheduler->hasRunningJobs();
1174     m_scheduler->setHasNetworkConnection(hasConnection);
1175     const bool finalState = m_scheduler->hasRunningJobs();
1176 
1177     if (initialState != finalState) {
1178         if (hasConnection) {
1179             KGet::showNotification("notification", i18n("Internet connection established, resuming transfers."), "dialog-info");
1180 
1181         } else {
1182             KGet::showNotification("notification", i18n("No internet connection, stopping transfers."), "dialog-info");
1183         }
1184     }
1185 }
1186 
1187 KGetPlugin *KGet::loadPlugin(const KPluginMetaData &md)
1188 {
1189     const KPluginFactory::Result<TransferFactory> result = KPluginFactory::instantiatePlugin<TransferFactory>(md, KGet::m_mainWindow);
1190 
1191     if (result) {
1192         return result.plugin;
1193     } else {
1194         KGet::showNotification(u"error"_s, i18n("Plugin loader could not load the plugin %1: %2.", md.fileName(), result.errorString), "dialog-info");
1195         qCCritical(KGET_DEBUG) << "KPluginFactory could not load the plugin" << md.fileName() << result.errorText;
1196         return nullptr;
1197     }
1198 }
1199 
1200 bool KGet::safeDeleteFile(const QUrl &url)
1201 {
1202     if (url.isLocalFile()) {
1203         QFileInfo info(url.toLocalFile());
1204         if (info.isDir()) {
1205             KGet::showNotification("notification", i18n("Not deleting\n%1\nas it is a directory.", url.toString()), "dialog-info");
1206             return false;
1207         }
1208         KIO::DeleteJob *del = KIO::del(url);
1209         del->exec();
1210         return true;
1211     }
1212 
1213     else
1214         KGet::showNotification("notification", i18n("Not deleting\n%1\nas it is not a local file.", url.toString()), "dialog-info");
1215     return false;
1216 }
1217 
1218 KNotification *
1219 KGet::showNotification(const QString &eventType, const QString &text, const QString &icon, const QString &title, const KNotification::NotificationFlags &flags)
1220 {
1221     return KNotification::event(eventType, title, text, icon, flags);
1222 }
1223 
1224 GenericObserver::GenericObserver(QObject *parent)
1225     : QObject(parent)
1226     , m_save(nullptr)
1227     , m_finishAction(nullptr)
1228 {
1229     QNetworkInformation::load(QNetworkInformation::Feature::Reachability);
1230     KGet::setHasNetworkConnection(QNetworkInformation::instance()->reachability() == QNetworkInformation::Reachability::Online);
1231 
1232     connect(KGet::model(), &TransferTreeModel::groupRemovedEvent, this, &GenericObserver::groupRemovedEvent);
1233     connect(KGet::model(), SIGNAL(transfersAddedEvent(QList<TransferHandler *>)), SLOT(transfersAddedEvent(QList<TransferHandler *>)));
1234     connect(KGet::model(), &TransferTreeModel::groupAddedEvent, this, &GenericObserver::groupAddedEvent);
1235     connect(KGet::model(), &TransferTreeModel::transfersRemovedEvent, this, &GenericObserver::transfersRemovedEvent);
1236     connect(KGet::model(),
1237             SIGNAL(transfersChangedEvent(QMap<TransferHandler *, Transfer::ChangesFlags>)),
1238             SLOT(transfersChangedEvent(QMap<TransferHandler *, Transfer::ChangesFlags>)));
1239     connect(KGet::model(),
1240             SIGNAL(groupsChangedEvent(QMap<TransferGroupHandler *, TransferGroup::ChangesFlags>)),
1241             SLOT(groupsChangedEvent(QMap<TransferGroupHandler *, TransferGroup::ChangesFlags>)));
1242     connect(KGet::model(), &TransferTreeModel::transferMovedEvent, this, &GenericObserver::transferMovedEvent);
1243 
1244     connect(QNetworkInformation::instance(), &QNetworkInformation::reachabilityChanged, this, &GenericObserver::slotNetworkStatusChanged);
1245 }
1246 
1247 GenericObserver::~GenericObserver()
1248 {
1249 }
1250 
1251 void GenericObserver::groupAddedEvent(TransferGroupHandler *handler)
1252 {
1253     Q_UNUSED(handler)
1254     KGet::save();
1255 }
1256 
1257 void GenericObserver::groupRemovedEvent(TransferGroupHandler *handler)
1258 {
1259     Q_UNUSED(handler)
1260     KGet::save();
1261 }
1262 
1263 void GenericObserver::transfersAddedEvent(const QList<TransferHandler *> &handlers)
1264 {
1265     Q_UNUSED(handlers)
1266     requestSave();
1267     KGet::calculateGlobalSpeedLimits();
1268     KGet::checkSystemTray();
1269 }
1270 
1271 void GenericObserver::transfersRemovedEvent(const QList<TransferHandler *> &handlers)
1272 {
1273     Q_UNUSED(handlers)
1274     requestSave();
1275     KGet::calculateGlobalSpeedLimits();
1276     KGet::checkSystemTray();
1277 }
1278 
1279 void GenericObserver::transferMovedEvent(TransferHandler *transfer, TransferGroupHandler *group)
1280 {
1281     Q_UNUSED(transfer)
1282     Q_UNUSED(group)
1283     requestSave();
1284     KGet::calculateGlobalSpeedLimits();
1285 }
1286 
1287 void GenericObserver::requestSave()
1288 {
1289     if (!m_save) {
1290         m_save = new QTimer(this);
1291         m_save->setInterval(5000);
1292         connect(m_save, &QTimer::timeout, this, &GenericObserver::slotSave);
1293     }
1294 
1295     // save regularly if there are running jobs
1296     m_save->setSingleShot(!KGet::m_scheduler->hasRunningJobs());
1297 
1298     if (!m_save->isActive()) {
1299         m_save->start();
1300     }
1301 }
1302 
1303 void GenericObserver::slotSave()
1304 {
1305     KGet::save();
1306 }
1307 
1308 void GenericObserver::transfersChangedEvent(QMap<TransferHandler *, Transfer::ChangesFlags> transfers)
1309 {
1310     bool checkSysTray = false;
1311     bool allFinished = true;
1312     QMap<TransferHandler *, Transfer::ChangesFlags>::const_iterator it;
1313     QMap<TransferHandler *, Transfer::ChangesFlags>::const_iterator itEnd = transfers.constEnd();
1314     for (it = transfers.constBegin(); it != itEnd; ++it) {
1315         TransferHandler::ChangesFlags transferFlags = *it;
1316         TransferHandler *transfer = it.key();
1317 
1318         if (transferFlags & Transfer::Tc_Status) {
1319             if ((transfer->status() == Job::Finished) && (transfer->startStatus() != Job::Finished)) {
1320                 KGet::showNotification(
1321                     u"finished"_s,
1322                     i18n("<p>The following file has finished downloading:</p><p style=\"font-size: small;\">%1</p>", transfer->dest().fileName()),
1323                     u"kget"_s,
1324                     i18n("Download completed"));
1325             } else if (transfer->status() == Job::Running) {
1326                 KGet::showNotification(
1327                     u"started"_s,
1328                     i18n("<p>The following transfer has been started:</p><p style=\"font-size: small;\">%1</p>", transfer->source().toString()),
1329                     u"kget"_s,
1330                     i18n("Download started"));
1331             } else if (transfer->status() == Job::Aborted && transfer->error().type != Job::AutomaticRetry) {
1332                 KNotification *notification =
1333                     KNotification::event(u"error"_s,
1334                                          i18n("Error"),
1335                                          i18n("<p>There has been an error in the following transfer:</p><p style=\"font-size: small;\">%1</p>"
1336                                               "<p>The error message is:</p><p style=\"font-size: small;\">%2</p>",
1337                                               transfer->source().toString(),
1338                                               transfer->error().text),
1339                                          transfer->error().iconName,
1340                                          KNotification::CloseOnTimeout);
1341                 if (transfer->error().type == Job::ManualSolve) {
1342                     m_notifications.insert(notification, transfer);
1343                     auto resolveAction = notification->addAction(i18nc("@action:button", "Resolve"));
1344                     connect(resolveAction, &KNotificationAction::activated, this, &GenericObserver::slotResolveTransferError);
1345                     connect(notification, &KNotification::closed, this, &GenericObserver::slotNotificationClosed);
1346                 }
1347             }
1348 
1349             checkSysTray = true;
1350             requestSave();
1351         }
1352 
1353         if (transferFlags & Transfer::Tc_Percent) {
1354             transfer->group()->setGroupChange(TransferGroup::Gc_Percent, true);
1355             transfer->checkShareRatio();
1356         }
1357 
1358         if (transferFlags & Transfer::Tc_DownloadSpeed) {
1359             transfer->group()->setGroupChange(TransferGroup::Gc_DownloadSpeed, true);
1360         }
1361 
1362         if (transferFlags & Transfer::Tc_UploadSpeed) {
1363             transfer->group()->setGroupChange(TransferGroup::Gc_UploadSpeed, true);
1364         }
1365 
1366         if ((transfer->status() == Job::Finished) || (transfer->status() == Job::FinishedKeepAlive)) {
1367             requestSave();
1368         } else {
1369             allFinished = false;
1370         }
1371     }
1372     allFinished = allFinished && allTransfersFinished();
1373 
1374     if (checkSysTray)
1375         KGet::checkSystemTray();
1376 
1377     // only perform after finished actions if actually the status changed (that is the
1378     // case if checkSysTray is set to true)
1379     if (checkSysTray && Settings::afterFinishActionEnabled() && allFinished) {
1380         qCDebug(KGET_DEBUG) << "All finished";
1381         KNotification *notification = nullptr;
1382 
1383         if (!m_finishAction) {
1384             m_finishAction = new QTimer(this);
1385             m_finishAction->setSingleShot(true);
1386             m_finishAction->setInterval(10000);
1387             connect(m_finishAction, SIGNAL(timeout()), this, SLOT(slotAfterFinishAction()));
1388         }
1389 
1390         switch (Settings::afterFinishAction()) {
1391         case KGet::Quit:
1392             notification = KGet::showNotification(u"notification"_s,
1393                                                   i18n("KGet is now closing, as all downloads have completed."),
1394                                                   "kget",
1395                                                   "KGet",
1396                                                   KNotification::Persistent | KNotification::CloseWhenWindowActivated);
1397             break;
1398         case KGet::Shutdown:
1399             notification = KGet::showNotification(u"notification"_s,
1400                                                   i18n("The computer will now turn off, as all downloads have completed."),
1401                                                   "system-shutdown",
1402                                                   i18nc("Shutting down computer", "Shutdown"),
1403                                                   KNotification::Persistent | KNotification::CloseWhenWindowActivated);
1404             break;
1405         case KGet::Hibernate:
1406             notification = KGet::showNotification(u"notification"_s,
1407                                                   i18n("The computer will now suspend to disk, as all downloads have completed."),
1408                                                   "system-suspend-hibernate",
1409                                                   i18nc("Hibernating computer", "Hibernating"),
1410                                                   KNotification::Persistent | KNotification::CloseWhenWindowActivated);
1411             break;
1412         case KGet::Suspend:
1413             notification = KGet::showNotification(u"notification"_s,
1414                                                   i18n("The computer will now suspend to RAM, as all downloads have completed."),
1415                                                   "system-suspend",
1416                                                   i18nc("Suspending computer", "Suspending"),
1417                                                   KNotification::Persistent | KNotification::CloseWhenWindowActivated);
1418             break;
1419         default:
1420             break;
1421         }
1422 
1423         if (notification) {
1424             auto abortAction = notification->addAction(i18nc("abort the proposed action", "Abort"));
1425             connect(abortAction, &KNotificationAction::activated, this, &GenericObserver::slotAbortAfterFinishAction);
1426             connect(m_finishAction, &QTimer::timeout, notification, &KNotification::close);
1427 
1428             if (!m_finishAction->isActive()) {
1429                 m_finishAction->start();
1430             }
1431         }
1432     } else if (allFinished) {
1433         KGet::showNotification(u"finishedall"_s, i18n("<p>All transfers have been finished.</p>"), u"kget"_s, i18n("Downloads completed"));
1434     }
1435 }
1436 
1437 void GenericObserver::slotResolveTransferError()
1438 {
1439     auto *notification = static_cast<KNotification *>(QObject::sender());
1440     if (notification) {
1441         TransferHandler *handler = m_notifications[notification];
1442         qDebug() << "Resolve error for" << handler->source().toString() << "with id" << handler->error().id;
1443         handler->resolveError(handler->error().id);
1444         m_notifications.remove(notification);
1445     }
1446 }
1447 
1448 void GenericObserver::slotNotificationClosed()
1449 {
1450     qDebug() << "Remove notification";
1451     auto *notification = static_cast<KNotification *>(QObject::sender());
1452     if (notification)
1453         m_notifications.remove(notification);
1454 }
1455 
1456 void GenericObserver::slotNetworkStatusChanged(QNetworkInformation::Reachability reachability)
1457 {
1458     KGet::setHasNetworkConnection(reachability == QNetworkInformation::Reachability::Online);
1459 }
1460 
1461 void GenericObserver::groupsChangedEvent(QMap<TransferGroupHandler *, TransferGroup::ChangesFlags> groups)
1462 {
1463     bool recalculate = false;
1464     foreach (const TransferGroup::ChangesFlags &flags, groups) {
1465         if (flags & TransferGroup::Gc_Percent || flags & TransferGroup::Gc_Status) {
1466             recalculate = true;
1467             break;
1468         }
1469     }
1470     qDebug() << "Recalculate limits?" << recalculate;
1471     if (recalculate)
1472         KGet::calculateGlobalSpeedLimits();
1473 }
1474 
1475 bool GenericObserver::allTransfersFinished()
1476 {
1477     bool quitFlag = true;
1478 
1479     // if all the downloads had state finished from
1480     // the beginning
1481     bool allWereFinished = true;
1482 
1483     foreach (TransferGroup *transferGroup, KGet::model()->transferGroups()) {
1484         foreach (TransferHandler *transfer, transferGroup->handler()->transfers()) {
1485             if ((transfer->status() != Job::Finished) && (transfer->status() != Job::FinishedKeepAlive)) {
1486                 quitFlag = false;
1487             }
1488             if ((transfer->status() == Job::Finished || transfer->status() == Job::FinishedKeepAlive)
1489                 && (transfer->startStatus() != Job::Finished && transfer->startStatus() != Job::FinishedKeepAlive)) {
1490                 allWereFinished = false;
1491             }
1492         }
1493     }
1494 
1495     // if the only downloads in the queue
1496     // are those that are already finished
1497     // before the current KGet instance
1498     // we don't want to quit
1499     if (allWereFinished) {
1500         return false;
1501     }
1502 
1503     // otherwise, we did some downloads right now, let quitFlag decide
1504     return quitFlag;
1505 }
1506 
1507 void GenericObserver::slotAfterFinishAction()
1508 {
1509     qCDebug(KGET_DEBUG);
1510 
1511     switch (Settings::afterFinishAction()) {
1512     case KGet::Quit:
1513         qCDebug(KGET_DEBUG) << "Quit Kget.";
1514         QTimer::singleShot(0, KGet::m_mainWindow, SLOT(slotQuit()));
1515         break;
1516     case KGet::Shutdown: {
1517         QTimer::singleShot(0, KGet::m_mainWindow, SLOT(slotQuit()));
1518 
1519         org::kde::Shutdown plasmaShutdown(QStringLiteral("org.kde.PlasmaShutdown"), QStringLiteral("/org/kde/PlasmaShutdown"), QDBusConnection::sessionBus());
1520 
1521         plasmaShutdown.logoutAndShutdown();
1522         break;
1523     }
1524     case KGet::Hibernate: {
1525         QDBusMessage call;
1526         call = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.PowerManagement"),
1527                                               QStringLiteral("/org/freedesktop/PowerManagement"),
1528                                               QStringLiteral("org.freedesktop.PowerManagement"),
1529                                               QStringLiteral("Hibernate"));
1530         QDBusConnection::sessionBus().asyncCall(call);
1531         break;
1532     }
1533     case KGet::Suspend: {
1534         QDBusMessage call;
1535         call = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.PowerManagement"),
1536                                               QStringLiteral("/org/freedesktop/PowerManagement"),
1537                                               QStringLiteral("org.freedesktop.PowerManagement"),
1538                                               QStringLiteral("Suspend"));
1539         QDBusConnection::sessionBus().asyncCall(call);
1540         break;
1541     }
1542     default:
1543         break;
1544     }
1545 }
1546 
1547 void GenericObserver::slotAbortAfterFinishAction()
1548 {
1549     qCDebug(KGET_DEBUG);
1550 
1551     m_finishAction->stop();
1552 }
1553 
1554 #include "moc_kget.cpp"