1 /* This file is part of the KDE project
2 
3    Copyright (C) 2005 Dario Massarin <nekkar@libero.it>
4    Copyright (C) 2007-2009 Lukas Appelhans <l.appelhans@gmx.de>
5    Copyright (C) 2008 Urs Wolfer <uwolfer @ kde.org>
6    Copyright (C) 2008 Dario Freddi <drf54321@gmail.com>
7    Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>
8 
9    This program is free software; you can redistribute it and/or
10    modify it under the terms of the GNU General Public
11    License as published by the Free Software Foundation; either
12    version 2 of the License, or (at your option) any later version.
13 */
14 
15 #include "core/kget.h"
16 
17 #include "mainwindow.h"
18 #include "core/mostlocalurl.h"
19 #include "core/transfer.h"
20 #include "core/transferdatasource.h"
21 #include "core/transfergroup.h"
22 #include "core/transfergrouphandler.h"
23 #include "core/transfertreemodel.h"
24 #include "core/transfertreeselectionmodel.h"
25 #include "core/plugin/plugin.h"
26 #include "core/plugin/transferfactory.h"
27 #include "core/kuiserverjobs.h"
28 #include "core/transfergroupscheduler.h"
29 #include "settings.h"
30 #include "core/transferhistorystore.h"
31 
32 #include "kget_debug.h"
33 
34 #include <algorithm>
35 #include <iostream>
36 
37 #include <KMessageBox>
38 #include <KLocalizedString>
39 #include <KIconLoader>
40 #include <KActionCollection>
41 #include <KIO/RenameDialog>
42 #include <KIO/DeleteJob>
43 #include <KSharedConfig>
44 #include <KPluginInfo>
45 #include <KConfigDialog>
46 #include <KPluginMetaData>
47 
48 #include <QAbstractItemView>
49 #include <QApplication>
50 #include <QDomElement>
51 #include <QClipboard>
52 #include <QFileDialog>
53 #include <QInputDialog>
54 #include <QSaveFile>
55 #include <QStandardPaths>
56 #include <QTimer>
57 #include <QTemporaryFile>
58 #include <QTextStream>
59 
60 #ifdef HAVE_KWORKSPACE
61 #include <QDBusConnection>
62 #include <QDBusMessage>
63 #include <QDBusPendingCall>
64 #include <kworkspace.h>
65 #endif
66 
67 
TransferData(const QUrl & source,const QUrl & destination,const QString & group,bool doStart,const QDomElement * element)68 KGet::TransferData::TransferData(const QUrl &source, const QUrl &destination, const QString& group, bool doStart, const QDomElement *element)
69   : src(source),
70     dest(destination),
71     groupName(group),
72     start(doStart),
73     e(element)
74 {
75 }
76 
77 /**
78  * This is our KGet class. This is where the user's transfers and searches are
79  * stored and organized.
80  * Use this class from the views to add or remove transfers or searches
81  * In order to organize the transfers inside categories we have a TransferGroup
82  * class. By definition, a transfer must always belong to a TransferGroup. If we
83  * don't want it to be displayed by the gui inside a specific group, we will put
84  * it in the group named "Not grouped" (better name?).
85  **/
86 
self(MainWindow * mainWindow)87 KGet* KGet::self( MainWindow * mainWindow )
88 {
89     if(mainWindow)
90     {
91         m_mainWindow = mainWindow;
92         m_jobManager = new KUiServerJobs(m_mainWindow);
93     }
94 
95     static KGet *m = new KGet();
96 
97     return m;
98 }
99 
addGroup(const QString & groupName)100 bool KGet::addGroup(const QString& groupName)
101 {
102     qCDebug(KGET_DEBUG);
103 
104     // Check if a group with that name already exists
105     if (m_transferTreeModel->findGroup(groupName))
106         return false;
107 
108     auto * group = new TransferGroup(m_transferTreeModel, m_scheduler, groupName);
109     m_transferTreeModel->addGroup(group);
110 
111     return true;
112 }
113 
delGroup(TransferGroupHandler * group,bool askUser)114 void KGet::delGroup(TransferGroupHandler *group, bool askUser)
115 {
116     TransferGroup *g = group->m_group;
117 
118     if (askUser) {
119         QWidget *configDialog = KConfigDialog::exists("preferences");
120         if (KMessageBox::warningYesNo(configDialog ? configDialog : m_mainWindow,
121                         i18n("Are you sure that you want to remove the group named %1?", g->name()),
122                         i18n("Remove Group"),
123                         KStandardGuiItem::remove(), KStandardGuiItem::cancel()) != KMessageBox::Yes)
124             return;
125     }
126 
127     m_transferTreeModel->delGroup(g);
128     g->deleteLater();
129 }
130 
delGroups(QList<TransferGroupHandler * > groups,bool askUser)131 void KGet::delGroups(QList<TransferGroupHandler*> groups, bool askUser)
132 {
133     if (groups.isEmpty())
134         return;
135     if (groups.count() == 1) {
136         KGet::delGroup(groups.first(), askUser);
137         return;
138     }
139     bool del = !askUser;
140     if (askUser) {
141         QStringList names;
142         foreach (TransferGroupHandler * handler, groups)
143             names << handler->name();
144         QWidget * configDialog = KConfigDialog::exists("preferences");
145         del = KMessageBox::warningYesNoList(configDialog ? configDialog : m_mainWindow,
146               i18n("Are you sure that you want to remove the following groups?"),
147               names,
148               i18n("Remove groups"),
149               KStandardGuiItem::remove(), KStandardGuiItem::cancel()) == KMessageBox::Yes;
150     }
151     if (del) {
152         foreach (TransferGroupHandler * handler, groups)
153             KGet::delGroup(handler, false);
154     }
155 }
156 
renameGroup(const QString & oldName,const QString & newName)157 void KGet::renameGroup(const QString& oldName, const QString& newName)
158 {
159     TransferGroup *group = m_transferTreeModel->findGroup(oldName);
160 
161     if(group)
162     {
163         group->handler()->setName(newName);
164     }
165 }
166 
transferGroupNames()167 QStringList KGet::transferGroupNames()
168 {
169     QStringList names;
170 
171     foreach(TransferGroup *group, m_transferTreeModel->transferGroups()) {
172         names << group->name();
173     }
174 
175     return names;
176 }
177 
addTransfer(QUrl srcUrl,QString destDir,QString suggestedFileName,QString groupName,bool start)178 TransferHandler * KGet::addTransfer(QUrl srcUrl, QString destDir, QString suggestedFileName, // krazy:exclude=passbyvalue
179                                     QString groupName, bool start)
180 {
181     srcUrl = mostLocalUrl(srcUrl);
182     // Note: destDir may actually be a full path to a file :-(
183     qCDebug(KGET_DEBUG) << "Source:" << srcUrl.url() << ", dest: " << destDir << ", sugg file: " << suggestedFileName;
184 
185     QUrl destUrl; // the final destination, including filename
186 
187     if ( srcUrl.isEmpty() )
188     {
189         //No src location: we let the user insert it manually
190         srcUrl = urlInputDialog();
191         if( srcUrl.isEmpty() )
192             return nullptr;
193     }
194 
195     if ( !isValidSource( srcUrl ) )
196         return nullptr;
197 
198     // when we get a destination directory and suggested filename, we don't
199     // need to ask for it again
200     bool confirmDestination = false;
201     if (destDir.isEmpty())
202     {
203         confirmDestination = true;
204         QList<TransferGroupHandler*> list = groupsFromExceptions(srcUrl);
205         if (!list.isEmpty()) {
206             destDir = list.first()->defaultFolder();
207             groupName = list.first()->name();
208         }
209 
210     } else {
211         // check whether destDir is actually already the path to a file
212         QUrl targetUrl = QUrl::fromLocalFile(destDir);
213         QString directory = targetUrl.adjusted(QUrl::RemoveFilename).path();
214         QString fileName = targetUrl.fileName(QUrl::PrettyDecoded);
215         if (QFileInfo(directory).isDir() && !fileName.isEmpty()) {
216             destDir = directory;
217             suggestedFileName = fileName;
218         }
219     }
220 
221     if (suggestedFileName.isEmpty())
222     {
223         confirmDestination = true;
224         suggestedFileName = srcUrl.fileName(QUrl::PrettyDecoded);
225         if (suggestedFileName.isEmpty())
226         {
227             // simply use the full url as filename
228             suggestedFileName = QUrl::toPercentEncoding( srcUrl.toDisplayString(), "/" );
229         }
230     }
231 
232     // now ask for confirmation of the entire destination url (dir + filename)
233     if (confirmDestination || !isValidDestDirectory(destDir))
234     {
235         do
236         {
237             destUrl = destFileInputDialog(destDir, suggestedFileName);
238             if (destUrl.isEmpty())
239                 return nullptr;
240 
241             destDir = destUrl.adjusted(QUrl::RemoveFilename).path();
242         } while (!isValidDestDirectory(destDir));
243     } else {
244         destUrl = QUrl::fromLocalFile(destDir + suggestedFileName);
245     }
246     destUrl = getValidDestUrl(destUrl, srcUrl);
247 
248     if (destUrl == QUrl())
249         return nullptr;
250 
251     TransferHandler *transfer = createTransfer(srcUrl, destUrl, groupName, start);
252     if (transfer) {
253         KGet::showNotification(m_mainWindow, "added",
254                                 i18n("<p>The following transfer has been added to the download list:</p><p style=\"font-size: small;\">%1</p>", transfer->source().toString()),
255                                 "kget", i18n("Download added"));
256     }
257 
258     return transfer;
259 }
260 
addTransfers(const QList<QDomElement> & elements,const QString & groupName)261 QList<TransferHandler*> KGet::addTransfers(const QList<QDomElement> &elements, const QString &groupName)
262 {
263     QList<TransferData> data;
264 
265     foreach(const QDomElement &e, elements) {
266         //We need to read these attributes now in order to know which transfer
267         //plugin to use.
268         QUrl srcUrl = QUrl(e.attribute("Source"));
269         QUrl destUrl = QUrl(e.attribute("Dest"));
270         data << TransferData(srcUrl, destUrl, groupName, false, &e);
271 
272         qCDebug(KGET_DEBUG) << "src=" << srcUrl << " dest=" << destUrl << " group=" << groupName;
273     }
274 
275     return createTransfers(data);
276 }
277 
addTransfer(QList<QUrl> srcUrls,QString destDir,QString groupName,bool start)278 const QList<TransferHandler *> KGet::addTransfer(QList<QUrl> srcUrls, QString destDir, QString groupName, bool start)
279 {
280     QList<QUrl> urlsToDownload;
281 
282     QList<QUrl>::iterator it = srcUrls.begin();
283     QList<QUrl>::iterator itEnd = srcUrls.end();
284 
285     QList<TransferHandler *> addedTransfers;
286 
287     for(; it!=itEnd ; ++it)
288     {
289         *it = mostLocalUrl(*it);
290         if ( isValidSource( *it ) )
291             urlsToDownload.append( *it );
292     }
293 
294     if ( urlsToDownload.count() == 0 )
295         return addedTransfers;
296 
297     if ( urlsToDownload.count() == 1 )
298     {
299         // just one file -> ask for filename
300         TransferHandler * newTransfer = addTransfer(srcUrls.first(), destDir, srcUrls.first().fileName(), groupName, start);
301 
302         if (newTransfer) {
303             addedTransfers.append(newTransfer);
304         }
305 
306         return addedTransfers;
307     }
308 
309     QUrl destUrl;
310 
311     // multiple files -> ask for directory, not for every single filename
312     if (!isValidDestDirectory(destDir))//TODO: Move that after the for-loop
313         destDir = destDirInputDialog();
314 
315     it = urlsToDownload.begin();
316     itEnd = urlsToDownload.end();
317 
318     QList<TransferData> data;
319     for ( ; it != itEnd; ++it )
320     {
321         if (destDir.isEmpty())
322         {
323             //TODO only use groupsFromExceptions if that is allowed in the settings
324             QList<TransferGroupHandler*> list = groupsFromExceptions(*it);
325             if (!list.isEmpty()) {
326                 destDir = list.first()->defaultFolder();
327                 groupName = list.first()->name();
328             }
329         }
330         destUrl = getValidDestUrl(QUrl::fromLocalFile(destDir), *it);
331 
332         if (destUrl == QUrl())
333             continue;
334 
335         data << TransferData(*it, destUrl, groupName, start);
336     }
337 
338     QList<TransferHandler*> transfers = createTransfers(data);
339     if (!transfers.isEmpty()) {
340         QString urls = transfers[0]->source().toString();
341         for (int i = 1; i < transfers.count(); ++i) {
342             urls += '\n' + transfers[i]->source().toString();
343         }
344 
345         QString message;
346         if (transfers.count() == 1) {
347             message = i18n("<p>The following transfer has been added to the download list:</p>");
348         } else {
349             message = i18n("<p>The following transfers have been added to the download list:</p>");
350         }
351         const QString content = QString("<p style=\"font-size: small;\">%1</p>").arg(urls);
352         KGet::showNotification(m_mainWindow, "added", message + content, "kget", i18n("Download added"));
353     }
354 
355     return transfers;
356 }
357 
358 
delTransfer(TransferHandler * transfer,DeleteMode mode)359 bool KGet::delTransfer(TransferHandler * transfer, DeleteMode mode)
360 {
361     return delTransfers(QList<TransferHandler*>() << transfer, mode);
362 }
363 
delTransfers(const QList<TransferHandler * > & handlers,DeleteMode mode)364 bool KGet::delTransfers(const QList<TransferHandler*> &handlers, DeleteMode mode)
365 {
366     if (!m_store) {
367         m_store = TransferHistoryStore::getStore();
368     }
369     QList<Transfer*> transfers;
370     QList<TransferHistoryItem> historyItems;
371     foreach (TransferHandler *handler, handlers) {
372         Transfer *transfer = handler->m_transfer;
373         transfers << transfer;
374         historyItems << TransferHistoryItem(*transfer);
375 
376         // TransferHandler deinitializations
377         handler->destroy();
378         // Transfer deinitializations (the deinit function is called by the destroy() function)
379         if (mode == AutoDelete) {
380             Transfer::DeleteOptions o = Transfer::DeleteTemporaryFiles;
381             if (transfer->status() != Job::Finished && transfer->status() != Job::FinishedKeepAlive)
382                 o |= Transfer::DeleteFiles;
383             transfer->destroy(o);
384         } else {
385             transfer->destroy((Transfer::DeleteTemporaryFiles | Transfer::DeleteFiles));
386         }
387     }
388     m_store->saveItems(historyItems);
389 
390     m_transferTreeModel->delTransfers(transfers);
391     qDeleteAll(transfers);
392     return true;
393 }
394 
395 
moveTransfer(TransferHandler * transfer,const QString & groupName)396 void KGet::moveTransfer(TransferHandler * transfer, const QString& groupName)
397 {
398   Q_UNUSED(transfer)
399   Q_UNUSED(groupName)
400 }
401 
redownloadTransfer(TransferHandler * transfer)402 void KGet::redownloadTransfer(TransferHandler * transfer)
403 {
404      QString group = transfer->group()->name();
405      QUrl src = transfer->source();
406      QString dest = transfer->dest().toLocalFile();
407      QString destFile = transfer->dest().fileName();
408 
409      KGet::delTransfer(transfer);
410      KGet::addTransfer(src, dest, destFile, group, true);
411 }
412 
selectedTransfers()413 QList<TransferHandler *> KGet::selectedTransfers()
414 {
415 //     qCDebug(KGET_DEBUG) << "KGet::selectedTransfers";
416 
417     QList<TransferHandler *> selectedTransfers;
418 
419     QModelIndexList selectedIndexes = m_selectionModel->selectedRows();
420     //sort the indexes as this can speed up operations like deleting etc.
421     std::sort(selectedIndexes.begin(), selectedIndexes.end());
422 
423     foreach(const QModelIndex &currentIndex, selectedIndexes)
424     {
425         ModelItem * item = m_transferTreeModel->itemFromIndex(currentIndex);
426         if (!item->isGroup())
427             selectedTransfers.append(item->asTransfer()->transferHandler());
428     }
429 
430     return selectedTransfers;
431 
432 
433 // This is the code that was used in the old selectedTransfers function
434 /*    QList<TransferGroup *>::const_iterator it = m_transferTreeModel->transferGroups().begin();
435     QList<TransferGroup *>::const_iterator itEnd = m_transferTreeModel->transferGroups().end();
436 
437     for( ; it!=itEnd ; ++it )
438     {
439         TransferGroup::iterator it2 = (*it)->begin();
440         TransferGroup::iterator it2End = (*it)->end();
441 
442         for( ; it2!=it2End ; ++it2 )
443         {
444             Transfer * transfer = (Transfer*) *it2;
445 
446             if( transfer->isSelected() )
447                 selectedTransfers.append( transfer->handler() );
448         }
449     }
450     return selectedTransfers;*/
451 }
452 
finishedTransfers()453 QList<TransferHandler *> KGet::finishedTransfers()
454 {
455     QList<TransferHandler *> finishedTransfers;
456 
457     foreach(TransferHandler *transfer, allTransfers())
458     {
459         if (transfer->status() == Job::Finished) {
460             finishedTransfers << transfer;
461         }
462     }
463     return finishedTransfers;
464 }
465 
selectedTransferGroups()466 QList<TransferGroupHandler *> KGet::selectedTransferGroups()
467 {
468     QList<TransferGroupHandler *> selectedTransferGroups;
469 
470     QModelIndexList selectedIndexes = m_selectionModel->selectedRows();
471 
472     foreach(const QModelIndex &currentIndex, selectedIndexes)
473     {
474         ModelItem * item = m_transferTreeModel->itemFromIndex(currentIndex);
475         if (item->isGroup()) {
476             TransferGroupHandler *group = item->asGroup()->groupHandler();
477             selectedTransferGroups.append(group);
478         }
479     }
480 
481     return selectedTransferGroups;
482 }
483 
model()484 TransferTreeModel * KGet::model()
485 {
486     return m_transferTreeModel;
487 }
488 
selectionModel()489 TransferTreeSelectionModel * KGet::selectionModel()
490 {
491     return m_selectionModel;
492 }
493 
load(QString filename)494 void KGet::load( QString filename ) // krazy:exclude=passbyvalue
495 {
496     qCDebug(KGET_DEBUG) << "(" << filename << ")";
497 
498     if(filename.isEmpty()) {
499         filename = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
500         // make sure that the DataLocation directory exists (earlier this used to be handled by KStandardDirs)
501         if (!QFileInfo::exists(filename)) {
502             QDir().mkpath(filename);
503         }
504         filename += QStringLiteral("/transfers.kgt");
505     }
506 
507     QTemporaryFile tmpFile;
508 
509     QUrl url = QUrl(filename);
510     if (url.scheme().isEmpty())
511         url.setScheme("file");
512     KIO::StoredTransferJob * job = KIO::storedGet(url);
513     job->exec();
514     if (job->data().isEmpty() || !tmpFile.open()) {
515         qCDebug(KGET_DEBUG) << "Transferlist empty or cannot open temporary file";
516         if (m_transferTreeModel->transferGroups().isEmpty()) //Create the default group
517             addGroup(i18n("My Downloads"));
518         return;
519     }
520     tmpFile.write(job->data());
521     tmpFile.close();
522 
523     QDomDocument doc;
524 
525     qCDebug(KGET_DEBUG) << "file:" << tmpFile.fileName();
526 
527     if(doc.setContent(&tmpFile))
528     {
529         QDomElement root = doc.documentElement();
530 
531         QDomNodeList nodeList = root.elementsByTagName("TransferGroup");
532         int nItems = nodeList.length();
533 
534         for( int i = 0 ; i < nItems ; i++ )
535         {
536             TransferGroup * foundGroup = m_transferTreeModel->findGroup( nodeList.item(i).toElement().attribute("Name") );
537 
538             qCDebug(KGET_DEBUG) << "KGet::load  -> group = " << nodeList.item(i).toElement().attribute("Name");
539 
540             if( !foundGroup )
541             {
542                 qCDebug(KGET_DEBUG) << "KGet::load  -> group not found";
543 
544                 auto * newGroup = new TransferGroup(m_transferTreeModel, m_scheduler);
545 
546                 m_transferTreeModel->addGroup(newGroup);
547 
548                 newGroup->load(nodeList.item(i).toElement());
549             }
550             else
551             {
552                 qCDebug(KGET_DEBUG) << "KGet::load  -> group found";
553 
554                 //A group with this name already exists.
555                 //Integrate the group's transfers with the ones read from file
556                 foundGroup->load(nodeList.item(i).toElement());
557             }
558         }
559     }
560     else
561     {
562         qCWarning(KGET_DEBUG) << "Error reading the transfers file";
563     }
564 
565     if (m_transferTreeModel->transferGroups().isEmpty()) //Create the default group
566         addGroup(i18n("My Downloads"));
567 
568     new GenericObserver(m_mainWindow);
569 }
570 
save(QString filename,bool plain)571 void KGet::save( QString filename, bool plain ) // krazy:exclude=passbyvalue
572 {
573     if ( !filename.isEmpty()
574         && QFile::exists( filename )
575         && (KMessageBox::questionYesNoCancel(nullptr,
576                 i18n("The file %1 already exists.\nOverwrite?", filename),
577                 i18n("Overwrite existing file?"), KStandardGuiItem::yes(),
578                 KStandardGuiItem::no(), KStandardGuiItem::cancel(), "QuestionFilenameExists" )
579                 != KMessageBox::Yes) )
580         return;
581 
582     if(filename.isEmpty()) {
583         filename = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
584         // make sure that the DataLocation directory exists (earlier this used to be handled by KStandardDirs)
585         if (!QFileInfo::exists(filename)) {
586             QDir().mkpath(filename);
587         }
588         filename += QStringLiteral("/transfers.kgt");
589     }
590 
591     qCDebug(KGET_DEBUG) << "Save transferlist to " << filename;
592 
593     QSaveFile file(filename);
594     if ( !file.open( QIODevice::WriteOnly ) )
595     {
596         //qCWarning(KGET_DEBUG)<<"Unable to open output file when saving";
597         KGet::showNotification(m_mainWindow, "error",
598                                i18n("Unable to save to: %1", filename));
599         return;
600     }
601 
602     if (plain) {
603         QTextStream out(&file);
604         foreach(TransferHandler *handler, allTransfers()) {
605             out << handler->source().toString() << '\n';
606         }
607     }
608     else {
609         QDomDocument doc(QString("KGetTransfers"));
610         QDomElement root = doc.createElement("Transfers");
611         doc.appendChild(root);
612 
613         foreach (TransferGroup * group, m_transferTreeModel->transferGroups())
614         {
615             QDomElement e = doc.createElement("TransferGroup");
616             root.appendChild(e);
617             group->save(e);
618             //KGet::delGroup((*it)->name());
619         }
620 
621         QTextStream stream( &file );
622         doc.save( stream, 2 );
623     }
624     file.commit();
625 }
626 
factories()627 QList<TransferFactory*> KGet::factories()
628 {
629     return m_transferFactories;
630 }
631 
pluginInfos()632 KPluginInfo::List KGet::pluginInfos()
633 {
634     return m_pluginInfoList;
635 }
636 
factory(TransferHandler * transfer)637 TransferFactory * KGet::factory(TransferHandler * transfer)
638 {
639     return transfer->m_transfer->factory();
640 }
641 
actionCollection()642 KActionCollection * KGet::actionCollection()
643 {
644     return m_mainWindow->actionCollection();
645 }
646 
setSchedulerRunning(bool running)647 void KGet::setSchedulerRunning(bool running)
648 {
649     if(running)
650     {
651         m_scheduler->stop(); //stopall first, to have a clean startingpoint
652 	    m_scheduler->start();
653     }
654     else
655 	    m_scheduler->stop();
656 }
657 
schedulerRunning()658 bool KGet::schedulerRunning()
659 {
660     return (m_scheduler->hasRunningJobs());
661 }
662 
setSuspendScheduler(bool isSuspended)663 void KGet::setSuspendScheduler(bool isSuspended)
664 {
665     m_scheduler->setIsSuspended(isSuspended);
666 }
667 
allTransfers()668 QList<TransferHandler*> KGet::allTransfers()
669 {
670     QList<TransferHandler*> transfers;
671 
672     foreach (TransferGroup *group, KGet::m_transferTreeModel->transferGroups())
673     {
674         transfers << group->handler()->transfers();
675     }
676     return transfers;
677 }
678 
allTransferGroups()679 QList<TransferGroupHandler*> KGet::allTransferGroups()
680 {
681     QList<TransferGroupHandler*> transfergroups;
682 
683     foreach (TransferGroup *group, KGet::m_transferTreeModel->transferGroups())
684     {
685         qDebug() << group->name();
686         transfergroups << group->handler();
687     }
688     return transfergroups;
689 }
690 
findTransfer(const QUrl & src)691 TransferHandler * KGet::findTransfer(const QUrl &src)
692 {
693     Transfer *transfer = KGet::m_transferTreeModel->findTransfer(src);
694     if (transfer)
695     {
696         return transfer->handler();
697     }
698     return nullptr;
699 }
700 
findGroup(const QString & name)701 TransferGroupHandler * KGet::findGroup(const QString &name)
702 {
703     TransferGroup *group = KGet::m_transferTreeModel->findGroup(name);
704     if (group)
705     {
706         return group->handler();
707     }
708     return nullptr;
709 }
710 
checkSystemTray()711 void KGet::checkSystemTray()
712 {
713     qCDebug(KGET_DEBUG);
714     bool running = false;
715 
716     foreach (TransferHandler *handler, KGet::allTransfers())
717     {
718         if (handler->status() == Job::Running)
719         {
720             running = true;
721             break;
722         }
723     }
724 
725     m_mainWindow->setSystemTrayDownloading(running);
726 }
727 
settingsChanged()728 void KGet::settingsChanged()
729 {
730     qCDebug(KGET_DEBUG);
731 
732     foreach (TransferFactory *factory, m_transferFactories)
733     {
734         factory->settingsChanged();
735     }
736 
737     m_jobManager->settingsChanged();
738     m_scheduler->settingsChanged();
739 }
740 
groupsFromExceptions(const QUrl & filename)741 QList<TransferGroupHandler*> KGet::groupsFromExceptions(const QUrl &filename)
742 {
743     QList<TransferGroupHandler*> handlers;
744     foreach (TransferGroupHandler * handler, allTransferGroups()) {
745         const QStringList patterns = 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 in a list, translate to the same thing you translated \"Enter a list of foo separated by ,\"", ","))
746         if (matchesExceptions(filename, patterns)) {
747             handlers.append(handler);
748         }
749     }
750 
751     return handlers;
752 }
753 
matchesExceptions(const QUrl & sourceUrl,const QStringList & patterns)754 bool KGet::matchesExceptions(const QUrl &sourceUrl, const QStringList &patterns)
755 {
756     foreach (const QString &pattern, patterns) {
757         const QString trimmedPattern = pattern.trimmed();
758         if (trimmedPattern.isEmpty()) {
759             continue;
760         }
761         QRegExp regExp = QRegExp(trimmedPattern);
762 
763         //try with Regular Expression first
764         regExp.setPatternSyntax(QRegExp::RegExp2);
765         regExp.setCaseSensitivity(Qt::CaseInsensitive);
766         if (regExp.exactMatch(sourceUrl.url())) {
767             return true;
768         }
769 
770         //now try with wildcards
771         if (!regExp.pattern().isEmpty() && !regExp.pattern().contains('*')) {
772             regExp.setPattern('*' + regExp.pattern());
773         }
774 
775         regExp.setPatternSyntax(QRegExp::Wildcard);
776         regExp.setCaseSensitivity(Qt::CaseInsensitive);
777 
778         if (regExp.exactMatch(sourceUrl.url())) {
779             return true;
780         }
781     }
782 
783     return false;
784 }
785 
setGlobalDownloadLimit(int limit)786 void KGet::setGlobalDownloadLimit(int limit)
787 {
788     m_scheduler->setDownloadLimit(limit);
789 }
790 
setGlobalUploadLimit(int limit)791 void KGet::setGlobalUploadLimit(int limit)
792 {
793     m_scheduler->setUploadLimit(limit);
794 }
795 
calculateGlobalSpeedLimits()796 void KGet::calculateGlobalSpeedLimits()
797 {
798     //if (m_scheduler->downloadLimit())//TODO: Remove this and the both other hacks in the 2 upper functions with a better replacement
799         m_scheduler->calculateDownloadLimit();
800     //if (m_scheduler->uploadLimit())
801         m_scheduler->calculateUploadLimit();
802 }
803 
calculateGlobalDownloadLimit()804 void KGet::calculateGlobalDownloadLimit()
805 {
806     m_scheduler->calculateDownloadLimit();
807 }
808 
calculateGlobalUploadLimit()809 void KGet::calculateGlobalUploadLimit()
810 {
811     m_scheduler->calculateUploadLimit();
812 }
813 
814 // ------ STATIC MEMBERS INITIALIZATION ------
815 TransferTreeModel * KGet::m_transferTreeModel;
816 TransferTreeSelectionModel * KGet::m_selectionModel;
817 QList<TransferFactory *> KGet::m_transferFactories;
818 KPluginInfo::List KGet::m_pluginInfoList;
819 TransferGroupScheduler * KGet::m_scheduler = nullptr;
820 MainWindow * KGet::m_mainWindow = nullptr;
821 KUiServerJobs * KGet::m_jobManager = nullptr;
822 TransferHistoryStore * KGet::m_store = nullptr;
823 bool KGet::m_hasConnection = true;
824 // ------ PRIVATE FUNCTIONS ------
KGet()825 KGet::KGet()
826 {
827 
828     m_scheduler = new TransferGroupScheduler();
829     m_transferTreeModel = new TransferTreeModel(m_scheduler);
830     m_selectionModel = new TransferTreeSelectionModel(m_transferTreeModel);
831 
832     QObject::connect(m_transferTreeModel, SIGNAL(transfersAddedEvent(QList<TransferHandler*>)),
833                      m_jobManager,        SLOT(slotTransfersAdded(QList<TransferHandler*>)));
834     QObject::connect(m_transferTreeModel, &TransferTreeModel::transfersAboutToBeRemovedEvent,
835                      m_jobManager,        &KUiServerJobs::slotTransfersAboutToBeRemoved);
836     QObject::connect(m_transferTreeModel, SIGNAL(transfersChangedEvent(QMap<TransferHandler*,Transfer::ChangesFlags>)),
837                      m_jobManager,        SLOT(slotTransfersChanged(QMap<TransferHandler*,Transfer::ChangesFlags>)));
838 
839     //Load all the available plugins
840     loadPlugins();
841 }
842 
~KGet()843 KGet::~KGet()
844 {
845     qDebug();
846     delete m_transferTreeModel;
847     delete m_jobManager;  //This one must always be before the scheduler otherwise the job manager can't remove the notifications when deleting.
848     delete m_scheduler;
849     delete m_store;
850 
851 }
852 
createTransfer(const QUrl & src,const QUrl & dest,const QString & groupName,bool start,const QDomElement * e)853 TransferHandler * KGet::createTransfer(const QUrl &src, const QUrl &dest, const QString& groupName,
854                           bool start, const QDomElement * e)
855 {
856     QList<TransferHandler*> transfer = createTransfers(QList<TransferData>() << TransferData(src, dest, groupName, start, e));
857     return (transfer.isEmpty() ? nullptr : transfer.first());
858 }
859 
createTransfers(const QList<TransferData> & dataItems)860 QList<TransferHandler*> KGet::createTransfers(const QList<TransferData> &dataItems)
861 {
862     QList<TransferHandler*> handlers;
863     if (dataItems.isEmpty()) {
864         return handlers;
865     }
866 
867     QList<bool> start;
868     QHash<TransferGroup*, QList<Transfer*> > groups;
869 
870     QStringList urlsFailed;
871     foreach (const TransferData &data, dataItems) {
872         qCDebug(KGET_DEBUG) << "srcUrl=" << data.src << " destUrl=" << data.dest << " group=" << data.groupName;
873 
874         TransferGroup *group = m_transferTreeModel->findGroup(data.groupName);
875         if (!group) {
876             qCDebug(KGET_DEBUG) << "KGet::createTransfer  -> group not found";
877             group = m_transferTreeModel->transferGroups().first();
878         }
879 
880         Transfer *newTransfer = nullptr;
881         foreach (TransferFactory *factory, m_transferFactories) {
882             qCDebug(KGET_DEBUG) << "Trying plugin   n.plugins=" << m_transferFactories.size() << factory->displayName();
883             if ((newTransfer = factory->createTransfer(data.src, data.dest, group, m_scheduler, data.e))) {
884     //             qCDebug(KGET_DEBUG) << "KGet::createTransfer   ->   CREATING NEW TRANSFER ON GROUP: _" << group->name() << "_";
885                 newTransfer->create();
886                 newTransfer->load(data.e);
887                 handlers << newTransfer->handler();
888                 groups[group] << newTransfer;
889                 start << data.start;
890                 break;
891             }
892         }
893         if (!newTransfer) {
894             urlsFailed << data.src.url();
895             qCWarning(KGET_DEBUG) << "Warning! No plugin found to handle" << data.src;
896         }
897     }
898 
899     //show urls that failed
900     if (!urlsFailed.isEmpty()) {
901         QString message = i18np("<p>The following URL cannot be downloaded, its protocol is not supported by KGet:</p>",
902                                 "<p>The following URLs cannot be downloaded, their protocols are not supported by KGet:</p>",
903                                 urlsFailed.count());
904 
905         QString content = urlsFailed.takeFirst();
906         foreach (const QString &url, urlsFailed) {
907             content += '\n' + url;
908         }
909         content = QString("<p style=\"font-size: small;\">%1</p>").arg(content);
910 
911         KGet::showNotification(m_mainWindow, "error", message + content, "dialog-error", i18n("Protocol unsupported"));
912     }
913 
914     //add the created transfers to the model and start them if specified
915     QHash<TransferGroup*, QList<Transfer*> >::const_iterator it;
916     QHash<TransferGroup*, QList<Transfer*> >::const_iterator itEnd = groups.constEnd();
917     for (it = groups.constBegin(); it != itEnd; ++it) {
918         KGet::model()->addTransfers(it.value(), it.key());
919     }
920     for (int i = 0; i < handlers.count(); ++i) {
921         if (start[i]) {
922             handlers[i]->start();
923         }
924     }
925 
926     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 the number of sent data?
927 }
928 
createTransferDataSource(const QUrl & src,const QDomElement & type,QObject * parent)929 TransferDataSource * KGet::createTransferDataSource(const QUrl &src, const QDomElement &type, QObject *parent)
930 {
931     qCDebug(KGET_DEBUG);
932 
933     TransferDataSource *dataSource;
934     foreach (TransferFactory *factory, m_transferFactories)
935     {
936         dataSource = factory->createTransferDataSource(src, type, parent);
937         if(dataSource)
938             return dataSource;
939     }
940     return nullptr;
941 }
942 
generalDestDir(bool preferXDGDownloadDir)943 QString KGet::generalDestDir(bool preferXDGDownloadDir)
944 {
945     QString dir = Settings::lastDirectory();
946 
947     if (preferXDGDownloadDir) {
948         dir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
949     }
950 
951     return dir;
952 }
953 
urlInputDialog()954 QUrl KGet::urlInputDialog()
955 {
956     QString newtransfer;
957     bool ok = false;
958 
959     QUrl clipboardUrl = QUrl(QApplication::clipboard()->text(QClipboard::Clipboard).trimmed());
960     if (clipboardUrl.isValid())
961         newtransfer = clipboardUrl.url();
962 
963     while (!ok)
964     {
965         newtransfer = QInputDialog::getText(nullptr, i18n("New Download"), i18n("Enter URL:"), QLineEdit::Normal, newtransfer, &ok);
966         newtransfer = newtransfer.trimmed();    //Remove any unnecessary space at the beginning and/or end
967 
968         if (!ok)
969         {
970             //user pressed cancel
971             return QUrl();
972         }
973 
974         QUrl src = QUrl(newtransfer);
975         if(src.isValid())
976             return src;
977         else
978             ok = false;
979     }
980     return QUrl();
981 }
982 
destDirInputDialog()983 QString KGet::destDirInputDialog()
984 {
985     QString destDir = QFileDialog::getExistingDirectory(nullptr, i18nc("@title:window", "Choose Directory"), generalDestDir(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
986     Settings::setLastDirectory(destDir);
987 
988     return destDir;
989 }
990 
destFileInputDialog(QString destDir,const QString & suggestedFileName)991 QUrl KGet::destFileInputDialog(QString destDir, const QString& suggestedFileName) // krazy:exclude=passbyvalue
992 {
993     if (destDir.isEmpty())
994         destDir = generalDestDir();
995 
996     // Use the destination name if not empty...
997     QUrl startLocation;
998     if (!suggestedFileName.isEmpty()) {
999         startLocation.setPath(destDir + suggestedFileName);
1000     } else {
1001         startLocation.setPath(destDir);
1002     }
1003 
1004     QUrl destUrl = QFileDialog::getSaveFileUrl(m_mainWindow, i18nc("@title:window", "Save As"), startLocation, QString());
1005     if (!destUrl.isEmpty()) {
1006         Settings::setLastDirectory(destUrl.adjusted(QUrl::RemoveFilename).path());
1007     }
1008 
1009     return destUrl;
1010 }
1011 
isValidSource(const QUrl & source)1012 bool KGet::isValidSource(const QUrl &source)
1013 {
1014     // Check if the URL is well formed
1015     if (!source.isValid()) {
1016         KGet::showNotification(m_mainWindow, "error",
1017                                i18n("Malformed URL:\n%1", source.toString()));
1018 
1019         return false;
1020     }
1021     // Check if the URL contains the protocol
1022     if (source.scheme().isEmpty()){
1023         KGet::showNotification(m_mainWindow, "error",
1024                                i18n("Malformed URL, protocol missing:\n%1", source.toString()));
1025 
1026         return false;
1027     }
1028     // Check if a transfer with the same url already exists
1029     Transfer * transfer = m_transferTreeModel->findTransfer( source );
1030     if (transfer)
1031     {
1032         if (transfer->status() == Job::Finished) {
1033             // transfer is finished, ask if we want to download again
1034             if (KMessageBox::questionYesNoCancel(nullptr,
1035                     i18n("You have already completed a download from the location: \n\n%1\n\nDownload it again?", source.toString()),
1036                     i18n("Download it again?"), KStandardGuiItem::yes(),
1037                     KStandardGuiItem::no(), KStandardGuiItem::cancel())
1038                     == KMessageBox::Yes) {
1039                 transfer->stop();
1040                 KGet::delTransfer(transfer->handler());
1041                 return true;
1042             }
1043             else
1044                 return false;
1045         }
1046         else {
1047             if (KMessageBox::warningYesNoCancel(nullptr,
1048                     i18n("You have a download in progress from the location: \n\n%1\n\nDelete it and download again?", source.toString()),
1049                     i18n("Delete it and download again?"), KStandardGuiItem::yes(),
1050                     KStandardGuiItem::no(), KStandardGuiItem::cancel())
1051                     == KMessageBox::Yes) {
1052                 transfer->stop();
1053                 KGet::delTransfer(transfer->handler());
1054                 return true;
1055             }
1056             else
1057                 return false;
1058         }
1059         return false;
1060     }
1061     return true;
1062 }
1063 
isValidDestDirectory(const QString & destDir)1064 bool KGet::isValidDestDirectory(const QString & destDir)
1065 {
1066     qCDebug(KGET_DEBUG) << destDir;
1067     if (!QFileInfo(destDir).isDir())
1068     {
1069         if (QFileInfo(QUrl(destDir).adjusted(QUrl::RemoveFilename).toString()).isWritable())
1070             return (!destDir.isEmpty());
1071         if (!QFileInfo(QUrl(destDir).adjusted(QUrl::RemoveFilename).toString()).isWritable() && !destDir.isEmpty())
1072             KMessageBox::error(nullptr, i18n("Directory is not writable"));
1073     }
1074     else
1075     {
1076         if (QFileInfo(destDir).isWritable())
1077             return (!destDir.isEmpty());
1078         if (!QFileInfo(destDir).isWritable() && !destDir.isEmpty())
1079             KMessageBox::error(nullptr, i18n("Directory is not writable"));
1080     }
1081     return false;
1082 }
1083 
getValidDestUrl(const QUrl & destDir,const QUrl & srcUrl)1084 QUrl KGet::getValidDestUrl(const QUrl& destDir, const QUrl &srcUrl)
1085 {
1086     qDebug() << "Source Url" << srcUrl << "Destination" << destDir;
1087     if ( !isValidDestDirectory(destDir.toLocalFile()) )
1088         return QUrl();
1089 
1090     QUrl destUrl = destDir;
1091 
1092     if (QFileInfo(destUrl.toLocalFile()).isDir())
1093     {
1094         QString filename = srcUrl.fileName();
1095         if (filename.isEmpty())
1096             filename = QUrl::toPercentEncoding( srcUrl.toString(), "/" );
1097         destUrl = destUrl.adjusted( QUrl::RemoveFilename );
1098         destUrl.setPath(destUrl.path() + filename);
1099     }
1100 
1101     Transfer * existingTransferDest = m_transferTreeModel->findTransferByDestination(destUrl);
1102     QPointer<KIO::RenameDialog> dlg = nullptr;
1103 
1104     if (existingTransferDest) {
1105         if (existingTransferDest->status() == Job::Finished) {
1106             if (KMessageBox::questionYesNoCancel(nullptr,
1107                     i18n("You have already downloaded that file from another location.\n\nDownload and delete the previous one?"),
1108                     i18n("File already downloaded. Download anyway?"), KStandardGuiItem::yes(),
1109                     KStandardGuiItem::no(), KStandardGuiItem::cancel())
1110                     == KMessageBox::Yes) {
1111                 existingTransferDest->stop();
1112                 KGet::delTransfer(existingTransferDest->handler());
1113                 //start = true;
1114             } else
1115                 return QUrl();
1116         } else {
1117             dlg = new KIO::RenameDialog( m_mainWindow, i18n("You are already downloading the same file"/*, destUrl.prettyUrl()*/), srcUrl,
1118                                      destUrl, KIO::RenameDialog_MultipleItems );
1119         }
1120     } else if (srcUrl == destUrl) {
1121         dlg = new KIO::RenameDialog(m_mainWindow, i18n("File already exists"), srcUrl,
1122                                     destUrl, KIO::RenameDialog_MultipleItems);
1123     } else if (destUrl.isLocalFile() && QFile::exists(destUrl.toLocalFile())) {
1124         dlg = new KIO::RenameDialog( m_mainWindow, i18n("File already exists"), srcUrl,
1125                                      destUrl, KIO::RenameDialog_Overwrite );
1126     }
1127 
1128     if (dlg) {
1129         int result = dlg->exec();
1130 
1131         if (result == KIO::Result_Rename || result == KIO::Result_Overwrite)
1132             destUrl = dlg->newDestUrl();
1133         else {
1134             delete(dlg);
1135             return QUrl();
1136         }
1137 
1138         delete(dlg);
1139     }
1140 
1141     return destUrl;
1142 }
1143 
loadPlugins()1144 void KGet::loadPlugins()
1145 {
1146     m_transferFactories.clear();
1147     m_pluginInfoList.clear();
1148 
1149     // TransferFactory plugins
1150     const QVector<KPluginMetaData> offers = KPluginLoader::findPlugins(QStringLiteral("kget"), [](const KPluginMetaData& md) {
1151         return md.value(QStringLiteral("X-KDE-KGet-framework-version")) == QString::number(FrameworkVersion) &&
1152             md.value(QStringLiteral("X-KDE-KGet-rank")).toInt() > 0 &&
1153             md.value(QStringLiteral("X-KDE-KGet-plugintype")) == QStringLiteral("TransferFactory");
1154     });
1155 
1156     qCDebug(KGET_DEBUG) << "Found" << offers.size() << "plugins";
1157 
1158     //Here we use a QMap only to easily sort the plugins by rank
1159     QMap<int, KPluginMetaData> sortedOffers;
1160 
1161     for (const KPluginMetaData& md : offers)
1162     {
1163         sortedOffers[md.value("X-KDE-KGet-rank").toInt()] = md;
1164         qCDebug(KGET_DEBUG) << " TransferFactory plugin found:\n"<<
1165          "  rank = " << md.value("X-KDE-KGet-rank").toInt() << '\n' <<
1166          "  plugintype = " << md.value("X-KDE-KGet-plugintype");
1167     }
1168 
1169     //I must fill this pluginList before and my m_transferFactories list after.
1170     //This because calling the KLibLoader::globalLibrary() erases the static
1171     //members of this class (why?), such as the m_transferFactories list.
1172     QList<KGetPlugin *> pluginList;
1173 
1174     const KConfigGroup plugins = KConfigGroup(KSharedConfig::openConfig(), "Plugins");
1175 
1176     for (const KPluginMetaData& md : qAsConst(sortedOffers))
1177     {
1178         KPluginInfo info(md);
1179         info.load(plugins);
1180         m_pluginInfoList.prepend(info);
1181 
1182         if (!info.isPluginEnabled())
1183         {
1184             qCDebug(KGET_DEBUG) << "TransferFactory plugin (" << md.fileName()
1185                              << ") found, but not enabled";
1186             continue;
1187         }
1188 
1189         KGetPlugin* plugin = loadPlugin(md);
1190         if (plugin != nullptr)
1191         {
1192             const QString pluginName = info.name();
1193 
1194             pluginList.prepend(plugin);
1195             qCDebug(KGET_DEBUG) << "TransferFactory plugin (" << md.fileName()
1196                          << ") found and added to the list of available plugins";
1197         }
1198         else
1199         {
1200             qCDebug(KGET_DEBUG) << "Error loading TransferFactory plugin ("
1201                          << md.fileName() << ")";
1202         }
1203     }
1204 
1205     foreach (KGetPlugin* plugin, pluginList)
1206     {
1207         m_transferFactories.append(qobject_cast<TransferFactory*>(plugin));
1208     }
1209 
1210     qCDebug(KGET_DEBUG) << "Number of factories = " << m_transferFactories.size();
1211 }
1212 
1213 
setHasNetworkConnection(bool hasConnection)1214 void KGet::setHasNetworkConnection(bool hasConnection)
1215 {
1216     qCDebug(KGET_DEBUG) << "Existing internet connection:" << hasConnection << "old:" << m_hasConnection;
1217     if (hasConnection == m_hasConnection) {
1218         return;
1219     }
1220     m_hasConnection = hasConnection;
1221     const bool initialState = m_scheduler->hasRunningJobs();
1222     m_scheduler->setHasNetworkConnection(hasConnection);
1223     const bool finalState = m_scheduler->hasRunningJobs();
1224 
1225     if (initialState != finalState) {
1226         if (hasConnection) {
1227             KGet::showNotification(m_mainWindow, "notification",
1228                                    i18n("Internet connection established, resuming transfers."),
1229                                    "dialog-info");
1230 
1231         } else {
1232             KGet::showNotification(m_mainWindow, "notification",
1233                                    i18n("No internet connection, stopping transfers."),
1234                                    "dialog-info");
1235         }
1236     }
1237 }
1238 
loadPlugin(const KPluginMetaData & md)1239 KGetPlugin* KGet::loadPlugin(const KPluginMetaData& md)
1240 {
1241     KPluginFactory* factory = KPluginLoader(md.fileName()).factory();
1242     if (factory) {
1243         return factory->create<TransferFactory>(KGet::m_mainWindow);
1244     } else {
1245         KGet::showNotification(m_mainWindow, "error",
1246                                i18n("Plugin loader could not load the plugin: %1.", md.fileName()),
1247                                "dialog-info");
1248         qCCritical(KGET_DEBUG) << "KPluginFactory could not load the plugin:" << md.fileName();
1249         return nullptr;
1250     }
1251 }
1252 
safeDeleteFile(const QUrl & url)1253 bool KGet::safeDeleteFile( const QUrl& url )
1254 {
1255     if ( url.isLocalFile() )
1256     {
1257         QFileInfo info( url.toLocalFile() );
1258         if ( info.isDir() )
1259         {
1260             KGet::showNotification(m_mainWindow, "notification",
1261                                    i18n("Not deleting\n%1\nas it is a directory.", url.toString()),
1262                                    "dialog-info");
1263             return false;
1264         }
1265         KIO::DeleteJob * del = KIO::del(url);
1266         del->exec();
1267         return true;
1268     }
1269 
1270     else
1271         KGet::showNotification(m_mainWindow, "notification",
1272                                i18n("Not deleting\n%1\nas it is not a local file.", url.toString()),
1273                                "dialog-info");
1274     return false;
1275 }
1276 
showNotification(QWidget * parent,const QString & eventType,const QString & text,const QString & icon,const QString & title,const KNotification::NotificationFlags & flags)1277 KNotification *KGet::showNotification(QWidget *parent, const QString &eventType,
1278                             const QString &text, const QString &icon, const QString &title, const KNotification::NotificationFlags &flags)
1279 {
1280     return KNotification::event(eventType, title, text, QIcon::fromTheme(icon).pixmap(KIconLoader::SizeMedium), parent, flags);
1281 }
1282 
GenericObserver(QObject * parent)1283 GenericObserver::GenericObserver(QObject *parent)
1284   : QObject(parent),
1285     m_save(nullptr),
1286     m_finishAction(nullptr)
1287 {
1288     //check if there is a connection
1289     KGet::setHasNetworkConnection(m_networkConfig.isOnline());
1290 
1291     connect(KGet::model(), &TransferTreeModel::groupRemovedEvent, this, &GenericObserver::groupRemovedEvent);
1292     connect(KGet::model(), SIGNAL(transfersAddedEvent(QList<TransferHandler*>)),
1293                            SLOT(transfersAddedEvent(QList<TransferHandler*>)));
1294     connect(KGet::model(), &TransferTreeModel::groupAddedEvent, this, &GenericObserver::groupAddedEvent);
1295     connect(KGet::model(), &TransferTreeModel::transfersRemovedEvent,
1296                            this, &GenericObserver::transfersRemovedEvent);
1297     connect(KGet::model(), SIGNAL(transfersChangedEvent(QMap<TransferHandler*,Transfer::ChangesFlags>)),
1298                            SLOT(transfersChangedEvent(QMap<TransferHandler*,Transfer::ChangesFlags>)));
1299     connect(KGet::model(), SIGNAL(groupsChangedEvent(QMap<TransferGroupHandler*,TransferGroup::ChangesFlags>)),
1300                            SLOT(groupsChangedEvent(QMap<TransferGroupHandler*,TransferGroup::ChangesFlags>)));
1301     connect(KGet::model(), &TransferTreeModel::transferMovedEvent,
1302                            this, &GenericObserver::transferMovedEvent);
1303     connect(&m_networkConfig, &QNetworkConfigurationManager::onlineStateChanged,
1304                          this, &GenericObserver::slotNetworkStatusChanged);
1305 
1306 }
1307 
~GenericObserver()1308 GenericObserver::~GenericObserver()
1309 {
1310 }
1311 
groupAddedEvent(TransferGroupHandler * handler)1312 void GenericObserver::groupAddedEvent(TransferGroupHandler *handler)
1313 {
1314     Q_UNUSED(handler)
1315     KGet::save();
1316 }
1317 
groupRemovedEvent(TransferGroupHandler * handler)1318 void GenericObserver::groupRemovedEvent(TransferGroupHandler *handler)
1319 {
1320     Q_UNUSED(handler)
1321     KGet::save();
1322 }
1323 
transfersAddedEvent(const QList<TransferHandler * > & handlers)1324 void GenericObserver::transfersAddedEvent(const QList<TransferHandler*> &handlers)
1325 {
1326     Q_UNUSED(handlers)
1327     requestSave();
1328     KGet::calculateGlobalSpeedLimits();
1329     KGet::checkSystemTray();
1330 }
1331 
transfersRemovedEvent(const QList<TransferHandler * > & handlers)1332 void GenericObserver::transfersRemovedEvent(const QList<TransferHandler*> &handlers)
1333 {
1334     Q_UNUSED(handlers)
1335     requestSave();
1336     KGet::calculateGlobalSpeedLimits();
1337     KGet::checkSystemTray();
1338 }
1339 
transferMovedEvent(TransferHandler * transfer,TransferGroupHandler * group)1340 void GenericObserver::transferMovedEvent(TransferHandler *transfer, TransferGroupHandler *group)
1341 {
1342     Q_UNUSED(transfer)
1343     Q_UNUSED(group)
1344     requestSave();
1345     KGet::calculateGlobalSpeedLimits();
1346 }
1347 
requestSave()1348 void GenericObserver::requestSave()
1349 {
1350     if (!m_save) {
1351         m_save = new QTimer(this);
1352         m_save->setInterval(5000);
1353         connect(m_save, &QTimer::timeout, this, &GenericObserver::slotSave);
1354     }
1355 
1356     //save regularly if there are running jobs
1357     m_save->setSingleShot(!KGet::m_scheduler->hasRunningJobs());
1358 
1359     if (!m_save->isActive()) {
1360         m_save->start();
1361     }
1362 }
1363 
slotSave()1364 void GenericObserver::slotSave()
1365 {
1366     KGet::save();
1367 }
1368 
transfersChangedEvent(QMap<TransferHandler *,Transfer::ChangesFlags> transfers)1369 void GenericObserver::transfersChangedEvent(QMap<TransferHandler*, Transfer::ChangesFlags> transfers)
1370 {
1371     bool checkSysTray = false;
1372     bool allFinished = true;
1373     QMap<TransferHandler*, Transfer::ChangesFlags>::const_iterator it;
1374     QMap<TransferHandler*, Transfer::ChangesFlags>::const_iterator itEnd = transfers.constEnd();
1375     for (it = transfers.constBegin(); it != itEnd; ++it)
1376     {
1377         TransferHandler::ChangesFlags transferFlags = *it;
1378         TransferHandler *transfer = it.key();
1379 
1380         if (transferFlags & Transfer::Tc_Status) {
1381             if ((transfer->status() == Job::Finished)   && (transfer->startStatus() != Job::Finished)) {
1382                 KGet::showNotification(KGet::m_mainWindow, "finished",
1383                                        i18n("<p>The following file has finished downloading:</p><p style=\"font-size: small;\">%1</p>", transfer->dest().fileName()),
1384                                        "kget", i18n("Download completed"));
1385             } else if (transfer->status() == Job::Running) {
1386                 KGet::showNotification(KGet::m_mainWindow, "started",
1387                                        i18n("<p>The following transfer has been started:</p><p style=\"font-size: small;\">%1</p>", transfer->source().toString()),
1388                                        "kget", i18n("Download started"));
1389             } else if (transfer->status() == Job::Aborted && transfer->error().type != Job::AutomaticRetry) {
1390                 KNotification * notification = KNotification::event("error", i18n("Error"), i18n("<p>There has been an error in the following transfer:</p><p style=\"font-size: small;\">%1</p>"
1391                                             "<p>The error message is:</p><p style=\"font-size: small;\">%2</p>", transfer->source().toString(), transfer->error().text),
1392                                              transfer->error().pixmap, KGet::m_mainWindow, KNotification::CloseOnTimeout);
1393                 if (transfer->error().type == Job::ManualSolve) {
1394                     m_notifications.insert(notification, transfer);
1395                     notification->setActions(QStringList() << i18n("Resolve"));
1396                     connect(notification, &KNotification::action1Activated, this, &GenericObserver::slotResolveTransferError);
1397                     connect(notification, &KNotification::closed, this, &GenericObserver::slotNotificationClosed);
1398                 }
1399             }
1400         }
1401 
1402         if (transferFlags & Transfer::Tc_Status) {
1403             checkSysTray = true;
1404             requestSave();
1405         }
1406 
1407         if (transferFlags & Transfer::Tc_Percent) {
1408             transfer->group()->setGroupChange(TransferGroup::Gc_Percent, true);
1409             transfer->checkShareRatio();
1410         }
1411 
1412         if (transferFlags & Transfer::Tc_DownloadSpeed) {
1413             transfer->group()->setGroupChange(TransferGroup::Gc_DownloadSpeed, true);
1414         }
1415 
1416         if (transferFlags & Transfer::Tc_UploadSpeed) {
1417             transfer->group()->setGroupChange(TransferGroup::Gc_UploadSpeed, true);
1418         }
1419 
1420         if ((transfer->status() == Job::Finished) || (transfer->status() == Job::FinishedKeepAlive)) {
1421             requestSave();
1422         } else {
1423             allFinished = false;
1424         }
1425     }
1426     allFinished = allFinished && allTransfersFinished();
1427 
1428     if (checkSysTray)
1429         KGet::checkSystemTray();
1430 
1431     //only perform after finished actions if actually the status changed (that is the
1432     //case if checkSysTray is set to true)
1433     if (checkSysTray && Settings::afterFinishActionEnabled() && allFinished)
1434     {
1435         qCDebug(KGET_DEBUG) << "All finished";
1436         KNotification *notification = nullptr;
1437 
1438         if (!m_finishAction) {
1439             m_finishAction = new QTimer(this);
1440             m_finishAction->setSingleShot(true);
1441             m_finishAction->setInterval(10000);
1442             connect(m_finishAction, SIGNAL(timeout()), this, SLOT(slotAfterFinishAction()));
1443         }
1444 
1445         switch (Settings::afterFinishAction()) {
1446             case KGet::Quit:
1447                 notification = KGet::showNotification(KGet::m_mainWindow, "notification", i18n("KGet is now closing, as all downloads have completed."), "kget", "KGet", KNotification::Persistent | KNotification::CloseWhenWidgetActivated);
1448                 break;
1449 #ifdef HAVE_KWORKSPACE
1450             case KGet::Shutdown:
1451                 notification = KGet::showNotification(KGet::m_mainWindow, "notification", i18n("The computer will now turn off, as all downloads have completed."), "system-shutdown", i18nc("Shutting down computer", "Shutdown"), KNotification::Persistent | KNotification::CloseWhenWidgetActivated);
1452                 break;
1453             case KGet::Hibernate:
1454                 notification = KGet::showNotification(KGet::m_mainWindow, "notification", i18n("The computer will now suspend to disk, as all downloads have completed."), "system-suspend-hibernate", i18nc("Hibernating computer", "Hibernating"), KNotification::Persistent | KNotification::CloseWhenWidgetActivated);
1455                 break;
1456             case KGet::Suspend:
1457                 notification = KGet::showNotification(KGet::m_mainWindow, "notification", i18n("The computer will now suspend to RAM, as all downloads have completed."), "system-suspend", i18nc("Suspending computer", "Suspending"), KNotification::Persistent | KNotification::CloseWhenWidgetActivated);
1458                 break;
1459 #endif
1460             default:
1461                 break;
1462         }
1463 
1464         if (notification) {
1465             notification->setActions(QStringList() << i18nc("abort the proposed action", "Abort"));
1466             connect(notification, &KNotification::action1Activated, this, &GenericObserver::slotAbortAfterFinishAction);
1467             connect(m_finishAction, &QTimer::timeout, notification, &KNotification::close);
1468 
1469             if (!m_finishAction->isActive()) {
1470                 m_finishAction->start();
1471             }
1472         }
1473     } else if (allFinished) {
1474         KGet::showNotification(KGet::m_mainWindow, "finishedall",
1475                                i18n("<p>All transfers have been finished.</p>"),
1476                                "kget", i18n("Downloads completed"));
1477     }
1478 }
1479 
slotResolveTransferError()1480 void GenericObserver::slotResolveTransferError()
1481 {
1482     auto * notification = static_cast<KNotification*>(QObject::sender());
1483     if (notification) {
1484         TransferHandler * handler = m_notifications[notification];
1485         qDebug() << "Resolve error for" << handler->source().toString() << "with id" << handler->error().id;
1486         handler->resolveError(handler->error().id);
1487         m_notifications.remove(notification);
1488     }
1489 }
1490 
slotNotificationClosed()1491 void GenericObserver::slotNotificationClosed()
1492 {
1493     qDebug() << "Remove notification";
1494     auto * notification = static_cast<KNotification*>(QObject::sender());
1495     if (notification)
1496         m_notifications.remove(notification);
1497 }
1498 
slotNetworkStatusChanged(bool online)1499 void GenericObserver::slotNetworkStatusChanged(bool online)
1500 {
1501     KGet::setHasNetworkConnection(online);
1502 }
1503 
groupsChangedEvent(QMap<TransferGroupHandler *,TransferGroup::ChangesFlags> groups)1504 void GenericObserver::groupsChangedEvent(QMap<TransferGroupHandler*, TransferGroup::ChangesFlags> groups)
1505 {
1506     bool recalculate = false;
1507     foreach (const TransferGroup::ChangesFlags &flags, groups)
1508     {
1509         if (flags & TransferGroup::Gc_Percent || flags & TransferGroup::Gc_Status) {
1510             recalculate = true;
1511             break;
1512         }
1513     }
1514     qDebug() << "Recalculate limits?" << recalculate;
1515     if (recalculate)
1516         KGet::calculateGlobalSpeedLimits();
1517 }
1518 
allTransfersFinished()1519 bool GenericObserver::allTransfersFinished()
1520 {
1521     bool quitFlag = true;
1522 
1523     // if all the downloads had state finished from
1524     // the beginning
1525     bool allWereFinished = true;
1526 
1527     foreach(TransferGroup *transferGroup, KGet::model()->transferGroups()) {
1528         foreach(TransferHandler *transfer, transferGroup->handler()->transfers()) {
1529             if ((transfer->status() != Job::Finished) && (transfer->status() != Job::FinishedKeepAlive)) {
1530                 quitFlag = false;
1531             }
1532             if ((transfer->status() == Job::Finished || transfer->status() == Job::FinishedKeepAlive) &&
1533                 (transfer->startStatus() != Job::Finished && transfer->startStatus() != Job::FinishedKeepAlive)) {
1534                 allWereFinished = false;
1535             }
1536         }
1537     }
1538 
1539     // if the only downloads in the queue
1540     // are those that are already finished
1541     // before the current KGet instance
1542     // we don't want to quit
1543     if (allWereFinished)
1544     {
1545         return false;
1546     }
1547 
1548     // otherwise, we did some downloads right now, let quitFlag decide
1549     return quitFlag;
1550 }
1551 
slotAfterFinishAction()1552 void GenericObserver::slotAfterFinishAction()
1553 {
1554     qCDebug(KGET_DEBUG);
1555 
1556     switch (Settings::afterFinishAction()) {
1557         case KGet::Quit:
1558             qCDebug(KGET_DEBUG) << "Quit Kget.";
1559             QTimer::singleShot(0, KGet::m_mainWindow, SLOT(slotQuit()));
1560             break;
1561     #ifdef HAVE_KWORKSPACE
1562         case KGet::Shutdown:
1563             QTimer::singleShot(0, KGet::m_mainWindow, SLOT(slotQuit()));
1564             KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmNo,
1565                         KWorkSpace::ShutdownTypeHalt,
1566                         KWorkSpace::ShutdownModeForceNow);
1567             break;
1568         case KGet::Hibernate: {
1569             QDBusMessage call;
1570             call = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.PowerManagement"),
1571                                                   QStringLiteral("/org/freedesktop/PowerManagement"),
1572                                                   QStringLiteral("org.freedesktop.PowerManagement"),
1573                                                   QStringLiteral("Hibernate"));
1574             QDBusConnection::sessionBus().asyncCall(call);
1575             break;
1576         }
1577         case KGet::Suspend: {
1578             QDBusMessage call;
1579             call = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.PowerManagement"),
1580                                                   QStringLiteral("/org/freedesktop/PowerManagement"),
1581                                                   QStringLiteral("org.freedesktop.PowerManagement"),
1582                                                   QStringLiteral("Suspend"));
1583             QDBusConnection::sessionBus().asyncCall(call);
1584             break;
1585         }
1586     #endif
1587         default:
1588             break;
1589     }
1590 }
1591 
slotAbortAfterFinishAction()1592 void GenericObserver::slotAbortAfterFinishAction()
1593 {
1594     qCDebug(KGET_DEBUG);
1595 
1596     m_finishAction->stop();
1597 }
1598 
1599 
1600