1 /***************************************************************************
2     This file contains private helper classes for the Smb4KSynchronizer
3     class.
4                              -------------------
5     begin                : Fr Okt 24 2008
6     copyright            : (C) 2008-2019 by Alexander Reinholdt
7     email                : alexander.reinholdt@kdemail.net
8  ***************************************************************************/
9 
10 /***************************************************************************
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  *   This program is distributed in the hope that it will be useful, but   *
17  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
19  *   General Public License for more details.                              *
20  *                                                                         *
21  *   You should have received a copy of the GNU General Public License     *
22  *   along with this program; if not, write to the                         *
23  *   Free Software Foundation, Inc., 51 Franklin Street, Suite 500, Boston,*
24  *   MA 02110-1335, USA                                                    *
25  ***************************************************************************/
26 
27 // application specific includes
28 #include "smb4ksynchronizer_p.h"
29 #include "smb4knotification.h"
30 #include "smb4ksettings.h"
31 #include "smb4kglobal.h"
32 #include "smb4kshare.h"
33 
34 // Qt includes
35 #include <QTimer>
36 #include <QPointer>
37 #include <QStandardPaths>
38 #include <QGridLayout>
39 #include <QLabel>
40 #include <QDialogButtonBox>
41 #include <QWindow>
42 #include <QApplication>
43 
44 // KDE includes
45 #define TRANSLATION_DOMAIN "smb4k-core"
46 #include <KCompletion/KLineEdit>
47 #include <KIOWidgets/KUrlCompletion>
48 #include <KI18n/KLocalizedString>
49 #include <KIconThemes/KIconLoader>
50 #include <KConfigGui/KWindowConfig>
51 
52 using namespace Smb4KGlobal;
53 
54 
Smb4KSyncJob(QObject * parent)55 Smb4KSyncJob::Smb4KSyncJob(QObject *parent)
56 : KJob(parent), m_share(0), m_process(0)
57 {
58   setCapabilities(KJob::Killable);
59   m_job_tracker = new KUiServerJobTracker(this);
60 }
61 
62 
~Smb4KSyncJob()63 Smb4KSyncJob::~Smb4KSyncJob()
64 {
65 }
66 
67 
start()68 void Smb4KSyncJob::start()
69 {
70   QTimer::singleShot(0, this, SLOT(slotStartSynchronization()));
71 }
72 
73 
setupSynchronization(const SharePtr & share)74 void Smb4KSyncJob::setupSynchronization(const SharePtr &share)
75 {
76   if (share)
77   {
78     m_share = share;
79   }
80 }
81 
82 
doKill()83 bool Smb4KSyncJob::doKill()
84 {
85   if (m_process && m_process->state() != KProcess::NotRunning)
86   {
87     m_process->terminate();
88   }
89 
90   return KJob::doKill();
91 }
92 
93 
slotStartSynchronization()94 void Smb4KSyncJob::slotStartSynchronization()
95 {
96   //
97   // Find the shell command
98   //
99   QString rsync = QStandardPaths::findExecutable("rsync");
100 
101   if (rsync.isEmpty())
102   {
103     Smb4KNotification::commandNotFound("rsync");
104     emitResult();
105     return;
106   }
107   else
108   {
109     // Go ahead
110   }
111 
112   //
113   // The synchronization dialog
114   //
115   if (m_share)
116   {
117     // Show the user an URL input dialog.
118     QPointer<Smb4KSynchronizationDialog> dlg = new Smb4KSynchronizationDialog(m_share, QApplication::activeWindow());
119 
120     if (dlg->exec() == QDialog::Accepted)
121     {
122       // Create the destination directory if it does not already exits.
123       QDir syncDir(dlg->destination().path());
124 
125       if (!syncDir.exists())
126       {
127         if (!QDir().mkpath(syncDir.path()))
128         {
129           Smb4KNotification::mkdirFailed(syncDir);
130           emitResult();
131           return;
132         }
133       }
134 
135       // Make sure that we have got the trailing slash present.
136       // rsync is very picky regarding it.
137       m_src = dlg->source();
138       m_dest = dlg->destination();
139 
140       delete dlg;
141     }
142     else
143     {
144       delete dlg;
145       emitResult();
146       return;
147     }
148   }
149   else
150   {
151     emitResult();
152     return;
153   }
154 
155   //
156   // The command
157   //
158   QStringList command;
159   command << rsync;
160   command << "--progress";
161 
162   //
163   // Basic settings
164   //
165   if (Smb4KSettings::archiveMode())
166   {
167     command << "--archive";
168   }
169   else
170   {
171     if (Smb4KSettings::recurseIntoDirectories())
172     {
173       command << "--recursive";
174     }
175 
176     if (Smb4KSettings::preserveSymlinks())
177     {
178       command << "--links";
179     }
180 
181     if (Smb4KSettings::preservePermissions())
182     {
183       command << "--perms";
184     }
185 
186     if (Smb4KSettings::preserveTimes())
187     {
188       command << "--times";
189     }
190 
191     if (Smb4KSettings::preserveGroup())
192     {
193       command << "--group";
194     }
195 
196     if (Smb4KSettings::preserveOwner())
197     {
198       command << "--owner";
199     }
200 
201     if (Smb4KSettings::preserveDevicesAndSpecials())
202     {
203       // Alias -D
204       command << "--devices";
205       command << "--specials";
206     }
207   }
208 
209   if (Smb4KSettings::relativePathNames())
210   {
211     command << "--relative";
212   }
213 
214   if (Smb4KSettings::noImpliedDirectories())
215   {
216     command << "--no-implied-dirs";
217   }
218 
219   if (Smb4KSettings::transferDirectories())
220   {
221     command << "--dirs";
222   }
223 
224   if (Smb4KSettings::makeBackups())
225   {
226     command << "--backup";
227 
228     if (Smb4KSettings::useBackupDirectory())
229     {
230       command << QString("--backup-dir=%1").arg(Smb4KSettings::backupDirectory().path());
231     }
232 
233     if (Smb4KSettings::useBackupSuffix())
234     {
235       command << QString("--suffix=%1").arg(Smb4KSettings::backupSuffix());
236     }
237   }
238 
239   //
240   // File handling
241   //
242   if (Smb4KSettings::updateTarget())
243   {
244     command << "--update";
245   }
246 
247   if (Smb4KSettings::updateInPlace())
248   {
249     command << "--inplace";
250   }
251 
252   if (Smb4KSettings::efficientSparseFileHandling())
253   {
254     command << "--sparse";
255   }
256 
257   if (Smb4KSettings::copyFilesWhole())
258   {
259     command << "--whole-file";
260   }
261 
262   if (Smb4KSettings::updateExisting())
263   {
264     command << "--existing";
265   }
266 
267   if (Smb4KSettings::ignoreExisting())
268   {
269     command << "--ignore-existing";
270   }
271 
272   if (Smb4KSettings::transformSymlinks())
273   {
274     command << "--copy-links";
275   }
276 
277   if (Smb4KSettings::transformUnsafeSymlinks())
278   {
279     command << "--copy-unsafe-links";
280   }
281 
282   if (Smb4KSettings::ignoreUnsafeSymlinks())
283   {
284     command << "--safe-links";
285   }
286 
287   if (Smb4KSettings::mungeSymlinks())
288   {
289     command << "--munge-links";
290   }
291 
292   if (Smb4KSettings::preserveHardLinks())
293   {
294     command << "--hard-links";
295   }
296 
297   if (Smb4KSettings::copyDirectorySymlinks())
298   {
299     command << "--copy-dirlinks";
300   }
301 
302   if (Smb4KSettings::keepDirectorySymlinks())
303   {
304     command << "--keep-dirlinks";
305   }
306 
307   if (Smb4KSettings::omitDirectoryTimes())
308   {
309     command << "--omit-dir-times";
310   }
311 
312   //
313   // File transfer
314   //
315   if (Smb4KSettings::compressData())
316   {
317     command << "--compress";
318 
319     if (Smb4KSettings::useCompressionLevel())
320     {
321       command << QString("--compress-level=%1").arg(Smb4KSettings::compressionLevel());
322     }
323 
324     if (Smb4KSettings::useSkipCompression())
325     {
326       command << QString("--skip-compress=%1").arg(Smb4KSettings::skipCompression());
327     }
328   }
329 
330   if (Smb4KSettings::self()->useMaximalTransferSize())
331   {
332     command << QString("--max-size=%1K").arg(Smb4KSettings::self()->maximalTransferSize());
333   }
334 
335   if (Smb4KSettings::self()->useMinimalTransferSize())
336   {
337     command << QString("--min-size=%1K").arg(Smb4KSettings::self()->minimalTransferSize());
338   }
339 
340   if (Smb4KSettings::keepPartial())
341   {
342     command << " --partial";
343 
344     if (Smb4KSettings::usePartialDirectory())
345     {
346       command << QString("--partial-dir=%1").arg(Smb4KSettings::partialDirectory().path());
347     }
348   }
349 
350   if (Smb4KSettings::useBandwidthLimit())
351   {
352     command << QString("--bwlimit=%1K").arg(Smb4KSettings::bandwidthLimit());
353   }
354 
355   //
356   // File deletion
357   //
358   if (Smb4KSettings::removeSourceFiles())
359   {
360     command << "--remove-source-files";
361   }
362 
363   if (Smb4KSettings::deleteExtraneous())
364   {
365     command << "--delete";
366   }
367 
368   if (Smb4KSettings::deleteBefore())
369   {
370     command << "--delete-before";
371   }
372 
373   if (Smb4KSettings::deleteDuring())
374   {
375     command << "--delete-during";
376   }
377 
378   if (Smb4KSettings::deleteAfter())
379   {
380     command << "--delete-after";
381   }
382 
383   if (Smb4KSettings::deleteExcluded())
384   {
385     command << "--delete-excluded";
386   }
387 
388   if (Smb4KSettings::ignoreErrors())
389   {
390     command << "--ignore-errors";
391   }
392 
393   if (Smb4KSettings::forceDirectoryDeletion())
394   {
395     command << "--force";
396   }
397 
398   if (Smb4KSettings::useMaximumDelete())
399   {
400     command << QString("--max-delete=%1").arg(Smb4KSettings::maximumDeleteValue());
401   }
402 
403   //
404   // Filtering
405   //
406   if (Smb4KSettings::useCVSExclude())
407   {
408     command << "--cvs-exclude";
409   }
410 
411   if (Smb4KSettings::useExcludePattern())
412   {
413     command << QString("--exclude=%1").arg(Smb4KSettings::excludePattern());
414   }
415 
416   if (Smb4KSettings::useExcludeFrom())
417   {
418     command << QString("--exclude-from=%1").arg(Smb4KSettings::excludeFrom().path());
419   }
420 
421   if (Smb4KSettings::useIncludePattern())
422   {
423     command << QString("--include=%1").arg(Smb4KSettings::includePattern());
424   }
425 
426   if (Smb4KSettings::useIncludeFrom())
427   {
428     command << QString("--include-from=%1").arg(Smb4KSettings::includeFrom().path());
429   }
430 
431   if (!Smb4KSettings::customFilteringRules().isEmpty())
432   {
433     qDebug() << "Do we need to spilt the filtering rules into a list?";
434     command << Smb4KSettings::customFilteringRules();
435   }
436 
437   if (Smb4KSettings::useFFilterRule())
438   {
439     command << "-F";
440   }
441 
442   if (Smb4KSettings::useFFFilterRule())
443   {
444     command << "-F";
445     command << "-F";
446   }
447 
448   //
449   // Miscellaneous
450   //
451   if (Smb4KSettings::useBlockSize())
452   {
453     command << QString("--block-size=%1").arg(Smb4KSettings::blockSize());
454   }
455 
456   if (Smb4KSettings::useChecksumSeed())
457   {
458     command << QString("--checksum-seed=%1").arg(Smb4KSettings::checksumSeed());
459   }
460 
461   if (Smb4KSettings::useChecksum())
462   {
463     command << "--checksum";
464   }
465 
466   if (Smb4KSettings::oneFileSystem())
467   {
468     command << "--one-file-system";
469   }
470 
471   if (Smb4KSettings::delayUpdates())
472   {
473     command << "--delay-updates";
474   }
475 
476   // Make sure that the trailing slash is present. rsync is very
477   // picky regarding it.
478   QString source = m_src.path() + (!m_src.path().endsWith('/') ? "/" : "");
479   QString destination = m_dest.path() + (!m_dest.path().endsWith('/') ? "/" : "");
480 
481   command << source;
482   command << destination;
483 
484   //
485   // The job tracker
486   //
487   m_job_tracker->registerJob(this);
488   connect(this, SIGNAL(result(KJob*)), m_job_tracker, SLOT(unregisterJob(KJob*)));
489 
490   //
491   // The process
492   //
493   m_process = new KProcess(this);
494   m_process->setEnv("LANG", "en_US.UTF-8");
495   m_process->setOutputChannelMode(KProcess::SeparateChannels);
496   m_process->setProgram(command);
497 
498   connect(m_process, SIGNAL(readyReadStandardOutput()), SLOT(slotReadStandardOutput()));
499   connect(m_process, SIGNAL(readyReadStandardError()),  SLOT(slotReadStandardError()));
500   connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotProcessFinished(int,QProcess::ExitStatus)));
501 
502   // Start the synchronization process
503   emit aboutToStart(m_dest.path());
504 
505   // Send description to the GUI
506   emit description(this, i18n("Synchronizing"),
507                    qMakePair(i18n("Source"), source),
508                    qMakePair(i18n("Destination"), destination));
509 
510   // Dummy to show 0 %
511   emitPercent(0, 100);
512 
513   m_process->start();
514 }
515 
516 
slotReadStandardOutput()517 void Smb4KSyncJob::slotReadStandardOutput()
518 {
519   QStringList stdOut = QString::fromUtf8(m_process->readAllStandardOutput(), -1).split('\n', QString::SkipEmptyParts);
520 
521   for (int i = 0; i < stdOut.size(); ++i)
522   {
523     if (stdOut.at(i)[0].isSpace())
524     {
525       // Get the overall transfer progress
526       if (stdOut.at(i).contains(" to-check="))
527       {
528         QString tmp = stdOut.at(i).section(" to-check=", 1, 1).section(')', 0, 0).trimmed();
529 
530         bool success1 = true;
531         bool success2 = true;
532 
533         qulonglong files = tmp.section('/', 0, 0).trimmed().toLongLong(&success1);
534         qulonglong total = tmp.section('/', 1, 1).trimmed().toLongLong(&success2);
535 
536         if (success1 && success2)
537         {
538           setProcessedAmount(KJob::Files, total - files);
539           setTotalAmount(KJob::Files, total);
540           emitPercent(total - files, total);
541         }
542       }
543       else if (stdOut.at(i).contains(" to-chk="))
544       {
545         // Make Smb4K work with rsync >= 3.1.
546         QString tmp = stdOut.at(i).section(" to-chk=", 1, 1).section(')', 0, 0).trimmed();
547 
548         bool success1 = true;
549         bool success2 = true;
550 
551         qulonglong files = tmp.section('/', 0, 0).trimmed().toLongLong(&success1);
552         qulonglong total = tmp.section('/', 1, 1).trimmed().toLongLong(&success2);
553 
554         if (success1 && success2)
555         {
556           setProcessedAmount(KJob::Files, total - files);
557           setTotalAmount(KJob::Files, total);
558           emitPercent(total - files, total);
559         }
560       }
561       else if (stdOut.at(i).contains(" ir-chk="))
562       {
563         // Make Smb4K work with rsync >= 3.1.
564         QString tmp = stdOut.at(i).section(" ir-chk=", 1, 1).section(')', 0, 0).trimmed();
565 
566         bool success1 = true;
567         bool success2 = true;
568 
569         qulonglong files = tmp.section('/', 0, 0).trimmed().toLongLong(&success1);
570         qulonglong total = tmp.section('/', 1, 1).trimmed().toLongLong(&success2);
571 
572         if (success1 && success2)
573         {
574           setProcessedAmount(KJob::Files, total - files);
575           setTotalAmount(KJob::Files, total);
576           emitPercent(total - files, total);
577         }
578       }
579 
580       // Get transfer rate
581       if (stdOut.at(i).contains("/s ", Qt::CaseSensitive))
582       {
583         bool success = true;
584 
585         double tmp_speed = stdOut.at(i).section(QRegExp("../s"), 0, 0).section(' ', -1 -1).trimmed().toDouble(&success);
586 
587         if (success)
588         {
589           // MB == 1000000 B and kB == 1000 B per definition!
590           if (stdOut.at(i).contains("MB/s"))
591           {
592             tmp_speed *= 1e6;
593           }
594           else if (stdOut.at(i).contains("kB/s"))
595           {
596             tmp_speed *= 1e3;
597           }
598 
599           ulong speed = (ulong)tmp_speed;
600           emitSpeed(speed /* B/s */);
601         }
602       }
603     }
604     else if (!stdOut.at(i).contains("sending incremental file list"))
605     {
606       QString file = stdOut.at(i).trimmed();
607 
608       QUrl src_url = m_src;
609       src_url.setPath(QDir::cleanPath(src_url.path() + '/' + file));
610 
611       QUrl dest_url = m_dest;
612       dest_url.setPath(QDir::cleanPath(dest_url.path() + '/' + file));
613 
614       // Send description to the GUI
615       emit description(this, i18n("Synchronizing"),
616                        qMakePair(i18n("Source"), src_url.path()),
617                        qMakePair(i18n("Destination"), dest_url.path()));
618     }
619   }
620 }
621 
622 
slotReadStandardError()623 void Smb4KSyncJob::slotReadStandardError()
624 {
625   //
626   // Get the error message
627   //
628   QString stdErr = QString::fromUtf8(m_process->readAllStandardError(), -1).trimmed();
629 
630   //
631   // Make sure the process is terminated
632   //
633   if (m_process->state() != KProcess::NotRunning)
634   {
635     m_process->terminate();
636   }
637 
638   //
639   // Report an error if the process was not terminated
640   //
641   if (!(stdErr.contains("rsync error") && stdErr.contains("(code 20)")))
642   {
643     Smb4KNotification::synchronizationFailed(m_src, m_dest, stdErr);
644   }
645 }
646 
647 
slotProcessFinished(int,QProcess::ExitStatus status)648 void Smb4KSyncJob::slotProcessFinished(int, QProcess::ExitStatus status)
649 {
650   // Dummy to show 100 %
651   emitPercent(100, 100);
652 
653   // Handle error.
654   switch (status)
655   {
656     case QProcess::CrashExit:
657     {
658       Smb4KNotification::processError(m_process->error());
659       break;
660     }
661     default:
662     {
663       break;
664     }
665   }
666 
667   // Finish job
668   emitResult();
669   emit finished(m_dest.path());
670 }
671 
672 
673 
Smb4KSynchronizationDialog(const SharePtr & share,QWidget * parent)674 Smb4KSynchronizationDialog::Smb4KSynchronizationDialog(const SharePtr &share, QWidget *parent)
675 : QDialog(parent), m_share(share)
676 {
677   setWindowTitle(i18n("Synchronization"));
678 
679   QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal, this);
680   m_swap_button = buttonBox->addButton(i18n("Swap Paths"), QDialogButtonBox::ActionRole);
681   m_swap_button->setToolTip(i18n("Swap source and destination"));
682   m_synchronize_button = buttonBox->addButton(i18n("Synchronize"), QDialogButtonBox::ActionRole);
683   m_synchronize_button->setToolTip(i18n("Synchronize the destination with the source"));
684   m_cancel_button = buttonBox->addButton(QDialogButtonBox::Cancel);
685 
686   m_cancel_button->setShortcut(Qt::Key_Escape);
687 
688   m_synchronize_button->setDefault(true);
689 
690   QGridLayout *layout = new QGridLayout(this);
691 
692   QLabel *pixmap = new QLabel(this);
693   QPixmap sync_pix = KDE::icon("folder-sync").pixmap(KIconLoader::SizeHuge);
694   pixmap->setPixmap(sync_pix);
695   pixmap->setAlignment(Qt::AlignBottom);
696 
697   QLabel *description = new QLabel(i18n("Please provide the source and destination "
698                                         "directory for the synchronization."), this);
699   description->setWordWrap(true);
700   description->setAlignment(Qt::AlignBottom);
701 
702   QUrl src_url  = QUrl(QDir::cleanPath(m_share->path()));
703   QUrl dest_url = QUrl(QDir::cleanPath(QString("%1/%2/%3").arg(Smb4KSettings::rsyncPrefix().path(), m_share->hostName(), m_share->shareName())));
704 
705   QLabel *source_label = new QLabel(i18n("Source:"), this);
706   m_source = new KUrlRequester(this);
707   m_source->setUrl(src_url);
708   m_source->setMode(KFile::Directory | KFile::LocalOnly);
709   m_source->lineEdit()->setSqueezedTextEnabled(true);
710   m_source->completionObject()->setCompletionMode(KCompletion::CompletionPopupAuto);
711   m_source->completionObject()->setMode(KUrlCompletion::FileCompletion);
712   m_source->setWhatsThis(i18n("This is the source directory. The data that it contains is to be written "
713     "to the destination directory."));
714 
715   QLabel *destination_label = new QLabel(i18n("Destination:"), this);
716   m_destination = new KUrlRequester(this);
717   m_destination->setUrl(dest_url);
718   m_destination->setMode(KFile::Directory | KFile::LocalOnly);
719   m_destination->lineEdit()->setSqueezedTextEnabled(true);
720   m_destination->completionObject()->setCompletionMode(KCompletion::CompletionPopupAuto);
721   m_destination->completionObject()->setMode(KUrlCompletion::FileCompletion);
722   m_destination->setWhatsThis(i18n("This is the destination directory. It will be updated with the data "
723     "from the source directory."));
724 
725   layout->addWidget(pixmap, 0, 0);
726   layout->addWidget(description, 0, 1, Qt::AlignBottom);
727   layout->addWidget(source_label, 1, 0);
728   layout->addWidget(m_source, 1, 1);
729   layout->addWidget(destination_label, 2, 0);
730   layout->addWidget(m_destination, 2, 1);
731   layout->addWidget(buttonBox, 3, 0, 1, 2);
732 
733   //
734   // Connections
735   //
736   connect(m_cancel_button, SIGNAL(clicked()), SLOT(slotCancelClicked()));
737   connect(m_synchronize_button, SIGNAL(clicked()), SLOT(slotSynchronizeClicked()));
738   connect(m_swap_button, SIGNAL(clicked()), SLOT(slotSwapPathsClicked()));
739 
740   //
741   // Set the dialog size
742   //
743   create();
744 
745   KConfigGroup group(Smb4KSettings::self()->config(), "SynchronizationDialog");
746   QSize dialogSize;
747 
748   if (group.exists())
749   {
750     KWindowConfig::restoreWindowSize(windowHandle(), group);
751     dialogSize = windowHandle()->size();
752   }
753   else
754   {
755     dialogSize = sizeHint();
756   }
757 
758   resize(dialogSize); // workaround for QTBUG-40584
759 }
760 
761 
~Smb4KSynchronizationDialog()762 Smb4KSynchronizationDialog::~Smb4KSynchronizationDialog()
763 {
764 }
765 
766 
source()767 const QUrl Smb4KSynchronizationDialog::source()
768 {
769   return m_source->url();
770 }
771 
772 
destination()773 const QUrl Smb4KSynchronizationDialog::destination()
774 {
775   return m_destination->url();
776 }
777 
778 
779 /////////////////////////////////////////////////////////////////////////////
780 //   SLOT IMPLEMENTATIONS
781 /////////////////////////////////////////////////////////////////////////////
782 
783 
slotCancelClicked()784 void Smb4KSynchronizationDialog::slotCancelClicked()
785 {
786   reject();
787 }
788 
789 
slotSynchronizeClicked()790 void Smb4KSynchronizationDialog::slotSynchronizeClicked()
791 {
792   KConfigGroup group(Smb4KSettings::self()->config(), "SynchronizationDialog");
793   KWindowConfig::saveWindowSize(windowHandle(), group);
794   accept();
795 }
796 
797 
slotSwapPathsClicked()798 void Smb4KSynchronizationDialog::slotSwapPathsClicked()
799 {
800   // Swap URLs.
801   QString sourceURL = m_source->url().path();
802   QString destinationURL = m_destination->url().path();
803 
804   m_source->setUrl(QUrl(destinationURL));
805   m_destination->setUrl(QUrl(sourceURL));
806 }
807 
808