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