File indexing completed on 2024-10-13 07:27:32
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 ¤tIndex, 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 ¤tIndex, 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"