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