File indexing completed on 2023-10-01 08:39:28

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