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 ¤tIndex, 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 ¤tIndex, 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"