File indexing completed on 2024-04-21 05:01:40

0001 /*
0002     This file contains private helper classes for the Smb4KSynchronizer
0003     class.
0004 
0005     SPDX-FileCopyrightText: 2008-2023 Alexander Reinholdt <alexander.reinholdt@kdemail.net>
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 // application specific includes
0010 #include "smb4ksynchronizer_p.h"
0011 #include "smb4kglobal.h"
0012 #include "smb4knotification.h"
0013 #include "smb4ksettings.h"
0014 
0015 // Qt includes
0016 #include <QLocale>
0017 #include <QRegularExpression>
0018 #include <QStandardPaths>
0019 #include <QTimer>
0020 
0021 // KDE includes
0022 #include <KLocalizedString>
0023 
0024 using namespace Smb4KGlobal;
0025 
0026 Smb4KSyncJob::Smb4KSyncJob(QObject *parent)
0027     : KJob(parent)
0028     , m_process(nullptr)
0029 {
0030     setCapabilities(KJob::Killable);
0031 
0032     m_terminated = false;
0033     m_jobTracker = new KUiServerJobTracker(this);
0034 }
0035 
0036 Smb4KSyncJob::~Smb4KSyncJob()
0037 {
0038 }
0039 
0040 void Smb4KSyncJob::start()
0041 {
0042     QTimer::singleShot(0, this, SLOT(slotStartSynchronization()));
0043 }
0044 
0045 void Smb4KSyncJob::setupSynchronization(const QUrl &sourceUrl, const QUrl &destinationUrl)
0046 {
0047     if (sourceUrl.isValid() && !sourceUrl.isEmpty() && destinationUrl.isValid() && !destinationUrl.isEmpty()) {
0048         m_sourceUrl = sourceUrl;
0049         m_destinationUrl = destinationUrl;
0050     }
0051 }
0052 
0053 bool Smb4KSyncJob::doKill()
0054 {
0055     if (m_process && m_process->state() != KProcess::NotRunning) {
0056         m_process->terminate();
0057         m_terminated = true;
0058     }
0059 
0060     return KJob::doKill();
0061 }
0062 
0063 void Smb4KSyncJob::slotStartSynchronization()
0064 {
0065     if (m_sourceUrl.isEmpty() || m_destinationUrl.isEmpty()) {
0066         emitResult();
0067         return;
0068     }
0069 
0070     QString rsync = QStandardPaths::findExecutable(QStringLiteral("rsync"));
0071 
0072     if (rsync.isEmpty()) {
0073         Smb4KNotification::commandNotFound(QStringLiteral("rsync"));
0074         emitResult();
0075         return;
0076     }
0077 
0078     QDir destinationDirectory(m_destinationUrl.path());
0079 
0080     if (!destinationDirectory.exists()) {
0081         if (!QDir().mkpath(destinationDirectory.path())) {
0082             Smb4KNotification::mkdirFailed(destinationDirectory);
0083             emitResult();
0084             return;
0085         }
0086     }
0087 
0088     //
0089     // The command
0090     //
0091     QStringList command;
0092     command << rsync;
0093     command << QStringLiteral("--progress");
0094     command << QStringLiteral("--info=progress2");
0095 
0096     //
0097     // Basic settings
0098     //
0099     if (Smb4KSettings::archiveMode()) {
0100         command << QStringLiteral("--archive");
0101     }
0102 
0103     if (Smb4KSettings::recurseIntoDirectories()) {
0104         command << QStringLiteral("--recursive");
0105     }
0106 
0107     if (Smb4KSettings::relativePathNames()) {
0108         command << QStringLiteral("--relative");
0109     }
0110 
0111     if (Smb4KSettings::noImpliedDirectories()) {
0112         command << QStringLiteral("--no-implied-dirs");
0113     }
0114 
0115     if (Smb4KSettings::transferDirectories()) {
0116         command << QStringLiteral("--dirs");
0117     }
0118 
0119     if (Smb4KSettings::makeBackups()) {
0120         command << QStringLiteral("--backup");
0121 
0122         if (Smb4KSettings::useBackupDirectory()) {
0123             command << QStringLiteral("--backup-dir=") + Smb4KSettings::backupDirectory().path();
0124         }
0125 
0126         if (Smb4KSettings::useBackupSuffix()) {
0127             command << QStringLiteral("--suffix=") + Smb4KSettings::backupSuffix();
0128         }
0129     }
0130 
0131     //
0132     // File handling
0133     //
0134     if (Smb4KSettings::updateTarget()) {
0135         command << QStringLiteral("--update");
0136     }
0137 
0138     if (Smb4KSettings::updateInPlace()) {
0139         command << QStringLiteral("--inplace");
0140     }
0141 
0142     if (Smb4KSettings::efficientSparseFileHandling()) {
0143         command << QStringLiteral("--sparse");
0144     }
0145 
0146     if (Smb4KSettings::copyFilesWhole()) {
0147         command << QStringLiteral("--whole-file");
0148     }
0149 
0150     if (Smb4KSettings::updateExisting()) {
0151         command << QStringLiteral("--existing");
0152     }
0153 
0154     if (Smb4KSettings::ignoreExisting()) {
0155         command << QStringLiteral("--ignore-existing");
0156     }
0157 
0158     if (Smb4KSettings::preserveSymlinks()) {
0159         command << QStringLiteral("--links");
0160     }
0161 
0162     if (Smb4KSettings::transformSymlinks()) {
0163         command << QStringLiteral("--copy-links");
0164     }
0165 
0166     if (Smb4KSettings::transformUnsafeSymlinks()) {
0167         command << QStringLiteral("--copy-unsafe-links");
0168     }
0169 
0170     if (Smb4KSettings::ignoreUnsafeSymlinks()) {
0171         command << QStringLiteral("--safe-links");
0172     }
0173 
0174     if (Smb4KSettings::mungeSymlinks()) {
0175         command << QStringLiteral("--munge-links");
0176     }
0177 
0178     if (Smb4KSettings::preserveHardLinks()) {
0179         command << QStringLiteral("--hard-links");
0180     }
0181 
0182     if (Smb4KSettings::copyDirectorySymlinks()) {
0183         command << QStringLiteral("--copy-dirlinks");
0184     }
0185 
0186     if (Smb4KSettings::keepDirectorySymlinks()) {
0187         command << QStringLiteral("--keep-dirlinks");
0188     }
0189 
0190     if (Smb4KSettings::preservePermissions()) {
0191         command << QStringLiteral("--perms");
0192     }
0193 
0194     if (Smb4KSettings::preserveACLs()) {
0195         command << QStringLiteral("--acls");
0196     }
0197 
0198     if (Smb4KSettings::preserveExtendedAttributes()) {
0199         command << QStringLiteral("--xattrs");
0200     }
0201 
0202     if (Smb4KSettings::preserveAccessTimes()) {
0203         command << QStringLiteral("--atimes");
0204     }
0205 
0206     if (Smb4KSettings::preserveCreateTimes()) {
0207         command << QStringLiteral("--crtimes");
0208     }
0209 
0210     if (Smb4KSettings::preserveOwner()) {
0211         command << QStringLiteral("--owner");
0212     }
0213 
0214     if (Smb4KSettings::preserveGroup()) {
0215         command << QStringLiteral("--group");
0216     }
0217 
0218     if (Smb4KSettings::preserveDevicesAndSpecials()) {
0219         // Alias -D
0220         command << QStringLiteral("--devices");
0221         command << QStringLiteral("--specials");
0222     }
0223 
0224     if (Smb4KSettings::preserveTimes()) {
0225         command << QStringLiteral("--times");
0226     }
0227 
0228     if (Smb4KSettings::omitDirectoryTimes()) {
0229         command << QStringLiteral("--omit-dir-times");
0230     }
0231 
0232     //
0233     // File transfer
0234     //
0235     if (Smb4KSettings::compressData()) {
0236         command << QStringLiteral("--compress");
0237 
0238         if (Smb4KSettings::useCompressionLevel()) {
0239             command << QStringLiteral("--compress-level=") + QString::number(Smb4KSettings::compressionLevel());
0240         }
0241 
0242         if (Smb4KSettings::useSkipCompression()) {
0243             command << QStringLiteral("--skip-compress=") + Smb4KSettings::skipCompression();
0244         }
0245     }
0246 
0247     if (Smb4KSettings::self()->useMaximalTransferSize()) {
0248         command << QStringLiteral("--max-size=") + QString::number(Smb4KSettings::self()->maximalTransferSize()) + QStringLiteral("kB");
0249     }
0250 
0251     if (Smb4KSettings::self()->useMinimalTransferSize()) {
0252         command << QStringLiteral("--min-size=") + QString::number(Smb4KSettings::self()->minimalTransferSize()) + QStringLiteral("kB");
0253     }
0254 
0255     if (Smb4KSettings::keepPartial()) {
0256         command << QStringLiteral(" --partial");
0257 
0258         if (Smb4KSettings::usePartialDirectory()) {
0259             command << QStringLiteral("--partial-dir=") + Smb4KSettings::partialDirectory().path();
0260         }
0261     }
0262 
0263     if (Smb4KSettings::useBandwidthLimit()) {
0264         command << QStringLiteral("--bwlimit=") + QString::number(Smb4KSettings::bandwidthLimit()) + QStringLiteral("kB");
0265     }
0266 
0267     //
0268     // File deletion
0269     //
0270     if (Smb4KSettings::removeSourceFiles()) {
0271         command << QStringLiteral("--remove-source-files");
0272     }
0273 
0274     if (Smb4KSettings::deleteExtraneous()) {
0275         command << QStringLiteral("--delete");
0276     }
0277 
0278     if (Smb4KSettings::deleteBefore()) {
0279         command << QStringLiteral("--delete-before");
0280     }
0281 
0282     if (Smb4KSettings::deleteDuring()) {
0283         command << QStringLiteral("--delete-during");
0284     }
0285 
0286     if (Smb4KSettings::deleteAfter()) {
0287         command << QStringLiteral("--delete-after");
0288     }
0289 
0290     if (Smb4KSettings::deleteExcluded()) {
0291         command << QStringLiteral("--delete-excluded");
0292     }
0293 
0294     if (Smb4KSettings::ignoreErrors()) {
0295         command << QStringLiteral("--ignore-errors");
0296     }
0297 
0298     if (Smb4KSettings::forceDirectoryDeletion()) {
0299         command << QStringLiteral("--force");
0300     }
0301 
0302     if (Smb4KSettings::useMaximumDelete()) {
0303         command << QStringLiteral("--max-delete=") + QString::number(Smb4KSettings::maximumDeleteValue());
0304     }
0305 
0306     //
0307     // Filtering
0308     //
0309     if (Smb4KSettings::useCVSExclude()) {
0310         command << QStringLiteral("--cvs-exclude");
0311     }
0312 
0313     if (Smb4KSettings::useExcludePattern()) {
0314         command << QStringLiteral("--exclude=") + Smb4KSettings::excludePattern();
0315     }
0316 
0317     if (Smb4KSettings::useExcludeFrom()) {
0318         command << QStringLiteral("--exclude-from=") + Smb4KSettings::excludeFrom().path();
0319     }
0320 
0321     if (Smb4KSettings::useIncludePattern()) {
0322         command << QStringLiteral("--include=") + Smb4KSettings::includePattern();
0323     }
0324 
0325     if (Smb4KSettings::useIncludeFrom()) {
0326         command << QStringLiteral("--include-from=") + Smb4KSettings::includeFrom().path();
0327     }
0328 
0329     if (Smb4KSettings::useCustomFilteringRules()) {
0330         if (!Smb4KSettings::customFilteringRules().isEmpty()) {
0331             qDebug() << "Do we need to spilt the filtering rules into a list?";
0332             command << Smb4KSettings::customFilteringRules();
0333         }
0334     }
0335 
0336     if (Smb4KSettings::useFFilterRule()) {
0337         command << QStringLiteral("-F");
0338     }
0339 
0340     if (Smb4KSettings::useFFFilterRule()) {
0341         command << QStringLiteral("-F");
0342         command << QStringLiteral("-F");
0343     }
0344 
0345     //
0346     // Miscellaneous
0347     //
0348     if (Smb4KSettings::useBlockSize()) {
0349         command << QStringLiteral("--block-size=") + QString::number(Smb4KSettings::blockSize());
0350     }
0351 
0352     if (Smb4KSettings::useChecksumSeed()) {
0353         command << QStringLiteral("--checksum-seed=") + QString::number(Smb4KSettings::checksumSeed());
0354     }
0355 
0356     if (Smb4KSettings::useChecksum()) {
0357         command << QStringLiteral("--checksum");
0358     }
0359 
0360     if (Smb4KSettings::oneFileSystem()) {
0361         command << QStringLiteral("--one-file-system");
0362     }
0363 
0364     if (Smb4KSettings::delayUpdates()) {
0365         command << QStringLiteral("--delay-updates");
0366     }
0367 
0368     // Make sure that the trailing slash is present. rsync is very
0369     // picky regarding it.
0370     QString source = m_sourceUrl.path() + (!m_sourceUrl.path().endsWith(QStringLiteral("/")) ? QStringLiteral("/") : QStringLiteral(""));
0371     QString destination = m_destinationUrl.path() + (!m_destinationUrl.path().endsWith(QStringLiteral("/")) ? QStringLiteral("/") : QStringLiteral(""));
0372 
0373     command << source;
0374     command << destination;
0375 
0376     //
0377     // The job tracker
0378     //
0379     m_jobTracker->registerJob(this);
0380     connect(this, &Smb4KSyncJob::result, m_jobTracker, &KUiServerJobTracker::unregisterJob);
0381 
0382     //
0383     // The process
0384     //
0385     m_process = new KProcess(this);
0386     m_process->setOutputChannelMode(KProcess::SeparateChannels);
0387     m_process->setProgram(command);
0388 
0389     connect(m_process, SIGNAL(readyReadStandardOutput()), SLOT(slotReadStandardOutput()));
0390     connect(m_process, SIGNAL(readyReadStandardError()), SLOT(slotReadStandardError()));
0391     connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(slotProcessFinished(int, QProcess::ExitStatus)));
0392 
0393     // Start the synchronization process
0394     Q_EMIT aboutToStart(m_destinationUrl.path());
0395 
0396     // Send description to the GUI
0397     Q_EMIT description(this, i18n("Synchronizing"), qMakePair(i18n("Source"), source), qMakePair(i18n("Destination"), destination));
0398 
0399     // Dummy to show 0 %
0400     emitPercent(0, 100);
0401 
0402     m_terminated = false;
0403     m_process->start();
0404 }
0405 
0406 void Smb4KSyncJob::slotReadStandardOutput()
0407 {
0408     QStringList stdOut = QString::fromUtf8(m_process->readAllStandardOutput()).split(QStringLiteral("\r"), Qt::SkipEmptyParts);
0409 
0410     for (const QString &line : stdOut) {
0411         if (line.contains(QStringLiteral("%"))) {
0412             bool success = true;
0413             QString transferInfo = line.trimmed().simplified();
0414 
0415             // Overall progress
0416             QString progressString = transferInfo.section(QStringLiteral(" "), 1, 1).replace(QStringLiteral("%"), QStringLiteral(""));
0417 
0418             if (!progressString.isEmpty()) {
0419                 qulonglong progress = progressString.toLongLong(&success);
0420 
0421                 if (success) {
0422                     setPercent(progress);
0423                 }
0424             }
0425 
0426             // Speed
0427             QRegularExpression expression(QStringLiteral("../s"));
0428             QString speedString = transferInfo.section(QStringLiteral(" "), 2, 2).section(expression, 0, 0);
0429 
0430             if (!speedString.isEmpty()) {
0431                 QLocale locale;
0432                 double speed = locale.toDouble(speedString, &success);
0433 
0434                 if (success) {
0435                     // MB == 1000000 B and kB == 1000 B per definition!
0436                     if (transferInfo.contains(QStringLiteral("MB/s"))) {
0437                         speed *= 1e6;
0438                     } else if (transferInfo.contains(QStringLiteral("kB/s"))) {
0439                         speed *= 1e3;
0440                     }
0441 
0442                     emitSpeed((ulong)speed);
0443                 }
0444             }
0445 
0446             // Transfered files
0447             QString transferedFilesString = transferInfo.section(QStringLiteral("xfr#"), 1, 1).section(QStringLiteral(","), 0, 0);
0448 
0449             if (!transferedFilesString.isEmpty()) {
0450                 qulonglong transferedFiles = transferedFilesString.toULongLong(&success);
0451 
0452                 if (success) {
0453                     setProcessedAmount(KJob::Files, transferedFiles);
0454                 }
0455             }
0456 
0457             // Total amount of files
0458             QString totalFilesString = transferInfo.section(QStringLiteral("/"), -1, -1).section(QStringLiteral(")"), 0, 0);
0459 
0460             if (!totalFilesString.isEmpty()) {
0461                 qulonglong totalFiles = totalFilesString.toULongLong(&success);
0462 
0463                 if (success) {
0464                     setTotalAmount(KJob::Files, totalFiles);
0465                 }
0466             }
0467 
0468         } else if (!line.contains(QStringLiteral("sending incremental file list"))) {
0469             QString relativePath = line.trimmed().simplified();
0470 
0471             QUrl sourceUrl = m_sourceUrl;
0472             sourceUrl.setPath(QDir::cleanPath(sourceUrl.path() + QStringLiteral("/") + relativePath));
0473 
0474             QUrl destinationUrl = m_destinationUrl;
0475             destinationUrl.setPath(QDir::cleanPath(destinationUrl.path() + QStringLiteral("/") + relativePath));
0476 
0477             // Send description to the GUI
0478             Q_EMIT description(this, i18n("Synchronizing"), qMakePair(i18n("Source"), sourceUrl.path()), qMakePair(i18n("Destination"), destinationUrl.path()));
0479         }
0480     }
0481 }
0482 
0483 void Smb4KSyncJob::slotReadStandardError()
0484 {
0485     if (!m_terminated) {
0486         QString stdErr = QString::fromUtf8(m_process->readAllStandardError()).trimmed();
0487         Smb4KNotification::synchronizationFailed(m_sourceUrl, m_destinationUrl, stdErr);
0488     }
0489 }
0490 
0491 void Smb4KSyncJob::slotProcessFinished(int, QProcess::ExitStatus status)
0492 {
0493     // Dummy to show 100 %
0494     emitPercent(100, 100);
0495 
0496     // Handle error.
0497     switch (status) {
0498     case QProcess::CrashExit: {
0499         Smb4KNotification::processError(m_process->error());
0500         break;
0501     }
0502     default: {
0503         break;
0504     }
0505     }
0506 
0507     // Finish job
0508     emitResult();
0509     Q_EMIT finished(m_destinationUrl.path());
0510 }